The marxml crate on crates.io. Per-symbol docs on docs.rs/marxml.
use marxml::{parse, Selector, SerializeOpts, Schema, AttrKind, validate};
let doc = parse(src)?;
let sel = Selector::parse(r#"task[status="todo"]"#)?;
for el in doc.select(&sel) {
println!("{}", el.attr("id").unwrap_or(""));
}
let updated = doc.update(&sel, &[("status", "done")]);
let xml = doc.to_xml(&SerializeOpts::pretty());
let json = doc.to_json();
let schema = Schema::builder()
.tag("task", |t| t.attr("id", AttrKind::String.required()))
.try_build()?;
let report = validate(&doc, &schema);
# Ok::<(), Box<dyn std::error::Error>>(())cargo add marxmlfn parse(input: &str) -> Result<Markdown, ParseError>
fn parse_owned(input: String) -> Result<Markdown, ParseError>
fn parse_fragment(input: &str) -> Result<Markdown, ParseError>
const MAX_INPUT_BYTES: usize // 16 MiB
const MAX_DEPTH: usize // 1024parse_owned accepts an owned String to avoid an extra allocation when you already have one. parse_fragment is an alias today; reserved for future fragment-specific semantics.
The parsed document. All methods take &self; mutators return a new String rather than rewriting the tree in place.
Reading
fn raw(&self) -> &str
fn root_count(&self) -> usize
fn root_elements(&self) -> impl Iterator<Item = ElementRef<'_>>
fn select(&self, sel: &Selector) -> impl Iterator<Item = ElementRef<'_>>Mutating — return the rewritten document as a new String:
fn update(&self, sel: &Selector, attrs: &[(&str, &str)]) -> String
fn replace_content(&self, sel: &Selector, body: &str) -> String
fn replace_text(&self, sel: &Selector, body: &str) -> String
fn replace_in(&self, sel: &Selector, pattern: &Regex, replacement: &str) -> String
fn replace_text_in(&self, sel: &Selector, pattern: &Regex, replacement: &str) -> StringFallible variants return a MutationReport (with applied / skipped counts) and surface programmer-error inputs (invalid XML name, duplicate key) as a MutateError instead of panicking:
fn try_update(&self, sel: &Selector, attrs: &[(&str, &str)]) -> Result<MutationReport, MutateError>
fn replace_content_report(&self, sel: &Selector, body: &str) -> MutationReport
fn replace_in_report(&self, sel: &Selector, pattern: &Regex, replacement: &str) -> MutationReportSerializing
fn to_xml(&self, opts: &SerializeOpts) -> String
fn to_json(&self) -> serde_json::ValueCompiled once, reused across calls.
fn Selector::parse(input: &str) -> Result<Selector, SelectorError>See DSL · Selectors for the grammar.
Build a schema with the fluent builder; pass it to validate.
fn Schema::builder() -> SchemaBuilder
impl SchemaBuilder {
fn tag(self, name: impl Into<String>, f: impl FnOnce(TagBuilder) -> TagBuilder) -> Self
fn build(self) -> Schema // panics on invalid input
fn try_build(self) -> Result<Schema, SchemaError>
}
impl TagBuilder {
fn attr(self, name: impl Into<String>, constraint: impl Into<AttrConstraint>) -> Self
fn child_required(self, name: impl Into<String>) -> Self
fn child_optional(self, name: impl Into<String>) -> Self
fn exclusive_children(self) -> Self
fn content_required(self) -> Self
}
enum AttrKind {
String,
Enum(Vec<String>), // also: AttrKind::one_of(["a", "b"])
Regex(String),
}
impl AttrKind {
fn required(self) -> AttrConstraint
fn optional(self) -> AttrConstraint
}See DSL · Schema for the validation semantics.
fn validate(doc: &Markdown, schema: &Schema) -> ValidationReportFree function (not a method on Markdown) so validation stays decoupled from the document type. Walks the tree once, accumulates every error.
struct ElementRef<'a> {
fn tag(&self) -> &str
fn attr(&self, name: &str) -> Option<&str>
fn attrs(&self) -> impl Iterator<Item = (&str, &str)>
fn content(&self) -> &str
fn text(&self) -> impl Iterator<Item = &str> // text segments between children
fn children(&self) -> impl Iterator<Item = ElementRef<'_>>
fn location(&self) -> SourceSpan
fn is_self_closing(&self) -> bool
fn select(&self, sel: &Selector) -> impl Iterator<Item = ElementRef<'_>>
}
struct SourceSpan { start: SourcePosition, end: SourcePosition }
struct SourcePosition { line: u32, offset: u32 } // line 1-based, offset 0-based byte
struct SerializeOpts {
fn pretty() -> Self
fn default() -> Self
// .indent(&str) / .self_close_empty(bool) builders
}
struct MutationReport {
pub output: String,
pub applied: usize,
pub skipped_overlaps: usize,
pub skipped_self_closing: usize,
}
struct ValidationReport {
fn is_valid(&self) -> bool
fn errors(&self) -> &[ValidationError]
fn len(&self) -> usize
fn iter(&self) -> impl Iterator<Item = &ValidationError>
}
// Escape helpers (rarely needed in app code; used internally by mutators)
fn escape_attr(s: &str) -> String
fn escape_text(s: &str) -> String
fn is_valid_name(s: &str) -> boolElementRef<'a>is borrowed. Its lifetime is tied to theMarkdownit came from. Hold theMarkdownalive while you read element refs.- Mutators are pure. They return a new
String; the originalMarkdownis never modified. To chain mutations, re-parsethe returned string. - Untouched bytes are preserved verbatim. Mutations splice into the raw source; whitespace, comments, and surrounding prose round-trip exactly.
try_*for runtime-sourced input. The plainupdatepanics on invalid XML names or duplicate keys (programmer-error inputs). Usetry_updatewhen the attribute slice comes from runtime data (config, RPC, user input).- Regex patterns are not anchored in
replace_in/replace_text_in— you control anchoring with^/$. Schema regex constraints, by contrast, are auto-anchored. - Selectors are compile-once. Cache
Selectorinstances when you'll use the same selector many times.
enum ParseError { /* … */ } // tokenizer + parser failures
enum SelectorError { Empty, UnexpectedEnd, Syntax { reason, at } }
enum MutateError { InvalidAttrName { name }, DuplicateAttrName { name } }
enum SchemaError { InvalidRegex {…}, InvalidName {…}, DuplicateTag {…}, DuplicateAttr {…} }
enum ValidationError {
MissingAttr { tag, attr, line },
InvalidAttr { tag, attr, value, reason, line },
MissingChild { tag, child, line },
UnexpectedChild { tag, child, line },
EmptyContent { tag, line },
}Every error type is #[non_exhaustive] — match with a wildcard arm. See docs.rs for per-variant detail.
- MSRV: Rust 1.75.
- Platforms: any target that builds the
regexandserde_jsoncrates (everywhere, in practice). - Edition: 2021.
- DSL · Selectors · DSL · Schema · DSL · Cookbook
- Node reference
- Architecture
- docs.rs/marxml — per-symbol rustdoc