[英]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.