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);