正则表达式中的最大十六进制值

rev*_*evo 20 php regex preg-match

不使用u标志可以使用的十六进制范围是[\x{00}-\x{ff}],但是使用u标志它可以达到一个4字节的值\x{7fffffff}([\x{00000000}-\x{7fffffff}]).

所以,如果我执行以下代码:

preg_match("/[\x{00000000}-\x{80000000}]+/u", $str, $match);
Run Code Online (Sandbox Code Playgroud)

会得到这个错误:

Warning: preg_match(): Compilation failed: character value in \x{...} sequence is too large
Run Code Online (Sandbox Code Playgroud)

所以我无法匹配类似于 with equivalent hex value of f0 a1 83 81.问题不在于如何匹配这些字母,而是这个范围和这个边界如何来自u修饰符应该将字符串视为UTF-16

自v8.30以来,PCRE支持UTF-16

echo PCRE_VERSION;
Run Code Online (Sandbox Code Playgroud)

PCRE版本与PHP 5.3.24 - 5.3.28,5.4.14 - 5.5.7:

8.32 2012-11-30
Run Code Online (Sandbox Code Playgroud)

PCRE版本与PHP 5.3.19 - 5.3.23,5.4.9 - 5.4.13:

8.31 2012-07-06
Run Code Online (Sandbox Code Playgroud)

http://3v4l.org/CrPZ8

nha*_*tdh 8

Unicode和UTF-8,UTF-16,UTF-32编码

Unicode是一个字符集,它指定从字符到代码点的映射,字符编码(UTF-8,UTF-16,UTF-32)指定如何存储Unicode代码点.

在Unicode中,字符映射到单个代码点,但根据其编码方式,它可以具有不同的表示形式.

我不想重新讨论这个讨论,所以如果你还不清楚这一点,请阅读绝对最低每个软件开发人员,绝对必须知道Unicode和字符集(没有借口!).

使用问题中的示例, maps to the code point U+210C1,但可以按F0 A1 83 81UTF-8,D844 DCC1UTF-16和000210C1UTF-32编码.

确切地说,上面的示例显示了如何将代码点映射到代码单元(字符编码形式).代码单元如何映射到八位位组序列是另一回事.请参阅Unicode编码模型

PCRE 8位,16位和32位库

由于PHP尚未采用PCRE2(版本10.10),所引用的文本来自原始PCRE的文档.

支持16位和32位库

PCRE包括对版本8.30中的16位字符串和版本8.32中的32位字符串的支持,以及默认的8位库.

除了支持8位字符串外,PCRE还通过两个额外的库支持16位字符串(来自版本8.30)和32位字符串(来自版本8.32).它们可以与8位库一样构建,也可以代替8位库.[...]

8位,16位,32位的含义

这里的8位,16位和32位是指数据单元(代码单元).

在使用16位库时,应将本文档中对字节和UTF-8的引用读取为16位数据单元和UTF-16,或者在使用32位库时读取为32位数据单元和UTF-32 ,除非另有说明.有关16位和32位库的具体差异的更多详细信息,请参见pcre16和pcre32页面.

这意味着8位/ 16位/ 32位库期望模式和输入字符串是8位/ 16位/ 32位数据单元的序列,或者是有效的UTF-8/UTF-16/UTF-32字符串.

针对不同宽度的数据单元的不同API

PCRE提供3组相同的API的8位,16位和32位库,由前缀分化(pcre_,pcre16_pcre_32分别地).

16位和32位函数的运行方式与它们的8位函数相同; 他们只是为他们的参数和结果使用不同的数据类型,他们的名字以pcre16_pcre32_代替pcre_.对于名称中包含UTF8的每个选项(例如PCRE_UTF8),都有相应的16位和32位名称,UTF8分别由UTF16或UTF32替换.这个设施实际上只是化妆品; 16位和32位选项名称定义相同的位值.

在PCRE2,类似的功能的命名约定使用,其中的8位/ 16位/ 32位的函数_8,_16,_32后缀分别.仅使用一个代码单元宽度的应用程序可以定义PCRE2_CODE_UNIT_WIDTH使用不带后缀的函数的通用名称.

UTF模式与非UTF模式

当UTF模式被设置(通过在图案的选项(*UTF),(*UTF8),(*UTF16),(*UTF32)1或编译选项PCRE_UTF8,PCRE_UTF16,PCRE_UTF32),数据单元的所有序列被解释为Unicode字符序列,其由所有代码点从U + 0000到U + 10FFFF ,代理人和物料清单除外.

