提问人:clarkk 提问时间:4/4/2023 最后编辑:clarkk 更新时间:4/5/2023 访问量:408
如何匹配编码为 UTF8 的 ZWSP(零角空间)[重复]
How to match ZWSP (Zero-width space) encoded as UTF8 [duplicate]
问:
我在匹配/替换编码为 UTF8 的 ZWSP unicode 时遇到了一些问题
ZWSP: \x20\x0B
ZWSP (UTF8): \xE2\x80\x8B
作为额外的测试用例,我使用了 NBSP(不间断空格),它按预期工作
全部处于 UTF8 模式preg_replace
/u
匹配NBSP时,它按预期工作。输入编码为 UTF8,输出为空(NBSP unicode 替换为空字符串)
匹配 ZWSP 时,仅当 ZWSP 输入未进行 UTF8 编码时,它才有效。
如果将 ZWSP 模式更改为 UTF8 编码版本并将输入保留为 UTF8,则它也不起作用
Q: 那么如何在 UTF8 中匹配 ZWSP?
...还是这是一个错误?
法典
$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 不是一个打开或关闭的设置,它是将文本转换为二进制数据并解释该二进制数据以取回文本的许多不同方法之一。
我不确定它是从哪里来的,或者它与任何事情有什么关系,但说某样东西“不是 UTF-8”就像说一个词“不是法语”,或者一块肉“不是鸡肉”。\x20\x0B
忽略这部分,让我们看一下关键的代码段:
$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 字符,其代码号是给定的十六进制数。如果值大于 127,则原始十六进制转义序列 \xhh 将匹配双字节 UTF-8 字符。
这有点令人困惑,但它是说通常,会匹配二进制字节,即 ;但使用 active,它将匹配 Unicode 码位,即“拉丁文小写字母 a with circumflex”。\xE2
E2
11100010
/u
U+00E2
例:
$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);
[现场演示]
您可以使用 ASCII 模式正则表达式匹配 UTF-8 字符串 - 在本例中,您匹配的是单独的字节。如果在 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 字符串文字
可用于定义代码点。这仅适用于双引号字符串。正如您在输出中看到的那样,该模式包含 UTF-8 编码中的实际 Unicode 字符。字符类仅显示一些空格。这也适用于输入字符串。它可以写成 .\u{XXXX}
"NBSP: |\u{00A0}|, ZWSP: |\u{200B}|"
PCRE 代码点定义
第二种模式是对代码点使用 PCRE 语法:。 应该在 PHP 字符串文字中转义(这里是一个回退,但显式总是好的)。您可以在模式输出中看到代码点定义。\x{XXXX}
\
匹配字节
您可以匹配字节。在这种情况下,正则表达式将不是 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: |-|"
}
评论