Handling namespaces attributes in fields (#4)
This commit is contained in:
parent
49212976a2
commit
afc39e276d
|
@ -1,2 +1,4 @@
|
||||||
/target
|
/target
|
||||||
|
/instant-xml/target
|
||||||
|
/instant-xml-macros/target
|
||||||
Cargo.lock
|
Cargo.lock
|
||||||
|
|
|
@ -2,10 +2,116 @@ extern crate proc_macro;
|
||||||
|
|
||||||
use proc_macro::TokenStream;
|
use proc_macro::TokenStream;
|
||||||
use quote::quote;
|
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> {
|
const XML: &str = "xml";
|
||||||
for attr in &input.attrs {
|
|
||||||
|
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!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Serializer {
|
||||||
|
default_namespace,
|
||||||
|
other_namespaces,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_header(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) {
|
||||||
|
output.extend(quote!(+ "<" + #root_name));
|
||||||
|
|
||||||
|
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) {
|
if !attr.path.is_ident(XML) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -22,16 +128,30 @@ fn retrieve_default_namespace(input: &DeriveInput) -> Option<String> {
|
||||||
};
|
};
|
||||||
|
|
||||||
if list.path.get_ident()? == "namespace" {
|
if list.path.get_ident()? == "namespace" {
|
||||||
if let NestedMeta::Lit(Lit::Str(v)) = list.nested.first()? {
|
return Some(list.to_owned());
|
||||||
return Some(v.value());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
const XML: &str = "xml";
|
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))]
|
#[proc_macro_derive(ToXml, attributes(xml))]
|
||||||
pub fn to_xml(input: TokenStream) -> TokenStream {
|
pub fn to_xml(input: TokenStream) -> TokenStream {
|
||||||
|
@ -39,25 +159,17 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
|
||||||
|
|
||||||
let ident = &ast.ident;
|
let ident = &ast.ident;
|
||||||
let root_name = ident.to_string();
|
let root_name = ident.to_string();
|
||||||
let header = match retrieve_default_namespace(&ast) {
|
let mut output: proc_macro2::TokenStream = TokenStream::from(quote!("".to_owned())).into();
|
||||||
Some(ns) => format!("{} xmlns=\"{}\"", root_name, ns),
|
|
||||||
None => root_name.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut output: proc_macro2::TokenStream =
|
let mut serializer = Serializer::new(&ast.attrs);
|
||||||
TokenStream::from(quote!("<".to_owned() + #header + ">")).into();
|
serializer.add_header(&root_name, &mut output);
|
||||||
|
|
||||||
match &ast.data {
|
match &ast.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
|
fields.named.iter().for_each(|field| {
|
||||||
.named
|
serializer.process_named_field(field, &mut output);
|
||||||
.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 + ">"));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
syn::Fields::Unnamed(_) => todo!(),
|
syn::Fields::Unnamed(_) => todo!(),
|
||||||
|
@ -67,7 +179,7 @@ pub fn to_xml(input: TokenStream) -> TokenStream {
|
||||||
_ => todo!(),
|
_ => todo!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
output.extend(quote!(+ "</" + #root_name + ">"));
|
serializer.add_footer(&root_name, &mut output);
|
||||||
|
|
||||||
TokenStream::from(quote!(
|
TokenStream::from(quote!(
|
||||||
impl ToXml for #ident {
|
impl ToXml for #ident {
|
||||||
|
|
|
@ -4,10 +4,12 @@ use instant_xml::{FromXml, ToXml};
|
||||||
struct Unit;
|
struct Unit;
|
||||||
|
|
||||||
#[derive(Debug, Eq, PartialEq, ToXml)]
|
#[derive(Debug, Eq, PartialEq, ToXml)]
|
||||||
#[xml(namespace("URI"))]
|
#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))]
|
||||||
struct StructWithNamedFields {
|
struct StructWithNamedFields {
|
||||||
flag: bool,
|
flag: bool,
|
||||||
|
#[xml(namespace(bar))]
|
||||||
string: String,
|
string: String,
|
||||||
|
#[xml(namespace("typo"))]
|
||||||
number: i32,
|
number: i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +29,6 @@ fn struct_with_named_fields() {
|
||||||
}
|
}
|
||||||
.to_xml()
|
.to_xml()
|
||||||
.unwrap(),
|
.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>"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue