Simplify output token stream for ToXml macro

This commit is contained in:
Dirkjan Ochtman 2022-09-07 09:53:11 +02:00
parent 6c2dd89ef3
commit c8cc5f5a48
1 changed files with 92 additions and 143 deletions

View File

@ -4,23 +4,15 @@ use quote::quote;
use crate::{ContainerMeta, FieldMeta}; use crate::{ContainerMeta, FieldMeta};
pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream { pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
let ident = &input.ident;
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let root_name = ident.to_string();
let mut serializer = Serializer::new(input);
let mut header = TokenStream::new();
serializer.add_header(&mut header);
let mut body = TokenStream::new(); let mut body = TokenStream::new();
let mut attributes = TokenStream::new(); let mut attributes = TokenStream::new();
let meta = ContainerMeta::from_derive(input);
match &input.data { match &input.data {
syn::Data::Struct(ref data) => { syn::Data::Struct(ref data) => {
match data.fields { match data.fields {
syn::Fields::Named(ref fields) => { syn::Fields::Named(ref fields) => {
fields.named.iter().for_each(|field| { fields.named.iter().for_each(|field| {
serializer.process_named_field(field, &mut body, &mut attributes); process_named_field(field, &mut body, &mut attributes, &meta);
}); });
} }
syn::Fields::Unnamed(_) => todo!(), syn::Fields::Unnamed(_) => todo!(),
@ -30,10 +22,32 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
_ => todo!(), _ => todo!(),
}; };
let mut footer = TokenStream::new(); let mut prefixes = TokenStream::new();
serializer.add_footer(&root_name, &mut footer); for (key, val) in &meta.ns.prefixes {
prefixes.extend(quote!(
if serializer.parent_namespaces.get(#val).is_none() {
serializer.output.write_str(" xmlns:")?;
serializer.output.write_str(#key)?;
serializer.output.write_str("=\"")?;
serializer.output.write_str(#val)?;
serializer.output.write_char('\"')?;
}
let current_namespaces = serializer.namespaces_token(); if let ::std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#val) {
v.insert(#key);
// Will remove added namespaces when going "up"
to_remove.push(#val);
};
));
}
let ident = &input.ident;
let root_name = ident.to_string();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let default_namespace = match &meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
};
quote!( quote!(
impl #impl_generics ToXml for #ident #ty_generics #where_clause { impl #impl_generics ToXml for #ident #ty_generics #where_clause {
@ -49,12 +63,37 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
attribute: None, attribute: None,
}; };
#attributes // Start tag
serializer.output.write_char('<')?;
if serializer.parent_default_namespace() != #default_namespace {
if let Some(prefix) = serializer.parent_namespaces.get(#default_namespace) {
serializer.output.write_str(prefix)?;
serializer.output.write_char(':')?;
serializer.output.write_str(field_context.name)?;
} else {
serializer.output.write_str(field_context.name)?;
serializer.output.write_str(" xmlns=\"")?;
serializer.output.write_str(#default_namespace)?;
serializer.output.write_char('\"')?;
}
} else {
serializer.output.write_str(field_context.name)?;
}
serializer.update_parent_default_namespace(#default_namespace);
let mut to_remove: Vec<&str> = Vec::new();
#prefixes
#attributes
serializer.consume_current_attributes()?;
serializer.output.write_char('>')?;
#header
#current_namespaces
#body #body
#footer
// Close tag
serializer.output.write_str("</")?;
serializer.output.write_str(#root_name)?;
serializer.output.write_char('>')?;
serializer.retrieve_parent_default_namespace();
// Removing current namespaces // Removing current namespaces
for it in to_remove { for it in to_remove {
@ -67,140 +106,50 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
) )
} }
struct Serializer { fn process_named_field(
meta: ContainerMeta, field: &syn::Field,
} body: &mut TokenStream,
attributes: &mut TokenStream,
meta: &ContainerMeta,
) {
let name = field.ident.as_ref().unwrap().to_string();
let field_value = field.ident.as_ref().unwrap();
impl<'a> Serializer { let declaration = quote!(
fn new(input: &syn::DeriveInput) -> Self { let mut field = FieldContext {
Self { name: #name,
meta: ContainerMeta::from_derive(input), attribute: None,
}
}
fn add_header(&mut self, output: &'a mut TokenStream) {
output.extend(quote!(
serializer.output.write_char('<')?;
));
let default_namespace = match &self.meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
}; };
);
output.extend(quote!( let field_meta = FieldMeta::from_field(field);
// Check if parent default namespace equals if field_meta.attribute {
if serializer.parent_default_namespace() != #default_namespace { attributes.extend(quote!(
if let Some(prefix) = serializer.parent_namespaces.get(#default_namespace) {
serializer.output.write_str(prefix)?;
serializer.output.write_char(':')?;
serializer.output.write_str(field_context.name)?;
} else {
serializer.output.write_str(field_context.name)?;
serializer.output.write_str(" xmlns=\"")?;
serializer.output.write_str(#default_namespace)?;
serializer.output.write_char('\"')?;
}
} else {
serializer.output.write_str(field_context.name)?;
}
serializer.update_parent_default_namespace(#default_namespace);
));
for (key, val) in &self.meta.ns.prefixes {
output.extend(quote!(
if serializer.parent_namespaces.get(#val).is_none() {
serializer.output.write_str(" xmlns:")?;
serializer.output.write_str(#key)?;
serializer.output.write_str("=\"")?;
serializer.output.write_str(#val)?;
serializer.output.write_char('\"')?;
}
));
}
// Attributes
output.extend(quote!(
serializer.consume_current_attributes()?;
));
output.extend(quote!(
serializer.output.write_char('>')?;
));
}
fn add_footer(&mut self, root_name: &str, output: &'a mut TokenStream) {
output.extend(quote!(
serializer.output.write_str("</")?;
serializer.output.write_str(#root_name)?;
serializer.output.write_char('>')?;
serializer.retrieve_parent_default_namespace();
));
}
fn process_named_field(
&mut self,
field: &syn::Field,
body: &mut TokenStream,
attributes: &mut TokenStream,
) {
let name = field.ident.as_ref().unwrap().to_string();
let field_value = field.ident.as_ref().unwrap();
let declaration = quote!(
let mut field = FieldContext {
name: #name,
attribute: None,
};
);
let field_meta = FieldMeta::from_field(field);
if field_meta.attribute {
attributes.extend(quote!(
#declaration
serializer.add_attribute_key(&#name)?;
field.attribute = Some(FieldAttribute::Attribute);
serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?;
));
return;
}
let ns = match field_meta.ns.uri {
Some(ns) => quote!(#ns),
None => match &self.meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
},
};
body.extend(quote!(
#declaration #declaration
match serializer.parent_namespaces.get(#ns) {
Some(prefix) => field.attribute = Some(FieldAttribute::Prefix(prefix)), serializer.add_attribute_key(&#name)?;
None => field.attribute = Some(FieldAttribute::Namespace(#ns)), field.attribute = Some(FieldAttribute::Attribute);
}
serializer.set_field_context(field)?; serializer.set_field_context(field)?;
self.#field_value.serialize(serializer)?; self.#field_value.serialize(serializer)?;
)); ));
return;
} }
fn namespaces_token(&self) -> TokenStream { let ns = match field_meta.ns.uri {
let mut namespaces = quote!( Some(ns) => quote!(#ns),
let mut to_remove: Vec<&str> = Vec::new(); None => match &meta.ns.uri {
); Some(ns) => quote!(#ns),
for (k, v) in self.meta.ns.prefixes.iter() { None => quote!(""),
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) {
v.insert(#k); body.extend(quote!(
// Will remove added namespaces when going "up" #declaration
to_remove.push(#v); match serializer.parent_namespaces.get(#ns) {
}; Some(prefix) => field.attribute = Some(FieldAttribute::Prefix(prefix)),
)) None => field.attribute = Some(FieldAttribute::Namespace(#ns)),
} }
namespaces serializer.set_field_context(field)?;
} self.#field_value.serialize(serializer)?;
));
} }