簡體   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