Complete the element-vs-element type clause of Particle Valid (Restriction): the restricting element's type must be, or derive by restriction from, the base element's type. Previously only names were compared, so an unrelated, wider, or ur-type element type was accepted under restriction. Enrich the content-model particle: Term.element carries the element type's derivation identity (the resolved type name, anyType for an absent type, nil for an inline anonymous type). Record named simpleType restriction derivations in the derivation table alongside complex ones. The element-vs-element check walks the user restriction chain and then the built-in lattice to decide derivation by name; an ur-type base, a union base (membership is not a restriction chain), and an inline anonymous type are permitted, so no valid restriction is rejected. XSTS: invalid schemas accepted 616 to 594, invalid instances accepted 165 to 160 (more invalid schemas rejected up front), valid schemas rejected held at 72, valid instances rejected held at 214.
77 lines
3.6 KiB
Swift
77 lines
3.6 KiB
Swift
@testable import PureXML
|
|
import Testing
|
|
|
|
/// Locks the two content-model matchers together (#115): the DTD matcher
|
|
/// (positional set closure) and the XSD matcher (Thompson NFA) solve the same
|
|
/// problem with different algorithms, so equivalent models must agree on every
|
|
/// child sequence.
|
|
@Suite("DTD vs XSD content-matcher differential")
|
|
struct ContentMatcherDifferentialTests {
|
|
private struct ModelCase {
|
|
let dtdModel: String
|
|
let xsdParticle: PureXML.Schema.Particle
|
|
let sequences: [(names: [String], valid: Bool)]
|
|
}
|
|
|
|
private func element(_ name: String, min: Int = 1, max: Int? = 1) -> PureXML.Schema.Particle {
|
|
.init(minOccurs: min, maxOccurs: max, term: .element(name: .init(name), type: nil, typeName: nil))
|
|
}
|
|
|
|
private func group(_ compositor: PureXML.Schema.Compositor, _ particles: [PureXML.Schema.Particle], min: Int = 1, max: Int? = 1) -> PureXML.Schema.Particle {
|
|
.init(minOccurs: min, maxOccurs: max, term: .group(.init(compositor: compositor, particles: particles)))
|
|
}
|
|
|
|
private var cases: [ModelCase] {
|
|
[
|
|
ModelCase(
|
|
dtdModel: "(a,b)",
|
|
xsdParticle: group(.sequence, [element("a"), element("b")]),
|
|
sequences: [(["a", "b"], true), (["b", "a"], false), (["a"], false), ([], false)],
|
|
),
|
|
ModelCase(
|
|
dtdModel: "(a|b)",
|
|
xsdParticle: group(.choice, [element("a"), element("b")]),
|
|
sequences: [(["a"], true), (["b"], true), (["a", "b"], false), ([], false)],
|
|
),
|
|
ModelCase(
|
|
dtdModel: "(a)*",
|
|
xsdParticle: group(.sequence, [element("a")], min: 0, max: nil),
|
|
sequences: [([], true), (["a"], true), (["a", "a", "a"], true), (["b"], false)],
|
|
),
|
|
ModelCase(
|
|
dtdModel: "(a)+",
|
|
xsdParticle: group(.sequence, [element("a")], min: 1, max: nil),
|
|
sequences: [([], false), (["a"], true), (["a", "a"], true)],
|
|
),
|
|
ModelCase(
|
|
dtdModel: "(a)?",
|
|
xsdParticle: group(.sequence, [element("a")], min: 0, max: 1),
|
|
sequences: [([], true), (["a"], true), (["a", "a"], false)],
|
|
),
|
|
ModelCase(
|
|
dtdModel: "((a,b)|c)",
|
|
xsdParticle: group(.choice, [group(.sequence, [element("a"), element("b")]), element("c")]),
|
|
sequences: [(["a", "b"], true), (["c"], true), (["a", "c"], false), (["a", "b", "c"], false)],
|
|
),
|
|
]
|
|
}
|
|
|
|
@Test("Equivalent DTD and XSD content models agree on every child sequence")
|
|
func test_matchersAgree() {
|
|
for modelCase in cases {
|
|
guard case let .children(particle) = PureXML.Validation.ContentModelParser.parse(modelCase.dtdModel) else {
|
|
Issue.record("DTD model '\(modelCase.dtdModel)' did not parse to element content")
|
|
continue
|
|
}
|
|
let nfa = PureXML.Schema.ContentNFABuilder.build(modelCase.xsdParticle)
|
|
for sequence in modelCase.sequences {
|
|
let dtd = PureXML.Validation.ContentModelMatcher.matchesChildren(particle, sequence.names)
|
|
let xsd = nfa.matchesWhole(sequence.names.map { PureXML.Model.QualifiedName($0) })
|
|
#expect(dtd == sequence.valid, "DTD '\(modelCase.dtdModel)' wrong for \(sequence.names)")
|
|
#expect(xsd == sequence.valid, "XSD '\(modelCase.dtdModel)' wrong for \(sequence.names)")
|
|
#expect(dtd == xsd, "matchers disagree on '\(modelCase.dtdModel)' for \(sequence.names)")
|
|
}
|
|
}
|
|
}
|
|
}
|