fix(xslt): give an axis-prefixed match pattern its node-test default priority (#266)
XSLT 1.0 5.5: the default priority of a single-step match pattern is set by its node test, regardless of an explicit child:: or attribute:: axis. defaultPriority matched the whole pattern string, so attribute::node() fell through to 0 (a name test) instead of -0.5 (a node() node test), wrongly outranking @*. Strip a leading child::/attribute:: axis before classifying the node test. Closes Apache Xalan conformance cases conflictres29 and conflictres30.
This commit is contained in:
parent
2fe8632bb2
commit
70fdcab840
@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Fixed
|
||||
|
||||
- A template match pattern written with an explicit `child::` or `attribute::` axis now gets the default priority of its node test (XSLT 1.0 5.5), not 0. `attribute::node()` is a `node()` node test, so its default priority is -0.5 (the same as `@*`), but it was treated as a name test and given priority 0, which made it wrongly outrank `@*`. The axis is now stripped before the node test is classified. Closes Apache Xalan conformance cases `conflictres29` and `conflictres30`.
|
||||
|
||||
- `xsl:strip-space` and `xsl:preserve-space` now match their `elements` name tests by namespace rather than by prefix (XSLT 1.0 3.4). A specifier `prefix:*` or `prefix:local` resolves the prefix to its namespace URI at parse time, and a source element is matched on its expanded name in order of specificity (an exact `{uri}local` over a namespace wildcard `{uri}*` over `*`), so a stylesheet that strips `ns1:*` strips a source element in that namespace whatever prefix the source bound it to, while leaving another namespace untouched. The tests previously compared the raw prefixed name strings, so a differing prefix did not match. Closes Apache Xalan conformance cases `whitespace06` and `whitespace07`.
|
||||
|
||||
- `disable-output-escaping` is now ignored when an `xsl:text` or `xsl:value-of` produces the string value of an attribute, comment, or processing-instruction node (XSLT 1.0 16.4: disabling escaping for a value used as something other than a text node is an error, with the recovery being to ignore it). The attribute value built from such content kept its raw-output markers, so the serializer left `<` and `>` unescaped in the attribute; the markers are now stripped for these three node kinds (only when the stylesheet uses the feature, so a private-use character in ordinary data is untouched). Closes Apache Xalan conformance case `output75`.
|
||||
|
||||
@ -221,11 +221,18 @@ extension PureXML.XSLT {
|
||||
XSLTNode.children(node, named: "with-param").map(binding)
|
||||
}
|
||||
|
||||
/// The XSLT default-priority rules for a match pattern.
|
||||
/// The XSLT default-priority rules for a match pattern. The priority of a
|
||||
/// single-step pattern is set by its node test, regardless of an explicit
|
||||
/// `child::` or `attribute::` axis (XSLT 1.0 5.5), so the axis is stripped
|
||||
/// before the node test is classified (`attribute::node()` is -0.5, like
|
||||
/// `node()`, not 0).
|
||||
static func defaultPriority(_ pattern: String) -> Double {
|
||||
if pattern.contains("/") || pattern.contains("[") { return 0.5 }
|
||||
if ["*", "@*", "node()", "text()", "comment()"].contains(pattern) { return -0.5 }
|
||||
if pattern.hasSuffix(":*") { return -0.25 }
|
||||
let test = pattern.hasPrefix("attribute::") ? String(pattern.dropFirst(11))
|
||||
: pattern.hasPrefix("child::") ? String(pattern.dropFirst(7))
|
||||
: pattern
|
||||
if ["*", "@*", "node()", "text()", "comment()", "processing-instruction()"].contains(test) { return -0.5 }
|
||||
if test.hasSuffix(":*") { return -0.25 }
|
||||
return 0
|
||||
}
|
||||
|
||||
|
||||
@ -3,8 +3,6 @@
|
||||
# must leave this list and a regression shows up as a new failure.
|
||||
attribset/attribset44
|
||||
axes/axes116
|
||||
conflictres/conflictres29
|
||||
conflictres/conflictres30
|
||||
extend/extend01
|
||||
extend/extend02
|
||||
extend/extend04
|
||||
|
||||
29
Tests/XSLTPatternPriorityTests.swift
Normal file
29
Tests/XSLTPatternPriorityTests.swift
Normal file
@ -0,0 +1,29 @@
|
||||
import Testing
|
||||
@testable import PureXML
|
||||
|
||||
/// XSLT 1.0 5.5 default-priority rules for template match patterns.
|
||||
@Suite("XSLT pattern priority")
|
||||
struct XSLTPatternPriorityTests {
|
||||
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("an axis-prefixed node test ties with @*, not outranks it (5.5)")
|
||||
func test_axisPatternPriority() throws {
|
||||
// attribute::node() is a node() node test, default priority -0.5 like @*,
|
||||
// not a name test (0). On the resulting tie the last template in document
|
||||
// order wins, so @* (declared last) applies, not attribute::node() which
|
||||
// was wrongly given priority 0 (Apache Xalan conflictres29, conflictres30).
|
||||
let style = """
|
||||
<xsl:stylesheet version="1.0" \(xsl)>
|
||||
<xsl:output omit-xml-declaration="yes"/>
|
||||
<xsl:template match="/"><out><xsl:apply-templates select="doc/e/@a"/></out></xsl:template>
|
||||
<xsl:template match="attribute::node()">node</xsl:template>
|
||||
<xsl:template match="@*">star</xsl:template>
|
||||
</xsl:stylesheet>
|
||||
"""
|
||||
#expect(try transform(style, "<doc><e a=\"1\"/></doc>") == "<out>star</out>")
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user