【问题标题】:Java CSV parser with unescaped quotes [closed]带有非转义引号的 Java CSV 解析器 [关闭]
【发布时间】:2013-02-19 01:45:05
【问题描述】:

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

【问题讨论】:

  • 我会说自己破解这个,一般来说,实际上不可能知道一个字符串什么时候应该结束,但至少不知道该行应该是什么样子。 LibreOffice 可能会正确解析此内容,但据您所知,它只是根据您的输入进行有根据的猜测。

标签: java csv supercsv


【解决方案1】:

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

如果您真的无法修复该 CSV,那么您可以提供自己的 Tokenizer(Super 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 的构造函数。

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      【解决方案3】:

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

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

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

      【讨论】:

      • +1。生成器需要使用标准 CSV 约定之一生成正确转义引号的输出:CSV 的某些变体使用"",而其他变体使用\"。要求人们只假设不带记录或行分隔符的双引号不被引用是不合理的,因为这会使您的 CSV 方言既模棱两可又不完整。
      • 我也尝试过 OpenCSV,但没有成功。所以我正在尝试一个正则表达式来找出流氓引号并将它们加倍。
      • @bowenl2 "(?![,$]) (也许 - 不记得 $ 在 [] 中是否特殊。我只是不使用正则表达式并将其写在代码中。)这也赢了'如果流氓引号后面跟着一个逗号,该逗号应该是字段内容的一部分。
      • @millimoose "(?
      • univocity-parsers 可以解析这个,只需使用“settings.setUnescapedQuoteHandling(STOP_AT_CLOSING_QUOTE)”
      猜你喜欢
      • 2013-01-10
      • 1970-01-01
      • 1970-01-01
      • 2012-05-25
      • 2019-08-23
      • 2013-03-09
      • 1970-01-01
      • 2019-02-18
      • 1970-01-01
      相关资源
      最近更新 更多