У меня возникли проблемы с сопоставлением/заменой юникода ZWSP, закодированного как UTF8
ZWSP: \x20\x0B
ZWSP (UTF8): \xE2\x80\x8B
В качестве дополнительного теста я использовал NBSP (неразрывный пробел), который работает, как и ожидалось.
Все preg_replace
в режиме UTF8 /u
При сопоставлении NBSP он работает как положено. Ввод кодируется как UTF8, а вывод пуст (юникод NBSP заменен пустой строкой)
При сопоставлении с ZWSP он работает только в том случае, если вход ZWSP не закодирован в UTF8.
Если вы измените шаблон ZWSP на кодированную версию UTF8 и сохраните ввод как 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
Как и многие люди, вы, кажется, не понимаете, что такое 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: |-|"
}
Вы можете использовать \u{XXXX}
для определения кода. Это будет работать только в строках с двойными кавычками. Как вы можете видеть в выводе, шаблон содержит фактические символы Юникода в кодировке UTF-8. Класс символов показывает только некоторое пространство. Это также будет работать для входной строки. Можно написать как "NBSP: |\u{00A0}|, ZWSP: |\u{200B}|"
.
Второй шаблон использует синтаксис 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: |-|"
}