xdoc-rs 0.1.1

Declarative XML engine for Rust
Documentation
use std::env;
use std::fs;
use std::path::Path;

use xdoc::builder::ElementBuilder;
use xdoc::component::Children;
use xdoc::core::XmlResult;
use xdoc::macros::xml;
use xdoc::writer::{to_string_pretty, WriterConfig};

#[derive(Debug, Clone)]
struct DocumentHeaderProps {
    id: String,
    issue_date: String,
}

#[allow(non_snake_case)]
fn DocumentHeader(props: DocumentHeaderProps) -> XmlResult<ElementBuilder> {
    xml! {
        <doc:Header xmlns:doc="urn:xdoc:example:document">
            <doc:ID>{ props.id }</doc:ID>
            <doc:IssueDate>{ props.issue_date }</doc:IssueDate>
        </doc:Header>
    }?
    .into_fragment()
    .into_single_element()
}

#[derive(Debug, Clone)]
struct SectionProps {
    name: &'static str,
}

#[allow(non_snake_case)]
fn Section(props: SectionProps, children: Children) -> XmlResult<ElementBuilder> {
    xml! {
        <doc:Section xmlns:doc="urn:xdoc:example:document" name={ props.name }>
            { children }
        </doc:Section>
    }?
    .into_fragment()
    .into_single_element()
}

#[derive(Debug, Clone)]
struct LineItemProps {
    code: &'static str,
    quantity: u32,
    subtotal: &'static str,
}

#[allow(non_snake_case)]
fn LineItem(props: LineItemProps, children: Children) -> XmlResult<ElementBuilder> {
    xml! {
        <doc:Line
            xmlns:doc="urn:xdoc:example:document"
            xmlns:meta="urn:xdoc:example:metadata"
            meta:code={ props.code }
            meta:quantity={ props.quantity }
            subtotal={ props.subtotal }
        >
            { children }
        </doc:Line>
    }?
    .into_fragment()
    .into_single_element()
}

#[derive(Debug, Clone)]
struct LineData {
    code: &'static str,
    quantity: u32,
    subtotal: &'static str,
    description: &'static str,
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let output_path = env::args()
        .nth(1)
        .unwrap_or_else(|| "target/xdoc-example.xml".to_owned());

    let document_id = "DOC-001";
    let issue_date = "2026-06-11";
    let customer_name = "Acme Components";
    let lines = Vec::from([
        LineData {
            code: "A001",
            quantity: 3,
            subtotal: "1250.00",
            description: "Generated with xml macro children",
        },
        LineData {
            code: "B210",
            quantity: 1,
            subtotal: "340.00",
            description: "Generated from a vector item",
        },
        LineData {
            code: "C305",
            quantity: 5,
            subtotal: "875.50",
            description: "Preserves vector order",
        },
    ]);

    let customer_children = xml! {
        <doc:Customer xmlns:doc="urn:xdoc:example:document">{ customer_name }</doc:Customer>
    }?;

    let lines_children = lines
        .iter()
        .map(|line| {
            xml! {
                <{LineItem} code={ line.code } quantity={ line.quantity } subtotal={ line.subtotal }>
                    <doc:Description xmlns:doc="urn:xdoc:example:document">
                        { line.description }
                    </doc:Description>
                </{LineItem}>
            }
        })
        .collect::<XmlResult<Vec<_>>>()?;

    let document = xml! {
        <doc:Document
            xmlns="urn:xdoc:example:default"
            xmlns:doc="urn:xdoc:example:document"
            xmlns:meta="urn:xdoc:example:metadata"
            kind="example"
            meta:version="1"
        >
            <{DocumentHeader} id={ document_id.to_owned() } issue_date={ issue_date.to_owned() }/>
            <{Section} name="customer">
                { customer_children }
            </{Section}>
            <{Section} name="lines">
                { lines_children }
            </{Section}>
        </doc:Document>
    }?
    .into_document()?;

    let xml = to_string_pretty(
        &document,
        WriterConfig::pretty().with_xml_declaration(false),
    )?;

    if let Some(parent) = Path::new(&output_path).parent() {
        fs::create_dir_all(parent)?;
    }

    fs::write(&output_path, xml)?;
    println!("wrote {output_path}");

    Ok(())
}