web-dev-qa-db-ja.com

エンコーディングを検出し、すべてUTF-8にする

私はさまざまなRSSフィードからたくさんのテキストを読み、それらを私のデータベースに挿入しています。

もちろん、フィードにはいくつかの異なる文字エンコーディングが使用されています。 UTF-8およびISO-8859-1.

残念ながら、テキストのエンコーディングに問題がある場合があります。例:

  1. "Fußball"の "ß"は私のデータベースでは "Â"のように見えるはずです。 「Â」の場合は正しく表示されます。

  2. 私のデータベースでは "Fußball"の "ß"が "ß"のように見えることがあります。それからもちろんそれは間違って表示されます。

  3. それ以外の場合、 "ß"は "ß"として保存されます - 変更はありません。それからそれはまた間違って表示されます。

ケース2と3を回避するために私は何ができますか?

どのようにしてすべてを同じエンコーディング、できればUTF-8にすることができますか?いつutf8_encode()を使用しなければならないのか、utf8_decode()を使用しなければならない場合(効果は明確ですが、関数を使用しなければならない場合はいつですか)、そしていつ入力を何もしなければなりませんか?

あなたは私を助けて、すべてを同じエンコーディングにする方法を教えてもらえますか?おそらくmb_detect_encoding()関数がありますか?このための関数を書くことができますか?だから私の問題は次のとおりです。

  1. テキストがどのようなエンコーディングを使用しているかを調べる方法は?
  2. どうやってUTF-8に変換するのですか - 古いエンコーディングは何でも?

このような機能は動作しますか?

function correct_encoding($text) {
    $current_encoding = mb_detect_encoding($text, 'auto');
    $text = iconv($current_encoding, 'UTF-8', $text);
    return $text;
}

私はそれをテストしましたが、うまくいきません。どうしたんだ?

287
caw

utf8_encode()をすでにUTF8の文字列に適用すると、文字化けしたUTF8出力が返されます。

これらすべての問題に対処する機能を作りました。それはEncoding::toUTF8()と呼ばれます。

あなたはあなたの文字列のエンコーディングが何であるかを知る必要はありません。 Latin1(iso 8859-1)、Windows-1252、UTF8のいずれか、または文字列にそれらを混在させることができます。 Encoding::toUTF8()はすべてをUTF8に変換します。

これは、UTF-8とLatin1を同じ文字列に混在させて、サービスがすべてのデータをめちゃくちゃにしているためです。

使用法:

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

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

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

ダウンロード:

https://github.com/neitanod/forceutf8

更新:

文字化けしているように見えるすべてのUTF8文字列を修正する別の関数Encoding::fixUFT8()を含めました。

使用法:

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

更新:関数(forceUTF8)をEncodingというクラスの静的関数のファミリーに変換しました。新しい関数はEncoding::toUTF8()です。

341

最初にどのエンコーディングが使用されているのかを検出する必要があります。 RSSフィードを(おそらくHTTP経由で)解析しているので、 Content-Type HTTPヘッダフィールドcharsetパラメータからエンコーディングを読むべきです。存在しない場合は、 XML処理命令encoding属性からエンコードを読み取ります。それも欠けている場合、 仕様で定義されているように UTF-8を使用してください。


編集これは私がおそらくすることです:

応答を送信して取得するには、 cURL を使用します。これにより、特定のヘッダフィールドを設定したり、レスポンスヘッダを取得したりすることができます。レスポンスを取得した後、HTTPレスポンスを解析してそれをヘッダーとボディに分割する必要があります。ヘッダはMIMEタイプを含むContent-Typeヘッダフィールドと(うまくいけば)encoding/charsetを持つcharsetパラメータを含むべきです。そうでない場合は、encoding属性が存在するかどうかXML PIを分析し、そこからエンコーディングを取得します。それも欠けている場合、XML仕様はエンコーディングとしてUTF-8を使用するように定義します。

$url = 'http://www.lr-online.de/storage/rss/rss/sport.xml';

