csv格式是常规语法还是无上下文语法?

时间:2014-07-04 05:10:41

标签: regex parsing csv context-free-grammar

我目前正在编写一个csv解析器。 csv格式的定义由RFC4180给出,它由ABNF定义。所以csv的定义绝对是一个无上下文的语法。但是,我想知道csv是否是常规语法?这样我就可以用一个有限状态机来解析它。此外,如果它只是一个常规语法并且可以通过有限状态机进行解析,这是否意味着它也可以通过正则表达式进行解析?

4 个答案:

答案 0 :(得分:1)

我没有任何正式的理论来验证这一点,但我非常确定可以使用正则表达式可靠地解析CSV文件。不过,最好使用两个正则表达式:

  • 匹配整个CSV行的一个正则表达式(包括引用字段中的换行符)
  • 另一个正则表达式(用于第一个匹配结果)以匹配单个字段

(除非您正在使用.NET正则表达式引擎,该引擎可以访问重复捕获组的各个捕获,或者除非您事先知道CSV文件中的列数并将其硬编码到正则表达式中)

匹配整个CSV行的PCRE正则表达式可以是:

/^(?:(?:[^",\r\n]*|"(?:""|[^"]*)*+")(?:,|$))*+(?=$)/m

您需要在此处使用/m修饰符,以允许^$匹配换行符。如果您逐行处理文件,那么正则表达式将在不是完整CSV行的行上失败(即,引用字段尚未关闭的位置),所以你会需要阅读下一行,将其添加到测试字符串并重新应用正则表达式(您可以在此方案中删除/m修饰符)。重复直到匹配。

获得该行后,您可以使用此正则表达式匹配每个连续的字段:

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

此处的匹配结果还包含分隔符(,或换行符),因此必须从组1中提取实际字段的内容。您还需要在第1组之后处理周围和嵌入的引号。匹配。

<强>解释

^             # Start of line (/m modifier!)
(?:           # Start of non-capturing group (to contain the entire line):
 (?:          # Start of non-capturing group (to contain a single field):
  [^",\r\n]*  # Either match a run of character except quotes, commas or newlines
 |            # or
  "           # Match a quoted field, starting with a quote, followed by
  (?:         # either...
   ""         # an escaped quote
  |           # or
   [^"]*      # anything that's not a quote
  )*+         # repeated as often as possible, no backtracking allowed
  "           # Then match a closing quote
 )            # End of group (=field)
 (?:,|$)      # Match a delimiter or the end of the line
)*+           # repeated as often as possible, no backtracking allowed
(?=$)         # Assert that we're now at the end of a line

答案 1 :(得分:1)

这个问题没有明确的答案,因为CSV是一种非常宽松的格式。在我观察到的CSV读者中,维护了无上下文和常规语法。例如,如果在封闭值结束后跟随逗号,则某些读者会抛出异常。

答案 2 :(得分:1)

您应该能够使用简单的有限状态机解析CSV文件。或者,更准确地说,根据精确的CSV格式,使用大量简单FSM中的一个。 (这并不意味着它是一个好主意。有一些CSV解析库可以更好地处理您可能在野外发现的所有奇怪的变体和未经编写的CSV文件规则。)

以下是一些(未经测试的)flex规则,对最简单的CSV变体没有良好的错误处理:

  • 字段用分隔,

  • 除了分隔记录的不带引号的换行符外,空格没有任何特殊之处

  • 必须引用包含&#34; 或换行符的字段;任何字段都可以引用。

  • 引用字段中的&#34; 表示为两个&#34; 字符。


%%
int record = 1;
int field = 1;

[^",\n]*/[^"]   { printf("Record %d Field %d: |%s|\n", record, field, yytext); }
[,]             { ++field; }
[\n]            { ++line; field = 1; }
["]([^"]|["]["]*)["]/[,\n] {
                  printf("Record %d Field %d: |%s|\n", record, field, yytext); }
.               { printf("Something bad happened in record %d field %d\n",
                          record, field); }

它没有正确处理引用的字符串(即,它不会删除引号或加倍的双引号)。

处理引用字段的最简单方法是使用启动条件(仍然作为FSM的一部分实现):

%x QUOTED

%%
int record = 1;
int field = 1;

[^",\n]*/[^"]     { printf("Record %d Field %d: |%s|\n", record, field, yytext); }
[,]               { ++field; }
[\n]              { ++line; field = 1; }

["]               { printf("Record %d Field %d: |", record, field); BEGIN(QUOTED); }
<QUOTED>[^"]*     { printf("%s", yytext); }
<QUOTED>["]["]    { putchar('"'); }
<QUOTED>["]/[,\n] { putchar('|'); putchar('\n'); BEGIN(INITIAL); }

<*>.              { printf("Something bad happened in record %d field %d\n",
                           record, field); }

答案 3 :(得分:1)

因此,基于理论的答案为否,CSV文件格式不是常规语言(基于该RFC)。

不是它的主要原因是基于规范中的这一行:

  

每一行在整个文件中应包含相同数量的字段。

要正式证明文件格式不是常规语言,可以使用pumping lemma for regular languages

请考虑由两行和p列组成的字符串(其中p是抽水引理的抽水长度),其中每个单元格都为空(因此,如果p = 3,则为“ ,, \ n,\ n” 。为了满足| xy | <= p和| y |> 1的条件,则“ y”在文件的第一行中必须是1个或多个逗号。第一行上的单元格要多于第二行。因此,这不是常规语言。

但是,通常情况下,理论答案可能并不是您真正需要的。首先,许多编程语言中的许多正则表达式语法(和有限状态机语法)实际上不仅仅支持真正的正则语言。

此外,仅因为您无法使用真正的正则表达式验证字符串是否确实符合CSV规范,并不意味着您仍然无法使用一个字符串来解析它。您可能只接受格式稍有错误的CSV文件(例如行长不均匀的CSV文件)。