PureXML/Sources/XPath/CoreFunctions.swift
Mihaela Mihaljevic 1286659c62 feat(xslt): add xsl:key/key(), format-number(), xsl:number, and document()
Reach full XSLT 1.0 key, numbering, and document parity:

- xsl:key declarations are compiled into an index over the source tree;
  the key() function looks nodes up by key name and value.
- format-number() renders a number against a picture string (digit places,
  decimal point, grouping, percent), Foundation-free.
- xsl:number generates the context node's position rendered as arabic,
  alphabetic, or roman numerals.
- document() resolves an external source through an injected loader so the
  transform never reaches the filesystem or network by default.

These hang off an XPath function-table injection mechanism: the evaluator
merges an engine-supplied FunctionTable, letting XSLT register current(),
key(), format-number(), and document() per evaluation.

Closes #63
2026-06-08 08:01:17 +02:00

31 lines
1.5 KiB
Swift

extension PureXML.XPath {
/// The core XPath functions tied to the four-type model and predicates:
/// `last`, `position`, `count`, `not`, `true`, `false`, `boolean`, `number`,
/// and `string`. The broader library (string, node, and number functions)
/// extends this table.
enum CoreFunctions {
nonisolated(unsafe) static let table = FunctionTable([
"last": { _, context in .number(Double(context.size)) },
"position": { _, context in .number(Double(context.position)) },
"count": { arguments, _ in
guard let nodes = arguments.first?.nodes else {
throw PureXML.XPath.QueryError.invalidArguments("count() requires a node-set")
}
return .number(Double(nodes.count))
},
"not": { arguments, _ in .boolean(!(arguments.first?.boolean ?? false)) },
"true": { _, _ in .boolean(true) },
"false": { _, _ in .boolean(false) },
"boolean": { arguments, _ in .boolean(arguments.first?.boolean ?? false) },
"number": { arguments, context in
if let first = arguments.first { return .number(first.number) }
return .number(PureXML.XPath.Value.parseNumber(context.node.stringValue))
},
"string": { arguments, context in
if let first = arguments.first { return .string(first.string) }
return .string(context.node.stringValue)
},
])
}
}