XSD 1.0 §3.11.4 (cvc-identity-constraint.3) requires each key/keyref/unique field to identify a node of simple type. A field that reaches an element declared with no type (the ur-type anyType) or an inline complex type without simple content does not, so the instance is invalid; the reference (libxml2) rejects it identically. PureXML accepted such instances because its identity validator could resolve a field target's type only for a global element reached by a direct-child name, not for a local element reached through a path (the W3C XSTS case idZ010 nests anyType fields several levels deep, e.g. a key field ./b/c where c has no type). A compile-time pass (XSDParser.identityNonSimpleFieldKeys) resolves each field's target element through its selector and field paths, by element-name steps, and records the fields whose declared target is provably non-simple. The validator rejects those at runtime unless the instance gives the node a simple type through xsi:type, since cvc reads the node's governing type (a text-only anyType element with xsi:type="xsd:int" is a valid field). The resolution is conservative: any wildcard, descendant axis, predicate, ref, or named non-builtin type leaves the field unflagged, so a valid field is never rejected. The one residual divergence path, two same-name child elements of differing simplicity in one inline model, is an Element-Declarations-Consistent violation the compiler already rejects. Verified over the full W3C XSTS corpus in release: invalid-instances-accepted 8 -> 7 (idZ010 now rejects), valid-instances-rejected held at 0 across all 14383 groups (no false positive), full suite (1837 tests), wasm, and lint all green. An adversarial critic that read every file and probed for a false positive could not construct one; the failure mode is under-rejection, the safe direction.
84 lines
3.8 KiB
Swift
84 lines
3.8 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
/// cvc-identity-constraint.3: a `key`/`keyref`/`unique` field must identify a node
|
|
/// of simple type. A field that reaches an element whose declared type is the
|
|
/// ur-type `anyType` (no type) or a complex type without simple content is invalid,
|
|
/// unless the instance gives the node a simple type through `xsi:type`. Mirrors the
|
|
/// W3C XSTS idZ010 shape (a `key`/`keyref` whose fields select no-type elements
|
|
/// nested through a path), the case PureXML previously accepted.
|
|
@Suite("Identity-constraint field must select a simple-typed node")
|
|
struct SchemaIdentityFieldSimpleTypeTests {
|
|
/// idZ010 in miniature: `root/a` carries a `key` whose field path reaches `c`,
|
|
/// declared with no type (so `anyType`). The field is not simple, so the
|
|
/// instance is invalid even though the value would otherwise be a fine key.
|
|
private func anyTypeFieldSchema() throws -> PureXML.Schema.Document {
|
|
try PureXML.Schema.Document("""
|
|
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
<xsd:element name="root">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="a" maxOccurs="unbounded">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="b">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="c"/>
|
|
</xsd:sequence></xsd:complexType>
|
|
</xsd:element>
|
|
</xsd:sequence></xsd:complexType>
|
|
</xsd:element>
|
|
</xsd:sequence></xsd:complexType>
|
|
<xsd:key name="k">
|
|
<xsd:selector xpath="a"/>
|
|
<xsd:field xpath="./b/c"/>
|
|
</xsd:key>
|
|
</xsd:element>
|
|
</xsd:schema>
|
|
""")
|
|
}
|
|
|
|
@Test("a key field reaching a no-type (anyType) element is rejected")
|
|
func test_anyTypeFieldRejected() throws {
|
|
let schema = try anyTypeFieldSchema()
|
|
let errors = try schema.validate("<root><a><b><c>1</c></b></a></root>")
|
|
#expect(!errors.isEmpty)
|
|
#expect(errors.contains { $0.reason.contains("simple content") })
|
|
}
|
|
|
|
@Test("a simple-typed field at the same path is accepted")
|
|
func test_simpleTypedFieldAccepted() throws {
|
|
let schema = try PureXML.Schema.Document("""
|
|
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
|
|
<xsd:element name="root">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="a" maxOccurs="unbounded">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="b">
|
|
<xsd:complexType><xsd:sequence>
|
|
<xsd:element name="c" type="xsd:int"/>
|
|
</xsd:sequence></xsd:complexType>
|
|
</xsd:element>
|
|
</xsd:sequence></xsd:complexType>
|
|
</xsd:element>
|
|
</xsd:sequence></xsd:complexType>
|
|
<xsd:key name="k"><xsd:selector xpath="a"/><xsd:field xpath="./b/c"/></xsd:key>
|
|
</xsd:element>
|
|
</xsd:schema>
|
|
""")
|
|
#expect(try schema.validate("<root><a><b><c>1</c></b></a><a><b><c>2</c></b></a></root>").isEmpty)
|
|
}
|
|
|
|
@Test("an anyType field rescued by a simple xsi:type is accepted")
|
|
func test_anyTypeRescuedByXsiType() throws {
|
|
// The element is declared anyType, but the instance gives it xsi:type=int,
|
|
// so its governing type is simple and the field is valid (cvc reads the
|
|
// governing type). This is the guard that keeps the check from a false
|
|
// positive on a legitimately simple-at-runtime node.
|
|
let schema = try anyTypeFieldSchema()
|
|
let xml = """
|
|
<root xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\
|
|
<a><b><c xsi:type="xsd:int">1</c></b></a></root>
|
|
"""
|
|
#expect(try schema.validate(xml).isEmpty)
|
|
}
|
|
}
|