我有一个包含引用问题的CSV文件:
"Albanese Confectionery","157137","ALBANESE BULK ASST. MINI WILD FRUIT WORMS 2" 4/5LB",9,90,0,0,0,.53,"21",50137,"3441851137","5 lb",1,4,4,$6.7,$6.7,$26.8
SuperCSV正在窒息这些水果虫(双关语)。我知道2"
应该是2""
,但事实并非如此。 LibreOffice实际上正确地解析了这个(让我感到惊讶)。我只想写我自己的小解析器,但其他行在字符串中有逗号:
"Albanese Confectionery","157230","ALBANESE BULK JET FIGHTERS,ASSORTED 4/5 B",9,90,0,0,0,.53,"21",50230,"3441851230","5 lb",1,4,4,$6.7,$6.7,$26.8
有没有人知道Java库会处理这样的疯狂事情?或者我应该尝试所有可用的?或者我最好自己解决这个问题?
答案 0 :(得分:6)
正确的解决方案是找到生成数据的人,然后使用键盘将其击败,直到他们解决问题为止。
一旦你用尽了这条路线,就可以尝试市场上的其他一些CSV解析器,过去我已经成功使用了OpenCSV。
即使OpenCSV无法解决开箱即用的问题,代码也很容易阅读,并且在Apache许可下可用,因此可以修改算法以处理您的奇怪数据,并且可能更容易而不是从头开始。
答案 1 :(得分:1)
甚至在这里感到惊讶,但我想我会自己破解它。我的意思是,你只需要读取行并通过分割引号/逗号生成标记,无论你想要什么。这样你就可以按照它的方式调整逻辑。这不是很难。该文件似乎被打破了,以便通过一些现有的解决方案似乎更多的工作。
有一点 - 如果LibreOffice已经正确解析它,你不能只是从那里保存文件,从而生成一个更合理的文件。但是,如果你认为LibreOffice可能在猜测,那就自己编写一个标记器。
答案 2 :(得分:1)
+1'令人窒息的水果虫'双关语 - 我的咖啡读数几乎呛到了:)
如果您确实无法修复该CSV,那么您可以提供自己的Tokenizer(Super CSV非常灵活!)。
您通常会编写自己的readColumns()
实现,但是在标记化之前,扩展默认Tokenizer并覆盖readLine()
方法以拦截字符串(并修复未转义的引号)会更快。< / p>
我在这里假设任何不在分隔符旁边或在行的开头/结尾的引号都应该被转义。它远非完美,但它适用于您的样本输入。你可以按照自己喜欢的方式实现这一点 - 我早上太早使用正则表达式:)
这样你根本不需要修改Super CSV(它只是插入),所以你也可以获得所有其他功能,如单元处理器和bean映射。
package org.supercsv;
import java.io.IOException;
import java.io.Reader;
import org.supercsv.io.Tokenizer;
import org.supercsv.prefs.CsvPreference;
public class FruitWormTokenizer extends Tokenizer {
public FruitWormTokenizer(Reader reader, CsvPreference preferences) {
super(reader, preferences);
}
@Override
protected String readLine() throws IOException {
final String line = super.readLine();
if (line == null) {
return null;
}
final char quote = (char) getPreferences().getQuoteChar();
final char delimiter = (char) getPreferences().getDelimiterChar();
// escape all quotes not next to a delimiter (or start/end of line)
final StringBuilder b = new StringBuilder(line);
for (int i = b.length() - 1; i >= 0; i--) {
if (quote == b.charAt(i)) {
final boolean validCharBefore = i - 1 < 0
|| b.charAt(i - 1) == delimiter;
final boolean validCharAfter = i + 1 == b.length()
|| b.charAt(i + 1) == delimiter;
if (!(validCharBefore || validCharAfter)) {
// escape that quote!
b.insert(i, quote);
}
}
}
return b.toString();
}
}
您可以将此Tokenizer提供给CsvReader的构造函数。