PureXML.XPath.Budget is the configurable counterpart of libxml2's
fixed 10M node-set cap (libxml2Compatible preset included), thrown as
a located QueryError instead of a silent refusal, default unbounded.
xsl:sort honors lang with tailored alphabet orders for Polish and
Russian derived from the conformance golds, case-folded. The WASM
gate executes all 985 tests under wasmtime on an actual WASI runtime,
wired into CI; the first run caught a real wasm32 trap (Int is 32
bits there, so format-number's digit extraction trapped at 2^31
despite the 2^53 guard; internals are now Int64 and the xsl:number
bound is platform-correct). Xalan baseline 127 -> 125. Closes#143,
closes#144.
EventReader.next(), both initializers, and the Event alias are public
(an internal typealias in a split file had been shadowing the public
Parsing.Event); a new PureXMLPublicAPITests target compiles without
testable import so facade gaps fail CI. The large-corpus benchmarks
exposed document-order sorting as quadratic on flat fan-outs; the fix's
first attempt was itself worse (an eager sibling table built per
predicate evaluation), caught by sampling the live run, and replaced
with a lazy table: single-node sets never touch order keys and a
parent's table builds on its second lookup. XPath at 42 MB drops
27.1 s to 1.57 s (190x to 11.7x); at 210 MB PureXML completes in 9.4 s
the query libxml2 refuses at its 10M node-set cap (#143 tracks our
configurable budget). Benchmark harness: size series to 210 MB, CORPUS
mode for real files (NASA, SwissProt measured), SAX stage,
refusal-aware table, NULL-safe C driver. All conformance suites,
Linux, and WASM verified. Closes#142.
A top-level variable evaluates with every previously evaluated global
in scope, so referencing an earlier top-level param works, directly or
via call-template; same-name globals resolve by import precedence with
the last fold winning, so a later sibling import beats an earlier one;
the stylesheet fold uniformly favors later contributions, matching the
post-order precedence model; and the XPath compiler accepts NCName:*
name tests, matching any name in the prefix's namespace at the -0.25
default priority, so @ped:* outranks @*. Xalan baseline 144 -> 134
(variable 10 -> 5, conflictres 8 -> 5).
The following and preceding axes work from attribute and namespace
starts, anchored at the owner element per XPath 1.0 document order:
an attribute follows its element, precedes the element's children,
and has no descendants of its own. Xalan baseline 182 -> 174
(axes 16 -> 8).
Templates carry their in-scope xmlns bindings into match patterns
(match cache keyed on pattern plus bindings) and instantiated
expressions, resolving prefixed name tests by namespace URI; with
bindings supplied an unprefixed test selects only the null namespace,
the XPath 1.0 rule. Instruction-scope capture includes the default
declaration: xsl:element honors in-scope xmlns="uri" for unprefixed
names, xmlns="" keeps them namespace-free, and an unusable created
name drops the wrapper, keeping content. Xalan baseline 263 -> 218
(namespace 41 -> 13).
apply-templates and for-each keep attribute and namespace nodes in
their selections; the context carries the non-tree current node with
its owner element backing the tree-only machinery, XPath evaluation
and current() start from it, match patterns select attribute nodes
through attribute identities in the match cache, the built-in rules
copy an attribute's or namespace's value as text, xsl:copy copies the
focused attribute or declaration itself, and an attribute result
arriving after element content is ignored (the XSLT recovery).
Sorting, for-each, and choose move to XSLTSorting.swift for the file
caps. Xalan baseline 352 -> 292 (axes 35 -> 16, copy 29 -> 16).
Node-set ordering previously recomputed both nodes' root paths on every
precedes comparison (treePath is O(depth x siblings), so sorts were
O(n log n x depth)). Add decorate-sort-undecorate helpers,
sortedByDocumentOrder() and firstInDocumentOrder(), that compute each node's
order key once, and switch the evaluator's order-and-dedup, the EXSLT set
functions, string-value coercion, and NodeFunctions' target lookup to them.
set:leading/trailing also decorate once, reusing the keys for the pivot
filter and the final sort. precedes stays for pairwise use, sharing the
key comparator (Node.ordered). Behavior unchanged, all suites green.
Closes#113
Add the EXSLT common/math/sets modules, dispatched by resolving a function's
prefix to its namespace (FunctionTable.call falls back to namespace lookup),
so any prefix bound to an EXSLT namespace works. math:min/max/highest/lowest/
abs/sqrt (with the NaN rule), set:distinct/difference/intersection/
has-same-node/leading/trailing (by node identity and document order), and
exsl:object-type. math:power and the transcendentals are omitted (they need a
C math library the Foundation-free target excludes); exsl:node-set awaits
result-tree-fragment support.
Part of #105 (Tier 3)
- Eval-time prefix bindings: Query.evaluate(over:namespaces:) and
value(at:...:namespaces:) supply prefix to URI bindings, so a name test
like q:item resolves q to a URI and matches by namespace regardless of
the document's own prefix. The match is namespace-aware only when
bindings are present, so existing unprefixed/prefix-string queries are
unchanged.
- XPointer schemes: xpath1() evaluates an XPath expression (like
xpointer()), and xmlns(p=uri) binds a prefix for the expression parts
that follow it, threaded into the XPath evaluation.
The range() scheme (and so XInclude range inclusion) remains.
Part of #88
Close the remaining documented parity edges:
- Canonical XML 2.0 TrimTextNodes: an option that strips leading and trailing
whitespace from each text node (dropping all-whitespace nodes), exposed as
Options.canonical2.
- XML Catalog delegation and chaining: delegatePublic, delegateSystem,
delegateURI, and nextCatalog entries are parsed, and resolveSystem/
resolvePublic/resolveURI gain a loadingCatalog overload that follows the
longest matching delegate prefix and then each nextCatalog, with a
visited-set guard against cycles.
- XPath number formatting: the canonical string form now expands any exponent
in the shortest double rendering to a plain decimal, since XPath 1.0 forbids
scientific notation.
Closes#70
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
Add PureXML.Validation.Schematron: compile a Schematron schema (matching
schema/pattern/rule/assert/report by local name, so the ISO and legacy
namespaces both work) and validate a document. A rule's context XPath
selects the nodes it fires on; each assert/report test is evaluated relative
to the node via the XPath context-node API. A failed assert is an error, a
matched report a warning; within a pattern a node fires the first matching
rule. Adds XPath.Query.nodes(over:) returning matched tree nodes so one tree
can be queried repeatedly.
Closes#52Closes#53Closes#25
Add Query.value(at:position:size:variables:) to evaluate an expression
against a context node already in a tree, with its proximity position, size,
and variable bindings. position(), last(), and the upward axes resolve from
that node. This completes full XPath 1.0: the per-node entry point that
downstream engines (XSLT, Schematron) drive.
Closes#43
Add the string functions (concat, starts-with, contains, substring-before,
substring-after, substring, string-length, normalize-space, translate), the
number functions (sum, floor, ceiling, round), and the node functions
(local-name, name, namespace-uri, id, lang), merged into the evaluation
library. All pure Swift, no Foundation: substring search is hand-rolled.
Without a DTD, id() matches an attribute named id or xml:id.
Closes#41
Compile and evaluate full XPath 1.0 expressions, not just location paths.
Add the four-type Value (node-set, boolean, number, string) with the spec's
coercion rules and canonical number formatting, a recursive-descent parser
for the operator grammar (or/and, equality, relational, additive,
multiplicative, unary minus, union) whose grammatical position disambiguates
* and the operator keywords, function calls, variable references, and filter
expressions. Predicates are now arbitrary expressions (a numeric predicate
is a position test). Comparison follows the node-set quantification rules.
Query gains value/number/string/boolean accessors and a $variable binding
map; the core functions last, position, count, not, true, false, boolean,
number, and string ship with it.
Closes#42
Rebuild the XPath engine on the parent-aware tree. Add an XPath node
abstraction (tree, attribute, namespace) with document order, and navigate
all thirteen axes: child, descendant, parent, ancestor, following-sibling,
preceding-sibling, following, preceding, attribute, namespace, self,
descendant-or-self, ancestor-or-self. Support the ./../@/// abbreviations
and processing-instruction() node tests. Reverse axes number nearest-first
for positional predicates; results return de-duplicated in document order.
The attribute axis excludes xmlns declarations; the namespace axis surfaces
in-scope namespace nodes.
Closes#40
Add PureXML.XPath: a compiled location-path query engine reimplemented from the
XPath 1.0 specification; no upstream code copied.
- Compiler parses a path into steps; Evaluator walks the tree returning a
Selection node-set (node or attribute) in document order.
- Forward axes only (child, descendant //, self ., attribute @), since the value
model has no parent pointers. Node tests: name, *, text(), node(), comment().
Predicates: [n], [@a], [@a='v'], [child], [child='v'].
- Public surface: XPath.Query (init compiles, evaluate/elements/strings), the
Selection enum (element / stringValue), QueryError, and the one-shot
PureXML.xpath(_:over:). Upward/sibling axes and the full expression language
are explicitly out of scope.
- 12 XPath tests; 61 total.