2014-07-04 53 views
1

我目前正在編寫一個csv解析器。 csv格式的定義由由ABNF定義的RFC4180給出。所以csv的定義絕對是一個無競爭的語法。但是,我想知道csv是否是正則語法?這樣我就可以用一個有限狀態機來解析它。此外,如果它恰好是一個正則語法,並且可以用有限狀態機進行分析,那麼這是否意味着它也可以通過正則表達式進行分析?csv格式是正則語法還是上下文無關語法?

+0

NIT採摘澄清你的條件的位:「如果CSV是正規文法」 :csv文件是語言的一個語句(字母表中所有可能的字符串的子集)。無論您是使用解析器還是可以脫離FSA,都取決於您爲此語言編寫的語法。如果你寫的語法是規則的,那麼你對FSA很好(這是最快的方法),如果你的語法沒有上下文,你需要一個解析器。順便說一下,R​​Es只是一種編寫常規語法的形式:您可以使用常規生產系統來描述RG,但是您必須驗證應用FSM的規則性。 – user1666959

+0

@ user1666959謝謝。現在我明白了「這取決於你寫什麼語法」。然而,我認爲在這種情況下我想知道的是「什麼是最低的文法可以用來解析csv」。現在我認爲這個「最低語法」是正規語法。 – n3v3rm03

回答

1

我沒有任何正式的理論可以驗證這一點,但我很確定CSV文件可以可靠地使用正則表達式進行分析。這可能是最好使用兩個正則表達式,但:

  • 一個正則表達式匹配整個CSV行(包括引號的字段換行符)
  • 另一個正則表達式(要在第一個比賽結果使用)配單場

(除非你使用.NET正則表達式引擎可用於訪問重複捕獲組的單獨捕獲,或者除非你知道你的CSV文件中的列數和事先硬代碼到你的正則表達式)。

一個PCRE正則表達式匹配整個CSV行可能是:

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

這裏需要使用/m修改,以允許^$匹配換行符。如果你正在逐行處理文件,那麼正則表達式在一個不是完整的CSV行的行上將會失敗(例如,引用的字段尚未關閉),所以你需要讀取下一行,add將其添加到您的測試字符串並重新應用正則表達式(您可以在此方案中刪除/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 
+1

語言確實很規律。但我真的不建議用正則表達式解析它。簡單的解析器通常更快,更易於調試。 – Joey

+1

關於'(?= $)':'$'已經是一個零寬度的斷言,所以你不需要通過將它貼在一個lookahead中來「斷言」它。 –

1

沒有明確回答這個問題,因爲CSV是一個非常鬆散的格式。我觀察到的CSV閱讀器中都保留了上下文無關和常規語法。例如,一些讀者在封閉值結束後會拋出一個異常,除非逗號出現。

+0

那麼,有各種各樣的CSV的變化,但有一個真正的RFC:http://tools.ietf.org/html/rfc4180 –

+0

@Bart - 該RFC是一個微妙的嘗試來記錄的格式,大部分它基於假設。 –

+0

是的,我現在剛注意到文檔中的*「它沒有指定任何類型的Internet標準」*。 –

0

您應該可以使用簡單的有限狀態機來解析CSV文件。或者,更確切地說,根據精確的CSV格式,使用大量簡單的FSM中的一種。 (這並不意味着這是一個好主意,還有一些CSV解析庫,它可以更好地處理所有奇怪的變體以及您可能會發現的不成文的CSV文件規則。)

這裏有沒有一些(未經測試)柔性規則良好的錯誤處理的最簡單的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); } 

不能處理正常引用的字符串(即,它不剝去引號或undouble加倍引號)。

處理引述領域的最簡單的方法是使用一個啓動條件(這仍然是作爲一個有限狀態機的部分實現):

%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); } 
相關問題