Shipped through 0.16.0

Done

  • Pure Rust XML parser and XPath data model, with no external entity resolution and therefore no XXE
  • Complete XPath 1.0 engine: all axes, all 27 core functions, XPath 1.0 comparison and conversion semantics
  • Schematron model, parser, include resolution, abstract pattern and abstract rule expansion
  • Validation with first-matching-rule-wins, phases, four let scopes, diagnostics, properties, subjects, flags, roles
  • SVRL, JSON, and human-readable text reports — and SVRL reading, making the format bidirectional
  • CLI with phase selection, output formats, flag filtering, and exit codes
  • Cross-document node-sets and XPath document(), costing nothing for schemas that do not use it
  • document(uri, base), closing the last ISO gap under the XPath 1.0 binding
  • Schema linting: the mistakes the model makes easy, caught without a document
  • --portability: constructs that behave differently under other processors, each backed by a measured divergence
  • Keys: <sch:key> and key(), turning a quadratic cross-reference check into a linear one
  • Static variable checking: a misspelled $name fails when the schema loads
  • Opt-in parallel pattern evaluation, with a report identical to the sequential one
  • XPath 2.0 phases 1 through 3: bindings, regular expressions, sequences, dates, durations, comparisons, and the type operators
  • XPath 2.0 kind tests as path node tests — element(), attribute(id), document-node()
  • XPath 2.0 phase 4: the numeric type hierarchy — instance of recognizes xs:integer, xs:decimal and xs:float as well as xs:double, tracked for literals and explicit casts
  • Differential and generated testing against the ISO reference implementation
  • Fuzz targets, criterion benchmarks, clippy pedantic, corpus test suite, runnable examples, and the specification
  • Five more lints — unused variables, empty rules, empty patterns, duplicate assertion tests, phases that activate nothing — and rule shadowing generalised from three special cases to pairwise subsumption
  • extends href, with fragment identifiers on both it and include
  • A denial of service in nested ranges and for loops, found by fuzzing, closed with a budget shared across every nested construct in one expression
  • Three optimisations found by profiling: linear location building, rule claims in a vector rather than a hash map, and a fused walk for the common rule context
  • XPath 2.0 phases 5 through 9: the remaining sequence functions, cardinality assertions and atomization, deep-equal(), resolve-uri(), and the adjust-*-to-timezone() family
  • XPath 3.0 phase 1: function items — inline function expressions, named function references, dynamic calls — and a real for-each(), the one function they exist for
  • XPath 3.0 phase 2: the arrow operator => and string concatenation ||
  • A stack overflow in eight repeating binary operators, found by fuzzing, closed with a shared parse_binary_chain helper
  • XPath 3.0 phase 3: the rest of the higher-order sequence functions — filter, fold-left, fold-right, for-each-pair — and function-item introspection
  • XPath 3.0 phase 4: the simple map operator !, plus the let expression found while documenting it
  • EQNames, Q{uri}local, for node name tests — the other gap phase 4 found, plus a stack-overflow regression in the recursion-depth margin, caught by fuzzing and closed the same day
  • Union types retracted as a gap, not implemented as one — neither the XPath 3.0 nor 3.1 grammar has a (T | T) union-type literal at all, so the syntax this roadmap once cited was never real XPath to begin with
  • Streaming validation, requested by the maintainer, overriding an earlier deferral here — one repeating record parsed, matched, fired, and discarded at a time, for schemas provably local to it, with no rework of the arena or NodeId model after all: verified with a counting allocator that live bytes stay identical at 10,000, 100,000, and 1,000,000 records

The changelog has the release-by-release detail.

Next

Ordered by value, not by phase number

One item remains, and it is an argument for not doing the work yet. That is deliberate: a roadmap that only lists ambitions is a wish list.

1. no_std core — blocked, and the earlier reasoning was wrong

The claim was that only I/O and the resolver need std. They are not the obstacle: quick-xml, which this crate's XML parser is built on, declares no no_std support and reaches for std::io and std::error throughout. Nothing in the tree can be parsed without it, so a no_std build would validate nothing.

regex, the dependency that looked likelier to block it, turns out to be #![no_std] already and would only need its std feature dropped.

So the real price is a hand-written XML tokenizer replacing a heavily-fuzzed one — a correctness risk taken in a validator, whose whole value is being right — bought for a target that WebAssembly, the plausible use case, already reaches with std. Not worth it on today's evidence.

Examined and abandoned

Recorded so they are not proposed again

A lint for a context that can never match

There is no vocabulary to check against. Schematron declares no element names of its own; it is layered on a grammar — a DTD, XML Schema, RELAX NG — that it cannot see. Inferring one from the names the schema happens to mention is circular: a context naming an element no test mentions is entirely normal. This would need the grammar as a second input, which is a different feature.

A lint for a report that reads like a requirement

Confusing assert with report is the classic Schematron mistake, and catching it would be valuable. But the only available signal is the wording of English prose, and a lint that misfires on "this invoice must be reviewed manually" teaches its reader to ignore the linter — the one outcome worth avoiding above all.

Not planned

Out of scope, on purpose

A roadmap item here is not a refusal to discuss it — it is a record of the reasoning, so a new argument can be weighed against the old one instead of restarting it.

Make the case for something spec/roadmap/