2010-02-02 19 views
8

tengo preg_match_all('/[aäeëioöuáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);caracteres UTF-8 en preg_match_all (PHP)

Si $in = 'hëllo'$out es:

array(1) { 
[0]=> 
    array(2) { 
    [0]=> 
    array(2) { 
     [0]=> 
     string(2) "ë" 
    [1]=> 
    int(1) 
} 
[1]=> 
array(2) { 
    [0]=> 
    string(1) "o" 
    [1]=> 
    int(5) 
    } 
} 
} 

La posición de o debería ser 4. He leído sobre este problema en línea (la ë se cuenta como 2). ¿Existe alguna solucion para esto? He visto mb_substr y similares, pero ¿hay algo como esto para preg_match_all?

Tipo relacionado: ¿Es el equivalente a preg_match_all en Python? (Devolviendo una matriz de coincidencias con su posición en la cadena)

+1

debe pedir que en una pregunta diferente, pero sí ... un matchobject pitón expresión regular contiene la posición de coincidencia por mo.start predeterminado() y mo.end() –

Respuesta

0

PHP no es compatible con Unicode muy bien, por lo que muchas funciones de cadenas, incluyendo preg_ *, todavía cuentan bytes en lugar de caracteres.

Intenté encontrar una solución codificando y decodificando cadenas, pero al final todo se redujo a la función preg_match_all.

Acerca de lo de python: un matchobject de python regex contiene la posición de coincidencia por defecto mo.start() y mo.end(). Ver: http://docs.python.org/library/re.html#finding-all-adverbs-and-their-positions

+0

Aparentemente fue planeado para ser arreglado en PHP6, pero hasta el momento, 2016 (6 años después) todavía está bajo discusión. Tengo que amar a los desarrolladores de PHP. Ellos no tienen ninguna pista real. –

6

Esto no es un error, PREG_OFFSET_CAPTURE se refiere a la compensación de bytes del carácter en la cadena.

mb_ereg_search_pos se comporta de la misma manera. Una posibilidad es cambiar la codificación a UTF-32 antes y luego dividir la posición por 4 (porque todas las unidades de código Unicode se representan como secuencias de 4 bytes en UTF-32):

mb_regex_encoding("UTF-32"); 
$string = mb_convert_encoding('hëllo', "UTF-32", "UTF-8"); 
$regex = mb_convert_encoding('[aäeëioöuáéíóú]', "UTF-32", "UTF-8"); 
mb_ereg_search_init ($string, $regex); 
$positions = array(); 
while ($r = mb_ereg_search_pos()) { 
    $positions[] = reset($r)/4; 
} 
print_r($positions); 

da:

 
Array 
(
    [0] => 1 
    [1] => 4 
) 

También podría convertir las posiciones binarias en posiciones de unidades de código. Para UTF-8, una implementación subóptima es:

function utf8_byte_offset_to_unit($string, $boff) { 
    $result = 0; 
    for ($i = 0; $i < $boff;) { 
     $result++; 
     $byte = $string[$i]; 
     $base2 = str_pad(
      base_convert((string) ord($byte), 10, 2), 8, "0", STR_PAD_LEFT); 
     $p = strpos($base2, "0"); 
     if ($p == 0) { $i++; } 
     elseif ($p <= 4) { $i += $p; } 
     else { return FALSE; } 
    } 
    return $result; 
} 
2

Hay solución simple, para ser usado después de preg_match() resultados emparejados. Es necesario para recorrer cada resultado del partido y reasignar valor de posición con el siguiente:

$utfPosition = mb_strlen(substr($wholeSubjectString, 0, $capturedEntryPosition), 'utf-8'); 

Probado en PHP 5.4 en Windows, depende de varios bytes única extensión PHP.

0

Otra forma de dividir UTF-8 $string por una expresión regular es utilizar la función preg_split(). Aquí está mi solución de trabajo:

$result = preg_split('~\[img/\d{1,}/img\]\s?~', $string, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE); 

PHP 5.3.17

Cuestiones relacionadas