Java:CSVパーサを作る(その3) - RFC4180対応 後編
←CSVパーサを作る(その2) - RFC4180対応 前編
前回の記事で未実装だった「レコードの確定」と「レコードのフィールドへの分割」を行うメソッドについて実装を行い,CSVパーサを完成させます。
【実装の考察】
●レコードの確定
レコード確定では,入力テキストデータに対して,ダブルクォーテーション(二重引用符)のペアをヒントに各レコードの末尾を確定して,レコードの切り分けを行います。処理手順は以下のようになります。
- BufferedReaderのreadLineメソッドを使ってテキストを1行分(現在の位置から改行が現れるまで,またはファイルの終了まで)取り出して,行の先頭からダブルクォーテーションを探す。見つからなければその1行を1レコードとして確定する。(readLineメソッドは「CR」「LF」「CRLF」を改行と認識するので,「CRLF以外の改行も考慮する」仕様の要求を満たしています。)
- ダブルクォーテーションが見つかった場合,ペアになる後ろのダブルクォーテーションを探す。後ろのダブルクォーテーションが見つかったらその位置から後続のダブルクォーテーションのペアを探す。この手順を行の終わりまで繰り返す。ダブルクォーテーションペアの外側で行が終了していれば,その行を1レコードとして確定する。
- ペアの後ろのダブルクォーテーションが見つからずにダブルクォーテーションペアの内側で改行に達したら,その改行を文字列フィールドに含まれる改行と見なしてBufferedReaderのreadLineメソッドより次の行を取り出して前の行と連結し,ペアの後ろのダブルクォーテーションを探すところから処理から継続する。これをダブルクォーテーションペアの外側で改行が見つかるまで繰り返す。後ろのダブルクォーテーションが見つからずにファイルの末尾に達したときは,ファイルの末尾にダブルクォーテーションを付加して行の末尾とする。この連結した行を1レコードとして確定する。
//------------------------------------------------------------------ /** * BufferedReaderから1レコード分のテキストを取り出す。 * @param reader 行データを取り出すBufferedReader。 * @return 1レコード分のテキスト。 * @throws IOException 入出力エラー */ private String buildRecord ( BufferedReader reader) throws IOException { String result = reader.readLine(); int pos; if (result != null && 0 < result.length() && 0 <= (pos = result.indexOf("\""))) { boolean inString = true; String rawline = result; String newline = null; StringBuffer buff = new StringBuffer(1024); while (true) { while (0 <= (pos = rawline.indexOf("\"", ++pos))) { inString = !inString; } if (inString && (newline = reader.readLine()) != null) { buff.append(rawline); buff.append("\n"); pos = -1; rawline = newline; continue; } else { if (inString || 0 < buff.length()) { buff.append(rawline); if (inString) { buff.append("\""); } result = buff.toString(); } break; } } } return result; } |
使用すべき改行コードの選択は,プログラムが稼動するプラットフォームの改行コードを取得する
「String returnStr = System.getProperty("line.separator");」
が知られていますが,実行環境によってはCSVパーサの出力結果を受け取るプログラムがCSVパーサとは異なるOSで稼動する場合もありうるので,上記コードで取得できる改行コードを常に適用できるとは限りません。
そのため,実コードでは使用する改行コードをプロパティファイルで設定できるようにするなど動作環境に合わせて変更できる仕組みを作る必要があります。
●レコードのフィールドへの分割
フィールド分割では,レコードに切り分けたテキストに対して,最初にレコード全体をカンマで分割し,分割した個々の文字列にダブルクォーテーションをヒントに必要な連結やエスケープ処理を行って,個々のフィールドを確定します。処理手順は以下のようになります。
- レコード全体をStringクラスのsplitメソッドを使ってカンマで分割し,分割した個々の文字列データを順に先頭からダブルクォーテーションを探す。見つからなければその文字列は1フィールドとして確定する。
- ダブルクォーテーションが見つかったら,次のダブルクォーテーションを探す。次のダブルクォーテーションの直後にダブルクォーテーションがあれば,エスケープされたダブルクォーテーションとして処理し,そうでなければフィールドの終わりと見なす。
- フィールドで後ろのダブルクォーテーションが見つからない場合,フィールドに含まれるカンマでsplitメソッドが分割したものと見なして,フィールドの後ろに(splitメソッドが削除した)カンマと次のフィールドを連結する。
- フィールドの開始と終了のダブルクォーテーションは削除する。
//------------------------------------------------------------------ /** * 1レコード分のテキストを分割してフィールドの配列にする。 * @param src 1レコード分のテキストデータ。 * @param dest フィールドの配列の出力先。 */ private void splitRecord ( String src, LinkedList dest) { String[] columns = src.split(","); int maxlen = columns.length; int startPos, endPos, columnlen; StringBuffer buff = new StringBuffer(1024); String column; boolean isInString, isEscaped; for (int index = 0; index < maxlen; index++) { |
以上で「RFC4180対応のCSVパーサ」はひととおりできあがったかと思います。
※上記コードでは,整形のため全角スペースを使用している部分があります。
【著作権表記】上記コードを含む本ブログのプログラムコードは,私的利用可,商用利用可,改変しての利用可です。利用の際に作者に許諾を得る必要はありません。
■関連書籍をAmazonで検索:[Java]
●ソースコードリーディングから学ぶ Javaの設計と実装
←この記事が役に立ったという方はクリックお願いします。
▼CSVパーサを作る[その1][その2][その3]
| 固定リンク
「プログラミング」カテゴリの記事
- シェルスクリプト:「.svn」ディレクトリを一括削除する(2015.03.23)
- JavaScript:excanvasを使ってWebページに画像を表示する(2012.05.07)
- C言語:TRUEとFALSEの値(2012.05.06)
- Java:CSVパーサを作る(その3) - RFC4180対応 後編(2008.06.13)
- Java:CSVパーサを作る(その2) - RFC4180対応 前編(2008.06.12)
「Java」カテゴリの記事
- Java:Reader/Writerにおけるclose()メソッド呼び出しの流儀(2015.06.29)
- Java:例外が起こったときに実行されるコードとされないコード(2015.03.31)
- Java:前回作成したコードの処理速度を比較する(2015.03.15)
- Java:数値を3桁ごとのカンマ区切りの文字列にする(2015.03.02)
- Java:全角の数値文字列を数値として受け付ける(2015.01.19)