Вопрос по utf-8, php, pcre, unicode – preg_match и UTF-8 в PHP

31

Я пытаюсь найти строку в кодировке UTF8, используяpreg_match.

preg_match('/H/u', "\xC2\xA1Hola!", $a_matches, PREG_OFFSET_CAPTURE);
echo $a_matches[0][1];

Это должно вывести 1, так как «H» находится в индексе 1 в строке «¡Hola!». Но он печатает 2. Таким образом, похоже, что он не рассматривает объект как строку в кодировке UTF8, хотя я передаю "u"модификатор в регулярном выражении.

У меня есть следующие настройки в моем php.ini, и другие функции UTF8 работают:

mbstring.func_overload = 7
mbstring.language = Neutral
mbstring.internal_encoding = UTF-8
mbstring.http_input = pass
mbstring.http_output = pass
mbstring.encoding_translation = Off

Есть идеи?

Ваш Ответ

7   ответов
1

возвращаемых preg_match, в правильные смещения utf:

final class NonUtfToUtfOffset
{
    /** @var int[] */
    private $utfMap = [];

    public function __construct(string $content)
    {
        $contentLength = mb_strlen($content);

        for ($offset = 0; $offset < $contentLength; $offset ++) {
            $char = mb_substr($content, $offset, 1);
            $nonUtfLength = strlen($char);

            for ($charOffset = 0; $charOffset < $nonUtfLength; $charOffset ++) {
                $this->utfMap[] = $offset;
            }
        }
    }

    public function convertOffset(int $nonUtfOffset): int
    {
        return $this->utfMap[$nonUtfOffset];
    }
}

Вы можете использовать это так:

$content = 'aą bać d';
$offsetConverter = new NonUtfToUtfOffset($content);

preg_match_all('#(bać)#ui', $content, $m, PREG_OFFSET_CAPTURE);

foreach ($m[1] as [$word, $offset]) {
    echo "bad: " . mb_substr($content, $offset, mb_strlen($word))."\n";
    echo "good: " . mb_substr($content, $offsetConverter->convertOffset($offset), mb_strlen($word))."\n";
}

https://3v4l.org/8Y32J

0

Вы можете посмотреть наТ-Regx библиотека.

pattern('/Hola/u')->match('\xC2\xA1Hola!')->first(function (Match $match) 
{
    echo $match->offset();     // characters
    echo $match->byteOffset(); // bytes
});

Этот$match->offset() UTF-8 безопасное смещение.

18

http://bugs.php.net/bug.php?id=37391

Переключатель 'u' имеет смысл только для pcre, сам PHP не знает об этом.

С точки зрения PHP, строки представляют собой последовательности байтов, и возвращаемое смещение в байтах кажется логичным (я не говорю «правильно»).

Отлично ... и они не предоставляют mb_preg_replace. JW.
Помните, что те же «правила», касающиеся обработки utf-8, применяются к 5-му параметру$offset, Образец:var_dump(preg_match('/#/u', "\xc3\xa4#",$matches,0,2)); AthanasiusKirchner
php знает о модификаторе u, он указан в руководстве, смотрите "u (PCRE_UTF8)"php.net/manual/en/reference.pcre.pattern.modifiers.php Walt Sorensen
эта работа очень хорошо / [^ \ w] / IU Maxim Colesnic
5

приведенный ниже код может работать как замена функций preg_match и preg_match_all и возвращает правильные совпадения сверный смещение для строк в кодировке UTF8.

     mb_internal_encoding('UTF-8');

     /**
     * Returns array of matches in same format as preg_match or preg_match_all
     * @param bool   $matchAll If true, execute preg_match_all, otherwise preg_match
     * @param string $pattern  The pattern to search for, as a string.
     * @param string $subject  The input string.
     * @param int    $offset   The place from which to start the search (in bytes).
     * @return array
     */
    function pregMatchCapture($matchAll, $pattern, $subject, $offset = 0)
    {
        $matchInfo = array();
        $method    = 'preg_match';
        $flag      = PREG_OFFSET_CAPTURE;
        if ($matchAll) {
            $method .= '_all';
        }
        $n = $method($pattern, $subject, $matchInfo, $flag, $offset);
        $result = array();
        if ($n !== 0 && !empty($matchInfo)) {
            if (!$matchAll) {
                $matchInfo = array($matchInfo);
            }
            foreach ($matchInfo as $matches) {
                $positions = array();
                foreach ($matches as $match) {
                    $matchedText   = $match[0];
                    $matchedLength = $match[1];
                    $positions[]   = array(
                        $matchedText,
                        mb_strlen(mb_strcut($subject, 0, $matchedLength))
                    );
                }
                $result[] = $positions;
            }
            if (!$matchAll) {
                $result = $result[0];
            }
        }
        return $result;
    }

    $s1 = 'Попробуем русскую строку для теста';
    $s2 = 'Try english string for test';

    var_dump(pregMatchCapture(true, '/обу/', $s1));
    var_dump(pregMatchCapture(false, '/обу/', $s1));

    var_dump(pregMatchCapture(true, '/lish/', $s2));
    var_dump(pregMatchCapture(false, '/lish/', $s2));

