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/
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 ダブルクォーテーション"含み
一応、改行等が正しく処理されている事が確認できました。