不使用u
标记,可以使用的十六进制范围是[\x{00}-\x{ff}]
,但是u
标记它会上升到4字节值\x{7fffffff}
({{ 1}})。
所以,如果我执行以下代码:
[\x{00000000}-\x{7fffffff}]
会出现此错误:
preg_match("/[\x{00000000}-\x{80000000}]+/u", $str, $match);
所以我无法将Warning: preg_match(): Compilation failed: character value in \x{...} sequence is too large
之类的字母与等效的十六进制值匹配。问题不在于如何匹配这些字母,而是如何匹配这些字母&此边界来自
f0 a1 83 81
修饰符应将字符串视为u
PCRE supports UTF-16 since v8.30
UTF-16
PCRE版本,PHP 5.3.24 - 5.3.28,5.4.14 - 5.5.7:
echo PCRE_VERSION;
PCRE版本,PHP 5.3.19 - 5.3.23,5.4.9 - 5.4.13:
8.32 2012-11-30
答案 0 :(得分:8)
Unicode是一个字符集,它指定从字符到代码点的映射,字符编码(UTF-8,UTF-16,UTF-32)指定如何存储Unicode代码点。
在Unicode中,字符映射到单个代码点,但根据编码方式的不同,它可以有不同的表示形式。
我不想重新讨论这个讨论,所以如果您仍然不清楚这一点,请阅读The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)。
使用问题中的示例,会映射到代码点
U+210C1
,但可以在UTF-8中编码为F0 A1 83 81
,在UTF中编码为D844 DCC1
16和UTF-32中的000210C1
。
确切地说,上面的示例显示了如何将代码点映射到代码单元(字符编码形式)。代码单元如何映射到八位位组序列是另一回事。见Unicode encoding model
由于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位是指数据单元(代码单元)。
本文档中对字节和UTF-8的引用应在使用16位库时读取为16位数据单元和UTF-16的引用,或在使用32时读取为32位数据单元和UTF-32比特库,除非另有说明。有关16位和32位库的具体差异的更多详细信息,请参见pcre16和pcre32页面。
这意味着8位/ 16位/ 32位库需要模式和输入字符串为8位/ 16位/ 32位数据单元或有效UTF-8 / UTF的序列-16 / UTF-32字符串。
PCRE为8位,16位和32位库提供了3组相同的API,分别由前缀(pcre_
,pcre16_
和pcre_32
区分。)< / p>
16位和32位函数的运行方式与它们的8位函数相同;他们只是为他们的参数和结果使用不同的数据类型,他们的名字以
pcre16_
或pcre32_
而不是pcre_
开头。对于名称中包含UTF8的每个选项(例如,PCRE_UTF8
),都有相应的16位和32位名称,UTF8分别由UTF16或UTF32替换。这个设施实际上只是化妆品; 16位和32位选项名称定义相同的位值。
在PCRE2中,a similar function naming convention is used,其中8位/ 16位/ 32位函数分别具有_8
,_16
,_32
后缀。只使用一个代码单元宽度的应用程序可以定义PCRE2_CODE_UNIT_WIDTH
以使用没有后缀的函数的通用名称。
设置UTF模式时(通过模式内选项(*UTF)
,(*UTF8)
,(*UTF16)
,(*UTF32)
1 或编译选项{ {1}},PCRE_UTF8
,PCRE_UTF16
),所有数据单元序列都被解释为Unicode字符序列,其中包含从U + 0000到U + 10FFFF的所有代码点,代理项和BOM。
1 内嵌式选项PCRE_UTF32
,(*UTF8)
,(*UTF16)
仅在相应的库中可用。您不能在8位库中使用(*UTF32)
,也不能在任何不匹配的组合中使用(*UTF16)
,因为它根本没有意义。 (*UTF)
在所有库中都可用,并提供了一种在模式中指定UTF模式的可移植方式。
在UTF模式下,模式(数据单元序列)通过将序列解码为UTF-,将解释和验证作为Unicode代码点序列8 / UTF-16 / UTF-32数据(取决于所使用的API),在编译之前。在匹配过程中,输入字符串也被解释并可选地验证为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
无效的Unicode代码点是范围0xd800到0xdfff(所谓的&#34;代理&#34;代码点)和0xffef。
PHP中的PCRE functions由a wrapper which translates PHP-specific flags and calls into 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}
)的任何值都是无意义的。此示例代码演示:
// 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)
由于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
,其中&#34;字符&#34;是一个字节。因此,生成的正则表达式匹配序列F0 A1 83
,后跟字节81
重复一次或多次。"/+/u"
并将其解释为UTF-8字符序列U+210C1 U+002B
。因此,生成的正则表达式匹配UTF-8字符串中重复一次或多次的代码点U+210C1
。除非输入包含其他二进制数据,否则强烈建议始终打开u
模式。该模式可以访问所有工具以正确匹配Unicode字符,并且输入和模式都被验证为有效的UTF字符串。
再次,以为例,上面的示例显示了两种指定正则表达式的方法:
"/\xf0\xa1\x83\x81+/u"
"/+/u"
第一种方法不适用于单引号字符串 - 由于\x
转义序列在单引号中无法识别,因此库将收到字符串\xf0\xa1\x83\x81+
,它与UTF模式将匹配U+00F0 U+00A1 U+0083
,然后重复U+0081
一次或多次。除此之外,它也让下一个阅读代码的人感到困惑:他们怎么知道它是一个或多个重复的单个Unicode字符?
第二种方法运行良好,它甚至可以与单引号字符串一起使用,但您需要以UTF-8编码保存文件,尤其是像ÿ
这样的字符的情况,因为该字符也是在单字节编码中有效。如果要匹配单个字符或一系列字符,此方法是一个选项。但是,作为角色范围的终点,可能不清楚您要匹配的是什么。比较a-z
,A-Z
,0-9
,א-ת
,而不是一-龥
(与大多数CJK Unified Ideographs block (4E00–9FFF)相匹配,但未分配的代码点除外结束)或一-十
(这是一个不正确的尝试匹配中文字符的数字从1到10)。
第三种方法是直接在十六进制转义中指定代码点:
"/\x{210C1}/u"
'/\x{210C1}/u'
当文件以任何与ASCII兼容的编码保存时,可以使用单引号和双引号字符串,并且在字符范围内提供清晰的代码点。此方法的缺点是不知道字符的外观,并且在指定Unicode字符序列时也很难读取。
答案 1 :(得分:3)
所以我无法匹配像f0 a1的等效十六进制值这样的字母 83 81.问题不在于如何匹配这些字母,而是如何匹配这些字母 范围&amp;这个边界来自于u修饰符应该将字符串视为 UTF-16
你混合了两个导致这种混乱的概念。
F0 A1 83 81
不是角色的十六进制值。这就是方法
UTF-8对字节流中该字符的代码点进行编码。
PHP支持\x{}
模式的UTF-16代码点是正确的,但{
和}
中的值表示UTF-16代码点,而不是实际使用的字节数编码字节流中的给定字符。
因此,\x{}
可以使用的最大可能值实际为10FFFF
。
要与PHP匹配,您需要使用它的代码点,@ minitech在其评论中建议\x{0210c1}
。
"Validity of strings"中PCRE documentation部分引用的进一步说明。
在进行任何其他处理之前,将检查整个字符串。 除了检查字符串的格式外,还有一个检查 确保所有代码点都位于U + 0到U + 10FFFF的范围内, 不包括代理区域。所谓的“非字符”代码 不排除积分,因为Unicode更正#9表明了这一点 他们不应该这样。
Unicode的“代理区域”中的字符保留供使用 UTF-16,它们成对使用,用值编码代码点 大于0xFFFF。由UTF-16对编码的代码点 可单独使用UTF-8和UTF-32编码。 (在 换句话说,整个替代品是UTF-16的软糖 不幸的是混淆了UTF-8和UTF-32。)
答案 2 :(得分:1)
我不确定php,但代码点上确实没有调控器
所以只有大约110万个有效的东西并不重要
这在任何时候都可能发生变化,但它并非真正取决于发动机
强制执行。保留的cp是有效范围内的孔,
在有效范围内有代理人,原因是无穷无尽的
除了字号以外没有其他限制。
对于UTF-32,你不能超过31位,因为32是符号位
0x00000000 - 0x7FFFFFFF
有意义,因为unsigned int
作为数据类型是32位硬件寄存器的自然大小。
对于UTF-16,甚至更真实的是你可以看到屏蔽到16位的相同限制。
位32仍然是将0x0000 - 0xFFFF
作为有效范围的符号位。
通常,如果您使用支持ICU的引擎,您应该可以使用它,
它将源和正则表达式转换为UTF-32。 Boost Regex就是这样一个引擎。
编辑:
关于UTF-16
我猜当Unicode超过16位时,他们在16位范围内为代理对打了一个洞。但它在这对之间只留下了20个总位数。
每个代理中的10位,其他6位用于确定hi或lo 看起来这样会使Unicode人员的限制为20位+额外的0xFFFF四舍五入到总共0x10FFFF的代码点,并且有无法使用的漏洞。
能够将所有代码点转换为不同的编码(8/16/32)
实际上必须是可兑换的。因此永远向后兼容的20位是
他们陷入了陷阱,但现在必须忍受。
无论如何,正则表达式引擎不会很快实施此限制,可能永远不会。 就代理而言,它们是 hole ,并且不能在模式之间转换格式错误的文字代理。这仅适用于转换期间的文字编码字符,而不是一个十六进制表示。例如,它很容易以UTF-16(仅)模式搜索未配对的代理人的文本,甚至是配对的文本。
但我认为正则表达式引擎并不真正关心漏洞或限制,他们只关心主题字符串的模式。不,引擎不会说:
&#39;嘿等等,模式为UTF-16我最好将\x{210C1}
转换为\x{D844}\x{DCC1}
。等等,如果我这样做了,如果量化\x{210C1}+
,我该怎么办,开始在它周围注入正则表达式构造?更糟糕的是,如果它在课堂上[\x{210C1}]
怎么办? Nah ..更好地将其限制为\x{FFFF}
。
我使用的一些方便的花花公子,伪代码代理转换:
Definitions:
====================
10-bits
3FF = 000000 1111111111
Hi Surrogate
D800 = 110110 0000000000
DBFF = 110110 1111111111
Lo Surrogate
DC00 = 110111 0000000000
DFFF = 110111 1111111111
Conversions:
====================
UTF-16 Surrogates to UTF-32
if ( TESTFOR_SURROGATE_PAIR(hi,lo) )
{
u32Out = 0x10000 + ( ((hi & 0x3FF) << 10) | (lo & 0x3FF) );
}
UTF-32 to UTF-16 Surrogates
if ( u32In >= 0x10000)
{
u32In -= 0x10000;
hi = (0xD800 + ((u32In & 0xFFC00) >> 10));
lo = (0xDC00 + (u32In & 0x3FF));
}
Macro's:
====================
#define TESTFOR_SURROGATE_HI(hs) (((hs & 0xFC00)) == 0xD800 )
#define TESTFOR_SURROGATE_LO(ls) (((ls & 0xFC00)) == 0xDC00 )
#define TESTFOR_SURROGATE_PAIR(hs,ls) ( (((hs & 0xFC00)) == 0xD800) && (((ls & 0xFC00)) == 0xDC00) )
//
#define PTR_TESTFOR_SURROGATE_HI(ptr) (((*ptr & 0xFC00)) == 0xD800 )
#define PTR_TESTFOR_SURROGATE_LO(ptr) (((*ptr & 0xFC00)) == 0xDC00 )
#define PTR_TESTFOR_SURROGATE_PAIR(ptr) ( (((*ptr & 0xFC00)) == 0xD800) && (((*(ptr+1) & 0xFC00)) == 0xDC00) )
答案 3 :(得分:1)
正如minitech在第一条评论中建议的那样,你必须使用代码点 - 对于这个角色,它是\x{210C1}
。这也是UTF-32中的编码形式。
F0 AF AB BF
是UTF-8编码序列(请参阅http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=210C1)。
有一些版本的PCRE,您可以使用最多\x{7FFFFFFF}
的值。但我真的不知道可以与之相匹配的东西。
引用http://www.pcre.org/pcre.txt:
在UTF-16模式下,字符代码为Unicode,范围 0到 0x10ffff ,但0xd800到0xdfff范围内的值除外 因为那些是&#34;代理&#34;成对使用的值进行编码 值大于0xffff。
[...]
在UTF-32模式下,字符代码为Unicode,范围 0到 0x10ffff ,但0xd800到0xdfff范围内的值除外 因为那些是&#34;代理&#34;在UTF-32中形成错误的值。
0x10ffff
是您可以用来匹配角色的最大值(这是我从中提取的内容)。 0x10ffff
目前也是unicode标准中定义的最大代码点(请参阅What are some of the differences between the UTFs?) - 因此上面的每个值都没有任何意义(或者我只是没有得到它)......
答案 4 :(得分:-1)
“但想知道正则表达式中的最大十六进制边界”: *在所有utf模式下:0x10ffff *原生8-bt模式:0xff *本机16位模式:0xffff *本机32位模式:0x1fffffff