format-number of a value at or beyond 2^53 ran an unguarded Double-to-Int conversion, a fatal trap reachable from user data; a fraction picture beyond 15 places overflowed the scale conversion the same way; and xsl:number value= beyond 2^53 trapped identically. Digit extraction clamps fraction places to 15 (the 2^53 exactness bound), pads back to the picture's minimum, renders out-of-range integers through the exponent expander, and xsl:number falls back to plain number rendering outside [1, 2^53). Sort-key rows always carry one key per sort. Regression tests cover all three trap paths.
106 lines
4.2 KiB
Swift
106 lines
4.2 KiB
Swift
/// A node queued for sorting: its original offset and computed keys.
|
|
private struct SortEntry {
|
|
let offset: Int
|
|
let node: PureXML.XPath.Node
|
|
let keys: [String]
|
|
}
|
|
|
|
extension PureXML.XSLT.Transformer {
|
|
func sorted(_ nodes: [PureXML.XPath.Node], _ sorts: [PureXML.XSLT.Sort], _ context: XSLTContext) -> [PureXML.XPath.Node] {
|
|
guard !sorts.isEmpty else { return nodes }
|
|
// Keys are computed once per node with the node's own evaluation
|
|
// context (its position and size in the selection, the caller's
|
|
// variables and bindings), so position()-based keys work.
|
|
let keyed = nodes.enumerated().map { offset, xnode -> SortEntry in
|
|
guard let owner = Self.ownerNode(xnode) else { return SortEntry(offset: offset, node: xnode, keys: sorts.map { _ in "" }) }
|
|
var keyContext = XSLTContext(
|
|
node: owner,
|
|
current: xnode.treeNode == nil ? xnode : nil,
|
|
position: offset + 1,
|
|
size: nodes.count,
|
|
variables: context.variables,
|
|
)
|
|
keyContext.namespaces = context.namespaces
|
|
return SortEntry(offset: offset, node: xnode, keys: sorts.map { string($0.select, keyContext) })
|
|
}
|
|
return keyed.sorted { lhs, rhs in
|
|
for (index, sort) in sorts.enumerated() {
|
|
let order = Self.compareKeys(lhs.keys[index], rhs.keys[index], sort)
|
|
if order != 0 { return order < 0 }
|
|
}
|
|
return lhs.offset < rhs.offset
|
|
}.map(\.node)
|
|
}
|
|
|
|
static func compareKeys(_ left: String, _ right: String, _ sort: PureXML.XSLT.Sort) -> Int {
|
|
var order: Int
|
|
if sort.numeric {
|
|
let leftNumber = PureXML.XPath.Value.parseNumber(left)
|
|
let rightNumber = PureXML.XPath.Value.parseNumber(right)
|
|
// NaN keys sort before every number (the Xalan order).
|
|
switch (leftNumber.isNaN, rightNumber.isNaN) {
|
|
case (true, true): order = 0
|
|
case (true, false): order = -1
|
|
case (false, true): order = 1
|
|
case (false, false): order = leftNumber == rightNumber ? 0 : (leftNumber < rightNumber ? -1 : 1)
|
|
}
|
|
} else if let caseOrder = sort.caseOrder {
|
|
order = caseInsensitiveCompare(left, right, caseOrder)
|
|
} else {
|
|
order = left == right ? 0 : (left < right ? -1 : 1)
|
|
}
|
|
return sort.descending ? -order : order
|
|
}
|
|
|
|
func forEach(
|
|
_ select: String,
|
|
_ sorts: [PureXML.XSLT.Sort],
|
|
_ body: [PureXML.XSLT.Instruction],
|
|
_ context: XSLTContext,
|
|
) -> [ResultItem] {
|
|
let nodes = sorted(selectXPathNodes(select, context), sorts, context)
|
|
var items: [ResultItem] = []
|
|
for (offset, xnode) in nodes.enumerated() {
|
|
guard let owner = Self.ownerNode(xnode) else { continue }
|
|
let itemContext = XSLTContext(
|
|
node: owner,
|
|
current: xnode.treeNode == nil ? xnode : nil,
|
|
position: offset + 1,
|
|
size: nodes.count,
|
|
variables: context.variables,
|
|
)
|
|
items += instantiate(body, itemContext)
|
|
}
|
|
return items
|
|
}
|
|
|
|
func chooseInstruction(
|
|
_ whens: [PureXML.XSLT.Branch],
|
|
_ otherwise: [PureXML.XSLT.Instruction],
|
|
_ context: XSLTContext,
|
|
) -> [ResultItem] {
|
|
for branch in whens where boolean(branch.test, context) {
|
|
return instantiate(branch.body, context)
|
|
}
|
|
return instantiate(otherwise, context)
|
|
}
|
|
|
|
func builtInRule(_ xnode: PureXML.XPath.Node, mode: String?, _ context: XSLTContext) -> [ResultItem] {
|
|
switch xnode {
|
|
case let .tree(node):
|
|
switch node.kind {
|
|
case .element, .document:
|
|
applyTemplates(to: node.children.map { .tree($0) }, mode: mode, parameters: [], context)
|
|
case .text, .cdata:
|
|
[.node(.text(node.value))]
|
|
default:
|
|
[]
|
|
}
|
|
case let .attribute(_, attribute):
|
|
[.node(.text(attribute.value))]
|
|
case let .namespace(_, _, uri):
|
|
[.node(.text(uri))]
|
|
}
|
|
}
|
|
}
|