寫一個 Markdown translator 玩?

Markdown 早在 2004 年就被發(fā)明出來,能夠普及到現(xiàn)在的程度離不開它的發(fā)明者和所有使用它來寫作的人??

它的基本語法說明可以在其發(fā)明者的 Blog 中找到。Markdown 的流行一定程度上離不開 github 的貢獻,同時 github 也擴展了 Markdown 語法,使得它可以表達更加豐富的內(nèi)容。

Markdown 的出現(xiàn)就是為了讓文本內(nèi)容變得易讀易寫,所以借助 Markdown 的語法格則,即使在純文本模式下也可以獲得比較好的閱讀效果。不過,很多情況下,我們會將 Markdown 解析成 HTML,配合 CSS 來獲得更加絢麗的閱讀感受。

在我最初接觸 Markdown 的時候,我發(fā)現(xiàn)很多解析器之間的結(jié)果存在差異,比如有這樣的文本:

# header
===

相信你會找到不只一個 Markdown 解析器,那么它們對于上面的文本會產(chǎn)生不同的解析結(jié)果嗎?

答案是肯定的。

那么是什么原因?qū)е虏煌慕馕銎髦g產(chǎn)生的結(jié)果會不同?我相信很多人都已經(jīng)知道了 i++ * i++ * i++ 這個經(jīng)典的 C語言的問題了 (如果不知道可以看這里)

沒錯,這就是 Markdown 中的 Undefined Behavior,究其原因就是因為 Markdown 的發(fā)明者并沒有給出 Markdown 語法的完整定義,他只是在語法說明中舉了一些簡單的例子,并附上了自己使用 Perl 寫的 Translator。不幸的是這段代碼早已年久失修,你可以很容易找出它的 bug,對于下面的文本:

> ## This is an H2 in a blockquote
> > nested blockquote
> H1
> ====

它會給出下面的結(jié)果:

<blockquote>
  <h2>This is an H2 in a blockquote</h2>

<blockquote>
  <p>nested blockquote</p>

<h1>H1</h1>
</blockquote>

<p></blockquote></p>

熟悉 HTML 語法的同學(xué)很容易就會發(fā)現(xiàn)最后一行的錯誤。

正是由于 Markdown 的作者沒有給出完整的語法定義,導(dǎo)致在遇到一些 Undefined Behavior 時,每個 translator 的作者必須自己去想一個合適的輸出結(jié)果,這樣就導(dǎo)致了各家的輸出會有一些出入。可能作者也沒想到 Markdown 會流行到這個程度吧,哈。

很早之前我曾用 js 寫過一個簡陋的 Markdown translator,它是由若干的正則配合一些流程控制語句來完成的。最近看到很多關(guān)于 Markdown 語法的文章,我就又回頭看了下那些代碼,于是決定使用 swift 重寫一個。

這次重寫我決定裝出點樣子,代碼中將不使用正則,在解析了 Markdown 文本之后,首先生成一個 Tree,然后才是根據(jù) Tree 中的內(nèi)容產(chǎn)生 HTML。

當(dāng)然在寫之前,還是要分析下 Markdown 語法,并不能只是看看一些小例子,需要在 github 和其他幾個 Markdown 編輯軟件不斷中試錯,小結(jié)出語法,并使用 XEBNF 來描述。

XEBNF 是我自己的 extend BNF,哈哈。使用 XEBNF 是因為 BNF 的 ::= 書寫太麻煩,而 ABNF 又沒有 EBNF 中的 exception - 功能。我還寫了一個 Atom 的 Syntax 來支持 XBNF,于是它們看起來像是這樣:

下面就是我目前小結(jié)出的語法,目前只有 block 元素,因為我先寫的塊元素的解析:

Char                          = [#x0000-#xD7FF] | [#xE000-#x10FFFF]
SP                            = #x0020
HTAB                          = #x0009
CR                            = #x000D
LF                            = #x000A
CRLF                          = CR LF
NL                            = CR | LF | CRLF
WSP                           = HTAB | SP
LWSP                          = NL | WSP
DIGIT                         = [#x0030-#x0039]

; md
md                            = md-block*

; md-block
md-block                      = paragraph | header | blockquote | list | codeblock

; paragraph
paragraph-begin               = SP{0,3} ( Char - ( LWSP | ">" | "*" | "#" ) )
paragraph-content             = ( ( Char - NL ) NL? )+
paragraph-end                 = NL{2} | NL ( Char - ( ">" | "*" | "#" ) )
paragraph                     = paragraph-begin paragraph-content paragraph-end

; header-setext
header-setext-begin           = SP{0,3} ( Char - ( LWSP | ">" | "*" | "# ) )
header-setext-content         = Char - NL
header-setext-end             = NL ( "=" | "-" )+
header-setext                 = header-setext-begin header-setext-content header-setext-end

; header-atx
header-atx-begin              = SP{0,3} "#"+ WSP* ( Char - LWSP )
header-atx-content            = Char - NL
header-atx-end                = "#"* NL
header-atx                    = header-atx-begin header-atx-content header-atx-end

; header
header                        = header-setext | header-atx

; blockquote
blockquote-begin              = SP{0,3} ">"
blockquote-content            = ( header | paragraph | blockquote | list | codeblock )*
blockquote-end                = NL{2} ( Char - ">" )

; list-ul-item
list-ul-item-begin            = SP{0,3} "*" WSP
list-ul-item-content          = ( paragraph | blockquote | list | codeblock )*
list-ul-item-end              = NL{2} ( Char - SP )
list-ul-item                  = list-ul-item-begin list-ul-item-content list-ul-item-end

; list-ul
list-ul                       = list-ul-item+

; list-ol-item
list-ol-item-begin            = SP{0,3} DIGIT "." WSP
list-ol-item-content          = ( paragraph | blockquote | list | codeblock )*
list-ol-item-end              = NL{2} ( Char - SP )
list-ol-item                  = list-ol-item+

; list
list                          = list-ul | list-ol

; codeblock-md
codeblock-md-begin            = ( SP{4} | HTAB ) WSP* ( Char - WSP )
codeblock-md-content          = Char* - codeblock-md-end
codeblock-md-end              = NL SP{0,3} ( Char - WSP )
codeblock-md                  = codeblock-md-begin codeblock-md-content codeblock-md-end

; codeblock-ext
codeblock-ext-begin           = SP{0,3} "`"{3} ( Char - NL ) NL
codeblock-ext-content         = Char* - codeblock-ext-end
codeblock-ext-end             = NL "`"{3} ( Char - NL ) NL
codeblock-ext                 = codeblock-ext-begin codeblock-ext-content codeblock-ext-end

; codeblock
codeblock                     = codeblock-md | codeblock-ext

; html-element
html-element-attr             = Char+ - LWSP
html-element-name             = Char+ - LWSP

html-void-element             = SP{0,3} "<"html-element-name WSP* "/>" NL

html-normal-element-start     = SP{0,3} "<"html-element-name (WSP+ html-element-attr)* WSP* ">"
html-normal-element-content   = Char* - html-normal-element-end
html-normal-element-end       = "</"html-element-name WSP* ">" NL
html-normal-element           = html-normal-element-start html-normal-element-content html-normal-element-end

html-element                  = html-empty-element | html-void-element

有了語法之后,就可以開始寫代碼了,過程是很枯燥的,就是運用遞歸下降的方式,都是一些很機械的工作,代碼在這里,相信會堅持寫完吧??

希望可以給想要自己動手寫一個 Markdown translator 的同學(xué)一點幫助吧??

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容