PureXML/Tests/SchemaSimpleContentChainTests.swift
Mihaela Mihaljevic ad80877d27 fix(schema): resolve a simpleContent base through complex-type chains; invalid-instances-accepted 39 to 37 (#147)
A complex type's simpleContent derivation can name another complex type with
simpleContent as its base. simpleContentType resolved a non-XSD, non-simpleType
base via context.simpleTypes only, so a complex-type base (absent from that map)
fell back to xs:string and left the content unconstrained: text invalid for the
real base type was accepted (baseTD00101, where Test restricts Test2 which extends
xs:int, yet "b" was accepted).

simpleContentBaseType now resolves the base to an XSD built-in, a named simple
type, or, when the base is a complex type with simpleContent, that type's own
resolved simple type by recursing through complexTypeNodes (cycle-guarded by a
visited set); the deriving node's facets are then applied over the resolved base's
facets. So Test resolves to int and non-int text is rejected, while a valid int
and the accumulated facets along the chain are honoured.

invalid-instances-accepted 39 to 37, no false positive (valid-instances-rejected
and valid-schemas-rejected held at 0), no other bucket moved. An adversarial
review exercised facet accumulation, named-simpleType bases in the chain,
multi-level and extension chains, and cycles, and found no over-rejection.
2026-06-18 15:44:54 +02:00

39 lines
1.8 KiB
Swift

@testable import PureXML
import Testing
/// A `simpleContent` derivation whose base is another complex type with
/// simpleContent resolves through the chain to the underlying simple type, so the
/// element's text is validated against it. A type restricting (or extending) a
/// complex type that ultimately derives from `xs:int` validates its content as an
/// int, not as unconstrained string (W3C sun baseTD00101).
@Suite("simpleContent base chain")
struct SchemaSimpleContentChainTests {
private func schema(method: String) throws -> PureXML.Schema.Document {
try PureXML.Schema.Document("""
<xsd:schema xmlns="u" xmlns:xsd="http://www.w3.org/2001/XMLSchema" targetNamespace="u">
<xsd:element name="root" type="Test"/>
<xsd:complexType name="Test"><xsd:simpleContent>
<xsd:\(method) base="Test2"/>
</xsd:simpleContent></xsd:complexType>
<xsd:complexType name="Test2"><xsd:simpleContent>
<xsd:extension base="xsd:int"/>
</xsd:simpleContent></xsd:complexType>
</xsd:schema>
""")
}
@Test("text invalid for the chained int base is rejected, valid int accepted")
func test_restrictionChainValidatesAsInt() throws {
let restriction = try schema(method: "restriction")
#expect(try !restriction.validate(#"<root xmlns="u">b</root>"#).isEmpty)
#expect(try restriction.validate(#"<root xmlns="u">42</root>"#).isEmpty)
}
@Test("an extension chain to int also validates the content as int")
func test_extensionChainValidatesAsInt() throws {
let extension0 = try schema(method: "extension")
#expect(try !extension0.validate(#"<root xmlns="u">b</root>"#).isEmpty)
#expect(try extension0.validate(#"<root xmlns="u">42</root>"#).isEmpty)
}
}