前回、SwiftでMarkdownを解析してオブジェクトツリーに変換するという記事を作成しましたが、今回はその続きです。
尚、前回記事で「レンダリングはしてくれるけどオブジェクトツリーにしてくれるパッケージあまりないな...」と言いましたが大抵のSwiftのMarkdownレンダリング系パッケージは
text
-->node tree
-->html
のような変換過程を辿っていることがわかりました。つまり今Tryしていることはいわゆる「車輪の再発明」ということです。勉強になるからいいかと割り切ってます
問題点
「とりあえず動くもの!」ということで前回はとりあえずif分岐をひたすらしていましたが流石にテストし難いです。
/// 以下はイメージです
func parse(text:String){
while(全文字探査するまで){
if(H1の場合){
}
else if(H2の場合){
}
// 中略。無限にelse if...
else if(コードブロック("```")の場合){
}
1文字進める、または処理済みの所まで進める()
}
}
変更
Markdownの要素(# H1
や**strong**
,...)ごとにシンプルなテストができるようにしたいです
Markdown要素ごとの解析処理の分化
前回記事で述べているように、この開発中のパッケージではMarkdownの各種要素をそれぞれインライン要素、ブロック要素とみなしています。それぞれの要素ごとにParserを作成するイメージでクラス分離していってます。
(紫で塗りつぶされているのはprotocol
)
BlockParserDelegate
もInlineParserDelegate
もそれぞれ、解析結果のオブジェクトと、まだ解析していないMarkdown文字列を返します。
BlockParserDelegate
import Foundation
public protocol BlockParserDelegate{
func parse(_ text:String, closure:(String)->[MDInline]) -> (nextText:String, node:MDBlock?);
}
InlineParserDelegate
import Foundation
public protocol InlineParserDelegate{
func parse(text:String) -> (nextText:String, node:MDInline?)
}
このProtocolの実装クラスStrongParser
import Foundation
public class StrongParser: InlineParserDelegate
{
public func parse(text:String) -> (nextText:String, node:MDInline?){
if(text.starts(with: "**")){
// scan
let subStartIndex = text.index(text.startIndex, offsetBy: 2)
let subText = text[subStartIndex...]
if let end = subText.range(of: "**"){
// text between ** and **
let content = subText[..<end.lowerBound]
return (String(subText[end.upperBound...]), MDStrong(String(content)))
}
}
return (text, nil)
}
}
StrongParser
では以下のようなイメージです
let parser = StrongParser();
let result1 = parser.parse(text: "**bold**text")
// result1.nextText -> "text"
// result1.node -> MDStrong(text:"bold")
let result2 = parser.parse(text: "texttext")
// result2.nextText -> "texttext"
// result2.node -> nil
結果
この変更によってMarkdown要素ごとのテストが可能になりました。あとは呼び出し側のクラスがテスト済みのそれらをうまく使うだけです。
今回の記事の内容を適用したまだまだ改善の余地が大きいMarkdown解析Githubリポジトリ