PureXML/Sources/XSLT/XSLTParserStructural.swift
Mihaela Mihaljevic 2134a23c41 feat(xslt): full xsl:number engine and stable document() identity
xsl:number implements all of XSLT 1.0 section 7.7: level
single/multiple/any with count and from matched as patterns through the
match cache, the value expression with the below-one bypass, and the
format-token engine (punctuation, separator and token reuse, zero
padding, alphabetic/roman/Greek sequences, digit grouping). An
unmatched single/multiple count renders empty; level=any always yields
a number. document() now caches per transform so repeated loads of the
same URI return the identical document and generate-id() stays unique
against live nodes (the idkey49 flake). Xalan baseline 557 -> 475
(numbering 84 -> 7).
2026-06-10 12:37:03 +02:00

61 lines
2.7 KiB
Swift

extension PureXML.XSLT.XSLTParser {
static func structuralInstruction(_ node: XSLTTree) -> PureXML.XSLT.Instruction? {
switch XSLTNode.localName(node) {
case "for-each":
.forEach(select: XSLTNode.attribute(node, "select") ?? "", sorts: sorts(node), body: body(node))
case "if":
.ifInstruction(test: XSLTNode.attribute(node, "test") ?? "", body: body(node))
case "choose":
choose(node)
case "element":
.element(
name: valueTemplate(XSLTNode.attribute(node, "name") ?? ""),
namespace: XSLTNode.attribute(node, "namespace").map(valueTemplate),
namespaces: inScopeNamespaces(node),
useAttributeSets: useAttributeSets(node),
body: body(node),
)
case "attribute":
.attribute(
name: valueTemplate(XSLTNode.attribute(node, "name") ?? ""),
namespace: XSLTNode.attribute(node, "namespace").map(valueTemplate),
namespaces: inScopeNamespaces(node),
body: body(node),
)
case "number":
.number(PureXML.XSLT.NumberSpec(
level: XSLTNode.attribute(node, "level") ?? "single",
count: XSLTNode.attribute(node, "count"),
from: XSLTNode.attribute(node, "from"),
value: XSLTNode.attribute(node, "value"),
format: XSLTNode.attribute(node, "format") ?? "1",
groupingSeparator: XSLTNode.attribute(node, "grouping-separator"),
groupingSize: XSLTNode.attribute(node, "grouping-size").flatMap { Int($0) },
))
case "comment":
.comment(body: body(node))
case "processing-instruction":
.processingInstruction(name: valueTemplate(XSLTNode.attribute(node, "name") ?? ""), body: body(node))
default:
nil
}
}
/// The namespace bindings in scope at a stylesheet node, for resolving
/// the prefix of a created name (7.1.2/7.1.3: the QName in a name
/// attribute is expanded using the instruction's own declarations).
static func inScopeNamespaces(_ node: XSLTTree) -> [String: String] {
var bindings: [String: String] = [:]
var current: XSLTTree? = node
while let candidate = current {
for attribute in candidate.attributes where attribute.name.prefix == "xmlns" {
if bindings[attribute.name.localName] == nil {
bindings[attribute.name.localName] = attribute.value
}
}
current = candidate.parent
}
return bindings
}
}