extern crate proc_macro;
extern crate quote;
extern crate syn;
use proc_macro::TokenStream;
use quote::quote;
use quote::TokenStreamExt;
#[proc_macro_attribute]
pub fn xml_element(attr: TokenStream, input: TokenStream) -> TokenStream {
let item: syn::Item = syn::parse(input).expect("failed to parse input");
let mut original_clone = item.clone();
let args = attr.to_string();
assert!(args.starts_with("\""), "`#[xml_element]` requires an argument of the form `#[xml_element(\"xml_element_name_here\")]`");
let element_name = args.trim_matches(&['=', ' ', '"'][..]);
match item {
syn::Item::Struct(ref struct_item) => {
return gen_impl_code(&element_name, &mut original_clone, struct_item);
},
_ => {
assert!(false, "#[xml_element] may only be applied to structs");
},
}
unreachable!();
}
fn remove_our_attrs_from_item_fields(original_struct: syn::Item) -> syn::Item {
let our_attrs = ["sxs_type_attr", "sxs_type_element", "sxs_type_text", "sxs_type_multi_element"];
let mut original_struct_clone = original_struct.clone();
for a in our_attrs.iter() {
original_struct_clone = remove_attr_from_item(original_struct_clone, a);
}
original_struct_clone
}
fn remove_attr_from_item(original_struct: syn::Item, to_remove: &str) -> syn::Item {
if let syn::Item::Struct(mut struct_item) = original_struct {
if let syn::Fields::Named(ref mut fields) = struct_item.fields {
for field in fields.named.iter_mut() {
let index = field.attrs.iter().position(|a| {
match a.interpret_meta() {
Some(w) => {
match w {
syn::Meta::Word(i) => &i.to_string() == to_remove,
syn::Meta::List(ml) => &ml.ident.to_string() == to_remove,
_ => false,
}
},
_ => false,
}
});
if let Some(found_index) = index {
field.attrs.remove(found_index);
}
}
}
return struct_item.into();
}
original_struct
}
fn gen_impl_code(new_element_name: &str, original_struct: &mut syn::Item, ast: &syn::ItemStruct) -> TokenStream {
let struct_ident = &ast.ident;
let attr_field_idents = get_field_idents_of_attr_type(&ast.fields, "sxs_type_attr");
let element_field_idents = get_field_idents_of_attr_type(&ast.fields, "sxs_type_element");
let multi_element_field_idents = get_field_idents_of_attr_type(&ast.fields, "sxs_type_multi_element");
let text_field_idents = get_field_idents_of_attr_type(&ast.fields, "sxs_type_text");
let from_impl = quote! {
impl From<#struct_ident> for XMLElement {
fn from(si: #struct_ident) -> Self {
XMLElement::from(&si)
}
}
};
let add_attrs_code = gen_xml_attr_code(attr_field_idents);
let add_elements_code = gen_xml_element_code(element_field_idents);
let add_multi_elements_code = gen_xml_multi_element_code(multi_element_field_idents);
let add_text_code = gen_xml_text_code(text_field_idents);
let from_ref_impl = quote! {
impl From<&#struct_ident> for XMLElement {
fn from(si: &#struct_ident) -> Self {
let mut new_ele = XMLElement::new(#new_element_name);
#add_attrs_code
#add_elements_code
#add_multi_elements_code
#add_text_code
new_ele
}
}
};
let original_struct_with_our_attrs_removed = remove_our_attrs_from_item_fields(original_struct.clone());
let gen = quote! {
#original_struct_with_our_attrs_removed
#from_ref_impl
#from_impl
};
gen.into()
}
fn gen_xml_attr_code(attr_field_idents: Vec<(syn::Ident, String, bool, bool)>) -> quote::__rt::TokenStream {
let attr_field_names: Vec<String> = attr_field_idents.iter().map(|(_,b,_,_)|b.clone()).collect();
let attr_idents: Vec<syn::Ident> = attr_field_idents.iter().map(|(a,_,_,_)|a.clone()).collect();
let attr_is_options: Vec<bool> = attr_field_idents.iter().map(|(_,_,_,d)|d.clone()).collect();
let mut add_attrs_code = quote!();
for i in 0..attr_is_options.len() {
let attr_is_option = attr_is_options.get(i).unwrap();
let attr_name = attr_field_names.get(i).unwrap();
let attr_ident = attr_idents.get(i).unwrap();
let attr_code = match attr_is_option {
false => {
quote! { new_ele.add_attr(#attr_name, &si.#attr_ident); }
},
true => {
quote! {
if let Some(a) = &si.#attr_ident {
new_ele.add_attr(#attr_name, &a);
}
}
},
};
add_attrs_code.append_all(attr_code);
}
add_attrs_code
}
fn gen_xml_text_code(text_field_idents: Vec<(syn::Ident, String, bool, bool)>) -> quote::__rt::TokenStream {
let text_idents: Vec<syn::Ident> = text_field_idents.iter().map(|(a,_,_,_)|a.clone()).collect();
let text_is_options: Vec<bool> = text_field_idents.iter().map(|(_,_,_,d)|d.clone()).collect();
let mut add_texts_code = quote!();
for i in 0..text_is_options.len() {
let text_is_option = text_is_options.get(i).unwrap();
let text_ident = text_idents.get(i).unwrap();
let text_code = match text_is_option {
false => {
quote! { new_ele.set_text(&si.#text_ident); }
},
true => {
quote! {
if let Some(a) = &si.#text_ident {
new_ele.set_text(&a);
}
}
},
};
add_texts_code.append_all(text_code);
}
add_texts_code
}
fn gen_xml_element_code(element_field_idents: Vec<(syn::Ident, String, bool, bool)>) -> quote::__rt::TokenStream {
let element_names: Vec<String> = element_field_idents.iter().map(|(_,b,_,_)|b.clone()).collect();
let element_renamed: Vec<bool> = element_field_idents.iter().map(|(_,_,c,_)|c.clone()).collect();
let element_idents: Vec<syn::Ident> = element_field_idents.iter().map(|(a,_,_,_)|a.clone()).collect();
let element_is_options: Vec<bool> = element_field_idents.iter().map(|(_,_,_,d)|d.clone()).collect();
let mut add_elements_code = quote!();
for i in 0..element_is_options.len() {
let element_is_option = element_is_options.get(i).unwrap();
let element_name = element_names.get(i).unwrap();
let element_was_renamed = element_renamed.get(i).unwrap();
let element_ident = element_idents.get(i).unwrap();
let element_code = match element_is_option {
false => match element_was_renamed {
false => quote! { new_ele.add_element(&si.#element_ident); },
true => quote! { new_ele.add_element(XMLElement::from(&si.#element_ident).name(#element_name)); },
},
true => match element_was_renamed {
false => quote! {
if let Some(a) = &si.#element_ident {
new_ele.add_element(a);
}
},
true => quote! {
if let Some(a) = &si.#element_ident {
new_ele.add_element(XMLElement::from(a).name(#element_name));
}
},
},
};
add_elements_code.append_all(element_code);
}
add_elements_code
}
fn gen_xml_multi_element_code(multi_element_field_idents: Vec<(syn::Ident, String, bool, bool)>) -> quote::__rt::TokenStream {
let multi_element_names: Vec<String> = multi_element_field_idents.iter().map(|(_,b,_,_)|b.clone()).collect();
let multi_element_renamed: Vec<bool> = multi_element_field_idents.iter().map(|(_,_,c,_)|c.clone()).collect();
let multi_element_idents: Vec<syn::Ident> = multi_element_field_idents.iter().map(|(a,_,_,_)|a.clone()).collect();
let multi_element_is_options: Vec<bool> = multi_element_field_idents.iter().map(|(_,_,_,d)|d.clone()).collect();
let mut add_multi_elements_code = quote!();
for i in 0..multi_element_is_options.len() {
let multi_element_is_option = multi_element_is_options.get(i).unwrap();
let multi_element_name = multi_element_names.get(i).unwrap();
let multi_element_was_renamed = multi_element_renamed.get(i).unwrap();
let multi_element_ident = multi_element_idents.get(i).unwrap();
let multi_element_code = match multi_element_is_option {
false => match multi_element_was_renamed {
false => quote! {
new_ele.add_elements(&si.#multi_element_ident);
},
true => quote! {
new_ele.add_elements_with_name(#multi_element_name, &si.#multi_element_ident);
},
},
true => match multi_element_was_renamed {
false => quote! {
if let Some(a) = &si.#multi_element_ident {
new_ele.add_elements(a);
}
},
true => quote! {
if let Some(a) = &si.#multi_element_ident {
new_ele.add_elements_with_name(#multi_element_name, a);
}
},
},
};
add_multi_elements_code.append_all(multi_element_code);
}
add_multi_elements_code
}
#[cfg(feature = "process_options")]
fn get_field_idents_of_attr_type(fields: &syn::Fields, attr_type: &str) -> Vec<(syn::Ident, String, bool, bool)> {
match fields {
syn::Fields::Named(ref fields) => {
let mut field_vec = Vec::new();
for field in &fields.named {
for a in field.attrs.clone().iter() {
if let Some(w) = a.interpret_meta() {
match w {
syn::Meta::Word(i) => {
if &i.to_string() == attr_type {
if field.ident.is_some() {
let is_option = is_option_type(&field.ty);
let val = (field.clone().ident.unwrap(), field.clone().ident.unwrap().to_string(), false, is_option);
field_vec.push(val);
}
}
},
syn::Meta::List(ref ml) => {
let newname = extract_ident_with_new_name(ml, attr_type);
if newname.is_some() && field.ident.is_some(){
let is_option = is_option_type(&field.ty);
let fc = field.clone();
field_vec.push((fc.ident.unwrap(), newname.unwrap(), true, is_option));
}
},
_ => {},
}
}
}
}
field_vec
}
_ => {
Vec::new()
},
}
}
#[cfg(not(feature = "process_options"))]
fn get_field_idents_of_attr_type(fields: &syn::Fields, attr_type: &str) -> Vec<(syn::Ident, String, bool, bool)> {
match fields {
syn::Fields::Named(ref fields) => {
let mut field_vec = Vec::new();
for field in &fields.named {
for a in field.attrs.clone().iter() {
if let Some(w) = a.interpret_meta() {
match w {
syn::Meta::Word(i) => {
if &i.to_string() == attr_type {
if field.ident.is_some() {
let val = (field.clone().ident.unwrap(), field.clone().ident.unwrap().to_string(), false, false);
field_vec.push(val);
}
}
},
syn::Meta::List(ref ml) => {
let newname = extract_ident_with_new_name(ml, attr_type);
if newname.is_some() && field.ident.is_some(){
let fc = field.clone();
field_vec.push((fc.ident.unwrap(), newname.unwrap(), true, false));
}
},
_ => {},
}
}
}
}
field_vec
}
_ => {
Vec::new()
},
}
}
fn extract_ident_with_new_name(ml: &syn::MetaList, attr_type: &str) -> Option<String> {
if ml.ident.to_string() != attr_type {
return None;
}
for nested in &ml.nested {
if let syn::NestedMeta::Meta(nv) = nested {
if let syn::Meta::NameValue(mnv) = nv {
if &mnv.ident.to_string() == "rename" {
if let syn::Lit::Str(ref ls) = mnv.lit {
return Some(ls.value());
}
}
}
}
}
None
}
#[cfg(feature = "process_options")]
fn is_option_type(ty: &syn::Type) -> bool {
if let syn::Type::Path(t) = ty {
if t.path.segments.len() > 0 {
let path_seg = &t.path.segments[0];
if path_seg.ident.to_string() == "Option" {
return true;
}
}
}
false
}