use proc_macro::TokenStream;
use proc_macro2::TokenStream as TokenStream2;
use quote::{format_ident, quote};
use syn::DataStruct;
#[proc_macro_derive(DeserializeXml, attributes(deserialize_xml))]
pub fn deserialize_xml_derive(input: TokenStream) -> TokenStream {
let ast = syn::parse(input).unwrap();
impl_deserialize_xml_macro(&ast)
}
struct ParserCode {
match_body: Vec<TokenStream2>,
}
enum Attr {
Tag(String), }
enum FieldType<'a> {
Basic,
Vec(&'a proc_macro2::Ident),
Option(&'a proc_macro2::Ident),
}
fn parse_field_type(ty: &syn::Type) -> FieldType {
use FieldType::*;
let path = match ty {
syn::Type::Path(syn::TypePath { path, .. }) => path,
_ => return Basic,
};
if path.segments.len() != 1 {
return Basic;
}
let ps = match path.segments.first() {
Some(ps @ syn::PathSegment { .. }) => ps,
None => return Basic,
};
if ps.ident != "Vec" && ps.ident != "Option" {
return Basic;
}
let generic_args = match &ps.arguments {
syn::PathArguments::AngleBracketed(syn::AngleBracketedGenericArguments {
args, ..
}) => args,
_ => return Basic,
};
if generic_args.len() != 1 {
return Basic;
}
let inner_path = match generic_args.first() {
Some(syn::GenericArgument::Type(syn::Type::Path(syn::TypePath { path, .. }))) => path,
_ => return Basic,
};
if inner_path.segments.len() != 1 {
return Basic;
}
if let Some(seg) = inner_path.segments.first() {
match ps.ident.to_string().as_str() {
"Vec" => Vec(&seg.ident),
"Option" => Option(&seg.ident),
_ => unreachable!("Earlier check should ensure this is either 'Vec' or 'Option'"),
}
} else {
Basic
}
}
fn generate_parser(ds: &DataStruct) -> ParserCode {
let mut match_body = vec![];
match &ds.fields {
syn::Fields::Named(named) => {
for field in &named.named {
let field_name = field.ident.as_ref().unwrap();
let field_type = parse_field_type(&field.ty);
let mut tag_name = match field_type {
FieldType::Basic => field_name.to_string().to_ascii_lowercase(),
FieldType::Vec(t) => t.to_string().to_ascii_lowercase(),
FieldType::Option(_) => field_name.to_string().to_ascii_lowercase(),
};
for raw_attr in &field.attrs {
for parsed_attr in parse_attr(raw_attr) {
match parsed_attr {
Attr::Tag(s) => {
tag_name = s;
}
}
}
}
let assignment_stmt = match field_type {
FieldType::Basic => quote! {
result.#field_name = DeserializeXml::from_reader(reader)?;
},
FieldType::Vec(_) => quote! {
result.#field_name.push(DeserializeXml::from_reader(reader)?);
},
FieldType::Option(_) => quote! {
result.#field_name = Some(DeserializeXml::from_reader(reader)?);
},
};
let match_body_case = quote! {
(BuildingStruct, Ok(StartElement { name, .. })) if name.local_name == #tag_name && name.prefix.is_none() => {
#assignment_stmt
}
};
match_body.push(match_body_case);
}
}
_ => panic!("#[derive(DeserializeXml)] is only implemented for named fields"), }
ParserCode { match_body }
}
fn parse_attr(attr: &syn::Attribute) -> Vec<Attr> {
let meta = match attr.parse_meta() {
Err(_) => return vec![],
Ok(syn::Meta::List(meta)) => meta,
Ok(meta) => panic!(
"#[deserialize_xml(...)] encountered unknown attribute: {:?}",
meta
),
};
if meta.path.segments.len() != 1 || meta.path.segments[0].ident != "deserialize_xml" {
return vec![];
}
let mut parsed_attrs = vec![];
for nested in meta.nested {
let nv = match nested {
syn::NestedMeta::Meta(syn::Meta::NameValue(nv)) => nv,
_ => panic!("#[deserialize_xml(...)]: unsupported attribute format"),
};
if nv.path.segments.len() != 1 {
return parsed_attrs;
}
match nv.path.segments[0].ident.to_string().as_str() {
"tag" => match nv.lit {
syn::Lit::Str(s) => parsed_attrs.push(Attr::Tag(s.value())),
x => panic!(
"#[deserialize_xml(...)]: unrecognized value for 'tag' attribute: {:?}",
x
),
},
x => panic!("#[deserialize_xml(...)]: unrecognized attribute: '{}'", x),
}
}
parsed_attrs
}
fn impl_deserialize_xml_macro(ast: &syn::DeriveInput) -> TokenStream {
let name = &ast.ident;
let mut tag_name = ast.ident.to_string().to_ascii_lowercase();
for raw_attr in &ast.attrs {
for parsed_attr in parse_attr(raw_attr) {
match parsed_attr {
Attr::Tag(s) => {
tag_name = s;
}
}
}
}
let parser_code = match &ast.data {
syn::Data::Struct(ds) => generate_parser(ds),
_ => panic!("#[derive(DeserializeXml)] is only implemented for structs"),
};
let match_body = parser_code.match_body;
let parser_state_enum_ident = format_ident!("ParserState{}", name);
let gen = quote! {
#[allow(non_camel_case_types)]
enum #parser_state_enum_ident {
Start,
BuildingStruct,
}
impl DeserializeXml for #name {
fn from_reader<R: ::deserialize_xml::Read>(reader: &mut ::deserialize_xml::Peekable<::deserialize_xml::xml::reader::Events<R>>) -> ::deserialize_xml::Result<Self> {
use ::deserialize_xml::xml::reader;
use ::deserialize_xml::xml::reader::XmlEvent::*;
use #parser_state_enum_ident::*;
let mut state = Start;
let mut result = Self::default();
while let Some(event) = reader.peek() {
match (&mut state, event) {
(_, Err(e)) => { reader.next().unwrap()?; },
(_, Ok(EndDocument)) => break,
(Start, Ok(StartDocument { .. })) => { reader.next(); },
(Start, Ok(StartElement { name, .. })) if name.local_name == #tag_name => {
state = BuildingStruct;
reader.next();
},
(_, Ok(EndElement { name, .. })) if name.local_name == #tag_name => {
reader.next(); break;
},
#(#match_body)*
(_, Ok(_)) => { reader.next(); },
}
}
Ok(result)
}
}
};
gen.into()
}