正则表达式中的最大十六进制值问题怎么解决?

内容来源于 Stack Overflow,并遵循CC BY-SA 3.0许可协议进行翻译与使用

  • 回答 (5)
  • 关注 (0)
  • 查看 (109)

不使用u标记可以使用的十六进制范围是[\x{00}-\x{ff}],但是u将其标记为4字节值。\x{7fffffff}(二)[\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饰符应将字符串视为UTF-16

PCRE自8.30以来支持UTF-16。

echo PCRE_VERSION;

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

8.32 2012-11-30

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

8.31 2012-07-06

http://3v4l.org/CrPZ8

提问于
用户回答回答于

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

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

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

我不想再重复这个讨论了,所以如果你还不清楚,请阅读每个软件开发人员绝对、积极的绝对最低限度必须了解Unicode和字符集(没有借口!)...

用问题中的例子,?映射到代码点U+210C1,但是它可以被编码为F0 A1 83 81在UTF-8中,D844 DCC1在UTF-16和000210C1在UTF-32。

准确地说,上面的示例展示了如何将代码点映射到代码单元(字符编码形式)。如何将代码单元映射到八进制序列则是另一回事。见Unicode编码模型

PCRE 8位16位32位库

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

支持16位和32位库

PCRE除了默认的8位库之外,还包括8.30版本中的16位字符串和8.32版本中的32位字符串。

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

8位,16位,32位的意思

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

除非另有规定,本文件中对字节和UTF-8的引用应该被理解为16位数据单元和UTF-16在使用16位库时的引用,或者32位数据单元和UTF-32在使用32位库时的引用。关于16位和32位库的具体差异的更多细节,请在pcre16和pCre32页中给出。

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

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

PCRE为8位、16位和32位库提供了3组相同的API,并按前缀(pcre_,,,pcre16_pcre_32分别)。

16位和32位函数的操作方式与它们的8位对应函数相同;它们只对参数和结果使用不同的数据类型,它们的名称以pcre16_pcre32_而不是pcre_对于每一个以UTF 8命名的选项(例如,PCRE_UTF8),有相应的16位和32位名称,分别由UTF 16或UTF 32代替UTF 8。这个工具实际上只是表面的;16位和32位选项名定义了相同的位值。

在PCRE 2中,使用类似的函数命名约定。,其中8位/16位/32位函数具有_8_16_32后缀。只使用一个代码单元宽度的应用程序可以定义PCRE2_CODE_UNIT_WIDTH若要使用不带后缀的函数的泛型名称,请执行以下操作。

UTF模式与非UTF模式

设置UTF模式时(通过模式内选项)(*UTF)(*UTF8)(*UTF16)(*UTF32)1或编译选项PCRE_UTF8PCRE_UTF16PCRE_UTF32),所有数据单元的序列都被解释为Unicode字符序列,其中包括从U+0000到U+10 FFFF的所有代码点,但代理项和BOM除外。

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

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

另一方面,当未设置UTF模式(非UTF模式)时,所有操作直接工作于数据单元序列。在这种模式下,字符类匹配一个数据单元,除了可以存储在单个数据单元中的最大值外,对数据单元的值没有任何限制。该模式可用于二进制数据中的结构匹配。然而,处理Unicode字符时不要使用此模式。,除非对ASCII很好,而忽略其他语言。

字符值约束使用八进制或十六进制数字指定的字符仅限于某些值,如下所示: 8位非UTF模式小于0x100 8位UTF-8模式小于0x10ffff,有效编码点16位非UTF模式小于0x10000 16位UTF-16模式小于0x10ffff,有效编码点32位非UTF模式小于0x100000000 32位utf-32模式小于0x10ffff模式和有效编码点。 无效的Unicode编码点是0xd 800到0xdfff(所谓的“代理”代码点)和0 xffef。

PHP和PCRE

PCRE函数在PHP中,将特定于PHP的标志和调用转换为PCREAPI的包装器。(如PHP 5.6.10分支所示)。

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

因此,PHP中的PCRE函数期望:

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

