В PHP <6, каков наилучший способ разбить строку на массив символов Unicode? Если вход не обязательно UTF-8?
Я хочу знать, является ли набор символов Unicode во входной строке подмножеством другого набора символов Unicode.
Почему бы не сбежать прямо к семейству функций mb_
, так как в первой паре ответов не было?
Вы можете использовать модификатор 'u' с регулярным выражением PCRE; см. Модификаторы шаблонов (цитирование):
u (PCRE8)
Этот модификатор включает дополнительные функции PCRE, которые несовместимы с Perl. Строки шаблонов рассматриваются как UTF-8. Этот модификатор доступен с PHP 4.1.0 или выше в Unix и с PHP 4.2.3 на win32. Срок действия шаблона UTF-8 проверяется с PHP 4.3.5.
Например, учитывая этот код:
header('Content-type: text/html; charset=UTF-8'); // So the browser doesn't make our lives harder $str = "abc 文字化け, efg"; $results = array(); preg_match_all('/./', $str, $results); var_dump($results[0]);
Вы получите непригодный результат:
array 0 => string 'a' (length=1) 1 => string 'b' (length=1) 2 => string 'c' (length=1) 3 => string ' ' (length=1) 4 => string ' ' (length=1) 5 => string ' ' (length=1) 6 => string ' ' (length=1) 7 => string ' ' (length=1) 8 => string ' ' (length=1) 9 => string ' ' (length=1) 10 => string ' ' (length=1) 11 => string ' ' (length=1) 12 => string ' ' (length=1) 13 => string ' ' (length=1) 14 => string ' ' (length=1) 15 => string ' ' (length=1) 16 => string ',' (length=1) 17 => string ' ' (length=1) 18 => string 'e' (length=1) 19 => string 'f' (length=1) 20 => string 'g' (length=1)
Но с этим кодом:
header('Content-type: text/html; charset=UTF-8'); // So the browser doesn't make our lives harder $str = "abc 文字化け, efg"; $results = array(); preg_match_all('/./u', $str, $results); var_dump($results[0]);
(Обратите внимание на «u» в конце регулярного выражения)
Вы получаете то, что хотите:
array 0 => string 'a' (length=1) 1 => string 'b' (length=1) 2 => string 'c' (length=1) 3 => string ' ' (length=1) 4 => string '文' (length=3) 5 => string '字' (length=3) 6 => string '化' (length=3) 7 => string 'け' (length=3) 8 => string ',' (length=1) 9 => string ' ' (length=1) 10 => string 'e' (length=1) 11 => string 'f' (length=1) 12 => string 'g' (length=1)
Надеюсь это поможет 🙂
Попробуй это:
preg_match_all('/./u', $text, $array);
Чуть проще, чем preg_match_all
:
preg_split('//u', $str, -1, PREG_SPLIT_NO_EMPTY)
Это возвращает вам 1-мерный массив символов. Нет необходимости в объекте совпадений.
Если по какой-то причине для вас недостаточно регулярного выражения. Я однажды написал Zend_Locale_UTF8
который оставлен, но может помочь вам, если вы решите сделать это самостоятельно.
В частности, посмотрите на класс Zend_Locale_UTF8_PHP5_String
который читает строки Unicode и работает с ними, разбивает их на отдельные символы (которые могут состоять из нескольких байтов, очевидно).
EDIT : Я просто рассказал, что svn-браузер ZF недоступен, поэтому я скопировал важные методы для удобства:
/** * Returns the UTF-8 code sequence as an array for any given $string. * * @access protected * @param string|integer $string * @return array */ protected function _decode( $string ) { $string = (string) $string; $length = strlen($string); $sequence = array(); for ( $i=0; $i<$length; ) { $bytes = $this->_characterBytes($string, $i); $ord = $this->_ord($string, $bytes, $i); if ( $ord !== false ) $sequence[] = $ord; if ( $bytes === false ) $i++; else $i += $bytes; } return $sequence; } /** * Returns the UTF-8 code of a character. * * @see http://en.wikipedia.org/wiki/UTF-8#Description * @access protected * @param string $string * @param integer $bytes * @param integer $position * @return integer */ protected function _ord( &$string, $bytes = null, $pos=0 ) { if ( is_null($bytes) ) $bytes = $this->_characterBytes($string); if ( strlen($string) >= $bytes ) { switch ( $bytes ) { case 1: return ord($string[$pos]); break; case 2: return ( (ord($string[$pos]) & 0x1f) << 6 ) + ( (ord($string[$pos+1]) & 0x3f) ); break; case 3: return ( (ord($string[$pos]) & 0xf) << 12 ) + ( (ord($string[$pos+1]) & 0x3f) << 6 ) + ( (ord($string[$pos+2]) & 0x3f) ); break; case 4: return ( (ord($string[$pos]) & 0x7) << 18 ) + ( (ord($string[$pos+1]) & 0x3f) << 12 ) + ( (ord($string[$pos+1]) & 0x3f) << 6 ) + ( (ord($string[$pos+2]) & 0x3f) ); break; case 0: default: return false; } } return false; } /** * Returns the number of bytes of the $position-th character. * * @see http://en.wikipedia.org/wiki/UTF-8#Description * @access protected * @param string $string * @param integer $position */ protected function _characterBytes( &$string, $position = 0 ) { $char = $string[$position]; $charVal = ord($char); if ( ($charVal & 0x80) === 0 ) return 1; elseif ( ($charVal & 0xe0) === 0xc0 ) return 2; elseif ( ($charVal & 0xf0) === 0xe0 ) return 3; elseif ( ($charVal & 0xf8) === 0xf0) return 4; /* elseif ( ($charVal & 0xfe) === 0xf8 ) return 5; */ return false; }
Я смог написать решение с использованием mb_*
, включая поездку в UTF-16 и обратно, возможно, в глупый попытке ускорить индексирование строк:
$japanese2 = mb_convert_encoding($japanese, "UTF-16", "UTF-8"); $length = mb_strlen($japanese2, "UTF-16"); for($i=0; $i<$length; $i++) { $char = mb_substr($japanese2, $i, 1, "UTF-16"); $utf8 = mb_convert_encoding($char, "UTF-8", "UTF-16"); print $utf8 . "\n"; }
Мне повезло избежать mb_internal_encoding
и просто указать все при каждом mb_*
. Я уверен, что закончу это с помощью решения preg
.