A particle restriction whose restricting element has a union-typed base element was checked by structural member subsetting: PureXML accepted a separately declared union whenever its members were a subset of the base union's. That is not the spec's derivation relation. The NameAndTypeOK type clause (XSD 1.0 section 3.9.6) requires the restricting element's type to be validly derived from the base element's type per Type Derivation OK (Simple), cos-st-derived-ok. The restricting type is now valid over a union base only if it is the base type, derives from it through a declared restriction chain, or (clause 2.2.4) validly derives from one of the union's member types. Scoped to the element-restriction path, so no attribute or simple-type derivation check moved. Catches particlesIk011 (a fresh union(integer) is not a restriction of union(integer, string)): XSTS invalid-schemas-accepted 9 to 8, valid-schemas- rejected held at 0 over the full corpus (the cardinal no-over-rejection check). Disclosed: particlesIk025/Ik027, where the restricting type derives from a union member, are accepted per cos-st-derived-ok 2.2.4, a contested reading their XSTS invalid expectation does not share. An in-repo test that asserted the old structural-subset behavior is corrected.
374 lines
22 KiB
Swift
374 lines
22 KiB
Swift
import Testing
|
|
@testable import PureXML
|
|
|
|
@Suite("XSD Particle Valid (Restriction)")
|
|
struct XSDParticleRestrictionTests {
|
|
private let xsNamespace = "xmlns:xs=\"http://www.w3.org/2001/XMLSchema\""
|
|
|
|
private func schema(base: String, restriction: String) -> String {
|
|
"""
|
|
<xs:schema \(xsNamespace)>
|
|
<xs:complexType name="B">\(base)</xs:complexType>
|
|
<xs:complexType name="R">
|
|
<xs:complexContent><xs:restriction base="B">\(restriction)</xs:restriction></xs:complexContent>
|
|
</xs:complexType>
|
|
<xs:element name="r" type="R"/>
|
|
</xs:schema>
|
|
"""
|
|
}
|
|
|
|
private func compiles(base: String, restriction: String) -> Bool {
|
|
(try? PureXML.Schema.Document(schema(base: base, restriction: restriction))) != nil
|
|
}
|
|
|
|
private func restrictionError(base: String, restriction: String) -> String? {
|
|
do {
|
|
_ = try PureXML.Schema.Document(schema(base: base, restriction: restriction))
|
|
return nil
|
|
} catch let error as PureXML.Schema.SchemaError {
|
|
return String(describing: error)
|
|
} catch {
|
|
return String(describing: error)
|
|
}
|
|
}
|
|
|
|
@Test("Valid restrictions compile: drop an optional, narrow a choice, keep order")
|
|
func test_validRestrictions() {
|
|
// Dropping an optional element.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/><xs:element name=\"b\" type=\"xs:string\" minOccurs=\"0\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
))
|
|
// Narrowing a choice to one branch.
|
|
#expect(compiles(
|
|
base: "<xs:choice><xs:element name=\"a\" type=\"xs:string\"/><xs:element name=\"b\" type=\"xs:string\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"a\" type=\"xs:string\"/></xs:choice>",
|
|
))
|
|
// Narrowing an occurrence range.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\" maxOccurs=\"unbounded\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" maxOccurs=\"2\"/></xs:sequence>",
|
|
))
|
|
// A maxOccurs=0 particle never occurs (pointless particle); it is removed
|
|
// before mapping, so it must not consume the base wildcard the next element
|
|
// restricts (particlesJd).
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:any namespace=\"##any\" minOccurs=\"0\" maxOccurs=\"unbounded\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"e1\" minOccurs=\"0\" maxOccurs=\"0\"/><xs:element name=\"e2\"/></xs:sequence>",
|
|
))
|
|
// A content-free restriction (an empty sequence) of an EMPTY base is valid:
|
|
// both accept only the empty sequence (addB079).
|
|
#expect(compiles(
|
|
base: "<xs:complexContent><xs:restriction base=\"xs:anyType\"/></xs:complexContent>",
|
|
restriction: "<xs:sequence/>",
|
|
))
|
|
}
|
|
|
|
@Test("An all-maxOccurs=0 derived sequence does not vacuously restrict a required base")
|
|
func test_emptyDerivedRequiredBase() {
|
|
// After pointless-particle removal the derived sequence is empty (accepts only
|
|
// the empty sequence); restricting a required choice {a|b} is invalid.
|
|
#expect(restrictionError(
|
|
base: "<xs:choice><xs:element name=\"a\" type=\"xs:string\"/><xs:element name=\"b\" type=\"xs:string\"/></xs:choice>",
|
|
restriction: "<xs:sequence><xs:element name=\"x\" minOccurs=\"0\" maxOccurs=\"0\"/></xs:sequence>",
|
|
)?.contains("not a valid restriction") == true)
|
|
// But restricting an emptiable base choice {a?|b} is valid.
|
|
#expect(compiles(
|
|
base: "<xs:choice><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\"/><xs:element name=\"b\" type=\"xs:string\"/></xs:choice>",
|
|
restriction: "<xs:sequence><xs:element name=\"x\" minOccurs=\"0\" maxOccurs=\"0\"/></xs:sequence>",
|
|
))
|
|
}
|
|
|
|
@Test("A restriction widening the occurrence range is rejected")
|
|
func test_wideningOccurrenceRejected() {
|
|
let error = restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" maxOccurs=\"2\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" maxOccurs=\"unbounded\"/></xs:sequence>",
|
|
)
|
|
#expect(error?.contains("not a valid restriction") == true, "\(String(describing: error))")
|
|
}
|
|
|
|
@Test("A restriction introducing a new element name is rejected")
|
|
func test_newNameRejected() {
|
|
let error = restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"z\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
#expect(error?.contains("not a valid restriction") == true)
|
|
}
|
|
|
|
@Test("A restriction reordering a sequence is rejected")
|
|
func test_reorderRejected() {
|
|
let error = restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/><xs:element name=\"b\" type=\"xs:string\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"b\" type=\"xs:string\"/><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
#expect(error?.contains("not a valid restriction") == true)
|
|
}
|
|
|
|
@Test("Restricting to empty requires an emptiable base")
|
|
func test_emptyRestriction() {
|
|
// Emptiable base: fine.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\"/></xs:sequence>",
|
|
restriction: "",
|
|
))
|
|
// Required base content: rejected.
|
|
let error = restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
restriction: "",
|
|
)
|
|
#expect(error?.contains("cannot be EMPTY") == true, "\(String(describing: error))")
|
|
}
|
|
|
|
@Test("An element restricts a wildcard only when its namespace is admitted")
|
|
func test_wildcardRestriction() {
|
|
// ##any admits the unqualified element.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:any processContents=\"lax\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
))
|
|
// ##other refuses an unqualified element.
|
|
let error = restrictionError(
|
|
base: "<xs:sequence><xs:any namespace=\"##other\" processContents=\"lax\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
#expect(error?.contains("not a valid restriction") == true)
|
|
}
|
|
|
|
@Test("A single-member derived sequence narrowing an occurrence restricts a wider base sequence")
|
|
func test_singleMemberSequenceNarrowsOccurrence() {
|
|
// particlesW008: the derived sequence has one member, so it normalizes to the
|
|
// bare element e1{2,3}; that must still be matched as a member of the base
|
|
// sequence (RecurseAsIfGroup), not have its {2,3} compared to the base
|
|
// sequence's own {1,1} occurrence.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"e1\" maxOccurs=\"3\"/>"
|
|
+ "<xs:element name=\"e2\" minOccurs=\"0\" maxOccurs=\"3\"/>"
|
|
+ "<xs:element name=\"e3\" minOccurs=\"0\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"e1\" minOccurs=\"2\" maxOccurs=\"3\"/></xs:sequence>",
|
|
))
|
|
// particlesM002: a derived single-branch choice normalizes to the bare element
|
|
// and still restricts a choice of sequences whose first branch begins with it.
|
|
#expect(compiles(
|
|
base: "<xs:choice><xs:sequence minOccurs=\"1\" maxOccurs=\"3\">"
|
|
+ "<xs:element name=\"c1\" minOccurs=\"2\" maxOccurs=\"2\"/><xs:element name=\"c2\" minOccurs=\"0\"/></xs:sequence>"
|
|
+ "<xs:sequence><xs:element name=\"d1\"/><xs:element name=\"d2\" minOccurs=\"0\"/></xs:sequence></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" minOccurs=\"2\" maxOccurs=\"2\"/></xs:choice>",
|
|
))
|
|
}
|
|
|
|
@Test("A group restricting a wildcard is valid only when its effective count fits the wildcard's range")
|
|
func test_groupRestrictsWildcardCardinality() {
|
|
// particlesHa: a sequence of named elements restricts a wildcard when every leaf
|
|
// is admitted AND the effective total count lies within the wildcard's range.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:any namespace=\"##any\" processContents=\"lax\" minOccurs=\"2\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\"/><xs:element name=\"b\"/></xs:sequence>",
|
|
))
|
|
// Too many leaves: three elements exceed the wildcard's maxOccurs of 2.
|
|
#expect(restrictionError(
|
|
base: "<xs:sequence><xs:any namespace=\"##any\" processContents=\"lax\" minOccurs=\"2\" maxOccurs=\"2\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\"/><xs:element name=\"b\"/><xs:element name=\"c\"/></xs:sequence>",
|
|
)?.contains("not a valid restriction") == true)
|
|
// Too few leaves: one element is below the wildcard's minOccurs of 2.
|
|
#expect(restrictionError(
|
|
base: "<xs:sequence><xs:any namespace=\"##any\" processContents=\"lax\" minOccurs=\"2\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\"/></xs:sequence>",
|
|
)?.contains("not a valid restriction") == true)
|
|
}
|
|
|
|
@Test("A content-bearing derived group never restricts a base single element")
|
|
func test_groupRestrictsSingleElement() {
|
|
// mgE014: base sequence(title?) normalizes to the bare element title{0,1};
|
|
// the derived sequence(title{0,0}) normalizes to the empty (content-free)
|
|
// group, which accepts only the empty sequence, valid against an emptiable
|
|
// element. This is the content-free path, not a derived-group/base-element rule.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"title\" minOccurs=\"0\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"title\" minOccurs=\"0\" maxOccurs=\"0\"/></xs:sequence>",
|
|
))
|
|
// XSD 1.0 §3.9.6 defines NO restriction rule for a derived group against a base
|
|
// element (only the reverse, an element against a base group). A content-bearing
|
|
// derived group restricting a single base element is therefore invalid, even
|
|
// when its leaves share the element's name and its effective count fits.
|
|
#expect(restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:choice maxOccurs=\"2\"><xs:element name=\"a\"/></xs:choice>",
|
|
)?.contains("not a valid restriction") == true)
|
|
// The same holds when the derived group's effective count exceeds the base
|
|
// element's maxOccurs, and when a leaf is differently named.
|
|
#expect(restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:sequence maxOccurs=\"10\"><xs:element name=\"a\"/></xs:sequence>",
|
|
)?.contains("not a valid restriction") == true)
|
|
#expect(restrictionError(
|
|
base: "<xs:sequence><xs:element name=\"a\" maxOccurs=\"3\"/></xs:sequence>",
|
|
restriction: "<xs:choice maxOccurs=\"2\"><xs:element name=\"b\"/></xs:choice>",
|
|
)?.contains("not a valid restriction") == true)
|
|
}
|
|
|
|
@Test("Conformance: the restriction corpus passes through the harness")
|
|
func test_conformanceCases() {
|
|
let valid = compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\" minOccurs=\"0\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
let invalid = compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/><xs:element name=\"b\" type=\"xs:string\"/></xs:sequence>",
|
|
)
|
|
let cases = [
|
|
PureXML.Validation.ConformanceCase(name: "faithful-restriction-accepted", actual: valid ? "valid" : "invalid", expected: "valid"),
|
|
PureXML.Validation.ConformanceCase(name: "additive-restriction-rejected", actual: invalid ? "valid" : "invalid", expected: "invalid"),
|
|
]
|
|
let failures = PureXML.Validation.Conformance.failures(in: cases)
|
|
#expect(failures.isEmpty, "\(failures.map(\.reason))")
|
|
}
|
|
|
|
@Test("NameAndTypeOK: a same-name element must keep a type that derives from the base's")
|
|
func test_elementTypeDerivation() {
|
|
// Renaming the element's type to an incompatible one (string -> int) is not a
|
|
// valid restriction: int does not derive from string.
|
|
#expect(!compiles(
|
|
base: "<xs:choice><xs:element name=\"c1\" type=\"xs:string\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" type=\"xs:int\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
))
|
|
// Narrowing to a derived type (integer -> int) is valid.
|
|
#expect(compiles(
|
|
base: "<xs:choice><xs:element name=\"c1\" type=\"xs:integer\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" type=\"xs:int\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
))
|
|
// Keeping the same type is valid.
|
|
#expect(compiles(
|
|
base: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
restriction: "<xs:sequence><xs:element name=\"a\" type=\"xs:string\"/></xs:sequence>",
|
|
))
|
|
}
|
|
|
|
@Test("NameAndTypeOK: widening a concrete-typed element to the ur-type is rejected")
|
|
func test_elementUrTypeWidening() {
|
|
// Restricting a string element to anyType/anySimpleType is widening, invalid.
|
|
#expect(!compiles(
|
|
base: "<xs:choice><xs:element name=\"c1\" type=\"xs:string\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" type=\"xs:anyType\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
))
|
|
#expect(!compiles(
|
|
base: "<xs:choice><xs:element name=\"c1\" type=\"xs:string\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" type=\"xs:anySimpleType\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
))
|
|
// The reverse (base is the ur-type, restriction narrows it) is valid.
|
|
#expect(compiles(
|
|
base: "<xs:choice><xs:element name=\"c1\" type=\"xs:anyType\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
restriction: "<xs:choice><xs:element name=\"c1\" type=\"xs:string\"/><xs:element name=\"c2\"/></xs:choice>",
|
|
))
|
|
// A user type whose local name is "anyType" (not the XSD ur-type) must not be
|
|
// mistaken for it: this is a valid same-type restriction.
|
|
#expect((try? PureXML.Schema.Document("""
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="urn:t" xmlns:t="urn:t">
|
|
<xs:simpleType name="anyType"><xs:restriction base="xs:string"/></xs:simpleType>
|
|
<xs:complexType name="B"><xs:choice><xs:element name="c1" type="t:anyType"/></xs:choice></xs:complexType>
|
|
<xs:complexType name="R"><xs:complexContent><xs:restriction base="t:B">
|
|
<xs:choice><xs:element name="c1" type="t:anyType"/></xs:choice>
|
|
</xs:restriction></xs:complexContent></xs:complexType>
|
|
</xs:schema>
|
|
""")) != nil)
|
|
}
|
|
|
|
@Test("NameAndTypeOK over named user and union types")
|
|
func test_elementUserTypeDerivation() {
|
|
func doc(_ elementC1: String, base baseC1: String, types: String) -> PureXML.Schema.Document? {
|
|
try? PureXML.Schema.Document("""
|
|
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
|
|
\(types)
|
|
<xs:complexType name="B"><xs:choice><xs:element name="c1" type="\(baseC1)"/></xs:choice></xs:complexType>
|
|
<xs:complexType name="R"><xs:complexContent><xs:restriction base="B">
|
|
<xs:choice><xs:element name="c1" type="\(elementC1)"/></xs:choice>
|
|
</xs:restriction></xs:complexContent></xs:complexType>
|
|
</xs:schema>
|
|
""")
|
|
}
|
|
let userTypes = """
|
|
<xs:simpleType name="foo"><xs:restriction base="xs:string"/></xs:simpleType>
|
|
<xs:simpleType name="bar"><xs:restriction base="foo"><xs:maxLength value="3"/></xs:restriction></xs:simpleType>
|
|
<xs:simpleType name="other"><xs:restriction base="xs:integer"/></xs:simpleType>
|
|
<xs:simpleType name="u"><xs:union memberTypes="xs:decimal xs:string"/></xs:simpleType>
|
|
"""
|
|
// bar restricts foo: a valid narrowing of a user type.
|
|
#expect(doc("bar", base: "foo", types: userTypes) != nil)
|
|
// foo restricting bar widens (foo is bar's base): invalid.
|
|
#expect(doc("foo", base: "bar", types: userTypes) == nil)
|
|
// two unrelated user types: invalid.
|
|
#expect(doc("other", base: "foo", types: userTypes) == nil)
|
|
// a union member (xs:string) validly restricts the union-typed base element.
|
|
#expect(doc("xs:string", base: "u", types: userTypes) != nil)
|
|
|
|
let unions = """
|
|
<xs:simpleType name="U1"><xs:union memberTypes="xs:integer xs:string"/></xs:simpleType>
|
|
<xs:simpleType name="U2"><xs:union memberTypes="xs:integer xs:string xs:boolean"/></xs:simpleType>
|
|
<xs:simpleType name="L1"><xs:list itemType="xs:string"/></xs:simpleType>
|
|
"""
|
|
// A separately-declared union whose members merely subset the base union's
|
|
// is NOT a valid derivation: cos-st-derived-ok requires the restricting
|
|
// type to be the base, derive from it through a declared restriction chain,
|
|
// or derive from one of its members. Structural member subsetting is not
|
|
// the spec's derivation relation (cf. XSTS particlesIk011).
|
|
#expect(doc("U1", base: "U2", types: unions) == nil)
|
|
// The reverse (U2 has a member U1 lacks) is invalid for the same reason.
|
|
#expect(doc("U2", base: "U1", types: unions) == nil)
|
|
// a list type restricting a union type: invalid.
|
|
#expect(doc("L1", base: "U1", types: unions) == nil)
|
|
}
|
|
}
|
|
|
|
@Suite("XSD element-reference namespace resolution")
|
|
struct XSDElementRefNamespaceTests {
|
|
@Test("An element reference resolves to its prefix's namespace, not the target namespace")
|
|
func test_elementRefNamespaceResolution() {
|
|
// particlesJj001: a ref to an imported element (imp -> http://importedXSD)
|
|
// restricts a `##other` wildcard. `##other` admits any namespace but the
|
|
// target, so an imported element is admitted and the restriction is valid;
|
|
// resolving the ref into the target namespace would make `##other` reject it.
|
|
let imported = "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:x=\"http://xsdtesting\""
|
|
+ " targetNamespace=\"http://xsdtesting\" xmlns:imp=\"http://importedXSD\">"
|
|
+ "<xsd:import namespace=\"http://importedXSD\"/>"
|
|
+ "<xsd:complexType name=\"B\"><xsd:sequence><xsd:any namespace=\"##other\" minOccurs=\"0\"/></xsd:sequence></xsd:complexType>"
|
|
+ "<xsd:complexType name=\"R\"><xsd:complexContent><xsd:restriction base=\"x:B\">"
|
|
+ "<xsd:sequence><xsd:element ref=\"imp:impElem1\" minOccurs=\"0\"/></xsd:sequence>"
|
|
+ "</xsd:restriction></xsd:complexContent></xsd:complexType></xsd:schema>"
|
|
#expect((try? PureXML.Schema.Document(imported)) != nil)
|
|
// The same shape with a ref into the TARGET namespace (a declared global) is
|
|
// invalid: `##other` forbids the target namespace, so the element is not
|
|
// admitted. This proves the resolved namespace is actually checked.
|
|
let targetNs = "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:x=\"http://xsdtesting\""
|
|
+ " targetNamespace=\"http://xsdtesting\" elementFormDefault=\"qualified\">"
|
|
+ "<xsd:element name=\"tElem\" type=\"xsd:string\"/>"
|
|
+ "<xsd:complexType name=\"B\"><xsd:sequence><xsd:any namespace=\"##other\" minOccurs=\"0\"/></xsd:sequence></xsd:complexType>"
|
|
+ "<xsd:complexType name=\"R\"><xsd:complexContent><xsd:restriction base=\"x:B\">"
|
|
+ "<xsd:sequence><xsd:element ref=\"x:tElem\" minOccurs=\"0\"/></xsd:sequence>"
|
|
+ "</xsd:restriction></xsd:complexContent></xsd:complexType></xsd:schema>"
|
|
#expect((try? PureXML.Schema.Document(targetNs)) == nil)
|
|
}
|
|
|
|
@Test("An unprefixed ref resolves through the default namespace, qualifying an instance element")
|
|
func test_unprefixedRefUsesDefaultNamespace() {
|
|
// The spec-faithful common idiom: the target namespace is also the default
|
|
// xmlns, so a bare ref="child" resolves to the target namespace and a
|
|
// target-namespace instance element validates against it.
|
|
let schema = "<xsd:schema xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns=\"urn:T\""
|
|
+ " targetNamespace=\"urn:T\" elementFormDefault=\"qualified\">"
|
|
+ "<xsd:element name=\"child\" type=\"xsd:string\"/>"
|
|
+ "<xsd:element name=\"root\"><xsd:complexType><xsd:sequence>"
|
|
+ "<xsd:element ref=\"child\"/></xsd:sequence></xsd:complexType></xsd:element></xsd:schema>"
|
|
guard let document = try? PureXML.Schema.Document(schema) else {
|
|
Issue.record("schema should compile")
|
|
return
|
|
}
|
|
#expect((try? document.validate("<root xmlns=\"urn:T\"><child>x</child></root>"))?.isEmpty == true)
|
|
// A child in the wrong namespace is rejected (the resolution is real, not a
|
|
// blanket accept).
|
|
let wrong = "<root xmlns=\"urn:T\"><child xmlns=\"urn:OTHER\">x</child></root>"
|
|
#expect((try? document.validate(wrong))?.isEmpty != true)
|
|
}
|
|
}
|