Ruby でパーサーコンビネータを使った CSV ファイルのパース処理 - RParsec 使用

id:fits:20101226 や id:fits:20101231 で実施したパーサーコンビネータによる CSV ファイルのパース処理を RParsec を使って JRuby でやってみました。

環境は以下の通り。

  • JRuby 1.5.6
  • RParsec 1.0

サンプルのソースは http://github.com/fits/try_samples/tree/master/blog/20110103/

事前準備 - RParsec のインストール

RubyGems を使って RParsec をインストールしておきます。

インストール
> gem install rparsec

CSVファイルのパース

以下の CSV ファイルをパースしてみる事にします。

test.csv
1,テスト1,"改行
含み"
2,test2,"カンマ,含み"
3,てすと3,"ダブルクォーテーション""含み"

注目する点は以下の通り。

  • 改行は \r\n にマッチ
  • >> と << は Scala の ~> と <~ と同等
  • separated は Haskell の sepBy と同等
  • value は Haskell の return と同等
  • many や separated は Parser のメソッド
  • Haskell の noneOf の代わりに not_char や not_string、もしくは regexp で正規表現を使用

また、quotedCell の many では結果的に quotedChar の文字コード配列が返される事になるため、bind に渡したブロック内で pack を使って文字列化するようにしました。
(文字列を得るには fragment を使う方法もあるが、fragment だとパース対象文字列の一部をそのまま抜き出す事になり、今回の用途には適さない)

parse_csv.rb
require 'rubygems'
require 'rparsec'

include RParsec::Parsers

eol = string "\r\n"
#packで文字列化できるように、" の文字コードを返すようにしている(value の箇所)
quotedChar = not_char('"') | string('""') >> value('"'[0])
#manyで文字コードの配列が結果的に返るため、packを使って文字列化
quotedCell = char('"') >> quotedChar.many.bind {|s| value(s.pack("c*"))} << char('"')
cell = quotedCell | regexp(/[^,\r\n]*/)
line = cell.separated(char ',')
csvFile = (line << eol).many

cs = $stdin.readlines.join
res = csvFile.parse cs

p res
puts res
実行結果
> jruby parse_csv.rb < test.csv
[["1", "\203e\203X\203g1", "\211\374\215s\r\n\212\334\202\335"], 
["2", "test2","\203J\203\223\203},\212\334\202\335"], 
["3", "\202\304\202\267\202\3063", "\203_\203u\203\213\203N\203H\201[\203e\201[\203V\203\207\203\223\"\212\334\202\335"]]
1
テスト1
改行
含み
2
test2
カンマ,含み
3
てすと3
ダブルクォーテーション"含み

一応、改行等が正しく処理されている事が確認できました。