Skip to content

Commit 4308b71

Browse files
authored
Merge pull request #70 from ZhgChgLi/feature/add-suport-class-and-id
Feature/add suport class and
2 parents 0b7f6f0 + 9bf01a0 commit 4308b71

11 files changed

+167
-14
lines changed

README.md

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,14 +48,14 @@ The chart above shows the elapsed time (in seconds) to render different HTML str
4848

4949
- File > Swift Packages > Add Package Dependency
5050
- Add `https://github.com/ZhgChgLi/ZMarkupParser.git`
51-
- Select "Up to Next Major" with "1.9.4"
51+
- Select "Up to Next Major" with "1.10.0"
5252

5353
or
5454

5555
```swift
5656
...
5757
dependencies: [
58-
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.9.4"),
58+
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.10.0"),
5959
]
6060
...
6161
.target(
@@ -74,7 +74,7 @@ platform :ios, '12.0'
7474
use_frameworks!
7575

7676
target 'MyApp' do
77-
pod 'ZMarkupParser', '~> 1.9.4'
77+
pod 'ZMarkupParser', '~> 1.10.0'
7878
end
7979
```
8080

@@ -275,6 +275,27 @@ To extend the tag name and customize its style, you can use the ExtendTagName cl
275275
let parser = ZHTMLParserBuilder.initWithDefault().add(ExtendTagName("zhgchgli"), withCustomStyle: MarkupStyle(backgroundColor: MarkupStyleColor(name: .aquamarine))).build()
276276
```
277277

278+
####Support for Class/ID Style Mapping and Parsing
279+
280+
The class HTML attribute can use the HTMLTagClassAttribute to define classNames with pre-defined styles.
281+
282+
HTML allows specifying multiple `class` attributes separated by spaces, but the `id` attribute can only be assigned a single value per HTML tag.
283+
284+
e.g.:
285+
```
286+
<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
287+
```
288+
289+
```
290+
let parser = ZHTMLParserBuilder.initWithDefault().add(HTMLTagClassAttribute(className: "text-red", render: {
291+
return MarkupStyle(foregroundColor: MarkupStyleColor(color: .red))
292+
})).add(HTMLTagClassAttribute(className: "text-small", render: {
293+
return MarkupStyle(font: MarkupStyleFont(.systemFont(ofSize: 6)))
294+
})).add(HTMLTagIdAttribute(idName: "header", render: {
295+
return MarkupStyle(font: MarkupStyleFont(.systemFont(ofSize: 36)))
296+
})).build()
297+
```
298+
278299
### Render HTML String
279300
```swift
280301
parser.render(htmlString) // NSAttributedString
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// HTMLTagClassAttribute.swift
3+
//
4+
//
5+
// Created by zhgchgli on 2024/6/14.
6+
//
7+
8+
import Foundation
9+
10+
public struct HTMLTagClassAttribute {
11+
public let className: String
12+
public let render: (() -> (MarkupStyle?))
13+
14+
public init(className: String, render: @escaping (() -> (MarkupStyle?))) {
15+
self.className = className
16+
self.render = render
17+
}
18+
19+
func isEqualTo(className: String) -> Bool {
20+
return self.className.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == className.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
21+
}
22+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//
2+
// HTMLTagIdAttribute.swift
3+
//
4+
//
5+
// Created by zhgchgli on 2024/6/14.
6+
//
7+
8+
import Foundation
9+
10+
public struct HTMLTagIdAttribute {
11+
public let idName: String
12+
public let render: (() -> (MarkupStyle?))
13+
14+
public init(idName: String, render: @escaping (() -> (MarkupStyle?))) {
15+
self.idName = idName
16+
self.render = render
17+
}
18+
19+
func isEqualTo(idName: String) -> Bool {
20+
return self.idName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == idName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
21+
}
22+
}

Sources/ZMarkupParser/HTML/Processor/HTMLElementMarkupComponentMarkupStyleVisitor.swift

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ struct HTMLElementMarkupComponentMarkupStyleVisitor: MarkupVisitor {
2424
let policy: MarkupStylePolicy
2525
let components: [HTMLElementMarkupComponent]
2626
let styleAttributes: [HTMLTagStyleAttribute]
27+
let classAttributes: [HTMLTagClassAttribute]
28+
let idAttributes: [HTMLTagIdAttribute]
29+
2730
let rootStyle: MarkupStyle?
2831

2932
func visit(_ markup: RootMarkup) -> Result {
@@ -245,6 +248,45 @@ extension HTMLElementMarkupComponentMarkupStyleVisitor {
245248
markupStyle = customStyle
246249
}
247250

251+
// id
252+
if let idString = htmlElement?.attributes?["id"],
253+
let idAttribute = idAttributes.first(where: { $0.isEqualTo(idName: idString) }),
254+
var thisMarkupStyle = idAttribute.render() {
255+
switch policy {
256+
case .respectMarkupStyleFromCode:
257+
if var markupStyle = markupStyle {
258+
markupStyle.fillIfNil(from: thisMarkupStyle)
259+
} else {
260+
markupStyle = thisMarkupStyle
261+
}
262+
case .respectMarkupStyleFromHTMLStyleAttribute:
263+
thisMarkupStyle.fillIfNil(from: markupStyle ?? defaultStyle)
264+
markupStyle = thisMarkupStyle
265+
}
266+
}
267+
// class
268+
if let classString = htmlElement?.attributes?["class"],
269+
classAttributes.count > 0 {
270+
let classNames = classString.split(separator: " ").filter { $0.trimmingCharacters(in: .whitespacesAndNewlines) != "" }
271+
272+
for className in classNames {
273+
if let classAttribute = classAttributes.first(where: { $0.isEqualTo(className: String(className)) }),
274+
var thisMarkupStyle = classAttribute.render() {
275+
switch policy {
276+
case .respectMarkupStyleFromCode:
277+
if var markupStyle = markupStyle {
278+
markupStyle.fillIfNil(from: thisMarkupStyle)
279+
} else {
280+
markupStyle = thisMarkupStyle
281+
}
282+
case .respectMarkupStyleFromHTMLStyleAttribute:
283+
thisMarkupStyle.fillIfNil(from: markupStyle ?? defaultStyle)
284+
markupStyle = thisMarkupStyle
285+
}
286+
}
287+
}
288+
}
289+
// style
248290
if let styleString = htmlElement?.attributes?["style"],
249291
styleAttributes.count > 0 {
250292
let styles = styleString.split(separator: ";").filter { $0.trimmingCharacters(in: .whitespacesAndNewlines) != "" }.map { $0.split(separator: ":") }

Sources/ZMarkupParser/HTML/Processor/HTMLElementWithMarkupToMarkupStyleProcessor.swift

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,22 @@ final class HTMLElementWithMarkupToMarkupStyleProcessor: ParserProcessor {
1212
typealias To = [MarkupStyleComponent]
1313

1414
let styleAttributes: [HTMLTagStyleAttribute]
15+
let classAttributes: [HTMLTagClassAttribute]
16+
let idAttributes: [HTMLTagIdAttribute]
17+
1518
let policy: MarkupStylePolicy
1619
let rootStyle: MarkupStyle?
17-
init(styleAttributes: [HTMLTagStyleAttribute], policy: MarkupStylePolicy, rootStyle: MarkupStyle?) {
20+
init(styleAttributes: [HTMLTagStyleAttribute], classAttributes: [HTMLTagClassAttribute], idAttributes: [HTMLTagIdAttribute], policy: MarkupStylePolicy, rootStyle: MarkupStyle?) {
1821
self.styleAttributes = styleAttributes
22+
self.classAttributes = classAttributes
23+
self.idAttributes = idAttributes
1924
self.policy = policy
2025
self.rootStyle = rootStyle
2126
}
2227

2328
func process(from: From) -> To {
2429
var components: [MarkupStyleComponent] = []
25-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: policy, components: from.1, styleAttributes: styleAttributes, rootStyle: rootStyle)
30+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: policy, components: from.1, styleAttributes: styleAttributes, classAttributes: classAttributes, idAttributes: idAttributes, rootStyle: rootStyle)
2631
walk(markup: from.0, visitor: visitor, components: &components)
2732
return components
2833
}

Sources/ZMarkupParser/HTML/ZHTMLParser.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ public final class ZHTMLParser {
2323
init(
2424
htmlTags: [HTMLTag],
2525
styleAttributes: [HTMLTagStyleAttribute],
26+
classAttributes: [HTMLTagClassAttribute],
27+
idAttributes: [HTMLTagIdAttribute],
2628
policy: MarkupStylePolicy,
2729
rootStyle: MarkupStyle?
2830
) {
@@ -33,7 +35,7 @@ public final class ZHTMLParser {
3335
self.markupRenderProcessor = MarkupRenderProcessor(rootStyle: rootStyle)
3436

3537
self.htmlParsedResultToHTMLElementWithRootMarkupProcessor = HTMLParsedResultToHTMLElementWithRootMarkupProcessor(htmlTags: htmlTags)
36-
self.htmlElementWithMarkupToMarkupStyleProcessor = HTMLElementWithMarkupToMarkupStyleProcessor(styleAttributes: styleAttributes, policy: policy, rootStyle: rootStyle)
38+
self.htmlElementWithMarkupToMarkupStyleProcessor = HTMLElementWithMarkupToMarkupStyleProcessor(styleAttributes: styleAttributes, classAttributes: classAttributes, idAttributes: idAttributes, policy: policy, rootStyle: rootStyle)
3739
}
3840

3941
static let dispatchQueue: DispatchQueue = DispatchQueue(label: "ZHTMLParser.Queue")

Sources/ZMarkupParser/HTML/ZHTMLParserBuilder.swift

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ public final class ZHTMLParserBuilder {
1111

1212
private(set) var htmlTags: [HTMLTag] = []
1313
private(set) var styleAttributes: [HTMLTagStyleAttribute] = []
14+
private(set) var classAttributes: [HTMLTagClassAttribute] = []
15+
private(set) var idAttributes: [HTMLTagIdAttribute] = []
1416
private(set) var rootStyle: MarkupStyle? = .default
1517
private(set) var policy: MarkupStylePolicy = .respectMarkupStyleFromHTMLStyleAttribute
1618

@@ -53,6 +55,26 @@ public final class ZHTMLParserBuilder {
5355
return self
5456
}
5557

58+
public func add(_ classAttribute: HTMLTagClassAttribute) -> Self {
59+
classAttributes.removeAll { thisAttribute in
60+
return thisAttribute.className == classAttribute.className
61+
}
62+
63+
classAttributes.append(classAttribute)
64+
65+
return self
66+
}
67+
68+
public func add(_ idAttribute: HTMLTagIdAttribute) -> Self {
69+
idAttributes.removeAll { thisAttribute in
70+
return thisAttribute.idName == idAttribute.idName
71+
}
72+
73+
idAttributes.append(idAttribute)
74+
75+
return self
76+
}
77+
5678
public func set(rootStyle: MarkupStyle) -> Self {
5779
self.rootStyle = rootStyle
5880
return self
@@ -67,6 +89,8 @@ public final class ZHTMLParserBuilder {
6789
return ZHTMLParser(
6890
htmlTags: htmlTags,
6991
styleAttributes: styleAttributes,
92+
classAttributes: classAttributes,
93+
idAttributes: idAttributes,
7094
policy: policy,
7195
rootStyle: rootStyle
7296
)

Tests/ZMarkupParserTests/HTML/HTMLElementMarkupComponentMarkupStyleVisitorTests.swift

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import XCTest
1212
final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
1313

1414
func testDefaultStyleByDefault() {
15-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [], styleAttributes: [], rootStyle: nil)
15+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [], styleAttributes: [], classAttributes: [], idAttributes: [], rootStyle: nil)
1616

1717
let result = visitor.visit(markup: HeadMarkup(level: .h1))
1818
XCTAssertEqual(result?.font.size, MarkupStyle.h1.font.size)
@@ -21,7 +21,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
2121
func testDefaultStyleByCustomStyle() {
2222
let markup = InlineMarkup()
2323
let customStyle = MarkupStyle(font: .init(size: 99))
24-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<span>test</span>"), attributes: [:]))], styleAttributes: [], rootStyle: nil)
24+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<span>test</span>"), attributes: [:]))], styleAttributes: [], classAttributes: [], idAttributes: [], rootStyle: nil)
2525

2626
let result = visitor.visit(markup: markup)
2727
XCTAssertEqual(result?.font.size, customStyle.font.size)
@@ -30,15 +30,15 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
3030
func testDefaultStyleShouldOverrideByCustomStyle() {
3131
let markup = HeadMarkup(level: .h1)
3232
let customStyle = MarkupStyle(font: .init(size: 99))
33-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: [:]))], styleAttributes: [], rootStyle: nil)
33+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: [:]))], styleAttributes: [], classAttributes: [], idAttributes: [], rootStyle: nil)
3434

3535
let result = visitor.visit(markup: markup)
3636
XCTAssertEqual(result?.font.size, customStyle.font.size)
3737
}
3838

3939
func testDefaultStyleShouldOverrideByStyleAttributed() {
4040
let markup = HeadMarkup(level: .h1)
41-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName()), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], rootStyle: nil)
41+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName()), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], classAttributes: [], idAttributes: [], rootStyle: nil)
4242

