Skip to content

Commit c82ef4b

Browse files
committed
bump version
1 parent a2712c6 commit c82ef4b

File tree

7 files changed

+221
-22
lines changed

7 files changed

+221
-22
lines changed

Package.resolved

Lines changed: 3 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,15 +11,13 @@ let package = Package(
1111
],
1212
dependencies: [
1313
.package(url: "https://github.com/pointfreeco/swift-snapshot-testing", from: "1.11.0"),
14-
.package(url: "https://github.com/ZhgChgLi/ZNSTextAttachment", from: "1.1.6"),
15-
.package(url: "https://github.com/alexaubry/HTMLString", from: "6.0.0")
14+
.package(url: "https://github.com/ZhgChgLi/ZNSTextAttachment", from: "1.1.6")
1615
],
1716
targets: [
1817
.target(
1918
name: "ZMarkupParser",
2019
dependencies: [
21-
"ZNSTextAttachment",
22-
"HTMLString"
20+
"ZNSTextAttachment"
2321
],
2422
path: "Sources"
2523
),

README.md

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

4646
- File > Swift Packages > Add Package Dependency
4747
- Add `https://github.com/ZhgChgLi/ZMarkupParser.git`
48-
- Select "Up to Next Major" with "1.3.8"
48+
- Select "Up to Next Major" with "1.4.0"
4949

5050
or
5151

5252
```swift
5353
...
5454
dependencies: [
55-
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.3.8"),
55+
.package(url: "https://github.com/ZhgChgLi/ZMarkupParser.git", from: "1.4.0"),
5656
]
5757
...
5858
.target(
@@ -71,7 +71,7 @@ platform :ios, '12.0'
7171
use_frameworks!
7272

7373
target 'MyApp' do
74-
pod 'ZMarkupParser', '~> 1.3.8'
74+
pod 'ZMarkupParser', '~> 1.4.0'
7575
end
7676
```
7777

Sources/HTMLString/HTMLString.swift

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
import Foundation
2+
3+
// MARK: Escaping
4+
5+
// ref: https://github.com/alexisakers/HTMLString/tree/main
6+
// Due to Cocoapods dependency issues, it is not possible to declare dependencies directly in the Pod Spec.
7+
// Therefore, the source code has been directly included in the project, and all copyright and intellectual property rights belong to the original author at https://github.com/alexisakers/HTMLString.
8+
9+
extension String {
10+
11+
///
12+
/// Returns a copy of the current `String` where every character incompatible with HTML Unicode
13+
/// encoding (UTF-16 or UTF-8) is replaced by a decimal HTML entity.
14+
///
15+
/// ### Examples
16+
///
17+
/// | String | Result | Format |
18+
/// |--------|--------|--------|
19+
/// | `&` | `&` | Decimal entity (part of the Unicode special characters) |
20+
/// | `Σ` | `Σ` | Not escaped (Unicode compliant) |
21+
/// | `🇺🇸` | `🇺🇸` | Not escaped (Unicode compliant) |
22+
/// | `a` | `a` | Not escaped (alphanumerical) |
23+
///
24+
25+
public func addingUnicodeEntities() -> String {
26+
var result = ""
27+
28+
for character in self {
29+
if HTMLStringMappings.unsafeUnicodeCharacters.contains(character) {
30+
// One of the required escapes for security reasons
31+
result.append(contentsOf: "&#\(character.asciiValue!);")
32+
} else {
33+
// Not a required escape, no need to replace the character
34+
result.append(character)
35+
}
36+
}
37+
38+
return result
39+
}
40+
41+
///
42+
/// Returns a copy of the current `String` where every character incompatible with HTML ASCII
43+
/// encoding is replaced by a decimal HTML entity.
44+
///
45+
/// ### Examples
46+
///
47+
/// | String | Result | Format |
48+
/// |--------|--------|--------|
49+
/// | `&` | `&` | Decimal entity |
50+
/// | `Σ` | `Σ` | Decimal entity |
51+
/// | `🇺🇸` | `🇺🇸` | Combined decimal entities (extented grapheme cluster) |
52+
/// | `a` | `a` | Not escaped (alphanumerical) |
53+
///
54+
/// ### Performance
55+
///
56+
/// If your webpage is unicode encoded (UTF-16 or UTF-8) use `addingUnicodeEntities` instead,
57+
/// as it is faster and produces a less bloated and more readable HTML.
58+
///
59+
60+
public func addingASCIIEntities() -> String {
61+
var result = ""
62+
63+
for character in self {
64+
if let asciiiValue = character.asciiValue {
65+
if HTMLStringMappings.unsafeUnicodeCharacters.contains(character) {
66+
// One of the required escapes for security reasons
67+
result.append(contentsOf: "&#\(asciiiValue);")
68+
} else {
69+
// Not a required escape, no need to replace the character
70+
result.append(character)
71+
}
72+
} else {
73+
// Not an ASCII Character, we need to escape.
74+
let escape = character.unicodeScalars.reduce(into: "") { $0 += "&#\($1.value);" }
75+
result.append(contentsOf: escape)
76+
}
77+
}
78+
79+
return result
80+
}
81+
}
82+
83+
// MARK: - Unescaping
84+
85+
extension String {
86+
87+
///
88+
/// Replaces every HTML entity in the receiver with the matching Unicode character.
89+
///
90+
/// ### Examples
91+
///
92+
/// | String | Result | Format |
93+
/// |--------|--------|--------|
94+
/// | `&` | `&` | Keyword entity |
95+
/// | `Σ` | `Σ` | Decimal entity |
96+
/// | `č` | `č` | Hexadecimal entity |
97+
/// | `🇺🇸` | `🇺🇸` | Combined decimal entities (extented grapheme cluster) |
98+
/// | `a` | `a` | Not an entity |
99+
/// | `&` | `&` | Not an entity |
100+
///
101+
102+
public func removingHTMLEntities() -> String {
103+
var result = ""
104+
var currentIndex = startIndex
105+
106+
while let delimiterIndex = self[currentIndex...].firstIndex(of: "&") {
107+
// Avoid unnecessary operations
108+
var semicolonIndex = self.index(after: delimiterIndex)
109+
110+
// Parse the last sequence (ex: Fish & chips & sauce -> "&" instead of "& chips &")
111+
var lastDelimiterIndex = delimiterIndex
112+
113+
while semicolonIndex != endIndex, self[semicolonIndex] != ";" {
114+
if self[semicolonIndex] == "&" {
115+
lastDelimiterIndex = semicolonIndex
116+
}
117+
118+
semicolonIndex = self.index(after: semicolonIndex)
119+
}
120+
121+
// Fast path if semicolon doesn't exists in current range
122+
if semicolonIndex == endIndex {
123+
result.append(contentsOf: self[currentIndex..<semicolonIndex])
124+
return result
125+
}
126+
127+
let escapableRange = index(after: lastDelimiterIndex) ..< semicolonIndex
128+
let escapableContent = self[escapableRange]
129+
130+
result.append(contentsOf: self[currentIndex..<lastDelimiterIndex])
131+
132+
let cursorPosition: Index
133+
if let unescapedNumber = escapableContent.unescapeAsNumber() {
134+
result.append(contentsOf: unescapedNumber)
135+
cursorPosition = self.index(semicolonIndex, offsetBy: 1)
136+
} else if let unescapedCharacter = HTMLStringMappings.unescapingTable[String(escapableContent)] {
137+
result.append(contentsOf: unescapedCharacter)
138+
cursorPosition = self.index(semicolonIndex, offsetBy: 1)
139+
} else {
140+
result.append(self[lastDelimiterIndex])
141+
cursorPosition = self.index(after: lastDelimiterIndex)
142+
}
143+
144+
currentIndex = cursorPosition
145+
}
146+
147+
result.append(contentsOf: self[currentIndex...])
148+
149+
return result
150+
}
151+
}
152+
153+
// MARK: - Helpers
154+
155+
extension StringProtocol {
156+
157+
/// Unescapes the receives as a number if possible.
158+
fileprivate func unescapeAsNumber() -> String? {
159+
guard hasPrefix("#") else { return nil }
160+
161+
let unescapableContent = self.dropFirst()
162+
let isHexadecimal = unescapableContent.hasPrefix("x") || hasPrefix("X")
163+
let radix = isHexadecimal ? 16 : 10
164+
165+
guard let numberStartIndex = unescapableContent.index(unescapableContent.startIndex, offsetBy: isHexadecimal ? 1 : 0, limitedBy: unescapableContent.endIndex) else {
166+
return nil
167+
}
168+
169+
let numberString = unescapableContent[numberStartIndex ..< endIndex]
170+
171+
guard let codePoint = UInt32(numberString, radix: radix), let scalar = UnicodeScalar(codePoint) else {
172+
return nil
173+
}
174+
175+
return String(scalar)
176+
}
177+
}

Sources/HTMLString/Mappings.swift

Lines changed: 34 additions & 0 deletions
Large diffs are not rendered by default.

Sources/ZMarkupParser/HTML/ZHTMLParser.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
//
77

88
import Foundation
9-
import HTMLString
109

1110
public final class ZHTMLParser {
1211
let htmlTags: [HTMLTag]

scripts/ZMarkupParser.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
Pod::Spec.new do |s|
22
s.name = "ZMarkupParser"
3-
s.version = "1.3.8"
3+
s.version = "1.4.0"
44
s.summary = "ZMarkupParser helps you to convert HTML String to NSAttributedString with customized style and tag through pure-Swift."
55
s.homepage = "https://github.com/ZhgChgLi/ZMarkupParser"
66
s.license = { :type => "MIT", :file => "LICENSE" }
@@ -11,5 +11,5 @@ Pod::Spec.new do |s|
1111
s.swift_version = "5.0"
1212
s.source_files = ["Sources/**/*.swift"]
1313
s.dependency 'ZNSTextAttachment', '~> 1.1.6'
14-
s.dependency 'HTMLString', '~> 6.0'
14+
s.dependency 'SnapshotTesting', '~> 1.9.0'
1515
end

0 commit comments

Comments
 (0)