Markdown-It 可能是目前擴(kuò)展性和活躍度最好的 Markdown Parser 了。出于各種原因,我們需要掌握 Markdown-It 對(duì) Markdown 的解析過程。
對(duì)于開發(fā)者(Plug-Ins 或者貢獻(xiàn)者),它提供了文檔,不過這個(gè)文檔可是建立在你首先閱讀源程序的基礎(chǔ)上。我寫這篇文章算是一個(gè)前傳吧,這樣閱讀源程序也能輕松點(diǎn)。
當(dāng)你創(chuàng)建了一個(gè) md = require('markdown-it')() 對(duì)象之后,就可以用它來(lái)渲染 MD 文檔了,例如: md.render("# I'm H1 ")。這個(gè)渲染過程分為主要的兩步:
- 將 MD 文檔 Parsing 為 Tokens。
- 渲染這個(gè) Tokens。
Parsing
Parsing 的過程是,首先創(chuàng)建一個(gè) Core Parser,這個(gè) Core Parser 包含一系列的缺省 Rules。這些 Rules 將順序執(zhí)行,每個(gè) Rule 都在前面的 Tokens 的基礎(chǔ)上,要么修改原來(lái)的 Token,要么添加新的 Token。這個(gè) Rules 的鏈條被稱為 Core Chain。缺省 Core Rules 如下:
- normalize: MD 文檔的換行符統(tǒng)一化;將空字符 \u0000 轉(zhuǎn)換為 \uFFFD
- block: 識(shí)別出哪些是 Block Token(Table, blockquote, Code, Fence 等),哪些是 Inline Token。如果是 Block Token,則啟動(dòng) Block Chain 來(lái)處理。
- inline: ?針對(duì) Block Rule 識(shí)別出來(lái)的 'inline' 類型的 token 進(jìn)行處理
- linkify: 檢測(cè) text 類型的 token 中是否有可是別的 URL(http 或者 mailto),如果有,則將原本完整的 text token 分為
text,link,text三部分(實(shí)際不只三個(gè) tokens, 因?yàn)?link_open,link_close這些 tokens 都會(huì)被產(chǎn)生) - replacements: ?完成諸如 (c) (C) → ? ,+- → ±的替換,同時(shí)躲開 link 中的包含的對(duì)象文字
- smartquotes: 完成引號(hào)的排印化處理
Block rule 的執(zhí)行過程,會(huì)啟動(dòng) Block Chain,這又是一堆 Rules 的執(zhí)行,缺省包含:
var _rules = [
// First 2 params - rule name & source. Secondary array - list of rules,
// which can be terminated by this one.
[ 'table', require('./rules_block/table'), [ 'paragraph', 'reference' ] ],
[ 'code', require('./rules_block/code') ],
[ 'fence', require('./rules_block/fence'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
[ 'blockquote', require('./rules_block/blockquote'), [ 'paragraph', 'reference', 'list' ] ],
[ 'hr', require('./rules_block/hr'), [ 'paragraph', 'reference', 'blockquote', 'list' ] ],
[ 'list', require('./rules_block/list'), [ 'paragraph', 'reference', 'blockquote' ] ],
[ 'reference', require('./rules_block/reference') ],
[ 'heading', require('./rules_block/heading'), [ 'paragraph', 'reference', 'blockquote' ] ],
[ 'lheading', require('./rules_block/lheading') ],
[ 'html_block', require('./rules_block/html_block'), [ 'paragraph', 'reference', 'blockquote' ] ],
[ 'paragraph', require('./rules_block/paragraph') ]
];
在 Block Chain 執(zhí)行完了,就是 Inline Rule ?執(zhí)行,也就啟動(dòng)了 Inline Chain,包含:
var _rules = [
[ 'text', require('./rules_inline/text') ],
[ 'newline', require('./rules_inline/newline') ],
[ 'escape', require('./rules_inline/escape') ],
[ 'backticks', require('./rules_inline/backticks') ],
[ 'strikethrough', require('./rules_inline/strikethrough').tokenize ],
[ 'emphasis', require('./rules_inline/emphasis').tokenize ],
[ 'link', require('./rules_inline/link') ],
[ 'image', require('./rules_inline/image') ],
[ 'autolink', require('./rules_inline/autolink') ],
[ 'html_inline', require('./rules_inline/html_inline') ],
[ 'entity', require('./rules_inline/entity') ]
];
var _rules2 = [
[ 'balance_pairs', require('./rules_inline/balance_pairs') ],
[ 'strikethrough', require('./rules_inline/strikethrough').postProcess ],
[ 'emphasis', require('./rules_inline/emphasis').postProcess ],
[ 'text_collapse', require('./rules_inline/text_collapse') ]
];
所以這個(gè) Parsing 的過程 ?是一個(gè)簡(jiǎn)單的樹形過程,Core Rules 執(zhí)行到 Block 和 Inline ?Rule 的時(shí)候,會(huì)分別再執(zhí)行 Block Chain 和 Inline Chain。整個(gè) Parsing 過程是同步的。
Markdown-It 的 API 允許 Plugin 作者選擇在?這些缺省的 Rules 的前后添加新的 Rule 函數(shù),從而實(shí)現(xiàn)對(duì)特定的 Token 的再處理(增刪改 Token 都可以)。可以說, Plugin 作者的靈活性是足夠大的。
Rendering
在所有 Tokens 都獲得之后,就可以渲染了。渲染就是把特定 Token 轉(zhuǎn)變?yōu)樘囟ǖ?HTML 的過程。
?Markdown-It 允許你為特定的 Token Type 掛載自己的渲染函數(shù),這個(gè)函數(shù)稱為 Renderer Rule。?Markdown-It 已經(jīng)定義了幾個(gè) 缺省的 Renderer Rules:
code_inline
code_block
fence
image
hardbreak
text
html_block
html_inline
上面這些名字,其實(shí)是對(duì)應(yīng)的 token.type 的值。渲染時(shí),如果遇到匹配的 token.type,那么就會(huì)用對(duì)應(yīng)的 Renderer Rule 來(lái)渲染。
如果?沒有找到對(duì)應(yīng)的 Renderer Rule,那么一個(gè)缺省的 render 函數(shù)會(huì)被調(diào)用。?
Renderer Rules 和 Parsing Rules 是兩個(gè)概念,不要混淆。?由于 Markdown-It 靈活的擴(kuò)展機(jī)制(你可以創(chuàng)建任何類型的 Token),因此到底會(huì)出現(xiàn)多少種 token types 其實(shí)是依賴于你裝了多少 Plugins。