4343
let result = visitor.visit(markup: markup)
4444
XCTAssertEqual(result?.font.size, 99)
@@ -47,7 +47,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
4747
func testDefaultStylePolicyRespectMarkupStyleFromCode() {
4848
let markup = HeadMarkup(level: .h1)
4949
let customStyle = MarkupStyle(font: .init(size: 109))
50-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], rootStyle: nil)
50+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromCode, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], classAttributes: [], idAttributes: [], rootStyle: nil)
5151

5252
let result = visitor.visit(markup: markup)
5353
XCTAssertEqual(result?.font.size, customStyle.font.size)
@@ -56,7 +56,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
5656
func testDefaultStylePolicyRespectMarkupStyleFromHTMLStyleAttribute() {
5757
let markup = HeadMarkup(level: .h1)
5858
let customStyle = MarkupStyle(font: .init(size: 109))
59-
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromHTMLStyleAttribute, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], rootStyle: nil)
59+
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: .respectMarkupStyleFromHTMLStyleAttribute, components: [.init(markup: markup, value: .init(tag: .init(tagName: H1_HTMLTagName(), customStyle: customStyle), tagAttributedString: NSAttributedString(string: "<h1>test</h1>"), attributes: ["style": "font-size:99pt"]))], styleAttributes: [FontSizeHTMLTagStyleAttribute()], classAttributes: [], idAttributes: [], rootStyle: nil)
6060

