Hatena::ブログ(Diary)

Jamzzの日々

2010-06-04

[]IronRuby 1.0における日本語コード変換

IronRuby 1.0ではmodule NKFのModule Functionsが定義されておらず、NKFモジュールおよびKconvモジュールの各機能が使えません。

そこでWindows版のnkfを使ってNKFモジュールおよびKconvモジュールの主要な機能が使えるようにする方法を考えてみました。

1.nkf32.dllをパスの通ったフォルダにコピーする

windows版のnkfは「nkf.exe nkf32.dll Windows用のダウンロード : Vector ソフトを探す!」からダウンロードできます。

nkf32.dllにはNT版と9x版があります。

Windows XP以降のOSの場合にはNTフォルダの下のnkf32.dllを使用します。

実行環境でパスの通ったフォルダ、例えば

C:\Program Files\IronRuby 1.0\bin

等にコピーします。

2.module NKFのModule Functionsの実装

上記nkf32.dllを使用してmodule NKFのModule Functionsを実装します。

例えば以下のソースをironnkf.rbというファイルに保存します。

require 'nkf'

$KCODE="SJIS"
require 'Win32API'

module NKF
  @@SetNkfOption = Win32API.new 'nkf32', 'SetNkfOption', 'p', 'i'
  @@NkfConvertSafe = Win32API.new 'nkf32', 'NkfConvertSafe', %(p i p p i), 'v'
  @@NkfGetKanjiCode = Win32API.new 'nkf32', 'NkfGetKanjiCode', 'v', 'i'
  
  def nkf(opt, str)
    @@SetNkfOption.call opt
    ret = "\x00" * (str.size * 3 + 10)
    ret_len = "\x00\x00\x00\x00"
    @@NkfConvertSafe.call ret, ret.GetByteCount, ret_len, str, str.GetByteCount
    ret_len = ret_len[0] + ret_len[1] * 0x100 + ret_len[2] * 0x10000 + ret_len[3] * 0x1000000
    ret[0..ret_len-1]
  end
  module_function :nkf
  
  def guess2(str)
    nkf "-g", str
    nkf_ret = @@NkfGetKanjiCode.call
    k_code = NKF::UNKNOWN
    case nkf_ret
    when 0
      k_code = NKF::SJIS
    when 1
      k_code = NKF::EUC
    when 2
      k_code = NKF::JIS
    when 3
      k_code = NKF::UTF8
    when 4, 5
      k_code = NKF::UTF16
    else
      k_code = NKF::UNKNOWN
    end
    k_code
  end
  module_function :guess2
  
  alias_method :guess, :guess2
  module_function :guess

#  alias_method :guess1, :guess2
#  module_function :guess1
end

この実装では

$KCODE="SJIS"

を設定しています。

他の個所で別の指定が必要な場合にはご注意ください。

都合が悪い場合にはメソッドの中でその時の$KCODEの値をコピーしておいて"SJIS"をセットしてから処理を行って、メソッドを抜けるときに元に戻すなどにする方が良いかもしれません。

例えば

  def nkf(opt, str)
    kcode_copy = $KCODE
    $KCODE = "SJIS"
    @@SetNkfOption.call opt
    ret = "\x00" * (str.size * 3 + 10)
    ret_len = "\x00\x00\x00\x00"
    @@NkfConvertSafe.call ret, ret.GetByteCount, ret_len, str, str.GetByteCount
    ret_len = ret_len[0] + ret_len[1] * 0x100 + ret_len[2] * 0x10000 + ret_len[3] * 0x1000000
    $KCODE = kcode_copy
    ret[0..ret_len-1]
  end

こんな感じです。

なおこの$KCODE="SJIS"とする理由なのですが、IronRubyではこの$KCODEの指定による動作が怪しいです。

実際に上記のスクリプトでは$KCODE="SJIS"を指定しないと期待するよう結果が得られません。

おそらく$KCODE="NONE"の場合にも文字列が意図せずに文字コード変換が行われているのではないかと思われます。

あともうひとつの注意点として上記の実装では#guess1がコメントアウトされています。

#guess1が使用される場合でとりあえず#guess2と同じ動作で良ければ

#  alias_method :guess1, :guess2
#  module_function :guess1

この2行のコメントをはずせばよいでしょう。

3.上記実装の利用

module NKFおよびmodule Kconvを使用する前に上記の実装ファイルをrequireしてください。

例えば

require 'ironnkf'

等とします。

これでNKF#nkfNKF#guess、Kconv#kconvKconv#isXXX、Kconv#toXXX等の主要な機能が利用できます。

他のライブラリなどで内部的にmodule NKFおよびmodule Kconv等を使用している場合にもそれらのライブラリを読み込む前にこの実装をrequireすれば大丈夫です。

4.その他、補足

上記内容の品質を保証するつもりはないので十分に確認できているわけではありませんが主な機能は利用できると思います。

制限事項として認識している点は以下の通りです。

  • NKF#guess1は未実装です。関連してKconv#guess_oldも機能しません。とりあえずNKF#guess2と同じ動作で良ければ上記2.にある通りaliasしてください。
  • NKF::NKF_RELEASE_DATE、NKF::NKF_VERSION、NKF::VERSIONのconstantには対応していません。nkf32.dllからバージョンに関する情報は取得できますが未実装です。

2010/10/15修正

みーすけさんのコメントの指摘内容を反映してコードを修正。

みーすけみーすけ 2010/10/15 16:38 はじめまして。IronRubyを使用するにあたり、本記事を大変参考にさせていただきました。ありがとうございます。

コード上に誤りではないかと思われる点を2点発見したため、指摘させてください。

> ret = '\x00' * (str.size * 3 + 10)

この行ですが、「'\x00'」ではなく「"\x00"」が正しいのではないでしょうか。
「'\x00'」でも動作はしますが、余分にメモリを確保することになるかと思います。

> ret_len = ret_len[0] + ret_len[1] * 0x10 + ret_len[2] * 0x1000 + ret_len[3] * 0x100000

この行ですが、0x10, 0x1000, 0x100000はそれぞれ0が一個足りないのではないでしょうか。
長い文字列の変換時に、正しい文字列長を判別できず、文字列が途中でカットされる動作になってしまうかと思います。

以上、失礼いたしました。

jamzzjamzz 2010/10/15 23:52 みーすけ さん

ありがとうございます。
全くご指摘の通りです。
本文も訂正いたします。

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/jamzz/20100604/1275627771