import Testing @testable import PureXML /// A first Tier-2 conformance corpus, driven through the validation framework: /// each case runs a PureXML subsystem and the `Conformance` validation rule /// reports any divergence from the spec-authoritative expected output. @Suite("Conformance corpus (validation-driven)") struct ConformanceCorpusTests { private struct Spec { let name: String let input: String let expected: String } /// Canonical XML 1.0 (inclusive, no comments) conformance points, with the /// expected canonical form taken from the specification's rules. private func canonicalCorpus() throws -> [PureXML.Validation.ConformanceCase] { let specs = [ Spec(name: "attribute-ordering", input: "", expected: ""), Spec(name: "empty-element-expanded", input: "", expected: ""), Spec(name: "comments-removed", input: "x", expected: "x"), Spec(name: "cdata-becomes-escaped-text", input: "&]]>", expected: "<x>&"), Spec(name: "processing-instruction", input: "", expected: ""), Spec(name: "namespace-rendered", input: "", expected: ""), ] return try specs.map { spec in let actual = try PureXML.Canonical.canonicalize(PureXML.parse(spec.input)) return PureXML.Validation.ConformanceCase(name: spec.name, actual: actual, expected: spec.expected) } } private struct XPathSpec { let name: String let expression: String let expected: String } /// XPath 1.0 core-function conformance points: the function's result coerced /// to a string, against the value the specification mandates. private func xpathCorpus() throws -> [PureXML.Validation.ConformanceCase] { let document = try PureXML.parse("") let specs = [ XPathSpec(name: "concat", expression: "concat('a','b','c')", expected: "abc"), XPathSpec(name: "substring-length", expression: "substring('12345',2,3)", expected: "234"), XPathSpec(name: "substring-open", expression: "substring('12345',2)", expected: "2345"), XPathSpec(name: "string-length", expression: "string-length('hello')", expected: "5"), XPathSpec(name: "normalize-space", expression: "normalize-space(' a b ')", expected: "a b"), XPathSpec(name: "translate", expression: "translate('bar','abc','ABC')", expected: "BAr"), XPathSpec(name: "substring-before", expression: "substring-before('a/b','/')", expected: "a"), XPathSpec(name: "substring-after", expression: "substring-after('a/b','/')", expected: "b"), XPathSpec(name: "contains", expression: "contains('hello','ell')", expected: "true"), XPathSpec(name: "starts-with", expression: "starts-with('hello','he')", expected: "true"), XPathSpec(name: "round-half-up", expression: "round(2.5)", expected: "3"), XPathSpec(name: "floor", expression: "floor(2.9)", expected: "2"), XPathSpec(name: "ceiling", expression: "ceiling(2.1)", expected: "3"), XPathSpec(name: "count", expression: "count(//item)", expected: "3"), XPathSpec(name: "div-by-zero-infinity", expression: "1 div 0", expected: "Infinity"), XPathSpec(name: "zero-div-zero-nan", expression: "0 div 0", expected: "NaN"), ] return try specs.map { spec in let actual = try PureXML.XPath.Query(spec.expression).string(over: document) return PureXML.Validation.ConformanceCase(name: spec.name, actual: actual, expected: spec.expected) } } private struct DatatypeSpec { let name: String let base: String let facets: String let value: String let valid: Bool } /// XSD datatype and facet conformance: an instance is validated against an /// element whose simple type restricts `base` with `facets`, and the verdict /// (valid / invalid) is checked against the specification. private func datatypeCorpus() throws -> [PureXML.Validation.ConformanceCase] { let specs = [ DatatypeSpec(name: "integer-valid", base: "xs:integer", facets: "", value: "42", valid: true), DatatypeSpec(name: "integer-rejects-decimal", base: "xs:integer", facets: "", value: "4.2", valid: false), DatatypeSpec(name: "minInclusive-ok", base: "xs:int", facets: "", value: "5", valid: true), DatatypeSpec(name: "minInclusive-fail", base: "xs:int", facets: "", value: "-1", valid: false), DatatypeSpec(name: "maxInclusive-fail", base: "xs:int", facets: "", value: "11", valid: false), DatatypeSpec(name: "length-ok", base: "xs:string", facets: "", value: "abc", valid: true), DatatypeSpec(name: "length-fail", base: "xs:string", facets: "", value: "ab", valid: false), DatatypeSpec(name: "pattern-ok", base: "xs:string", facets: "", value: "abc", valid: true), DatatypeSpec(name: "pattern-fail", base: "xs:string", facets: "", value: "ab1", valid: false), DatatypeSpec(name: "enumeration-ok", base: "xs:string", facets: "", value: "red", valid: true), DatatypeSpec(name: "enumeration-fail", base: "xs:string", facets: "", value: "blue", valid: false), DatatypeSpec(name: "boolean-ok", base: "xs:boolean", facets: "", value: "true", valid: true), DatatypeSpec(name: "boolean-fail", base: "xs:boolean", facets: "", value: "yes", valid: false), ] return try specs.map { spec in let xsd = """ \(spec.facets) """ let errors = try PureXML.Schema.Document(xsd).validate("\(spec.value)") return PureXML.Validation.ConformanceCase( name: spec.name, actual: errors.isEmpty ? "valid" : "invalid", expected: spec.valid ? "valid" : "invalid", ) } } private struct RelaxNGSpec { let name: String let schema: String let xml: String let valid: Bool } /// RELAX NG pattern conformance: an instance is validated against a compact /// schema, and the verdict is checked against the pattern's semantics. private func relaxNGCorpus() throws -> [PureXML.Validation.ConformanceCase] { let specs = [ RelaxNGSpec(name: "text-ok", schema: "start = element a { text }", xml: "hi", valid: true), RelaxNGSpec(name: "text-rejects-child", schema: "start = element a { text }", xml: "", valid: false), RelaxNGSpec(name: "optional-present", schema: "start = element a { element b { text }? }", xml: "x", valid: true), RelaxNGSpec(name: "optional-absent", schema: "start = element a { element b { text }? }", xml: "", valid: true), RelaxNGSpec(name: "oneOrMore-fail", schema: "start = element a { element b { text }+ }", xml: "", valid: false), RelaxNGSpec(name: "choice-ok", schema: "start = element a { element b { text } | element c { text } }", xml: "y", valid: true), RelaxNGSpec(name: "group-order-fail", schema: "start = element a { element b { text }, element c { text } }", xml: "yx", valid: false), RelaxNGSpec(name: "attribute-required", schema: "start = element a { attribute id { text } }", xml: "", valid: false), RelaxNGSpec(name: "attribute-ok", schema: "start = element a { attribute id { text } }", xml: "", valid: true), RelaxNGSpec(name: "interleave-order-independent", schema: "start = element a { element b { text } & element c { text } }", xml: "yx", valid: true), RelaxNGSpec(name: "empty-ok", schema: "start = element a { empty }", xml: "", valid: true), RelaxNGSpec(name: "empty-rejects-text", schema: "start = element a { empty }", xml: "x", valid: false), ] return try specs.map { spec in let conforms = try PureXML.Schema.RelaxNG(compact: spec.schema).validate(spec.xml) return PureXML.Validation.ConformanceCase( name: spec.name, actual: conforms ? "valid" : "invalid", expected: spec.valid ? "valid" : "invalid", ) } } private struct XSLTSpec { let name: String let stylesheet: String let source: String let expected: String } /// A text-output stylesheet wrapping a single `match="/"` template body, so /// the result is exactly the body's output with no stray whitespace. private func textSheet(_ body: String) -> String { "" + "\(body)" } /// XSLT 1.0 transformation conformance: a stylesheet over a source, with the /// text result checked against what the spec prescribes. private func xsltCorpus() throws -> [PureXML.Validation.ConformanceCase] { let multiTemplate = "" + "" + "" + "[]" let calling = "" + "" + "" + "hi " let specs = [ XSLTSpec(name: "value-of", stylesheet: textSheet(""), source: "hi", expected: "hi"), XSLTSpec( name: "for-each", stylesheet: textSheet(""), source: "ab", expected: "ab", ), XSLTSpec(name: "if", stylesheet: textSheet("yes"), source: "1", expected: "yes"), XSLTSpec( name: "choose-otherwise", stylesheet: textSheet("noelse"), source: "", expected: "else", ), XSLTSpec( name: "sort", stylesheet: textSheet(""), source: "312", expected: "123", ), XSLTSpec(name: "variable", stylesheet: textSheet(""), source: "", expected: "x"), XSLTSpec(name: "format-number", stylesheet: textSheet(""), source: "", expected: "1,234.50"), XSLTSpec(name: "concat", stylesheet: textSheet(""), source: "xy", expected: "x-y"), XSLTSpec(name: "apply-templates", stylesheet: multiTemplate, source: "ab", expected: "[a][b]"), XSLTSpec(name: "call-template-param", stylesheet: calling, source: "", expected: "hi world"), ] return try specs.map { spec in try PureXML.Validation.ConformanceCase( name: spec.name, actual: PureXML.XSLT.transform(stylesheet: spec.stylesheet, source: spec.source), expected: spec.expected, ) } } /// HTML5 tree-construction conformance: the parsed-then-serialized fragment, /// against the normalized form the HTML5 parsing algorithm prescribes. The /// fragment parser now reconstructs active formatting elements (#109 fixed). private func htmlCorpus() -> [PureXML.Validation.ConformanceCase] { let specs = [ Spec(name: "void-elements", input: "

