Флаг 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» на «ВикипедиЯ»

Ключевой момент: каждый знак текста в кодировке UTF-8 может иметь от 1 до 6 байт. Так что здесь приходится использовать «побайтные» и «многобайтные» функции.

Рассмотрим следующий пример:

Флаг PREG_OFFSET_CAPTURE и UTF-8 (PHP)

Код реализации примера:

$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] ." -&gt; ". $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] ."' -&gt; '". $fragment ."' [". $match[1] ."]\n";
}
}

Результат выполнения кода:

'прекрасна' -> 'прекрасна' [16]
'прекрасна' -> 'прекрасна' [36]

Алгоритм получения многобайтной подстроки побайтно:

  • используя «побайтную» функцию strlen() мы получаем длинную многобайтной подстроки в байтах;
  • используя «многобайтную» функцию mb_strcut() мы получаем фрагмент, начиная с $match[1] байта, определённой ранее длины в байтах.

И так, для знаков в многобайтных строках используется разное количество байт. Флаг PREG_OFFSET_CAPTURE в PHP-функциях для работы с регулярными выражениями позволяет добавить в результат поиска позицию найденной подстроки, но в байтах. Для получения позиции подстроки в знаках, можно использовать вычисление знаком функцией mb_strlen() в стоящем перед ней фрагменте, полученном функцией substr(). Но правильней использовать функцию mb_strcut(), которая (в отличии от mb_substr()) работает с байтами.

Twitter Facebook ВКонтакте Одноклассники Google+

Комментариев нет:

Отправить комментарий