Templates carry their in-scope xmlns bindings into match patterns (match cache keyed on pattern plus bindings) and instantiated expressions, resolving prefixed name tests by namespace URI; with bindings supplied an unprefixed test selects only the null namespace, the XPath 1.0 rule. Instruction-scope capture includes the default declaration: xsl:element honors in-scope xmlns="uri" for unprefixed names, xmlns="" keeps them namespace-free, and an unusable created name drops the wrapper, keeping content. Xalan baseline 263 -> 218 (namespace 41 -> 13).
98 lines
4.5 KiB
Swift
98 lines
4.5 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 {
|
|
if attribute.name.prefix == "xmlns", bindings[attribute.name.localName] == nil {
|
|
bindings[attribute.name.localName] = attribute.value
|
|
} else if attribute.name.prefix == nil, attribute.name.localName == "xmlns", bindings[""] == nil {
|
|
bindings[""] = attribute.value
|
|
}
|
|
}
|
|
current = candidate.parent
|
|
}
|
|
return bindings
|
|
}
|
|
|
|
/// The namespace declarations a literal result element copies to the
|
|
/// result (7.1.1): its in-scope bindings minus the XSLT namespace and
|
|
/// the namespaces of the prefixes listed in `exclude-result-prefixes`
|
|
/// and `extension-element-prefixes` in scope.
|
|
static func copiedNamespaces(_ node: XSLTTree) -> [String: String] {
|
|
var bindings = inScopeNamespaces(node)
|
|
var excludedURIs: Set<String> = [XSLTNode.namespace]
|
|
for prefix in excludedPrefixTokens(node) {
|
|
let key = prefix == "#default" ? "" : prefix
|
|
if let uri = bindings[key] { excludedURIs.insert(uri) }
|
|
}
|
|
return bindings.filter { !excludedURIs.contains($0.value) && !($0.key.isEmpty && $0.value.isEmpty) }
|
|
}
|
|
|
|
/// Every `exclude-result-prefixes`/`extension-element-prefixes` token
|
|
/// in scope at `node` (the unprefixed forms on xsl:stylesheet, the
|
|
/// xsl-prefixed forms on literal elements).
|
|
private static func excludedPrefixTokens(_ node: XSLTTree) -> [String] {
|
|
var tokens: [String] = []
|
|
var current: XSLTTree? = node
|
|
while let candidate = current {
|
|
for attribute in candidate.attributes {
|
|
let local = attribute.name.localName
|
|
guard local == "exclude-result-prefixes" || local == "extension-element-prefixes" else { continue }
|
|
let isXSLElement = XSLTNode.isXSL(candidate)
|
|
let qualified = attribute.name.prefix == "xsl"
|
|
if isXSLElement == !qualified {
|
|
tokens += attribute.value.split(whereSeparator: \.isWhitespace).map(String.init)
|
|
}
|
|
}
|
|
current = candidate.parent
|
|
}
|
|
return tokens
|
|
}
|
|
}
|