4

我有一个 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 库?还是我应该尝试所有可用的?还是我自己破解这个更好?

4

3 回答 3

6

正确的解决方案是找到生成数据的人,然后用键盘敲打他们,直到他们最终解决问题。

一旦你用尽了这条路线,你可以尝试市场上的其他一些 CSV 解析器,我过去曾成功使用过OpenCSV

即使 OpenCSV 不能解决开箱即用的问题,代码也很容易阅读,并且在 Apache 许可下可用,因此可以修改算法以处理不稳定的数据,并且可能比从头开始更容易刮。

于 2013-03-04T21:10:33.000 回答
1

在这里甚至我自己都感到惊讶,但我想我会自己破解它。我的意思是,您只需要阅读这些行并通过拆分引号/逗号来生成标记,无论您想要什么。这样您就可以按照适合您的方式调整逻辑。这不是很难。该文件似乎被破坏了,因此通过一些现有的解决方案似乎需要更多的工作。

不过有一点 - 如果 LibreOffice 已经正确解析它,你不能从那里保存文件,从而生成一个更合理的文件。但是,如果您认为 LibreOffice 可能在猜测,请自己编写标记器。

于 2013-03-04T21:15:44.310 回答
1

+1 为“水果蠕虫窒息”双关语 - 我读到这句话时差点被咖啡呛到:)

如果您真的无法修复该 CSV,那么您可以提供自己的 Tokenizer(超级 CSV 非常灵活!)。

您通常会编写自己的readColumns()实现,但扩展默认的 Tokenizer 并覆盖在readLine()字符串被标记化之前拦截字符串(并修复未转义的引号)的方法会更快。

我在这里做了一个假设,任何不在分隔符旁边或在行首/结尾处的引号都应该被转义。它远非完美,但它适用于您的样本输入。你可以随心所欲地实现它——早上太早了,我无法使用正则表达式:)

这样,您根本不必修改 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 的构造函数。

于 2013-03-04T23:17:40.027 回答