日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

解析markdown_markdown-it 原理浅析

發(fā)布時間:2024/8/23 编程问答 47 豆豆
生活随笔 收集整理的這篇文章主要介紹了 解析markdown_markdown-it 原理浅析 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

最近使用 markdown-it 比較多,也開發(fā)了一些插件,在這個過程中對源碼進行了研讀,最終寫了這篇文章。需要了解細節(jié)的讀者可以自行閱讀文檔。

此文分為兩個部分:原理剖析和原理應用(編寫插件)。

markdown-it 原理

輸入一串 markdown 代碼,最后得到一串 html 代碼,整體流程如下:

我們以一個簡單的例子來解釋整個流程:?# 我是一個例子? -> ?<h1>我是一個例子</h1>?

首先,它會被解析器拿到,經(jīng)過各個解析規(guī)則處理后得到一個 token 流,接著這個 token 流被渲染器拿到,經(jīng)過各個渲染規(guī)則處理后逐步拼接成一個 html 字符串。

解析器

markdown-it 內(nèi)置了七個核心規(guī)則,在上圖我對解析規(guī)則使用了虛線,因為它們是可以被啟用/禁用的。我們這篇文章只來聊聊最核心的兩個規(guī)則:block 和 inline。

規(guī)范指出:

我們可以將一篇 Markdown 文檔視為一系列塊,塊是一種結構化的元素,如段落,塊引用,列表,標題,規(guī)則和代碼塊。一些塊(如塊引號和列表項)可以包含其他塊; 其他(如標題和段落)包含內(nèi)聯(lián)內(nèi)容,如文本,鏈接,強調(diào)文本,圖像,行內(nèi)代碼等。
塊結構的解析優(yōu)先級始終高于內(nèi)聯(lián)結構。這意味著解析可以分兩步進行:
1.識別 markdown 文檔的塊結構;
2.將段落,標題和其他塊結構中的文本行,作為內(nèi)聯(lián)結構解析。
注意,第一步需要按順序處理行,但第二步可以并行化,因為一個塊元素的內(nèi)聯(lián)解析不會影響任何其他塊的內(nèi)聯(lián)解析。
塊分為兩種類型:容器塊和葉子塊,容器塊可以包含其他塊,但葉子塊不能包含其他塊。

具體解析時,會圍繞著 line 和 character 兩個維度來解析。

