HaskellとUTF8での日本語文字列の取扱い
Haskellの日本語の取扱いで困っているような同じような人(Haskell初心者さん)へ向けて。本記事では、Ubuntuで全てUTF8で統一しているにも関わらず困った事象です。WindowsでDOS窓環境だったりすると、また違った困りかたになると思います(DOS窓はSJISのため)。
概要
Haskellを使ってHTMLのスクレーピングしようとしたときに、はまった日本語の取扱いについて書く。
具体的に困った事象
UTF8で保存したファイルのHaskellプログラムの中で
hoge == "次ページ"
のように、ごく普通に日本語の文字列マッチングした場合の話。
hogeは、tagSoupを通して加工した文字列が入っている変数。次ページというのが、ここでマッチさせたい日本語の文字列。
Webブラウザで保存してあったHTMLファイルをreadFileで、読み込んだ場合はTrueが返ってくる。しかし、Network.HTTP.simpleHTTPでネットから、同じホームページを直接読み込むと、マッチングにFalseが返ってくる。同じプログラムのはずなのに。もちろん、utf8で記述されているホームページですよ。
解決方法
cabalでutf8-srtingをインストールする。
そこで、一時的にファイル保存せずにネットから直接、読んできたときに、
hoge == Codec.Binary.UTF8.String.encodeString "次ページ"
とencodeString関数を通せばTrueになった。
原因追求編
環境
- Ubuntu 12.04
- GHC7.4.1
- cabal install tagsoup
- cabal install utf8-string
文字コードについて
すべてUTF8のはず。つまり、、、、
調査
GHCの日本語の扱いを把握する必要がある。後ほど、推測した原因を書くが、とりあえず、GHCiで確認できる事実を列挙する。
Main> putStrLn "日本語" 日本語 Main> print "日本語" "\26085\26412\35486" Main> Codec.Binary.UTF8.String.encode "日本語" [230,151,165,230,156,172,232,170,158] Main> Codec.Binary.UTF8.String.encodeString "日本語" "\230\151\165\230\156\172\232\170\158" Main> Codec.Binary.UTF8.String.decodeString "\230\151\165\230\156\172\232\170\158" "\26085\26412\35486" Main> putStrLn "\26085\26412\35486" 日本語
推測1(GHCとライブラリ)
GHCiで調べた事実と、http://itpro.nikkeibp.co.jp/article/COLUMN/20100406/346695/?P=5
から、以下のように推測している。
- GHCは、デフォルトのままでUTF8を扱える。
- printの場合は、ShowクラスでUTF8を、数値で表示する。
- putStrの場合は、そのまま日本語として表示される。
- readFileで、UTF8のファイルは、そのまま読み込まれGHC側に解釈される。
- Network.HTTP.simpleHTTPは、encodingした形で読み込まれる(というより、ネットワーク越しから生バイナリを、単純に取り込んでいるということなのかな?)
- tagSoupは素通し。
- Webブラウザで保存した場合は、WebブラウザがUTF-8と解釈してデコードしてくれている。たぶん。
推測2(utf8とUnicode)
日本語周り(というかユニコード)の知識が、曖昧なため、間違っているかも。
単なるバイナリデータをutf8としてデコード(Codec.Binary.UTF8.String.decode)すると、Unicodeに変換されるということなのではないか?
つまり、"\230\151\165\230\156\172\232\170\158"が生のバイナリで、これをutf8としてデコードすると"\26085\26412\35486"というユニコードに変換される。
わかりやすく書くと
バイナリ = utf-8としてdecode => ユニコード バイナリ <= utf-8としてencode => ユニコード
という感じ。