Skip to content

Commit

Permalink
Merge pull request #70 from ZhgChgLi/feature/add-suport-class-and-id
Browse files Browse the repository at this point in the history
Feature/add suport class and
  • Loading branch information
zhgchgli0718 authored Jun 14, 2024
2 parents 0b7f6f0 + 9bf01a0 commit 4308b71
Show file tree
Hide file tree
Showing 11 changed files with 167 additions and 14 deletions.
27 changes: 24 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ The chart above shows the elapsed time (in seconds) to render different HTML str

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

or

```swift
...
dependencies: [
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.9.4"),
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.10.0"),
]
...
.target(
Expand All @@ -74,7 +74,7 @@ platform :ios, '12.0'
use_frameworks!

target 'MyApp' do
pod 'ZMarkupParser', '~> 1.9.4'
pod 'ZMarkupParser', '~> 1.10.0'
end
```

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

####Support for Class/ID Style Mapping and Parsing

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

### Render HTML String
```swift
parser.render(htmlString) // NSAttributedString
Expand Down
22 changes: 22 additions & 0 deletions Sources/ZMarkupParser/HTML/HTMLTag/HTMLTagClassAttribute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// HTMLTagClassAttribute.swift
//
//
// Created by zhgchgli on 2024/6/14.
//

import Foundation

public struct HTMLTagClassAttribute {
public let className: String
public let render: (() -> (MarkupStyle?))

public init(className: String, render: @escaping (() -> (MarkupStyle?))) {
self.className = className
self.render = render
}

func isEqualTo(className: String) -> Bool {
return self.className.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == className.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
}
22 changes: 22 additions & 0 deletions Sources/ZMarkupParser/HTML/HTMLTag/HTMLTagIdAttribute.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
//
// HTMLTagIdAttribute.swift
//
//
// Created by zhgchgli on 2024/6/14.
//

import Foundation

public struct HTMLTagIdAttribute {
public let idName: String
public let render: (() -> (MarkupStyle?))

public init(idName: String, render: @escaping (() -> (MarkupStyle?))) {
self.idName = idName
self.render = render
}

func isEqualTo(idName: String) -> Bool {
return self.idName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() == idName.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ struct HTMLElementMarkupComponentMarkupStyleVisitor: MarkupVisitor {
let policy: MarkupStylePolicy
let components: [HTMLElementMarkupComponent]
let styleAttributes: [HTMLTagStyleAttribute]
let classAttributes: [HTMLTagClassAttribute]
let idAttributes: [HTMLTagIdAttribute]

let rootStyle: MarkupStyle?

func visit(_ markup: RootMarkup) -> Result {
Expand Down Expand Up @@ -245,6 +248,45 @@ extension HTMLElementMarkupComponentMarkupStyleVisitor {
markupStyle = customStyle
}

// id
if let idString = htmlElement?.attributes?["id"],
let idAttribute = idAttributes.first(where: { $0.isEqualTo(idName: idString) }),
var thisMarkupStyle = idAttribute.render() {
switch policy {
case .respectMarkupStyleFromCode:
if var markupStyle = markupStyle {
markupStyle.fillIfNil(from: thisMarkupStyle)
} else {
markupStyle = thisMarkupStyle
}
case .respectMarkupStyleFromHTMLStyleAttribute:
thisMarkupStyle.fillIfNil(from: markupStyle ?? defaultStyle)
markupStyle = thisMarkupStyle
}
}
// class
if let classString = htmlElement?.attributes?["class"],
classAttributes.count > 0 {
let classNames = classString.split(separator: " ").filter { $0.trimmingCharacters(in: .whitespacesAndNewlines) != "" }

for className in classNames {
if let classAttribute = classAttributes.first(where: { $0.isEqualTo(className: String(className)) }),
var thisMarkupStyle = classAttribute.render() {
switch policy {
case .respectMarkupStyleFromCode:
if var markupStyle = markupStyle {
markupStyle.fillIfNil(from: thisMarkupStyle)
} else {
markupStyle = thisMarkupStyle
}
case .respectMarkupStyleFromHTMLStyleAttribute:
thisMarkupStyle.fillIfNil(from: markupStyle ?? defaultStyle)
markupStyle = thisMarkupStyle
}
}
}
}
// style
if let styleString = htmlElement?.attributes?["style"],
styleAttributes.count > 0 {
let styles = styleString.split(separator: ";").filter { $0.trimmingCharacters(in: .whitespacesAndNewlines) != "" }.map { $0.split(separator: ":") }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,22 @@ final class HTMLElementWithMarkupToMarkupStyleProcessor: ParserProcessor {
typealias To = [MarkupStyleComponent]

let styleAttributes: [HTMLTagStyleAttribute]
let classAttributes: [HTMLTagClassAttribute]
let idAttributes: [HTMLTagIdAttribute]

let policy: MarkupStylePolicy
let rootStyle: MarkupStyle?
init(styleAttributes: [HTMLTagStyleAttribute], policy: MarkupStylePolicy, rootStyle: MarkupStyle?) {
init(styleAttributes: [HTMLTagStyleAttribute], classAttributes: [HTMLTagClassAttribute], idAttributes: [HTMLTagIdAttribute], policy: MarkupStylePolicy, rootStyle: MarkupStyle?) {
self.styleAttributes = styleAttributes
self.classAttributes = classAttributes
self.idAttributes = idAttributes
self.policy = policy
self.rootStyle = rootStyle
}

func process(from: From) -> To {
var components: [MarkupStyleComponent] = []
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: policy, components: from.1, styleAttributes: styleAttributes, rootStyle: rootStyle)
let visitor = HTMLElementMarkupComponentMarkupStyleVisitor(policy: policy, components: from.1, styleAttributes: styleAttributes, classAttributes: classAttributes, idAttributes: idAttributes, rootStyle: rootStyle)
walk(markup: from.0, visitor: visitor, components: &components)
return components
}
Expand Down
4 changes: 3 additions & 1 deletion Sources/ZMarkupParser/HTML/ZHTMLParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ public final class ZHTMLParser {
init(
htmlTags: [HTMLTag],
styleAttributes: [HTMLTagStyleAttribute],
classAttributes: [HTMLTagClassAttribute],
idAttributes: [HTMLTagIdAttribute],
policy: MarkupStylePolicy,
rootStyle: MarkupStyle?
) {
Expand All @@ -33,7 +35,7 @@ public final class ZHTMLParser {
self.markupRenderProcessor = MarkupRenderProcessor(rootStyle: rootStyle)

self.htmlParsedResultToHTMLElementWithRootMarkupProcessor = HTMLParsedResultToHTMLElementWithRootMarkupProcessor(htmlTags: htmlTags)
self.htmlElementWithMarkupToMarkupStyleProcessor = HTMLElementWithMarkupToMarkupStyleProcessor(styleAttributes: styleAttributes, policy: policy, rootStyle: rootStyle)
self.htmlElementWithMarkupToMarkupStyleProcessor = HTMLElementWithMarkupToMarkupStyleProcessor(styleAttributes: styleAttributes, classAttributes: classAttributes, idAttributes: idAttributes, policy: policy, rootStyle: rootStyle)
}

static let dispatchQueue: DispatchQueue = DispatchQueue(label: "ZHTMLParser.Queue")
Expand Down
24 changes: 24 additions & 0 deletions Sources/ZMarkupParser/HTML/ZHTMLParserBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public final class ZHTMLParserBuilder {

private(set) var htmlTags: [HTMLTag] = []
private(set) var styleAttributes: [HTMLTagStyleAttribute] = []
private(set) var classAttributes: [HTMLTagClassAttribute] = []
private(set) var idAttributes: [HTMLTagIdAttribute] = []
private(set) var rootStyle: MarkupStyle? = .default
private(set) var policy: MarkupStylePolicy = .respectMarkupStyleFromHTMLStyleAttribute

Expand Down Expand Up @@ -53,6 +55,26 @@ public final class ZHTMLParserBuilder {
return self
}

public func add(_ classAttribute: HTMLTagClassAttribute) -> Self {
classAttributes.removeAll { thisAttribute in
return thisAttribute.className == classAttribute.className
}

classAttributes.append(classAttribute)

return self
}

public func add(_ idAttribute: HTMLTagIdAttribute) -> Self {
idAttributes.removeAll { thisAttribute in
return thisAttribute.idName == idAttribute.idName
}

idAttributes.append(idAttribute)

return self
}

public func set(rootStyle: MarkupStyle) -> Self {
self.rootStyle = rootStyle
return self
Expand All @@ -67,6 +89,8 @@ public final class ZHTMLParserBuilder {
return ZHTMLParser(
htmlTags: htmlTags,
styleAttributes: styleAttributes,
classAttributes: classAttributes,
idAttributes: idAttributes,
policy: policy,
rootStyle: rootStyle
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import XCTest
final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {

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

let result = visitor.visit(markup: HeadMarkup(level: .h1))
XCTAssertEqual(result?.font.size, MarkupStyle.h1.font.size)
Expand All @@ -21,7 +21,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
func testDefaultStyleByCustomStyle() {
let markup = InlineMarkup()
let customStyle = MarkupStyle(font: .init(size: 99))
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)
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)

let result = visitor.visit(markup: markup)
XCTAssertEqual(result?.font.size, customStyle.font.size)
Expand All @@ -30,15 +30,15 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
func testDefaultStyleShouldOverrideByCustomStyle() {
let markup = HeadMarkup(level: .h1)
let customStyle = MarkupStyle(font: .init(size: 99))
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)
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)

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

func testDefaultStyleShouldOverrideByStyleAttributed() {
let markup = HeadMarkup(level: .h1)
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)
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)

let result = visitor.visit(markup: markup)
XCTAssertEqual(result?.font.size, 99)
Expand All @@ -47,7 +47,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
func testDefaultStylePolicyRespectMarkupStyleFromCode() {
let markup = HeadMarkup(level: .h1)
let customStyle = MarkupStyle(font: .init(size: 109))
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)
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)

let result = visitor.visit(markup: markup)
XCTAssertEqual(result?.font.size, customStyle.font.size)
Expand All @@ -56,7 +56,7 @@ final class HTMLElementMarkupComponentMarkupStyleVisitorTests: XCTestCase {
func testDefaultStylePolicyRespectMarkupStyleFromHTMLStyleAttribute() {
let markup = HeadMarkup(level: .h1)
let customStyle = MarkupStyle(font: .init(size: 109))
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)
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)

let result = visitor.visit(markup: markup)
XCTAssertEqual(result?.font.size, 99)
Expand Down
15 changes: 15 additions & 0 deletions Tests/ZMarkupParserTests/HTML/ZHTMLParserBuilderTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,26 @@ final class ZHTMLParserBuilderTests: XCTestCase {
func testAddHTMLTagStyleAttribute() {
var builder = ZHTMLParserBuilder()
XCTAssertEqual(builder.styleAttributes.count, 0, "styleAttributes should be empty after init.")
XCTAssertEqual(builder.idAttributes.count, 0, "idAttributes should be empty after init.")
XCTAssertEqual(builder.classAttributes.count, 0, "classAttributes should be empty after init.")

builder = builder.add(ExtendHTMLTagStyleAttribute(styleName: "zhgchgli", render: { _ in
return nil
}))
XCTAssertEqual(builder.styleAttributes.count, 1, "styleAttributes should have 1 element.")
XCTAssertEqual(builder.styleAttributes[0].styleName, "zhgchgli", "styleAttributes should have zhgchgli style name element.")

builder = builder.add(HTMLTagClassAttribute(className: "zhgchgli", render: {
return nil
}))
XCTAssertEqual(builder.classAttributes.count, 1, "classAttributes should have 1 element.")
XCTAssertEqual(builder.classAttributes[0].className, "zhgchgli", "classAttributes should have zhgchgli class element.")

builder = builder.add(HTMLTagIdAttribute(idName: "zhgchgli", render: {
return nil
}))
XCTAssertEqual(builder.idAttributes.count, 1, "idAttributes should have 1 element.")
XCTAssertEqual(builder.idAttributes[0].idName, "zhgchgli", "idAttributes should have zhgchgli id element.")
}

func testBuild() {
Expand Down
2 changes: 1 addition & 1 deletion Tests/ZMarkupParserTests/HTML/ZHTMLParserTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import XCTest

final class ZHTMLParserTests: XCTestCase {

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

func testRender() {
let string = "Test<a href=\"https://zhgchg.li\">Qoo</a>DDD"
Expand Down
2 changes: 1 addition & 1 deletion scripts/ZMarkupParser.podspec
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "ZMarkupParser"
s.version = "1.9.4"
s.version = "1.10.0"
s.summary = "ZMarkupParser helps you to convert HTML String to NSAttributedString with customized style and tag through pure-Swift."
s.homepage = "https://github.com/ZhgChgLi/ZMarkupParser"
s.license = { :type => "MIT", :file => "LICENSE" }
Expand Down

0 comments on commit 4308b71

Please sign in to comment.