ZMarkupParser|Convert HTML String to NSAttributedString with Custom Style Keys
Developers facing challenges in converting HTML strings to NSAttributedString can utilize ZMarkupParser to apply precise style key mappings, ensuring accurate and customizable text rendering in iOS applications.
点击这里查看本文章简体中文版本。
點擊這裡查看本文章正體中文版本。
This post was translated with AI assistance — let me know if anything sounds off!
ZMarkupParser HTML String to NSAttributedString Converter Tool
Convert HTML String to NSAttributedString with Corresponding Key Style Settings
ZhgChgLi / ZMarkupParser
Features
Developed purely in Swift, it uses Regex to parse HTML tags and tokenize them. It analyzes and corrects tag accuracy (fixing tags without an end and misaligned tags), then converts them into an abstract syntax tree. Finally, it uses the Visitor Pattern to map HTML tags to abstract styles, producing the final NSAttributedString result. No parser libraries are used.
Supports HTML Render (to NSAttributedString) / Stripper (removes HTML tags) / Selector functions
Automatic Analysis and Correction of Tag Accuracy (Fix Missing End Tags & Misplaced Tags)
<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)Support custom style specification
e.g.<b></b>
->weight: .semibold & underline: 1
Support custom HTML tag parsing
e.g. Parse<zhgchgli></zhgchgli>
into the desired styleIncludes architecture design to facilitate expansion of HTML Tag support
Currently supports basic styles as well as ul/ol/li lists and hr divider rendering. Future expansions will allow quick support for other HTML Tags.Support extending style parsing from the
style
HTML Attribute
HTML allows text styles to be specified via the style attribute. Similarly, this package also supports styles defined in thestyle
attribute.
e.g.<b style=”font-size: 20px”></b>
->bold + font size 20 px
Support iOS/macOS
Support HTML Color Name to UIColor/NSColor
Test Coverage: 80%+
Supports parsing of
<img>
images,<ul>
item lists,<table>
tables, and other HTML tags.Higher Performance than
NSAttributedString.DocumentType.html
Performance Analysis
Test Environment: 2022/M2/24GB Memory/macOS 13.2/XCode 14.1
X Axis: Number of HTML Characters
Y-axis: Rendering Time (seconds)
*Additionally, NSAttributedString.DocumentType.html
will crash (EXC_BAD_ACCESS) with strings longer than 54,600+ characters.
Try it out
You can directly download the project, open ZMarkupParser.xcworkspace
, select the ZMarkupParser-Demo
target, then build and run to test the functionality.
Installation
Supports SPM/Cocoapods, please refer to Readme.
How to Use
Style Declaration
MarkupStyle/MarkupStyleColor/MarkupStyleParagraphStyle are wrappers for 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
...
You can declare styles according to the HTML tags you want to apply them to:
1
let myStyle = MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
HTML Tag
Declare the HTML tags to render and their corresponding markup styles. The currently predefined HTML tag names are as follows:
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
...
This way, when parsing the <a>
tag, the specified MarkupStyle will be applied.
Extend HTMLTagName:
1
let zhgchgli = ExtendTagName("zhgchgli")
HTML Style Attribute
As mentioned earlier, HTML supports specifying styles via the Style Attribute. Here, we also abstract the supported styles and extensions. The currently predefined HTML Style Attributes are as follows:
1
2
3
4
5
6
7
ColorHTMLTagStyleAttribute(), // color
BackgroundColorHTMLTagStyleAttribute(), // background-color
FontSizeHTMLTagStyleAttribute(), // font-size
FontWeightHTMLTagStyleAttribute(), // font-weight
LineHeightHTMLTagStyleAttribute(), // line-height
WordSpacingHTMLTagStyleAttribute(), // word-spacing
...
Extend 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
})
Usage
1
2
3
import ZMarkupParser
let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13))).build()
initWithDefault
automatically adds predefined HTML Tag Names, default corresponding MarkupStyles, and predefined Style Attributes.
set(rootStyle:)
can set the default style for the entire string, or it can be left unspecified.
Customization
1
2
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build() // will use the markup style you specify to render the extended HTML tag <zhgchgli></zhgchgli>
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build() // will use the markup style you specify to render <b></b> instead of the 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
Additionally, if rendering long strings, you can use async methods to prevent UI blocking.
1
2
3
parser.render(String) { _ in }...
parser.stripper(String) { _ in }...
parser.selector(String) { _ in }...
Know-how
The hyperlink style in UITextView depends on linkTextAttributes, so there may be cases where NSAttributedString.Key is set but has no visible effect.
UILabel does not support specifying URL styles, so NSAttributedString.key may be set but have no visible effect.
If you need to render complex HTML, you still need to use WKWebView (including JS/tables rendering).
Technical Principles and Development Story: “The Story of Manually Building an HTML Parser”
Welcome Contributions and Issue Reporting for Prompt Fixes
If you have any questions or feedback, feel free to contact me.
This post was originally published on Medium (View original post), and automatically converted and synced by ZMediumToMarkdown.