这解释了问题中的行为:

  • 在非UTF模式下(没有u标志),十六进制regex转义序列中的最大值为ff(如[\x{00}-\x{ff}])
  • 在UTF模式中,任何超过0x10ffff的值(如\x{7fffffff})在十六进制正则表达式中,转义序列是完全没有意义的。

示例代码

此示例代码演示:

  • 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)

因为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其中“字符”是一个字节。因此,结果regex与序列匹配。F0 A1 83后跟字节81重复一次或多次。
  • "/?+/u"被验证并解释为一个utf-8字符序列。U+210C1 U+002B因此,结果regex与代码点匹配。U+210C1在UTF-8字符串中重复一次或多次。

匹配Unicode字符

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

再次,使用?例如,上面的示例显示了指定regex的两种方法:

"/\xf0\xa1\x83\x81+/u"
"/?+/u"

第一种方法不适用于单引号字符串--as\x在单引号中无法识别转义序列,库将接收字符串。\xf0\xa1\x83\x81+,它将与UTF模式相结合。U+00F0 U+00A1 U+0083紧随其后U+0081重复一次或多次。除此之外,阅读代码的下一个人也会感到困惑:他们怎么知道这是一个重复了一次或多次的Unicode字符?

第二种方法工作良好,甚至可以使用单引号字符串,但需要用utf-8编码保存文件,特别是使用字符的情况,如ÿ,因为字符在单字节编码中也是有效的。如果要匹配单个字符或字符序列,则此方法是选项。但是,作为字符范围的终点,可能不清楚要匹配的是什么。比较a-zA-Z0-9א-ת,相对于一-龥(与大多数CJK统一表意文字块(4E00-9FFF)除结尾处未分配的代码点外)或一-十(这是将数字从1到10匹配中文字符的错误尝试)。

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

"/\x{210C1}/u"
'/\x{210C1}/u'

当该文件保存在任何与ASCII兼容的编码中时,这是可行的,它同时适用于单引号和双引号字符串,并且在字符范围内给出了清晰的代码点。这种方法的缺点是不知道字符的样子,而且在指定Unicode字符序列时也很难读懂。

热门问答

腾讯云广州一区DNS变更,需要怎么操作?

思潮澎湃轻描淡写的生活,但思潮澎湃
推荐
我也收到相关的通知了,这里分享下~ 2019年1月31日,腾讯云将对广州地区旧的基础网络DNS服务器(10.225.30.181、10.225.30.223)进行下线。在此期间,腾讯云提供最新的DNS服务器供您更新使用。 我们建议您尽快将DNS服务器配置进行更新,并且我们为您提供...... 展开详请

CMQ创建队列成功,紧接着发送消息,报队列不存在?

CreateQueue成功后,创建队列的时间为1s,您可以等待下在sendMessage

云呼叫中心只能用户自己开发吗?

腾讯云通信团队

腾讯 · 腾讯云通信团队 (已认证)

腾讯高级产品经理
推荐

目前呼叫中心只有API文档,需要用户自己开发。如果用户需要saas系统的呼叫中心可以使用智能外呼机器人:https://cloud.tencent.com/product/ccsr

ios端推流setRenderRotation无效?

西风

renzha.net · 站长 (已认证)

www.renzha.net
推荐

你有没有调整观众端表现,即通过对 LivePushConfig 中的homeOrientation设置项进行配置,它控制的是观众端看到的视频宽高比是16:9还是6:19,调整后的结果可以用播放器查看以确认是否符合预期。

腾讯云直播 CNAME 记录添加 的 值是多少???

西风

renzha.net · 站长 (已认证)

www.renzha.net
推荐
第一步:域名备案 控制台进行域名提交管理前,需对域名进行备案,详情请查看 域名备案 和 域名备案和配置常见问题 文档。 第二步:添加域名 在视频直播菜单栏内选择【域名管理】,在域名管理页面可以看到已创建域名、类型、状态、添加时间和操作。 可添加和管理的域名类型有播放域名和推流域...... 展开详请

【建议】【API】使用API创建子网的时候允许指定已有路由表?

推荐

控制台使用的是新的接口,批量创建子网,https://cloud.tencent.com/document/product/215/31960,可以指定路由表。terraform开发的时候是基于api2.0开发的,还没有这个接口,因此暂时无法使用

所属标签

扫码关注云+社区