diff --git a/Sources/Schema/RelaxNGCompactParser.swift b/Sources/Schema/RelaxNGCompactParser.swift index cbaaf9a..d5d90ea 100644 --- a/Sources/Schema/RelaxNGCompactParser.swift +++ b/Sources/Schema/RelaxNGCompactParser.swift @@ -308,6 +308,11 @@ private extension RNCParser { switch peek() { case .symbol("*"): advance() + // `* - nameClass` is any name except the subtracted class. + if peek() == .symbol("-") { + advance() + return .anyNameExcept(parseSimpleNameClass(attribute: attribute)) + } return .anyName case .symbol("("): advance() diff --git a/Tests/RelaxNGNameClassTests.swift b/Tests/RelaxNGNameClassTests.swift new file mode 100644 index 0000000..7ac91f4 --- /dev/null +++ b/Tests/RelaxNGNameClassTests.swift @@ -0,0 +1,22 @@ +@testable import PureXML +import Testing + +@Suite("RELAX NG compact name-class subtraction") +struct RelaxNGNameClassTests { + private func valid(_ rnc: String, _ xml: String) throws -> Bool { + try PureXML.Schema.RelaxNG(compact: rnc).validate(xml) + } + + @Test("* - name admits any element except the subtracted one") + func test_subtraction() throws { + let rnc = "element root { element * - skip { empty }* }" + #expect(try valid(rnc, "")) + #expect(try !valid(rnc, "")) + } + + @Test("A plain wildcard still admits everything") + func test_plainWildcard() throws { + let rnc = "element root { element * { empty }* }" + #expect(try valid(rnc, "")) + } +}