#![doc=include_str!("../README.md")]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::Ident;
use quote::{quote, ToTokens};
use syn::{
ext::IdentExt as _,
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
token, Block, LitStr, Token,
};
struct XmlTag {
#[allow(dead_code)]
open: Token![<],
is_end_tag: Option<Token![/]>,
name: XmlId,
attributes: Vec<XmlAttribute>,
self_close: Option<Token![/]>,
#[allow(dead_code)]
close: Token![>],
}
impl Parse for XmlTag {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
open: input.parse()?,
is_end_tag: input.parse()?,
name: input.parse()?,
attributes: {
let mut vec = Vec::new();
loop {
let lookahead = input.lookahead1();
if lookahead.peek(Token![/]) || lookahead.peek(Token![>]) {
break;
} else {
vec.push(input.parse()?)
}
}
vec
},
self_close: input.parse()?,
close: input.parse()?,
})
}
}
struct XmlId(Vec<ExtraXmlIdSeg>);
impl XmlId {
pub fn to_lit_str(&self) -> LitStr {
let mut buf = String::new();
for x in &self.0 {
match x {
ExtraXmlIdSeg::Ident(x) => buf.push_str(&x.to_string()),
ExtraXmlIdSeg::Dot(_) => buf.push('.'),
ExtraXmlIdSeg::Colon(_) => buf.push(':'),
ExtraXmlIdSeg::Dash(_) => buf.push('-'),
}
}
LitStr::new(&buf, self.0[0].span())
}
}
impl Parse for XmlId {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut vec = Vec::new();
let mut last_one_ident = false;
loop {
if (input.peek(syn::Ident::peek_any) && !last_one_ident)
|| input.peek(Token![.])
|| input.peek(Token![:])
|| input.peek(Token![-])
{
if input.peek(syn::Ident::peek_any) {
last_one_ident = true;
} else {
last_one_ident = false;
}
vec.push(input.parse()?);
} else {
break;
}
}
Ok(Self(vec))
}
}
impl ToTokens for XmlId {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
for seg in &self.0 {
seg.to_tokens(tokens);
}
}
}
enum ExtraXmlIdSeg {
Ident(Ident),
Dot(Token![.]),
Colon(Token![:]),
Dash(Token![-]),
}
impl ExtraXmlIdSeg {
fn span(&self) -> proc_macro2::Span {
match self {
ExtraXmlIdSeg::Ident(x) => x.span(),
ExtraXmlIdSeg::Dot(x) => x.span(),
ExtraXmlIdSeg::Colon(x) => x.span(),
ExtraXmlIdSeg::Dash(x) => x.span(),
}
}
}
impl Parse for ExtraXmlIdSeg {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Token![.]) {
Ok(ExtraXmlIdSeg::Dot(input.parse()?))
} else if lookahead.peek(Token![:]) {
Ok(ExtraXmlIdSeg::Colon(input.parse()?))
} else if lookahead.peek(Token![-]) {
Ok(ExtraXmlIdSeg::Dash(input.parse()?))
} else {
Ok(ExtraXmlIdSeg::Ident(input.parse()?))
}
}
}
impl ToTokens for ExtraXmlIdSeg {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::Dot(x) => x.to_tokens(tokens),
Self::Colon(x) => x.to_tokens(tokens),
Self::Dash(x) => x.to_tokens(tokens),
Self::Ident(x) => x.to_tokens(tokens),
}
}
}
struct XmlAttribute {
name: XmlId,
#[allow(dead_code)]
eq: Token![=],
value: XmlVal,
}
impl Parse for XmlAttribute {
fn parse(input: ParseStream) -> syn::Result<Self> {
Ok(Self {
name: input.parse()?,
eq: input.parse()?,
value: input.parse()?,
})
}
}
enum XmlVal {
Str(LitStr),
Block(Block),
}
impl ToTokens for XmlVal {
fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
match self {
Self::Str(x) => x.to_tokens(tokens),
Self::Block(x) => quote! {#x}.to_tokens(tokens),
}
}
}
impl Parse for XmlVal {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(token::Brace) {
Ok(XmlVal::Block(input.parse()?))
} else {
Ok(XmlVal::Str(input.parse()?))
}
}
}
#[proc_macro]
pub fn xml(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as XmlTag);
let mut attrs = Vec::new();
for attribute in input.attributes {
let name = attribute.name.to_lit_str();
let val = attribute.value;
attrs.push(quote! {
event_start.push_attribute((#name, #val));
});
}
let name = input.name.to_lit_str();
let expanded = if input.is_end_tag.is_some() {
if input.self_close.is_some() {
quote! { compile_error!("Tag cannot start with `</` and end with `/>`") }
} else {
if !attrs.is_empty() {
quote! { compile_error!("End tag cannot have attribute") }
} else {
quote! {{
let mut event = ::quick_xml::events::BytesEnd::new(#name);
::quick_xml::events::Event::End(event)
}}
}
}
} else {
let event_type = if input.self_close.is_some() {
quote! {Empty}
} else {
quote! {Start}
};
quote! {{
let mut event_start = ::quick_xml::events::BytesStart::new(#name);
#(#attrs)*
::quick_xml::events::Event::#event_type(event_start)
}}
};
TokenStream::from(expanded)
}