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 proc_macro2::{Ident, Punct, Spacing};
use syn::{Error, token::{Gt, Div, And, Paren, Brace, Pound}, LitStr, parse::{Parse, ParseStream, ParseBuffer, discouraged::Speculative}, ext::IdentExt, Token, LitInt, LitFloat};
use quote::ToTokens;
use super::*;

fn parse_many<'a, T: Parse>(input: &'a ParseBuffer<'a>) -> syn::Result<Vec<T>> {
    let mut v = Vec::new();
    loop {
        let fork = input.fork();
        if let Ok(c) = fork.parse() {
            v.push(c);
            input.advance_to(&fork);
        } else {
            break;
        }
    }
    Ok(v)
}

impl Parse for Xml {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut xml = Vec::new();
        while !input.is_empty() {
            xml.push(input.parse()?);
        }
        Ok(Self(xml))
    }
}

impl Parse for SimpleName {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let mut segments = Vec::new();
        while input.peek(Token![-]) {
            segments.push(SimpleNameSeg::Minus(input.parse()?));
        }
        segments.push(SimpleNameSeg::Ident(Ident::parse_any(input)?));
        while input.peek(Token![-]) {
            while input.peek(Token![-]) {
                segments.push(SimpleNameSeg::Minus(input.parse()?));
            }
            segments.push(SimpleNameSeg::Ident(Ident::parse_any(input)?));
        }
        Ok(Self(segments))
    }
}

impl Parse for Name {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let n1 = input.parse()?;
        let n = if input.peek(Colon) {
            let colon = input.parse()?;
            let name = input.parse()?;
            Self {
                namespace: Some((n1, colon)),
                name,
            }
        } else {
            Self {
                namespace: None,
                name: n1,
            }
        };
        Ok(n)
    }
}

impl Parse for Tag {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let s = Tag {
            lt: input.parse()?,
            name: input.parse()?,
            attrs: input.call(parse_many)?,
            tail: input.parse()?,
        };

        if let TagTail::Complex { name2, .. } = &s.tail {
            if &s.name != name2 {
                return Err(Error::new(name2.span(), format!("Invalid ending tag, expected: {}", s.name)));
            }
        }

        Ok(s)
    }
}

impl Parse for TagTail {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let look = input.lookahead1();
        if look.peek(Div) {
            Ok(Self::Simple {
                slash: input.parse()?,
                gt: input.parse()?,
            })
        } else if look.peek(Gt) {
            fn parse_content<'a>(input: &'a ParseBuffer<'a>) -> syn::Result<Vec<Content>> {
                let mut c = Vec::new();
                while !(input.peek(Lt) && input.peek2(Div)) {
                    c.push(input.parse()?);
                }
                Ok(c)
            }
            Ok(Self::Complex {
                gt: input.parse()?,
                inner: Xml(input.call(parse_content)?),
                lt: input.parse()?,
                slash: input.parse()?,
                name2: input.parse()?,
                gt2: input.parse()?,
            })
        } else {
            Err(look.error())
        }
    }
}

impl Parse for Attr {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        Ok(Self {
            name: input.parse()?,
            eq: input.parse()?,
            value: input.parse()?,
        })
    }
}

impl Parse for Value {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let look = input.lookahead1();
        if look.peek(Brace) {
            input.parse().map(Self::Block)
        } else if look.peek(LitStr) {
            input.parse().map(Self::Str)
        } else {
            Err(look.error())
        }
    }
}

macro_rules! if_punct {
    (@internal $input:ident, $token:tt) => {
        {
            let tk: Token![$token] = $input.parse()?;
            let s = tk.into_token_stream().to_string();
            assert_eq!(s, stringify!($token));
            let ch = s.chars().next().unwrap();
            Ok(Self::Punct(Punct::new(ch, Spacing::Alone)))
        }
    };
    ($input:ident, $look:ident, [$first:tt, $($token:tt),+ $(,)?], $else:tt) => {
        if $look.peek(Token![$first]) {
            if_punct!(@internal $input, $first)
        }
        $(else if $look.peek(Token![$token]) { if_punct!(@internal $input, $token) })+
        else $else
    };
}

impl Parse for Content {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let look = input.lookahead1();
        look.peek(Paren);

        if_punct!(input, look, [+, @, !, ^, :, ,, /, ., =, |, #, ?, %, ;, *, -, _], {
            if look.peek(Lt) {
                input.parse().map(Self::Tag)
            } else if look.peek(Brace) {
                input.parse().map(Self::Block)
            } else if look.peek(Ident::peek_any) {
                Ident::parse_any(input).map(Self::Ident)
            } else if look.peek(LitStr) {
                input.parse().map(Self::Str)
            } else if look.peek(LitInt) {
                input.parse().map(Self::Int)
            } else if look.peek(LitFloat) {
                input.parse().map(Self::Float)
            } else if look.peek(And) {
                Ok(Self::Escape {
                    amp: input.parse()?,
                    escape: input.parse()?,
                    semi: input.parse()?,
                })
            } else {
                return Err(look.error());
            }
        })
    }
}

impl Parse for Escape {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let look = input.lookahead1();
        if look.peek(Pound) {
            let pound: Pound = input.parse()?;
            let look = input.lookahead1();
            if look.peek(LitInt) {
                Ok(Escape::Int { pound, lit: input.parse()? })
            } else if look.peek(Ident::peek_any) {
                let ident = Ident::parse_any(input)?;
                let name = ident.to_string();
                let mut iter = name.chars();
                if let Some('x') = iter.next() {
                    let value = iter.map_while(|ch| ch.to_digit(16)).fold(0, |a, b| a * 16 + b);

                    if value != 0 {
                        Ok(Escape::X {
                            pound,
                            value,
                            x: ident,
                        })
                    } else {
                        Err(Error::new(ident.span(), "Escape sequence must not be zero."))
                    }
                } else {
                    Err(Error::new(ident.span(), "Invalid escape sequence, expected: `x`, or decimal"))
                }
            } else {
                Err(look.error())
            }
        } else if look.peek(Ident::peek_any) {
            let ident = Ident::parse_any(input)?;
            let x = match ident.to_string().as_str() {
                "lt"    => Escape::Lt(ident.span()),
                "gt"    => Escape::Gt(ident.span()),
                "amp"   => Escape::Amp(ident.span()),
                "apos"  => Escape::Apos(ident.span()),
                "quot"  => Escape::Quot(ident.span()),
                _       => return Err(Error::new(ident.span(), "Invalid escape sequence, expected: `lt`, `gt`, `amp`, `apos`, `quot`, or `#`")),
            };
            Ok(x)
        } else {
            Err(look.error())
        }
    }
}