$accept = array(
    'type' => array('application/rss+xml', 'application/xml', 'application/rdf+xml', 'text/xml'),
    'charset' => array_diff(mb_list_encodings(), array('pass', 'auto', 'wchar', 'byte2be', 'byte2le', 'byte4be', 'byte4le', 'BASE64', 'UUENCODE', 'HTML-ENTITIES', 'Quoted-Printable', '7bit', '8bit'))
);
$header = array(
    'Accept: '.implode(', ', $accept['type']),
    'Accept-Charset: '.implode(', ', $accept['charset']),
);
$encoding = null;
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
curl_setopt($curl, CURLOPT_HTTPHEADER, $header);
$response = curl_exec($curl);
if (!$response) {
    // error fetching the response
} else {
    $offset = strpos($response, "\r\n\r\n");
    $header = substr($response, 0, $offset);
    if (!$header || !preg_match('/^Content-Type:\s+([^;]+)(?:;\s*charset=(.*))?/im', $header, $match)) {
        // error parsing the response
    } else {
        if (!in_array(strtolower($match[1]), array_map('strtolower', $accept['type']))) {
            // type not accepted
        }
        $encoding = trim($match[2], '"\'');
    }
    if (!$encoding) {
        $body = substr($response, $offset + 4);
        if (preg_match('/^<\?xml\s+version=(?:"[^"]*"|\'[^\']*\')\s+encoding=("[^"]*"|\'[^\']*\')/s', $body, $match)) {
            $encoding = trim($match[1], '"\'');
        }
    }
    if (!$encoding) {
        $encoding = 'utf-8';
    } else {
        if (!in_array($encoding, array_map('strtolower', $accept['charset']))) {
            // encoding not accepted
        }
        if ($encoding != 'utf-8') {
            $body = mb_convert_encoding($body, 'utf-8', $encoding);
        }
    }
    $simpleXML = simplexml_load_string($body, null, LIBXML_NOERROR);
    if (!$simpleXML) {
        // parse error
    } else {
        echo $simpleXML->asXML();
    }
}
72
Gumbo

エンコードを検出するのは難しいです。

mb_detect_encodingは、合格した候補者の数に基づいて推測して機能します。一部のエンコーディングでは、特定のバイトシーケンスが無効であるため、さまざまな候補を区別できます。残念ながら、同じバイトが有効である(しかし異なる)エンコーディングがたくさんあります。このような場合、エンコーディングを決定する方法はありません。これらの場合に推測するためにあなた自身のロジックを実装することができます。たとえば、日本のサイトからのデータは日本語のエンコーディングを持つ可能性が高くなります。

西ヨーロッパ言語のみを扱う限り、考慮すべき3つの主要なエンコーディングはutf-8iso-8859-1およびcp-1252です。これらは多くのプラットフォームでデフォルトになっているので、間違って報告される可能性が最も高いです。例えば。人々が異なるエンコーディングを使用する場合、彼らはそれについて率直である可能性があります。したがって、エンコーディングがこれら3つのうちの1つとして報告されていない限り、良い戦略はプロバイダを信頼することです。 mb_check_encodingを使用して、実際に有効であることを再度確認する必要があります(validbeingと同じではありません)。 - 同じ入力が多くのエンコーディングに対して有効かもしれません)。それらのうちの1つであれば、mb_detect_encodingを使ってそれらを区別することができます。幸いなことに、それはかなり決定的です。あなたはUTF-8,ISO-8859-1,WINDOWS-1252という適切なdetect-sequenceを使う必要があります。

エンコーディングを検出したら、それを内部表現に変換する必要があります(UTF-8が唯一の正しい選択です)。関数utf8_encodeは、ISO-8859-1UTF-8に変換するので、その特定の入力タイプに対してのみ使用できます。他のエンコーディングの場合はmb_convert_encodingを使います。

35
troelskn

A本当にisUTF8-関数を実装するための良い方法は php.net にあります。

function isUTF8($string) {
    return (utf8_encode(utf8_decode($string)) == $string);
}
14
harpax

