diff --git a/CHANGELOG.md b/CHANGELOG.md index df2d74e..3298964 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- `xsl:number level="single"` now counts the element that is the `from` boundary when that element also matches the `count` pattern (XSLT 1.0 7.7). The search for the count node stopped at the nearest `from`-matching ancestor before testing it against `count`, so a pattern like `count="a|b" from="a"` produced an empty number for an element inside an `a` (the count node is that `a`, which is also the boundary). The count pattern is now tested before the boundary stops the walk. Closes Apache Xalan conformance case `numbering63`. + - The `format` attribute of `xsl:number` is now evaluated as an attribute value template (XSLT 1.0 7.7), so `format="{$f}"` uses the value of `$f` rather than the literal string. It was stored and used verbatim, so a computed or passed-in format was never applied. Closes Apache Xalan conformance case `namedtemplate12`. - A large integer-valued number now converts to a string with no decimal point (XPath 1.0 4.2). The number-to-string formatter only treated a value below 1e15 as an integer, so an integer such as `1234567890123456` (still exact in a double, which represents integers exactly up to 2^53) printed as `1234567890123456.0`. The integer bound is now 2^63, the range in which the conversion is safe. Closes Apache Xalan conformance case `string132`. diff --git a/Sources/XSLT/XSLTNumbering.swift b/Sources/XSLT/XSLTNumbering.swift index 1d914a8..5e012f9 100644 --- a/Sources/XSLT/XSLTNumbering.swift +++ b/Sources/XSLT/XSLTNumbering.swift @@ -42,10 +42,13 @@ enum XSLTNumbering { return [total] default: // single var current: Tree? = node - while let candidate = current, !matchesFrom(candidate) { + while let candidate = current { + // The count node may be the from node itself when it matches both + // patterns (7.7), so test count before stopping at the boundary. if matchesCount(candidate) { return [siblingPosition(of: candidate, matching: matchesCount)] } + if matchesFrom(candidate) { break } current = candidate.parent } return [] diff --git a/Tests/Fixtures/xalan-baseline.txt b/Tests/Fixtures/xalan-baseline.txt index 713f1e9..cfcc397 100644 --- a/Tests/Fixtures/xalan-baseline.txt +++ b/Tests/Fixtures/xalan-baseline.txt @@ -20,7 +20,6 @@ namespace/namespace19 namespace/namespace23 namespace/namespace24 namespace/namespace35 -numbering/numbering63 output/output22 output/output26 output/output63 diff --git a/Tests/XSLTNumberingTests.swift b/Tests/XSLTNumberingTests.swift index bbe06d2..a22535d 100644 --- a/Tests/XSLTNumberingTests.swift +++ b/Tests/XSLTNumberingTests.swift @@ -19,6 +19,21 @@ struct XSLTNumberingTests { return try PureXML.XSLT.transform(stylesheet: style, source: source) } + @Test("level=single counts the from node itself when it also matches count (7.7)") + func test_singleFromCountOverlap() throws { + // count and from both match `a`; a t's nearest count ancestor is its + // parent a, which is also the from boundary, so it is still counted + // (Apache Xalan numbering63). + let style = """ + + + + , + + """ + #expect(try PureXML.XSLT.transform(stylesheet: style, source: "") == "1,2,") + } + @Test("the format attribute is an attribute value template (7.7)") func test_formatIsAVT() throws { // format="{$f}" evaluates the AVT, so a passed-in format string drives the