如何利用Swift解析MarkDown

由于自己的开源项目需要使用MarkDown作为主要的文本语法,本该由iOS小组同学负责的东西由于他有点事情所以就改为由我负责了。由于也是第一次接触在Swift上边解析MarkDown,所以先去了解了一些github上边的开源项目,但是少之又少,而且实现的功能都很简单(大多数都是使用NSAttributeString来实现简单的加粗、倾斜、下划线),并不能达到我所期望的效果(支持90%以上的MarkDown并支持HTML)。

更新,IBM提供了C库用来解析MardDown到HTML

如何解决问题

首先根据经验,我首先想到的是CoreText + Regex。没错,给我一段MarkDown的文本,先用Regex匹配出每一个MarkDown的Element,然后使用CoreText绘制出来,如果是图片则预先占位,表格则细化解析,绘制。尝试着下手之后,发现MarkDown的语法不是一般的多,这样写下去光是解析器我可能要写一个月左右(编码、测试等)。

于是,抱着比较懒的心态,我想到了我在做PC端的时候用到的marked.js,是我用来把MarkDown文本转为HTML的,这样一来,客户端的文本解析与预览就可以通过这个工具搞定一大半了,所以接下来的事情就是把marked.js加入到项目中,load并调用方法。既然是与JS交互,相信读者对JavaScriptCore这个库一定不是很陌生。

过程
  1. 加载:先把marked.js拖拽到项目中,然后在某文件中引入JavaScriptCore,并加载
1
2
3
4
5
6
7
8
9
10
11
12
import JavaScriptCore
private var context: JSContext = JSContext()
private var marked: JSValue?
// initialized javascript
do {
try _ = context.evaluateScript(String(contentsOfFile: Bundle.main.path(forResource: "marked", ofType: "js")!))
marked = context.objectForKeyedSubscript("marked")
} catch {
debugPrint("initialized marked js with error!")
}

要注意的是,这里的marked.js文件我们要稍作修改,需要把最外层的function(涵义是定义并马上执行)去掉。

  1. 调用

上边我们通过marked = context.objectForKeyedSubscript("marked")这样的方式获得了marked这个方法,紧接着按照官方文档的要求去调用:

1
2
3
4
5
guard let value = marked?.call(withArguments: [content]) else {
return false
}
let markedHTML = value.toString()

这样,我们通过marked?.call来调用这个方法,并获得返回的value,将结果toString()

以上完成的步骤就是将textView输入的文字转为HTML,接下来就是渲染了,很简单,依旧按照PC的思路,MarkDown-HTML的样式使用github-markdown-css,代码高亮使用highlight.js,为了快速,我们将这些css与js资源文件也一并压缩打包到App的bundle中,然后使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!DOCTYPE HTML>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1, minimal-ui">
<link rel="stylesheet" type="text/css" href="github-markdown.css"/>
<link rel="stylesheet" href="libs/highlight.js/9.8.0/styles/color-brewer.min.css">
<style>
.markdown-body {
box-sizing: border-box;
padding: 10px;
}
</style>
</head>
<body>
<div id="container">
<article class="markdown-body">
[markdown-content-flag]
</article>
</div>
<script src="libs/highlight.js/9.8.0/highlight.min.js"></script>
<script>hljs.initHighlightingOnLoad();</script>
</body>
</html>

读者可能会注意到其中有[markdown-content-flag]这样一个字串,用于我们加载之前替换为MarkDown文本:

1
2
3
4
5
6
7
8
9
10
do {
var htmlContent = try String(contentsOfFile: Bundle.main.path(forResource: "PNPreview", ofType: "html")!)
let range = htmlContent.range(of: "[markdown-content-flag]")
htmlContent.replaceSubrange(range!, with: HTML)
webView.stopLoading()
webView.loadHTMLString(htmlContent, baseURL: nil)
} catch {
}

这样,展示也就没有问题了,然后大家可以在其中做很多层自定义处理。最终我写了简单的demo用于测试,效果如下:

这样,编辑器的一大部分就解决掉了。当然这对于用户来讲还不是最好的体验,最好的体验应该是我们一边输入一边看到效果的,这种效果的实现方式依旧可以使用上边的方式来实现。

关于MarkDown-CSS样式以及代码高亮的效果大家可以自由的去寻找。

在客户端工作之外,接触到了其他各个方面的语言确实对自我提升很大, 我这里说的不是你能读懂的的代码多了,而是说解决问题的思路更多了。每一种语言都有自己的独到之处。甚至你可以从一些管理方面的书籍中学习到一些实际的技巧应用到人员的组织、调配,项目的合理安排与规划等。

评论