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

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

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

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

热门问答

腾讯云 COS 怎么才能外链调用 m3u8 到别的网站播放?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
设置公有读私有写:当访问对象时,COS 读取到对象的权限为公有读,此时无论存储桶为何种权限,对象都可以被直接下载 设置步骤 登录 对象存储控制台,选择左侧菜单栏【存储桶列表】,进入存储桶列表页面。单击需要修改对象权限的对应存储桶,进入存储桶。 📷 找到需要设置权限的对象(如 e...... 展开详请

Ubuntu搭建的WordPress如何修改php.ini?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
php新手很多不知道怎么查配置文件在哪,这里提供一个很简单的方法 使用 php -i 命令可以打印php的详细信息,可以把这堆东西输出一下 php -i > outputphp.txt,结合 grep 查找命令 php -i| grep php.ini 打印结果如下 Config...... 展开详请

归档存储采用的存储介质是什么, 安全可靠吗?

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
归档存储主要是针对海量、重要且访问频率极低的非结构化数据进行长期的归档保存和备份管理。 在数据安全层面,归档存储提供数据锁定机制,防止数据被修改和删除,保障数据安全。 技术架构: image.png 与对象存储的差异 归档存储 CAS 是一项离线存储服务,不同于在线的对象存储 ...... 展开详请

在按官网手册排错后依然提示1004错误?

看你的代码好像是短信相关的代码,1004错误代表请求包解析失败,通常情况下是由于没有遵守 API 接口说明规范导致的。 建议您通过以下方式定位解决: 首先,要确认发送的请求是否是标准的 json 格式; 第二,检查是否有将单引号当做双引号使用(json 标准应该是双引号); 第...... 展开详请

redis数据库应该怎样连接???

滑稽园扛把子

Swoole · PHP开发工程师 (已认证)

As a PHP Developer
推荐
实例初始化完成后,连接腾讯云Redis时,需要输入设置的密码。主从版和集群版的连接示例如下 主从版连接示例 主从版支持2种格式 • 格式1,“实例id:密码”的格式类型,例如您的实例id是crs-bkuza6i3,设置的密码是abcd1234,则连接命令如下 redis-cli ...... 展开详请

如何使用holer实现从外网访问本地WEB应用?

Dingda

Dingda · 站长 (已认证)

多一些不为什么的坚持
推荐
解压holer软件 获取holer access key信息: 在holer官网上申请专属的holer access key或者使用开源社区上公开的access key信息。 启动holer服务: Windows系统平台: 打开CMD窗口进入可执行程序所在的目录下,执行命令:...... 展开详请

所属标签

扫码关注云+社区