Rails:UTF8にもかかわらずシリアライズされたハッシュでエンコードする

私はちょうどルビー1.9.2からルビ1.9.3p0(2011-10-30リビジョン33570)に更新しました。私のレールアプリケーションはデータベースのバックエンドとしてpostgresqlを使用します。システムのロケールはUTF8であり、データベースのエンコーディングも同様です。 railsアプリケーションのデフォルトエンコーディングもUTF8です。私は英語の文字だけでなく中国語の文字も入力する中国人のユーザーを持っています。文字列は、UTF8でエンコードされた文字列として格納されます。

Railsバージョン:3.0.9

データベース内の既存の中国語文字列の一部が正しく表示されなくなったためです。これはすべての文字列には影響しませんが、シリアル化されたハッシュの一部である文字列にのみ影響します。プレーンな文字列として格納されている他の文字列は、すべて正しいと思われます。


例:

これは、シリアル化されたハッシュで、UTF8文字列としてデータベースに格納されます。

broken = "--- !map:ActiveSupport::HashWithIndifferentAccess \ncheckbox: \"1\"\nchoice: \"Round Paper Clips  \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n\"\ninfo: \"10\\xE7\\x9B\\x92\"\n"

この文字列をルビのハッシュに変換するために、私は YAML.load でデシリアライズします:

broken_hash = YAML.load(broken)

これは、内容が文字化けしたハッシュを返します。

{"checkbox"=>"1", "choice"=>"Round Paper Clips  ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089\r\n", "info"=>"10ç\u009B\u0092"}

The garbled stuff is supposed to be UTF8-encoded Chinese. broken_hash['info'].encoding tells me that ruby thinks this is #. I disagree.

興味深いことに、前にシリアル化されていない他のすべての文字列は、上手く見えます。同じレコードでは、別のフィールドには、レールコンソール、psqlコンソール、およびブラウザのちょうどよい漢字が含まれています。すべての文字列---シリアル化されたハッシュまたはプレーンストリングに関係なく、更新が正常に行われたのでデータベースに保存されます。


Rubyの主張がUTF-8であったにもかかわらず、誤ったエンコーディング(GB2312またはANSIなど)からUTF-8に変換されたテキストを変換しようとしましたが、もちろん失敗しました。これは私が使用したコードです:

require 'iconv'
Iconv.conv('UTF-8', 'GB2312', broken_hash['info'])

これは失敗します。なぜなら、rubyは文字列中の不正なシーケンスをどうするかを知らないからです。

私は実際には、古い、おそらく壊れたシリアライズされたハッシュ文字列をすべて修正し、それを使ってスクリプトを実行したいだけです。これらの壊れた文字列を中国語に似ているものに変換する方法はありますか?


私は生の文字列(上記の例では "broken"と呼ばれる)でエンコードされたUTF-8文字列を再生しました。これは、シリアル化された文字列でエンコードされた中国語の文字列です。

chinese = "\\ xEF \\ xBC \\ x88 \\ xE5 \\ x9B \\ x9E \\ xE5 \\ xBD \\ xA2 \\ xE9 \\ x92 \\ x88 \\ xEF \\ xBC \\ x89 \\ r \\ n \ "

私はそれをエスケープバックスラッシュをエスケープしてエスケープすることで、これを実際のUTF-8エンコードされた文字列に変換するのは簡単だと気付きました。

chinese_ok = "¥xEF¥xBC¥x88¥xE5¥x9B¥x9E¥xE5¥xBD¥xA2¥xE9¥x92¥x88¥xEF¥xBC¥x89¥r"

This returns a proper UTF-8-encoded Chinese string: "(回形针)\r\n"

文字列をルビのハッシュに変換するために YAML.load(...)を使用した場合にのみ、この問題が解消されます。おそらく、生の文字列が YAML.load に送られる前にそれを処理する必要があります。ちょうど私がこれがなぜそうであるか疑問に思います...


面白い!これはおそらく1.9.3にデフォルトで使用されているYAMLエンジンの "psych"のためです。私は YAML :: ENGINE.yamler = 'syck' で "syck"エンジンに切り替えました。壊れた文字列は正しく解析されています。

9
シリアライズされたハッシュの列の種類は何ですか?
追加された 著者 mu is too short,
列をバイナリに変更するとどうなりますか?それは文字列を「8ビットASCII」(つまり生のバイト)として取り出し、 YAML.load を形に変換します。簡単なテストとして、 YAML.load(broken)の前に broken.force_encoding( 'binary')することができます。
追加された 著者 mu is too short,
Iconv.conv( 'UTF-8'、 'ISO-8859-1'、 "\ xEF \ xBC \ x88 \ xE5 \ x9B \ x9E \ xE5 \ xBD \ xA2 \ xE9 \ x92 \ x88 \ irb の中に\ xEF \ xBC \ x8‌ 9 ")文字列はUTF-8であると主張していますが、私は彼らがLatin-1に混乱していると思います。
追加された 著者 mu is too short,
文字列に生のバイト( \ xEF \ xBC ...)を残している irb でダブルバックスラッシュを手動で削除すると、Rubyインタプリタは簡単なUTF -8文字列に中国語文字をそのまま残して、 puts broken_string を実行すると、中国語が表示されます。
追加された 著者 mu is too short,
@muistooshort:列の種類は text です。
追加された 著者 rekado,
バイナリへの変換は役に立ちませんでした。結果のハッシュは、なしの場合と同じです。
追加された 著者 rekado,
YAML.load は、二重バックスラッシュを削除してシリアライズされたハッシュ文字列を手動で編集しても問題ありません。上記の文字列の代わりに、この文字列を読み込むと、次のような文字列が読み込まれます:<! - >:!--- map:ActiveSupport :: HashWithIndifferentAccess \ ncheckbox:\ "1 \" \ nchoice:\ "丸い紙クリップ\ xEF \ xBC \ x88 \ xE5 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \ x9 \/code>
追加された 著者 rekado,

