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/>.
use std::fmt::{self, Display, Formatter};
use super::*;

const INDENT: usize = 4;

struct IndentedXml<'a>(&'a Xml, usize);
struct IndentedTag<'a>(&'a Tag, usize);
struct IndentedContent<'a>(&'a Content, usize);

fn escape_attr(s: &str) -> String {
    s.chars().fold(String::new(), |mut s, ch| {
        match ch {
            '\\' => s + "\\\\",
            '\"' => s + "\\\"",
            '\n' => s + "\\n",
            _    => { s.push(ch); s },
        }
    })
}

impl Display for IndentedXml<'_> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let Self(x, ind) = self;
        let ind2 = ind + INDENT;
        x.0.iter().try_for_each(|x| write!(f, "{}", IndentedContent(x, ind2)))
    }
}

impl Display for IndentedTag<'_> {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        let Self(tag, ind) = self;
        let ind2 = ind + INDENT;
        let n = &tag.name;
        write!(f, "{:ind$}<{n}", "")?;
        tag.attrs.iter().try_for_each(|a| write!(f, " {a}"))?;
        if let Some(inner) = tag.inner.as_ref() {
            match &inner.0[..] {
                &[] => write!(f, "></{n}>"),
                &[ref x] => {
                    let sep = match x {
                        Content::Tag(_)     => true,
                        Content::Nested(_)  => true,
                        Content::Word(_)    => false,
                    };

                    if sep {
                        writeln!(f ,">")?;
                        writeln!(f, "{}", IndentedContent(x, ind2))?;
                        write!(f, "{:ind$}</{n}>", "")
                    } else {
                        write!(f, ">{x}</{n}>")
                    }
                },
                data => {
                    writeln!(f, ">")?;
                    data.iter().try_for_each(|x| writeln!(f, "{}", IndentedContent(x, ind2)))?;
                    write!(f, "{:ind$}</{n}>", "")
                }
            }
        } else {
            write!(f, " />")
        }
    }
}

impl Display for IndentedContent<'_> {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        let Self(x, ind) = self;
        match x {
            Content::Tag(t)     => write!(f, "{}", IndentedTag(t, *ind)),
            Content::Word(w)    => write!(f, "{:ind$}{}", "", w),
            Content::Nested(x)  => write!(f, "{}", IndentedXml(x, *ind - INDENT)),
        }
    }
}


impl Display for Document {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        if self.xml_decl_attrs.len() > 0 {
            write!(f, "<?xml")?;
            for (n, v) in &self.xml_decl_attrs {
                write!(f, " {}=\"{}\"", n, escape_attr(v))?;
            }
            writeln!(f, "?>")?;
        }
        if let Some(doctype) = &self.doctype {
            writeln!(f, "<!DOCTYPE {doctype}>")?;
        }
        write!(f, "{}", self.root)
    }
}

impl Display for Xml {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        self.0.iter().try_for_each(|x| write!(f, "{x}"))
    }
}

impl Display for Tag {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}", IndentedTag(self, 0))
    }
}

impl Display for Attr {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "{}=\"{}\"", self.name, escape_attr(&self.value))
    }
}

impl Display for Content {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        match self {
            Self::Tag(t)    => write!(f, "{t}"),
            Self::Word(w)   => write!(f, "{w}"),
            Self::Nested(x) => write!(f, "{}", IndentedXml(x, 0)),
        }
    }
}