sirve que preg_match pattern para matches examples php unicode utf-8 pcre

que - preg_match y UTF-8 en PHP



regex php string (6)

Estoy intentando buscar una cadena codificada en UTF8 usando preg_match .

preg_match(''/H/u'', "/xC2/xA1Hola!", $a_matches, PREG_OFFSET_CAPTURE); echo $a_matches[0][1];

Esto debería imprimir 1, ya que "H" está en el índice 1 en la cadena "¡Hola!". Pero imprime 2. Parece que no trata al sujeto como una cadena codificada en UTF8, a pesar de que estoy pasando el modifier "u" en la expresión regular.

Tengo la siguiente configuración en mi php.ini, y otras funciones UTF8 están funcionando:

mbstring.func_overload = 7 mbstring.language = Neutral mbstring.internal_encoding = UTF-8 mbstring.http_input = pass mbstring.http_output = pass mbstring.encoding_translation = Off

¿Algunas ideas?


Aunque el modificador u hace que tanto el patrón como el sujeto se interpreten como UTF-8, los desplazamientos capturados todavía se cuentan en bytes.

Puede usar mb_strlen para obtener la longitud en caracteres UTF-8 en lugar de bytes:

$str = "/xC2/xA1Hola!"; preg_match(''/H/u'', $str, $a_matches, PREG_OFFSET_CAPTURE); echo mb_strlen(substr($str, 0, $a_matches[0][1]));


Escribí una clase pequeña para convertir las compensaciones devueltas por preg_match a las compensaciones utf correctas:

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]; } }

Puedes usarlo así:

$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"; }

https://3v4l.org/8Y32J



Parece que esta es una "característica", ver http://bugs.php.net/bug.php?id=37391

''u'' cambiar solo tiene sentido para pcre, PHP mismo no lo sabe.

Desde el punto de vista de PHP, las cadenas son secuencias de bytes y el desplazamiento del byte de retorno parece lógico (no digo "correcto").


Perdóneme por necroposting, pero puede ser que alguien lo encuentre útil: el código a continuación puede funcionar como reemplazo de las funciones preg_match y preg_match_all y devuelve las coincidencias correctas con el desplazamiento correcto para las cadenas codificadas en 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));

Salida de mi ejemplo:

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) } }


Si todo lo que quiere hacer es encontrar la posición segura de múltiples bytes de 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";

Salida:

¡Hola! 1 H