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).
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.
- 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 viaDatatypeRegistry::new().decimal/date-time ordering anddurationcomparison have documented, deliberate precision limits — seeplan/DECISIONS.md's Phase 06 entry. - Any other
datatypeLibrary(e.g. vnu's ownhttp://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.
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.
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).
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-rustis technically strong (passes the official 384-test RELAX NG test suite, reads.rncnatively, 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.
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.
MIT — see LICENSE.