import Testing @testable import PureXML /// The XML 1.0 errata behaviors from the eduni burn-down: 3.3.3 attribute- /// value normalization, the lenient undeclared-entity path (production 68), /// the E15 family (references and comments in EMPTY elements, character- /// reference whitespace in element content), E2 duplicate enumeration /// tokens, and E14 declarations completed inside PE replacements. @Suite("XML errata validity") struct ErrataValidityTests { private func limits() -> PureXML.Parsing.Limits { PureXML.Parsing.Limits(allowDoctype: true) } private func errors(_ xml: String, strict: Bool = false) throws -> [String] { try PureXML.validateAgainstInternalDTD(xml, strict: strict).map(\.reason) } @Test("Literal whitespace in attribute values normalizes to spaces; character references survive") func test_attributeValueNormalization() throws { let node = try PureXML.parse("") guard case let .document(children) = node, case let .element(element)? = children.first else { Issue.record("no element") return } #expect(element.attributes[0].value == "1 2 3") #expect(element.attributes[1].value == "x\ty") } @Test("With an unread external subset, an undeclared entity is a finding, not a fatal error") func test_lenientUndeclaredEntity() throws { let xml = "]>\n&mystery;" let found = try errors(xml) #expect(found.contains { $0.contains("'&mystery;' is referenced but not declared") }) // Without any external declarations possible it stays fatal. #expect(throws: PureXML.Parsing.ParseError.self) { _ = try PureXML.parse("]>\n&mystery;", limits: limits()) } } @Test("E15: references, comments, and whitespace are content for EMPTY elements") func test_emptyElementContent() throws { let reference = "]>\n∅" #expect(try !errors(reference).isEmpty) let comment = "]>\n" #expect(try !errors(comment).isEmpty) let whitespace = "]>\n " #expect(try !errors(whitespace).isEmpty) let clean = "]>\n" #expect(try errors(clean).isEmpty) } @Test("E15: character-reference whitespace is data in element content; entity whitespace is not") func test_elementContentWhitespace() throws { let charRef = "]>\n " #expect(try !errors(charRef).isEmpty) let viaEntity = "]>\n&s;" #expect(try errors(viaEntity).isEmpty) let doubleEscape = "]>\n&s;" #expect(try !errors(doubleEscape).isEmpty) } @Test("E2: duplicate tokens in enumerated and NOTATION lists are reported") func test_duplicateEnumerationTokens() throws { let xml = "]>\n" #expect(try errors(xml).contains { $0.contains("repeats the token 'one'") }) } @Test("E14: a declaration completed inside a PE replacement parses and reports the VC") func test_declarationPENesting() throws { let dtd = "\n\">\n\n" let resolver = PureXML.Parsing.EntityResolver( resolveEntity: { _, _ in nil }, resolveExternalSubset: { _ in dtd }, ) let found = try PureXML.validateAgainstInternalDTD(xml, resolver: resolver).map(\.reason) #expect(found.contains { $0.contains("Proper Declaration/PE Nesting") }) } }