Reimplement the serializer as an explicit work-stack walk instead of recursion, mirroring libxml2's xmlNodeDumpOutputInternal, so it is no longer the depth bottleneck. Reimplemented in Swift; no upstream code copied. Also fix a mixed-content correctness bug: formatting is now suppressed for any element with text or CDATA children (previously only when ALL children were text), so pretty-printing never injects whitespace around significant text. Note surfaced while testing: the remaining depth ceiling is the recursive Model.Node indirect enum (recursive ARC teardown overflows at extreme depth), which is why the parser must cap nesting depth (next).
76 lines
3.1 KiB
Swift
76 lines
3.1 KiB
Swift
@testable import PureXML
|
|
import Testing
|
|
|
|
@Suite("Serializer")
|
|
struct SerializerTests {
|
|
@Test("Empty element self-closes")
|
|
func test_emptyElementSelfCloses() {
|
|
let node = PureXML.Model.Node.element(.init("br"))
|
|
#expect(PureXML.serialize(node, options: .compact) == "<br/>")
|
|
}
|
|
|
|
@Test("Attributes and text are escaped")
|
|
func test_escapesAttributesAndText() {
|
|
let element = PureXML.Model.Element(
|
|
"a",
|
|
attributes: [.init("href", "x?b=1&c=2")],
|
|
children: [.text("1 < 2")],
|
|
)
|
|
let xml = PureXML.serialize(.element(element), options: .compact)
|
|
#expect(xml == "<a href=\"x?b=1&c=2\">1 < 2</a>")
|
|
}
|
|
|
|
@Test("Attribute whitespace is escaped as numeric character references")
|
|
func test_escapesAttributeWhitespace() {
|
|
// libxml2 escapes tab/newline/CR in attribute values so they survive a
|
|
// spec-compliant parser's attribute-value normalization.
|
|
let element = PureXML.Model.Element("a", attributes: [.init("x", "1\t2\n3\r4>5")])
|
|
let xml = PureXML.serialize(.element(element), options: .compact)
|
|
#expect(xml == "<a x=\"1	2 3 4>5\"/>")
|
|
}
|
|
|
|
@Test("Pretty printing indents nested elements")
|
|
func test_prettyPrintsNested() {
|
|
let inner = PureXML.Model.Element("title", children: [.text("Guide")])
|
|
let outer = PureXML.Model.Element("book", children: [.element(inner)])
|
|
let xml = PureXML.serialize(.element(outer))
|
|
#expect(xml == "<book>\n <title>Guide</title>\n</book>\n")
|
|
}
|
|
|
|
@Test("Mixed content is emitted inline, not reformatted")
|
|
func test_mixedContentStaysInline() {
|
|
// Pretty-printing must not inject whitespace around text in mixed content.
|
|
let element = PureXML.Model.Element(
|
|
"p",
|
|
children: [.text("hi "), .element(.init("b", children: [.text("x")])), .text(" bye")],
|
|
)
|
|
let xml = PureXML.serialize(.element(element))
|
|
#expect(xml == "<p>hi <b>x</b> bye</p>\n")
|
|
}
|
|
|
|
@Test("Nested trees serialize iteratively and correctly")
|
|
func test_nestedSerializesCorrectly() {
|
|
// The serializer uses an explicit work stack (not recursion), so it is
|
|
// not the depth bottleneck. The remaining ceiling is the recursive
|
|
// Model.Node value itself, which is why the parser caps nesting depth.
|
|
var node = PureXML.Model.Node.text("leaf")
|
|
for _ in 0 ..< 200 {
|
|
node = .element(.init("n", children: [node]))
|
|
}
|
|
let xml = PureXML.serialize(node, options: .compact)
|
|
#expect(xml.hasPrefix("<n><n>"))
|
|
#expect(xml.hasSuffix("</n></n>"))
|
|
#expect(xml.contains("<n>leaf</n>"))
|
|
}
|
|
|
|
@Test("CDATA and comments round-trip verbatim")
|
|
func test_emitsCDATAAndComments() {
|
|
#expect(PureXML.serialize(.cdata("a<b")) == "<![CDATA[a<b]]>")
|
|
#expect(PureXML.serialize(.comment(" note ")) == "<!-- note -->")
|
|
#expect(
|
|
PureXML.serialize(.processingInstruction(target: "xml-stylesheet", data: "href=\"s.css\""))
|
|
== "<?xml-stylesheet href=\"s.css\"?>",
|
|
)
|
|
}
|
|
}
|