feat(xslt): key() over a node-set second argument
key(name, node-set), including key('k', .), now unions the indexed nodes
for every value node's string value and de-duplicates, instead of using
only the first node's string value. A string value still works directly.
Part of #82.
This commit is contained in:
parent
73f8fb74af
commit
47b88f19b2
@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- 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.
|
||||
- XSLT `method="html"` output (#82). An `xsl:output method="html"` result is now serialized by the HTML rules (void elements like `<br>` emitted without a self-closing slash, raw-text elements like `<script>`/`<style>` left unescaped) instead of as XML.
|
||||
- XSLT `xsl:sort case-order` (#82). When `case-order="upper-first"`/`"lower-first"` is set, the sort comparison is case-insensitive with the case used only to break ties among otherwise-equal strings; without it the default codepoint order is kept.
|
||||
- XSLT functions `generate-id`, `system-property`, `element-available`,
|
||||
|
||||
@ -114,8 +114,21 @@ extension PureXML.XSLT {
|
||||
}
|
||||
.adding("key") { arguments, _ in
|
||||
let name = arguments.first?.string ?? ""
|
||||
let value = arguments.count > 1 ? arguments[1].string : ""
|
||||
return .nodeSet((keys[name]?[value] ?? []).map { .tree($0) })
|
||||
guard arguments.count > 1 else { return .nodeSet([]) }
|
||||
// A node-set second argument unions the matches for each node's
|
||||
// string value; any other value is used directly as a string.
|
||||
let values: [String] = if let nodes = arguments[1].nodes {
|
||||
nodes.compactMap(\.treeNode).map(\.stringValue)
|
||||
} else {
|
||||
[arguments[1].string]
|
||||
}
|
||||
var matched: [PureXML.Model.TreeNode] = []
|
||||
for value in values {
|
||||
for node in keys[name]?[value] ?? [] where !matched.contains(where: { $0 === node }) {
|
||||
matched.append(node)
|
||||
}
|
||||
}
|
||||
return .nodeSet(matched.map { .tree($0) })
|
||||
}
|
||||
.adding("document") { arguments, _ in
|
||||
guard let uri = arguments.first?.string, let text = loader(uri),
|
||||
|
||||
56
Tests/XSLTKeyNodeSetTests.swift
Normal file
56
Tests/XSLTKeyNodeSetTests.swift
Normal file
@ -0,0 +1,56 @@
|
||||
@testable import PureXML
|
||||
import Testing
|
||||
|
||||
@Suite("XSLT key() with the context node and node-set values")
|
||||
struct XSLTKeyNodeSetTests {
|
||||
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 let source = """
|
||||
<root>
|
||||
<item cat="a">1</item><item cat="a">2</item><item cat="b">3</item>
|
||||
<lookup>a</lookup><lookup>b</lookup>
|
||||
</root>
|
||||
"""
|
||||
|
||||
@Test("key('k', .) uses the context node's string value")
|
||||
func test_contextNode() throws {
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:key name="byCat" match="item" use="@cat"/>
|
||||
<xsl:output method="text"/>
|
||||
<xsl:template match="/"><xsl:for-each select="//lookup"><xsl:value-of select="count(key('byCat', .))"/><xsl:text>,</xsl:text></xsl:for-each></xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
// <lookup>a</lookup> -> 2 items, <lookup>b</lookup> -> 1 item.
|
||||
#expect(try transform(style, source) == "2,1,")
|
||||
}
|
||||
|
||||
@Test("key('k', node-set) unions the matches for every node's value")
|
||||
func test_nodeSetValue() throws {
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:key name="byCat" match="item" use="@cat"/>
|
||||
<xsl:output method="text"/>
|
||||
<xsl:template match="/"><xsl:value-of select="count(key('byCat', //lookup))"/></xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
// a -> 2, b -> 1, unioned and de-duplicated -> 3.
|
||||
#expect(try transform(style, source) == "3")
|
||||
}
|
||||
|
||||
@Test("key with a string value still works")
|
||||
func test_stringValue() throws {
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:key name="byCat" match="item" use="@cat"/>
|
||||
<xsl:output method="text"/>
|
||||
<xsl:template match="/"><xsl:value-of select="count(key('byCat', 'a'))"/></xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
#expect(try transform(style, source) == "2")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user