ZMarkupParser 是一个纯 Swift 库,帮助您将 HTML 字符串转换为带自定义样式和标签的 NSAttributedString。
技术细节
特性
- 使用纯 Swift 和正则表达式解析 HTML 字符串。
- 自动修正无效的 HTML 字符串,包括混合或孤立的标签(例如,
<a>链接<b>加粗链接</a>加粗</b><br>
-><a>链接<b>加粗链接</b></a><b>加粗</b><br/>
)。 - 与基于 XMLParser 的解析器相比,更兼容 HTML 标签。
- 可定制的 HTML 标签解析器,支持无缝扩展标签支持,并支持自定义标签样式。
- 支持 HTML 渲染、剥离和选择。
- 支持
<ul>
列表视图,<table>
表格视图,<img>
图片,也支持<hr>
水平线等。 - 支持解析和设置来自 HTML 标签属性(如 style="color:red")的样式。
- 支持将 HTML 颜色名称解析为 UIColor/NSColor。
- 比
NSAttributedString.DocumentType.html
有更好的性能。 - 完整的测试用例和测试覆盖率。
试一试!
要运行ZMarkupParser演示,请下载仓库并打开ZMarkupParser.xcworkspace。然后选择ZMarkupParser-Demo目标并运行以开始探索库。享受!
性能基准
(2022年2月/24GB内存/macOS 13.2/XCode 14.1)
请注意,使用DocumentType.html选项渲染NSAttributedString可能会导致当HTML字符串长度超过54,600+字符时崩溃。为了避免此问题,请考虑使用ZMarkupParser。
上面的图表显示了渲染不同HTML字符串长度(x)的耗时(秒)。如你所见,ZMarkupParser的性能优于NSAttributedString.DocumentType.html,特别是对于较长的HTML字符串。
安装
Swift包管理器
- 文件 > Swift包 > 添加包依赖
- 添加
https://github.com/ZhgChgLi/ZMarkupParser.git
- 选择 "升级至下一主要版本" 并设置为 "1.5.0"
或者
...
dependencies: [
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.5.0"),
]
...
.target(
...
dependencies: [
"ZMarkupParser",
],
...
)
CocoaPods
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, '12.0'
use_frameworks!
target 'MyApp' do
pod 'ZMarkupParser', '~> 1.5.0'
end
如何工作?(用伪代码解释)
- 输入 HTML 字符串:
<a>链接<b>加粗链接</a>加粗</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")
- 将标签映射到抽象的 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)
- 使用访问者模式遍历每个树叶 Markup/MarkupStyle 并通过递归将其组合到 NSAttributedString 中。
结果
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";
}
示例
简介
HTML 标签名
ZMarkupParser 提供了一组预定义的标签名称,这些名称映射到抽象标记类,例如 A_HTMLTagName() 对应于空标签,B_HTMLTagName() 对应于 标签,以此类推。这种映射用于在解析过程中创建对应标记类的实例。
此外,如果有一个未定义的标签或者您想自定义自己的标签,您可以使用 ExtendTagName(tagName: String)
方法创建一个自定义标签名称并将其映射到您自己设计的抽象标记类。
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
MarkupStyle 包装器包含各种属性,用于定义 NSAttributedString 的属性。这些属性包括:
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
...
例如,您可以初始化或定义一个 MarkupStyle 对象,设置您想要的属性,例如将字体大小设置为 13,背景颜色设置为碧绿色。
MarkupStyle(font: MarkupStyleFont(size: 13), backgroundColor: MarkupStyleColor(name: .aquamarine))
HTMLTagStyleAttribute
这些是预定义的样式属性,可以在 HTML 标签到 NSAttributedString 属性的转换中使用。每个样式属性都有一个对应的类,定义其行为以及如何将其应用于 NSAttributedString。
ColorHTMLTagStyleAttribute(), // color
BackgroundColorHTMLTagStyleAttribute(), // background-color
FontSizeHTMLTagStyleAttribute(), // font-size
FontWeightHTMLTagStyleAttribute(), // font-weight
LineHeightHTMLTagStyleAttribute(), // line-height
WordSpacingHTMLTagStyleAttribute(), // word-spacing
如果有一个未定义的样式属性,可以使用 ExtendHTMLTagStyleAttribute 类来定义它。此类接受一个样式名称和一个闭包,该闭包接收一个现有的样式和新样式属性值,然后返回一个应用了新属性的新的样式。
例如: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()
代码使用initWithDefault()
方法初始化一个新的ZHTMLParserBuilder对象并设置默认设置。该方法添加所有预定义的HTML标签名称和样式属性,并设置标签的默认MarkupStyle以进行渲染。
然后,调用set(rootStyle: MarkupStyle)
方法以指定要渲染的默认根样式。这种根样式将应用于解析器生成的整个属性字符串。
最后,在末尾调用build()
方法生成解析器对象。
自定义标签样式/扩展标签名称
以下代码片段演示了如何自定义标签样式或扩展标签名称
要自定义标签样式,您可以使用ZHTMLParserBuilder类的add方法,并传递HTMLTagName实例和MarkupStyle对象作为参数。例如,以下代码片段将使用自定义的MarkupStyle渲染标签。
let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build()
要扩展标签名称并自定义其样式,您可以使用ExtendTagName类和ZHTMLParserBuilder类的add方法。例如,以下代码片段将扩展标签名称并使用自定义的MarkupStyle渲染它。
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build()
渲染HTML字符串
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)
去除HTML字符串
parser.stripper(htmlString) // NSAttributedString
选择器 HTML 字符串
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
选择器+渲染 HTML 字符串
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 }...
如果你需要渲染很大的 HTML 字符串,请使用异步方式。
需要注意的事项
- 要更改 UITextView 中链接的样式,需要将 linkTextAttributes 属性设置为一个包含期望样式属性的 NSAttributedString.Key 值。
- 如果你使用 UILabel 渲染格式化字符串,请注意,你不能使用 NSAttributedString.Key.foregroundColor 属性更改 .link 文本的颜色。
- ZHTMLParser 库旨在渲染部分 HTML 内容,可能不适用于渲染非常大的或复杂的 HTML 文档。对于这些用例,最好使用网页视图来渲染 HTML 内容。
使用者是谁
Pinkoi.com 是亚洲领先的原创设计商品、数字创作和工作坊体验的在线市场。
关于
其他作品
Swift库
- ZMarkupParser 是一个纯Swift库,帮助您将HTML字符串转换为具有自定义样式和标签的NSAttributedString。
- ZPlayerCacher 是AVAssetResourceLoaderDelegate协议的一个轻量级实现,使AVPlayerItem能够支持缓存流媒体文件。
- ZNSTextAttachment 允许NSTextAttachment从远程URL下载图片,支持UITextView和UILabel。
集成工具
- ZReviewTender 是一个从 App Store 和 Google Play 控制台抓取应用评论并将其集成到您的工作流程中的工具。
- ZMediumToMarkdown 是一个强大的工具,可以让您轻松地将 Medium 文章下载并转换为 Markdown 格式。
捐助
如果您觉得这个库很有用,请考虑给它点星或向您的朋友推荐。
随时可以打开一个问题或通过拉取请求提交修复/贡献。 :)