Как сопоставить ZWSP (пробел нулевой ширины), закодированный как UTF8

У меня возникли проблемы с сопоставлением/заменой юникода ZWSP, закодированного как UTF8

ZWSP: \x20\x0B
ZWSP (UTF8): \xE2\x80\x8B

В качестве дополнительного теста я использовал NBSP (неразрывный пробел), который работает, как и ожидалось.

Все preg_replace в режиме UTF8 /u

  • При сопоставлении NBSP он работает как положено. Ввод кодируется как UTF8, а вывод пуст (юникод NBSP заменен пустой строкой)

  • При сопоставлении с ZWSP он работает только в том случае, если вход ZWSP не закодирован в UTF8.

  • Если вы измените шаблон ZWSP на кодированную версию UTF8 и сохраните ввод как UTF8, он также не будет работать.

Q: Тогда как сопоставить ZWSP в UTF8?

...или это баг?

код

$nbsp       = '\xA0'; // Non-breaking space
$zwsp       = '\x20\x0B'; // Zero-width space
$zwsp_utf8  = '\xE2\x80\x8B';

$input_nbsp_utf8    = "\xC2\xA0";
$input_zwsp         = "\x20\x0B";
$input_zwsp_utf8    = "\xE2\x80\x8B";

// NBSP
echo "NBSP\n-----\n";
echo "in: $input_nbsp_utf8--\nhex: ".bin2hex($input_nbsp_utf8)."\n";
$output = preg_replace('/'.$nbsp.'/u', '', $input_nbsp_utf8);
echo "out: $output--\nhex: ".bin2hex($output)."\n\n";

// ZWSP (input: **not** UTF8)
echo "ZWSP (input: **not** UTF8)\n-----\n";
echo "in: $input_zwsp--\nhex: ".bin2hex($input_zwsp)."\n";
$output = preg_replace('/'.$zwsp.'/u', '', $input_zwsp);
echo "out: $output--\nhex: ".bin2hex($output)."\n\n";

// ZWSP (input: UTF8)
echo "ZWSP (input: UTF8)\n-----\n";
echo "in: $input_zwsp_utf8--\nhex: ".bin2hex($input_zwsp_utf8)."\n";
$output = preg_replace('/'.$zwsp.'/u', '', $input_zwsp_utf8);
echo "out: $output--\nhex: ".bin2hex($output)."\n\n";

// ZWSP (pattern: UTF8, input: UTF8)
echo "ZWSP (pattern: UTF8, input: UTF8)\n-----\n";
echo "in: $input_zwsp_utf8--\nhex: ".bin2hex($input_zwsp_utf8)."\n";
$output = preg_replace('/'.$zwsp_utf8.'/u', '', $input_zwsp_utf8);
echo "out: $output--\nhex: ".bin2hex($output)."\n\n";

Выход

NBSP
-----
in:  --
hex: c2a0
out: --
hex:

ZWSP (input: **not** UTF8)
-----
in:
     --
hex: 200b
out: --
hex:

ZWSP (input: UTF8)
-----
in: ​--
hex: e2808b
out: ​--
hex: e2808b // Output should be empty

ZWSP (pattern: UTF8, input: UTF8)
-----
in: ​--
hex: e2808b
out: ​--
hex: e2808b // Output should be empty
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
0
55
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Как и многие люди, вы, кажется, не понимаете, что такое UTF-8. UTF-8 не является параметром, который может быть включен или выключен, это один из многих способов преобразования текста в двоичные данные и интерпретация этих двоичных данных для получения обратно текста.

Я не уверен, откуда взялся \x20\x0B или какое это имеет отношение к чему-либо, но сказать что-то «не UTF-8» — это то же самое, что сказать слово «не французское» или кусок мяса «не курица». .

Не обращая внимания на эту часть, давайте посмотрим на ключевой фрагмент кода:

$input_zwsp_utf8 = "\xE2\x80\x8B";
$output = preg_replace('/\xE2\x80\x8B/u', '', $input_zwsp_utf8);

Вы указали модификатор /u, о котором в инструкции написано:

Строки шаблона и темы обрабатываются как UTF-8.

Затем вы сопоставили, используя нотацию \xhh, которая описана в управляющих последовательностях:

После "\x" читается до двух шестнадцатеричных цифр (буквы могут быть в верхнем или нижнем регистре). В режиме UTF-8 допускается "\x{...}", где содержимое фигурных скобок представляет собой строку шестнадцатеричных цифр. Он интерпретируется как символ UTF-8, кодовый номер которого является заданным шестнадцатеричным числом. Исходная шестнадцатеричная escape-последовательность \xhh соответствует двухбайтовому символу UTF-8, если значение больше 127.