2 答え

これは、利用可能な2つのYAMLエンジン "syck"と "psych"の動作の違いによって引き起こされたようです。 YAMLエンジンをsyckに設定するには:

YAML :: ENGINE.yamler = 'syck'

YAMLエンジンをpsychに戻すには:

YAML :: ENGINE.yamler = 'psych'

"syck"エンジンは文字列を期待どおりに処理し、適切な中国語文字列でハッシュに変換します。 "psych"エンジンが使用されている場合(ルビ1.9.3のデフォルト)、変換によって文字化けが発生します。

上記の行(2つのうちの最初の行)を config/application.rb に追加すると、この問題が解決されます。 "syck"エンジンはもはや維持されていないので、この回避策を使用して、 "psych"の文字列を受け入れられるようにしてください。

12
追加された
私たちは同じことを同時に見ていたようです。私はすべてをPsych形式にエンコードし直すか、YAMLを完全に破棄し、JSONまたは他の安定した/移植可能な形式を使用して手動でシリアル化します。
追加された 著者 mu is too short,
ところで、あなたはあなた自身の答えを受け入れることができ、私はこの場合にそうするのが理にかなっていると思います。
追加された 著者 mu is too short,

1.9.3 NEWSファイルから:

* yaml
  * The default YAML engine is now Psych. You may downgrade to syck by setting
    YAML::ENGINE.yamler = 'syck'.

どうやら、SyckとPsychのYAMLエンジンは非ASCII文字列を違ったやり方で扱います。

あなたが持っているようなハッシュを与えられた:

h = {
    "checkbox" => "1",
    "choice"   => "Round Paper Clips  (回形针)\r\n",
    "info"     => "10盒"
}

古いSyckエンジンを使う:

>> YAML::ENGINE.yamler = 'syck'
>> h.to_yaml
=> "--- \ncheckbox: "1"\nchoice: "Round Paper Clips  \\xEF\\xBC\\x88\\xE5\\x9B\\x9E\\xE5\\xBD\\xA2\\xE9\\x92\\x88\\xEF\\xBC\\x89\\r\\n"\ninfo: "10\\xE7\\x9B\\x92"\n"

私たちはあなたのデータベースに現在持っている醜いダブルバックスラッシュフォーマットを取得します。 Psychに切り替える:

>> YAML::ENGINE.yamler = 'psych'
=> "psych"
>> h.to_yaml
=> "---\ncheckbox: '1'\nchoice: ! "Round Paper Clips  (回形针)\\r\\n"\ninfo: 10盒\n"

文字列は通常のUTF-8形式のままです。 Latin-1になるように手動でエンコードすると、

>> Iconv.conv('UTF-8', 'ISO-8859-1', "\xEF\xBC\x88\xE5\x9B\x9E\xE5\xBD\xA2\xE9\x92\x88\xEF\xBC\x89") 
=> "ï¼\u0088å\u009B\u009Eå½¢é\u0092\u0088ï¼\u0089"

あなたが見ているナンセンスを手に入れます。

YAMLのドキュメントはかなり薄いので、Psychに古いSyckフォーマットを理解させることができるかどうかはわかりません。私は3つの選択肢があると思います:

  1. 旧式のサポートされなくなったSyckエンジンを使用すると、何かYAMLする前に YAML :: ENGINE.yamler = 'syck' する必要があります。
  2. Syckを使用してすべてのYAMLを読み込んでデコードし、Psychを使用して再エンコードして保存します。
  3. JSON(またはその他の安定した予測可能な移植可能なテキスト形式)を使用して手動でシリアライズ/デシリアライズするために serialize の使用を停止するか、アソシエーションテーブルを使用してシリアル化されたデータをすべて。
9
追加された
@rekado:私はYAMLから完全に離れていきたいと思います。データシリアライゼーションのための恐ろしいフォーマットだと思うし、Railsの人たちは serialize にそれを使うのは愚かでした。しかし、私はまた、自然に生まれた異端者です:)
追加された 著者 mu is too short,
ハ、それはクールだ:私はそれを考え出した後、あなたの答えを提出した。私は今、 "syck"を強制的に使用してアプリケーションを一時的に修正しました。最終的には、私はそれをハードなやり方で行い、すべてを「精神」で再エンコーディングする必要があります。実際には、互換性のない変更が好きではありません。
追加された 著者 rekado,