relax-ng 0.2.2

A pure-Rust RELAX NG schema parser and validator
Documentation
# relax-ng

A pure-Rust implementation of [RELAX NG](https://relaxng.org/) — reads a
RELAX NG schema (compact `.rnc` or XML `.rng` syntax) and validates a
document's structure against it: which elements/attributes are allowed
where, in what order, how many times.

Generic and standalone — not tied to HTML, XML parsing, or any specific
document type. Callers provide their own parsed document via a trait; this
crate only implements the RELAX NG side (schema parsing, composition,
pattern matching, datatypes).

## Usage

```rust
use relax_ng::{
    Content, DatatypeRegistry, Element, ExpandedName, ResolveError, Schema, SchemaResolver,
    SchemaSource, SchemaSyntax,
};

// This crate never reads files or the network itself — it always asks a
// caller-supplied resolver for any `include`/`externalRef`. A schema with
// none of those never actually calls it.
struct NoResolver;
impl SchemaResolver for NoResolver {
    fn resolve(&self, href: &str, _base_uri: &str) -> Result<SchemaSource, ResolveError> {
        Err(ResolveError::new(format!("no such resource: {href}")))
    }
}

let source = SchemaSource::new(
    "element greeting { attribute lang { text }, text }",
    "memory:/greeting.rnc",
    SchemaSyntax::Compact,
);
let schema = Schema::compile(&source, &NoResolver).expect("schema compiles");

// Implement `Element` over your own document tree — an XML DOM, an HTML
// parser's tree, whatever you already have. This crate parses none of it.
struct Doc { /* ... */ }
impl Element for Doc {
    fn name(&self) -> ExpandedName { /* ... */ unimplemented!() }
    fn attributes(&self) -> impl Iterator<Item = (ExpandedName, String)> { /* ... */ std::iter::empty() }
    fn children(&self) -> impl Iterator<Item = Content<Self>> { /* ... */ std::iter::empty() }
}

let errors = schema.validate(&DatatypeRegistry::new(), &Doc { /* ... */ })
    .expect("only built-in/XSD datatypes are used");
assert!(errors.is_empty(), "document is valid");
```

See [`examples/validate.rs`](examples/validate.rs) for a complete,
runnable version (`cargo run --example validate`), and the `Schema`/
`Element` rustdoc for the full contract.

## Datatype libraries

- The RELAX NG built-in library (empty `datatypeLibrary`, `string`/
  `token`) and XSD (`http://www.w3.org/2001/XMLSchema-datatypes` — all 19
  primitive types, the built-in derived types, and all facets) are
  implemented and registered by default via `DatatypeRegistry::new()`.
  `decimal`/date-time ordering and `duration` comparison have documented,
  deliberate precision limits — see `plan/DECISIONS.md`'s Phase 06 entry.
- Any other `datatypeLibrary` (e.g. vnu's own
  `http://whattf.org/datatype-draft`) is **not** implemented by this
  crate — register your own [`DatatypeLibrary`] implementation on the
  registry instead; an unregistered library fails schema compilation
  fast, with a clear error, rather than silently mismatching.

## Resource loading

This crate never opens a file or makes a network request on its own.
`SchemaSource` carries schema text plus a base URI; a caller-supplied
`SchemaResolver` resolves any `include`/`externalRef` an `href` — reading
from disk, an in-memory map, HTTP, or anything else, with whatever
security boundary (allowed schemes, path restrictions) fits the caller.

## Status

Parsing (both `.rnc` and `.rng`), composition/simplification, the
Brzozowski-derivative validation engine, and the built-in+XSD datatype
libraries are implemented. The vendored official RELAX NG test suite
passes at 100% for both schema classification and instance validation
(the subset not requiring a multi-file resolver — see
`plan/07-conformance-loop.md`). Not yet published to crates.io; the public
API may still change before `1.0`. See `plan/00-STATUS.md` for the
authoritative, up-to-date phase-by-phase status (not tracked in git,
local working document; see `plan/README.md` for why).

## Why this exists

Built for [`html-conform`](https://github.com/casoon/html-conform)'s
RELAX NG validation layer (schema.rs), after evaluating two existing
options and rejecting both — see that project's `plan/DECISIONS.md` for
the full writeup:

- `xmloxide`'s RelaxNG engine has several confirmed bugs (upstream issues
  [#52]https://github.com/jonwiggins/xmloxide/issues/52–[#56]https://github.com/jonwiggins/xmloxide/issues/56),
  the worst of which rejects almost any attribute in a realistically
  modular schema.
- [`dholroyd/relaxng-rust`]https://github.com/dholroyd/relaxng-rust is
  technically strong (passes the official 384-test RELAX NG test suite,
  reads `.rnc` natively, handles multi-file composition) but carries
  **no license at all** — unusable as a dependency without the author's
  explicit permission.

This crate is architecturally *informed by* `relaxng-rust`'s public
design (the syntax → model → validator split, targeting the official
RELAX NG test suite) but is an independent, clean-room implementation —
no code taken from it — published under MIT from the start.

## Package name and versioning

Published on crates.io as `relax-ng`. The current `0.1.1` in `Cargo.toml`
is a pre-release working version, not a semver commitment — breaking
changes to the public API are still expected before a `1.0` release.
After that, this crate follows normal semver.

## License

MIT — see [LICENSE](LICENSE).