Handling namespaces attributes in fields (#4)

This commit is contained in:
choinskib 2022-07-04 14:33:54 +02:00 committed by GitHub
parent 49212976a2
commit afc39e276d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 155 additions and 39 deletions

2
.gitignore vendored
View File

@ -1,2 +1,4 @@
/target
/instant-xml/target
/instant-xml-macros/target
Cargo.lock

View File

@ -2,36 +2,156 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, DeriveInput, Lit, Meta, NestedMeta};
use std::collections::HashMap;
use syn::{parse_macro_input, Lit, Meta, NestedMeta};
fn retrieve_default_namespace(input: &DeriveInput) -> Option<String> {
for attr in &input.attrs {
if !attr.path.is_ident(XML) {
continue;
const XML: &str = "xml";
enum FieldAttribute {
Namespace(String),
PrefixIdentifier(String),
}
struct Serializer {
default_namespace: Option<String>,
other_namespaces: HashMap<String, String>,
}
impl<'a> Serializer {
pub fn new(attributes: &'a Vec<syn::Attribute>) -> Serializer {
let mut default_namespace = None;
let mut other_namespaces = HashMap::default();
if let Some(list) = Self::retrieve_namespace_list(attributes) {
match list.path.get_ident() {
Some(ident) if ident == "namespace" => {
let mut iter = list.nested.iter();
if let Some(NestedMeta::Lit(Lit::Str(v))) = iter.next() {
default_namespace = Some(v.value());
}
for item in iter {
match item {
NestedMeta::Meta(Meta::NameValue(key)) => {
if let Lit::Str(value) = &key.lit {
other_namespaces.insert(
key.path.get_ident().unwrap().to_string(),
value.value(),
);
}
}
_ => todo!(),
}
}
}
_ => (),
}
}
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,
_ => todo!(),
};
if list.path.get_ident()? == "namespace" {
if let NestedMeta::Lit(Lit::Str(v)) = list.nested.first()? {
return Some(v.value());
}
Serializer {
default_namespace,
other_namespaces,
}
}
None
}
fn add_header(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) {
output.extend(quote!(+ "<" + #root_name));
const XML: &str = "xml";
if let Some(default_namespace) = self.default_namespace.as_ref() {
output.extend(quote!(+ " xmlns=\"" + #default_namespace + "\""));
}
let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect();
sorted_values.sort();
for (key, val) in sorted_values {
output.extend(quote!(+ " xmlns:" + #key + "=\"" + #val + "\""));
}
output.extend(quote!(+ ">"));
}
fn add_footer(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) {
output.extend(quote!(+ "</" + #root_name + ">"));
}
fn process_named_field(
&mut self,
field: &syn::Field,
output: &'a mut proc_macro2::TokenStream,
) {
let field_name = field.ident.as_ref().unwrap().to_string();
let field_value = field.ident.as_ref().unwrap();
let mut prefix = String::default();
match Self::retrieve_field_attribute(field) {
Some(FieldAttribute::Namespace(namespace)) => {
output.extend(quote!(+ "<" + #field_name + " xmlns=\"" + #namespace + "\""));
}
Some(FieldAttribute::PrefixIdentifier(prefix_key))
if !self.other_namespaces.is_empty() =>
{
match self.other_namespaces.get(&prefix_key) {
Some(_) => {
prefix = prefix_key + ":";
output.extend(quote!(+ "<" + #prefix + #field_name));
}
None => todo!(), // return the error
};
}
_ => {
// Without the namespace
output.extend(quote!(+ "<" + #field_name));
}
};
output.extend(
quote!(+ ">" + self.#field_value.to_string().as_str() + "</" + #prefix + #field_name + ">"),
);
}
fn retrieve_namespace_list(attributes: &Vec<syn::Attribute>) -> Option<syn::MetaList> {
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,
_ => todo!(),
};
if list.path.get_ident()? == "namespace" {
return Some(list.to_owned());
}
}
None
}
fn retrieve_field_attribute(input: &syn::Field) -> Option<FieldAttribute> {
if let Some(list) = Self::retrieve_namespace_list(&input.attrs) {
match list.nested.first() {
Some(NestedMeta::Lit(Lit::Str(v))) => {
return Some(FieldAttribute::Namespace(v.value()));
}
Some(NestedMeta::Meta(Meta::Path(v))) => {
if let Some(ident) = v.get_ident() {
return Some(FieldAttribute::PrefixIdentifier(ident.to_string()));
}
}
_ => (),
};
}
None
}
}
#[proc_macro_derive(ToXml, attributes(xml))]
pub fn to_xml(input: TokenStream) -> TokenStream {
@ -39,25 +159,17 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
let ident = &ast.ident;
let root_name = ident.to_string();
let header = match retrieve_default_namespace(&ast) {
Some(ns) => format!("{} xmlns=\"{}\"", root_name, ns),
None => root_name.clone(),
};
let mut output: proc_macro2::TokenStream = TokenStream::from(quote!("".to_owned())).into();
let mut output: proc_macro2::TokenStream =
TokenStream::from(quote!("<".to_owned() + #header + ">")).into();
let mut serializer = Serializer::new(&ast.attrs);
serializer.add_header(&root_name, &mut output);
match &ast.data {
syn::Data::Struct(ref data) => {
match data.fields {
syn::Fields::Named(ref fields) => {
fields
.named
.iter()
.for_each(|field| {
let field_name = field.ident.as_ref().unwrap().to_string();
let field_value = field.ident.as_ref().unwrap();
output.extend(quote!(+ "<" + #field_name + ">" + self.#field_value.to_string().as_str() + "</" + #field_name + ">"));
fields.named.iter().for_each(|field| {
serializer.process_named_field(field, &mut output);
});
}
syn::Fields::Unnamed(_) => todo!(),
@ -67,7 +179,7 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
_ => todo!(),
};
output.extend(quote!(+ "</" + #root_name + ">"));
serializer.add_footer(&root_name, &mut output);
TokenStream::from(quote!(
impl ToXml for #ident {

View File

@ -4,10 +4,12 @@ use instant_xml::{FromXml, ToXml};
struct Unit;
#[derive(Debug, Eq, PartialEq, ToXml)]
#[xml(namespace("URI"))]
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
struct StructWithNamedFields {
flag: bool,
#[xml(namespace(bar))]
string: String,
#[xml(namespace("typo"))]
number: i32,
}
@ -27,6 +29,6 @@ fn struct_with_named_fields() {
}
.to_xml()
.unwrap(),
"<StructWithNamedFields xmlns=\"URI\"><flag>true</flag><string>test</string><number>1</number></StructWithNamedFields>"
"<StructWithNamedFields xmlns=\"URI\" xmlns:bar=\"BAZ\" xmlns:foo=\"BAR\"><flag>true</flag><bar:string>test</bar:string><number xmlns=\"typo\">1</number></StructWithNamedFields>"
);
}