PureXML/Tests/ContentMatcherDifferentialTests.swift
Mihaela Mihaljevic 83e6e27d12 style(format): adopt --importgrouping testable-bottom from canonical rules-swift
Group @testable imports at the bottom, per public/rules-swift's swiftformat. A
behavior-preserving import-ordering reformat across 252 files (mostly tests).
--trailingclosures was deliberately not adopted (cosmetic, and swiftformat flags
it as caution-worthy).
2026-06-18 20:33:07 +02:00

77 lines
3.6 KiB
Swift

import Testing
@testable import PureXML
/// 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)")
}
}
}
}