Вывод моего примера:

    array(1) {
      [0]=>
      array(1) {
        [0]=>
        array(2) {
          [0]=>
          string(6) "обу"
          [1]=>
          int(4)
        }
      }
    }
    array(1) {
      [0]=>
      array(2) {
        [0]=>
        string(6) "обу"
        [1]=>
        int(4)
      }
    }
    array(1) {
      [0]=>
      array(1) {
        [0]=>
        array(2) {
          [0]=>
          string(4) "lish"
          [1]=>
          int(7)
        }
      }
    }
    array(1) {
      [0]=>
      array(2) {
        [0]=>
        string(4) "lish"
        [1]=>
        int(7)
      }
    }
Ну, вот почему вы должны включить объяснение того, что делает ваш код. Люди не понимают, что вы пытаетесь сделать здесь. nhahtdh
Отредактируйте мой ответ, добавили тесты. Guy Fawkes
Можете ли вы объяснить, что делает ваш код, вместо того, чтобы просто вставлять дамп кода? И как это отвечает на вопрос? nhahtdh
Он делает именно то, что описано в комментариях, и возвращает ПРАВИЛЬНЫЕ смещения строк. Это предмет вопроса. Понятия не имею, почему у меня было -2 для моего ответа. Это работает для меня. Guy Fawkes
1

что вам нужно, это найти многобайтовую безопасную позицию H, попробуйте mb_strpos ()

mb_internal_encoding('UTF-8');
$str = "\xC2\xA1Hola!";
$pos = mb_strpos($str, 'H');
echo $str."\n";
echo $pos."\n";
echo mb_substr($str,$pos,1)."\n";

Выход:

¡Hola!
1
H
Это был просто упрощенный пример, но это может быть полезно для других. JW.
25

Попробуйте добавить это(* UTF8) перед регулярным выражением:

preg_match('(*UTF8)/H/u', "\xC2\xA1Hola!", $a_matches, PREG_OFFSET_CAPTURE);

Магия, благодаря комментарию вhttp://www.php.net/manual/es/function.preg-match.php#95828

Интересно, хотя я думаю, что вам нужно начальное/ перед(*UTF8), Это не работает в моей системе, но может в других. Что это выводит, когда вы делаетеecho $a_matches[0][1];? JW.
Не меняет смещения для меня, но это интересно знать. Исходная документация для этой «функции»:pcre.org/pcre.txt BurninLeo
Я использовал это так на PHP 5.4.29, работает как шарм:preg_match_all('/(*UTF8)[^A-Za-z0-9\s]/', $txt, $matches); Novalis
Я считаю, что это ответ, работал для меня. работал достаточно. Pooya
У меня не работает ни на PHP 5.6, ни на PHP 7 в Ubuntu 16.04.(*UTF8) до разделителя ошибка, после не имеет никакого эффекта. Я подозреваю, что это зависит от того, как / где вы получили свой PHP, в частности, настройки, которыеlibpcre* был скомпилирован с. user2609094
34

u Модификатор позволяет интерпретировать как шаблон, так и тему как UTF-8, захваченные смещения по-прежнему считаются в байтах.

Вы можете использоватьmb_strlen чтобы получить длину в символах UTF-8, а не в байтах:

$str = "\xC2\xA1Hola!";
preg_match('/H/u', $str, $a_matches, PREG_OFFSET_CAPTURE);
echo mb_strlen(substr($str, 0, $a_matches[0][1]));
@ Tomalak человек, это 2012 и PHP все еще отстойплачевно в Юникоде. Michael Robinson
Человек, это 2010 и PHP все еще отстойплачевно в Юникоде. Tomalak
@jerdiggity Это 2018 год, и, как вы уже догадались, PHP все еще отстойплачевно в Юникоде. Harry Sadler
@MichaelRobinson мужик, это 2014, а PHP все еще отстойплачевно в Юникоде Luka Ramishvili
@pathros Сейчас 2016 и PHPВсе еще ужасно отстой в Unicode ... и PHP7 ничего не изменил в этом случае. Tomasz Kowalczyk

Похожие вопросы