2010-02-02 4 views
8

У меня есть preg_match_all('/[aäeëioöuáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);UTF-8 символов в preg_match_all (PHP)

Если $in = 'hëllo'$out является:

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

Положение o должно быть 4. Я прочитал об этой проблеме в оперативном режиме (ë подсчитывается как 2). Есть ли решение для этого? Я видел mb_substr и тому подобное, но есть ли что-то вроде этого для preg_match_all?

Вид связанных: Является ли их эквивалент preg_match_all в Python? (Возвращение массива совпадений с их положением в строке)

+1

вы должны спросить, что в другом вопросе, но да ... питон регулярное выражение matchobject содержит позицию совпадения по умолчанию mo.start() и mo.end() –

ответ

0

PHP не поддерживает юникод очень хорошо, поэтому множество строковых функций, включая preg_ *, по-прежнему подсчитывают байты вместо символов.

Я попытался найти решение с помощью кодирования и декодирования строк, но в конечном итоге все это сработало до функции preg_match_all.

О вещи python: объект соответствия regex python содержит позицию соответствия по умолчанию mo.start() и mo.end(). См.: http://docs.python.org/library/re.html#finding-all-adverbs-and-their-positions

+0

По-видимому, это было запланировано на PHP6, но пока 2016 (6 лет спустя) все еще обсуждается. Должен любить разработчиков PHP. У них нет реальной подсказки. –

6

Это не ошибка, PREG_OFFSET_CAPTURE относится к смещению байта символа в строке.

mb_ereg_search_pos ведет себя одинаково. Одна возможность состоит в том, чтобы изменить кодировку UTF-32 перед тем, а затем разделить позицию на 4 (потому что все блоки Юникода код представлены в виде 4-байтовых последовательностей в 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); 

дает:

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

Вы также можете преобразовать двоичные позиции в позиции блока кода. Для UTF-8, субоптимальная реализация:

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

Существует простое решение, чтобы использовать после того, как preg_match() результаты совпадают. Вам нужно перебирать каждый результат матча и переназначить значение позиции со следующим:

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

Проверено на PHP 5.4 под Windows, зависит только расширение Multibyte PHP.

0

Другим способом разделения UTF-8 $string на регулярное выражение является использование функции preg_split(). Вот мой рабочий раствор:

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

PHP 5.3.17