【问题标题】:PHP preg_replace() fails when a non UTF8 Character is detected检测到非 UTF8 字符时 PHP preg_replace() 失败
【发布时间】:2015-10-02 11:56:57
【问题描述】:

发现非 UTF 8 字符时 PHP 正则表达式失败!

我需要剥离 40,000 条数据库记录以从 custom_size mysql 表字段中获取宽度和高度值。

文件有各种不同的随机格式。

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

在找到一些非 UTF 8 字符的记录之前,下面的代码在 99% 的情况下都能正常工作。

31*3235”x21” 是 2 个示例。

当这些运行时,我得到这些 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>';         

    }

}

对此有什么想法吗?我无法重建服务器软件以添加支持


刚刚找到了一个可以转换为 UTF8 的 PHP 库的答案,这似乎很有帮助 https://stackoverflow.com/a/3521396/143030

【问题讨论】:

  • 如果您的输入不是 utf-8,为什么要使用 u 标志?而且该模式似乎不需要它。
  • @Jonny5:如果输入的是 Unicode 文本,u 标志是必须的,因为它会影响模式的解释方式。
  • 相关:stackoverflow.com/questions/10037336/… 顺便说一句,如果您发现其他问题解决了您的问题,您可以将您的问题作为重复项关闭,或者将其作为答案发布,而不是将解决方案编辑到问题。
  • @nhahtdh 他只匹配 ascii 字符 0-9x 和文字 . 没有区别。对于其他情况,我同意你的看法。此外,他正在使用 strtolower 函数,该函数不是为 utf-8 输入设计的 > 指向输入不是多字节的,否则将使用 mb_strtolower

标签: php regex utf-8 pcre


【解决方案1】:

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

例如,字符 U+201D: RIGHT DOUBLE QUOTATION MARK 在 UTF-8 中使用三个字节:

$a = '”';

for ($i=0; $i < strlen($a); $i++) {
    echo dechex(ord($a[$i])), ' ';
}

结果:

e2 80 9d

要在 PCRE 正则表达式引擎中启用多字节读取,您可以在模式的开头使用以下指令之一:(*UTF)(*UTF8)(*UTF16)(*UTF32) 或 u 修饰符 (打开可用的多字节模式,但这也将速记字符类的含义扩展为 unicode,例如 \s\d\w...。换句话说,u 修饰符是一个快捷方式对于 (*UTFx)(*UCP) 会更改字符类。)

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

您似乎不是这种情况,因为当您使用 u 修饰符时,您会获得以下显式消息:

this version of PCRE is not compiled with PCRE_UTF8 support

除非您决定使用支持 UTF8 编译的 PCRE 模块将 PHP 安装更改为 1,否则您无能为力。

但是,在您的情况下,这并不是真正的问题,因为在您的模式中,即使您的输入是 UTF8 编码,u 修饰符也是完全没用的。

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

Unicode  char   UTF8    Name
--------------------------------------------------------
U+007D     }       7d   RIGHT CURLY BRACKET
U+007E     ~       7e   TILDE
U+007F             7f   <control>
U+0080          c2 80   <control>
U+0081          c2 81   <control>
...
U+00BE     ¾    c2 be   VULGAR FRACTION THREE QUARTERS
U+00BF     ¿    c2 bf   INVERTED QUESTION MARK
U+00C0     À    c3 80   LATIN CAPITAL LETTER A WITH GRAVE
U+00C1     Á    c3 81   LATIN CAPITAL LETTER A WITH ACUTE
...

所以你可以写:

$string = preg_replace( '/[^0-9x.]+/', '', strtolower( $string ) );

(无需使用 i 修饰符,因为您的字符串已经是小写了。无需在字符类中转义点并使用捕获组。添加 + 量词可加快替换速度,因为几个连续字符在一个替换中被删除,而不是一个一个地删除。)

和:

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-02
    • 2015-05-16
    • 2010-11-11
    相关资源
    最近更新 更多