Skip to content

thebytefarm/marxml

marxml

Fast markdown + XML query and mutation. Rust core, Node bindings. Workers of the markup, unite.

CI crates.io npm version License

Warning

Pre-1.0 · under active development. marxml is on 0.X.X and APIs, types, and selector grammar may still shift between minor versions. Not recommended for production yet — pin a specific version and read the changelog before bumping. Licensed MIT/Apache-2.0 (so it's always at your own risk anyway).

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. Same API in Rust and Node.

Features

  • Find tags with selectors. task[id^="4."], phase > task, note:not([archived]) — the CSS subset you already know.
  • Edit surgically. Change an attribute or replace inner content. 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.
  • Fast on both sides. Native speed in Node via prebuilt binaries for macOS and Linux. Windows support is pending — see release notes.

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. Same shape in Rust and Node.

Install

Rust

cargo add marxml

Node / TypeScript

pnpm add marxml

Rust 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>>(())

Node quickstart

import { parse } from 'marxml'

const src = `
<phase id="1" status="todo">
  <task id="1.1" status="todo">do this</task>
  <task id="1.2" status="done">finished</task>
</phase>
`

const doc = parse(src)

for (const task of doc.select('task[status="todo"]')) {
  console.log(task.attrs.id) // "1.1"
}

const updated = doc.updateAttrs('task[status="todo"]', [
  { name: 'status', value: 'done' },
])

Docs

  • DSL reference — selectors, validation schema, cookbook recipes, formal grammar.
  • Rust reference — API surface, design notes, lints, MSRV.
  • Node referenceMarkdownDoc shape, distribution, regex behavior.
  • Architecture — tokenizer, mutation strategy, two-track API.

License

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