inline-xml 0.3.2

Embed XML data directly in your Rust code
Documentation
// Copyright (C) 2023 Benjamin Stürz
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.
//! Embed XML data directly in your Rust code.
//!
//! # Example
//! ```rust
//! use inline_xml::xml;
//!
//! let x = xml! {
//!     <root>
//!         <config name="name">John Doe</config>
//!         <config name="age">42</config>
//!     </root>
//! };
//! ```
//!
//! # How to use
//! Use the [`xml! {..}`](crate::xml) macro to embed XML-structured data directly into your Rust code.
//! During the compile-time of your Rust program, the content inside of your `xml!` invocations
//! will be checked and converted into an instance of [`Xml`](crate::Xml).
//! This makes sure that all invocations of the `xml!` macro are guaranteed to be valid XML at runtime.
//!
//! ## Note on [`xml!`](crate::xml) vs [`xml_tag!`](crate::xml_tag)
//! The `xml! {..}` macro allows multiple top-level tags. If this is not desired, please use the `xml_tag! {..}` macro.
//!
//! # Dynamic Data
//! You can include dynamically-generated data into the `xml!` macro.
//! This can be achieved by putting your Rust code (eg. variables) into `{..}`.
//!
//! ## Example
//! ```rust
//! use inline_xml::xml;
//!
//! let x = 3;
//! let y = 2;
//! let xml = xml! {
//!     <p>{x} + {y} = {x + y}</p>
//! };
//! ```
//!
//! # Bugs and Workarounds
//! ## Whitespace
//! At the moment of writing, [`proc_macro_span`](https://github.com/rust-lang/rust/issues/54725) is not stabilized,
//! therefore the macro can't know about any whitespace used inside of the macro invocation,
//! because the Rust's token tree parser ignores all whitespace.
//!
//! Consider the following example:
//! ```rust
//! use inline_xml::xml;
//!
//! let xml = xml! { <p>Hello World!</p> };
//! println!("{xml}");
//! ```
//! A reasonably thinking person would expect the output to be `<p>Hello World!</p>`,
//! but instead it would be:
//! ```xml
//! <p>
//!     Hello
//!     World
//!     !
//! </p>
//! ```
//! or `<p>Hello World !</p>`, depending on how the formatter is implemented.
//!
//! # Supported XML syntax
//! - "Normal" tags (`<tag>inner</tag>`)
//! - "Empty tags" (`<tag />`)
//! - Attributes (`<tag attr="value" />`)
//! - Namespaces in Tag names (`<ns:tag />`)
//! - Namespaces in Attr names (`<tag ns:attr="value" />`)
//!
//! ## Extensions
//! - Dynamic tag body (`<tag>Hello {text}</tag>`)
//! - Dynamic attr values (`<tag attr={value} />`)
//! - Escaping strings (`<tag>"<Hello> \"World\"&"</tag>`)
//!
//! # Unsupported XML syntax
//! - XML declarations (`<?xml version="1.0" encoding="UTF-8"?>`)
//! - DTD declarations (`<!DOCTYPE html>`)
//! - Probably a little more...

/// Generate an [`Xml`](crate::Xml) struct from XML.
/// This macro allows specifying multiple XML root nodes.
/// If you only want a single XML node, please use [`xml_tag`](crate::xml_tag).
/// # Examples
/// ## XML configuration generator
/// This example generates a simple XML config file generator.
/// ```rust
/// use inline_xml::{Xml, xml};
/// fn config(name: &str, age: u8) -> Xml {
///     xml! {
///         <name>{name}</name>
///         <age>{age}</age>
///     }
/// }
/// ```
/// ## Simple inline-xml website
/// ``` rust
/// use inline_xml::{Xml, xml};
/// fn index() -> Xml {
///     xml! {
///         <html>
///             <head>
///                 <title>Example Website</title>
///                 <meta charset="UTF-8" />
///             </head>
///             <body>
///                 <h1 align="center">Example Website</h1>
///                 <p style="color: red;">"This is an example website."</p>
///                 <p>"Here are some escaped characters: &<>\"'"</p>
///             </body>
///         </html>
///     }
/// }
/// ```
/// ## Simple inline-xml form
/// ``` rust
/// use inline_xml::{Tag, xml_tag};
/// fn form() -> Tag {
///     xml_tag! {
///         <form action="/login" method="post">
///             <label>Username:</label><br />
///             <input type="text" name="username" />
///             <label>Password:</label><br />
///             <input type="password" name="password" />
///             <button type="submit">Login</button>
///         </form>
///     }
/// }
/// ```
pub use inline_xml_macros::xml;

/// This macro is similar to xml! {}, but instead of generating
/// an [`Xml`](crate::Xml) struct, it generates a single [`Tag`](crate::Tag).
pub use inline_xml_macros::xml_tag;

mod fmt;
mod flatten;
mod impls;
#[cfg(test)]
mod tests;

/// This trait must be implemented on types, before they can be used inside the `xml! {..}` macro.
pub trait ToXml {
    fn to_xml(&self) -> Xml;
}

/// Collect XML from an iterator.
/// # Example
/// ```rust
/// use inline_xml::*;
///
/// let v = [1, 2, 3];
/// let xml = xml! {
///     <root>
///     {v.into_iter().map(|x| xml! { <p>{x}</p> }).collect_xml()}
///     </root>
/// };
/// println!("{xml}");
/// ```
/// ## Output
/// ```xml
/// <root>
///     <p>1</p>
///     <p>2</p>
///     <p>3</p>
/// </root>
/// ```
pub trait CollectXml
where
    Self: Sized,
{
    fn collect_xml(self) -> Xml;
}

/// An XML document with metadata.
///
/// # Example
/// ``` rust
/// use inline_xml::*;
///
/// fn main() {
///     let xml = xml! {
///         <html>
///             <h1>Example Document</h1>
///             <p>"This is an example document."</p>
///         </html>
///     };
///
///     let doc = xml
///         .into_document()
///         .expect("Failed to create XML document.")
///         .with_xml_version("1.0")
///         .with_xml_encoding("UTF-8")
///         .with_xml_attr("standalone", "yes")
///         .with_doctype("html");
///
///     println!("{doc}");
/// }
/// ```
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Document {
    pub root: Tag,
    pub xml_decl_attrs: Vec<(String, String)>,
    pub doctype: Option<String>,
}

#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Xml(pub Vec<Content>);

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Tag {
    pub name: String,
    pub attrs: Vec<Attr>,
    pub inner: Option<Xml>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Attr {
    pub name: String,
    pub value: String,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Content {
    Tag(Tag),
    Word(String),
    Nested(Xml),
}