6161
let result = visitor.visit(markup: markup)
6262
XCTAssertEqual(result?.font.size, 99)

Tests/ZMarkupParserTests/HTML/ZHTMLParserBuilderTests.swift

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,26 @@ final class ZHTMLParserBuilderTests: XCTestCase {
5252
func testAddHTMLTagStyleAttribute() {
5353
var builder = ZHTMLParserBuilder()
5454
XCTAssertEqual(builder.styleAttributes.count, 0, "styleAttributes should be empty after init.")
55+
XCTAssertEqual(builder.idAttributes.count, 0, "idAttributes should be empty after init.")
56+
XCTAssertEqual(builder.classAttributes.count, 0, "classAttributes should be empty after init.")
57+
5558
builder = builder.add(ExtendHTMLTagStyleAttribute(styleName: "zhgchgli", render: { _ in
5659
return nil
5760
}))
5861
XCTAssertEqual(builder.styleAttributes.count, 1, "styleAttributes should have 1 element.")
5962
XCTAssertEqual(builder.styleAttributes[0].styleName, "zhgchgli", "styleAttributes should have zhgchgli style name element.")
63+
64+
builder = builder.add(HTMLTagClassAttribute(className: "zhgchgli", render: {
65+
return nil
66+
}))
67+
XCTAssertEqual(builder.classAttributes.count, 1, "classAttributes should have 1 element.")
68+
XCTAssertEqual(builder.classAttributes[0].className, "zhgchgli", "classAttributes should have zhgchgli class element.")
69+
70+
builder = builder.add(HTMLTagIdAttribute(idName: "zhgchgli", render: {
71+
return nil
72+
}))
73+
XCTAssertEqual(builder.idAttributes.count, 1, "idAttributes should have 1 element.")
74+
XCTAssertEqual(builder.idAttributes[0].idName, "zhgchgli", "idAttributes should have zhgchgli id element.")
6075
}
6176

6277
func testBuild() {

Tests/ZMarkupParserTests/HTML/ZHTMLParserTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import XCTest
1111

1212
final class ZHTMLParserTests: XCTestCase {
1313

14-
private let parser = ZHTMLParser(htmlTags: ZHTMLParserBuilder.htmlTagNames.map({ HTMLTag(tagName: $0.0) }), styleAttributes: ZHTMLParserBuilder.styleAttributes, policy: .respectMarkupStyleFromCode, rootStyle: MarkupStyle(kern: 999))
14+
private let parser = ZHTMLParser(htmlTags: ZHTMLParserBuilder.htmlTagNames.map({ HTMLTag(tagName: $0.0) }), styleAttributes: ZHTMLParserBuilder.styleAttributes, classAttributes: [], idAttributes: [], policy: .respectMarkupStyleFromCode, rootStyle: MarkupStyle(kern: 999))
1515

1616
func testRender() {
1717
let string = "Test<a href=\"https://zhgchg.li\">Qoo</a>DDD"

0 commit comments

Comments
 (0)