このチートシートには、PHPでのUTF-8処理に関する一般的な注意事項がいくつか記載されています。 http://developer.loftdigital。 com/blog/php-utf-8チートシート

文字列内のマルチバイト文字を検出するこの関数は、( source )も役立つことがあります。


function detectUTF8($string)
{
    return preg_match('%(?:
        [\xC2-\xDF][\x80-\xBF]             # non-overlong 2-byte
        |\xE0[\xA0-\xBF][\x80-\xBF]        # excluding overlongs
        |[\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte
        |\xED[\x80-\x9F][\x80-\xBF]        # excluding surrogates
        |\xF0[\x90-\xBF][\x80-\xBF]{2}     # planes 1-3
        |[\xF1-\xF3][\x80-\xBF]{3}         # planes 4-15
        |\xF4[\x80-\x8F][\x80-\xBF]{2}     # plane 16
        )+%xs', 
    $string);
}
11
miek

少し頭を上げて、あなたはあなたのデータベースで "ß"が "Â"として表示されるべきであると言いました。

これは、おそらくlatin1文字エンコーディングのデータベースを使用しているか、おそらくphp-mysql接続が正しく設定されていないためです。つまり、phpはmysqlがutf-8を使用するように設定していると考えますbelives phpはiso-8859-1としてエンコードされたデータを送信しているので、送信されたデータをもう一度utf-8としてエンコードしようとすると、このような問題が発生する可能性があります。

これを見てください、あなたを助けるかもしれません: http://php.net/manual/en/function.mysql-set-charset.php

9
Krynble

応答はさまざまなエンコーディングでコーディングされる可能性があるため、入力時に文字セットをテストする必要があります。
次の関数を使って検出と翻訳を行うことで、すべてのコンテンツをUTF-8に送信します。

function fixRequestCharset()
{
  $ref = array( &$_GET, &$_POST, &$_REQUEST );
  foreach ( $ref as &$var )
  {
    foreach ( $var as $key => $val )
    {
      $encoding = mb_detect_encoding( $var[ $key ], mb_detect_order(), true );
      if ( !$encoding ) continue;
      if ( strcasecmp( $encoding, 'UTF-8' ) != 0 )
      {
        $encoding = iconv( $encoding, 'UTF-8', $var[ $key ] );
        if ( $encoding === false ) continue;
        $var[ $key ] = $encoding;
      }
    }
  }
}

そのルーチンはリモートホストから来るすべてのPHP変数をUTF-8に変換します。
または、エンコードを検出または変換できなかった場合は値を無視します。
あなたは自分のニーズに合わせてカスタマイズできます。
変数を使う前にそれを呼び出すだけです。

3
cavila

mb_detect_encodingmb_convert_encodingに関して興味深いことは、あなたが提案するエンコーディングの順番が重要であるということです。

// $input is actually UTF-8

mb_detect_encoding($input, "UTF-8", "ISO-8859-9, UTF-8");
// ISO-8859-9 (WRONG!)

mb_detect_encoding($input, "UTF-8", "UTF-8, ISO-8859-9");
// UTF-8 (OK)

そのため、予想されるエンコーディングを指定するときには特定の順序を使用することをお勧めします。それでも、これは絶対確実というわけではないことに留意してください。

3
Halil Özgür

エンコーディングは、UTF-8に2回エンコードしたように見えます。つまり、他のエンコーディングからUTF-8へ、そしてまたUTF-8へ。あなたがiso-8859-1を持っているかのように、iso-8859-1からutf-8に変換し、そしてUTF-8への別の変換のために新しい文字列をiso-8859-1として扱いました。

これが、あなたがしたことの疑似コードです。

$inputstring = getFromUser();
$utf8string = iconv($current_encoding, 'utf-8', $inputstring);
$flawedstring = iconv($current_encoding, 'utf-8', $utf8string);

