UTF-8 characters in preg_match_all (PHP)
Я имею preg_match_all('/[aäeëioöuáéíóú]/u', $in, $out, PREG_OFFSET_CAPTURE);
Если $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
should be 4. I've read about this problem online (the ë
gets counted as 2). Есть ли решение для этого? я видел mb_substr
and similar, but is there something like this for preg_match_all
?
Kind of related: Is their an equivalent of preg_match_all
в питоне? (Returning an array of matches with their position in the string)
4 ответа
PHP не очень хорошо поддерживает юникод, поэтому многие строковые функции, включая preg_*, по-прежнему считают байты вместо символов.
Я пытался найти решение путем кодирования и декодирования строк, но в конечном итоге все сводилось к функции preg_match_all.
О Python: математический объект регулярного выражения Python содержит позицию соответствия по умолчанию mo.start() и mo.end(). Смотрите: http://docs.python.org/library/re.html
Это не ошибка, 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);
дает:
массив ( [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;
}
Существует простой обходной путь, который следует использовать после сопоставления результатов preg_match(). Вам нужно перебрать каждый результат матча и переназначить значение позиции следующим образом:
$utfPosition = mb_strlen(substr($wholeSubjectString, 0, $capturedEntryPosition), 'utf-8');
Протестировано на php 5.4 под Windows, зависит только от многобайтового расширения PHP.
Еще один способ расколоть 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