読者です 読者をやめる 読者になる 読者になる

No Programming, No Life

新しいNPNLです。http://d.hatena.ne.jp/fumokmm/ から引っ越してきました。

Re:RFC 4180対応版 CSVレコードの分解

お題: Server error
投稿: Server error

RFC 4180対応版*1CSVを分解するお題でした。

こんな感じになりました

def csv = '''\
"aaa","b
bb","ccc",zzz,"y""Y""y",xxx\
'''
resolveCSV(csv).eachWithIndex{ it, idx -> println "${idx+1} => ${it}" }

/** CSVレコードの分解(RFC 4180対応版) */
def resolveCSV(String csv) {
    csv.split(',').inject(['""']){ result, it ->
        // 一個前が「半開」ならそこに追加
        if (result[-1] ==~ /^".*[^"]$/ || result[-1].count('"') % 2 == 1) {
            result[-1] = [result[-1], it].join(',') // 「,」で結合
        // 一個前が「閉」なら新しく要素を追加
        } else {
            // 「"」で囲んでおく
            result << ((it =~ /"/) ? it : '""'.toList().join(it))
        }
        result
    }[1..-1]*.replaceAll(/^"|"$/, '')*.replaceAll(/"{2}/, '"')
}

動作確認はGroovy Version: 1.5.7 JVM: 1.6.0_10にて。

入力内容 (上記より)


"aaa","b
bb","ccc",zzz,"y""Y""y",xxx

出力結果


1 => aaa
2 => b
bb
3 => ccc
4 => zzz
5 => y"Y"y
6 => xxx

ポイント

csv.split(',').inject(['""']){ result, it ->

の部分でダミーで ['""']を入れておいて*2

}[1..-1]*.replaceAll(/^"|"$/, '')*.replaceAll(/"{2}/, '"')

[1..-1] で消し去っています。
その後は、要素に両端のダブルクォート(")を消し去って、更に二重ダブルクォートをダブルクォート(")に置換しています。

*1:要素にダブルクォート(") or カンマ(,)を含む場合は、ダブルクォート(")で囲んで、ダブルクォート自体(")は二重ダブルクォート("")にしてエスケープする。それ以外はダブルクォート(")で囲んでもいいし、囲まなくてもいいよっていう仕様ですね、Excelなんかが標準採用してるやつかな?

*2:一番最初の一個前判定で必ず成功させるために入れてます