Флаг PREG_OFFSET_CAPTURE и UTF-8 (PHP)
Из статьи вы узнаете о том, что возвращают preg_match_all()
, preg_match()
и др. функции для работы с регулярными выражениями при использовании флага PREG_OFFSET_CAPTURE
в строках с кодировкой UTF-8 и как с этим работать в PHP.
Использование флага PREG_OFFSET_CAPTURE
в PHP-функциях для работы с регулярными выражениями позволяет добавить в результат поиска позицию найденной подстроки в байтах. Проблема такого подхода наиболее очевидна при работе с многобайтными строками, например в кодировке UTF-8.
UTF-8 (от англ. Unicode Transformation Format, 8-bit — формат преобразования Юникода, 8-битный) — одна из общепринятых и стандартизированных кодировок текста, которая позволяет хранить символы Юникода, используя переменное количество байт (от 1 до 6).
Ключевой момент: каждый знак текста в кодировке UTF-8 может иметь от 1 до 6 байт. Так что здесь приходится использовать «побайтные» и «многобайтные» функции.
Рассмотрим следующий пример:

Код реализации примера:
$str = 'она была прекрасна, прекрасна как мечта';
echo "<pre>";
if( preg_match_all(
"'прекрасна'iu",
$str,
$matches,
PREG_OFFSET_CAPTURE
) ){
var_dump($matches);
}
Результат выполнения кода:
array(1
{
[0]=>
array(2) {
[0]=>
array(2) {
[0]=>
string(18) "прекрасна"
[1]=>
int(16)
}
[1]=>
array(2) {
[0]=>
string(18) "прекрасна"
[1]=>
int(36)
}
}
}
В примере шаблон регулярного выражения: «'прекрасна'iu»
— не содержит подмаски (указывается в круглых скобках), так что результат состоит только из одного подмассива $matches[0]
.
С учётом использования флага PREG_OFFSET_CAPTURE
, каждое значение подмассива $matches[0]
представляет собой массив из подстроки и её позиции в строке, данное в байтах.
Перевод количества байтов в количество знаков
Данное решение — «костыль», который приведен лишь для демонстрации алгоритма перевода количества байт фрагмента в количество его знаков.
$str = 'она была прекрасна, прекрасна как мечта';
echo "<pre>";
if( preg_match_all(
"'прекрасна'iu",
$str,
$matches,
PREG_OFFSET_CAPTURE
) ){
foreach( $matches[0] as $match ){
$chars = mb_strlen(substr($str, 0, $match[1]));
echo "'". $match[0] ."': ". $match[1] ." -> ". $chars ."\n";
}
}
Результат выполнения кода:
'прекрасна': 16 -> 9
'прекрасна': 36 -> 20
Алгоритм перевода количества байтов в количество знаков:
- используя «побайтную» функцию
substr(
) мы получает многобайтный фрагмент от0
до$match[1]
байта, который находится перед найденной подстрокой, например: «она была » — в кодировке UTF-8 или: «РѕРЅР° была » — в Windows-1251; - подсчитанное функцией
mb_strlen()
количество знаков в полученном многобайтном фрагменте — это позиция найденной подстроки$match[0]
в знаках.
Получение многобайтной подстроки побайтно
Если «многобайтная» функция mb_substr()
работает со знаками, то mb_strcut()
— с байтами. Позиция подстроки в байтах нам уже известна, это $match[1], а длину многобайтной строки в байты определяет «побайтная» функцию strlen()
.
Для наглядности рассмотрим следующих пример кода:
$str = 'она была прекрасна, прекрасна как мечта';
echo "<pre>";
if( preg_match_all(
"'прекрасна'iu",
$str,
$matches,
PREG_OFFSET_CAPTURE
) ){
foreach( $matches[0] as $match ){
$fragment = mb_strcut($str, $match[1], strlen($match[0]));
echo "'". $match[0] ."' -> '". $fragment ."' [". $match[1] ."]\n";
}
}
Результат выполнения кода:
'прекрасна' -> 'прекрасна' [16]
'прекрасна' -> 'прекрасна' [36]
Алгоритм получения многобайтной подстроки побайтно:
- используя «побайтную» функцию
strlen()
мы получаем длинную многобайтной подстроки в байтах; - используя «многобайтную» функцию
mb_strcut()
мы получаем фрагмент, начиная с$match[1]
байта, определённой ранее длины в байтах.
И так, для знаков в многобайтных строках используется разное количество байт. Флаг PREG_OFFSET_CAPTURE
в PHP-функциях для работы с регулярными выражениями позволяет добавить в результат поиска позицию найденной подстроки, но в байтах. Для получения позиции подстроки в знаках, можно использовать вычисление знаком функцией mb_strlen()
в стоящем перед ней фрагменте, полученном функцией substr()
. Но правильней использовать функцию mb_strcut()
, которая (в отличии от mb_substr()
) работает с байтами.
Комментариев нет:
Отправить комментарий