ZMarkupParser

awesome

ZMarkupParser 是一个纯 Swift 库,可帮助您使用自定义样式和标签将 HTML 字符串转换为 NSAttributedString。

技术细节

特性

请我喝杯咖啡 ❤️❤️❤️

bmc-button

如果这个项目帮助了您,请随时赞助我一杯咖啡,谢谢。

试试看!

Simulator Screen Recording - iPhone 14 Pro - 2023-03-09 at 23 38 25

要运行 ZMarkupParser 演示,请下载存储库并打开 ZMarkupParser.xcworkspace。然后,选择 ZMarkupParser-Demo 目标并运行它以开始探索该库。尽情享用!

性能基准测试

Performance Benchmark

(2022/M2/24GB 内存/macOS 13.2/XCode 14.1)

请注意,当 HTML 字符串的长度超过 54,600+ 个字符时,使用 DocumentType.html 选项渲染 NSAttributedString 可能会导致崩溃。 为避免此问题,请考虑改用 ZMarkupParser。

上图显示了渲染不同 HTML 字符串长度 (x) 所用的时间(以秒为单位)。 可以看出,ZMarkupParser 的性能优于 NSAttributedString.DocumentType.html,尤其是对于较大的 HTML 字符串。

安装

Swift Package Manager

或者

...
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

它是如何工作的?(用伪代码解释)

  1. 输入 html 字符串: <a>Link<b>LinkBold</a>Bold</b>
  2. 通过正则表达式将字符串转换为标签元素数组
[
  {tagStart: "a"},
  {string: "Link"},
  {tagStart: "b"},
  {string: "LinkBold"},
  {tagClose: "a"},
  {string: "Bold"},
  {tagClose: "b"}
]
  1. 遍历标签元素数组以自动更正混合标签并查找孤立标签
[
  {tagStart: "a"},
  {string: "Link"},
  {tagStart: "b"},
  {string: "LinkBold"},
  {tagClose: "b"},
  {tagClose: "a"},
  {tagStart: "b"},
  {string: "Bold"},
  {tagClose: "b"}
]
  1. 将标签元素数组转换为抽象语法树
RootMarkup
|--A
|  |--String("Link")
|  |--B
|     |--String("LinkBold")
|
|--B
   |--String("Bold")
  1. 将标签映射到抽象 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)
  1. 使用访问者模式来访问每个树叶 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";
}

示例

ZMarkupParser Exmple

介绍

HTMLTagName

ZMarkupParser 提供了一组预定义的标签名称,这些标签名称映射到抽象的 markup 类,例如 A_HTMLTagName() 用于 ,B_HTMLTagName() 用于 ,等等。 此映射用于在解析过程中创建相应的 markup 类的实例。

此外,如果存在未定义的标签或者您想自定义自己的标签,可以使用 ExtendTagName(tagName: String) 方法创建自定义标签名称,并将其映射到您自己设计的抽象 markup 类。

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() 方法以生成解析器对象。

自定义列表项标签 (ul/ol)

let parser = ZHTMLParserBuilder.initWithDefault().add(OL_HTMLTagName(), withCustomStyle: MarkupStyle(paragraphStyle: MarkupStyleParagraphStyle(textListStyleType: .circle, textListHeadIndent: 4, textListIndent: 8))).build()

ZhgChgLi-2024-05-30_23-25-57

自定义标签样式/扩展标签名称

这些代码片段演示了如何自定义标签的样式或扩展标签名称

要自定义标签的样式,可以使用 ZHTMLParserBuilder 类的 add 方法,并提供 HTMLTagName 的实例和 MarkupStyle 对象作为参数。 例如,以下代码片段将使用自定义 markup 样式来渲染 标签

let parser = ZHTMLParserBuilder.initWithDefault().add(B_HTMLTagName(), withCustomStyle: MarkupStyle(font: MarkupStyleFont(size: 18, weight: .style(.semibold)))).build()

要扩展标签名称并自定义其样式,可以使用 ExtendTagName 类和 ZHTMLParserBuilder 类的 add 方法。 例如,以下代码片段会将标签名称扩展为 并使用自定义 markup 样式来渲染它

let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build()

支持类/ID 样式映射和解析

class HTML 属性可以使用 HTMLTagClassAttribute 来定义具有预定义样式的 classNames。

HTML 允许指定多个以空格分隔的 class 属性,但是 id 属性每个 HTML 标签只能分配一个值。

例如

<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()

渲染 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 字符串,请改用异步。

须知

赞助商

谁在使用

pinkoi

Pinkoi.com 是亚洲领先的原创设计商品、数字创作和工作坊体验在线市场。


关于

其他作品

Swift 库

集成工具

捐赠

Buy Me A Coffe

如果您觉得此库有帮助,请考虑为该 repo 点赞或将其推荐给您的朋友。

欢迎提出 issue 或通过拉取请求提交修复/贡献。:)