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
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)
Unicode是一个字符集,它指定从字符到代码点的映射,字符编码(UTF-8,UTF-16,UTF-32)指定如何存储Unicode代码点.
在Unicode中,字符映射到单个代码点,但根据其编码方式,它可以具有不同的表示形式.
我不想重新讨论这个讨论,所以如果你还不清楚这一点,请阅读绝对最低每个软件开发人员,绝对必须知道Unicode和字符集(没有借口!).
使用问题中的示例, maps to the code point
U+210C1
,但可以按F0 A1 83 81
UTF-8,D844 DCC1
UTF-16和000210C1
UTF-32编码.
确切地说,上面的示例显示了如何将代码点映射到代码单元(字符编码形式).代码单元如何映射到八位位组序列是另一回事.请参阅Unicode编码模型
由于PHP尚未采用PCRE2(版本10.10),所引用的文本来自原始PCRE的文档.
PCRE包括对版本8.30中的16位字符串和版本8.32中的32位字符串的支持,以及默认的8位库.
除了支持8位字符串外,PCRE还通过两个额外的库支持16位字符串(来自版本8.30)和32位字符串(来自版本8.32).它们可以与8位库一样构建,也可以代替8位库.[...]
这里的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字符串.
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)
,(*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并且忽略其余语言.
对字符值的约束
使用八进制或十六进制数字指定的字符仅限于某些值,如下所示:
Run Code Online (Sandbox Code Playgroud)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
无效的Unicode代码点是0xd800到0xdfff(所谓的"代理"代码点)和0xffef.
PHP中的PCRE函数由包装器实现,该包装器将特定于PHP的标志和调用转换为PCRE API(如PHP 5.6.10分支中所示).
源代码调用PCRE 8位库API(pcre_
),因此传递给preg_
函数的任何字符串都被解释为8位数据单元(字节)的序列.因此,即使构建了PCRE 16位和32位库,也无法通过PHP端的API访问它们.
因此,PHP中的PCRE函数需要:
这解释了问题中的行为:
u
标志)中,十六进制正则表达式转义序列中的最大值为FF(如图所示[\x{00}-\x{ff}]
)\x{7fffffff}
十六进制正则表达式转义序列中超过0x10ffff(类似)的任何值都是无意义的.此示例代码演示:
// 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+210C1
UTF-8字符串中重复一次或多次的代码点.除非输入包含其他二进制数据,否则强烈建议始终打开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字符序列时也很难读取.
所以我无法匹配类似 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}
。
在进行任何其他处理之前,将检查整个字符串。除了检查字符串的格式之外,还进行检查以确保所有代码点都位于 U+0 到 U+10FFFF 范围内(不包括代理区域)。所谓的“非字符”代码点并未被排除,因为 Unicode 勘误表 #9 明确表示不应排除它们。
Unicode 的“代理区域”中的字符保留供 UTF-16 使用,它们成对使用以对值大于 0xFFFF 的代码点进行编码。由 UTF-16 对编码的代码点在 UTF-8 和 UTF-32 编码中独立可用。(换句话说,整个替代品都是 UTF-16 的捏造,不幸的是,它混淆了 UTF-8 和 UTF-32。)
归档时间: |
|
查看次数: |
1934 次 |
最近记录: |