Skip to content

Commit 110197e

Browse files
committed
handle CSS declarations with multiple selectors
1 parent 1355dce commit 110197e

File tree

5 files changed

+83
-18
lines changed

5 files changed

+83
-18
lines changed

DOM/Sources/Parser.XML.Scanner.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,14 @@ package extension XMLParser {
5555
package mutating func scanStringIfPossible(_ token: String) -> Bool {
5656
return (try? self.scanString(token)) == true
5757
}
58-
58+
59+
@discardableResult
60+
package mutating func nextScanString(_ token: String) -> Bool {
61+
scanner.currentIndex = currentIndex
62+
defer { scanner.currentIndex = currentIndex }
63+
return scanStringIfPossible(token)
64+
}
65+
5966
package mutating func scanString(matchingAny tokens: Set<String>) throws -> String {
6067
scanner.currentIndex = currentIndex
6168
guard let match = tokens.first(where: { scanner.scanString($0) != nil }) else {

DOM/Sources/Parser.XML.StyleSheet.swift

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,13 +71,15 @@ extension XMLParser {
7171
var scanner = XMLParser.Scanner(text: removeCSSComments(from: text))
7272
var entries = [DOM.StyleSheet.Selector: [String: String]]()
7373

74-
var last: (DOM.StyleSheet.Selector, [String: String])?
75-
repeat {
76-
last = try scanner.scanNextSelector()
77-
if let last = last {
78-
entries[last.0] = last.1
74+
while let (selectors, attributes) = try scanner.scanNextSelectorDecl() {
75+
for selector in selectors {
76+
var copy = entries[selector] ?? [:]
77+
for (key, value) in attributes {
78+
copy[key] = value
79+
}
80+
entries[selector] = copy
7981
}
80-
} while last != nil
82+
}
8183

8284
return entries
8385
}
@@ -91,15 +93,10 @@ extension XMLParser {
9193

9294
extension XMLParser.Scanner {
9395

94-
mutating func scanNextSelector() throws -> (DOM.StyleSheet.Selector, [String: String])? {
95-
if let c = try scanNextClass() {
96-
return (.class(c), try scanAtttributes())
97-
} else if let id = try scanNextID() {
98-
return (.id(id), try scanAtttributes())
99-
} else if let e = try scanNextElement() {
100-
return (.element(e), try scanAtttributes())
101-
}
102-
return nil
96+
mutating func scanNextSelectorDecl() throws -> ([DOM.StyleSheet.Selector], [String: String])? {
97+
let selectorTypes = try scanSelectorTypes()
98+
guard !selectorTypes.isEmpty else { return nil }
99+
return (selectorTypes, try scanAtttributes())
103100
}
104101

105102
private mutating func scanNextClass() throws -> String? {
@@ -124,7 +121,30 @@ extension XMLParser.Scanner {
124121
}
125122

126123
private mutating func scanSelectorName() throws -> String? {
127-
try scanString(upTo: "{").trimmingCharacters(in: .whitespacesAndNewlines)
124+
guard !nextScanString("{") else { return nil }
125+
let name = try scanString(upTo: .init(charactersIn: "{,")).trimmingCharacters(in: .whitespacesAndNewlines)
126+
scanStringIfPossible(",")
127+
return name
128+
}
129+
130+
mutating func scanSelectorTypes() throws -> [DOM.StyleSheet.Selector] {
131+
var selectors: [DOM.StyleSheet.Selector] = []
132+
while let next = try scanNextSelectorType() {
133+
selectors.append(next)
134+
}
135+
return selectors
136+
}
137+
138+
private mutating func scanNextSelectorType() throws -> DOM.StyleSheet.Selector? {
139+
if let name = try scanNextClass() {
140+
return .class(name)
141+
} else if let name = try scanNextID() {
142+
return .id(name)
143+
} else if let name = try scanNextElement() {
144+
return .element(name)
145+
} else {
146+
return nil
147+
}
128148
}
129149

130150
private mutating func scanAtttributes() throws -> [String: String] {

DOM/Tests/Parser.XML.StyleSheetTests.swift

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,4 +110,40 @@ final class ParserXMLStyleSheetTests: XCTestCase {
110110
XCTAssertEqual(sheet[.class("b")]?.fill, .color(.keyword(.blue)))
111111
XCTAssertEqual(sheet[.element("rect")]?.fill, .color(.keyword(.pink)))
112112
}
113+
114+
func testMergesSelectors() throws {
115+
let entries = try XMLParser.parseEntries(
116+
"""
117+
.a {
118+
fill: red;
119+
}
120+
.a {
121+
stroke: blue;
122+
}
123+
"""
124+
)
125+
126+
XCTAssertEqual(
127+
entries,
128+
[.class("a"): ["fill": "red", "stroke": "blue"]]
129+
)
130+
}
131+
132+
func testMutlipleSelectors() throws {
133+
let entries = try XMLParser.parseEntries(
134+
"""
135+
.a, .b {
136+
fill: red;
137+
}
138+
"""
139+
)
140+
141+
XCTAssertEqual(
142+
entries,
143+
[
144+
.class("a"): ["fill": "red"],
145+
.class("b"): ["fill": "red"]
146+
]
147+
)
148+
}
113149
}

Examples/Sources/GalleryView.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,8 @@ struct GalleryView: View {
5252
"yawning.svg",
5353
"thats-no-moon.svg",
5454
"alert.svg",
55-
"effigy.svg"
55+
"effigy.svg",
56+
"stylesheet-multiple.svg"
5657
].compactMap {
5758
SVG(named: $0, in: .samples)
5859
}
Lines changed: 1 addition & 0 deletions
Loading

0 commit comments

Comments
 (0)