[英]Is csv format regular grammar or context-free grammar?
我目前正在编写一个csv解析器。 csv格式的定义由ABNF定义的RFC4180给出。 因此,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.