marxml 0.1.0

Fast markdown + XML query and mutation. Rust core for the marxml ecosystem.
Documentation

[!WARNING] Pre-release · under active development. marxml is in the 0.0.x placeholder phase — the name on crates.io is reserved, but the working API lands in 0.1.0. APIs, types, and selector grammar will change without notice until then. Don't depend on it in production yet.

marxml lets you read and write XML-shaped tags embedded in markdown documents. Find them with CSS-style selectors, change them surgically, validate they're well-formed — without rewriting the prose around them.

Features

  • Find tags with selectors. task[id^="4."], phase > task, note:not([archived]) — the CSS subset you already know. Compiled once via Selector::parse, reused many.
  • Edit surgically. Mutators take &str and return String by splicing into the source. Every byte you didn't touch comes back identical: prose, whitespace, comments, ordering.
  • Validate the shape. Required attributes, enum/regex constraints, child rules — declarative schema, structured errors with line numbers.
  • No unsafe. Workspace-wide unsafe_code = "forbid". Hand-rolled state-machine tokenizer, stack-based tree assembler, thiserror error types.

Why?

marxml started as plumbing for a workflow agent — plan and phase-planning documents (think GSD-style trackers) stored as markdown with task state inside XML tags. Agents needed to update those tags reliably: flip a status, append a note, mark a child done — without rewriting the surrounding prose or hallucinating new structure.

The general lesson: LLMs drift at the prose level but stay disciplined inside known XML tags. Scope the model's output to a tag, and the read/write boundary becomes deterministic again. marxml is the read/write layer for that boundary — selectors to find tags, byte-preserving mutation to change them, schema to verify what came back.

Install

cargo add marxml

MSRV: 1.75.

Quickstart

use marxml::{parse, Selector};

let src = r#"
<phase id="1" status="todo">
  <task id="1.1" status="todo">do this</task>
  <task id="1.2" status="done">finished</task>
</phase>
"#;

let doc = parse(src)?;
let sel = Selector::parse(r#"task[status="todo"]"#)?;

for task in doc.select(&sel) {
    println!("{}", task.attr("id").unwrap_or(""));
}

let updated = doc.update(&sel, &[("status", "done")]);
println!("{updated}");
# Ok::<(), Box<dyn std::error::Error>>(())

Docs

API reference on docs.rs. Long-form documentation lives in the repository:

  • Rust reference — API surface, design notes, lints, MSRV.
  • DSL reference — selectors, validation schema, cookbook recipes, formal grammar.
  • Architecture — tokenizer state machine, mutation strategy, two-track API.

There's also an npm package of the same name — same API surface via napi-rs bindings, prebuilt binaries per platform.

License

Dual-licensed under either MIT or Apache 2.0 at your option.