xml_macro/
lib.rs

1#![doc=include_str!("../README.md")]
2extern crate proc_macro;
3use proc_macro::TokenStream;
4use proc_macro2::{Ident, Span, TokenStream as TokenStream2};
5use quote::{quote, ToTokens};
6use syn::{
7    braced,
8    ext::IdentExt as _,
9    parse::{Parse, ParseStream},
10    parse_macro_input,
11    spanned::Spanned,
12    token::{self, Brace},
13    Block, LitStr, Token,
14};
15
16struct Xmls {
17    events: Vec<XmlEvent>,
18}
19
20impl Parse for Xmls {
21    fn parse(input: ParseStream) -> syn::Result<Self> {
22        let mut events = Vec::new();
23        while !input.is_empty() {
24            events.push(input.parse()?)
25        }
26        Ok(Self { events })
27    }
28}
29
30/// An xml event
31enum XmlEvent {
32    Tag(XmlTag),
33    Text(Block),
34}
35
36impl Parse for XmlEvent {
37    fn parse(input: ParseStream) -> syn::Result<Self> {
38        let lookahead = input.lookahead1();
39        if lookahead.peek(Brace) {
40            Ok(Self::Text(input.parse()?))
41        } else {
42            Ok(Self::Tag(input.parse()?))
43        }
44    }
45}
46
47impl ToTokens for XmlEvent {
48    fn to_tokens(&self, tokens: &mut TokenStream2) {
49        match self {
50            XmlEvent::Tag(x) => x.to_tokens(tokens),
51            XmlEvent::Text(x) => quote! {
52                ::quick_xml::events::Event::Text (
53                    ::quick_xml::events::BytesText::new(&*#x)
54                )
55            }
56            .to_tokens(tokens),
57        }
58    }
59}
60
61/// An xml tag
62struct XmlTag {
63    /// The open token
64    #[allow(dead_code)]
65    open: Token![<],
66    is_end_tag: Option<Token![/]>,
67    name: XmlId,
68    attributes: Vec<XmlAttribute>,
69    self_close: Option<Token![/]>,
70    #[allow(dead_code)]
71    close: Token![>],
72}
73
74impl Parse for XmlTag {
75    fn parse(input: ParseStream) -> syn::Result<Self> {
76        Ok(Self {
77            open: input.parse()?,
78            is_end_tag: input.parse()?,
79            name: input.parse()?,
80            attributes: {
81                let mut vec = Vec::new();
82                loop {
83                    let lookahead = input.lookahead1();
84                    if lookahead.peek(Token![/]) || lookahead.peek(Token![>]) {
85                        break;
86                    } else {
87                        vec.push(input.parse()?)
88                    }
89                }
90                vec
91            },
92            self_close: input.parse()?,
93            close: input.parse()?,
94        })
95    }
96}
97
98impl ToTokens for XmlTag {
99    fn to_tokens(&self, tokens: &mut TokenStream2) {
100        let mut attrs = Vec::new();
101        for attribute in &self.attributes {
102            let name = attribute.key();
103            let val = attribute.value();
104            attrs.push(quote! {
105                event_start.push_attribute((#name, #val));
106            });
107        }
108
109        let name = self.name.to_lit_str();
110        let expanded = if self.is_end_tag.is_some() {
111            if self.self_close.is_some() {
112                quote! { compile_error!("Tag cannot start with `</` and end with `/>`") }
113            } else {
114                if !attrs.is_empty() {
115                    quote! { compile_error!("End tag cannot have attribute") }
116                } else {
117                    quote! {{
118                    let mut event = ::quick_xml::events::BytesEnd::new(#name);
119                    ::quick_xml::events::Event::End(event)
120                    }}
121                }
122            }
123        } else {
124            let event_type = if self.self_close.is_some() {
125                quote! {Empty}
126            } else {
127                quote! {Start}
128            };
129            quote! {{
130                let mut event_start = ::quick_xml::events::BytesStart::new(#name);
131                #(#attrs)*
132                ::quick_xml::events::Event::#event_type(event_start)
133            }}
134        };
135
136        tokens.extend(expanded);
137    }
138}
139
140struct XmlId(Vec<ExtraXmlIdSeg>);
141
142impl XmlId {
143    pub fn to_lit_str(&self) -> LitStr {
144        let mut buf = String::new();
145        for x in &self.0 {
146            match x {
147                ExtraXmlIdSeg::Ident(x) => buf.push_str(&x.to_string()),
148                ExtraXmlIdSeg::Dot(_) => buf.push('.'),
149                ExtraXmlIdSeg::Colon(_) => buf.push(':'),
150                ExtraXmlIdSeg::Dash(_) => buf.push('-'),
151            }
152        }
153        LitStr::new(&buf, self.0[0].span())
154    }
155}
156
157impl Parse for XmlId {
158    fn parse(input: ParseStream) -> syn::Result<Self> {
159        let mut vec = Vec::new();
160        let mut last_one_ident = false;
161        loop {
162            if (input.peek(syn::Ident::peek_any) && !last_one_ident)
163                || input.peek(Token![.])
164                || input.peek(Token![:])
165                || input.peek(Token![-])
166            {
167                if input.peek(syn::Ident::peek_any) {
168                    last_one_ident = true;
169                } else {
170                    last_one_ident = false;
171                }
172                vec.push(input.parse()?);
173            } else {
174                break;
175            }
176        }
177        Ok(Self(vec))
178    }
179}
180
181impl ToTokens for XmlId {
182    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
183        for seg in &self.0 {
184            seg.to_tokens(tokens);
185        }
186    }
187}
188
189enum ExtraXmlIdSeg {
190    Ident(Ident),
191    Dot(Token![.]),
192    Colon(Token![:]),
193    Dash(Token![-]),
194}
195
196impl ExtraXmlIdSeg {
197    fn span(&self) -> proc_macro2::Span {
198        match self {
199            ExtraXmlIdSeg::Ident(x) => x.span(),
200            ExtraXmlIdSeg::Dot(x) => x.span(),
201            ExtraXmlIdSeg::Colon(x) => x.span(),
202            ExtraXmlIdSeg::Dash(x) => x.span(),
203        }
204    }
205}
206
207impl Parse for ExtraXmlIdSeg {
208    fn parse(input: ParseStream) -> syn::Result<Self> {
209        let lookahead = input.lookahead1();
210        if lookahead.peek(Token![.]) {
211            Ok(ExtraXmlIdSeg::Dot(input.parse()?))
212        } else if lookahead.peek(Token![:]) {
213            Ok(ExtraXmlIdSeg::Colon(input.parse()?))
214        } else if lookahead.peek(Token![-]) {
215            Ok(ExtraXmlIdSeg::Dash(input.parse()?))
216        } else {
217            Ok(ExtraXmlIdSeg::Ident(input.parse()?))
218        }
219    }
220}
221
222impl ToTokens for ExtraXmlIdSeg {
223    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
224        match self {
225            Self::Dot(x) => x.to_tokens(tokens),
226            Self::Colon(x) => x.to_tokens(tokens),
227            Self::Dash(x) => x.to_tokens(tokens),
228            Self::Ident(x) => x.to_tokens(tokens),
229        }
230    }
231}
232
233/// An attribute in xml, such as `name="Josh"`
234enum XmlAttribute {
235    Idented {
236        #[allow(dead_code)]
237        brace: Brace,
238        ident: Ident,
239    },
240    KV {
241        name: XmlId,
242        #[allow(dead_code)]
243        eq: Token![=],
244        value: XmlVal,
245    },
246}
247
248impl XmlAttribute {
249    fn key(&self) -> LitStr {
250        match self {
251            XmlAttribute::Idented { ident, .. } => LitStr::new(&ident.to_string(), ident.span()),
252            XmlAttribute::KV { name, .. } => name.to_lit_str(),
253        }
254    }
255
256    fn value(&self) -> TokenStream2 {
257        match self {
258            XmlAttribute::Idented { ident, .. } => quote!({&*#ident}),
259            XmlAttribute::KV { value, .. } => quote!(&*#value),
260        }
261    }
262}
263
264impl Parse for XmlAttribute {
265    fn parse(input: ParseStream) -> syn::Result<Self> {
266        let lookahead = input.lookahead1();
267        if lookahead.peek(syn::token::Brace) {
268            let content;
269            Ok(Self::Idented {
270                brace: braced!(content in input),
271                ident: content.parse()?,
272            })
273        } else {
274            Ok(Self::KV {
275                name: input.parse()?,
276                eq: input.parse()?,
277                value: input.parse()?,
278            })
279        }
280    }
281}
282
283enum XmlVal {
284    Str(LitStr),
285    Block(Block),
286}
287
288impl ToTokens for XmlVal {
289    fn to_tokens(&self, tokens: &mut proc_macro2::TokenStream) {
290        match self {
291            Self::Str(x) => x.to_tokens(tokens),
292            Self::Block(x) => quote! {#x}.to_tokens(tokens),
293        }
294    }
295}
296
297impl Parse for XmlVal {
298    fn parse(input: ParseStream) -> syn::Result<Self> {
299        let lookahead = input.lookahead1();
300        if lookahead.peek(token::Brace) {
301            Ok(XmlVal::Block(input.parse()?))
302        } else {
303            Ok(XmlVal::Str(input.parse()?))
304        }
305    }
306}
307
308#[proc_macro]
309pub fn xml(input: TokenStream) -> TokenStream {
310    let input = parse_macro_input!(input as XmlEvent);
311    match input {
312        XmlEvent::Tag(tag) => tag.to_token_stream().into(),
313        XmlEvent::Text(text) => quote! {
314            ::quick_xml::events::Event::Text(
315                ::quick_xml::events::BytesText::new(&*#text)
316            )
317        }
318        .into(),
319    }
320}
321
322#[proc_macro]
323pub fn xmls(input: TokenStream) -> TokenStream {
324    let xmls = parse_macro_input!(input as Xmls);
325    let mut ts = TokenStream2::new();
326    for xml in xmls.events {
327        xml.to_tokens(&mut ts);
328        Token![,](Span::call_site()).to_tokens(&mut ts);
329    }
330    quote! {[#ts]}.into()
331}