从字符串中删除非utf8字符

时间:2009-09-09 18:33:04

标签: php regex

我在从字符串中删除非utf8字符时遇到问题,这些字符无法正常显示。字符就像这个0x97 0x61 0x6C 0x6F(十六进制表示)

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

22 个答案:

答案 0 :(得分:118)

如果您将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

答案 1 :(得分:76)

使用正则表达式方法:

$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序列一部分的单个字节,但不捕获那些。替换是捕获到组1中的任何内容。这有效地删除了所有无效字节。

可以通过将无效字节编码为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字符的序列。

答案 2 :(得分:56)

您可以使用mbstring:

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

...将删除无效字符。

请参阅:Replacing invalid UTF-8 characters by question marks, mbstring.substitute_character seems ignored

答案 3 :(得分:18)

此功能删除所有非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?

答案 4 :(得分:11)

试试这个:

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

根据iconv manual,该函数将第一个参数作为输入字符集,第二个参数作为输出字符集,第三个参数作为实际输入字符串。

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

答案 5 :(得分:11)

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

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

答案 6 :(得分:6)

我创建了一个从字符串中删除无效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;
}

答案 7 :(得分:6)

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

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

您可以在此处详细了解:http://php.net/manual/en/function.mb-convert-encoding.php news

答案 8 :(得分:6)

自PHP 5.5起就可以使用UConverter。如果使用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);
}
自PHP 5.4以来,

htmlspecialchars可用于删除无效的字节序列。 Htmlspecialchars比preg_match更好地处理大字节和精度。可以看到使用正则表达式的许多错误实现。

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

答案 9 :(得分:3)

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

答案 10 :(得分:3)

欢迎使用2019年和正则表达式中的/u修饰符,它将为您处理UTF-8多字节字符

如果仅使用mb_convert_encoding($value, 'UTF-8', 'UTF-8'),则字符串中仍然会出现不可打印的字符

此方法将:

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

方法:

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

答案 11 :(得分:2)

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

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

如果您关注是,它会将空格保留为有效字符。

做了我需要的东西。它消除了当今流行的表情符号,这些符号不适合MySQL的“utf8”和#ut;字符集,这给了我错误,如#34; SQLSTATE [HY000]:常规错误:1366字符串值不正确&#34;。

有关详细信息,请参阅https://www.drupal.org/node/1824506#comment-6881382

答案 12 :(得分:2)

substr() 可以破坏你的多字节字符!

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

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

为了防止这些问题,我实施了以下步骤:

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

答案 13 :(得分:1)

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

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

答案 14 :(得分:1)

所以规则是第一个UTF-8 octlet将高位设置为标记,然后使用1到4位来表示有多少额外的octlet;那么每个附加的小号必须将高两位设置为10。

伪python将是:

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。但是,一旦你遇到一个畸形人物,就不清楚要做什么样的剥离了。

答案 15 :(得分:1)

嗨,您可以使用简单的正则表达式

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

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

答案 16 :(得分:0)

与问题略有不同,但我正在做的是使用HtmlEncode(字符串),

伪代码在这里

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 中文呢?"

我知道这不是完美的,但为我做的工作。

答案 17 :(得分:0)

也许不是最精确的解决方案,但只需一行代码即可完成工作:

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

utf8_decode会将字符转换为问号;
str_replace将删除问号。

答案 18 :(得分:0)

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 = '';
}

使用我们的服务

答案 19 :(得分:0)

我尝试了许多关于这个主题的解决方案,但在我的具体情况下,没有一个对我有用。但我在这个链接上找到了一个很好的解决方案: 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;
}

希望对你们有所帮助。

答案 20 :(得分:0)

下一次消毒对我有用:

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

答案 21 :(得分:-1)

iconv:

怎么样?

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

没有在PHP本身内部使用它,但它总是在命令行上对我很好。您可以使用它替换无效字符。