Skip to content

Commit f4d23e5

Browse files
performance nit-picks for minify logic
1 parent abf1e49 commit f4d23e5

File tree

4 files changed

+71
-51
lines changed

4 files changed

+71
-51
lines changed

Sources/HTMLKitUtilities/HTMLElementType.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,4 +146,12 @@ public enum HTMLElementType : String, CaseIterable, Sendable {
146146
return false
147147
}
148148
}
149+
150+
@inlinable
151+
public var tagName : String {
152+
switch self {
153+
case .variable: return "var"
154+
default: return rawValue
155+
}
156+
}
149157
}

Sources/HTMLKitUtilities/HTMLKitUtilities.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,14 @@ extension StringLiteralExprSyntax {
107107
}
108108
}
109109
extension Collection {
110+
/// - Returns: The element at the given index, checking if the index is within bounds (`>= startIndex && < endIndex`).
110111
package func get(_ index: Index) -> Element? {
111112
return index >= startIndex && index < endIndex ? self[index] : nil
112113
}
114+
/// - Returns: The element at the given index, only checking if the index is less than `endIndex`.
115+
package func getPositive(_ index: Index) -> Element? {
116+
return index < endIndex ? self[index] : nil
117+
}
113118
}
114119
extension LabeledExprListSyntax {
115120
package func get(_ index: Int) -> Element? {

Sources/HTMLKitUtilities/Minify.swift

Lines changed: 57 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,58 +5,49 @@
55
// Created by Evan Anderson on 3/31/25.
66
//
77

8-
import SwiftSyntax
9-
108
extension HTMLKitUtilities {
11-
static let defaultPreservedWhitespaceTags:Set<String> = Set([
12-
"a", "abbr",
13-
"b", "bdi", "bdo", "button",
14-
"cite", "code",
15-
"data", "dd", "dfn", "dt",
16-
"em",
17-
"h1", "h2", "h3", "h4", "h5", "h6",
18-
"i",
19-
"kbd",
20-
"label", "li",
21-
"mark",
22-
"p",
23-
"q",
24-
"rp",
25-
"rt",
26-
"ruby",
27-
"s", "samp", "small", "span", "strong", "sub", "sup",
28-
"td", "time", "title", "tr",
29-
"u",
30-
"var",
31-
"wbr"
32-
].map { "<" + $0 + ">" })
9+
static let defaultPreservedWhitespaceTags:Set<Substring> = Set(Array<HTMLElementType>(arrayLiteral:
10+
.a, .abbr,
11+
.b, .bdi, .bdo, .button,
12+
.cite, .code,
13+
.data, .dd, .dfn, .dt,
14+
.em,
15+
.h1, .h2, .h3, .h4, .h5, .h6,
16+
.i,
17+
.kbd,
18+
.label, .li,
19+
.mark,
20+
.p,
21+
.q,
22+
.rp,
23+
.rt,
24+
.ruby,
25+
.s, .samp, .small, .span, .strong, .sub, .sup,
26+
.td, .time, .title, .tr,
27+
.u,
28+
.variable,
29+
.wbr
30+
).map { "<" + $0.tagName + ">" })
3331

3432
/// Removes whitespace between elements.
3533
public static func minify(
3634
html: String,
37-
preservingWhitespaceForTags: Set<String> = []
35+
preservingWhitespaceForTags: Set<Substring> = []
3836
) -> String {
39-
var preservedWhitespaceTags:Set<String> = Self.defaultPreservedWhitespaceTags
40-
preservedWhitespaceTags.formUnion(preservingWhitespaceForTags)
4137
var result:String = ""
4238
result.reserveCapacity(html.count)
4339
let tagRegex = "[^/>]+"
44-
let openElementRegex = "(<\(tagRegex)>)"
45-
let openElementRanges = html.ranges(of: try! Regex(openElementRegex))
46-
47-
let closeElementRegex = "(</\(tagRegex)>)"
48-
let closeElementRanges = html.ranges(of: try! Regex(closeElementRegex))
40+
let openElementRanges = html.ranges(of: try! Regex("(<\(tagRegex)>)"))
41+
let closeElementRanges = html.ranges(of: try! Regex("(</\(tagRegex)>)"))
4942

5043
var openingRangeIndex = 0
5144
var ignoredClosingTags:Set<Range<String.Index>> = []
5245
for openingRange in openElementRanges {
5346
let tag = html[openingRange]
5447
result += tag
55-
let closure:(Character) -> Bool = preservedWhitespaceTags.contains(String(tag)) ? { _ in true } : {
56-
!($0.isWhitespace || $0.isNewline)
57-
}
48+
let closure = Self.defaultPreservedWhitespaceTags.contains(tag) || preservingWhitespaceForTags.contains(tag) ? appendAll : appendIfPreserved
5849
let closestClosingRange = closeElementRanges.first(where: { $0.lowerBound > openingRange.upperBound })
59-
if let nextOpeningRange = openElementRanges.get(openingRangeIndex + 1) {
50+
if let nextOpeningRange = openElementRanges.getPositive(openingRangeIndex + 1) {
6051
var i = openingRange.upperBound
6152
var lowerBound = nextOpeningRange.lowerBound
6253
if let closestClosingRange {
@@ -68,13 +59,7 @@ extension HTMLKitUtilities {
6859
}
6960
}
7061
// anything after the opening tag, upto the end of the next closing tag
71-
while i < lowerBound {
72-
let char = html[i]
73-
if closure(char) {
74-
result.append(char)
75-
}
76-
html.formIndex(after: &i)
77-
}
62+
closure(html, &i, lowerBound, &result)
7863
// anything after the closing tag and before the next opening tag
7964
while i < nextOpeningRange.lowerBound {
8065
let char = html[i]
@@ -85,12 +70,8 @@ extension HTMLKitUtilities {
8570
}
8671
} else if let closestClosingRange {
8772
// anything after the opening tag and before the next closing tag
88-
let slice = html[openingRange.upperBound..<closestClosingRange.lowerBound]
89-
for char in slice {
90-
if closure(char) {
91-
result.append(char)
92-
}
93-
}
73+
var i = openingRange.upperBound
74+
closure(html, &i, closestClosingRange.lowerBound, &result)
9475
}
9576
openingRangeIndex += 1
9677
}
@@ -101,4 +82,31 @@ extension HTMLKitUtilities {
10182
}
10283
return result
10384
}
85+
}
86+
87+
// MARK: append
88+
extension HTMLKitUtilities {
89+
fileprivate static func appendAll(
90+
html: String,
91+
i: inout String.Index,
92+
bound: String.Index,
93+
result: inout String
94+
) {
95+
result += html[i..<bound]
96+
i = bound
97+
}
98+
fileprivate static func appendIfPreserved(
99+
html: String,
100+
i: inout String.Index,
101+
bound: String.Index,
102+
result: inout String
103+
) {
104+
while i < bound {
105+
let char = html[i]
106+
if !(char.isWhitespace || char.isNewline) {
107+
result.append(char)
108+
}
109+
html.formIndex(after: &i)
110+
}
111+
}
104112
}

Tests/HTMLKitTests/MinifyTests.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import HTMLKit
1212

1313
struct MinifyTests {
1414
@Test func minifyHTML() {
15-
/////
1615
var expected = "<html><body><p>\ndude&dude </p>r ly<div>what</div></body></html>"
1716
var result:String = HTMLKitUtilities.minify(html: "\n<html>\n <body><p>\ndude&dude </p>r ly\n<div>\nwh at</div></body>\n</html>")
1817
#expect(expected == result)
@@ -25,7 +24,7 @@ struct MinifyTests {
2524
<path d="M3 6h18"></path>
2625
<path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"></path>
2726
<path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"></path></svg>
28-
"""#, preservingWhitespaceForTags: ["svg", "path", "circle"])
27+
"""#)
2928
#expect(expected == result)
3029
}
3130
}

0 commit comments

Comments
 (0)