試してみてください:

  1. mb_detect_encoding()を使ってエンコーディングを検出します。
  2. uTF-8の場合は、iso-8859-1に変換し、手順1を繰り返します。
  3. 最後に、再びUTF-8に変換する

それは、「ミドル」変換ではiso-8859-1を使ったと仮定しています。 windows-1252を使っていたら、windows-1252(latin1)に変換してください。オリジナルのソースエンコーディングは重要ではありません。あなたが使用していたものに欠陥がありましたが、2回目の変換です。

これは何が起こったのか私の推測です。 1つの拡張ASCIIバイトの代わりに4バイトを取得するためにできることは他にほとんどありません。

ドイツ語もiso-8859-2とwindows-1250(latin2)を使います。

3
Ivan Vučica

RSSフィードの文字エンコーディングの作成は 複雑なようです 。通常のWebページでさえ、それらのエンコーディングを省略したり、嘘をついたりすることがよくあります。

そのため、正しい方法でエンコーディングを検出してから何らかの形の自動検出(推測)にフォールバックすることができます。

2
Kevin ORourke

私はこれがより古い質問であることを知っています、しかし私は決して傷つけない有用な答えを考えます。デスクトップアプリケーション、SQLite、およびGET/POST変数間のエンコーディングに問題がありました。あるものはUTF-8であり、あるものはASCIIであり、そして基本的にすべてのものは外国の文字が関与したときにめちゃくちゃになるでしょう。

これが私の解決策です。処理の前に各ページロードであなたのGET/POST/REQUEST(私はクッキーを省略しましたが、あなたは望むならそれらを追加することができます)をスクラブします。ヘッダーでうまくいきます。ソースのエンコーディングを自動的に検出できない場合、PHPは警告をスローします。したがって、これらの警告は@で抑制されます。

//Convert everything in our vars to UTF-8 for playing Nice with the database...
//Use some auto detection here to help us not double-encode...
//Suppress possible warnings with @'s for when encoding cannot be detected
try
{
    $process = array(&$_GET, &$_POST, &$_REQUEST);
    while (list($key, $val) = each($process)) {
        foreach ($val as $k => $v) {
            unset($process[$key][$k]);
            if (is_array($v)) {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = $v;
                $process[] = &$process[$key][@mb_convert_encoding($k,'UTF-8','auto')];
            } else {
                $process[$key][@mb_convert_encoding($k,'UTF-8','auto')] = @mb_convert_encoding($v,'UTF-8','auto');
            }
        }
    }
    unset($process);
}
catch(Exception $ex){}
2
jocull

AGES以来、エンコードの解決方法を調べていましたが、このページはおそらく何年にもわたる検索の結論です。私はあなたが述べた提案のいくつかをテストしました、そしてここに私のノートがあります:

これは私のテスト文字列です:

これは「書いて書いた」文字列で、私はそれらを見るためにp''Sòme '特別な文字を使っています、fùnctìonによって変換されました! & それでおしまい!

この文字列をutf8_general_ciとして設定されているフィールドのDBに保存するためにINSERTを行います。

私のページの文字セットはUTF-8です

そのようにINSERTを実行した場合、私のDBにはおそらく火星から来た文字がいくつかあります。そのため、それらを何らかの "安全な" UTF-8に変換する必要があります。 utf8_encode()を試しましたが、それでもエイリアンの文字が私のデータベースに侵入していました...

それで私は番号8で投稿されたforceUTF8関数を使用しようとしました、しかしDBで保存されたストリングはそのように見えます:

