だいぶ前に作ったapache.commons.csvとapache.poiを使ったCSVファイルからエクセル出力処理で、
java.io.IOException: (line 3) invalid char between encapsulated token and delimiter
が発生していました。原因は、ダブルクォーテーションで括っているデータ項目の中にダブルクォーテーションが混ざっていたからなのですが、データを出力する側で対処するのが出来そうにないので、CSVファイルを読み込む所で何とかならんものかと検討。
CSVファイルを読む箇所は下記のようにInputStreamが渡され、CSVRecordリストを取得して色々やるという内容です。
public static int parseCSV(InputStream input){
BufferedReader in = new BufferedReader(new InputStreamReader(input));
CSVParser parser = CSVFormat
.EXCEL
.withIgnoreEmptyLines(true)
.withIgnoreSurroundingSpaces(false)
.withRecordSeparator(System.getProperty("line.separator"))
.withDelimiter(',')
.withEscape('\\')
.withQuote('"')
.parse(in);
List<CSVRecord> recordList = parser.getRecords();
//以下、略
殆どの場合で問題は発生しないので、出来れば失敗した場合のみリトライ出来ればいいかなと、データ内に混ざりこんだダブルクォーテーションをエスケープするようにしてみます。正規表現での変換を3段階かますことで、一部ケース(データ内にカンマとダブルクォーテーションが連続した場合)は問題ですが、今回はまあこの位で。。
次に失敗した後のフォローとなるので、読込が進んでしまっているInputStreamを何とかしなければなりません。結局、IOUtilsでテンポラリファイルにコピーしてから読み込むようにしました。
public static int parseCSV(InputStream input){
// BufferedReader in = new BufferedReader(new InputStreamReader(input));
File fstream = File.createTempFile("csv",".txt");
FileOutputStream out = new FileOutputStream(fstream);
IOUtils.copy(input, out);
out.close();
List<CSVRecord> recordList;
try(BufferedInputStream bf = new BufferedInputStream(new FileInputStream(fstream.getAbsoluteFile()));
BufferedReader in = new BufferedReader(new InputStreamReader(bf));)
{
CSVParser parser = CSVFormat
.EXCEL
.withIgnoreEmptyLines(true)
.withIgnoreSurroundingSpaces(false)
.withRecordSeparator(System.getProperty("line.separator"))
.withDelimiter(',')
.withEscape('\\')
.withQuote('"')
.parse(in);
recordList = parser.getRecords();
}
catch(Exception e){
logger.error("retry parse tmpfile:" + fstream.getAbsoluteFile(),e );
BufferedInputStream bf = new BufferedInputStream(new FileInputStream(fstream.getAbsoluteFile()));
BufferedReader in = new BufferedReader(new InputStreamReader(bf,"UTF-8"));
StringBuilder sb = new StringBuilder();
String line;
while ((line = in.readLine()) != null) {
line.replaceAll("\\\\", "");
line = line.replaceAll("(?!(^|,|\",))\"(?!$|\")","\\\\\"");
line = line.replaceAll(",\\\\\"",",\"");
sb.append(line+System.getProperty("line.separator"));
}
CSVParser parser = CSVFormat
.EXCEL
.withIgnoreEmptyLines(true)
.withIgnoreSurroundingSpaces(false)
.withRecordSeparator(System.getProperty("line.separator"))
.withDelimiter(',')
.withEscape('\\')
.withQuote('"')
.parse(new StringReader(sb.toString()));
recordList = parser.getRecords();
bf.close();
in.close();
}
fstream.deleteOnExit();
//以下、略