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

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

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

不使用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字符序列时也很难读懂。

热门问答

腾讯云GPU服务器不能联外网吗?

小爱同学

腾讯云 · 技术支持 (已认证)

推荐
腾讯云GPU服务器可连外网,GPU 云服务器提供和标准CVM 云服务器一致的方便快捷的管理方式。 图片.png GPU云服务器作为CVM云服务器的一类特殊实例,购买、 操作、维护等方式与CVM云服务器一致 图片.png GPU 云服务器(GPU Cloud Computin...... 展开详请

win服务器怎么给文件夹配置755权限?

推荐
下面以腾讯云win服务器(Windows Server 2016 数据中心版 64位中文版)为文件夹配置755权限为例 1.右击【属性】 图片.png 2 .选择【安全】- 【编辑】 图片.png 3. 可对当前文件进行755权限配置 图片.png 要修改某个文件的权...... 展开详请

腾讯云sdk 兼容JDK6?

推荐

如果你说的是https://cloud.tencent.com/document/sdk/Java的话,jdk最低版本是1.7,不支持1.6

android 离线推送 为什么setOfflinePushListener不回调?

嗨喽你好摩羯座
推荐
您好,使用云通信 IM SDK 的通知栏提醒,建议参考:https://cloud.tencent.com/document/product/269/9234 中的描述来操作,通知栏提醒的内容由类 TIMOfflinePushNotification 来定义,可以通过这个类对外...... 展开详请

为什么cmq的topic配置订阅者为queue,向topic发送消息无法到达queue?

是的, 向topic发送消息应该会立即投递到订阅者。您可以检查您配置的队列名称是否正确且是真实存在的队列。如还不能解决您的问题,您可以点击控制台右上角的“工单”,进行问题进一步的排查,腾讯云会有专业的售后24小时为您服务。

脏字过滤只支持 TIMTextElem 消息,对自定义消息无效,请问有接口可以主动检查吗?

学生路人
推荐
您好,脏字检查的内容包括单聊和群组消息(只检查文本消息 TIMTextElem,不支持对自定义消息 TIMCustomElem 的过滤)、群名片、群组资料(群名称、群简介、群公告)用户资料和好友关系链中 bytes 类型的数据(如昵称、好友备注和好友分组等)。目前没有这样的接口喔...... 展开详请

所属标签

扫码关注云+社区