如何从PHP的UTF-8字符串中替换/删除4(+)字节字符


How to replace/remove 4(+)-byte characters from a UTF-8 string in PHP?

MySQL似乎不支持默认UTF-8字符集中超过3字节的字符。

那么,在PHP中,我如何在字符串中摆脱所有4(及更多)字节字符,并将它们替换为其他字符?

注意:您不应该只是删除,而是替换为替换字符U+FFFD以避免unicode攻击,主要是XSS:

http://unicode.org/reports/tr36/Deletion_of_Noncharacters

preg_replace('/['x{10000}-'x{10FFFF}]/u', "'xEF'xBF'xBD", $value);

由于4字节UTF-8序列总是以字节0xF0-0xF7开头,因此以下内容应该可以工作:

$str = preg_replace('/['xF0-'xF7].../s', '', $str);

或者,您可以在UTF-8模式下使用preg_replace,但这可能会更慢:

$str = preg_replace('/['x{10000}-'x{10FFFF}]/u', '', $str);

这可以工作,因为4字节的UTF-8序列用于从0x10000开始的补充Unicode平面中的代码点。

下面是一个例子:

<?php 
 mb_internal_encoding("UTF-8");
 //utf8 string,  13 bytes, 9 utf8 chars, 7 ASCII, 1 in latin1, 1 outside the BMP
 $str = "qué 'xF0'x9D'x92'xB3 tal"; 
 $array = mbStringToArray($str);
 print "str: [$str]  strlen:" . strlen($str) . " chars:" . count($array) . "'n";
 $str1 = "";
 foreach($array as $c) {
   //  print "$c : " .  strlen($c)  ."'n";
   $str1 .= strlen($c)<=3? $c : '?';
 }
 print "[$str1]'n";

 function mbStringToArray ($str) {
    if (empty($str)) return false;
    $len = mb_strlen($str);
    $array = array();
    for ($i = 0; $i < $len; $i++) {
        $array[] = mb_substr($str, $i, 1);
    }
    return $array;
 }

或者,更紧凑和高效一点:

<?php /// 
 mb_internal_encoding("UTF-8");
 //utf8 string,  13 bytes, 9 utf8 chars, 7 ASCII, 1 in latin1, 1 outside the BMP
 $str = "qué 'xF0'x9D'x92'xB3 tal";
 $str1 = trimOutsideBMP($str);
 print "original: [$str]'n";
 print "trimmed:  [$str1]'n";

 // Replaces non-BMP characters in the UTF-8 string by a '?' character 
 // Assumes UTF-8 default encoding ( if not sure, call first mb_internal_encoding("UTF-8"); )
 function trimOutsideBMP($str) {
    if (empty($str)) return $str;
    $len = mb_strlen($str);
    $str1 = '';
    for ($i = 0; $i < $len; $i++) {
        $c = mb_substr($str, $i, 1);
        $str1 .= strlen($c) <= 3 ? $c : '?';
    }
    return $str1;
 }

在试图解决我自己的问题时遇到了这个问题(Facebook将某些表情符号作为4字节字符,Amazon Mechanical Turk不接受4字节字符)。

我最终使用这个,不需要mbstring扩展:

function remove_4_byte($string) {
    $char_array = preg_split('/(?<!^)(?!$)/u', $string );
    for($x=0;$x<sizeof($char_array);$x++) {
        if(strlen($char_array[$x])>3) {
            $char_array[$x] = "";
        }
    }
    return implode($char_array, "");
}

下面的函数将3和4字节的字符从utf8字符串更改为'#':

function remove3and4bytesCharFromUtf8Str($str) {
        return preg_replace('/(['xF0-'xF7]...)|(['xE0-'xEF]..)/s', '#', $str);
    }

这是我过滤掉4字节字符的实现

$string = preg_replace_callback(
    '/./u',
    function (array $match) {
        return strlen($match[0]) >= 4 ? null : $match[0];
    },
    $string
);

您可以调整它并将null(删除字符)替换为一些替代字符串。您还可以用其他一些字节长度检查替换>= 4

另一个过滤器实现,更复杂。

它尝试音译为ASCII字符,否则使用unicode替换字符以避免XSS,例如。: <a href='java'uFEFFscript:alert("XSS")'>

$tr = preg_replace_callback('/(['x{10000}-'x{10FFFF}])/u', function($m){
    $c = iconv('ISO-8859-2', 'UTF-8',iconv('utf-8','ISO-8859-2//TRANSLIT//IGNORE', $m[1]));
    if($c == '')
        return '�';
    return $c;
}, $s);