Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

文字化けした「’」を元に戻す方法 ― Rubyで学ぶ二重エンコード問題の解決

先週の記事では、ほとんどのエンコーディング問題を解決できるシンプルなプロセスを紹介しました。しかし、中には解決がはるかに難しいエンコーディング問題が存在します。

きっとあなたも見たことがあるはずです。カーリークォート(’)が「’」に、emダッシュ(—)が「—」に変わってしまう現象です。まるで自分がおかしくなったのかと疑いたくなりますが、本来ならそのまま動くはずのものです。

巨大な対応表を作り、壊れた文字を見つけては正しい文字に置き換えることもできます:

[{broken: '–',    fixed: "—"}
 {broken: "‗",    fixed: "–"}
 {broken: "‘",    fixed: "‘"}
 {broken: "’",    fixed: "’"}
 {broken: "“",    fixed: "“"}
 {broken: "”", fixed: "”"}, ...]

しかし、もっと簡単で確実な修正方法があるのです。

なぜ美しいタイポグラフィはいつも壊れるのか?

先週学んだように、エンコーディングとは、意味を持たないバイトのグループを表示可能な文字へ変換する仕組みのことです。すべての文字を1バイトで表現できるわけではありません。256種類以上の文字が存在するからです。そのため、カーリークォート(’)のような一部の文字は、複数のバイトで表現されます:

irb(main):001:0> "they’re".bytes
=> [116, 104, 101, 121, 226, 128, 153, 114, 101]

文字列は7文字しかないのに、9バイトで表現されているのです!

カーリークォートだけに注目してみると:

irb(main):002:0> "’".bytes
=> [226, 128, 153]

3バイト使っていることがわかります。そして、壊れた文字列「they’re」は、本来1文字であるべき場所に3文字含まれています。これは偶然とは思えませんよね?

本来、この3バイトはUTF-8として読めばカーリークォートを表します。ところが、各バイトがそれぞれ別の文字として表示されてしまっています。では、どのエンコーディングが [226, 128, 153] を「’」として表現するのでしょうか?主要なエンコーディングのテーブルをいくつか調べると、答えはWindows-1252だとわかります。

irbで確認できます:

irb(main):003:0> "they’re".force_encoding("Windows-1252").encode("UTF-8")
=> "they’re"

(最後の .encode("UTF-8") は、コンソールに文字列を表示するために必要です。)

そうです!これが問題の正体です。しかし、事態はさらに悪化します。

データは本来UTF-8であるべきなのに、Windows-1252として誤読されています。そして、そのデータをおそらくデータベースやファイルにUTF-8として保存することになるでしょう。Rubyは親切にも自動的にUTF-8へ変換してくれるため、結果的に次のようになってしまいます:

irb(main):004:0> "they’re".force_encoding("Windows-1252").encode("UTF-8")
=> "they’re"
irb(main):005:0> "they’re".force_encoding("Windows-1252").encode("UTF-8").bytes
=> [116, 104, 101, 121, 195, 162, 226, 130, 172, 226, 132, 162, 114, 101]

文字列はなんと2回も不正にエンコードされてしまったのです。壊れた文字たちは、あたかも最初からそこにあるべきだったかのように見えます。原因を知らないままでは、元に戻すのはほぼ不可能でしょう。

どうやって直せばいいのか?

元に戻すにはどうすればいいでしょうか?問題を逆順に考えてみましょう:

  • UTF-8の文字列(they’re)がある

  • それはWindows-1252の文字列(they’re)から変換されたものである

  • さらにそのバイトは、本来UTF-8として読まれるべきだった(they’re)

修正するには、この手順を逆にたどればいいのです。encodeを使ってUTF-8の文字列をWindows-1252の文字列に変換し直し、次にforce_encodingを使って、その誤ってエンコードされたWindows-1252の文字列を強制的にUTF-8として読み込みます:

irb(main):006:0> "they’re".encode("Windows-1252").force_encoding("UTF-8")
=> "they’re"

直りました!

ただし、小さな問題が一つ…

残念ながら、この問題に気づいたのは、おそらく大量のファイルやデータベースレコードに不正にエンコードされたデータが混入していたからでしょう。しかし、すべてのファイルやレコードが必ずしも壊れているわけではありません。正常なデータと壊れたデータが混在している可能性があります。特に、サイト訪問者から送信されたデータの場合はそうです。

その場合、すべての文字列に対して盲目的にこのコードを実行してはいけません:

irb(main):007:0> "they’re".encode("Windows-1252").force_encoding("UTF-8")
=> "they’re"
irb(main):008:0> "they’re".encode("Windows-1252").force_encoding("UTF-8")
=> "they\x92re"

正常なデータに実行すれば、それを壊れたデータに変えてしまうだけです。では、どうすればいいのでしょうか?

ヒューリスティック(経験則)を使う方法があります。「â」のような壊れた文字を含む文字列だけを変更対象にするのです。有効な文字列に「â」のような文字が絶対に現れないのであれば、この方法はうまく機能します。

ただ、私が前回この種のバグを修正したときは、安全策を取りたいと思いました。そこで、もう一つの便利な道具――自分の目を使いました。

不正にエンコードされた文字列を見つけるたびに、置換後の文字列と一緒に出力したのです:

Changing title with ID 6 from "They’re over there!" to "They’re over there!"

こうすれば、実際に変更された少数の文字列を目視でスポットチェックでき、さらなる破損が起きていないことを確認できました。

頭が痛くなってきた…

先週も述べたように、同じデータに対する複数の解釈を頭の中で整理しておくのは大変です!混乱したときは、irbコンソールで実際に試してみると理解が深まります。ぜひ試してみてください。コンソールを開いて、「—」と「‗」、「“」と「“」の間を行き来できるか確認してみましょう。

こうした複雑な概念を繰り返し練習することが、いざという時に自信を持って対処できるようになる最短ルートです。『Practicing Rails』の無料サンプル章では、そのために最適なテクニックとプロセスを学ぶことができます。


  1. AndroidアプリでURLからビットマップ(Bitmap)を取得する方法を解説

    はじめにこの記事では、Androidアプリで指定したURLからビットマップ画像を取得して画面に表示する方法を、サンプルコードとともに段階的に解説します。ネットワーク上の画像を取得する処理は、AsyncTaskを使ってバックグラウンドで実行するのがポイントです。手順1:新しいプロジェクトを作成するまず、Android Studioを開き、「File」→「New Project」を選択して、必要な情報を入力し、新しいプロジェクトを作成します。手順2:レイアウトファイル(activity_main.xml)を編集する次に、res/layout/activity_main.xmlに以下のコードを追加し

  2. ChromeでGmailの新着通知を受け取る方法|標準設定と拡張機能を使い分ける

    パソコンでGmailの新着メッセージをいちいち確認するのは面倒です。Chromeの通知機能を活用すれば、新しいメールが届いた瞬間にすぐ気づけます。基本的な通知はGmailの標準設定だけで実現でき、さらに柔軟な運用を求めるなら無料のブラウザ拡張機能を使えば、Gmailのウィンドウやタブを閉じている間でも最新情報を受け取ることが可能です。 Gmailの標準機能で通知を受け取る 拡張機能をインストールしたくない場合は、Gmailアカウント内の通知設定を利用しましょう。有効にすると、ChromeやSafari、Firefoxなど主要なブラウザでGmailからの通知を受け取れるようになります。ただしこ