import Testing
@testable import PureXML
/// Content splicing (4.4.2 Included, #133): a general entity whose replacement
/// text contains markup is reparsed as content, so elements inside an entity
/// become elements in the tree and in the pull-event stream, with character
/// references expanded at declaration time (4.4.5).
@Suite("Entity replacement splicing")
struct EntitySplicingTests {
private func limits() -> PureXML.Parsing.Limits {
PureXML.Parsing.Limits(allowDoctype: true)
}
private let document = """
text
">
]>
&example;
"""
@Test("An entity's markup becomes elements in the tree")
func test_treeShape() throws {
let node = try PureXML.parse(document, limits: limits())
guard case let .document(children) = node, let root = children.compactMap(\.element).first else {
Issue.record("no root")
return
}
#expect(root.children.count == 1)
guard case let .element(paragraph)? = root.children.first else {
Issue.record("expected a p element, got \(String(describing: root.children.first))")
return
}
#expect(paragraph.name.localName == "p")
#expect(paragraph.children.count == 1)
if case let .text(value)? = paragraph.children.first {
#expect(value == "text")
} else {
Issue.record("expected text inside p")
}
}
@Test("The pull-event stream sees the spliced elements")
func test_pullEvents() throws {
var reader = PureXML.Parsing.EventReader(document, limits: limits())
var names: [String] = []
while let event = try reader.next() {
switch event {
case let .startElement(name, _): names.append("+\(name.localName)")
case let .endElement(name): names.append("-\(name.localName)")
case let .characters(text) where !text.isEmpty: names.append("t:\(text)")
default: break
}
}
#expect(names == ["+root", "+p", "t:text", "-p", "-root"])
}
@Test("The spliced document validates against its DTD content model")
func test_dtdValidates() throws {
let errors = try PureXML.validateAgainstInternalDTD(document, limits: limits())
#expect(errors.isEmpty, "\(errors)")
}
@Test("Character references expand at declaration, so markup from them splices")
func test_characterReferenceMarkup() throws {
// xmltest valid/sa/024 and 087 shapes.
let viaCharRef = """
">
]>
&e;
"""
let node = try PureXML.parse(viaCharRef, limits: limits())
guard case let .document(children) = node, let doc = children.compactMap(\.element).first else {
Issue.record("no root")
return
}
#expect(doc.children.compactMap(\.element).first?.name.localName == "foo")
}
@Test("The Appendix D double escape yields a literal ampersand in content")
func test_doubleEscape() throws {
let xml = """
]>
&e;
"""
let node = try PureXML.parse(xml, limits: limits())
guard case let .document(children) = node, let doc = children.compactMap(\.element).first,
case let .text(value)? = doc.children.first
else {
Issue.record("no text")
return
}
#expect(value == "a & b")
}
@Test("A recursive markup entity is rejected, not looped")
func test_recursionRejected() {
let xml = """
&a;">
]>
&a;
"""
#expect(throws: PureXML.Parsing.ParseError.self) {
_ = try PureXML.parse(xml, limits: limits())
}
}
@Test("Tags spanning the entity boundary stay rejected")
func test_boundarySpanRejected() {
let xml = """
">
]>
&e;
"""
#expect(throws: PureXML.Parsing.ParseError.self) {
_ = try PureXML.parse(xml, limits: limits())
}
}
}