use std::sync::LazyLock;
use relax_ng::{CompileError, ResolveError, Schema, SchemaResolver, SchemaSource, SchemaSyntax};
const HTML5_FILES: &[(&str, &str)] = &[
(
"applications.rnc",
include_str!("../schema/html5/applications.rnc"),
),
("aria.rnc", include_str!("../schema/html5/aria.rnc")),
("block.rnc", include_str!("../schema/html5/block.rnc")),
("common.rnc", include_str!("../schema/html5/common.rnc")),
(
"core-scripting.rnc",
include_str!("../schema/html5/core-scripting.rnc"),
),
("data.rnc", include_str!("../schema/html5/data.rnc")),
("embed.rnc", include_str!("../schema/html5/embed.rnc")),
(
"form-datatypes.rnc",
include_str!("../schema/html5/form-datatypes.rnc"),
),
("media.rnc", include_str!("../schema/html5/media.rnc")),
("meta.rnc", include_str!("../schema/html5/meta.rnc")),
(
"microdata.rnc",
include_str!("../schema/html5/microdata.rnc"),
),
("phrase.rnc", include_str!("../schema/html5/phrase.rnc")),
("revision.rnc", include_str!("../schema/html5/revision.rnc")),
("ruby.rnc", include_str!("../schema/html5/ruby.rnc")),
(
"sectional.rnc",
include_str!("../schema/html5/sectional.rnc"),
),
(
"structural.rnc",
include_str!("../schema/html5/structural.rnc"),
),
("tables.rnc", include_str!("../schema/html5/tables.rnc")),
(
"web-components.rnc",
include_str!("../schema/html5/web-components.rnc"),
),
(
"web-forms.rnc",
include_str!("../schema/html5/web-forms.rnc"),
),
(
"web-forms2.rnc",
include_str!("../schema/html5/web-forms2.rnc"),
),
];
const HTML5_ROOT_MODULE: (&str, &str) =
("html5/html5.rnc", include_str!("../schema/html5/html5.rnc"));
const SVG_MATHML_PATCH: (&str, &str) = (
"html5-svg-mathml.rnc",
include_str!("../schema/html5-svg-mathml.rnc"),
);
const SVG11_FILES: &[(&str, &str)] = &[
(
"svg-animation.rnc",
include_str!("../schema/svg11/svg-animation.rnc"),
),
(
"svg-animevents-attrib.rnc",
include_str!("../schema/svg11/svg-animevents-attrib.rnc"),
),
(
"svg-basic-clip.rnc",
include_str!("../schema/svg11/svg-basic-clip.rnc"),
),
(
"svg-basic-filter.rnc",
include_str!("../schema/svg11/svg-basic-filter.rnc"),
),
(
"svg-basic-font.rnc",
include_str!("../schema/svg11/svg-basic-font.rnc"),
),
(
"svg-basic-graphics-attrib.rnc",
include_str!("../schema/svg11/svg-basic-graphics-attrib.rnc"),
),
(
"svg-basic-structure.rnc",
include_str!("../schema/svg11/svg-basic-structure.rnc"),
),
(
"svg-basic-text.rnc",
include_str!("../schema/svg11/svg-basic-text.rnc"),
),
("svg-clip.rnc", include_str!("../schema/svg11/svg-clip.rnc")),
(
"svg-conditional.rnc",
include_str!("../schema/svg11/svg-conditional.rnc"),
),
(
"svg-container-attrib.rnc",
include_str!("../schema/svg11/svg-container-attrib.rnc"),
),
(
"svg-core-attrib.rnc",
include_str!("../schema/svg11/svg-core-attrib.rnc"),
),
(
"svg-cursor.rnc",
include_str!("../schema/svg11/svg-cursor.rnc"),
),
(
"svg-datatypes.rnc",
include_str!("../schema/svg11/svg-datatypes.rnc"),
),
(
"svg-docevents-attrib.rnc",
include_str!("../schema/svg11/svg-docevents-attrib.rnc"),
),
(
"svg-extensibility.rnc",
include_str!("../schema/svg11/svg-extensibility.rnc"),
),
(
"svg-extresources-attrib.rnc",
include_str!("../schema/svg11/svg-extresources-attrib.rnc"),
),
(
"svg-filter.rnc",
include_str!("../schema/svg11/svg-filter.rnc"),
),
("svg-font.rnc", include_str!("../schema/svg11/svg-font.rnc")),
(
"svg-gradient.rnc",
include_str!("../schema/svg11/svg-gradient.rnc"),
),
(
"svg-graphevents-attrib.rnc",
include_str!("../schema/svg11/svg-graphevents-attrib.rnc"),
),
(
"svg-graphics-attrib.rnc",
include_str!("../schema/svg11/svg-graphics-attrib.rnc"),
),
(
"svg-hyperlink.rnc",
include_str!("../schema/svg11/svg-hyperlink.rnc"),
),
(
"svg-image.rnc",
include_str!("../schema/svg11/svg-image.rnc"),
),
(
"svg-marker.rnc",
include_str!("../schema/svg11/svg-marker.rnc"),
),
("svg-mask.rnc", include_str!("../schema/svg11/svg-mask.rnc")),
(
"svg-opacity-attrib.rnc",
include_str!("../schema/svg11/svg-opacity-attrib.rnc"),
),
(
"svg-paint-attrib.rnc",
include_str!("../schema/svg11/svg-paint-attrib.rnc"),
),
(
"svg-pattern.rnc",
include_str!("../schema/svg11/svg-pattern.rnc"),
),
(
"svg-profile.rnc",
include_str!("../schema/svg11/svg-profile.rnc"),
),
(
"svg-script.rnc",
include_str!("../schema/svg11/svg-script.rnc"),
),
(
"svg-shape.rnc",
include_str!("../schema/svg11/svg-shape.rnc"),
),
(
"svg-structure.rnc",
include_str!("../schema/svg11/svg-structure.rnc"),
),
(
"svg-style.rnc",
include_str!("../schema/svg11/svg-style.rnc"),
),
("svg-text.rnc", include_str!("../schema/svg11/svg-text.rnc")),
("svg-view.rnc", include_str!("../schema/svg11/svg-view.rnc")),
(
"svg-viewport-attrib.rnc",
include_str!("../schema/svg11/svg-viewport-attrib.rnc"),
),
(
"svg-xlink-attrib.rnc",
include_str!("../schema/svg11/svg-xlink-attrib.rnc"),
),
];
const SVG11_ROOT_MODULE: (&str, &str) = (
"svg11/svg11-inc.rnc",
include_str!("../schema/svg11/svg11-inc.rnc"),
);
const MML3_FILES: &[(&str, &str)] = &[
(
"mathml3-common.rnc",
include_str!("../schema/mml3/mathml3-common.rnc"),
),
(
"mathml3-content.rnc",
include_str!("../schema/mml3/mathml3-content.rnc"),
),
(
"mathml3-presentation.rnc",
include_str!("../schema/mml3/mathml3-presentation.rnc"),
),
(
"mathml3-strict-content.rnc",
include_str!("../schema/mml3/mathml3-strict-content.rnc"),
),
];
const MML3_ROOT_MODULE: (&str, &str) = (
"mml3/mathml3-inc.rnc",
include_str!("../schema/mml3/mathml3-inc.rnc"),
);
const ROOT_ENTRY: &str = "include \"html5/html5.rnc\"\ninclude \"html5-svg-mathml.rnc\"\n";
struct EmbeddedResolver;
impl SchemaResolver for EmbeddedResolver {
fn resolve(&self, href: &str, _base_uri: &str) -> Result<SchemaSource, ResolveError> {
std::iter::once(HTML5_ROOT_MODULE)
.chain(std::iter::once(SVG_MATHML_PATCH))
.chain(std::iter::once(SVG11_ROOT_MODULE))
.chain(std::iter::once(MML3_ROOT_MODULE))
.chain(HTML5_FILES.iter().copied())
.chain(SVG11_FILES.iter().copied())
.chain(MML3_FILES.iter().copied())
.find(|(name, _)| *name == href)
.map(|(name, text)| {
SchemaSource::new(text, format!("embedded:/{name}"), SchemaSyntax::Compact)
})
.ok_or_else(|| ResolveError::new(format!("no embedded schema module for {href:?}")))
}
}
fn compile_html5_schema() -> Result<Schema, CompileError> {
let source = SchemaSource::new(ROOT_ENTRY, "embedded:/root.rnc", SchemaSyntax::Compact);
Schema::compile(&source, &EmbeddedResolver)
}
static HTML5_SCHEMA: LazyLock<Result<Schema, CompileError>> = LazyLock::new(compile_html5_schema);
pub(crate) fn html5_schema() -> Result<&'static Schema, &'static CompileError> {
HTML5_SCHEMA.as_ref()
}
const WHATWG_DATATYPE_LIBRARY_URI: &str = "http://whattf.org/datatype-draft";
static DATATYPE_REGISTRY: LazyLock<relax_ng::DatatypeRegistry> = LazyLock::new(|| {
let mut registry = relax_ng::DatatypeRegistry::new();
registry.register(
WHATWG_DATATYPE_LIBRARY_URI,
crate::datatypes::WhatwgDatatypeLibrary,
);
registry
});
pub(crate) fn validate_document(
document: &crate::infoset::NormalizedHtmlDocument,
) -> Result<Vec<relax_ng::ValidationError<crate::finding::SourceLocation>>, String> {
let schema = html5_schema().map_err(ToString::to_string)?;
let Some(root) = document.root_element() else {
return Ok(Vec::new());
};
schema
.validate(&DATATYPE_REGISTRY, &root)
.map_err(|error| error.to_string())
}
pub(crate) fn findings(
errors: &[relax_ng::ValidationError<crate::finding::SourceLocation>],
) -> Vec<crate::finding::Finding> {
errors
.iter()
.map(|error| crate::finding::Finding {
rule_id: "schema.html5".to_owned(),
severity: crate::finding::Severity::Error,
message: error.to_string(),
location: error.location().copied(),
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn embedded_html5_schema_compiles() {
html5_schema().expect("the real vendored HTML5 schema should compile");
}
#[test]
fn cached_schema_is_reused_across_calls() {
let first = html5_schema().expect("schema compiles");
let second = html5_schema().expect("schema compiles");
assert!(std::ptr::eq(first, second));
}
#[test]
fn broken_schema_produces_a_clean_error_not_a_panic() {
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 broken { text",
"embedded:/broken.rnc",
SchemaSyntax::Compact,
);
let result = Schema::compile(&source, &NoResolver);
assert!(result.is_err());
}
}