将 HTML 字符串转换为具有自定义样式和标记的 NSAttributedString
ZMarkupParser 帮助您通过 pure-Swift 将 HTML 字符串转换为具有自定义样式和标签的 NSAttributedString。
特征
- 使用 pure-Swift 通过 Rexgex 解析 HTML 字符串。
- 自动更正无效的 HTML 字符串,包括混合或隔离标记。(例如
<a>Link<b>LinkBold</a>Bold</b><br>
-><a>Link<b>LinkBold</b></a><b>Bold</b><br/>
) - 无痛的扩展HTML标签解析器和您想要的自定义HTML标签样式。
- Support HTML Render/HTML Stripper/HTML Selector
- Support list view and horizontal line…etc
<ul><ol>
<hr>
- Support parse & set style from html tag attributes.
style="color:red"
- Support parse HTML Color name to UIColor/NSColor.
- Higher performance than
NSAttributedString.DocumentType.html
Try it!
downloaded repo and open , select target to run it! have fun!ZMarkupParser.xcworkspace
ZMarkupParser-Demo
Performance Benchmark
(2022/M2/24GB Memory/macOS 13.2/XCode 14.1)
NSAttributedString.DocumentType.html
will crashed when render more than 54,600+ length of string.
- x: html string length
- y: time elapsed(second)
Installation
Swift Package Manager
- File > Swift Packages > Add Package Dependency
- Add
https://github.com/ZhgChgLi/ZMarkupParser.git
- Select “Up to Next Major” with “1.1.6”
or
...
dependencies: [
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.1.6"),
]
...
.target(
...
dependencies: [
"ZMarkupParser",
],
...
)
CocoaPods
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
target 'MyApp' do
pod 'ZMarkupParser', '~> 1.1.6'
end
How it works? (explain with Pseudocode)
- Input html string:
<a>Link<b>LinkBold</a>Bold</b>
- Convert string to array of tag element through Regex:
[{tagStrat: a}, {string: Link}, {tagStart: b}, {string: LinkBold}, {tagClose: a}, {string: Bold}, {tagClose: b}]
- Traversal tag element array to autocorrent mixed tag and find isolated tag:
[{tagStrat: a}, {string: Link}, {tagStart: b}, {string: LinkBold}, {tagClose: b}, {tagClose: a}, {tagStart: b}, {string: Bold}, {tagClose: b}]
- Convert tag element array to abstract syntax tree:
// RootMarkup
// / \
// A B
// / \ |
// String("Link") B String("Bold")
// |
// String("LinkBold")
//
- Mapping tag to abstract Markup/MarkupStyle
- Use Visitor Pattern to visit every tree leaf Markup/MarkupStyle and combine it to NSAttributedString through recursion.
- Result:
Link{
NSColor = "UIExtendedSRGBColorSpace 0 0.478431 1 1";
NSFont = "<UICTFont: 0x145d17600> font-family: \".SFUI-Regular\"; font-weight: normal; font-style: normal; font-size: 13.00pt";
NSUnderline = 1;
}LinkBold{
NSColor = "UIExtendedSRGBColorSpace 0 0.478431 1 1";
NSFont = "<UICTFont: 0x145d18710> font-family: \".SFUI-Semibold\"; font-weight: bold; font-style: normal; font-size: 18.00pt";
NSUnderline = 1;
}Bold{
NSFont = "<UICTFont: 0x145d18710> font-family: \".SFUI-Semibold\"; font-weight: bold; font-style: normal; font-size: 18.00pt";
}
Example
Introduction
HTMLTagName
Abstract of html tag, there is some pre-defined html tag name down below.
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>
...
If there is HTML tag not be defined or customized tag, you could use to wrapped it.ExtendTagName("tagName")
MarkupStyle/MarkupStyleColor/MarkupStyleParagraphStyle
Wrapper of because of inheritable puerpose.NSAttributedStrin.Key
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
...
You could init or define MarkupStyle you want.
MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
HTMLTagStyleAttribute
Abstract of html style attributes, there is some pre-defined attributes down below.
ColorHTMLTagStyleAttribute(), // color
BackgroundColorHTMLTagStyleAttribute(), // background-color
FontSizeHTMLTagStyleAttribute(), // font-size
FontWeightHTMLTagStyleAttribute(), // font-weight
LineHeightHTMLTagStyleAttribute(), // line-height
WordSpacingHTMLTagStyleAttribute(), // word-spacing
If there is Style attribute not be defined, you could use to wrapped it.ExtendHTMLTagStyleAttribute("styleAttributeName", MarkupStyle)
Usage
import ZMarkupParser
Builder Pattern to Build Parser
let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13)).build()
initWithDefault()
will add all pre-defined HTMLTagName/HTMLTagStyleAttribute and use Tag’s default MarkupStyle to render.
set(rootStyle:MarkupStyle)
specify default root style to render, will apply to whole attributed string.
build()
call build() at the end, to generate parser object.
Customized Tag Style/Extend Tag Name
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))) // will use markupstyle you specify to render <b></b> instead of default bold markup style
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))) // will use markupstyle you specify to render extend html tag <zhgchgli></zhgchgli>
Parse HTML String
parser.render(htmlString) // NSAttributedString
// work with UITextView
textView.setHtmlString(htmlString)
// work with UILabel
label.setHtmlString(htmlString)
Stripper HTML String
parser.stripper(htmlString) // NSAttributedString
Selector HTML String
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
Selector+Render HTML String
let selector = parser.selector(htmlString) // HTMLSelector e.g. input: <a><b>Test</b>Link</a>
parser.render(selector.first("a")?.first("b"))
With Async
parser.render(String) { _ in }...
parser.stripper(String) { _ in }...
parser.selector(String) { _ in }...
If you want to render huge html string, please use async instead.
Things to know
- Unsupport Parse to NSTextAttacment currently, due to need async task & native textview with NSTextAttacment didn’t impletation reuse, insert image throught NSTextAttacment to TextView will lead to Out of memory.
<img>
- Need to set for UITextView to change .link style
linkTextAttributes
- UILabel is not allow to change .link text color style throught NSAttributedString.key.foregroundColor
- Due to limitation, colud only use this parser render in partial html, do not use in redner whole huge html (please use webview.loadhtml instead.)
Made In Taiwan 🇹🇼🇹🇼🇹🇼
If this is helpful, please help to star the repo or recommend it to your friends.
Please feel free to open an Issue or submit a fix/contribution via Pull Request. 🙂