Merge: XSLT xsl:fallback (#81)

This commit is contained in:
Mihaela Mihaljevic 2026-06-09 01:52:22 +02:00
commit 618bf170c8
5 changed files with 60 additions and 3 deletions

View File

@ -9,6 +9,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- XSLT `xsl:fallback` (#81). An unrecognized XSLT element instantiates its
`xsl:fallback` children in its place (forwards-compatible processing), or is
dropped when it has none; a fallback under a supported instruction is ignored.
- XSLT `xsl:apply-imports` (#81). Re-applies templates to the current node in the
current mode, considering only templates of lower import precedence than the
one being instantiated (so an overriding template can extend the imported one),

View File

@ -67,6 +67,9 @@ public extension PureXML.XSLT {
/// `xsl:apply-imports`: re-applies templates to the current node, in the
/// current mode, considering only templates of lower import precedence.
case applyImports
/// The `xsl:fallback` content of an unrecognized XSLT element, instantiated
/// in its place under forwards-compatible processing.
case fallback(body: [Instruction])
}
/// The `xsl:output` controls that shape serialization: the output method, and

View File

@ -225,7 +225,11 @@ extension PureXML.XSLT {
}
private static func xslInstruction(_ node: Tree) -> Instruction? {
simpleInstruction(node) ?? structuralInstruction(node)
if let known = simpleInstruction(node) ?? structuralInstruction(node) { return known }
// An unrecognized XSLT element instantiates its xsl:fallback children
// (forwards-compatible processing); with none, it is dropped.
let fallback = XSLTNode.children(node, named: "fallback").flatMap(body)
return fallback.isEmpty ? nil : .fallback(body: fallback)
}
private static func simpleInstruction(_ node: Tree) -> Instruction? {

View File

@ -121,9 +121,12 @@ extension PureXML.XSLT {
var context = context
for instruction in body {
if termination.message != nil { break }
if case let .variable(name, select, varBody) = instruction {
switch instruction {
case let .variable(name, select, varBody):
context.variables[name] = variableValue(select, varBody, context)
} else {
case let .fallback(fallbackBody):
items += instantiate(fallbackBody, context)
default:
items += evaluate(instruction, context)
}
}

View File

@ -0,0 +1,44 @@
@testable import PureXML
import Testing
@Suite("XSLT xsl:fallback")
struct XSLTFallbackTests {
private let xsl = "xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\""
private func transform(_ stylesheet: String, _ source: String) throws -> String {
try PureXML.XSLT.transform(stylesheet: stylesheet, source: source)
}
private func wrap(_ templateBody: String) -> String {
"""
<xsl:stylesheet version="1.0" \(xsl)>
<xsl:output method="xml" omit-xml-declaration="yes"/>
<xsl:template match="/"><out>\(templateBody)</out></xsl:template>
</xsl:stylesheet>
"""
}
@Test("An unknown XSLT element instantiates its xsl:fallback")
func test_unknownWithFallback() throws {
let style = wrap("<xsl:future-thing><xsl:fallback>fb</xsl:fallback></xsl:future-thing>")
#expect(try transform(style, "<x/>") == "<out>fb</out>")
}
@Test("An unknown XSLT element with no fallback is dropped")
func test_unknownNoFallback() throws {
let style = wrap("<xsl:future-thing/>kept")
#expect(try transform(style, "<x/>") == "<out>kept</out>")
}
@Test("xsl:fallback under a supported instruction is ignored")
func test_fallbackUnderSupported() throws {
let style = wrap("<xsl:fallback>ignored</xsl:fallback>kept")
#expect(try transform(style, "<x/>") == "<out>kept</out>")
}
@Test("Fallback content may itself be an instruction sequence")
func test_fallbackSequence() throws {
let style = wrap("<xsl:future-thing><xsl:fallback><a/><xsl:value-of select=\"r/@v\"/></xsl:fallback></xsl:future-thing>")
#expect(try transform(style, "<r v=\"7\"/>") == "<out><a/>7</out>")
}
}