Merge: XSLT result-tree fragment as node-set (#82)
This commit is contained in:
commit
95073da7ed
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- XSLT result-tree fragments usable as node-sets (#82). An `xsl:variable` with a body now binds a queryable document fragment instead of a plain string, so `count($rtf/*)`, `$rtf/child`, and `xsl:for-each select="$rtf/..."` work; it still has its concatenated text as a string value.
|
||||
- XSLT `xsl:output cdata-section-elements` (#82). The text content of the named result elements is emitted in `<![CDATA[…]]>` sections instead of escaped, leaving nested elements and other elements unaffected.
|
||||
- XSLT `xsl:output` `doctype-public`/`doctype-system` (#82). When either is set, a `<!DOCTYPE>` for the result's root element (PUBLIC when both are given, SYSTEM otherwise) is emitted before the serialized output, for XML and HTML methods.
|
||||
- XSLT `key()` over a node-set second argument (#82). `key(name, node-set)` (including `key('k', .)`) now unions the matches for every node's string value and de-duplicates, rather than using only the first node's string value.
|
||||
|
||||
@ -110,8 +110,6 @@ extension PureXML.XSLT {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Instantiation
|
||||
|
||||
fileprivate func instantiate(_ body: [Instruction], _ context: XSLTContext) -> [ResultItem] {
|
||||
var items: [ResultItem] = []
|
||||
var context = context
|
||||
@ -131,7 +129,9 @@ extension PureXML.XSLT {
|
||||
|
||||
private func variableValue(_ select: String?, _ body: [Instruction], _ context: XSLTContext) -> PureXML.XPath.Value {
|
||||
if let select { return value(select, context) ?? .string("") }
|
||||
return .string(Self.text(of: instantiate(body, context)))
|
||||
// A body variable is a result-tree fragment: a queryable document node.
|
||||
let children = instantiate(body, context).compactMap(Self.nodeOf).map(PureXML.Model.TreeNode.init)
|
||||
return .nodeSet([.tree(PureXML.Model.TreeNode.document(children: children))])
|
||||
}
|
||||
|
||||
fileprivate func value(_ expression: String, _ context: XSLTContext) -> PureXML.XPath.Value? {
|
||||
|
||||
57
Tests/XSLTResultFragmentTests.swift
Normal file
57
Tests/XSLTResultFragmentTests.swift
Normal file
@ -0,0 +1,57 @@
|
||||
@testable import PureXML
|
||||
import Testing
|
||||
|
||||
@Suite("XSLT result-tree fragment as node-set")
|
||||
struct XSLTResultFragmentTests {
|
||||
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 body(_ select: String) -> String {
|
||||
"""
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:output method="text"/>
|
||||
<xsl:template match="/">
|
||||
<xsl:variable name="rtf"><a>1</a><b>2</b><a>3</a></xsl:variable>
|
||||
<xsl:value-of select="\(select)"/>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
}
|
||||
|
||||
@Test("count($rtf/*) counts the fragment's top-level elements")
|
||||
func test_countAll() throws {
|
||||
#expect(try transform(body("count($rtf/*)"), "<x/>") == "3")
|
||||
}
|
||||
|
||||
@Test("A name test selects matching fragment children")
|
||||
func test_nameTest() throws {
|
||||
#expect(try transform(body("count($rtf/a)"), "<x/>") == "2")
|
||||
}
|
||||
|
||||
@Test("A path into the fragment reads a child's value")
|
||||
func test_childValue() throws {
|
||||
#expect(try transform(body("$rtf/b"), "<x/>") == "2")
|
||||
}
|
||||
|
||||
@Test("The fragment still has a string value (its concatenated text)")
|
||||
func test_stringValue() throws {
|
||||
#expect(try transform(body("string($rtf)"), "<x/>") == "123")
|
||||
}
|
||||
|
||||
@Test("for-each iterates the fragment's nodes")
|
||||
func test_forEach() throws {
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:output method="text"/>
|
||||
<xsl:template match="/">
|
||||
<xsl:variable name="rtf"><a>1</a><b>2</b><a>3</a></xsl:variable>
|
||||
<xsl:for-each select="$rtf/a"><xsl:value-of select="."/><xsl:text>,</xsl:text></xsl:for-each>
|
||||
</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
#expect(try transform(style, "<x/>") == "1,3,")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user