PureXML/Tests/SchemaAttributeWildcardIntersectionTests.swift
Mihaela Mihaljevic 0d3fbf1b0a fix(schema): intersect a complex type's attribute wildcard sources; invalid-instances-accepted 94 to 89 (#147)
A complex type's effective {attribute wildcard} is the intersection of the
anyAttribute wildcards it draws from its own declaration and each referenced
attribute group (XSD 1.0 cos-aw-intersect), not their union. An attribute is
admitted only if every source admits it, so two attribute groups constraining
disjoint namespaces admit nothing and an attribute in either is rejected.

attributeWildcard(under:_:visited:) combined the sources with Wildcard.union,
which let through an attribute that only one source allowed. It now uses a new
Wildcard.intersection covering the namespace cases (##any identity, enumerated
set intersection, enumerated-with-##other exclusion via exclude(_:otherThan:)).
A type with a single anyAttribute source is unchanged (intersection with the
nil identity is the lone wildcard), and a complexContent extension's union with
its base wildcard (XSDComplexContent) is untouched. The inexpressible
##other/##other-different-target and mismatched processContents cases resolve to
the less restrictive result, so the change never over-rejects.

XSTS invalid-instances-accepted 94 to 89, no false positive (valid-instances-
rejected and valid-schemas-rejected held at 0 across the whole corpus), no other
bucket moved. An adversarial review exercised nested groups, ##other/##local and
cross-import namespace cases, processContents, extension-vs-restriction, and
single-source invariance, and found no new over-rejection.
2026-06-18 11:21:32 +02:00

65 lines
3.6 KiB
Swift

@testable import PureXML
import Testing
/// A complex type's effective `{attribute wildcard}` is the INTERSECTION of the
/// wildcards it draws from its own `anyAttribute` and from each referenced
/// attribute group (XSD 1.0 cos-aw-intersect): an attribute is admitted only if
/// every source admits it. Combining several `anyAttribute`s by union instead let
/// an attribute through that only one source allowed (W3C sun test007).
@Suite("Attribute wildcard intersection")
struct SchemaAttributeWildcardIntersectionTests {
/// Two attribute groups whose namespaces do not overlap intersect to the empty
/// wildcard, so an attribute in either single namespace is rejected.
@Test("disjoint attribute-group wildcards admit nothing")
func test_disjointIntersectionRejects() throws {
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:foo" xmlns="urn:foo">
<xs:element name="emptywc"><xs:complexType>
<xs:attributeGroup ref="ga"/>
<xs:attributeGroup ref="gb"/>
</xs:complexType></xs:element>
<xs:attributeGroup name="ga"><xs:anyAttribute processContents="skip" namespace="urn:a"/></xs:attributeGroup>
<xs:attributeGroup name="gb"><xs:anyAttribute processContents="skip" namespace="urn:b"/></xs:attributeGroup>
</xs:schema>
""")
#expect(try !schema.validate(#"<emptywc xmlns="urn:foo" xmlns:a="urn:a" a:x="1"/>"#).isEmpty)
#expect(try !schema.validate(#"<emptywc xmlns="urn:foo" xmlns:b="urn:b" b:x="1"/>"#).isEmpty)
}
/// A group restricted to one namespace intersected with a broader local
/// `anyAttribute` keeps only the shared namespace: that one is admitted, the
/// others are rejected. This is the FP-guard: the intersection must NOT reject
/// the attribute both sources allow.
@Test("overlapping wildcards admit the shared namespace only")
func test_overlapKeepsSharedNamespace() throws {
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:foo" xmlns="urn:foo">
<xs:element name="justA"><xs:complexType>
<xs:attributeGroup ref="ga"/>
<xs:anyAttribute processContents="skip" namespace="urn:a urn:b urn:c"/>
</xs:complexType></xs:element>
<xs:attributeGroup name="ga"><xs:anyAttribute processContents="skip" namespace="urn:a"/></xs:attributeGroup>
</xs:schema>
""")
// urn:a is in both sources: admitted (no false positive).
#expect(try schema.validate(#"<justA xmlns="urn:foo" xmlns:a="urn:a" a:x="1"/>"#).isEmpty)
// urn:b is only in the local wildcard, not the group: rejected.
#expect(try !schema.validate(#"<justA xmlns="urn:foo" xmlns:b="urn:b" b:x="1"/>"#).isEmpty)
}
/// A type with a SINGLE `anyAttribute` source is unchanged: intersection with no
/// other source leaves the lone wildcard intact, so a permissive `##any` still
/// admits everything.
@Test("a single anyAttribute source is unaffected by intersection")
func test_singleSourceUnchanged() throws {
let schema = try PureXML.Schema.Document("""
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:foo" xmlns="urn:foo">
<xs:element name="open"><xs:complexType>
<xs:anyAttribute processContents="skip"/>
</xs:complexType></xs:element>
</xs:schema>
""")
#expect(try schema.validate(#"<open xmlns="urn:foo" xmlns:a="urn:a" a:x="1" b="2"/>"#).isEmpty)
}
}