1所述的被图案选项(*UTF8),(*UTF16),(*UTF32)仅在相应的库中可用.您不能(*UTF16)在8位库中使用,也不能在任何不匹配的组合中使用,因为它根本没有意义.(*UTF)在所有库中都可用,并提供了一种在模式中指定UTF模式的可移植方式.

在UTF模式下,模式(数据单元序列)通过将序列解码为UTF-8/UTF-16/UTF-32数据(取决于所使用的API ),将其解释验证为Unicode代码点序列,在编译之前.在匹配过程中,输入字符串也被解释并可选地验证为Unicode代码点序列.在此模式下,字符类匹配一个有效的Unicode代码点.

另一方面,当未设置UTF模式(非UTF模式)时,所有操作都直接处理数据单元序列.在此模式下,字符类与一个数据单元匹配,除了可以存储在单个数据单元中的最大值之外,对数据单元的值没有限制.此模式可用于匹配二进制数据中的结构.但是,在处理Unicode字符时不要使用此模式,除非您使用ASCII并且忽略其余语言.

对字符值的约束

使用八进制或十六进制数字指定的字符仅限于某些值,如下所示:

8-bit non-UTF mode    less than 0x100
8-bit UTF-8 mode      less than 0x10ffff and a valid codepoint
16-bit non-UTF mode   less than 0x10000
16-bit UTF-16 mode    less than 0x10ffff and a valid codepoint
32-bit non-UTF mode   less than 0x100000000
32-bit UTF-32 mode    less than 0x10ffff and a valid codepoint
Run Code Online (Sandbox Code Playgroud)

无效的Unicode代码点是0xd800到0xdfff(所谓的"代理"代码点)和0xffef.

PHP和PCRE

PHP中的PCRE函数包装器实现,该包装器将特定于PHP的标志和调用转换为PCRE API(如PHP 5.6.10分支中所示).

源代码调用PCRE 8位库API(pcre_),因此传递给preg_函数的任何字符串都被解释为8位数据单元(字节)的序列.因此,即使构建了PCRE 16位和32位库,也无法通过PHP端的API访问它们.

因此,PHP中的PCRE函数需要:

  • ...非UTF模式下的字节数组(默认),库以8位"字符"读取,并编译以匹配8位"字符"的字符串.
  • ...一个包含UTF-8编码的Unicode字符串的字节数组,该库以Unicode字符读取并编译以匹配UTF-8 Unicode字符串.

这解释了问题中的行为:

  • 在非UTF模式(无u标志)中,十六进制正则表达式转义序列中的最大值为FF(如图所示[\x{00}-\x{ff}])
  • 在UTF模式下,\x{7fffffff}十六进制正则表达式转义序列中超过0x10ffff(类似)的任何值都是无意义的.

示例代码

此示例代码演示:

  • PHP字符串只是字节数组,并不了解编码.
  • PCRE功能中UTF模式和非UTF模式之间的差异.
  • PCRE函数调用8位库
// NOTE: Save this file as UTF-8

// Take note of double-quoted string literal, which supports escape sequence and variable expansion
// The code won't work correctly with single-quoted string literal, which has restrictive escape syntax
// Read more at: https://php.net/language.types.string
$str_1 = "\xf0\xa1\x83\x81\xf0\xa1\x83\x81";
$str_2 = "";
$str_3 = "\xf0\xa1\x83\x81\x81\x81\x81\x81\x81";

echo ($str_1 === $str_2)."\n";

var_dump($str_3);

// Test 1a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_1, $match);
print_r($match); // Only match 

// Test 1b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_2, $match);
print_r($match); // Only match  (same as 1a)

// Test 1c
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_3, $match);
print_r($match); // Match  and the five bytes of 0x81

// Test 2a
$match = null;
preg_match("/+/", $str_1, $match);
print_r($match); // Only match  (same as 1a)

// Test 2b
$match = null;
preg_match("/+/", $str_2, $match);
print_r($match); // Only match  (same as 1b and 2a)

// Test 2c
$match = null;
preg_match("/+/", $str_3, $match);
print_r($match); // Match  and the five bytes of 0x81 (same as 1c)

// Test 3a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_1, $match);
print_r($match); // Match two 

// Test 3b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_2, $match);
print_r($match); // Match two  (same as 3a)

// Test 4a
$match = null;
preg_match("/+/u", $str_1, $match);
print_r($match); // Match two  (same as 3a)

