文字列から非UTF8文字を削除する


112

文字列からutf8以外の文字を削除すると問題が発生しますが、正しく表示されません。文字はこのような0x97 0x61 0x6C 0x6F(16進表記)

それらを削除する最良の方法は何ですか?正規表現か何か?


1
ここにリストされた解決策は私にとってはうまくいかなかったので、「文字の検証」セクションに私の答えを見つけました:webcollab.sourceforge.net/unicode.html
bobef

これ関連しますが、必ずしも似ているわけではありませんが、親しい従兄弟に似ています:)
Wayne Weibel

回答:


87

正規表現アプローチを使用する:

$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バイトに一致しますが、それらはキャプチャしません。置換は、グループ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文字のシーケンスを照合します。


$regex = <<<'END'PHP 5.3.xの代わりに何を使用するのですか?
serhio 2010

代わりに、それらをヒアドキュメント形式に変換することもできますが、読みやすさは若干低下します。もう1つの可能性は、単一引用符の文字列を使用することですが、その場合はコメントを削除する必要があります。
Markus Jarderot、2010

この行には小さなタイプミスがあり、空と見なされるため、空の代わりにelseif (!empty($captures([2])) {使用する必要があります。また、この機能は非常に遅いですが、これはより速く実行できますか?!== """0"
ケンダルホプキンス

2
この式には大きなメモリの問題がありますここを参照してください
ジャック

1
@ MarkusJarderot、 Regex .......うーん、この関数はプロダクション対応ですか?この関数のテストケースはありますか?
Pacerier 2015

132

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


13
素晴らしいもの!他のすべてのソリューションは無効な文字を破棄しますが、これはそれを修正します。驚くばかり。
giorgio79

4
あなたは素晴らしい機能を果たしました!私は過去にXMLフィードで多くの作業をしましたが、常にエンコーディングに問題がありました。ありがとうございました。
コスタノス2013年

5
わたしは、あなたを愛しています。悪いUTF8文字に対する "bloomoin"作業の時間を節約してくれました。ありがとう。
John Ballinger、2013年

4
これは素晴らしいです。ありがとう
EdgeCaseBerg 2014年

2
すばらしい!嬉しいです。+100で投票できたらいいのに;-)
Codebeat

61

mbstringを使用できます。

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

...無効な文字を削除します。

参照:無効なUTF-8文字を疑問符で置き換えると、mbstring.substitute_characterは無視されるようです


1
@Alliswellどれ?例を挙げていただけますか?
Frosty Z

確かに<0x1a>
Alliswell

1
@Alliswell私が間違っていなければ<0x1a>、印刷可能な文字ではありませんが、完全に有効なUTF-8シーケンスです。印刷できない文字で問題が発生する可能性がありますか?:このチェックstackoverflow.com/questions/1176904/...
フロスティZ

はい、そうです。どうも!
Alliswell

mb convertを呼び出す前に、mbstring置換文字をnoneに設定する必要がありini_set('mbstring.substitute_character', 'none');ました。そうしないと、結果に疑問符が付きました。
cby016

21

この関数はすべての非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?

8
なぜすべて大文字の関数名なのですか?Ewww。
クリスベイカー

5
それはASCIIであり、質問が望んでいたものにさえ近づいていません。
misaxi 2013

1
これはうまくいきました。Google Maps APIがAPIリクエストURLの「非UTF-8文字」が原因でエラーを報告したときに、問題に直面しました。犯人はí有効なUTF-8文字である住所フィールドの文字でした表を参照してください。士気:APIエラーメッセージを信頼しない:)
バレンタインShi

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

これは私が使っているものです。かなりうまくいくようです。http://planetozh.com/blog/2005/01/remove-invalid-characters-in-utf-8/から取得


うまくいきませんでした。テストした行を添付したいのですが、残念ながら無効な文字が含まれています。
Nir O.

3
申し訳ありませんが、さらにテストを行った結果、これが思ったとおりに機能していないことに気付きました。現在、stackoverflow.com
a / 8215387/138023

14

これを試して:

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

iconvマニュアルによれば、関数は最初のパラメーターを入力文字セット、2番目のパラメーターを出力文字セット、3番目のパラメーターを実際の入力文字列として受け取ります。

入力文字セットと出力文字セットの両方をUTF-8に設定し、//IGNOREフラグを出力文字セットに追加すると、関数は、出力文字セットで表すことができない入力文字列のすべての文字を削除(ストリップ)します。したがって、有効な入力文字列をフィルタリングします。


コードスニペットをダンプするのではなく、答えが何をするかを説明します。
Tomasz Kowalczyk 2014

3
私はこれを試してみました//IGNOREが、無効なUTF-8が存在するという通知を抑制しているようには見えません(もちろん、これは知っており、修正したいのですが)。マニュアルの高い評価のコメントは、それが数年前からバグだったと考えているようです。
15年

常に使用することをお勧めしますiconv。@halfer多分あなたの入力データはutf-8からのものではありません。別のオプションは、ASCIIに再変換してから再びutf-8に戻すことです。私の場合、私は次のiconvように使用しました$output = iconv("UTF-8//", "ISO-8859-1//IGNORE", $input );
m3nda

@ erm3nda:私はこの使用例を正確に覚えていません-間違った文字セットで宣言されたUTF-8 Webサイトを解析している可能性があります。メモをありがとう、私はそれが将来の読者のために役立つと確信しています。
16年

はい、何かがわからない場合は、テストして、最後にキーを押してください;-)
m3nda


6

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

PHP 5.4以降、htmlspecialcharsを使用して無効なバイトシーケンスを削除できます。大きなサイズのバイトと精度を処理するには、Htmlspecialcharsがpreg_matchよりも優れています。正規表現を使用した多くの誤った実装が見られます。

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

3つの優れたソリューションがありますが、ユーザーがそれらの中からどのように選択するかは明確ではありません。
ボブ・レイ

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

上記のすべての複雑な答えのうち、これが私にとってはトリックでした!ありがとう。
EminÖzlem、2016年

この機能に困惑しています。ord()0から255の範囲の結果を返します。ifこの関数の巨人は、ord()決して戻らないユニコード範囲をテストします。この関数が機能する理由を誰かが明確にしたい場合は、洞察をいただければ幸いです。
i336_

4

2019へようこそ、/uUTF-8マルチバイト文字を処理する正規表現の修飾子

使用するだけの場合mb_convert_encoding($value, 'UTF-8', 'UTF-8')は、文字列に印刷不可能な文字が含まれることになります

このメソッドは:

  • 無効なUTF-8マルチバイト文字をすべて削除します mb_convert_encoding
  • \r\x00(NULL-byte)のような印刷できない文字やその他の制御文字を削除します。preg_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


2047へようこそphp-mbstring。デフォルトではphpにパックされていません。
NVRM


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を参照してください


これiconvは、昔ながらの正規表現ベースのものよりもはるかに優れており、preg_replace現在では非推奨です。
m3nda 2016年

3
preg_replaceは非推奨ではありません
Oleksii Chekulaiev 2016年

1
あなたは完全に正しいですereg_replace()、申し訳ありません。
m3nda 2016年

2

おそらく最も正確な解決策ではないかもしれませんが、1行のコードで作業を完了できます。

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

utf8_decode文字を疑問符に変換します。
str_replace疑問符が取り除かれます。


何百ものソリューションを試した後、それが機能した唯一のソリューションはあなたのものです。
Haritsinh Gohil

1

したがって、ルールは、最初のUTF-8オクレットにハイビットがマーカーとして設定され、次に1〜4ビットが追加のオクレットの数を示すことです。次に、追加の各オクレットで上位2ビットを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に変換できます。ただし、不正な形式の文字を取得すると、どのような除去が行われるかは明確ではありません。


c = (ch << 1)(c & 1)ループをスキップして、最初はゼロになります。テストはおそらく(c & 128)
マーカス・ジャドロ

1

Unicode基本言語平面外のすべてのUnicode文字を削除するには、次の手順に従います。

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

0

質問とは少し異なりますが、私がやっていることは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 中文呢?"

私はそれが完璧ではないことを知っていますが、私のために仕事をします。


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

それは私たちのサービスに働きます


2
コードのみの答えではなく、これが質問にどのように答えるかを説明するコンテキストを追加できますか
アルンヴィノス

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.