2010-02-02 6 views
8

J'ai preg_match_all('/[aäeëioöuáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);caractères UTF-8 dans preg_match_all (PHP)

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

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 position de o devrait être 4. J'ai lu sur ce problème en ligne (le ë est compté comme 2). Y a-t-il une solution pour cela? J'ai vu mb_substr et similaire, mais est-ce qu'il y a quelque chose comme ça pour preg_match_all?

Sorte de parenté: Est-ce que c'est un équivalent de preg_match_all en Python? (Renvoyer un tableau de correspondances avec leur position dans la chaîne)

+1

vous devriez demander à une autre question, mais oui ... un python regex MatchObject contient la position du match par défaut mo.start() et mo.end() –

Répondre

0

PHP ne supporte pas très bien l'Unicode, donc beaucoup de fonctions de chaînes, y compris preg_ *, comptent encore les octets au lieu des caractères.

J'ai essayé de trouver une solution en codant et en décodant des chaînes, mais finalement, tout est descendu à la fonction preg_match_all. À propos de la chose python: un objet de correspondance python regex contient la position de correspondance par défaut mo.start() et mo.end(). Voir: http://docs.python.org/library/re.html#finding-all-adverbs-and-their-positions

+0

Apparemment, il était prévu d'être corrigé dans PHP6, mais pour l'instant, 2016 (6 ans plus tard) il est encore en discussion. Je dois aimer les développeurs PHP. Ils n'ont aucune idée réelle. –

6

Ce n'est pas un bogue, PREG_OFFSET_CAPTURE fait référence au décalage d'octet du caractère dans la chaîne.

mb_ereg_search_pos se comporte de la même manière. Une possibilité est de modifier le codage UTF-32 avant et diviser ensuite la position en 4 (parce que toutes les unités de code Unicode sont représentés sous forme de séquences de 4 octets dans 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); 

donne:

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

Vous pouvez également convertir les positions binaires en positions d'unité de code. Pour UTF-8, une mise en œuvre est suboptimale:

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

Il y a solution simple, à utiliser après les résultats preg_match() apparié. Vous devez itérer chaque résultat du match et réassigner la valeur de position suivante:

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

Testé sur php 5.4 sous Windows, dépend de l'extension PHP Multibyte uniquement.

0

Une autre façon de diviser UTF-8 $string par une expression régulière consiste à utiliser la fonction preg_split(). Voici ma solution de travail:

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

PHP 5.3.17