php 如何匹配编码为UTF8的ZWSP(零宽度空间)[重复]

vq8itlhq  于 2023-04-10  发布在  PHP
关注(0)|答案(2)|浏览(226)

此问题已在此处有答案

Remove or match a Unicode Zero Width Space PHP(2个答案)
Remove or replace a ZERO WIDTH NON-JOINER character(2个答案)
5天前关闭。
我在匹配/替换编码为UTF8的ZWSP unicode时遇到一些问题

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

作为一个额外的测试用例,我使用了NBSP(非中断空格),它可以按预期工作
所有preg_replace都处于UTF8模式/u

  • 当匹配NBSP时,它按预期工作。输入编码为UTF8,输出为空(NBSP unicode替换为空字符串)
  • 当匹配ZWSP时,它在ZWSP输入是UTF8编码的情况下工作。
  • 如果您将ZWSP模式更改为UTF8编码版本并将输入保持为UTF8,则它也不工作

Q:那么如何在UTF8中匹配ZWSP?

还是这是一个bug?
代码

$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
wljmcqd8

wljmcqd81#

您可以使用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字符串

你可以使用\u{XXXX}来定义一个代码点。这只适用于双引号字符串。正如你在输出中看到的,模式包含UTF-8编码的实际unicode字符。字符类只显示一些空格。这也适用于输入字符串。它可以写成"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: |-|"
}
xzlaal3s

xzlaal3s2#

像很多人一样,你似乎对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修饰符,关于the manual says
模式和主题字符串被视为UTF-8。
然后,您使用\xhh表示法进行匹配,其中is described under escape sequences
在“\x”之后,最多读取两个十六进制数字(字母可以是大写或小写)。在UTF-8模式下,允许使用“\x{...}”,其中大括号的内容是十六进制数字的字符串。它被解释为UTF-8字符,其代码号是给定的十六进制数。原始的十六进制转义序列,\xhh,如果值大于127,则匹配双字节UTF-8字符。
这有点令人困惑,但它说通常情况下,\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,“零宽度空间”。
因此,either 将字符串视为二进制,不要使用/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);

[ Live Demo ]

相关问题