Python使用Mistune對markdown自定義規則解析

Mistune——更快的markdown解析器

在Python中有很多markdown解析器,以前我一直使用的是Python-markdown,一個純Python實現的markdown解析器,別的不說,慢的要死倒是真的。每次點擊保存后,都要響應很久,我開始一直以為是我的vps在國外導致的,后來還用了Mistune才知道,不是網速的問題,是解析器的速度問題。

沒有對比就沒有傷害,Mistune是所有純Python實現中最快的一個。在純Python環境中,幾乎比Python- markdown快4倍,在Cython的幫助下,幾乎快5倍。現在我使用了CPython,幾乎是點擊完保存的一瞬間,就解析完了,可以說感覺是很明顯的。

基礎用法

一個簡單的栗子:

import mistune

mistune.markdown('I am using **mistune markdown parser**')
# output: <p>I am using <strong>mistune markdown parser</strong></p>

如果關心性能,官方推薦使用對象實例化后在進行調用

import mistune

markdown = mistune.Markdown()
markdown('I am using **mistune markdown parser**')

選擇項

在使用mistune.Renderer的時候會有一些選擇項提供修改

renderer = mistune.Renderer(escape=True, hard_wrap=True)
# use this renderer instance
markdown = mistune.Markdown(renderer=renderer)
markdown(text)
  • escape: 如果設置為False,則不會轉義所有原始html標記。
  • hard_wrap: 如果設置為True,它將具有GFM換行功能。所有新行都將替換為<br>標記。GFM跟標準MD一樣,行尾不允許直接回車換行,必須是\n\n或者空格空格\n
  • use_xhtml: 如果設置為True,則所有標記都將使用xhtml,例如:<hr />。
  • parse_block_html:只解析block級別的。
  • parse_inline_html: 只解析inline級別的。

Renderer(渲染器)

官方提供了渲染器,當然你也可以繼承官方的Renderer然后自己重寫或者添加一些方法,這里給了一個官網給出的栗子,是一個給所有代碼塊添加highlighting標簽的栗子。

import mistune
from pygments import highlight
from pygments.lexers import get_lexer_by_name
from pygments.formatters import html

class HighlightRenderer(mistune.Renderer):
    def block_code(self, code, lang):
        if not lang:
            return '\n<pre><code>%s</code></pre>\n' % \
                mistune.escape(code)
        lexer = get_lexer_by_name(lang, stripall=True)
        formatter = html.HtmlFormatter()
        return highlight(code, lexer, formatter)

renderer = HighlightRenderer()
markdown = mistune.Markdown(renderer=renderer)
print(markdown('```python\nassert 1 == 1\n```'))

下面是關于解析內容的很關鍵的定義,一個是塊級解析(Block Level),一個是小跨度級解析(Span Level),也就是每句話中間出現的那種markdown解析,加粗什么的,還有一個是腳注(Footnotes)

Block Level

塊級的API如下:

block_code(code, language=None) #代碼塊
block_quote(text) #引用塊
block_html(html) #原生html?
header(text, level, raw=None) # 不知道是啥
hrule() # 不知道是啥
list(body, ordered=True) # 不知道是啥
list_item(text) # 不知道是啥
paragraph(text) # 段落
table(header, body) # 表格
table_row(content) # 表格行
table_cell(content, **flags) # 表格單元

Span Level

塊級的API如下:

autolink(link, is_email=False) #引用鏈接
codespan(text) #行級code
double_emphasis(text) #加粗
emphasis(text) #斜體
image(src, title, alt_text) #圖片
linebreak() #不知道
newline() #新的一行?
link(link, title, content) #引用鏈接
strikethrough(text) #不知道
text(text) #普通文字
inline_html(text) #內嵌HTML

Footnotes

腳注的API如下:

footnote_ref(key, index)
footnote_item(key, text)
footnotes(text)

自定義規則

下面就是自定義規則了,我們大概了解了每個API,官方源碼里面主要分了以下幾個類

  • BlockGrammar:關于塊級的語法正則定義
  • BlockLexer:塊級詞法分析器
  • InlineGrammar:關于行級的語法正則定義
  • InlineLexer:行級詞法分析器
  • Markdown:Markdown解析
  • Renderer:解析器

在這里,我舉一個真實的栗子來講解,首先拋出問題

問題:我在使用mathtype的時候,書寫公式,例如這個公式||x||_1= \sum_{i=0}^{n}|x_i|,這是L1范數的公式,現在解析肯定沒有問題了,我已經修復了,但如果是以前,會由于||x||后面的字符_sum后面的字符_,匹配到markdown中就是斜體的定義,那么我的公式就會變為

||x|| @1= \[email protected] {i=0}^{n}|x_i|?

這個樣子。所以為了解決這個問題,我要重寫有關于下劃線_和乘號*的markdown語法。

解決辦法:

首先定位一下,主要引起這個的有兩個,一個是加粗,一個是斜體,所以我重寫他們。

  1. 定義自己的InlineLexer,繼承InlineLexer;
  2. 找到規則double_emphasisemphasis,我將加粗改寫為了只允許**框住,斜體改寫為了@框??;
  3. 加入到default_rules這個list里面;
  4. 重寫輸出函數output_double_emphasisoutput_emphasis。
class MyInlineLexer(InlineLexer):
    def enable_delete_em(self):
        self.rules.double_emphasis = re.compile(
            r'\*{2}([\s\S]+?)\*{2}(?!\*)'  # **word**
        )
        self.rules.emphasis = re.compile(
            r'\@((?:\*\*|[^\*])+?)\@(?!\*)'  # @[email protected]
        )
        self.default_rules.insert(3, 'double_emphasis')
        self.default_rules.insert(3, 'emphasis')

    def output_double_emphasis(self, m):
        text = m.group(1)
        return self.renderer.double_emphasis(text)

    def output_emphasis(self, m):
        text = m.group(1)
        return self.renderer.emphasis(text)

然后在解析markdown的時候

renderer = Renderer()
inline = MyInlineLexer(renderer)
inline.enable_delete_em()
markdown = mistune.Markdown(renderer=renderer, inline=inline)
  1. 實例化Renderer;
  2. 實例化自己的詞法分析器;
  3. 調用enable_delete_em()重寫對應的地方;
  4. 最后,實例化markdown,傳入對應的renderer與inline即可。

Reference

Mistune文檔

Markdown Parsers in Python

posted @ 2020-03-02 13:47  harrylyx  閱讀(...)  評論(...編輯  收藏