当检测到非UTF8字符时,PHP preg_replace()失败

当检测到非UTF8字符时,PHP preg_replace()失败,php,regex,utf-8,pcre,Php,Regex,Utf 8,Pcre,找到非UTF 8字符时PHP正则表达式失败 我需要剥离40000条数据库记录,从custom\u sizemysql表字段中获取宽度和高度值 该字段有各种不同的随机格式 最可靠的方法是从x的左右两侧获取一个数值,并从中删除所有非数值 下面的代码在99%的时间内运行良好,直到找到一些非UTF 8字符的记录 31*32和35“x21”是两个示例 当这些都运行时,我得到这些PHP错误和脚本暂停 Warning: preg_replace(): Compilation failed: this vers

找到非UTF 8字符时PHP正则表达式失败

我需要剥离40000条数据库记录,从
custom\u size
mysql表字段中获取宽度和高度值

该字段有各种不同的随机格式

最可靠的方法是从
x
的左右两侧获取一个数值,并从中删除所有非数值

下面的代码在99%的时间内运行良好,直到找到一些非UTF 8字符的记录

31*32
35“x21”
是两个示例

当这些都运行时,我得到这些PHP错误和脚本暂停

Warning: preg_replace(): Compilation failed: this version of PCRE is not compiled with PCRE_UTF8 support at offset 1683977065 on line 21

Warning: preg_match(): Compilation failed: this version of PCRE is not compiled with PCRE_UTF8 support at offset 0 on line 24
演示:

<?php

$strings = array(

    '12x12',
    '172.61 cm x 28.46 cm',
    '31"x21"',
    '1"x1"',
    '31*32',
    '35”x21”'
);


foreach($strings as $string){

    if($string != ''){

        $string = str_replace('”','"',$string);

        // Strip out all characters except for numbers, letter x, and decimal points
        $string = preg_replace( '/([^0-9x\.])/ui', '', strtolower( $string ) );

        // Find anything that fits the number X number format
        preg_match( '/([0-9]+(\.[0-9]+)?)x([0-9]+(\.[0-9]+)?)/ui', $string, $values ); 

        echo 'Original value: ' .$string.'<br>';
        echo 'Width: ' .$values[1].'<br>';
        echo 'Height: ' .$values[3].'<br><hr><br>';         

    }

}

默认情况下,PCRE正则表达式引擎每次读取一个字节的字符串,因此,默认情况下,当使用UTF-8等多字节编码时,它忽略可能构成单个字符的字节序列,并将它们视为分离的字节(一个字节,一个字符)

例如,字符U+201D:RIGHT双引号在UTF-8中使用了三个字节:

$a = '”';

for ($i=0; $i < strlen($a); $i++) {
    echo dechex(ord($a[$i])), ' ';
}
要在PCRE正则表达式引擎中启用多字节读取,可以在模式开头使用以下指令之一:
(*UTF)
(*UTF8)
(*UTF16)
(*UTF32)
,或u修饰符(这将打开可用的多字节模式,但也扩展了速记字符类的含义,如
\s
\d
\w
…到unicode。换句话说,u修饰符是更改字符类的
(*UTFx)
(*UCP)
的快捷方式。)

但是这些特性只有在PCRE模块是在这些编码的支持下编译的情况下才可用(大多数默认PHP安装都是这种情况,但它不是绝对系统的或强制性的)

似乎情况并非如此,因为当您使用u修饰符时,您会获得以下明确信息:

此版本的PCRE未使用PCRE\u UTF8支持编译
除非您决定使用UTF8支持编译的PCRE模块将PHP安装更改为1,否则您无法对此进行任何操作

然而,在您的情况下这并不是一个真正的问题,因为在您的模式中,u修饰符是完全无用的,即使您的输入是UTF8编码的

原因是您的两种模式仅使用ASCII文字字符(00-7F范围内的字符),并且UTF8编码中超出ASCII范围的字符从不使用此范围内的字节:

Unicode字符UTF8名称
--------------------------------------------------------
U+007D}7d右花括号
U+007E~7e瓷砖
U+007F 7f
U+0080 c2 80
U+0081 c2 81
...
U+00BE¾c2为普通分数四分之三
U+00BF?c2 bf倒问号
U+00C0Àc3 80拉丁文大写字母A随以坟墓
U+00C1Ác3 81带锐音符的拉丁文大写字母A
...
所以你可以写:

$string = preg_replace( '/[^0-9x.]+/', '', strtolower( $string ) );
(无需使用i修饰符,因为您的字符串已经是小写的。无需转义字符类中的点并使用捕获组。添加
+
量词可以加快替换速度,因为在一次替换中会删除多个连续字符,而不是一个接一个。)

以及:


如果您的输入不是utf-8,为什么要使用
u
标志?模式似乎也不需要它。@Jonny5:如果输入是Unicode文本,
u
标志是必须的,因为它会影响模式的解释方式。相关:顺便说一句,如果您发现其他问题解决了您的问题,您可以作为dupl关闭您的问题复制或将其作为答案发布,而不是将解决方案编辑到问题中。@nhahtdh他只匹配ascii字符
0-9
x
和文本
没有区别。对于其他情况,我同意你的看法。此外,他使用的
strtolower
函数不是为utf-8输入>点设计的g输入不是多字节的,否则将使用
mb\u strtolower
if (preg_match('/([0-9]+(?:\.[0-9]+)?)x([0-9]+(?:\.[0-9]+)?)/', $string, $values)) {
    echo 'Original value: ', $string, '<br>';
    echo 'Width: ', $values[1], '<br>';
    echo 'Height: ', $values[2], '<br><hr><br>';
}
$a = preg_replace('/^./', '', '”abc');

for ($i=0; $i < strlen($a); $i++) {
    echo ' ', dechex(ord($a[$i]));
}
 80 9d 61 62 63
# �  �  a  b  c