JRubyでは、Ruby関数とJava関数を組み合わせることで、AS400(CCSID5026、5035)の文字列変換が容易に行なえます。
はい。あると思います。
早速、実験してみましょう。
なぜJRubyなのか?
なぜJRubyなのか?
まず、私がネットでの調査した内容を、箇条書きにしていきます。
- Javaは、文字列から1文字抜粋といった処理がサロゲートペア・絵文字に対応しているとは言えなさそう
- Rubyではサロゲートペアでも絵文字でも1文字は1文字とカウントされ、1文字単位の抜粋も容易
- Rubyでの文字コード変換は、encodeメソッドだが、取り扱える(マイナー)文字コードに限界がありそう
- 文字コード変換にはKconvもあるが、いまいち情報に乏しい(気がする)
- 調べると、Javaではマイナーな文字コードの変換をサポートしている模様
- JRubyでは、Rubyの全機能に加え、Java関数・メソッドが利用できる
ここで言う「マイナーな文字コード」とは、CCSIDの5026や5035といったもの。
RubyはRubyとJavaの「いいところ取り」ができるので、こういった処理での出番がありそうですね。
JRubyも(Ruby同様)サロゲートペアは正しく扱われるか?
プログラミング例
Rubyではサロゲートペアでも絵文字でも1文字は1文字とカウントされるが、JRubyでも同じ挙動なのか、念のために調べてみましょう。
(これが大前提になります。)
●len.rb
#require 'bundler/setup' # UTF-8文字列 strUtf8 = "abcabc①你好𩸽🍣🍺" puts "UTF-8文字数=>#{strUtf8.size}" # Shift_JIS化 # 半角英小文字は大文字に変換 strSjis = strUtf8.encode("Shift_JIS", invalid: :replace, undef: :replace).upcase(:ascii) puts "Shift_JIS文字数=>#{strUtf8.size}" # UTF-8に戻す strSjis2Utf8 = strSjis.encode("utf-8", invalid: :replace, undef: :replace) puts "Shift_JIS文字数(utf8戻したもの)=>#{strSjis2Utf8.size}" # 配列化 arrUtf8 = strUtf8.split('') arrSjis = strSjis2Utf8.split('') # ファイルに結果を出力 file = File.open("sample.txt", "w") arrUtf8.each_with_index do |st, idx| file.puts "UTF-8=>#{st}、Shift_JIS=>#{strSjis2Utf8[idx]}" end file.close exit
upcase
で半角英字は大文字になりますが、全角も大文字ならないようにするには、Ruby2.4.0以降ではupcase(:ascii)
とする必要があるようです。
実行結果
実行結果は下記の通り。
> jruby len.rb UTF-8文字数=>12 Shift_JIS文字数=>12 Shift_JIS文字数(utf8戻したもの)=>12
実行結果のsample.txt
は…
UTF-8=>a、Shift_JIS=>A UTF-8=>b、Shift_JIS=>B UTF-8=>c、Shift_JIS=>C UTF-8=>a、Shift_JIS=>a UTF-8=>b、Shift_JIS=>b UTF-8=>c、Shift_JIS=>c UTF-8=>①、Shift_JIS=>? UTF-8=>你、Shift_JIS=>? UTF-8=>好、Shift_JIS=>好 UTF-8=>𩸽、Shift_JIS=>? UTF-8=>🍣、Shift_JIS=>? UTF-8=>🍺、Shift_JIS=>?
JavaベースのJRubyでも問題なく、サロゲートペアでも絵文字でも、1文字ずつ抜粋できますね。
サロゲートペアについてまとめると、こうなります。
サロゲートペア操作 | Java/JavaベースのWeb言語 | JRuby |
---|---|---|
文字数カウント | 2文字としてカウント 裏技でJava関数を組み合わせることにより可能 (codePointCount) |
1文字でカウント |
文字を抽出 | 不可能 (Javaのバージョンが古い?) |
可能 |
JRuby、使えそうですね!
ただし、マイナーな文字コードの対応を行うため、次章ではRubyのecode()
ではなく、Javaの関数を使用します。
JRubyでJavaのメソッドを使用し、文字コードを変換する
Rubyのencodeメソッドでは扱えない文字コードがある?
オフィス用のコンピュータでは、半角英字の小文字が使えなかったり、丸数字がダメで、Unicodeなんてもってのほか…といった、かなりシビアな文字コードを要求されるものがあります。
有名どころではAS400のCCSID=5026あるいは5035でしょうか。
この2つの違いは半角英小文字が使用できない/使用できるの違いくらいで、S_JISに毛が生えたような文字コードなのですが、S_JISとは多少異なります。
この2つの文字コードで特に厄介なのは、機種依存文字の中で使用できる/使用できない文字が混在すること。
具体的には、「①」が使用できませんが、「㈱」は使用可能。
Windows-31Jでは両方とも扱え、Shift_JISでは両方とも扱えないので、この2つでは完璧な変換はできません。セーフティなのはShift_JISですが、「5026/5035で扱える文字は可能な限り変換したい」というニーズがあると思いますので、そのニーズに可能な限り応えられるプログラムを作成していきます。
JRubyならJavaのgetBytes()で変換できそう
Rubyの文字コード変換encode()
では5026/5035相当の文字コードは扱っていないようです。
しかしJavaのjava.lang.String.new
やgetBytes()
ではどうやら扱っている模様。
Olacleにある、「サポートされているエンコーディング 」の表中の「Cp930」がCCSID=5026相当で、「Cp939」がCCSID=5035相当のようですね。
JRubyでのコーディング例
CCSID=5026の場合のコーディング例。
●test_5026.rb
#require 'bundler/setup' require 'java' # UTF-8文字列 javaStrUtf8 = java.lang.String.new("abcabc①㈱你好𩸽🍣🍺".upcase(:ascii)) # 5026に変換 javaStr5026 = java.lang.String.new(javaStrUtf8.getBytes("Cp930"), "Cp930") javaStrNewUtf8 = java.lang.String.new(javaStr5026.getBytes("UTF-8"), "UTF-8") # 配列化 arr_Utf8 = javaStrUtf8.to_s.split('') arr_5026 = javaStrNewUtf8.to_s.split('') # ファイルに結果を出力 file = File.open("sample5026.txt", "w") file.puts "UTF-8=>#{javaStrUtf8}、Cp930(5026)=>#{javaStrNewUtf8}" file.puts "-"*20 file.puts "●CCSID=5026との比較" arr_Utf8.each_with_index do |st, idx| file.puts "UTF-8=>#{st}、Cp930(5026)=>#{arr_5026[idx]}" end file.close
CCSID=5035の場合はこちら。
●test_5035.rb
#require 'bundler/setup' require 'java' # UTF-8文字列 javaStrUtf8 = java.lang.String.new("abcabc①㈱你好𩸽🍣🍺") # 5035に変換 javaStr5035 = java.lang.String.new(javaStrUtf8.getBytes("Cp939"), "Cp939") javaStrNewUtf8 = java.lang.String.new(javaStr5035.getBytes("UTF-8"), "UTF-8") # 配列化 arr_Utf8 = javaStrUtf8.to_s.split('') arr_5035 = javaStrNewUtf8.to_s.split('') # ファイルに結果を出力 file = File.open("sample5035.txt", "w") file.puts "UTF-8=>#{javaStrUtf8}、Cp939(5035)=>#{javaStrNewUtf8}" file.puts "-"*20 file.puts "●CCSID=5035との比較" arr_Utf8.each_with_index do |st, idx| file.puts "UTF-8=>#{st}、Cp939(5035)=>#{arr_5035[idx]}" end file.close
Java変数やメソッドを使用しますので、require 'java'
を忘れずに。
5026/5035の違いは主に変数や、cp930
とcp939
ですが、5026のみupcase(:ascii)
が5行目に挿入されています。
5026の場合は半角英小文字は認められないのですが、Javaの文字コード変換ではcp930/cp939関わらず通ってしまうため、Rubyのupcase(:ascii)
で、5026のみ半角英小数字を大文字化しています。
java.lang.String.new()
は見た目通りJavaのメソッドです。
これらとJavaメソッドgetBytes()
を組み合わせて、文字列を変換・再定義していきます。
ソース前半の文字列の定義や変換はJavaで行っていますので、Rubyではそのままでは文字列として使用できません。
下記のようにto_s
メソッドにて、Rubyで扱える文字列に変換します。
# 配列化 arr_Utf8 = javaStrUtf8.to_s.split('') arr_5035 = javaStrNewUtf8.to_s.split('')
こういった言語間同士の変数型変換は、RubyでPythonのリソースを実行できるPyCall でも見られますね。
プログラム例ではJava変数(上記ではjavaStrUtf8
とjavaStrNewUtf8
)と、Ruby変数(同じくarr_Utf8
とarr_5035
)の見分けが付くようにしています。
Javaの変数はキャメルケース推奨ですが、Rubyはスネークケースが推奨されています。
実行結果
実行してみましょう。 5026はこちら。
jruby test_5026.rb
5035はこちら。
jruby test_5035.rb
実行結果(5026)。
UTF-8=>ABCabc①㈱你好𩸽🍣🍺、Cp930(5026)=>ABCabc?㈱?好??? -------------------- ●CCSID=5026との比較 UTF-8=>A、Cp930(5026)=>A UTF-8=>B、Cp930(5026)=>B UTF-8=>C、Cp930(5026)=>C UTF-8=>a、Cp930(5026)=>a UTF-8=>b、Cp930(5026)=>b UTF-8=>c、Cp930(5026)=>c UTF-8=>①、Cp930(5026)=>? UTF-8=>㈱、Cp930(5026)=>㈱ UTF-8=>你、Cp930(5026)=>? UTF-8=>好、Cp930(5026)=>好 UTF-8=>𩸽、Cp930(5026)=>? UTF-8=>🍣、Cp930(5026)=>? UTF-8=>🍺、Cp930(5026)=>?
キーポイントは、5026は機種依存文字の内「①」は対象外、「㈱」は対象なのですが、期待どおり「①」は「?」となり、「㈱」は生き残りました! これは素晴らしい!
さらに、5035では…
UTF-8=>abcabc①㈱你好𩸽🍣🍺、Cp939(5035)=>abcabc?㈱?好??? -------------------- ●CCSID=5035との比較 UTF-8=>a、Cp939(5035)=>a UTF-8=>b、Cp939(5035)=>b UTF-8=>c、Cp939(5035)=>c UTF-8=>a、Cp939(5035)=>a UTF-8=>b、Cp939(5035)=>b UTF-8=>c、Cp939(5035)=>c UTF-8=>①、Cp939(5035)=>? UTF-8=>㈱、Cp939(5035)=>㈱ UTF-8=>你、Cp939(5035)=>? UTF-8=>好、Cp939(5035)=>好 UTF-8=>𩸽、Cp939(5035)=>? UTF-8=>🍣、Cp939(5035)=>? UTF-8=>🍺、Cp939(5035)=>?
5026との違いは、5035は英小文字を許していることです。
5026/5035とも、変換できない文字は「?」となります。
上記プログラム例では、「?」となった場合の変換前の文字も拾うことができますので、仮にこれを入力フォーム画面の住所欄だとすると、
住所欄の内「①你𩸽🍣🍺」は入力できない文字です。
といったように、変換できなかった文字はこれとこれ!といったメッセージを表示することが可能です。
最後に
実験結果の感想ですが、まさにRubyとJavaのいいところ取りだと思います。
今回のプログラム例では、単に変換のみを扱いましたが、以下に応用できそうです。
- CSVデータを介して、バッチプログラムにてオフィスコンピュータにデータを渡す際のチェック処理として
- オフィスコンピュータがJDBCをサポートしている場合は、APIプログラムを作成しリアルタイムにデータを更新する際のチェック処理として
Webシステムからオフィスコンピュータへの、データの受け渡し部分に実装できそうですね。
それでは、また。