Я пытаюсь найти строку с кодировкой UTF8, используя preg_match .
preg_match('/H/u', "\xC2\xA1Hola!", $a_matches, PREG_OFFSET_CAPTURE); echo $a_matches[0][1];
Это должно печатать 1, так как «H» имеет индекс 1 в строке «¡Hola!». Но он печатает 2. Поэтому кажется, что он не рассматривает тему как кодированную в UTF8 строку, хотя я передаю модификатор «u» в регулярном выражении.
У меня есть следующие настройки в моем php.ini, и работают другие функции UTF8:
mbstring.func_overload = 7 mbstring.language = Neutral mbstring.internal_encoding = UTF-8 mbstring.http_input = pass mbstring.http_output = pass mbstring.encoding_translation = Off
Есть идеи?
Похоже, что это «функция», см. http://bugs.php.net/bug.php?id=37391
Переключатель «u» имеет смысл только для pcre, сам PHP не знает об этом.
С точки зрения PHP, строки являются байтовыми последовательностями, а возвращаемое смещение байтов кажется логичным (я не говорю «правильно»).
Хотя модификатор u позволяет интерпретировать как шаблон, так и субъект как UTF-8, захваченные смещения все еще подсчитываются в байтах.
Вы можете использовать mb_strlen
для получения длины в символах UTF-8, а не в байтах:
$str = "\xC2\xA1Hola!"; preg_match('/H/u', $str, $a_matches, PREG_OFFSET_CAPTURE); echo mb_strlen(substr($str, 0, $a_matches[0][1]));
Попробуйте добавить это (* UTF8) перед регулярным выражением:
preg_match('(*UTF8)/H/u', "\xC2\xA1Hola!", $a_matches, PREG_OFFSET_CAPTURE);
Магия, благодаря комментарию в http://www.php.net/manual/es/function.preg-match.php#95828
Извините меня за некропостинг, но может быть кто-то сочтет это полезным: код ниже может работать как замена для функций preg_match и preg_match_all и возвращает правильные совпадения с правильным смещением для строк с кодировкой UTF8.
mb_internal_encoding('UTF-8'); /** * Returns array of matches in same format as preg_match or preg_match_all * @param bool $matchAll If true, execute preg_match_all, otherwise preg_match * @param string $pattern The pattern to search for, as a string. * @param string $subject The input string. * @param int $offset The place from which to start the search (in bytes). * @return array */ function pregMatchCapture($matchAll, $pattern, $subject, $offset = 0) { $matchInfo = array(); $method = 'preg_match'; $flag = PREG_OFFSET_CAPTURE; if ($matchAll) { $method .= '_all'; } $n = $method($pattern, $subject, $matchInfo, $flag, $offset); $result = array(); if ($n !== 0 && !empty($matchInfo)) { if (!$matchAll) { $matchInfo = array($matchInfo); } foreach ($matchInfo as $matches) { $positions = array(); foreach ($matches as $match) { $matchedText = $match[0]; $matchedLength = $match[1]; $positions[] = array( $matchedText, mb_strlen(mb_strcut($subject, 0, $matchedLength)) ); } $result[] = $positions; } if (!$matchAll) { $result = $result[0]; } } return $result; } $s1 = 'Попробуем русскую строку для теста'; $s2 = 'Try english string for test'; var_dump(pregMatchCapture(true, '/обу/', $s1)); var_dump(pregMatchCapture(false, '/обу/', $s1)); var_dump(pregMatchCapture(true, '/lish/', $s2)); var_dump(pregMatchCapture(false, '/lish/', $s2));
Результат моего примера:
array(1) { [0]=> array(1) { [0]=> array(2) { [0]=> string(6) "обу" [1]=> int(4) } } } array(1) { [0]=> array(2) { [0]=> string(6) "обу" [1]=> int(4) } } array(1) { [0]=> array(1) { [0]=> array(2) { [0]=> string(4) "lish" [1]=> int(7) } } } array(1) { [0]=> array(2) { [0]=> string(4) "lish" [1]=> int(7) } }
Я написал небольшой класс для преобразования смещений, возвращаемых preg_match, в соответствующие смещения utf:
final class NonUtfToUtfOffset { /** @var int[] */ private $utfMap = []; public function __construct(string $content) { $contentLength = mb_strlen($content); for ($offset = 0; $offset < $contentLength; $offset ++) { $char = mb_substr($content, $offset, 1); $nonUtfLength = strlen($char); for ($charOffset = 0; $charOffset < $nonUtfLength; $charOffset ++) { $this->utfMap[] = $offset; } } } public function convertOffset(int $nonUtfOffset): int { return $this->utfMap[$nonUtfOffset]; } }
Вы можете использовать его так:
$content = 'aą bać d'; $offsetConverter = new NonUtfToUtfOffset($content); preg_match_all('#(bać)#ui', $content, $m, PREG_OFFSET_CAPTURE); foreach ($m[1] as [$word, $offset]) { echo "bad: " . mb_substr($content, $offset, mb_strlen($word))."\n"; echo "good: " . mb_substr($content, $offsetConverter->convertOffset($offset), mb_strlen($word))."\n"; }
Если все, что вы хотите сделать, это найти многобайтовое безопасное положение H try mb_strpos ()
mb_internal_encoding('UTF-8'); $str = "\xC2\xA1Hola!"; $pos = mb_strpos($str, 'H'); echo $str."\n"; echo $pos."\n"; echo mb_substr($str,$pos,1)."\n";
Вывод:
¡Hola! 1 H