ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.
ZMarkupParser is a pure-Swift library that helps you convert HTML strings into NSAttributedString with customized styles and tags.
<a>Link<b>LinkBold</a>Bold</b><br>
-> <a>Link<b>LinkBold</b></a><b>Bold</b><br/>
).<ul>
list views, <table>
table view, <img>
image, also <hr>
horizontal lines, and more.NSAttributedString.DocumentType.html
.If this project has helped you, feel free to sponsor me a cup of coffee, thank you.
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!
(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.
https://github.com/ZhgChgLi/ZMarkupParser.git
or
...
dependencies: [
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.11.0"),
]
...
.target(
...
dependencies: [
"ZMarkupParser",
],
...
)
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
target 'MyApp' do
pod 'ZMarkupParser', '~> 1.11.0'
end
<a>Link<b>LinkBold</a>Bold</b>
[
{tagStart: "a"},
{string: "Link"},
{tagStart: "b"},
{string: "LinkBold"},
{tagClose: "a"},
{string: "Bold"},
{tagClose: "b"}
]
[
{tagStart: "a"},
{string: "Link"},
{tagStart: "b"},
{string: "LinkBold"},
{tagClose: "b"},
{tagClose: "a"},
{tagStart: "b"},
{string: "Bold"},
{tagClose: "b"}
]
RootMarkup
|--A
| |--String("Link")
| |--B
| |--String("LinkBold")
|
|--B
|--String("Bold")
RootMarkup
|--A(underline=true)
| |--String("Link")(color=blue, font=13pt)
| |--B
| |--String("LinkBold")(color=blue, font=18pt, bold=true)
|
|--B(font=18pt, bold=true)
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";
}
ZMarkupParser provides a set of pre-defined tag names that map to abstract markup classes, such as A_HTMLTagName() for , B_HTMLTagName() for , 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
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))
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
})
import ZMarkupParser
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 names and style attributes, and sets the tag’s default MarkupStyle to render.
Then, the set(rootStyle: MarkupStyle)
method is called to specify the default root style to render. This root style will be applied to the entire attributed string that is generated by the parser.
Finally, the build()
method is called at the end to generate the parser object.
ul/ol
)let parser = ZHTMLParserBuilder.initWithDefault().add(OL_HTMLTagName(), withCustomStyle: MarkupStyle(paragraphStyle: MarkupStyleParagraphStyle(textListStyleType: .circle, textListHeadIndent: 4, textListIndent: 8))).build()
These code snippets demonstrate how to customize the style of a tag or extend the tag name:
To customize the style of a tag, you can use the add method of the ZHTMLParserBuilder class and provide an instance of HTMLTagName and a MarkupStyle object as parameters. For example, the following code snippet will use a custom markup style to render the tag:
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build()
To extend the tag name and customize its style, you can use the ExtendTagName class and the add method of the ZHTMLParserBuilder class. For example, the following code snippet will extend the tag name to
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build()
The class HTML attribute can use the HTMLTagClassAttribute to define classNames with pre-defined styles.
HTML allows specifying multiple class
attributes separated by spaces, but the id
attribute can only be assigned a single value per HTML tag.
e.g.:
<span id="header">hey</span>hey <span id="text-red text-small">Teste de texto text small</span> hey<span class="text-red">hey</span>heyhey
let parser = ZHTMLParserBuilder.initWithDefault().add(HTMLTagClassAttribute(className: "text-red", render: {
return MarkupStyle(foregroundColor: MarkupStyleColor(color: .red))
})).add(HTMLTagClassAttribute(className: "text-small", render: {
return MarkupStyle(font: MarkupStyleFont(.systemFont(ofSize: 6)))
})).add(HTMLTagIdAttribute(idName: "header", render: {
return MarkupStyle(font: MarkupStyleFont(.systemFont(ofSize: 36)))
})).build()
parser.render(htmlString) // NSAttributedString
By default, ZMarkupParser will decode HTML entities using the [HTMLString](https://github.com/alexisakers/HTMLString) library.
If you wish to keep the raw data clean, you can disable this feature by using the following syntax: `parser.render(htmlString, forceDecodeHTMLEntities: false)` or `setHtmlString(attributedString, with: parser, forceDecodeHTMLEntities: false)`.
// work with UITextView
textView.setHtmlString(htmlString)
// work with UILabel
label.setHtmlString(htmlString)
parser.stripper(htmlString) // NSAttributedString
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").get() // will return dict struct
selector.filter("a") // will return json string of dict
let selector = parser.selector(htmlString) // HTMLSelector e.g. input: <a><b>Test</b>Link</a>
parser.render(selector.first("a")?.first("b"))
parser.render(String) { _ in }...
parser.stripper(String) { _ in }...
parser.selector(String) { _ in }...
If you want to render huge html string, please use async instead.
MarkupStyleColor(sponsor: .pinkoi(.navy))
.Pinkoi.com is Asia’s leading online marketplace for original design goods, digital creations, and workshop experiences.
MarkupStyleVendorColor(vendor: .pinkoi(.navy))
.If you find this library helpful, please consider starring the repo or recommending it to your friends.
Feel free to open an issue or submit a fix/contribution via pull request. 😃