PureXML/Tests/StringValueDepthSafetyTests.swift
Mihaela Mihaljevic 48c2c5c7db
fix(xpath): make string-value extraction iterative and comment-correct (#351)
Computing a node's string-value walked its subtree recursively, so it
overflowed the stack on a deeply-nested node (around 2000 deep on a Swift
Task stack): `XPath.Query("/a/a").strings(over:)` over a 4000-deep chain
crashed. `TreeNode.textContent` and the value-Node string-value behind
`Selection.stringValue` now walk an explicit stack, pushing children reversed
to concatenate in document order.

The value-Node path also wrongly folded descendant comment and
processing-instruction data into an element's string-value, disagreeing with
the tree-node path and XPath 1.0 (5.1/5.5: only text and CDATA contribute to
an element's string-value; a comment or PI node's own string-value, 5.6/5.7,
is still its data). It now matches. No test depended on the old behaviour:
1789 pass, including the XPath and XSLT value corpora.

Closes the string-value sites of #350; the transform walks (canonicalize,
DTD defaults, XSLT result tree) remain tracked there.
2026-06-26 20:44:49 +02:00

41 lines
1.9 KiB
Swift

import Testing
@testable import PureXML
/// String-value extraction must not recurse on a node's depth (#350). Computing
/// the concatenated character data of an element walked its subtree recursively,
/// so it overflowed the stack on a deeply-nested node (as shallow as a couple of
/// thousand deep on a Swift Task stack, which these tests run on). The walk is
/// now iterative; these tests reach depths the recursive form could not survive.
@Suite("String-value depth safety")
struct StringValueDepthSafetyTests {
private func deepDocument(_ depth: Int, leaf: String) -> PureXML.Model.Node {
var node: PureXML.Model.Node = .text(leaf)
for index in 0 ..< depth {
// A comment sibling at each level proves comments do not contribute to
// the XPath string-value of an element.
node = .element(.init("a", children: [.comment("c\(index)"), node]))
}
return .document([node])
}
@Test("strings() on a 50k-deep node returns the leaf text without overflowing")
func test_deepStringValueIsBounded() throws {
let document = deepDocument(50000, leaf: "leaf")
let strings = try PureXML.XPath.Query("/a").strings(over: document)
#expect(strings == ["leaf"]) // comments excluded from an element's string-value
}
@Test("TreeNode.textContent on a deep subtree is iterative and excludes comments")
func test_deepTextContentIsBounded() {
let tree = PureXML.Model.TreeNode(deepDocument(50000, leaf: "deep"))
#expect(tree.textContent == "deep")
}
@Test("string-value concatenates descendant text in document order")
func test_stringValueOrderAndContent() throws {
let xml = "<r>one<a>two</a>three<b>four</b></r>"
let strings = try PureXML.XPath.Query("/r").strings(over: PureXML.parse(xml))
#expect(strings == ["onetwothreefour"])
}
}