Это немного сбивает с толку, но это говорит о том, что обычно \xE2 будет соответствовать двоичному байту E2, то есть 11100010; но с активным /u вместо этого он будет соответствовать кодовой точке Unicode U+00E2, которая является «строчной латинской буквой a с циркумфлексом».

Пример:

$input = 'â';

echo "in: $input\nhex: ".bin2hex($input)."\n";
$output = preg_replace('/\xE2/u', '', $input);
echo "out: $output\nhex: ".bin2hex($output)."\n\n";

Выход:

in: â
hex: c3a2
out: 
hex:

То, что он не будет соответствовать, - это кодовая точка Unicode U+200B, «пробел нулевой ширины».

Итак, либо обрабатывайте свою строку как двоичную, не используйте модификатор /u и ищите ожидаемую строку байтов:

$input_zwsp_utf8 = "\xE2\x80\x8B";
$output = preg_replace('/\xE2\x80\x8B/', '', $input_zwsp_utf8);

Или обработайте свою строку как UTF-8 и найдите интересующую вас кодовую точку:

$input_zwsp_utf8 = "\xE2\x80\x8B";
$output = preg_replace('/\x{200B}/u', '', $input_zwsp_utf8);

[Живая демонстрация]

Вы можете сопоставить строку UTF-8, используя регулярное выражение режима ASCII - в этом случае вы сопоставляете отдельные байты. Если вы используете регулярное выражение в режиме UTF-8, ввод ДОЛЖЕН быть допустимой строкой UTF-8.

Совпадение кодовых точек

// input with UTF-8 byte sequences
$input    = "NBSP: |\xC2\xA0|, ZWSP: |\xE2\x80\x8B|";
$patterns = [
    // codepoint defined in PHP string literal with \u{}
    "([\u{00A0}\u{200B}])u",
    // codepoint defined in PCRE with \x{}
    '([\\x{00A0}\\x{200B}])u',
];

foreach ($patterns as $pattern) {
    var_dump(
        [
            'pattern' => $pattern,
            'input' => $input,
            'output' => preg_replace($pattern, '-', $input),
        ]
    );
}

Выход:

array(3) {
  ["pattern"]=>
  string(10) "([ ​])u"
  ["input"]=>
  string(23) "NBSP: | |, ZWSP: |​|"
  ["output"]=>
  string(20) "NBSP: |-|, ZWSP: |-|"
}
array(3) {
  ["pattern"]=>
  string(21) "([\x{00A0}\x{200B}])u"
  ["input"]=>
  string(23) "NBSP: | |, ZWSP: |​|"
  ["output"]=>
  string(20) "NBSP: |-|, ZWSP: |-|"
}

Строковые литералы PHP

Вы можете использовать \u{XXXX} для определения кода. Это будет работать только в строках с двойными кавычками. Как вы можете видеть в выводе, шаблон содержит фактические символы Юникода в кодировке UTF-8. Класс символов показывает только некоторое пространство. Это также будет работать для входной строки. Можно написать как "NBSP: |\u{00A0}|, ZWSP: |\u{200B}|".

Определение кодовой точки PCRE

Второй шаблон использует синтаксис PCRE для кодовой точки: \x{XXXX}. \ следует экранировать в строковых литералах PHP (здесь запасной вариант, но всегда лучше быть явным). Вы можете увидеть определение кодовой точки в выводе шаблона.

Соответствующие байты

Вы можете сопоставить байты. В этом случае регулярное выражение не будет в UTF-8, а входная строка будет обрабатываться как байты. Это имеет некоторые последствия, такие как то, что вы не можете использовать классы символов - в этом режиме они соответствуют только одиночным байтам.

$input    = "NBSP: |\xC2\xA0|, ZWSP: |\xE2\x80\x8B|";
$patterns = [
    // matching as bytes
    '((?:\\xC2\\xA0|\\xE2\\x80\\x8B))',
];

foreach ($patterns as $pattern) {
    var_dump(
        [
            'pattern' => $pattern,
            'input' => $input,
            'output' => preg_replace($pattern, '-', $input),
        ]
    );
}

Выход:

array(3) {
  ["pattern"]=>
  string(27) "((?:\xC2\xA0|\xE2\x80\x8B))"
  ["input"]=>
  string(23) "NBSP: | |, ZWSP: |​|"
  ["output"]=>
  string(20) "NBSP: |-|, ZWSP: |-|"
}

Другие вопросы по теме