このブログの更新は Twitterアカウント @m_hiyama で通知されます。
Follow @m_hiyama

メールでのご連絡は hiyama{at}chimaira{dot}org まで。

はじめてのメールはスパムと判定されることがあります。最初は、信頼されているドメインから差し障りのない文面を送っていただけると、スパムと判定されにくいと思います。

参照用 記事

Erlang実験室:EDocで日本語を使う方法

Erlangには、EDocという、javadocとよく似たドキュメンテーション生成ツールがあります。

EDocはErlangソースファイル内のドキュメンテーションコメントからマニュアルを生成します。が、そのままでは日本語が使えないんですよね。

症状

まず、Shift JISのような(非Unicodeな)ネイティブエンコーディングで書くと、


error in XML parser: {ucs,{bad_utf8_character_code}}.
のようなエラーになります。EDoc内で使っているXMLパーザー(xmerl、「Erlang実験室:ErlangのXML処理」を参照)が文句を言っているようです。

しょうがないので、入力ファイル(Erlangソースファイル)内ではUTF-8を使うことにして再実行。


Bad value on output port 'efile'
ハァー!?

Bad value云々はIOシステムの深い部分から出ているエラーメッセージのようです。メモリ内にデータ構造を作ることは成功しています。その証拠に、edoc:get_doc("utf8comments.erl").とすると、XMLデータ構造を取得できます。このデータ構造をファイルに書き出すときに何か不都合が起きているようです。

調査

EDocのソースファイルは、$ERLANG_HOME/lib/edoc-0.7.2/src/ 内にあります。ファイルへの書き出しは、edoc_lib:write_file/4 で行いますが、出力の実行にはファイルディスクリプタFDとテキストデータTextに対してio:put_chars(FD, Text)を呼び出します。

io:put_chars/2に渡されるTextを眺めたら、[..., 32,26085,26412,35486,46]なんてのがあります。IOはバイト単位なので、0から255の整数値でないと"Bad value on output port"ですわな、確かに。


26085 = 0x65e5
26412 = 0x672c
35486 = 0x8a9e
なんですが、http://www.unicode.org/charts/PDF/U4E00.pdfを調べてみると、

  • U+65E5は「日」(U4E00.pdfのP.25)
  • U+672Cは「本」(U4E00.pdfのp.27)
  • U+8A9Eは「語」(U4E00.pdfのp.62)

つまり、XMLバーザーが、Unicode文字番号(コードポイント)そのままの整数値を使ったリスト(DOMのテキストノード相当のデータ構造)を作っていたわけです。

26085のような大きな整数値は、出力の手前で、適当なエンコーディング・スキームによるバイト列(に相当する整数列)に変換しなくてはなりません。

対処

モジュールxmerl_ucs内にto_utf8/1という関数があります。


> xmerl_ucs:to_utf8(26085).
[230,151,165]
> xmerl_ucs:to_utf8([26085,26412,35486]).
[230,151,165,230,156,172,232,170,158]

このxmerl_ucs:to_utf8/1で出力直前に変換をほどこすことにします。to_utf8は入れ子のリストをフラットにしてしまいますが実害はありません。[追記 date="2007-10-02"]もう少しマシなパッチはコチラ[/追記]

修正の差分:

--- edoc_lib.erl.orig	Wed Mar 28 03:44:18 2007
+++ edoc_lib.erl	Fri Sep 28 10:24:14 2007
@@ -41,7 +41,7 @@
 -import(edoc_report, [report/2, warning/2]).
 
 -include("edoc.hrl").
--include("xmerl.hrl").
+-include_lib("xmerl/include/xmerl.hrl").
 
 -define(FILE_BASE, "/").
 
@@ -660,9 +660,10 @@
     Dir1 = filename:join([Dir | packages:split(Package)]),
     File = filename:join(Dir1, Name),
     filelib:ensure_dir(File),
+    Utf8Text = xmerl_ucs:to_utf8(Text),
     case file:open(File, [write]) of
 	{ok, FD} ->
-	    io:put_chars(FD, Text),
+	    io:put_chars(FD, Utf8Text),
 	    file:close(FD),
 	    ok;
 	{error, R} ->

事実上、Utf8Text = xmerl_ucs:to_utf8(Text),の1行を追加しただけです。-includeから-include_libへの変更はコンパイルを楽にするためです。なお、コマンドラインからの再コンパイルは:


> erlc -o ../ebin edoc_lib.erl

[追記]「Erlang EDoc、一筋縄ではいかないや」「Erlang EDoc、やんなっちゃう」も参照。[/追記]