从字符串中删除非 utf8字符

我在从字符串中删除非 utf8字符时遇到了问题,这些字符没有正确显示。字符类似于0x970x610x6C 0x6F (十六进制表示)

删除它们的最佳方法是什么? 正则表达式还是其他什么?

227453 次浏览

所以规则是,第一个 UTF-8 octlet 将高位设置为标记,然后1到4位表示有多少额外的 octlet; 然后每个额外的 octlet 必须将高2位设置为10。

伪蟒蛇是:

newstring = ''
cont = 0
for each ch in string:
if cont:
if (ch >> 6) != 2: # high 2 bits are 10
# do whatever, e.g. skip it, or skip whole point, or?
else:
# acceptable continuation of multi-octlet char
newstring += ch
cont -= 1
else:
if (ch >> 7): # high bit set?
c = (ch << 1) # strip the high bit marker
while (c & 1): # while the high bit indicates another octlet
c <<= 1
cont += 1
if cont > 4:
# more than 4 octels not allowed; cope with error
if !cont:
# illegal, do something sensible
newstring += ch # or whatever
if cont:
# last utf-8 was not terminated, cope

同样的逻辑应该可以翻译成 php。然而,它不清楚什么样的剥离是要做一旦你得到一个畸形的字符。

使用 regex 方法:

$regex = <<<'END'
/
(
(?: [\x00-\x7F]                 # single-byte sequences   0xxxxxxx
|   [\xC0-\xDF][\x80-\xBF]      # double-byte sequences   110xxxxx 10xxxxxx
|   [\xE0-\xEF][\x80-\xBF]{2}   # triple-byte sequences   1110xxxx 10xxxxxx * 2
|   [\xF0-\xF7][\x80-\xBF]{3}   # quadruple-byte sequence 11110xxx 10xxxxxx * 3
){1,100}                        # ...one or more times
)
| .                                 # anything else
/x
END;
preg_replace($regex, '$1', $text);

它搜索 UTF-8序列,并将其捕获到第1组。它还匹配不能识别为 UTF-8序列一部分的单个字节,但不捕获这些字节。替换就是第一组捕获的东西。这将有效地删除所有无效字节。

通过将无效字节编码为 UTF-8字符,可以修复字符串。但如果错误是随机的,这可能会留下一些奇怪的符号。

$regex = <<<'END'
/
(
(?: [\x00-\x7F]               # single-byte sequences   0xxxxxxx
|   [\xC0-\xDF][\x80-\xBF]    # double-byte sequences   110xxxxx 10xxxxxx
|   [\xE0-\xEF][\x80-\xBF]{2} # triple-byte sequences   1110xxxx 10xxxxxx * 2
|   [\xF0-\xF7][\x80-\xBF]{3} # quadruple-byte sequence 11110xxx 10xxxxxx * 3
){1,100}                      # ...one or more times
)
| ( [\x80-\xBF] )                 # invalid byte in range 10000000 - 10111111
| ( [\xC0-\xFF] )                 # invalid byte in range 11000000 - 11111111
/x
END;
function utf8replacer($captures) {
if ($captures[1] != "") {
// Valid byte sequence. Return unmodified.
return $captures[1];
}
elseif ($captures[2] != "") {
// Invalid byte of the form 10xxxxxx.
// Encode as 11000010 10xxxxxx.
return "\xC2".$captures[2];
}
else {
// Invalid byte of the form 11xxxxxx.
// Encode as 11000011 10xxxxxx.
return "\xC3".chr(ord($captures[3])-64);
}
}
preg_replace_callback($regex, "utf8replacer", $text);

编辑:

  • !empty(x)将匹配非空值("0"被认为是空值)。
  • x != ""将匹配非空值,包括 "0"
  • x !== ""将匹配除 ""以外的任何东西。

在这种情况下,x != ""似乎是最好的选择。

我也加快了比赛的速度。它不是单独匹配每个字符,而是匹配有效的 UTF-8字符序列。