a
bc

", expected: "

a
bc

"), Spec(name: "implied-li-close", input: "
  • one
  • two
", expected: "
  • one
  • two
"), Spec(name: "implied-p-close", input: "

first

second", expected: "

first

second

"), Spec(name: "tag-case-normalized", input: "
x
", expected: "
x
"), Spec(name: "adoption-agency", input: "", expected: ""), Spec(name: "adoption-agency-with-text", input: "X", expected: "X"), Spec(name: "entities-decoded", input: "

a & b < c A

", expected: "

a & b < c A

"), Spec(name: "stray-end-tag-ignored", input: "bold", expected: "bold"), ] return specs.map { spec in PureXML.Validation.ConformanceCase( name: spec.name, actual: PureXML.HTML.serialize(PureXML.HTML.parse(spec.input)), expected: spec.expected, ) } } @Test("The C14N conformance corpus passes with no located failures") func test_corpusConforms() throws { let failures = try PureXML.Validation.Conformance.failures(in: canonicalCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("The XPath core-function conformance corpus passes with no located failures") func test_xpathCorpusConforms() throws { let failures = try PureXML.Validation.Conformance.failures(in: xpathCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("The XSD datatype/facet conformance corpus passes with no located failures") func test_datatypeCorpusConforms() throws { let failures = try PureXML.Validation.Conformance.failures(in: datatypeCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("The RELAX NG pattern conformance corpus passes with no located failures") func test_relaxNGCorpusConforms() throws { let failures = try PureXML.Validation.Conformance.failures(in: relaxNGCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("The XSLT transformation conformance corpus passes with no located failures") func test_xsltCorpusConforms() throws { let failures = try PureXML.Validation.Conformance.failures(in: xsltCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("The HTML5 tree-construction conformance corpus passes with no located failures") func test_htmlCorpusConforms() { let failures = PureXML.Validation.Conformance.failures(in: htmlCorpus()) #expect(failures.isEmpty, "\(failures.map(\.reason))") } @Test("A divergent case is reported as a located conformance failure") func test_divergenceReported() { let bad = PureXML.Validation.ConformanceCase(name: "wrong", actual: "", expected: "") let failures = PureXML.Validation.Conformance.failures(in: [bad]) #expect(failures.count == 1) #expect(failures.first?.codingPath.map(\.stringValue) == ["wrong"]) #expect(failures.first?.reason.contains("case 'wrong'") == true) } }