Символы UTF-8 в preg_match_all (PHP)

у меня preg_match_all('/[aäeëioöuáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);

If $in = 'hëllo' $out is:

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? (возвращение массива совпадений с их позицией в строке)


person roflwaffle    schedule 02.02.2010    source источник
comment
вы должны задать это в другом вопросе, но да... объект соответствия регулярного выражения python содержит позицию совпадения по умолчанию mo.start() и mo.end()   -  person Tor Valamo    schedule 03.02.2010


Ответы (4)


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

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

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

person Tor Valamo    schedule 02.02.2010
comment
Судя по всему, это планировалось исправить в PHP6, но пока, 2016 (6 лет спустя) это пока только обсуждается. Должен любить PHP-разработчиков. У них нет реальной подсказки. - person Tor Valamo; 20.01.2016

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

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

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

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

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

person Николай Конев    schedule 27.02.2014