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, ""))
+ }
+}