那 iconv 呢:

Http://php.net/manual/en/function.iconv.php

我还没有在 PHP 内部使用过它,但它在命令行中的表现总是很好。您可以让它替换无效字符。

$string = preg_replace('~&([a-z]{1,2})(acute|cedil|circ|grave|lig|orn|ring|slash|th|tilde|uml);~i', '$1', htmlentities($string, ENT_COMPAT, 'UTF-8'));

如果您将 utf8_encode()应用于已有的 UTF8字符串,它将返回一个混乱的 UTF8输出。

我做了一个函数来解决所有这些问题,它叫做 Encoding::toUTF8()

您不需要知道字符串的编码方式。它可以是 Latin1(ISO8859-1)、 Windows-1252或 UTF8,也可以是字符串的混合。Encoding::toUTF8()将所有内容转换为 UTF8。

我这么做是因为一个服务给了我一个混乱的数据源,将这些编码混合在同一个字符串中。

用法:

require_once('Encoding.php');
use \ForceUTF8\Encoding;  // It's namespaced now.


$utf8_string = Encoding::toUTF8($mixed_string);


$latin1_string = Encoding::toLatin1($mixed_string);

我已经包含了另一个函数 Encoding: : fixUTF8() ,它将修复每个看起来由于被多次编码到 UTF8而产生混乱的 UTF8字符串。

用法:

require_once('Encoding.php');
use \ForceUTF8\Encoding;  // It's namespaced now.


$utf8_string = Encoding::fixUTF8($garbled_utf8_string);

例子:

echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");
echo Encoding::fixUTF8("FÃÂédÃÂération Camerounaise de Football");
echo Encoding::fixUTF8("Fédération Camerounaise de Football");

将输出:

Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football
Fédération Camerounaise de Football

下载:

Https://github.com/neitanod/forceutf8

可以使用 mbstring:

$text = mb_convert_encoding($text, 'UTF-8', 'UTF-8');

将删除无效字符。

见: 用问号替换无效的 UTF-8字符,似乎忽略了 mbstring.change _ string

$text = iconv("UTF-8", "UTF-8//IGNORE", $text);

这是我正在使用的。似乎工作得很好。从 http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/采取

删除 Unicode 基本语言平面以外的所有 Unicode 字符:

$str = preg_replace("/[^\\x00-\\xFFFF]/", "", $str);

UConverter 可以从 PHP 5.5开始使用。如果使用 intl 扩展名而不使用 mbstring,UConverter 是更好的选择。

function replace_invalid_byte_sequence($str)
{
return UConverter::transcode($str, 'UTF-8', 'UTF-8');
}


function replace_invalid_byte_sequence2($str)
{
return (new UConverter('UTF-8', 'UTF-8'))->convert($str);
}

可以使用 htmlspecalchars 删除 PHP 5.4以来的无效字节序列。Htmlspecalchars 在处理大字节和准确性方面优于 preg _ match。使用正则表达式可以看出许多错误的实现。

function replace_invalid_byte_sequence3($str)
{
return htmlspecialchars_decode(htmlspecialchars($str, ENT_SUBSTITUTE, 'UTF-8'));
}

这个函数删除所有非 ASCII 字符,它很有用,但不能解决这个问题:
这是我的函数,不管编码如何,它总是有效的:

function remove_bs($Str) {
$StrArr = str_split($Str); $NewStr = '';
foreach ($StrArr as $Char) {
$CharNo = ord($Char);
if ($CharNo == 163) { $NewStr .= $Char; continue; } // keep £
if ($CharNo > 31 && $CharNo < 127) {
$NewStr .= $Char;
}
}
return $NewStr;
}

工作原理:

echo remove_bs('Hello õhowå åare youÆ?'); // Hello how are you?

与问题稍有不同,但我要做的是使用 HtmlEncode (string) ,

这里是伪代码

var encoded = HtmlEncode(string);
encoded = Regex.Replace(encoded, "&#\d+?;", "");
var result = HtmlDecode(encoded);

