2007/12/31

JRubyと日本語 解析(2)

とりあえず、JRubyではUTF8でソース書いて使うのが良いみたい。正規表現は/ほげ/uのようにuオプションを使えばヒットする。ここ参照

JRubyの内部はRubyStringというクラスで文字列を持っているっぽい。これはByte列で文字列をもっている。Kernel#openとかでファイルを開いたときは、Shift_JIS(Windows-31J)のファイルならばShift_JIS(Windows-31J)のバイト列で保持するし、InputStreamReaderでエンコードを指定して読み込んだとかJavaのAPI経由ならUTF-8で持っているみたい?

SJISを読んでEUCを出力するときは、Openの場合はSJISから

open(ARGV[0]).each do |line| puts NKF.nkf('-eS', line) end

InputStreamReaderの場合はUTF-8から

def open_with_encoding(path, encode, &block) reader = nil begin reader = java.io.BufferedReader.new( java.io.InputStreamReader.new( java.io.FileInputStream.new(ARGV[0]), encode)) line = nil while (line = reader.readLine()) != nil block.call(line) end ensure reader.close unless reader == nil end end open_with_encoding(ARGV[0], 'Windows-31J') do |line| puts NKF.nkf('-eW', line) end

JRubyのNKFはEUCを指定したときマルイチとか丸付き文字が入っていると、例外を吐いて落ちるみたい。これは内部で使用しているCharsetEncoderでWindows-31Jの丸付き数字をEUCにマッピングできないため、CharacterCodingExceptionをスローしてしまい、invalid encodingと怒られる。丸付き数字をEUCに変換は今のところあきらめるしかないようだ。

そもそもEUC-JPには丸付き文字(NEC拡張)が存在せず、Windowsで扱えるのは(Shift_JISに対するWindows-31Jみたいな)NEC拡張を含むEUC-JPを使っているからだそうで、Javaでは1.5からEUC_JP_Solarisというのを指定すれば使えるようだ。(JRubyのNKFは普通のEUC-JPを指定しているのでこれを試しに書き換えてみた。)ただしこれを使うと「~」がうまく扱えなくなった。参考

JRubyと日本語 解析(1)

JRubyの日本語処理についてソースを見てみる。対象はJRuby1.0.3

org.jruby.Mainから始まり、コマンドライン解析を行うorg.jruby.util.CommandLineParserでは-Kオプションが存在する。(Usageには存在しないオプションもいくつか存在するようだ。)

次にorg.jruby.Mainのインタープリター実行処理であるrunInterpreterメソッドでCommandlineParser#getScriptSourceでソースファイル(.rb)を読み出すReaderを設定する。このメソッドで、コマンドラインオプションで設定したorg.jruby.util.KCodeを使わずに、KCode.NONEというKCodeオブジェクト強制的に使用している。

そこに以下のようなコメントがある。

// KCode.NONE is used because KCODE does not affect parse in Ruby 1.8

Ruby1.8ではStringの内部として文字列をバイト列で保持するため、問題ないように思われるが、このKCode.NONEを使用した場合、ソースをiso-8859-1で解析するため、ShiftJISのソースを読ませたときに「十」のような2バイト目が\になるような文字が含まれていると正しくソースを解析できずにエラーになる。

ということで、CommandlineParser#getScriptSource()のハードコード部分を、設定されているKCodeオブジェクトを使用するように変更。また、java.util.KCodeのdecoderとencoderメソッドでUTF-8とそれ以外しか判定がない部分にSJISとEUCの処理を追加する。

とりあえずこれで、JRubyをビルドして以下のスクリプトを実行してみた。

puts "あ" puts "十"

jruby -Ks sjis.rb B ^

エラーにはならなくなったが文字が壊れた。RubyStringにするときにおかしいのか、Yaccの処理が2バイト文字を扱えないのか。とりあえずだめだということがわかった。

2007/12/21

Rubyと日本語1.9