これは、 "wröngngwrötten"文字列です。私は、p'''me '特別な文字を見て、それをfüctonで変換します。 & それでおしまい!

そこで、このページでさらにいくつかの情報を収集し、それらを他のページの他の情報とマージすることで、私はこの解決策に関する問題を解決しました。

$finallyIDidIt = mb_convert_encoding(
  $string,
  mysql_client_encoding($resourceID),
  mb_detect_encoding($string)
);

今私のデータベースで私は正しいエンコーディングの私の文字列を持っています。

注:mysql_client_encoding関数には注意してください。この関数はリソースIDをパラメータとして使用するため、DBに接続する必要があります。

しかし、そうですね、私はINSERTの前にその再エンコードをしているだけなので、私にとっては問題にはなりません。

私はこれがこのページのような人が私を助けてくれるのを助けることを願っています!

みんなありがとう!

マウロ

2
Mauro

それは簡単です:あなたがUTF-8ではない何かを手に入れるとき、あなたはそのINTO utf8にENCODEしなければなりません。

そのため、ISO-8859-1の特定のフィードを取得するときは、utf8_encodeを介して解析してください。

ただし、UTF8フィードを取得している場合は、何もする必要はありません。

2
Seb

php.net/ mb_detect_encoding

echo mb_detect_encoding($str, "auto");

または

echo mb_detect_encoding($str, "UTF-8, ASCII, ISO-8859-1");

結果がどうなっているのか本当にわかりませんが、フィードの一部をさまざまなエンコードで取り、mb_detect_encodingが機能するかどうか試してみることをお勧めします。

更新
autoは "ASCII、JIS、UTF-8、EUC-JP、SJIS"の略です。検出された文字セットを返します。 iconv を使用して、文字列をutf-8に変換できます。

<?php
function convertToUTF8($str) {
    $enc = mb_detect_encoding($str);

    if ($enc && $enc != 'UTF-8') {
        return iconv($enc, 'UTF-8', $str);
    } else {
        return $str;
    }
}
?>

私はそれをテストしていないので、保証はありません。そしてもっと簡単な方法があるかもしれません。

1
stefs

私のために働いた@harpax。私の場合、これで十分です。

if (isUTF8($str)) { 
    echo $str; 
}
else
{
    echo iconv("ISO-8859-1", "UTF-8//TRANSLIT", $str);
}
1
PJ Brunet

ŸßのためのMojibakeです。データベースには、16進数があります。

DF if the column is "latin1",
C39F if the column is utf8 -- OR -- it is latin1, but "double-encoded"
C383C5B8 if double-encoded into a utf8 column

あなたはPHPのエンコーディング/デコーディング関数を使用しないでください。代わりに、データベースとデータベースへの接続を正しく設定する必要があります。

MySQLが関係している場合は、以下を参照してください。 utf8文字に関する問題。私が見るものは私が保存したものではありません

0
Rick James

あなたのphpスクリプトを整理した後、mysqlにあなたがどんな文字セットを渡しているのか、そして受け取りたいのかを忘れないでください。

例:文字セットutf8を設定します。

Latin1 I/Oセッションでutf8データをlatin1テーブルに渡すと、これらの厄介な鳥の餌が得られます。私はoscommerce店で一日おきにこれを見ます。前後にそれが正しいように見えるかもしれません。しかし、phpmyadminは真実を示します。 mysqlに渡す文字セットを指定することで、mysqlデータの変換が自動的に処理されます。

既存のスクランブルされたmysqlデータを回復する方法は議論するもう一つのスレッドです。 :)

0
tim

ヘッダからエンコーディングを取得し、それをutf-8に変換します。

$post_url='http://website.domain';

/// Get headers ////////////////////////////////////////////////////////////
function get_headers_curl($url) 
{ 
    $ch = curl_init(); 

    curl_setopt($ch, CURLOPT_URL,            $url); 
    curl_setopt($ch, CURLOPT_HEADER,         true); 
    curl_setopt($ch, CURLOPT_NOBODY,         true); 
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); 
    curl_setopt($ch, CURLOPT_TIMEOUT,        15); 

    $r = curl_exec($ch); 
    return $r; 
}
$the_header = get_headers_curl($post_url);
/// check for redirect /////////////////////////////////////////////////
if (preg_match("/Location:/i", $the_header)) {
    $arr = explode('Location:', $the_header);
    $location = $arr[1];

    $location=explode(chr(10), $location);
    $location = $location[0];

$the_header = get_headers_curl(trim($location));
}
/// Get charset /////////////////////////////////////////////////////////////////////
if (preg_match("/charset=/i", $the_header)) {
    $arr = explode('charset=', $the_header);
    $charset = $arr[1];

    $charset=explode(chr(10), $charset);
    $charset = $charset[0];
    }
///////////////////////////////////////////////////////////////////////////////
// echo $charset;

if($charset && $charset!='UTF-8') { $html = iconv($charset, "UTF-8", $html); }
0
Arsen

最も投票された答えはうまくいきません。ここに私のものがあり、それが役立つことを願っています。

function toUTF8($raw) {
    try{
        return mb_convert_encoding($raw, "UTF-8", "auto"); 
    }catch(\Exception $e){
        return mb_convert_encoding($raw, "UTF-8", "GBK"); 
    }
}
0
fzyzcjy

このバージョンはドイツ語用ですが、$ CHARSETSと$ TESTCHARSを変更できます

class CharsetDetector
{
private static $CHARSETS = array(
"ISO_8859-1",
"ISO_8859-15",
"CP850"
);
private static $TESTCHARS = array(
"€",
"ä",
"Ä",
"ö",
"Ö",
"ü",
"Ü",
"ß"
);
public static function convert($string)
{
    return self::__iconv($string, self::getCharset($string));
}
public static function getCharset($string)
{
    $normalized = self::__normalize($string);
    if(!strlen($normalized))return "UTF-8";
    $best = "UTF-8";
    $charcountbest = 0;
    foreach (self::$CHARSETS as $charset) {
        $str = self::__iconv($normalized, $charset);
        $charcount = 0;
        $stop   = mb_strlen( $str, "UTF-8");

        for( $idx = 0; $idx < $stop; $idx++)
        {
            $char = mb_substr( $str, $idx, 1, "UTF-8");
            foreach (self::$TESTCHARS as $testchar) {

                if($char == $testchar)
                {

                    $charcount++;
                    break;
                }
            }
        }
        if($charcount>$charcountbest)
        {
            $charcountbest=$charcount;
            $best=$charset;
        }
        //echo $text."<br />";
    }
    return $best;
}
private static function __normalize($str)
{

$len = strlen($str);
$ret = "";
for($i = 0; $i < $len; $i++){
    $c = ord($str[$i]);
    if ($c > 128) {
        if (($c > 247)) $ret .=$str[$i];
        elseif ($c > 239) $bytes = 4;
        elseif ($c > 223) $bytes = 3;
        elseif ($c > 191) $bytes = 2;
        else $ret .=$str[$i];
        if (($i + $bytes) > $len) $ret .=$str[$i];
        $ret2=$str[$i];
        while ($bytes > 1) {
            $i++;
            $b = ord($str[$i]);
            if ($b < 128 || $b > 191) {$ret .=$ret2; $ret2=""; $i+=$bytes-1;$bytes=1; break;}
            else $ret2.=$str[$i];
            $bytes--;
        }
    }
}
return $ret; 
}
private static function __iconv($string, $charset)
{
    return iconv ( $charset, "UTF-8" , $string );
}
}
0

私はここで解決策を見つけます http://deer.org.ua/2009/10/06/1/

class Encoding
{
    /**
     * http://deer.org.ua/2009/10/06/1/
     * @param $string
     * @return null
     */
    public static function detect_encoding($string)
    {
        static $list = ['utf-8', 'windows-1251'];

        foreach ($list as $item) {
            try {
                $sample = iconv($item, $item, $string);
            } catch (\Exception $e) {
                continue;
            }
            if (md5($sample) == md5($string)) {
                return $item;
            }
        }
        return null;
    }
}

$content = file_get_contents($file['tmp_name']);
$encoding = Encoding::detect_encoding($content);
if ($encoding != 'utf-8') {
    $result = iconv($encoding, 'utf-8', $content);
} else {
    $result = $content;
}

私は@は悪い決断だと思います、そしてdeer.org.uaからの解決にいくつかの変更を加えます。

0
Paul