inline_xml/lib.rs
1// Copyright (C) 2023 Benjamin Stürz
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15//! Embed XML data directly in your Rust code.
16//!
17//! # Example
18//! ```rust
19//! use inline_xml::xml;
20//!
21//! let x = xml! {
22//! <root>
23//! <config name="name">John Doe</config>
24//! <config name="age">42</config>
25//! </root>
26//! };
27//! ```
28//!
29//! # How to use
30//! Use the [`xml! {..}`](crate::xml) macro to embed XML-structured data directly into your Rust code.
31//! During the compile-time of your Rust program, the content inside of your `xml!` invocations
32//! will be checked and converted into an instance of [`Xml`](crate::Xml).
33//! This makes sure that all invocations of the `xml!` macro are guaranteed to be valid XML at runtime.
34//!
35//! ## Note on [`xml!`](crate::xml) vs [`xml_tag!`](crate::xml_tag)
36//! The `xml! {..}` macro allows multiple top-level tags. If this is not desired, please use the `xml_tag! {..}` macro.
37//!
38//! # Dynamic Data
39//! You can include dynamically-generated data into the `xml!` macro.
40//! This can be achieved by putting your Rust code (eg. variables) into `{..}`.
41//!
42//! ## Example
43//! ```rust
44//! use inline_xml::xml;
45//!
46//! let x = 3;
47//! let y = 2;
48//! let xml = xml! {
49//! <p>{x} + {y} = {x + y}</p>
50//! };
51//! ```
52//!
53//! # Bugs and Workarounds
54//! ## Whitespace
55//! At the moment of writing, [`proc_macro_span`](https://github.com/rust-lang/rust/issues/54725) is not stabilized,
56//! therefore the macro can't know about any whitespace used inside of the macro invocation,
57//! because the Rust's token tree parser ignores all whitespace.
58//!
59//! Consider the following example:
60//! ```rust
61//! use inline_xml::xml;
62//!
63//! let xml = xml! { <p>Hello World!</p> };
64//! println!("{xml}");
65//! ```
66//! A reasonably thinking person would expect the output to be `<p>Hello World!</p>`,
67//! but instead it would be:
68//! ```xml
69//! <p>
70//! Hello
71//! World
72//! !
73//! </p>
74//! ```
75//! or `<p>Hello World !</p>`, depending on how the formatter is implemented.
76//!
77//! # Supported XML syntax
78//! - "Normal" tags (`<tag>inner</tag>`)
79//! - "Empty tags" (`<tag />`)
80//! - Attributes (`<tag attr="value" />`)
81//! - Namespaces in Tag names (`<ns:tag />`)
82//! - Namespaces in Attr names (`<tag ns:attr="value" />`)
83//!
84//! ## Extensions
85//! - Dynamic tag body (`<tag>Hello {text}</tag>`)
86//! - Dynamic attr values (`<tag attr={value} />`)
87//! - Escaping strings (`<tag>"<Hello> \"World\"&"</tag>`)
88//!
89//! # Unsupported XML syntax
90//! - XML declarations (`<?xml version="1.0" encoding="UTF-8"?>`)
91//! - DTD declarations (`<!DOCTYPE html>`)
92//! - Probably a little more...
93
94/// Generate an [`Xml`](crate::Xml) struct from XML.
95/// This macro allows specifying multiple XML root nodes.
96/// If you only want a single XML node, please use [`xml_tag`](crate::xml_tag).
97/// # Examples
98/// ## XML configuration generator
99/// This example generates a simple XML config file generator.
100/// ```rust
101/// use inline_xml::{Xml, xml};
102/// fn config(name: &str, age: u8) -> Xml {
103/// xml! {
104/// <name>{name}</name>
105/// <age>{age}</age>
106/// }
107/// }
108/// ```
109/// ## Simple inline-xml website
110/// ``` rust
111/// use inline_xml::{Xml, xml};
112/// fn index() -> Xml {
113/// xml! {
114/// <html>
115/// <head>
116/// <title>Example Website</title>
117/// <meta charset="UTF-8" />
118/// </head>
119/// <body>
120/// <h1 align="center">Example Website</h1>
121/// <p style="color: red;">"This is an example website."</p>
122/// <p>"Here are some escaped characters: &<>\"'"</p>
123/// </body>
124/// </html>
125/// }
126/// }
127/// ```
128/// ## Simple inline-xml form
129/// ``` rust
130/// use inline_xml::{Tag, xml_tag};
131/// fn form() -> Tag {
132/// xml_tag! {
133/// <form action="/login" method="post">
134/// <label>Username:</label><br />
135/// <input type="text" name="username" />
136/// <label>Password:</label><br />
137/// <input type="password" name="password" />
138/// <button type="submit">Login</button>
139/// </form>
140/// }
141/// }
142/// ```
143pub use inline_xml_macros::xml;
144
145/// This macro is similar to xml! {}, but instead of generating
146/// an [`Xml`](crate::Xml) struct, it generates a single [`Tag`](crate::Tag).
147pub use inline_xml_macros::xml_tag;
148
149mod fmt;
150mod flatten;
151mod impls;
152#[cfg(test)]
153mod tests;
154
155/// This trait must be implemented on types, before they can be used inside the `xml! {..}` macro.
156pub trait ToXml {
157 fn to_xml(&self) -> Xml;
158}
159
160/// Collect XML from an iterator.
161/// # Example
162/// ```rust
163/// use inline_xml::*;
164///
165/// let v = [1, 2, 3];
166/// let xml = xml! {
167/// <root>
168/// {v.into_iter().map(|x| xml! { <p>{x}</p> }).collect_xml()}
169/// </root>
170/// };
171/// println!("{xml}");
172/// ```
173/// ## Output
174/// ```xml
175/// <root>
176/// <p>1</p>
177/// <p>2</p>
178/// <p>3</p>
179/// </root>
180/// ```
181pub trait CollectXml
182where
183 Self: Sized,
184{
185 fn collect_xml(self) -> Xml;
186}
187
188/// An XML document with metadata.
189///
190/// # Example
191/// ``` rust
192/// use inline_xml::*;
193///
194/// fn main() {
195/// let xml = xml! {
196/// <html>
197/// <h1>Example Document</h1>
198/// <p>"This is an example document."</p>
199/// </html>
200/// };
201///
202/// let doc = xml
203/// .into_document()
204/// .expect("Failed to create XML document.")
205/// .with_xml_version("1.0")
206/// .with_xml_encoding("UTF-8")
207/// .with_xml_attr("standalone", "yes")
208/// .with_doctype("html");
209///
210/// println!("{doc}");
211/// }
212/// ```
213#[derive(Debug, Clone, PartialEq, Eq)]
214pub struct Document {
215 pub root: Tag,
216 pub xml_decl_attrs: Vec<(String, String)>,
217 pub doctype: Option<String>,
218}
219
220#[derive(Debug, Default, Clone, PartialEq, Eq)]
221pub struct Xml(pub Vec<Content>);
222
223#[derive(Debug, Clone, PartialEq, Eq)]
224pub struct Tag {
225 pub name: String,
226 pub attrs: Vec<Attr>,
227 pub inner: Option<Xml>,
228}
229
230#[derive(Debug, Clone, PartialEq, Eq)]
231pub struct Attr {
232 pub name: String,
233 pub value: String,
234}
235
236#[derive(Debug, Clone, PartialEq, Eq)]
237pub enum Content {
238 Tag(Tag),
239 Word(String),
240 Nested(Xml),
241}