diff --git a/DOM/Sources/Parser.XML.StyleSheet.swift b/DOM/Sources/Parser.XML.StyleSheet.swift index 91d7fa5..c3ccae0 100644 --- a/DOM/Sources/Parser.XML.StyleSheet.swift +++ b/DOM/Sources/Parser.XML.StyleSheet.swift @@ -126,6 +126,9 @@ extension XMLParser.Scanner { if let attributes = try scanNextFontFace() { return (.atRule("font-face"), attributes) } + if let name = scanUnknownAtRule() { + return (.atRule(name), [:]) + } let selectorTypes = try scanSelectorTypes() guard !selectorTypes.isEmpty else { return nil } return (.selector(selectorTypes), try scanAtttributes()) @@ -148,6 +151,25 @@ extension XMLParser.Scanner { return try scanAtttributes() } + /// Skips unknown @ rules (e.g. @media, @supports) including nested blocks. + /// Returns the rule name if an unknown @ rule was consumed, nil otherwise. + mutating func scanUnknownAtRule() -> String? { + guard doScanString("@") else { return nil } + guard let name = try? scanString(upTo: .init(charactersIn: "{")), + doScanString("{") else { return nil } + var depth = 1 + while depth > 0 && !isEOF { + if doScanString("{") { + depth += 1 + } else if doScanString("}") { + depth -= 1 + } else { + _ = try? scanString(upTo: .init(charactersIn: "{}")) + } + } + return name.trimmingCharacters(in: .whitespacesAndNewlines) + } + private mutating func scanNextElement() throws -> String? { do { return try scanSelectorName() diff --git a/DOM/Tests/Parser.XML.StyleSheetTests.swift b/DOM/Tests/Parser.XML.StyleSheetTests.swift index 9116669..3c60fc5 100644 --- a/DOM/Tests/Parser.XML.StyleSheetTests.swift +++ b/DOM/Tests/Parser.XML.StyleSheetTests.swift @@ -191,6 +191,47 @@ struct ParserXMLStyleSheetTests { ) } + @Test + func skipsMediaQueries() throws { + let entries = try XMLParser.parseSelectorEntries( + """ + g { fill: #000; } + + @media (prefers-color-scheme: dark) { + g { fill: #fff; } + } + """ + ) + + #expect( + entries == [ + .element("g"): ["fill": "#000"] + ] + ) + } + + @Test + func skipsNestedAtRules() throws { + let entries = try XMLParser.parseSelectorEntries( + """ + .a { fill: red; } + @supports (display: grid) { + @media (min-width: 600px) { + .b { fill: blue; } + } + } + .c { stroke: green; } + """ + ) + + #expect( + entries == [ + .class("a"): ["fill": "red"], + .class("c"): ["stroke": "green"] + ] + ) + } + @Test func parsesFontFaceEntries() throws { let entries = try XMLParser.parseFontFaceEntries(