#![doc=include_str!("../README.md")]
extern crate proc_macro;
use proc_macro::TokenStream;
use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens};
use syn::{
braced,
ext::IdentExt as _,
parse::{Parse, ParseStream},
parse_macro_input,
spanned::Spanned,
token::{self, Brace},
Block, LitStr, Token,
};
struct Xmls {
events: Vec<XmlEvent>,
}
impl Parse for Xmls {
fn parse(input: ParseStream) -> syn::Result<Self> {
let mut events = Vec::new();
while !input.is_empty() {
events.push(input.parse()?)
}
Ok(Self { events })
}
}
enum XmlEvent {
Tag(XmlTag),
Text(Block),
}
impl Parse for XmlEvent {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(Brace) {
Ok(Self::Text(input.parse()?))
} else {
Ok(Self::Tag(input.parse()?))
}
}
}
impl ToTokens for XmlEvent {
fn to_tokens(&self, tokens: &mut TokenStream2) {
match self {
XmlEvent::Tag(x) => x.to_tokens(tokens),
XmlEvent::Text(x) => quote! {
::quick_xml::events::Event::Text (
::quick_xml::events::BytesText::new(&*#x)
)
}
.to_tokens(tokens),
}
}
}
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()?,
})
}
}
impl ToTokens for XmlTag {
fn to_tokens(&self, tokens: &mut TokenStream2) {
let mut attrs = Vec::new();
for attribute in &self.attributes {
let name = attribute.key();
let val = attribute.value();
attrs.push(quote! {
event_start.push_attribute((#name, #val));
});
}
let name = self.name.to_lit_str();
let expanded = if self.is_end_tag.is_some() {
if self.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 self.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)
}}
};
tokens.extend(expanded);
}
}
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),
}
}
}
enum XmlAttribute {
Idented {
#[allow(dead_code)]
brace: Brace,
ident: Ident,
},
KV {
name: XmlId,
#[allow(dead_code)]
eq: Token![=],
value: XmlVal,
},
}
impl XmlAttribute {
fn key(&self) -> LitStr {
match self {
XmlAttribute::Idented { ident, .. } => LitStr::new(&ident.to_string(), ident.span()),
XmlAttribute::KV { name, .. } => name.to_lit_str(),
}
}
fn value(&self) -> TokenStream2 {
match self {
XmlAttribute::Idented { ident, .. } => quote!({&*#ident}),
XmlAttribute::KV { value, .. } => quote!(&*#value),
}
}
}
impl Parse for XmlAttribute {
fn parse(input: ParseStream) -> syn::Result<Self> {
let lookahead = input.lookahead1();
if lookahead.peek(syn::token::Brace) {
let content;
Ok(Self::Idented {
brace: braced!(content in input),
ident: content.parse()?,
})
} else {
Ok(Self::KV {
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 XmlEvent);
match input {
XmlEvent::Tag(tag) => tag.to_token_stream().into(),
XmlEvent::Text(text) => quote! {
::quick_xml::events::Event::Text(
::quick_xml::events::BytesText::new(&*#text)
)
}
.into(),
}
}
#[proc_macro]
pub fn xmls(input: TokenStream) -> TokenStream {
let xmls = parse_macro_input!(input as Xmls);
let mut ts = TokenStream2::new();
for xml in xmls.events {
xml.to_tokens(&mut ts);
Token).to_tokens(&mut ts);
}
quote! {[#ts]}.into()
}