PureXML/Tests/XSLTTests.swift
Mihaela Mihaljevic 23db1289ed feat(xslt): add xsl:include/import, xsl:output, comment, and processing-instruction
Round out the XSLT 1.0 stylesheet and output controls:

- xsl:include folds another stylesheet's templates, keys, globals, and output
  at the same import precedence; xsl:import folds them one precedence lower,
  so the importing stylesheet's rules win conflicts. Both resolve their href
  through the injected document loader, never the filesystem.
- xsl:output drives serialization: method (xml or text), indent,
  omit-xml-declaration, encoding, version, and standalone, layered over the
  caller's emitter options. The text method emits character data only.
- xsl:comment and xsl:processing-instruction produce comment and PI nodes
  from their instantiated bodies.

Templates now carry an import precedence that the matcher ranks ahead of
priority and document order.

Closes #64
2026-06-08 08:08:38 +02:00

167 lines
6.4 KiB
Swift

@testable import PureXML
import Testing
@Suite("XSLT transform")
struct XSLTTests {
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)
}
@Test("call-template passes with-param values, falling back to param defaults")
func test_callTemplateParams() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<out>
<xsl:call-template name="greet"><xsl:with-param name="who" select="'Ada'"/></xsl:call-template>
<xsl:call-template name="greet"/>
</out>
</xsl:template>
<xsl:template name="greet">
<xsl:param name="who" select="'world'"/>
<hi><xsl:value-of select="$who"/></hi>
</xsl:template>
</xsl:stylesheet>
"""
#expect(try transform(stylesheet, "<x/>") == "<out><hi>Ada</hi><hi>world</hi></out>")
}
@Test("apply-templates passes parameters to the matched template")
func test_applyTemplatesParams() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<xsl:apply-templates select="r/i"><xsl:with-param name="p" select="'X'"/></xsl:apply-templates>
</xsl:template>
<xsl:template match="i">
<xsl:param name="p" select="'-'"/>
<v><xsl:value-of select="$p"/><xsl:value-of select="."/></v>
</xsl:template>
</xsl:stylesheet>
"""
#expect(try transform(stylesheet, "<r><i>a</i><i>b</i></r>") == "<v>Xa</v><v>Xb</v>")
}
@Test("Modes route nodes to mode-specific templates")
func test_modes() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<toc><xsl:apply-templates select="doc/h" mode="toc"/></toc>
<body><xsl:apply-templates select="doc/h"/></body>
</xsl:template>
<xsl:template match="h" mode="toc"><li><xsl:value-of select="."/></li></xsl:template>
<xsl:template match="h"><h1><xsl:value-of select="."/></h1></xsl:template>
</xsl:stylesheet>
"""
let source = "<doc><h>A</h><h>B</h></doc>"
#expect(try transform(stylesheet, source) == "<toc><li>A</li><li>B</li></toc><body><h1>A</h1><h1>B</h1></body>")
}
@Test("The identity transform reproduces the input")
func test_identity() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="*">
<xsl:copy><xsl:copy-of select="@*"/><xsl:apply-templates/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
"""
let source = "<a id=\"1\"><b>text</b><c/></a>"
#expect(try transform(stylesheet, source) == source)
}
@Test("value-of outputs the string value of an expression")
func test_valueOf() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<result><xsl:value-of select="doc/name"/></result>
</xsl:template>
</xsl:stylesheet>
"""
#expect(try transform(stylesheet, "<doc><name>Ada</name></doc>") == "<result>Ada</result>")
}
@Test("apply-templates dispatches to matching templates")
func test_applyTemplates() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/"><ul><xsl:apply-templates select="list/item"/></ul></xsl:template>
<xsl:template match="item"><li><xsl:value-of select="."/></li></xsl:template>
</xsl:stylesheet>
"""
let source = "<list><item>a</item><item>b</item></list>"
#expect(try transform(stylesheet, source) == "<ul><li>a</li><li>b</li></ul>")
}
@Test("for-each iterates a node-set")
func test_forEach() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<out><xsl:for-each select="r/x"><v><xsl:value-of select="@n"/></v></xsl:for-each></out>
</xsl:template>
</xsl:stylesheet>
"""
let source = "<r><x n=\"1\"/><x n=\"2\"/></r>"
#expect(try transform(stylesheet, source) == "<out><v>1</v><v>2</v></out>")
}
@Test("if and choose select conditionally")
func test_conditionals() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<xsl:for-each select="nums/n">
<xsl:choose>
<xsl:when test=". > 0"><pos><xsl:value-of select="."/></pos></xsl:when>
<xsl:otherwise><neg/></xsl:otherwise>
</xsl:choose>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
"""
let source = "<nums><n>5</n><n>-3</n></nums>"
#expect(try transform(stylesheet, source) == "<pos>5</pos><neg/>")
}
@Test("xsl:attribute and xsl:element build the result dynamically")
func test_dynamicElement() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<xsl:element name="box"><xsl:attribute name="w"><xsl:value-of select="size/@w"/></xsl:attribute></xsl:element>
</xsl:template>
</xsl:stylesheet>
"""
#expect(try transform(stylesheet, "<size w=\"10\"/>") == "<box w=\"10\"/>")
}
@Test("xsl:sort orders the selected nodes")
func test_sort() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/">
<out><xsl:apply-templates select="r/n"><xsl:sort select="." data-type="number"/></xsl:apply-templates></out>
</xsl:template>
<xsl:template match="n"><v><xsl:value-of select="."/></v></xsl:template>
</xsl:stylesheet>
"""
let source = "<r><n>3</n><n>1</n><n>2</n></r>"
#expect(try transform(stylesheet, source) == "<out><v>1</v><v>2</v><v>3</v></out>")
}
@Test("Attribute value templates substitute expressions")
func test_attributeValueTemplate() throws {
let stylesheet = """
<xsl:stylesheet \(xsl)>
<xsl:template match="/"><a href="id-{doc/@k}">link</a></xsl:template>
</xsl:stylesheet>
"""
#expect(try transform(stylesheet, "<doc k=\"7\"/>") == "<a href=\"id-7\">link</a>")
}
}