From dee065cd9ae1f02b20c18ca93b6406a00b2abca3 Mon Sep 17 00:00:00 2001 From: Dirkjan Ochtman Date: Thu, 1 Sep 2022 16:29:07 +0200 Subject: [PATCH] Refactor how attributes are parsed --- instant-xml-macros/src/de.rs | 69 +++++------- instant-xml-macros/src/lib.rs | 202 +++++++++++++++++++--------------- instant-xml-macros/src/ser.rs | 107 +++++++++--------- 3 files changed, 191 insertions(+), 187 deletions(-) diff --git a/instant-xml-macros/src/de.rs b/instant-xml-macros/src/de.rs index dbb5382..75dfda3 100644 --- a/instant-xml-macros/src/de.rs +++ b/instant-xml-macros/src/de.rs @@ -1,7 +1,7 @@ use proc_macro2::{Ident, Span, TokenStream}; use quote::{quote, ToTokens}; -use crate::{namespaces, retrieve_field_attribute, FieldAttribute}; +use crate::{ContainerMeta, FieldMeta, Namespace}; struct Tokens { enum_: TokenStream, @@ -34,6 +34,13 @@ impl quote::ToTokens for Deserializer { impl Deserializer { pub fn new(input: &syn::DeriveInput) -> Deserializer { let ident = &input.ident; + let container_meta = ContainerMeta::from_derive(input); + let default_namespace = match container_meta.ns.default { + Namespace::Default => String::new(), + Namespace::Prefix(_) => panic!("container namespace cannot be prefix"), + Namespace::Literal(ns) => ns, + }; + let generics = (&input.generics).into_token_stream(); let lifetimes = (&input.generics.params).into_token_stream(); @@ -56,10 +63,9 @@ impl Deserializer { let name = ident.to_string(); let mut out = TokenStream::new(); - let (default_namespace, other_namespaces) = namespaces(&input.attrs); let mut namespaces_map = quote!(let mut namespaces_map = std::collections::HashMap::new();); - for (k, v) in other_namespaces.iter() { + for (k, v) in container_meta.ns.prefixes.iter() { namespaces_map.extend(quote!( namespaces_map.insert(#k, #v); )) @@ -78,26 +84,16 @@ impl Deserializer { match data.fields { syn::Fields::Named(ref fields) => { fields.named.iter().enumerate().for_each(|(index, field)| { - let mut field_namespace = None; - let (tokens, def_prefix, is_element) = match retrieve_field_attribute(field) { - Some(FieldAttribute::Namespace(value)) => { - field_namespace = Some(value); - (&mut elements_tokens, None, true) + let field_meta = FieldMeta::from_field(field); + if let Namespace::Prefix(prefix) = &field_meta.ns.default { + if !container_meta.ns.prefixes.contains_key(prefix) { + panic!("unknown prefix for this type"); } - Some(FieldAttribute::PrefixIdentifier(def_prefix)) => { - if other_namespaces.get(&def_prefix).is_none() { - panic!("Namespace with such prefix do not exist for this struct"); - } - - (&mut elements_tokens, Some(def_prefix), true) - }, - Some(FieldAttribute::Attribute) => { - (&mut attributes_tokens, None, false) - } - None => { - (&mut elements_tokens, None, true) - }, + } + let tokens = match field_meta.attribute { + true => &mut attributes_tokens, + false => &mut elements_tokens, }; Self::process_field( @@ -106,9 +102,7 @@ impl Deserializer { &mut declare_values, &mut return_val, tokens, - is_element, - def_prefix, - field_namespace, + field_meta, ); }); } @@ -237,9 +231,7 @@ impl Deserializer { declare_values: &mut TokenStream, return_val: &mut TokenStream, tokens: &mut Tokens, - is_element: bool, - def_prefix: Option, - field_namespace: Option, + field_meta: FieldMeta, ) { let field_var = field.ident.as_ref().unwrap(); let field_var_str = field_var.to_string(); @@ -254,7 +246,7 @@ impl Deserializer { const #const_field_var_str: &str = <#no_lifetime_type>::KIND.name(#field_var_str); )); - if is_element { + if !field_meta.attribute { tokens.names.extend(quote!( #const_field_var_str => __Elements::#enum_name, )); @@ -268,28 +260,21 @@ impl Deserializer { let mut #enum_name: Option<#no_lifetime_type> = None; )); - let def_prefix = match def_prefix { - Some(def_prefix) => quote!(Some(#def_prefix)), - None => quote!(None::<&str>), + let (field_prefix, new_default_ns) = match field_meta.ns.default { + Namespace::Default => (quote!(None::<&str>), quote!(None::<&str>)), + Namespace::Prefix(prefix) => (quote!(Some(#prefix)), quote!(None)), + Namespace::Literal(ns) => (quote!(None::<&str>), quote!(Some(#ns))), }; - let field_namespace = match field_namespace { - Some(field_namespace) => { - quote!(let field_namespace: Option<&str> = Some(#field_namespace);) - } - None => quote!(let field_namespace: Option<&str> = None;), - }; - - if is_element { + if !field_meta.attribute { tokens.match_.extend(quote!( __Elements::#enum_name => { if #enum_name.is_some() { panic!("duplicated value"); } - deserializer.compare_namespace(&item.prefix, #def_prefix)?; - #field_namespace - deserializer.set_next_def_namespace(field_namespace)?; + deserializer.compare_namespace(&item.prefix, #field_prefix)?; + deserializer.set_next_def_namespace(#new_default_ns)?; #enum_name = Some(<#no_lifetime_type>::deserialize(deserializer)?); }, )); diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 8f50b66..f750f58 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -7,105 +7,20 @@ use std::collections::HashMap; use proc_macro2::TokenStream; use quote::{quote, ToTokens}; -use syn::{parse_macro_input, Lit, Meta, NestedMeta}; +use syn::punctuated::Punctuated; +use syn::{parse_macro_input, Meta, NestedMeta}; use crate::ser::Serializer; -const XML: &str = "xml"; - -pub(crate) enum FieldAttribute { - Namespace(String), - PrefixIdentifier(String), - Attribute, -} - -pub(crate) fn namespaces(attributes: &Vec) -> (String, HashMap) { - let mut default_namespace = String::new(); - let mut other_namespaces = HashMap::default(); - - let (list, name) = match retrieve_attr_list(attributes) { - Some((Some(list), name)) => (list, name), - None => return (default_namespace, other_namespaces), - _ => panic!("wrong parameters"), - }; - - if name == "namespace" { - let mut iter = list.nested.iter(); - let mut next = iter.next(); - if let Some(NestedMeta::Lit(Lit::Str(v))) = next { - default_namespace = v.value(); - next = iter.next(); - } - - while let Some(value) = next { - if let NestedMeta::Meta(Meta::NameValue(key)) = value { - if let Lit::Str(value) = &key.lit { - other_namespaces - .insert(key.path.get_ident().unwrap().to_string(), value.value()); - next = iter.next(); - continue; - } - } - panic!("Wrong data") - } - } - - (default_namespace, other_namespaces) -} - -pub(crate) fn retrieve_field_attribute(input: &syn::Field) -> Option { - match retrieve_attr_list(&input.attrs) { - Some((Some(list), name)) if name.as_str() == "namespace" => match list.nested.first() { - Some(NestedMeta::Lit(Lit::Str(v))) => Some(FieldAttribute::Namespace(v.value())), - Some(NestedMeta::Meta(Meta::Path(v))) => { - if let Some(ident) = v.get_ident() { - Some(FieldAttribute::PrefixIdentifier(ident.to_string())) - } else { - panic!("unexpected parameter"); - } - } - _ => panic!("unexpected parameter"), - }, - Some((None, name)) if name.as_str() == "attribute" => Some(FieldAttribute::Attribute), - None => None, - _ => panic!("unexpected parameter"), - } -} - -fn retrieve_attr_list(attributes: &Vec) -> Option<(Option, String)> { - for attr in attributes { - if !attr.path.is_ident(XML) { - continue; - } - - let nested = match attr.parse_meta() { - Ok(Meta::List(meta)) => meta.nested, - Ok(_) => todo!(), - _ => todo!(), - }; - - let list = match nested.first() { - Some(NestedMeta::Meta(Meta::List(list))) => list, - Some(NestedMeta::Meta(Meta::Path(path))) => { - return Some((None, path.get_ident()?.to_string())) - } - _ => return None, - }; - - return Some((Some(list.to_owned()), list.path.get_ident()?.to_string())); - } - - None -} - #[proc_macro_derive(ToXml, attributes(xml))] pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); + let ident = &ast.ident; let generics = (&ast.generics).into_token_stream(); let root_name = ident.to_string(); - let mut serializer = Serializer::new(&ast.attrs); + let mut serializer = Serializer::new(&ast); let mut header = TokenStream::new(); serializer.add_header(&mut header); @@ -162,9 +77,118 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { #[proc_macro_derive(FromXml, attributes(xml))] pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); + let deserializer = de::Deserializer::new(&ast); proc_macro::TokenStream::from(quote!( #deserializer )) } + +#[derive(Default)] +struct ContainerMeta { + ns: NamespaceMeta, +} + +impl ContainerMeta { + fn from_derive(input: &syn::DeriveInput) -> ContainerMeta { + let mut meta = ContainerMeta::default(); + for item in meta_items(&input.attrs) { + match item { + Meta::List(list) if list.path.is_ident("namespace") => { + meta.ns = NamespaceMeta::from_list(&list.nested) + } + _ => panic!("invalid xml attribute syntax"), + } + } + meta + } +} + +#[derive(Default)] +struct FieldMeta { + attribute: bool, + ns: NamespaceMeta, +} + +impl FieldMeta { + fn from_field(input: &syn::Field) -> FieldMeta { + let mut meta = FieldMeta::default(); + for item in meta_items(&input.attrs) { + match item { + Meta::Path(path) if path.is_ident("attribute") => meta.attribute = true, + Meta::List(list) if list.path.is_ident("namespace") => { + meta.ns = NamespaceMeta::from_list(&list.nested) + } + _ => panic!("invalid xml attribute syntax"), + } + } + meta + } +} + +#[derive(Default)] +struct NamespaceMeta { + default: Namespace, + prefixes: HashMap, +} + +impl NamespaceMeta { + fn from_list(list: &Punctuated) -> NamespaceMeta { + let mut meta = NamespaceMeta::default(); + for (i, item) in list.iter().enumerate() { + match item { + NestedMeta::Meta(inner) => match inner { + Meta::Path(path) => match path.get_ident() { + Some(id) => meta.default = Namespace::Prefix(id.to_string()), + None => panic!("invalid xml attribute syntax"), + }, + Meta::NameValue(nv) => match (nv.path.get_ident(), &nv.lit) { + (Some(id), syn::Lit::Str(lit)) => { + meta.prefixes.insert(id.to_string(), lit.value()); + } + _ => panic!("invalid xml attribute syntax"), + }, + _ => panic!("invalid xml attribute syntax"), + }, + NestedMeta::Lit(syn::Lit::Str(lit)) if i == 0 => { + meta.default = Namespace::Literal(lit.value()) + } + _ => panic!("invalid xml attribute syntax"), + } + } + meta + } +} + +fn meta_items(attrs: &[syn::Attribute]) -> impl Iterator + '_ { + attrs + .iter() + .filter_map(|attr| { + if !attr.path.is_ident("xml") { + return None; + } + + match attr.parse_meta() { + Ok(Meta::List(meta)) => Some(meta.nested.into_iter()), + _ => panic!("unexpected xml attribute syntax"), + } + }) + .flatten() + .map(|item| match item { + NestedMeta::Meta(item) => item, + NestedMeta::Lit(_) => panic!("unexpected xml attribute syntax"), + }) +} + +enum Namespace { + Default, + Prefix(String), + Literal(String), +} + +impl Default for Namespace { + fn default() -> Self { + Namespace::Default + } +} diff --git a/instant-xml-macros/src/ser.rs b/instant-xml-macros/src/ser.rs index 6542b20..f213c8f 100644 --- a/instant-xml-macros/src/ser.rs +++ b/instant-xml-macros/src/ser.rs @@ -1,22 +1,16 @@ -use std::collections::HashMap; - use proc_macro2::TokenStream; use quote::quote; -use crate::{namespaces, retrieve_field_attribute, FieldAttribute}; +use crate::{ContainerMeta, FieldMeta, Namespace}; pub struct Serializer { - default_namespace: String, - other_namespaces: HashMap, + meta: ContainerMeta, } impl<'a> Serializer { - pub fn new(attributes: &'a Vec) -> Serializer { - let (default_namespace, other_namespaces) = namespaces(attributes); - - Serializer { - default_namespace, - other_namespaces, + pub fn new(input: &syn::DeriveInput) -> Self { + Self { + meta: ContainerMeta::from_derive(input), } } @@ -26,7 +20,11 @@ impl<'a> Serializer { serializer.output.write_str(field_context.name)?; )); - let default_namespace = &self.default_namespace; + let default_namespace = match &self.meta.ns.default { + Namespace::Default => "", + Namespace::Prefix(_) => panic!("type cannot have prefix as namespace"), + Namespace::Literal(ns) => ns, + }; output.extend(quote!( // Check if parent default namespace equals if serializer.parent_default_namespace() != #default_namespace { @@ -37,7 +35,7 @@ impl<'a> Serializer { serializer.update_parent_default_namespace(#default_namespace); )); - let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect(); + let mut sorted_values: Vec<_> = self.meta.ns.prefixes.iter().collect(); sorted_values.sort(); for (key, val) in sorted_values { @@ -87,53 +85,50 @@ impl<'a> Serializer { }; ); - let stream_ref = match retrieve_field_attribute(field) { - Some(FieldAttribute::Namespace(namespace)) => { - body.extend(quote!( - #declaration - field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace)); - )); - body - } - Some(FieldAttribute::PrefixIdentifier(prefix_key)) => { - match self.other_namespaces.get(&prefix_key) { - Some(val) => { - body.extend(quote!( - #declaration + let field_meta = FieldMeta::from_field(field); + if field_meta.attribute { + attributes.extend(quote!( + #declaration - // Check if such namespace already exist, if so change its prefix to parent prefix - let prefix_key = match serializer.parent_namespaces.get(#val) { - Some(key) => key, - None => #prefix_key, - }; - )); - } - None => panic!("Prefix not defined: {}", prefix_key), - }; + serializer.add_attribute_key(&#name)?; + field.attribute = Some(instant_xml::FieldAttribute::Attribute); + serializer.set_field_context(field)?; + self.#field_value.serialize(serializer)?; + )); + return; + } - body.extend(quote!( - field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key)); - )); - body - } - Some(FieldAttribute::Attribute) => { - attributes.extend(quote!( - #declaration + if let Namespace::Literal(ns) = &field_meta.ns.default { + body.extend(quote!( + #declaration + field.attribute = Some(instant_xml::FieldAttribute::Namespace(#ns)); + )); + } else if let Namespace::Prefix(prefix) = &field_meta.ns.default { + match self.meta.ns.prefixes.get(prefix) { + Some(val) => { + body.extend(quote!( + #declaration - serializer.add_attribute_key(&#name)?; - field.attribute = Some(instant_xml::FieldAttribute::Attribute); - )); - attributes - } - _ => { - body.extend(quote!( - #declaration - )); - body - } + // Check if such namespace already exist, if so change its prefix to parent prefix + let prefix_key = match serializer.parent_namespaces.get(#val) { + Some(key) => key, + None => #prefix, + }; + )); + } + None => panic!("Prefix not defined: {}", prefix), + }; + + body.extend(quote!( + field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key)); + )); + } else { + body.extend(quote!( + #declaration + )); }; - stream_ref.extend(quote!( + body.extend(quote!( serializer.set_field_context(field)?; self.#field_value.serialize(serializer)?; )); @@ -143,7 +138,7 @@ impl<'a> Serializer { let mut namespaces = quote!( let mut to_remove: Vec<&str> = Vec::new(); ); - for (k, v) in self.other_namespaces.iter() { + for (k, v) in self.meta.ns.prefixes.iter() { namespaces.extend(quote!( // Only adding to HashMap if namespace do not exist, if it exist it will use the parent defined prefix if let std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#v) {