在Python中使用SimpleParse模組進行解析的教程

來源:互聯網
上載者:User
與大多數程式員一樣,我經常需要標識存在於文字文件中的組件和結構,這些文檔包括:記錄檔、設定檔、分隔的資料以及格式更自由的(但還是半結構化的)報表格式。所有這些文檔都擁有它們自己的“小語言”,用於規定什麼能夠出現在文檔內。

我編寫處理這些非正式解析任務的程式的方法總是有點象大雜燴,其中包括定製狀態機器、Regex以及上下文驅動的字串測試。這些程式中的模式大概總是這樣:“讀一些文本,弄清是否可以用它來做些什麼,然後可能再多讀一些文本,一直嘗試下去。”

各種形式的解析器將文檔中組件和結構的描述提煉成簡明、清晰和 說明性的規則,該規則規定了如何標識文檔的組成部分。這裡,說明性方面是最令人信服的。我所有的舊的特別的解析器都採用了這種風格:讀一些字元、作決定、累加一些變數、清空、重複。正如本專欄關於函數型編程的部分文章中所評述的,程式流的方法風格相對來說容易出錯並且難以維護。

正式解析器幾乎總是使用擴充巴科斯範式(Extended Backus-Naur Form(EBNF))上的變體來描述它們所描述語言的“文法”。我們在這裡研究的工具是這樣做的,流行的編譯器開發工具 YACC(及其變體)也是這樣做的。基本上,EBNF 文法對您可能在文檔中找到的 組件賦予名稱;另外,經常將較小的組件組成較大的組件。由運算子 ― 通常和您在Regex中看到的符號相同 ― 來指定小組件在較大的組件中出現的頻率和順序。在解析器交談(parser-talk)中,文法中每個命名的組件稱為一個“產品(production)”。

可能讀者甚至還不知道 EBNF,卻已經看到過啟動並執行 EBNF 描述了。例如,大家熟悉的 Python 語言參考大全(Python Language Reference)定義了浮點數在 Python 中是什麼樣子:
EBNF 樣式的浮點數描述

floatnumber: pointfloat | exponentfloat
pointfloat: [intpart] fraction | intpart "."
exponentfloat: (nonzerodigit digit* | pointfloat) exponent
intpart: nonzerodigit digit* | "0"
fraction: "." digit+
exponent: ("e"|"E") ["+"|"-"] digit+

或者您可能見過以 EBNF 樣式定義的 XML DTD 元素。例如,developerWorks 教程的 類似於:
developerWorks DTD 中 EBNF 樣式的描述

代碼如下:

拼字稍有不同,但是量化、交替和定序這些一般概念都存在於所有 EBNF 樣式的語言文法中。
使用 SimpleParse 構建標記列表

SimpleParse 是一個有趣的工具。要使用這個模組,您需要底層模組 mxTextTools ,它用 C 實現了一個“標記引擎”。 mxTextTools (請參閱本文後面的 參考資料)的功能強大,但是相當難用。一旦在 mxTextTools 上放置了 SimpleParse 後,工作就簡單多了。

使用 SimpleParse 確實很簡單,因為不需要考慮 mxTextTools 的大部分複雜性。首先,應該建立一種 EBNF 樣式的文法,用來描述要處理的語言。第二步是調用 mxTextTools 來建立一個 標記列表,當文法應用於文檔時,該列表描述所有成功的產品。最後,使用 mxTextTools 返回的標記列表來進行實際操作。

對於本文,我們要解析的“語言”是“智能 ASCII”所使用的一組標記代碼,這些代碼用來表示諸如黑體、模組名以及書籍標題之類的內容。這就是先前使用 mxTextTools 來標識的同一種語言,在先前的部分中,使用Regex和狀態機器。該語言比完整的程式設計語言簡單得多,但已經足夠複雜而有代表性。

這裡,我們可能需要回顧一下。 mxTextTools 提供給我們的“標記列表”是什麼東西?這基本上是一個嵌套結構,它只是給出了每個產品在源文本中匹配的字元位移量。 mxTextTools 快速遍曆源文本,但是它不對源文本本身 做任何操作(至少當使用 SimpleParse 文法時不進行任何操作)。讓我們研究一個簡化的標記列表:
從 SimpleParse 文法產生的標記列表

