Skip to content

Repository files navigation

relax-ng

A pure-Rust implementation of RELAX NG — 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

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 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'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#56), the worst of which rejects almost any attribute in a realistically modular schema.
  • 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.

About

Spec-derived, MIT-licensed RELAX NG (schema/instance validation) crate for Rust — no HTML/XML parser dependency in the core.

Topics

Resources

Stars

1 star

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages