带有未转义引号的Java CSV解析器

时间:2013-03-04 20:32:24

标签: java csv supercsv

我有一个包含引用问题的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库会处理这样的疯狂事情?或者我应该尝试所有可用的?或者我最好自己解决这个问题?

3 个答案:

答案 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的构造函数。