diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 0bc8cc2..48e8e05 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -1,8 +1,8 @@ extern crate proc_macro; -use proc_macro::TokenStream; +use proc_macro2::TokenStream; use quote::quote; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use syn::{parse_macro_input, Lit, Meta, NestedMeta}; const XML: &str = "xml"; @@ -54,60 +54,89 @@ impl<'a> Serializer { } } - fn add_header(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) { - output.extend(quote!(+ "<" + #root_name)); + fn keys_set(&self) -> BTreeSet<&str> { + self.other_namespaces + .iter() + .map(|(k, _)| k.as_str()) + .collect() + } + + fn add_header(&mut self, root_name: &str, output: &'a mut TokenStream) { + output.extend(quote!( + serializer.output.write_char('<')?; + serializer.output.write_str(#root_name)?; + )); if let Some(default_namespace) = self.default_namespace.as_ref() { - output.extend(quote!(+ " xmlns=\"" + #default_namespace + "\"")); + output.extend(quote!( + serializer.output.write_str(" xmlns=\"")?; + serializer.output.write_str(#default_namespace)?; + serializer.output.write_char('\"')?; + )); } 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!( + serializer.output.write_str(" xmlns:")?; + serializer.output.write_str(#key)?; + serializer.output.write_str("=\"")?; + serializer.output.write_str(#val)?; + serializer.output.write_char('\"')?; + )); } - - output.extend(quote!(+ ">")); + output.extend(quote!( + serializer.output.write_char('>')?; + )); } - fn add_footer(&mut self, root_name: &str, output: &'a mut proc_macro2::TokenStream) { - output.extend(quote!(+ "")); + fn add_footer(&mut self, root_name: &str, output: &'a mut TokenStream) { + output.extend(quote!( + serializer.output.write_str("')?; + )); } fn process_named_field( &mut self, field: &syn::Field, - output: &'a mut proc_macro2::TokenStream, + output: &'a mut TokenStream, + missing_prefixes: &'a mut BTreeSet, ) { - let field_name = field.ident.as_ref().unwrap().to_string(); + let name = field.ident.as_ref().unwrap().to_string(); let field_value = field.ident.as_ref().unwrap(); - let mut prefix = String::default(); + + output.extend(quote!( + let mut field = instant_xml::FieldContext { + name: #name, + attribute: None, + }; + )); match Self::retrieve_field_attribute(field) { - Some(FieldAttribute::Namespace(namespace)) => { - output.extend(quote!(+ "<" + #field_name + " xmlns=\"" + #namespace + "\"")); + Some(FieldAttribute::Namespace(namespace_key)) => { + output.extend(quote!( + field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace_key)); + )); } - 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 + Some(FieldAttribute::PrefixIdentifier(prefix_key)) => { + output.extend(quote!( + field.attribute = Some(instant_xml::FieldAttribute::Prefix(#prefix_key)); + )); + + if self.other_namespaces.get(&prefix_key).is_none() { + missing_prefixes.insert(prefix_key); }; } - _ => { - // Without the namespace - output.extend(quote!(+ "<" + #field_name)); - } + _ => {} }; - output.extend( - quote!(+ ">" + self.#field_value.to_string().as_str() + ""), - ); + output.extend(quote!( + self.#field_value.serialize(serializer, Some(&field))?; + )); } fn retrieve_namespace_list(attributes: &Vec) -> Option { @@ -154,14 +183,15 @@ impl<'a> Serializer { } #[proc_macro_derive(ToXml, attributes(xml))] -pub fn to_xml(input: TokenStream) -> TokenStream { +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 root_name = ident.to_string(); - let mut output: proc_macro2::TokenStream = TokenStream::from(quote!("".to_owned())).into(); - + let mut missing_prefixes = BTreeSet::new(); let mut serializer = Serializer::new(&ast.attrs); + + let mut output = TokenStream::new(); + serializer.add_header(&root_name, &mut output); match &ast.data { @@ -169,7 +199,7 @@ pub fn to_xml(input: TokenStream) -> TokenStream { match data.fields { syn::Fields::Named(ref fields) => { fields.named.iter().for_each(|field| { - serializer.process_named_field(field, &mut output); + serializer.process_named_field(field, &mut output, &mut missing_prefixes); }); } syn::Fields::Unnamed(_) => todo!(), @@ -181,22 +211,50 @@ pub fn to_xml(input: TokenStream) -> TokenStream { serializer.add_footer(&root_name, &mut output); - TokenStream::from(quote!( + let current_prefixes = serializer.keys_set(); + proc_macro::TokenStream::from(quote!( impl ToXml for #ident { - fn write_xml(&self, write: &mut W) -> Result<(), instant_xml::Error> { - write.write_str(&(#output))?; + fn serialize(&self, serializer: &mut instant_xml::Serializer, _field_data: Option<&instant_xml::FieldContext>) -> Result<(), instant_xml::Error> + where + W: std::fmt::Write, + { + let mut field_context = instant_xml::FieldContext { + name: #root_name, + attribute: None, + }; + + // Check if prefix exist + #( + if serializer.parent_prefixes.get(#missing_prefixes).is_none() { + return Err(instant_xml::Error::WrongPrefix); + } + )*; + + // Adding current prefixes + let mut to_remove: Vec<&str> = Vec::new(); + #(if serializer.parent_prefixes.insert(#current_prefixes) { + to_remove.push(#current_prefixes); + };)*; + + #output + + // Removing current prefixes + for it in to_remove { + serializer.parent_prefixes.remove(it); + } + Ok(()) } - } + }; )) } #[proc_macro_derive(FromXml, attributes(xml))] -pub fn from_xml(input: TokenStream) -> TokenStream { +pub fn from_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let ast = parse_macro_input!(input as syn::ItemStruct); let ident = &ast.ident; let name = ident.to_string(); - TokenStream::from(quote!( + proc_macro::TokenStream::from(quote!( impl<'xml> FromXml<'xml> for #ident { fn from_xml(input: &str) -> Result { use ::instant_xml::parse::Parse; diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 98facff..99f2c89 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -1,4 +1,4 @@ -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::fmt; use thiserror::Error; @@ -10,13 +10,175 @@ pub use macros::{FromXml, ToXml}; pub mod parse; pub trait ToXml { - fn write_xml(&self, write: &mut W) -> Result<(), Error>; - fn to_xml(&self) -> Result { - let mut out = String::new(); - self.write_xml(&mut out)?; - Ok(out) + let mut output = String::new(); + let mut serializer = Serializer::new(&mut output); + self.serialize(&mut serializer, None)?; + Ok(output) } + + fn serialize( + &self, + serializer: &mut Serializer, + field_context: Option<&FieldContext>, + ) -> Result<(), Error> + where + W: fmt::Write; +} + +macro_rules! to_xml_for_number { + ($typ:ty) => { + impl ToXml for $typ { + fn serialize( + &self, + serializer: &mut Serializer, + field_context: Option<&FieldContext>, + ) -> Result<(), Error> + where + W: fmt::Write, + { + match field_context { + Some(field_context) => { + serializer.add_open_tag(field_context)?; + write!(serializer.output, "{}", &self)?; + serializer.add_close_tag(field_context)?; + Ok(()) + } + None => Err(Error::UnexpectedValue), + } + } + } + }; +} + +to_xml_for_number!(i8); +to_xml_for_number!(i16); +to_xml_for_number!(i32); +to_xml_for_number!(i64); +to_xml_for_number!(u8); +to_xml_for_number!(u16); +to_xml_for_number!(u32); +to_xml_for_number!(u64); + +impl ToXml for bool { + fn serialize( + &self, + serializer: &mut Serializer, + field_context: Option<&FieldContext>, + ) -> Result<(), Error> + where + W: fmt::Write, + { + let value = match self { + true => "true", + false => "false", + }; + + match field_context { + Some(field_context) => { + serializer.add_open_tag(field_context)?; + serializer.output.write_str(value)?; + serializer.add_close_tag(field_context)?; + Ok(()) + } + None => Err(Error::UnexpectedValue), + } + } +} + +impl ToXml for String { + fn serialize( + &self, + serializer: &mut Serializer, + field_context: Option<&FieldContext>, + ) -> Result<(), Error> + where + W: fmt::Write, + { + match field_context { + Some(field_context) => { + serializer.add_open_tag(field_context)?; + serializer.output.write_str(self)?; + serializer.add_close_tag(field_context)?; + Ok(()) + } + None => Err(Error::UnexpectedValue), + } + } +} + +pub struct Serializer<'xml, W> +where + W: fmt::Write, +{ + #[doc(hidden)] + pub parent_prefixes: BTreeSet<&'xml str>, + #[doc(hidden)] + pub output: &'xml mut W, +} + +impl<'xml, W: std::fmt::Write> Serializer<'xml, W> { + pub fn new(output: &'xml mut W) -> Self { + Self { + parent_prefixes: BTreeSet::new(), + output, + } + } + + fn add_open_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> { + match field_context.attribute { + Some(FieldAttribute::Prefix(prefix)) => { + self.output.write_char('<')?; + self.output.write_str(prefix)?; + self.output.write_char(':')?; + self.output.write_str(field_context.name)?; + self.output.write_char('>')?; + } + Some(FieldAttribute::Namespace(namespace)) => { + self.output.write_char('<')?; + self.output.write_str(field_context.name)?; + self.output.write_str(" xmlns=\"")?; + self.output.write_str(namespace)?; + self.output.write_str("\">")?; + } + _ => { + self.output.write_char('<')?; + self.output.write_str(field_context.name)?; + self.output.write_char('>')?; + } + } + Ok(()) + } + + fn add_close_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> { + match field_context.attribute { + Some(FieldAttribute::Prefix(prefix)) => { + self.output.write_str("')?; + } + _ => { + self.output.write_str("')?; + } + } + Ok(()) + } +} + +pub enum FieldAttribute<'xml> { + Prefix(&'xml str), + Namespace(&'xml str), +} + +pub struct FieldContext<'xml> { + #[doc(hidden)] + pub name: &'xml str, + #[doc(hidden)] + pub attribute: Option>, } pub trait FromXml<'xml>: Sized { @@ -40,4 +202,6 @@ pub enum Error { UnexpectedEndOfStream, #[error("unexpected value")] UnexpectedValue, + #[error("wrong prefix")] + WrongPrefix, } diff --git a/instant-xml/tests/all.rs b/instant-xml/tests/all.rs index f93efde..12282bc 100644 --- a/instant-xml/tests/all.rs +++ b/instant-xml/tests/all.rs @@ -3,6 +3,30 @@ use instant_xml::{FromXml, ToXml}; #[derive(Debug, Eq, FromXml, PartialEq, ToXml)] struct Unit; +#[derive(Debug, Eq, PartialEq, ToXml)] +#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] +struct StructWithCustomField { + test: Nested, +} + +#[derive(Debug, Eq, PartialEq, ToXml)] +struct Nested { + #[xml(namespace(bar))] + flag: bool, +} + +#[derive(Debug, Eq, PartialEq, ToXml)] +#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] +struct StructWithCustomFieldWrongPrefix { + test: NestedWrongPrefix, +} + +#[derive(Debug, Eq, PartialEq, ToXml)] +struct NestedWrongPrefix { + #[xml(namespace(dar))] + flag: bool, +} + #[derive(Debug, Eq, PartialEq, ToXml)] #[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] struct StructWithNamedFields { @@ -32,3 +56,28 @@ fn struct_with_named_fields() { "truetest1" ); } + +#[test] +fn struct_with_custom_field() { + assert_eq!( + StructWithCustomField { + test: Nested { + flag: true, + }, + } + .to_xml() + .unwrap(), + "true" + + ); +} + +#[test] +#[should_panic] +fn struct_with_custom_field_wrong_prefix() { + StructWithCustomFieldWrongPrefix { + test: NestedWrongPrefix { flag: true }, + } + .to_xml() + .unwrap(); +}