(1, [('plain',  0,  15,  [('word', 0, 4, [('alphanums', 0, 4, [])]),  ('whitespace', 4, 5, []),  ('word', 5, 10, [('alphanums', 5, 10, [])]),  ('whitespace', 10, 11, []),  ('word', 11, 14, [('alphanums', 11, 14, [])]),  ('whitespace', 14, 15, [])]), ('markup',  15,  27, ... 289)

中間的省略符號表示了一批更多的匹配。但是我們看到的部分敘述了下列內容。根產品(“para”)取得成功並結束於位移量 289 處(源文本的長度)。子產品“plain”的位移量為 0 到 15。“plain”子產品本身由更小的產品組成。在“plain”產品之後,“markup”產品的位移量為 15 到 27。這裡省略了詳細資料,但是第一個“markup”由組件組成,並且源文本中稍後還有另外的產品取得成功。

“智能 ASCII”的 EBNF 樣式的文法

我們已經瀏覽了 SimpleParse + mxTextTools 所能提供的標記列表。但是我們確實需要研究用來產生這個標記列表的文法。實際工作在文法中發生。EBNF 文法讀起來幾乎不需加以說明(儘管 確實需要一點思考和測試來設計一個文法):
typographify.def

para      := (plain / markup)+plain     := (word / whitespace / punctuation)+whitespace   := [ \t\r\n]+alphanums   := [a-zA-Z0-9]+word      := alphanums, (wordpunct, alphanums)*, contraction?wordpunct   := [-_]contraction  := "'", ('am'/'clock'/'d'/'ll'/'m'/'re'/'s'/'t'/'ve')markup     := emph / strong / module / code / titleemph      := '-', plain, '-'strong     := '*', plain, '*'module     := '[', plain, ']'code      := "'", plain, "'"title     := '_', plain, '_'punctuation  := (safepunct / mdash)mdash     := '--'safepunct   := [!@#$%^&()+=|\{}:;<>,.?/"]

這種文法和您口頭描述“智能 ASCII”的方式幾乎完全相同,非常清晰。段落由一些純文字和一些標記文本組成。純文字由某些字、空白和標點符號的集合組成。標記文本可能是強調文本、著重強調文本或模組名等等。著重強調文本由星號環繞。標記文本就是由諸如此類的部分組成的。需要考慮的是幾個特性,類似於到底什麼是“字”,或者可以用什麼符號結束縮寫,但是 EBNF 的句法不會成為障礙。

相比之下,使用Regex可以更精練地描述同類規則。“智能 ASCII”標記程式的第一個版本就是這樣做的。但是編寫這種精練難度大得多,並且以後調整也更為困難。下列代碼錶示了很大程度上(但不精確地)相同的規則集:
智能 ASCII 的 Python regexs

# [module] names    re_mods =      r""'([\(\s'/">]|^)\[(.*?)\]([<\s\.\),:;'"?!/-])"""# *strongly emphasize* words    re_strong =     r""'([\(\s'/"]|^)\*(.*?)\*([\s\.\),:;'"?!/-])"""# -emphasize- words    re_emph =      r""'([\(\s'/"]|^)-(.*?)-([\s\.\),:;'"?!/])"""# _Book Title_ citations    re_title =     r""'([\(\s'/"]|^)_(.*?)_([\s\.\),:;'"?!/-])"""# 'Function()" names    re_funcs =     r""'([\(\s/"]|^)'(.*?)'([\s\.\),:;"?!/-])"""

如果您發現或發明了該語言的某種經過微小更新的變體,將它和 EBNF 文法一起使用要比和那些Regex一起使用簡單得多。此外,通常使用 mxTextTools 執行模式操作甚至更快些。

產生和使用標記列表

對於樣本程式,我們將實際文法放置在一個單獨的檔案中。對於大多數用途而言,這種組織比較好,便於使用。通常,更改文法和更改應用程式邏輯是不同種類的任務;這些檔案反映了這一點。但是我們對文法所做的全部處理就是將它作為一個字串傳遞給 SimpleParse 函數,因此我們大體上可以將它包括到主應用程式中(或者甚至以某種方式動態產生它)。

讓我們研究完整的(簡化)標記應用程式:
typographify.py

import     os    from     sys     import     stdin, stdout, stderr    from     simpleparse     import     generator    from     mx.TextTools     import     TextToolsinput = stdin.read()decl = open(    'typographify.def'    ).read()    from     typo_html     import     codesparser = generator.buildParser(decl).parserbyname(    'para'    )taglist = TextTools.tag(input, parser)    for     tag, beg, end, parts     in     taglist[1]:      if     tag ==     'plain'    :    stdout.write(input[beg:end])      elif     tag ==     'markup'    :    markup = parts[0]    mtag, mbeg, mend = markup[:3]    start, stop = codes.get(mtag, (    ''    ,    ''    ))    stdout.write(start + input[mbeg+1:mend-1] + stop)stderr.write(    'parsed %s chars of %s\n'     % (taglist[-1], len(input)))

這就是它所做的。首先讀入文法,然後根據文法建立一個 mxTextTools 解析器。接下來,我們將標記表/解析器應用於輸入源來建立一個標記列表。最後,我們迴圈遍曆標記列表,並且發出一些新的標記文本。當然,該迴圈可以對遇到的每個產品做我們所期望的任何其它事情。

由於智能 ASCII 所使用的特殊文法,源文本中的任何內容都可歸類於“plain”產品或“markup”產品。因此,對於迴圈遍曆標記列表中的單個層級,它已經足夠了(除非我們正好尋找位元定標記產品層級低一級的層級,譬如“title”)。但是格式更自由的文法 ― 譬如出現在大多數程式設計語言中的文法 ― 可以輕鬆地在標記列表中向下遞迴,並在每個層級上尋找產品名稱。例如,如果一種文法中允許嵌套標記代碼,或許可以使用這種遞迴風格。您可能會喜歡弄清如何調整文法的練習(提示:請記住允許各產品彼此遞迴)。

轉至輸出的特殊標記代碼還是儲存到另一個檔案中了,這是由於組織的原因而非本質原因。在這裡我們使用了一個技巧,就是用一個字典作為一個 switch 語句(儘管樣本中的 otherwise 情況還是太狹窄了)。這個想法就是:將來我們可能希望建立多種“輸出格式”的檔案,比如說 HTML、DocBook、LaTeX 或者其它格式。用於樣本的特殊標記檔案類似於:
typo_html.py

codes = \{     'emph'      : (    ''    ,     ''    ),     'strong'     : (    ''    ,     ''    ),     'module'     : (    '' , ''    ),     'code'      : (    ''    ,     ''    ),     'title'      : (    ''    ,     ''    ),}

把這種格式擴充到其它輸出格式很簡單。

結束語

SimpleParse 為含義模糊的 mxTextTools C 模組的準系統和速度提供了一種簡明的並且十分易讀的 EBNF 樣式的封裝器。此外,即使只是順便學會的,許多程式員也已經相當熟悉 EBNF 文法了。關於什麼更容易理解,我不能提供 證明 ― 這一點因各人的直覺而異 ― 但是我可以根據原始碼長度給出量化評估。先前手工開發的 mxTypographify 模組的大小如下:

代碼如下:

wc mxTypographify.py

199 776 7041 mxTypographify.py

這 199 行中,相當數量的行是注釋。這些行中有 18 行是標記函數所包含的Regex版本,包含該標記函數是用於計時比較。但是該程式的功能基本上和上面列出的 typographify.py 的功能相同。相比之下,我們的 SimpleParse 程式,包括其支援檔案在內,大小如下:

代碼如下:

wc typo*.def typo*.py

19 79 645 typographify.def
20 79 721 typographify.py
6 25 205 typo_html.py
45 183 1571 total

換句話說,行數大約只有前者的四分之一。這個版本的注釋較少,但是那主要是因為 EBNF 文法的自我描述能力很強。我不希望太過強調程式碼數 ― 顯然,您可以通過最小化或最大化代碼長度做手腳。但是通常對程式員的工作進行研究,少數實際經驗結論之一是:“千行代碼/人月”相當接近於常數,和語言以及庫關係不大。當然,依次地,Regex版本是 SimpleParse 版本長度的三分之一 ― 但是我認為其運算式的密度使得它極難維護並且更難編寫。總而言之,我認為 SimpleParse 是所考慮的方法中最好的。

  • 聯繫我們

    該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

    如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

    A Free Trial That Lets You Build Big!

    Start building with 50+ products and up to 12 months usage for Elastic Compute Service

    • Sales Support

      1 on 1 presale consultation

    • After-Sales Support

      24/7 Technical Support 6 Free Tickets per Quarter Faster Response

    • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.