输入和输出

"Headlight\x007E Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"
"Headlight~ Bracket, &#123; Cafe Racer<> Style, Stainless Steel 中文呢?"

我知道这不完美,但这工作对我来说。

我创建了一个函数,用于从字符串中删除无效的 UTF-8字符。 在生成 XML 导出文件之前,我使用它来清除对27000个产品的描述。

public function stripInvalidXml($value) {
$ret = "";
$current;
if (empty($value)) {
return $ret;
}
$length = strlen($value);
for ($i=0; $i < $length; $i++) {
$current = ord($value{$i});
if (($current == 0x9) || ($current == 0xA) || ($current == 0xD) || (($current >= 0x20) && ($current <= 0xD7FF)) || (($current >= 0xE000) && ($current <= 0xFFFD)) || (($current >= 0x10000) && ($current <= 0x10FFFF))) {
$ret .= chr($current);
}
else {
$ret .= "";
}
}
return $ret;
}

试试这个:

$string = iconv("UTF-8","UTF-8//IGNORE",$string);

根据 Iconv 手册,函数将以第一个参数作为输入字符集,第二个参数作为输出字符集,第三个参数作为实际输入字符串。

如果将输入和输出字符集都设置为 UTF-8,并将 //IGNORE标志附加到输出字符集,则函数将删除(去掉)输入字符串中不能由输出字符集表示的所有字符。因此,过滤有效的输入字符串。

文本可能包含 非 utf8字符。请先尝试:

$nonutf8 = mb_convert_encoding($nonutf8 , 'UTF-8', 'UTF-8');

你可以在这里了解更多: http://php.net/manual/en/function.mb-convert-encoding.php[新闻][2]

从最近的补丁到 Drupal 的 Feeds JSON 解析器模块:

//remove everything except valid letters (from any language)
$raw = preg_replace('/(?:\\\\u[\pL\p{Zs}])+/', '', $raw);

如果你担心是的,它保留空格作为有效的字符。

做了我需要做的。它删除了现在广泛使用的表情符号,这些表情符号不符合 MySQL 的“ utf8”字符集,并且出现了“ SQLSTATE [ HY000] : 通用错误: 1366错误字符串值”等错误。

详情请参阅 https://www.drupal.org/node/1824506#comment-6881382

欢迎使用2019和 regex 中的 /u修饰符,它将为您处理 UTF-8多字节字符

如果您只使用 mb_convert_encoding($value, 'UTF-8', 'UTF-8'),那么您的字符串中仍然会有不可打印的字符

这种方法将:

  • mb_convert_encoding删除所有无效的 UTF-8多字节字符
  • 使用 preg_replace删除所有不可打印的字符,如 \r\x00(空字节)和其他控制字符

方法:

function utf8_filter(string $value): string{
return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}

[:print:]匹配所有可打印字符和 \n换行,并剥离一切其他

您可以看到下面的 ASCII 表。.可打印字符范围从32到127,但是换行 \n是控制字符范围从0到31的一部分,所以我们必须添加换行到正则表达式 /[^[:print:]\n]/u

https://cdn.shopify.com/s/files/1/1014/5789/files/Standard-ASCII-Table_large.jpg?10669400161723642407

您可以尝试发送字符串通过正则表达式与字符以外的可打印范围,如 \x7F(DEL) ,\x1B(Esc)等,看看他们是如何剥离

function utf8_filter(string $value): string{
return preg_replace('/[^[:print:]\n]/u', '', mb_convert_encoding($value, 'UTF-8', 'UTF-8'));
}


$arr = [
'Danish chars'          => 'Hello from Denmark with æøå',
'Non-printable chars'   => "\x7FHello with invalid chars\r \x00"
];


foreach($arr as $k => $v){
echo "$k:\n---------\n";
    

$len = strlen($v);
echo "$v\n(".$len.")\n";
    

$strip = utf8_decode(utf8_filter(utf8_encode($v)));
$strip_len = strlen($strip);
echo $strip."\n(".$strip_len.")\n\n";
    

echo "Chars removed: ".($len - $strip_len)."\n\n\n";
}