// Test 4b
$match = null;
preg_match("/+/u", $str_2, $match);
print_r($match); // Match two  (same as 3b and 4a)
Run Code Online (Sandbox Code Playgroud)

由于PHP字符串只是一个字节数组,只要文件在某些​​与ASCII兼容的编码中正确保存,PHP就会很乐意读取字节,而无需关心它最初的编码.程序员完全负责编码和正确解码字符串.

由于上述原因,如果您以UTF-8编码保存上面的文件,您将看到$str_1并且$str_2是相同的字符串.$str_1从转义序列解码,而$str_2从源代码逐字读取.其结果,"/\xf0\xa1\x83\x81+/u""/+/u"为下面的相同的字符串(也为壳体"/\xf0\xa1\x83\x81+/""/+/").

UTF模式和非UTF模式之间的区别清楚地显示在上面的示例中:

  • "/+/"被视为一个字符序列,F0 A1 83 81 2B其中"字符"是一个字节.因此,生成的正则表达式匹配序列,F0 A1 83后跟字节81重复一次或多次.
  • "/+/u"被验证并解释为UTF-8字符序列U+210C1 U+002B.因此,生成的正则表达式匹配U+210C1UTF-8字符串中重复一次或多次的代码点.

匹配Unicode字符

除非输入包含其他二进制数据,否则强烈建议始终打开u模式.该模式可以访问所有工具以正确匹配Unicode字符,并且输入和模式都被验证为有效的UTF字符串.

再次,使用 as example, the example above shows two ways to specify the regex:

"/\xf0\xa1\x83\x81+/u"
"/+/u"
Run Code Online (Sandbox Code Playgroud)

The first method doesn't work with single-quoted string -- as \x转义序列不在单引号的认可,该库将收到的字符串\xf0\xa1\x83\x81+,这与UTF模式结合将匹配U+00F0 U+00A1 U+0083之后U+0081重复一次或多次.除此之外,下一个阅读代码的人也会感到困惑:他们怎么知道这是一个或多个重复的单个Unicode字符呢?

第二种方法效果很好,它甚至可以与单引号字符串一起使用,但是您需要以UTF-8编码保存文件,尤其是字符的情况ÿ,因为字符在单字节编码中也是有效的.如果要匹配单个字符或一系列字符,此方法是一个选项.但是,作为角色范围的终点,可能不清楚您要匹配的是什么.比较a-z,A-Z,0-9,?-?,相对于?-?(其最匹配的CJK统一汉字块(4E00-9FFF)除了在端未分配的代码点)或?-?(这是一个不正确的尝试匹配中国字符数从1到10).

第三种方法是直接在十六进制转义中指定代码点:

"/\x{210C1}/u"
'/\x{210C1}/u'
Run Code Online (Sandbox Code Playgroud)

当文件以任何与ASCII兼容的编码保存时,这适用于单引号和双引号字符串,并且还在字符范围内提供清晰的代码点.此方法的缺点是不知道字符的外观,并且在指定Unicode字符序列时也很难读取.


Ibr*_*jar 4

所以我无法匹配类似 f0 a1 83 81 的等效十六进制值的字母。问题不是如何匹配这些字母,而是这个范围和这个边界是如何来自的,因为 u 修饰符应该将字符串视为 UTF-16

您混合了两个概念,这导致了这种混乱。

F0 A1 83 81不是字符的十六进制值。这是 UTF-8 对字节流中该字符的代码点进行编码的方式。

PHP 支持该模式的 UTF-16 代码点是正确的,但和\x{}中的值代表 UTF-16 代码点,而不是用于对字节流中给定字符进行编码的实际字节。{}

因此,您可以使用的最大可能值\x{}实际上是10FFFF

为了与 PHP 匹配,您需要使用它的代码点,正如 @minitech 在他的评论中建议的那样\x{0210c1}

进一步的解释引用自PCRE 文档的“字符串有效性”部分。

在进行任何其他处理之前,将检查整个字符串。除了检查字符串的格式之外,还进行检查以确保所有代码点都位于 U+0 到 U+10FFFF 范围内(不包括代理区域)。所谓的“非字符”代码点并未被排除,因为 Unicode 勘误表 #9 明确表示不应排除它们。

Unicode 的“代理区域”中的字符保留供 UTF-16 使用,它们成对使用以对值大于 0xFFFF 的代码点进行编码。由 UTF-16 对编码的代码点在 UTF-8 和 UTF-32 编码中独立可用。(换句话说,整个替代品都是 UTF-16 的捏造,不幸的是,它混淆了 UTF-8 和 UTF-32。)