inline-xml-macros 0.2.0

Macros for the inline-xml crate
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::Write;
use proc_macro2::TokenStream;
use quote::{quote, ToTokens};
use super::*;

fn escape(s: &str) -> String {
    s.chars().fold(String::new(), |mut s, ch| match ch {
        '<'     => s + "&lt;",
        '>'     => s + "&gt;",
        '&'     => s + "&amp;",
        '\''    => s + "&apos;",
        '"'     => s + "&quot;",
        _       => { write!(&mut s, "{ch}").unwrap(); s },
    })
}

impl ToTokens for Xml {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let inner = &self.0;
        let s = quote! {
            inline_xml::Xml(vec![#(#inner),*]).flattened()
        };
        tokens.extend(s)
    }
}

impl ToTokens for Name {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        self.to_string().to_tokens(tokens)
    }
}

impl ToTokens for Tag {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = &self.name;
        let attrs = &self.attrs;

        let inner = match &self.tail {
            TagTail::Simple { .. } => quote! { None },
            TagTail::Complex { inner,  .. } => {
                quote! {
                    Some(#inner)
                }
            }
        };

        let s = quote! {
            inline_xml::Tag {
                name: #name.into(),
                attrs: vec![#(#attrs),*],
                inner: #inner,
            }
        };
        tokens.extend(s);
    }
}

impl ToTokens for Attr {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let name = &self.name;
        let value = &self.value;
        let s = quote! {
            inline_xml::Attr {
                name: #name.into(),
                value: #value,
            }
        };
        tokens.extend(s)
    }
}

impl ToTokens for Content {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = match self {
            Self::Tag(t)    => quote! { inline_xml::Content::Tag(#t) },
            Self::Ident(i)  => quote! { inline_xml::Content::Word(stringify!(#i).into()) },
            Self::Int(i)    => quote! { inline_xml::Content::Word(stringify!(#i).into()) },
            Self::Float(f)  => quote! { inline_xml::Content::Word(stringify!(#f).into()) },
            Self::Punct(p)  => quote! { inline_xml::Content::Word(stringify!(#p).into()) },
            Self::Str(s)    => {
                let s = escape(&s.value());
                quote! {
                    inline_xml::Content::Word(#s.into())
                }
            },
            Self::Block(b)  => quote! {
                inline_xml::Content::Nested({ use inline_xml::ToXml; #b.to_xml() })
            },
            Self::Escape { escape, .. } => {
                let s = match escape {
                    Escape::Lt(_)   => "lt".to_owned(),
                    Escape::Gt(_)   => "gt".to_owned(),
                    Escape::Amp(_)  => "amp".to_owned(),
                    Escape::Apos(_) => "apos".to_owned(),
                    Escape::Quot(_) => "quot".to_owned(),
                    Escape::Int { lit, .. } => format!("#{}", lit),
                    Escape::X { value, .. } => format!("#x{:x}", value),
                };
                let s = format!("&{s};");
                quote! {
                    inline_xml::Content::Word(#s.into())
                }
            },
        };
        tokens.extend(s)
    }
}

impl ToTokens for Value {
    fn to_tokens(&self, tokens: &mut TokenStream) {
        let s = match self {
            Self::Str(s)    => quote! { #s.into() },
            Self::Block(b)  => quote! { #b.to_string() }
        };
        tokens.extend(s)
    }
}