如何封装一个支持LaTex的Markdown解析器

起因

最近在仿写一款优秀的写作软件,优秀的写作软件支持markdown的解析自然是一个必不可少的功能,在思考如何实现解析器的同时,恰好阅读了钟颖大佬出品的关于“代码编辑器”的技术文章,发现可以使用 WebView封装 markdown-ithighlightjs.org 一类的web项目,低成本地实现一个有着不错效果的CommonMark解析器。

实现过程

1. HTML文件创建

创建本地文件 index.html,并输入代码。页面中用到的js文件和资源需要后续创建和生成。<body>标签中唯一的<div>将用于后续插入通过 markdown-it渲染的代码,页面的样式布局通过css文件实现和优化。

<html>
    <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
        <link rel="stylesheet" href="./main.css" />
        <script src="./main.js"></script>
    </head>
    <body>
        <div class="container" id="contents"></div>
    </body>
</html>

2. JS文件创建

创建本地文件 index.js,在文件中编写需要注入网页的js代码。

import hljs from 'highlight.js'
import MarkdownIt from 'markdown-it'
import emoji from 'markdown-it-emoji'
import './../css/bootstrap.css'
import './../css/gist.css'
import './../css/github.css'
import './../css/index.css'

window.showMarkdown = (percentEncodedMarkdown, enableImage = true) => {

  if (!percentEncodedMarkdown) {
    return
  }

  const markdownText = decodeURIComponent(percentEncodedMarkdown)

  let markdown = new MarkdownIt({
    html: true,
    breaks: true,
    linkify: true,
    highlight: function(code){
        return hljs.highlightAuto(code).value;
    }
  })
  if (!enableImage) {
    markdown = markdown.disable('image')
  }
  markdown.use(emoji)
  markdown.use(require('markdown-it-latex2img'))
  let html = markdown.render(markdownText)

  document.getElementById('contents').innerHTML = html
  let tables = document.querySelectorAll('table')
  tables.forEach((table) => {
    table.classList.add('table')
  })
  let codes = document.querySelectorAll('pre code')
  codes.forEach((code) => {
    hljs.highlightBlock(code)
  })

}

主要用到的js库有:

markdown-it:100% CommonMark 语法解析
highlight.js:代码高亮
markdown-it-emoji:emoji语法支持
markdown-it-latex2img:基于服务器端的MathJax解析器

代码的基本逻辑主要是:
1.由于markdown格式的内容被传入时事先进行了编码操作,所以这里需要调用decodeURIComponent()函数对内容先进行解码。
2.使用 markdown-it别结合所需插件对内容进行渲染,更多关于 markdown-it的使用和插件支持,可以参考官方的API文档:《markdown-it 中文文档》
3.将渲染后的代码插入 index.html页面的对应位置,并支持表格(内嵌功能)和代码高亮的显示。

3. 控件封装

业务逻辑:创建 WKWebView,并注入js代码(showMarkdown()函数调用)
关键代码

  @objc public func load(markdown: String?, enableImage: Bool = true) {
    guard let markdown = markdown else { return }

    if let url = htmlURL {
      let templateRequest = URLRequest(url: url)

      let escapedMarkdown = self.escape(markdown: markdown) ?? ""
      let imageOption = enableImage ? "true" : "false"
      let script = "window.showMarkdown('\(escapedMarkdown)', \(imageOption));"
      let userScript = WKUserScript(source: script, injectionTime: .atDocumentEnd, forMainFrameOnly: true)

      let controller = WKUserContentController()
      controller.addUserScript(userScript)

      let configuration = WKWebViewConfiguration()
      configuration.userContentController = controller

      let wv = WKWebView(frame: self.bounds, configuration: configuration)
      wv.scrollView.isScrollEnabled = self.isScrollEnabled
      wv.translatesAutoresizingMaskIntoConstraints = false
      wv.navigationDelegate = self
      addSubview(wv)
    
      // 代码省略:布局约束、空间样式
      // ...

      wv.load(templateRequest)
    } else {
      // TODO: raise error
    }
  }

  private func escape(markdown: String) -> String? {
    return markdown.addingPercentEncoding(withAllowedCharacters: CharacterSet.alphanumerics)
  }

4. 资源文件打包

到此,代码层面的工作就可以结束了。
最后我们需要使用 Webpack工具将js应用程序打包成方便使用的静态资源,输出名为 main.js的文件,将其放置在 index.html文件相同的目录下。这里提供一个可供参考的配置文件:webpack.config.js

写在最后

本文所描述的实现过程,主要参考了三方库 MarkdownView,因为需要支持LaTeX的解析,我稍微重写了showMarkdown函数,加入了 markdown-it-latex2img库的使用,能够将数学公式以图片的形式进行展示。过程中,还学习了 Webpack工具的使用和js静态资源的打包。Webview项目的封装能够为原生提供更丰富的功能,能够为复杂的业务实现提供更多的思路和扩展,希望本文能够对需要的人有所帮助。

参考资料:

Taio开发笔记
Webpack教程
markdown-it API文档
keitaoouchi/MarkdownView

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容