繁体   English   中英

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

[英]Is csv format regular grammar or context-free grammar?

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

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

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

(除非您使用的是.NET正则表达式引擎,该引擎提供对重复捕获组的单个捕获的访问权限,或者除非您事先知道CSV文件中的列数并将其硬编码到正则表达式中)。

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

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

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

一旦有了该行,就可以使用此正则表达式来匹配每个后续字段:

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

这里的比赛结果也包含分隔符( ,或换行),因此实际的字段的内容,必须从组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

由于CSV是一种非常宽松的格式,因此没有明确的答案。 在我观察到的CSV阅读器中,无上下文语法和常规语法都得到了维护。 例如,如果在封闭值的结尾之后出现逗号以外的内容,则某些读者会抛出异常。

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

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

  • 字段之间用分隔

  • 空格在任何方面都没有特殊之处,除了用单引号引起来的分隔记录的换行符

  • 其中包括字段“,或换行字符必须被引用;任何字段可以被引用。

  • 一个在引用一个字段被表示为两个字符。


%%
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); }

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

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

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

要正式证明文件格式不是常规语言,可以对常规语言使用抽水引理

考虑一个由2行和p列组成的字符串(其中p是来自抽运引理的抽运长度),其中每个像元都是空的(因此,如果p = 3,则为“ ,, \\ n,\\ n”。要满足| xy | <= p和| y |> 1的条件,则“ y”必须在文件的第一行中是1个或多个逗号。如果您随后“抽取” y,则将有更多第一行中的单元格,然后第二行中的单元格,因此,这不是常规语言。

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM