【问题标题】:CSV parsing for embedded double quotes嵌入双引号的 CSV 解析
【发布时间】:2010-12-18 20:22:33
【问题描述】:

我编写了一个简单的 CSV 文件解析器。但是在查看wiki page on CSV formats 之后,我注意到基本格式的一些“扩展”。通过双引号特别嵌入逗号。我已经设法解析了这些,但是还有第二个问题:嵌入式双引号。

例子:

12345,"ABC, ""IJK"" XYZ" -> [1234] 和 [ABC, "IJK" XYZ]

我似乎找不到正确的方法来区分封闭的双引号和无双引号。所以我的问题是解析上述 CVS 格式的正确方法/算法是什么?

【问题讨论】:

  • 看起来引号字符在您的 CSV 文件中由连续的两个引号字符表示。这类似于 SQL 数据库通常引用其单引号字符的方式,并且也用于其他上下文。您是关于这种引用方法的问题,还是您在问如何修改您的 code 来处理这个问题?我们可能需要查看您的一些代码。
  • 你能告诉我们一些你的代码吗?有很多方法可以解决这个问题,但我认为将它们全部展示出来是多余的。如果您可以向我们提供您当前方法的提示,我们可以指导我们的答案以适应您已经在做的事情。
  • @Zenikoder:我已经在下面的答案中进一步解释了。
  • 不要忘记嵌入的行尾字符。大多数人都会构建错误的解析器(甚至是现在流行的商业应用程序)。

标签: c++ algorithm parsing csv


【解决方案1】:

我通常对此的思考方式基本上是将引用的值视为单个未引用的值形成由引号连接的值的双引号值序列。也就是说,

  • 解析行中的下一个原子:
    • 读到第一个非空白字符
    • 如果当前字符不是引号:
      • 标记当前位置
      • 阅读到下一个逗号或换行符
      • 返回标记和逗号前字符之间的文本(如果合适,去掉空格)
    • 如果当前字符是引号:
      • 创建一个空字符串缓冲区
      • 而当前字符不是引号
        • 标记当前位置+1(跳过引号字符)
        • 阅读下一个报价
        • 如果缓冲区不为空,请为其添加引号
        • 将标记和当前位置之前的字符之间的文本附加到缓冲区(去除两个引号)
        • 前进一个字符(超过刚刚读到的引号)
      • 阅读到下一个逗号或换行符
      • 返回缓冲区

本质上,拆分带引号的字符串的每个双引号段,然后用引号将它们连接在一起。因此:"ABC, ""IJK"" XYZ" 变为 ABC, IJK XYZ,进而变为 ABC, "IJK" XYZ

【讨论】:

  • 有趣——我从来没有考虑过拆分和连接的问题。 +1
【解决方案2】:

我会使用单字符前瞻来执行此操作,因此如果您正在扫描字符串并找到双引号,请查看 next 字符以查看它是否也是双引号.如果是,则该对表示输出中的单个双引号字符。如果它是任何其他字符,则您正在查看引用字符串的末尾(希望下一个字符是逗号!)。在查看下一个字符时,请务必考虑行尾条件。

【讨论】:

    【解决方案3】:

    如果您找到双引号,那么您应该在单词/字符串的末尾查找双引号。如果找不到,则说明有错误。报价也一样。

    我建议您尝试使用 Flex/Bison 来为 CSV 文件编写解析器。这两个工具都将帮助您生成解析器,然后您可以将 C 文件与解析器一起使用并从您的 C++ 程序中调用它。 在 Flex 上,您创建一个可以找到您的标记的扫描仪,例如“word”或“”word””。在 Bison 上,您可以定义语法。

    【讨论】:

    • Flex 有点矫枉过正;野牛完全是矫枉过正。
    • @Zenikoder:网上有代码。也许你应该得到一份The Practice of Programming - 这是一本很棒的书! - 并下载包含 CSV 库的源代码。
    • 对不起,这是不正确的,除非内容没有逗号,因为您无法识别结尾是什么。
    【解决方案4】:

    双双引号 ("") 是文字双引号,而单双引号 (") 用于包围文本(包括逗号)。

    这是一个 csv 字段的正则表达式,如果这样可以让事情变得更容易:

    ([^",\n][^,\n]*)|"((?:[^"]|"")+)"
    

    如果字段不在引号中,第 1 组将包含字段,如果字段在引号中,则第 2 组将包含字段,减去周围的引号。在这种情况下,只需将所有 "" 实例替换为 "

    【讨论】:

    • @Zenikoder 否。认为 "" 的行为很像 \\ 在字符串文字中的行为。一个本身就是一个元字符,加倍它说“我只想要文字字符”。
    • 对于准确的 CSV,您建议的正则表达式是正确的 - 我很想确保它在一开始就被锚定,但这是一个小细节。您还可以获得格式错误的 CSV,其中您会获得一个“字段”,例如:"this part is OK"but this is bogus,。您可以忽略、拒绝或接受多余的字符。你不能做的就是把它们当作下一个领域。
    • @Jonathan Leffler:对。要使用它,您需要在匹配字符串开头的锚点和\s*[\n,]\s* 之间交替使用。如果后面的搜索失败,那么您的 CSV 中有一些垃圾文本。成功后,第 1 组会告诉您您是在新字段还是新行上。
    • 我在上一条评论中忘记了正则表达式中的括号。应该是:\s*([\n,])\s*
    【解决方案5】:

    我建议阅读:Stop Rolling Your Own CSV Parser 和这个CSV RFC。第一个实际上只是有人希望您使用他们的 C# CSV 解析器,但仍然解释了许多问题。

    您的解析器应该一次检查一个字符。我为我的parser in D 使用了双布尔策略。每个引号都会切换字符串是否被引用的天气。当在引用的单元格中时,您在点击引用时标记,并关闭引用。如果下一个字符是引号,则打开引号,将引号添加到结果中并关闭标志。如果下一个字符不是引号,则标志将关闭,引号也将关闭。

    【讨论】:

      猜你喜欢
      • 2016-07-02
      • 2011-12-12
      • 2018-05-13
      • 2014-02-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-05
      相关资源
      最近更新 更多