PHP str_word_count() многобайтово безопасно?
Я хочу использовать str_word_count()
на строку UTF-8.
Это безопасно в PHP? Мне кажется, что так и должно быть (особенно если учесть, что нет mb_str_word_count()
).
Но на php.net есть много людей, которые мутят воду, представляя свои собственные "многобайтовые совместимые" версии функции.
Я думаю, я хочу знать...
При условии
str_word_count
просто считает все последовательности символов в разделенных" "
(пробел), он должен быть безопасным для многобайтовых строк, даже если он не обязательно знает последовательности символов, верно?Есть ли в UTF-8 эквивалентные символы пробела, которые не являются ASCII?
" "
(пространство)?#
Я думаю, в этом и заключается проблема.
4 ответа
Я бы сказал, что ты угадаешь правильно. И действительно, в UTF-8 есть пробелы, которые не являются частью US-ASCII. Чтобы дать вам пример таких пространств:
- Символ Unicode 'NO-BREAK SPACE' (U + 00A0): 2 байта в UTF-8: 0xC2 0xA0 (c2a0)
И, возможно, также:
- Символ Unicode 'NEXT LINE (NEL)' (U + 0085): 2 байта в UTF-8: 0xC2 0x85 (c285)
- Символ Unicode 'LINE SEPARATOR' (U + 2028): 3 байта в UTF-8: 0xE2 0x80 0xA8 (e280a8)
- Символ Unicode 'РАЗДЕЛИТЕЛЬ ПАРАГРАФА' (U + 2029): 3 байта в UTF-8: 0xE2 0x80 0xA8 (e280a8)
В любом случае, первый - "NO-BREAK SPACE" (U+00A0) - является хорошим примером, поскольку он также является частью кодировок Latin-X. И руководство по PHP уже дает подсказку, что str_word_count
будет зависеть от локали.
Если мы хотим проверить это, мы можем установить языковой стандарт UTF-8 и передать недопустимую строку, содержащую \xA0
последовательность и, если это все еще считается символом разрыва слов, эта функция явно не безопасна в UTF-8, следовательно, не является многобайтовой (как и та, которая не определена в соответствии с вопросом):
<?php
/**
* is PHP str_word_count() multibyte safe?
* @link https://stackru.com/q/8290537/367456
*/
echo 'New Locale: ', setlocale(LC_ALL, 'en_US.utf8'), "\n\n";
$test = "aword\xA0bword aword";
$result = str_word_count($test, 2);
var_dump($result);
Выход:
New Locale: en_US.utf8
array(3) {
[0]=>
string(5) "aword"
[6]=>
string(5) "bword"
[12]=>
string(5) "aword"
}
Как видно из этой демонстрации, эта функция полностью не справляется с обещанием локали, которое она дает на странице руководства (я не удивляюсь и не жалею об этом, чаще всего, если вы читаете, что функция зависит от локали в PHP, запускаете ее для своей жизни и находите такую). это не так), который я использую здесь, чтобы продемонстрировать, что он никоим образом не делает ничего, касающегося кодировки символов UTF-8.
Вместо этого для UTF-8 вы должны взглянуть на расширение PCRE:
PCRE хорошо понимает Unicode и UTF-8 в PHP в частности. Это также может быть довольно быстро, если вы тщательно создадите шаблон регулярного выражения.
Насчет "шаблона ответа" - я не получаю требование "работать быстрее". Мы не говорим здесь о долгом или большом количестве событий, так кого это волнует, если это займет несколько миллисекунд или нет?
Тем не менее, str_word_count работает с мягким дефисом:
function my_word_count($str) {
return str_word_count(str_replace("\xC2\xAD",'', $str));
}
функция, которая соответствует утверждениям (но, вероятно, не быстрее, чем str_word_count):
function my_word_count($str) {
$mystr = str_replace("\xC2\xAD",'', $str); // soft hyphen encoded in UTF-8
return preg_match_all('~[\p{L}\'\-]+~u', $mystr); // regex expecting UTF-8
}
Функция preg по сути та же самая, что уже предложена, за исключением того, что а) она уже возвращает счетчик, поэтому нет необходимости указывать совпадения, что должно ускорить его, и б) действительно не должно быть отступления iconv, IMO.
О комментарии:
Я вижу, что ваши PCRE-функции имеют меньшую производительность, чем моя preg_word_count(), потому что нужен str_replace, который вам не нужен: '~[^\p{L}\'-\xC2\xAD]+~u'работает нормально (!).
Я считал, что с другой стороны, замена строки удалит только многобайтовый символ, но ваше регулярное выражение будет иметь дело с \\xC2
а также \\xAD
в любом порядке они могут появиться, что неправильно. Рассмотрим зарегистрированный знак, который является \xC2\xAE.
Однако теперь, когда я думаю об этом из-за того, как работает действующий UTF-8, это не будет иметь большого значения, поэтому его следует использовать одинаково хорошо. Таким образом, мы можем просто иметь функцию
function my_word_count($str) {
return preg_match_all('~[\p{L}\'\-\xC2\xAD]+~u', $str); // regex expecting UTF-8
}
без необходимости в спичках или других заменах.
О str_word_count(str_replace("\xC2\xAD",'', $str));, если стабильно с UTF8, хорошо, но, похоже, нет.
Если вы прочитаете эту ветку, вы поймете, что str_replace безопасен, если вы придерживаетесь допустимых строк UTF-8. Я не видел никаких доказательств в вашей ссылке на обратное.
РЕДАКТИРОВАНИЕ (чтобы показать новые подсказки): есть возможное решение с использованием str_word_count()
с PHP v5.1!
function my_word_count($str, $myLangChars="àáãâçêéíîóõôúÀÁÃÂÇÊÉÍÎÓÕÔÚ") {
return str_word_count($str, 0, $myLangChars);
}
но не на 100%, потому что я пытаюсь добавить в $ myLangChars \xC2\xAD
( символ SHY - SOFT HYPHEN), который должен быть компонентом слова на любом языке, и он не работает ( см.).
Другое, не очень быстрое, но полное и гибкое решение (извлечено здесь), основанное на библиотеке PCRE, но с возможностью имитации str_word_count()
Поведение на недействительном UTF8:
/**
* Like str_word_count() but showing how preg can do the same.
* This function is most flexible but not faster than str_word_count.
* @param $wRgx the "word regular expression" as defined by user.
* @param $triggError changes behaviour causing error event.
* @param $OnBadUtfTryAgain when true mimic the str_word_count behaviour.
* @return 0 or positive integer as word-count, negative as PCRE error.
*/
function preg_word_count($s,$wRgx='/[-\'\p{L}\xC2\xAD]+/u', $triggError=true,
$OnBadUtfTryAgain=true) {
if ( preg_match_all($wRgx,$s,$m) !== false )
return count($m[0]);
else {
$lastError = preg_last_error();
$chkUtf8 = ($lastError==PREG_BAD_UTF8_ERROR);
if ($OnBadUtfTryAgain && $chkUtf8)
return preg_word_count(
iconv('CP1252','UTF-8',$s), $wRgx, $triggError, false
);
elseif ($triggError) trigger_error(
$chkUtf8? 'non-UTF8 input!': "error PCRE_code-$lastError",
E_USER_NOTICE
);
return -$lastError;
}
}
(ШАБЛОН ОТВЕТ) помощь для щедрости!
(это не ответ, это помощь для щедрости, потому что я не могу изменить ни дублировать вопрос)
Мы хотим посчитать "слова реального мира" в тексте латинского языка UTF-8.
ДЛЯ БОЛЬНИ, НАМ НУЖНО:
- функция, которая соответствует
assert
ниже и быстрее чемstr_word_count
; - или же
str_word_count
работа с персонажем SHy (как?); - или же
preg_word_count
работать быстрее (используя preg_replace? регулярное выражение разделителя слов?).
УТВЕРЖДАЕТ
Предположим, что "многобайтовая безопасная" функция my_word_count()
существует, тогда следующие утверждения должны быть верными:
assert_options(ASSERT_ACTIVE, 1);
$text = "1,2,3,4=0 (1 2 3 4)=0 (... ,.)=0 (2.5±0.1; 0.5±0.2)=0";
assert( my_word_count($text)==0 ); // no word there
$text = "(one two,three;four)=4 (five-six se\xC2\xADven)=2";
assert( my_word_count($text)==6 ); // hyphen merges two words
$text = "(um±dois três)=3 (àáãâçêéíîóõôúÀÁÃÂÇÊÉÍÎÓÕÔÚ)=1";
assert( my_word_count($text)==4 ); // a UTF8 case
$text = "(ÍSÔ9000-X, ISÔ 9000-X, ÍSÔ-9000-X)=6"; //Codes are words?
assert( my_word_count($text)==6 ); // suppose no: X is another word
Все, что он делает, это подсчитывает количество пробелов или слов между ними. если вам интересно, вы можете просто сделать свою собственную функцию подсчета, используя разнесение и подсчет.
Каждый раз, когда байт пространства ascii найден, он расщепляется, и все, что на самом деле есть для него.