ZMarkupParser|Swift HTML String 轉 NSAttributedString 工具,支援客製樣式與高效能渲染
針對 iOS/macOS 開發者,提供純 Swift 實作的 HTML String 轉 NSAttributedString 工具,支援自動修正錯誤標籤、客製樣式設定與多種 HTML Tag,提升渲染效能並避免長字串閃退,助你打造流暢又穩定的文字顯示效果。
Click here to view the English version of this article, translated by OpenAI.
出於 SEO 考量,本文標題與描述經 GPT 調整,原始版本請參考內文,若有不恰當的用語,敬請指正。
ZMarkupParser HTML String 轉換 NSAttributedString 工具
轉換 HTML String 成 NSAttributedString 對應 Key 樣式設定
ZhgChgLi / ZMarkupParser
功能
- 使用純 Swift 開發,透過 Regex 剖析出 HTML Tag 並經過 Tokenization,分析修正 Tag 正確性(修正沒有 end 的 tag & 錯位 tag),再轉換成 abstract syntax tree,最終使用 Visitor Pattern 將 HTML Tag 與抽象樣式對應,得到最終 NSAttributedString 結果;其中不依賴任何 Parser Lib。
- 支援 HTML Render (to NSAttributedString) / Stripper (剝離 HTML Tag) / Selector 功能
- 自動分析修正 Tag 正確性(修正沒有 end 的 tag & 錯位 tag)
<br>
-><br/>
<b>Bold<i>Bold+Italic</b>Italic</i>
-><b>Bold<i>Bold+Italic</i></b><i>Italic</i>
<Congratulation!>
-><Congratulation!>
(treat as String) - 支援客製化樣式指定 e.g.
<b></b>
->weight: .semilbold & underline: 1
- 支援自行擴充 HTML Tag 解析 e.g. 解析
<zhgchgli></zhgchgli>
成想要的樣式 - 包含架構設計,方便對 HTML Tag 進行擴充 目前純了支援基本的樣式之外還支援 ul/ol/li 列表及 hr 分隔線渲染,未來要擴充支援其他 HTML Tag 也能快速支援
- 支援從
style
HTML Attribute 擴充解析樣式 HTML 可以從 style 指定文字樣式,同樣的,此套件也能支援從style
中指定樣式 e.g.<b style=”font-size: 20px”></b>
->粗體+字型 20 px
- 支援 iOS/macOS
- 支援 HTML Color Name to UIColor/NSColor
- Test Coverage: 80%+
- 支援
<img>
圖片、<ul>
項目清單、<table>
表格…等等 HTMLTag 解析 - 比
NSAttributedString.DocumentType.html
更高的效能
效能分析
- 測試環境:2022/M2/24GB Memory/macOS 13.2/XCode 14.1
- X 軸:HTML 字數
- Y 軸:渲染所花時間(秒)
*另外 NSAttributedString.DocumentType.html
超過 54,600+ 長度字串就會閃退 (EXC_BAD_ACCESS)。
試玩
可直接下載專案打開 ZMarkupParser.xcworkspace
選擇 ZMarkupParser-Demo
Target Build & Run 直接測試效果。
安裝
支援 SPM/Cocoapods ,請參考 Readme 。
使用方式
樣式宣告
MarkupStyle/MarkupStyleColor/MarkupStyleParagraphStyle,對應 NSAttributedString.Key 的封裝。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var font:MarkupStyleFont
var paragraphStyle:MarkupStyleParagraphStyle
var foregroundColor:MarkupStyleColor? = nil
var backgroundColor:MarkupStyleColor? = nil
var ligature:NSNumber? = nil
var kern:NSNumber? = nil
var tracking:NSNumber? = nil
var strikethroughStyle:NSUnderlineStyle? = nil
var underlineStyle:NSUnderlineStyle? = nil
var strokeColor:MarkupStyleColor? = nil
var strokeWidth:NSNumber? = nil
var shadow:NSShadow? = nil
var textEffect:String? = nil
var attachment:NSTextAttachment? = nil
var link:URL? = nil
var baselineOffset:NSNumber? = nil
var underlineColor:MarkupStyleColor? = nil
var strikethroughColor:MarkupStyleColor? = nil
var obliqueness:NSNumber? = nil
var expansion:NSNumber? = nil
var writingDirection:NSNumber? = nil
var verticalGlyphForm:NSNumber? = nil
...
可依照自己想套用到 HTML Tag 上對應的樣式自行宣告:
1
let myStyle = MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
HTML Tag
宣告要渲染的 HTML Tag 與對應的 Markup Style,目前預定義的 HTML Tag Name 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
A_HTMLTagName(), // <a></a>
B_HTMLTagName(), // <b></b>
BR_HTMLTagName(), // <br></br>
DIV_HTMLTagName(), // <div></div>
HR_HTMLTagName(), // <hr></hr>
I_HTMLTagName(), // <i></i>
LI_HTMLTagName(), // <li></li>
OL_HTMLTagName(), // <ol></ol>
P_HTMLTagName(), // <p></p>
SPAN_HTMLTagName(), // <span></span>
STRONG_HTMLTagName(), // <strong></strong>
U_HTMLTagName(), // <u></u>
UL_HTMLTagName(), // <ul></ul>
DEL_HTMLTagName(), // <del></del>
IMG_HTMLTagName(handler: ZNSTextAttachmentHandler), // <img> and image downloader
TR_HTMLTagName(), // <tr>
TD_HTMLTagName(), // <td>
TH_HTMLTagName(), // <th>
...and more
...
這樣解析 <a>
Tag 時就會套用到指定的 MarkupStyle。
擴充 HTMLTagName:
1
let zhgchgli = ExtendTagName("zhgchgli")
HTML Style Attribute
如同前述,HTML 支援從 Style Attribute 指定樣式,這邊也抽象出來可指定支援的樣式跟擴充,目前預定義的 HTML Style Attribute 如下:
1
2
3
4
5
6
7
ColorHTMLTagStyleAttribute(), // color
BackgroundColorHTMLTagStyleAttribute(), // background-color
FontSizeHTMLTagStyleAttribute(), // font-size
FontWeightHTMLTagStyleAttribute(), // font-weight
LineHeightHTMLTagStyleAttribute(), // line-height
WordSpacingHTMLTagStyleAttribute(), // word-spacing
...
擴充 Style Attribute:
1
2
3
4
5
6
7
8
9
ExtendHTMLTagStyleAttribute(styleName: "text-decoration", render: { value in
var newStyle = MarkupStyle()
if value == "underline" {
newStyle.underline = NSUnderlineStyle.single
} else {
// ...
}
return newStyle
})
使用
1
2
3
import ZMarkupParser
let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13)).build()
initWithDefault
會自動加入預先定義的 HTML Tag Name & 預設對應的 MarkupStyle 還有預先定義的 Style Attribute。
set(rootStyle:)
可指定整個字串的預設樣式,也可不指定。
客製化
1
2
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build() // will use markupstyle you specify to render extend html tag <zhgchgli></zhgchgli>
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build() // will use markupstyle you specify to render <b></b> instead of default bold markup style
HTML Render
1
2
3
4
5
6
let attributedString = parser.render(htmlString) // NSAttributedString
// work with UITextView
textView.setHtmlString(htmlString)
// work with UILabel
label.setHtmlString(htmlString)
HTML Stripper
1
parser.stripper(htmlString)
Selector HTML String
1
2
3
4
5
6
7
let selector = parser.selector(htmlString) // HTMLSelector e.g. input: <a><b>Test</b>Link</a>
selector.first("a")?.first("b").attributedString // will return Test
selector.filter("a").attributedString // will return Test Link
// render from selector result
let selector = parser.selector(htmlString) // HTMLSelector e.g. input: <a><b>Test</b>Link</a>
parser.render(selector.first("a")?.first("b"))
Async
另外如果要渲染長字串,可改用 async 方法,防止卡 UI。
1
2
3
parser.render(String) { _ in }...
parser.stripper(String) { _ in }...
parser.selector(String) { _ in }...
Know-how
- UITextView 中的超連結樣式是看 linkTextAttributes,所以會出現 NSAttributedString.key 明明有設定但卻沒出現效果的情況。
- UILabel 不支援指定 URL 樣式,所以會出現 NSAttributedString.key 明明有設定但卻沒出現效果的情況。
- 如果要渲染複雜的 HTML,還是需要使用 WKWebView (包含 JS/表格. .渲染)。
技術原理及開發故事:「 手工打造 HTML 解析器的那些事 」
歡迎貢獻及提出 Issue 將盡快修正
有任何問題及指教歡迎 與我聯絡 。
本文首次發表於 Medium ➡️ 前往查看,由 ZMediumToMarkdown 與 Medium-to-jekyll-starter 提供自動轉換與同步技術。