RFC 4180対応版 CSVレコードの分解
Posted feedbacks - Java
無理矢理、正規表現でやってみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import java.util.regex.*;
import java.util.*;
public class Sample {
private static final Pattern spliter =
Pattern.compile("((\"[^\"]*+\")+|[^,]*+),?");
private static final Pattern doubleQuote = Pattern.compile("\"\"");
public static String[] splitCSV(String rec) {
Matcher m = spliter.matcher(rec);
ArrayList<String> cols = new ArrayList<String>();
while (m.find()) {
String col = m.group(1);
if (col != null) {
if (col.startsWith("\"")) {
col = col.substring(1, col.length() - 1);
}
col = doubleQuote.matcher(col).replaceAll("\"");
cols.add(col);
}
if (m.end() >= rec.length())
break;
}
return cols.toArray(new String[cols.size()]);
}
public static void main(String[] args) throws Exception {
String sample = "\"aaa\",\"b\nbb\",\"ccc\",zzz,\"y\"\"Y\"\"y\",xxx";
String[] cols = splitCSV(sample);
for (int i = 0; i < cols.length; i++) {
System.out.printf("%d => %s%n", i + 1, cols[i]);
}
}
}
|
Javaらしく(?)Readerクラスで処理してみました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 | import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
public class Answer33 {
public static void main(String[] args) {
String str = "\"aaa\",\"b\nbb\",\"ccc\",zzz,\"y\"\"Y\"\"y\",xxx";
CSVDataReader reader = new CSVDataReader(new StringReader(str));
try {
while (true) {
int cell = reader.getCellNumber();
String s = reader.readCell();
if (s == null) break;
System.out.println(cell + " => " + s);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
class CSVDataReader extends BufferedReader {
private int cellCount_;
public CSVDataReader(Reader reader) {
super(reader);
cellCount_ = 1;
}
public int getCellNumber() {
return cellCount_;
}
public String readCell() throws IOException {
int c = read();
if (c < 0) return null;
cellCount_++;
StringBuilder builder = new StringBuilder();
boolean quote = (c == '"');
if (!quote) {
if (c == '\r' || c == '\n') return "";
builder.append((char) c);
}
OUTER: while ((c = read()) >= 0) {
if (quote) {
INNER: switch (c) {
case '"':
int next = read();
switch (next) {
case '"':
builder.append('"');
break INNER;
case ',':
break OUTER;
default:
throw new IllegalStateException();
}
default:
builder.append((char) c);
}
} else {
switch (c) {
case ',':
case '\r':
case '\n':
break OUTER;
case '"':
throw new IllegalStateException();
default:
builder.append((char) c);
}
}
}
return builder.toString();
}
}
|
オブジェクト指向らしく(?)STATEパターン的なものを作りました。 CallBackにはレコード/CSVの終了がプッシュされてきますがこのお題では使ってません。 改行コードはReaderが全て\nに変換してくれるので、どのタイプでも大丈夫なはずです。 個人的にはappend(), collectToken(), endOfParse() あたりをもう少しなんとか…とか 各Handler#handle()がどうも似てるような気がしてどうにかならないものかと…。 実行例: $java CSVParser 1 => aaa 2 => b bb 3 => ccc 4 => zzz 5 => y"Y"y 6 => xxx 7 => eee,EEE 8 => 9 => 10 => 11 => 12 => DDD 13 => 14 => fff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 | import java.io.IOException;
import java.io.LineNumberReader;
import java.io.Reader;
import java.io.StringReader;
public class CSVParser {
public static void main(String[] args) throws IOException {
CSVParser parser = new CSVParser(new CallBack() {
private int num = 0;
@Override
public void setToken(String token) {
System.out.println((++num)+" => "+token);
}
@Override
public void endRecord() {}
@Override
public void endCsv() {}
});
parser.parse(new StringReader("\"aaa\",\"b\nbb\",\"ccc\",zzz,\"y\"\"Y\"\"y\",xxx,\"eee,EEE\",,,\n,DDD,\"\",fff"));
}
public interface CallBack {
/**
* CSVのトークンを切り出した後に、格納するためにパーサから呼び出される。
* @param token CSVトークン
*/
void setToken(String token);
/**
* CSVレコードの最後のトークンの setToken() を呼び出した後にパーサから呼び出される。
*/
void endRecord();
/**
* 最後のCSVレコードのendRecord() を呼び出した後にパーサから呼び出される。
*/
void endCsv();
}
/**
* CSVParserが処理中に発生した例外を示す実行時例外クラス
* <p>処理中だったCSVファイルの行番号を格納する。
*/
public class ParseException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int linenumber;
ParseException(int linenumber) {
super();
this.linenumber = linenumber;
}
ParseException(String message, int linenumber) {
super(message);
this.linenumber = linenumber;
}
ParseException(String message, int linenumber, Throwable cause) {
super(message, cause);
this.linenumber = linenumber;
}
ParseException(int linenumber, Throwable cause) {
super(cause);
this.linenumber = linenumber;
}
/**
* CSVファイルの行番号を返す
* @return 行番号(1~)
*/
public int getLineNumber() { return linenumber; }
/**
* 詳細メッセージを返します。
* @return 詳細メッセージ(無い場合はnull)
*/
@Override
public String getMessage() {
return super.getMessage()+" [line="+linenumber+"]";
}
}
private LineNumberReader lnreader;
private CallBack callback;
private StringBuffer tokenBuffer = new StringBuffer();
public CSVParser(CallBack callback) {
if(callback == null) throw new NullPointerException();
this.callback = callback;
}
public void parse(Reader reader) throws IOException {
lnreader = new LineNumberReader(reader);
for(Handler h=firstRecordCharHandler; h!=null; h=h.handle(lnreader.read()));
}
protected boolean isComment(int c) { return c == '#'; }
protected boolean isQuot(int c) { return c == '"'; }
protected boolean isEndOfToken(int c) { return c == ',' || isEndOfRecord(c); }
protected boolean isEndOfRecord(int c) { return c == '\n' || isEndOfCsv(c); }
protected boolean isEndOfCsv(int c) { return c == -1; }
protected Handler append(int c, Handler next) {
tokenBuffer.append((char)c);
return next;
}
protected Handler collectToken(int c) {
callback.setToken(tokenBuffer.toString());
tokenBuffer.setLength(0);
if(isEndOfRecord(c)) {
callback.endRecord();
if(isEndOfCsv(c)) return endOfParse();
return firstRecordCharHandler;
}
return firstTokenCharHandler;
}
protected Handler endOfParse() {
callback.endCsv();
return null;
}
private abstract class Handler {
abstract Handler handle(int c);
}
private final Handler firstRecordCharHandler = new FirstRecordCharHandler();
private final Handler skipLineHandler = new SkipLineHandler();
private final Handler firstTokenCharHandler = new FirstTokenCharHandler();
private final Handler quottingTokenHandler = new QuottingTokenHandler();
private final Handler quotInQuotCharHandler = new QuotInQuotCharHandler();
private final Handler unquottingTokenHandler = new UnquottingTokenHandler();
/**
* CSVのレコード(行)の最初の文字を処理するハンドラクラス
* <p>
* '"'で始まる場合はコメント行として改行までスキップし、
* それ以外の(有効)文字はCSVトークンの始まりとして処理し、
* EOFなら解析を終了する。
*
* @see CSVParser#isComment(int)
* @see CSVParser#isEndOfCsv(int)
* @see CSVParser#endOfParse(int)
*/
private class FirstRecordCharHandler extends Handler {
@Override
public Handler handle(int c) {
if(isComment(c)) return skipLineHandler;
if(isEndOfCsv(c)) return endOfParse();
return firstTokenCharHandler.handle(c);
}
}
/**
* CSVの一行を読み飛ばすハンドラクラス
* <p>
* EOFなら解析を終了し、改行なら firstRecordCharHandler に以降の処理を任せる。
*/
private class SkipLineHandler extends Handler {
@Override
public Handler handle(int c) {
if(isEndOfCsv(c)) return endOfParse();
if(isEndOfRecord(c)) return firstRecordCharHandler;
return this;
}
}
/**
* CSVトークンの最初の文字を処理するハンドラクラス
* <p>
* '"'なら次の'"'までをトークンとして扱い、それ以外の(有効)文字なら
* トークン終了文字(','、改行、EOF)までをトークンとして扱うようにする。
*
* @see CSVParser#isQuot(int)
* @see CSVParser.QuottingTokenHandler
* @see CSVParser.UnquottingTokenHandler
*/
private class FirstTokenCharHandler extends Handler {
@Override
public Handler handle(int c) {
if(isQuot(c)) return quottingTokenHandler;
return unquottingTokenHandler.handle(c);
}
}
/**
* '"'で囲まれたCSVトークンを取り出すハンドラクラス
* <p>
* '"'が現れるまで文字格納処理を行い、現れたらエスケープ判定を行うハンドラクラスに委譲する。
*
* @throws ParseException 終了を示す'"'が現れる前にEOFになった場合
*
* @see CSVParser#isQuot(int)
* @see CSVParser#isEndOfCsv(int)
* @see CSVParser.QuotInQuotCharHandler
*/
private class QuottingTokenHandler extends Handler {
@Override
public Handler handle(int c) {
if(isQuot(c)) return quotInQuotCharHandler;
if(isEndOfCsv(c)) throw new ParseException("quot error.", lnreader.getLineNumber()+1);
return append(c, this);
}
}
/**
* '"'で始まったCSVトークン内で'"'が表れた場合のハンドラクラス
* <p>
* 続いて'"'が来ればエスケープされた'"'と見なして文字格納処理を行い、
* トークン終了文字が来ればトークン終了と見なしてトークン格納処理を行う。
*
* @throws ParseException '"'もしくはトークン終了文字以外が表れた場合
*
* @see CSVParser#isQuot(int)
* @see CSVParser#isEndOfToken(int)
* @see CSVParser.QuottingTokenHandler
*/
private class QuotInQuotCharHandler extends Handler {
@Override
public Handler handle(int c) {
if(isEndOfToken(c)) return collectToken(c);
if(isQuot(c)) return append(c, quottingTokenHandler);
throw new ParseException("quot error.", lnreader.getLineNumber()+1);
}
}
/**
* '"'で囲まれていないCSVトークンを取り出すハンドラクラス
* <p>
* トークン終了文字が表れるまで文字格納処理を行い、表れたらトークン格納処理を行う。
*
* @throws ParseException '"'が表れた場合
*
* @see CSVParser#isQuot(int)
* @see CSVParser#isEndOfToken(int)
* @see CSVParser#collectToken(int)
*/
private class UnquottingTokenHandler extends Handler {
@Override
public Handler handle(int c) {
if(isEndOfToken(c)) return collectToken(c);
if(isQuot(c)) throw new ParseException("quot error.", lnreader.getLineNumber()+1);
return append(c, this);
}
}
}
|


raynstard
#3389()
Rating1/1=1.00
[ reply ]