ZMarkupParser
ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.
Install / Use
/learn @ZhgChgLi/ZMarkupParserREADME

ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.
Technical Details:
- [Chinese] Development Notes
- [English] Development Notes (translated using ChatGPT)
- [English] Brief Introduction to Operation
Features
- [x] Parse HTML strings using pure-Swift and regular expressions.
- [x] Automatically correct invalid HTML strings, including mixed or isolated tags (e.g.,
<a>Link<b>LinkBold</a>Bold</b><br>-><a>Link<b>LinkBold</b></a><b>Bold</b><br/>). - [x] More compatible with HTML tags than a parser that is based on XMLParser.
- [x] Customizable HTML tag parser with painless extended tag support and the ability to customize tag styles.
- [x] Support for HTML rendering, stripping, and selecting.
- [x] Support for
<ul>list views,<table>table view,<img>image, also<hr>horizontal lines, and more. - [x] Support for parsing and setting styles from HTML tag attributes such as style="color:red".
- [x] Support for parsing HTML color names into UIColor/NSColor.
- [x] Better performance compared to
NSAttributedString.DocumentType.html. - [x] Fully test cases and test coverage.
Buy me a beer ❤️❤️❤️
If this project has helped you, feel free to sponsor me a cup of coffee, thank you.
Try it!

To run the ZMarkupParser demo, download the repository and open ZMarkupParser.xcworkspace. Then, select the ZMarkupParser-Demo target and run it to start exploring the library. Enjoy!
Performance Benchmark
(2022/M2/24GB Memory/macOS 13.2/XCode 14.1)
Note that rendering an NSAttributedString with the DocumentType.html option can cause a crash when the length of the HTML string exceeds 54,600+ characters. To avoid this issue, consider using ZMarkupParser instead.
The chart above shows the elapsed time (in seconds) to render different HTML string lengths (x). As you can see, ZMarkupParser performs better than NSAttributedString.DocumentType.html, especially for larger HTML strings.
Installation
Swift Package Manager
- File > Swift Packages > Add Package Dependency
- Add
https://github.com/ZhgChgLi/ZMarkupParser.git - Select "Up to Next Major" with "1.12.0"
or
...
dependencies: [
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.12.0"),
]
...
.target(
...
dependencies: [
"ZMarkupParser",
],
...
)
CocoaPods
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
target 'MyApp' do
pod 'ZMarkupParser', '~> 1.12.0'
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:
[
{tagStart: "a"},
{string: "Link"},
{tagStart: "b"},
{string: "LinkBold"},
{tagClose: "a"},
{string: "Bold"},
{tagClose: "b"}
]
- Traverse tag element array to autocorrect mixed tags and find isolated tags:
[
{tagStart: "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
| |--String("Link")
| |--B
| |--String("LinkBold")
|
|--B
|--String("Bold")
- Map tag to abstract Markup/MarkupStyle:
RootMarkup
|--A(underline=true)
| |--String("Link")(color=blue, font=13pt)
| |--B
| |--String("LinkBold")(color=blue, font=18pt, bold=true)
|
|--B(font=18pt, bold=true)
- 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
ZMarkupParser provides a set of pre-defined tag names that map to abstract markup classes, such as A_HTMLTagName() for <a></a>, B_HTMLTagName() for <b></b>, and so on. This mapping is used to create instances of the corresponding markup classes during the parsing process.
In addition, if there is a tag that is not defined or you want to customize your own tag, you can use the ExtendTagName(tagName: String) method to create a custom tag name and map it to an abstract markup class of your own design.
A_HTMLTagName(), // <a></a>
B_HTMLTagName(), // <b></b>
BR_HTMLTagName(), // <br></br> and also <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
MarkupStyle/MarkupStyleColor/MarkupStyleParagraphStyle
The MarkupStyle wrapper contains various properties that are used to define the attributes of an NSAttributedString. These properties includes:
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
...
For example, you can initialize or define a MarkupStyle object with the properties you want, such as setting the font size to 13 and the background color to aquamarine:
MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
HTMLTagStyleAttribute
These are pre-defined style attributes that can be used in the conversion of HTML tags to NSAttributedString attributes. Each style attribute has a corresponding class that defines its behavior and how it should be applied to the NSAttributedString.
ColorHTMLTagStyleAttribute(), // color
BackgroundColorHTMLTagStyleAttribute(), // background-color
FontSizeHTMLTagStyleAttribute(), // font-size
FontWeightHTMLTagStyleAttribute(), // font-weight
LineHeightHTMLTagStyleAttribute(), // line-height
WordSpacingHTMLTagStyleAttribute(), // word-spacing
If there is a style attribute that is not defined, the ExtendHTMLTagStyleAttribute class can be used to define it. This class takes in a style name and a closure that takes in an existing style and the value of the new style attribute and returns a new style with the new attribute applied.
For exmaple: style="text-decoration"
ExtendHTMLTagStyleAttribute(styleName: "text-decoration", render: { fromStyle, value in
var newStyle = fromStyle
if value == "underline" {
newStyle.underline = NSUnderlineStyle.single
} else {
// ...
}
return newStyle
})
Usage
import ZMarkupParser
Builder Pattern to Build Parser
let parser = ZHTMLParserBuilder.initWithDefault().set(rootStyle: MarkupStyle(font: MarkupStyleFont(size: 13)).build()
The code initializes a new ZHTMLParserBuilder object with default settings using the initWithDefault() method. This method adds all pre-defined HTML tag
Related Skills
node-connect
344.1kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
96.8kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
344.1kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
344.1kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。