對于每一行來說,解釋的結果有以下三種:

  • 用來關閉一個或多個塊結構。
  • 用來創(chuàng)建一個或多個新塊結構,作為最后打開的塊結構的子節(jié)點。
  • 可以將文本添加到樹上剩余的最后(最深的)打開的塊結構上。
  • 對于我們這個例子,會先創(chuàng)建一個 heading 塊,然后將文本內(nèi)容添加到這個塊上。下一行沒有內(nèi)容,于是塊關閉。

    字符包括非空白字符和空格(?U+0020?),制表符 (?U+0009?),換行符(?U+000A?),行列表(?U+000B?),換頁(?U+000C?)或回車(?U+000D?)這些空白字符。這里我們不做展開。

    這期間會接觸到的規(guī)則有 block、inline、heading、text。

  • block 規(guī)則,會用來解析 ?# 我是一個例子?
    • 先進入 tokenize 函數(shù),內(nèi)含十一個 block 規(guī)則。
    • heading 規(guī)則
    • 得到 heading_open 、inline、 heading_close 三個 token
  • inline 規(guī)則,會用來解析 ?我是一個例子?
    • 先進入 parse 函數(shù),內(nèi)含四個 inline 規(guī)則
    • text 規(guī)則
    • 得到 text 的 token

    解析完畢,我們得到了 3 + 1 個 token:

    token 流

    這里我們得到的結果不是一顆 AST 樹,而是一個數(shù)組,markdown-it 稱之為 token 流。為什么呢?

    官方解釋是:

    • Tokens 是一個簡單的數(shù)組。(AST 是一個對象)
    • 打開的標簽和關閉的標簽可以隔離。
    • 將“內(nèi)聯(lián)容器(inline container)”作為一種特殊的 block token 對象。它有嵌套的 tokens,如粗體,斜體,文本等等。

    這樣做有什么好處呢?這樣就可以并行處理 block 和 inline 類型的 token 了。

    生成 token 流后,它們就被會傳遞給 renderer。

    渲染器

    它會遍歷所有 token,將每個 token 傳遞給與 token 的 type 屬性同名的規(guī)則。markdown-it 內(nèi)置了九種規(guī)則:圍欄、行內(nèi)代碼、代碼塊、html 塊、行內(nèi) html、圖片、硬換行、軟換行、文本。

    type 屬性不在內(nèi)置規(guī)則的 token 將會被被傳入 renderToken 中當一個普通 token 處理,這里不作展開。

    回到我們的例子中來:

    heading_open 會被渲染成 ?<h1>?

    inline 中的 text 會被渲染成 ?我是一個例子?

    heading_close 會被渲染成 ?</h1>?

    markdown-it 插件

    一些 markdown-it 插件就利用了上述的原理。

    markdown-it-container

    這個插件可以讓你支持內(nèi)容塊:比如 vuepress 的內(nèi)容塊:


    這是如何實現(xiàn)的呢?我們可以根據(jù)之前的介紹推測一個內(nèi)容塊的 token 流:

    第一行和第三行有 block 型的 token,一個代表 open,一個代表 close。第二行是 inline 型的 token,其中的內(nèi)容是 inline 型的。

    由于內(nèi)容塊中是 inline 類型,所以圍欄、行內(nèi)代碼、代碼塊、html 塊、行內(nèi) html、圖片、硬換行、軟換行、文本都是支持的。

    實際上,我們會逐行掃描,找到匹配 ?::: tip? 這樣的內(nèi)容塊語法,將它作為一個塊結構開始進行解析,直到有 ?:::? 的行結束。其中的每一行,都將解析為 paragraph_open、inline、paragraph_close。

    解析后的 token 流最后分別渲染 ?<div>? 、若干 p 標簽、 ?</div>?。

    markdown-it-anchor

    這個插件可以對標題進行錨點抽取,以便閱讀文檔時能快速定位位置。

    這里也可以推測一下,是不是往原本是 heading_open type 的 token 之前插入了一個 token 呢?這個 token 渲染出來就是錨點。

    實際上,的確是插入了 token,但不止一個,因為錨點是可點擊的,所以實際上是一個 a 鏈接,也就是 link_open、inline、link_close 三個 token。而且也不是插入在 heading_open 之前,而是 heading_open 和 heading_close 之間的 inline 子元素里了,因為 ?#? 是和 ?Markdown 語法?平級的。

    注意事項:
    1.因為標題可能是@#$等特殊字符,會造成 url 哈希無效,所以需要對錨點的哈希值轉義。
    2.可能會出現(xiàn)重名的標題,所以需要對哈希進行標記

    給鏈接添加屬性

    官方有一個寫插件的例子:添加 target="_blank" 屬性到所有鏈接。

    有兩種方式:

  • 修改渲染器規(guī)則
  • // 如果覆蓋,或者是對默認渲染器的代理,則記住老的渲染器。 var defaultRender = md.renderer.rules.link_open || function(tokens, idx, options, env, self) {return self.renderToken(tokens, idx, options); };md.renderer.rules.link_open = function (tokens, idx, options, env, self) {// 如果你確認其他的插件不能添加 `target` - 放棄以下檢查:var aIndex = tokens[idx].attrIndex('target');if (aIndex < 0) {tokens[idx].attrPush(['target', '_blank']); // 添加新屬性} else {tokens[idx].attrs[aIndex][1] = '_blank'; // 替換已經(jīng)存在的屬性值}// 傳遞 token 到默認的渲染器。return defaultRender(tokens, idx, options, env, self); };
  • 修改 token
  • var iterator = require('markdown-it-for-inline');var md = require('markdown-it')().use(iterator, 'url_new_win', 'link_open', function (tokens, idx) {var aIndex = tokens[idx].attrIndex('target');if (aIndex < 0) {tokens[idx].attrPush(['target', '_blank']);} else {tokens[idx].attrs[aIndex][1] = '_blank';}});

    結語

    markdown-it 作為一款經(jīng)典的 js 解析 markdown 的庫,其中思想和設計都可以細細揣摩,回味久久。

    總結

    以上是生活随笔為你收集整理的解析markdown_markdown-it 原理浅析的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。