Https://www.tehplayground.com/q5sj3foddhv1atpr

也许不是最精确的解决方案,但它只需要一行代码就能完成任务:

echo str_replace("?","",(utf8_decode($str)));

utf8_decode将字符转换为问号;
str_replace将去掉问号。

static $preg = <<<'END'
%(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
| \xE0[\xA0-\xBF][\x80-\xBF]
| [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2}
| \xED[\x80-\x9F][\x80-\xBF]
| \xF0[\x90-\xBF][\x80-\xBF]{2}
| [\xF1-\xF3][\x80-\xBF]{3}
| \xF4[\x80-\x8F][\x80-\xBF]{2}
)%xs
END;
if (preg_match_all($preg, $string, $match)) {
$string = implode('', $match[0]);
} else {
$string = '';
}

它对我们的服务有效

Subr ()可以破坏您的多字节字符!

在我的例子中,我使用 substr($string, 0, 255)来确保用户提供的值适合数据库。有时候,它会将一个多字节字符分成两半,并导致数据库错误与“不正确的字符串值”。

您可以使用 mb_substr($string,0,255),对于 MySQL5来说可能没有问题,但是 MySQL4计算的是字节而不是字符,因此根据多字节字符的数量,它仍然太长。

为了防止这些问题,我采取了以下步骤:

  1. 我增加了字段的大小(在本例中,它是一个更改日志,因此不能防止更长的输入)
  2. 我还是做了 mb_substring以防还是太长
  3. 我使用了上面@Markus Jarderot 提供的可接受的答案,以确保是否有一个真正长的条目,在长度限制处有一个多字节字符,我们可以在结尾处去掉多字节字符的一半。

这里你可以使用简单的正则表达式

$text = preg_replace('/[\x00-\x1F\x80-\xFF]/', '', $text);

它将从字符串中截断所有非 UTF-8字符

我尝试了很多关于这个主题的解决方案,但没有一个对我有用,在我的特殊情况下。但我找到了一个很好的解决办法: Https://www.ryadel.com/en/php-skip-invalid-characters-utf-8-xml-file-string/

基本上,这个函数解决了我的问题:

function sanitizeXML($string)
{
if (!empty($string))
{
// remove EOT+NOREP+EOX|EOT+<char> sequence (FatturaPA)
$string = preg_replace('/(\x{0004}(?:\x{201A}|\x{FFFD})(?:\x{0003}|\x{0004}).)/u', '', $string);
 

$regex = '/(
[\xC0-\xC1] # Invalid UTF-8 Bytes
| [\xF5-\xFF] # Invalid UTF-8 Bytes
| \xE0[\x80-\x9F] # Overlong encoding of prior code point
| \xF0[\x80-\x8F] # Overlong encoding of prior code point
| [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start
| [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start
| [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start
| (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle
| (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence
| (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence
| (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence
| (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2)
)/x';
$string = preg_replace($regex, '', $string);
 

$result = "";
$current;
$length = strlen($string);
for ($i=0; $i < $length; $i++)
{
$current = ord($string{$i});
if (($current == 0x9) ||
($current == 0xA) ||
($current == 0xD) ||
(($current >= 0x20) && ($current <= 0xD7FF)) ||
(($current >= 0xE000) && ($current <= 0xFFFD)) ||
(($current >= 0x10000) && ($current <= 0x10FFFF)))
{
$result .= chr($current);
}
else
{
$ret;    // use this to strip invalid character(s)
// $ret .= " ";    // use this to replace them with spaces
}
}
$string = $result;
}
return $string;
}

希望对你们有所帮助。

下一个消毒方法对我有效:

$string = mb_convert_encoding($string, 'UTF-8', 'UTF-8');
$string = iconv("UTF-8", "UTF-8//IGNORE", $string);