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.
65 lines
3.6 KiB
Swift
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)
|
|
}
|
|
}
|