Ruby1.9での変更点にあった。
Ruby1.9を語る

  • 文字列をバイトベースではなく、文字ベースで扱うようにした
    それぞれの文字はエンコーディングに対応
  • エンコーディングの指定
    openもエンコーディングに対応
    open(path, "r:EUC-JP"){|f| f.read}
    #デフォルトは、Rubyスクリプトのソースコードと同じ
    こうするとEUC-JPの文字列を得る (RubyスクリプトがUTF-8だからといって、それに自動的に変換されたりはしない)

すばらしい!Rubyっていまいち日本語が使いにくいなと思っていたところだったので非常にうれしい。JRubyへの実装はどういう状況なんだろう。楽しみ。

2007/12/19

JRubyと日本語

JRubyでどうも日本語の処理がうまくないなぁということでちょっと調べてみた。

環境はXP Proに以下

C:\test>ruby -v ruby 1.8.6 (2007-03-13 patchlevel 0) [i386-mswin32] C:\test>jruby -v ruby 1.8.5 (2007-12-15 rev 5200) [x86-jruby1.0.3]

(1)スクリプトに日本語を直接書いたとき(Shift_JISで保存)。

puts "こんにちは、JRuby!" puts "①" puts '十' puts "~"

Rubyは-Ksオプションをつければ動くけど、JRubyはエラーになる。

:1: sjis.rb:3: Invalid char `\201' in expression (SyntaxError)

「十」の2バイト目が\と同じになるというむかーし昔のCGIでよくでた文字化けと同じ。 puts '十\'で回避可能。
ちなみにJRubyには-Kオプションがない。

(2)ファイル入出力

こんにちは、JRuby! ① 表 ~

っていう内容のファイルをSJIS、EUC、UTF8で保存して

require 'kconv' require 'nkf' open(ARGV[0]).each do |line| puts line.tosjis puts NKF.nkf('-s', line) end

で読み込んで吐かせるだけ。

rubyはちゃんと動くんだけどJRubyだと。

C:\test>jruby output.rb euc.txt Kconv :こんにちは、JRuby! nkf :こんにちは、JRuby! Kconv :ュ。 nkf :ュ。 Kconv :ノス nkf :ノス Kconv :。チ nkf :。チ

C:\test>jruby output.rb utf8.rb Kconv :puts "縺薙s縺ォ縺。縺ッ縲゛Ruby!" nkf :puts "縺薙s縺ォ縺。縺ッ縲゛Ruby!" C:/tools/dev/jruby-1.0.3/lib/ruby/1.8/kconv.rb:207:in `tosjis': invalid encoding (ArgumentError) from :1 from :1:in `each' from :1

ばけらった。
どうもopen(file)のところでプラットフォームのデフォルトエンコードで読んだ文字列がByte列でもっていないようでNKFとかKConvできてない気がする。

(3)正規表現 Rubyだと日本語一文字ずつ扱うのに$KCODE='sjis'とかして "日本語".split(//)みたいなイディオムがあるらしいんだけど JRubyだとうまくいかなかったり、日本語を使った正規表現が マッチしなかったり。

$KCODE='SJIS' require 'nkf' open(ARGV[0]).each do |line| i=0 line.split(//).each do |c| puts "#{i}:#{c}" i += 1 end end

rubyは動くけどJRubyはエラー。これもlineに一行取った時点で文字列が壊れる。

ついでに1行野郎のテスト。

C:\test\kcode>ruby -e "open(ARGV[1]).grep(/#{ARGV[0]}/) {|s| puts s }" あ sjis.txt あいう①~十abc123

C:\test\kcode>jruby -e "open(ARGV[1]).grep(/#{ARGV[0]}/) {|s| puts s }" あ sjis.txt 's' は、内部コマンドまたは外部コマンド、 操作可能なプログラムまたはバッチ ファイルとして認識されていません。

おや?JRubyの起動バッチの仕組みがいけてないっぽいなぁ。

教えて、Rubyの日本語の扱いに詳しい人。