From 2a9901bc84f4a834fa57da0ab9db9b58c7e4a085 Mon Sep 17 00:00:00 2001 From: choinskib <37154155+choinskib@users.noreply.github.com> Date: Wed, 31 Aug 2022 17:03:01 +0200 Subject: [PATCH] Serializer namespaces and attributes (#13) --- instant-xml-macros/src/lib.rs | 54 ++++++------- instant-xml-macros/src/se.rs | 123 +++++++++++++++++++++-------- instant-xml/src/lib.rs | 144 +++++++++++++++++++++++++--------- instant-xml/tests/all.rs | 110 ++++++++++++++++++-------- 4 files changed, 300 insertions(+), 131 deletions(-) diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index ee80de9..91149d6 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -3,7 +3,7 @@ extern crate proc_macro; mod de; mod se; -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use proc_macro2::TokenStream; use quote::quote; @@ -33,19 +33,22 @@ pub(crate) fn get_namespaces( if name == "namespace" { let mut iter = list.nested.iter(); - if let Some(NestedMeta::Lit(Lit::Str(v))) = iter.next() { + let mut next = iter.next(); + if let Some(NestedMeta::Lit(Lit::Str(v))) = next { default_namespace = v.value(); + next = iter.next(); } - for item in iter { - if let NestedMeta::Meta(Meta::NameValue(key)) = item { + while let Some(value) = next { + if let NestedMeta::Meta(Meta::NameValue(key)) = value { if let Lit::Str(value) = &key.lit { other_namespaces .insert(key.path.get_ident().unwrap().to_string(), value.value()); + next = iter.next(); continue; } } - panic!("Wrong data"); + panic!("Wrong data") } } @@ -102,17 +105,19 @@ 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 missing_prefixes = BTreeSet::new(); let mut serializer = Serializer::new(&ast.attrs); - let mut output = TokenStream::new(); - serializer.add_header(&mut output); + let mut header = TokenStream::new(); + serializer.add_header(&mut header); + + let mut body = TokenStream::new(); + let mut attributes = TokenStream::new(); match &ast.data { syn::Data::Struct(ref data) => { match data.fields { syn::Fields::Named(ref fields) => { fields.named.iter().for_each(|field| { - serializer.process_named_field(field, &mut output, &mut missing_prefixes); + serializer.process_named_field(field, &mut body, &mut attributes); }); } syn::Fields::Unnamed(_) => todo!(), @@ -122,39 +127,34 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { _ => todo!(), }; - serializer.add_footer(&root_name, &mut output); + let mut footer = TokenStream::new(); + serializer.add_footer(&root_name, &mut footer); - let current_prefixes = serializer.keys_set(); + let current_namespaces = serializer.namespaces_token(); proc_macro::TokenStream::from(quote!( impl ToXml for #ident { - fn serialize(&self, serializer: &mut instant_xml::Serializer, _field_data: Option<&instant_xml::FieldContext>) -> Result<(), instant_xml::Error> + fn serialize(&self, serializer: &mut instant_xml::Serializer) -> Result<(), instant_xml::Error> where W: std::fmt::Write, { + println!("ident: {}", #root_name); + let _ = serializer.consume_field_context(); 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::WrongNamespace); - } - )*; + #attributes - // Adding current prefixes - let mut to_remove: Vec<&str> = Vec::new(); - #(if serializer.parent_prefixes.insert(#current_prefixes) { - to_remove.push(#current_prefixes); - };)*; + #header + #current_namespaces + #body + #footer - #output - - // Removing current prefixes + // Removing current namespaces for it in to_remove { - serializer.parent_prefixes.remove(it); + serializer.parent_namespaces.remove(it); } Ok(()) diff --git a/instant-xml-macros/src/se.rs b/instant-xml-macros/src/se.rs index 45f49bf..dc93699 100644 --- a/instant-xml-macros/src/se.rs +++ b/instant-xml-macros/src/se.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use proc_macro2::TokenStream; use quote::quote; @@ -20,40 +20,43 @@ impl<'a> Serializer { } } - pub fn keys_set(&self) -> BTreeSet<&str> { - self.other_namespaces - .iter() - .map(|(k, _)| k.as_str()) - .collect() - } - pub fn add_header(&mut self, output: &'a mut TokenStream) { output.extend(quote!( serializer.output.write_char('<')?; serializer.output.write_str(field_context.name)?; )); - if !self.default_namespace.is_empty() { - let default_namespace = &self.default_namespace; - output.extend(quote!( + let default_namespace = &self.default_namespace; + output.extend(quote!( + // Check if parent default namespace equals + if serializer.parent_default_namespace() != #default_namespace { serializer.output.write_str(" xmlns=\"")?; serializer.output.write_str(#default_namespace)?; serializer.output.write_char('\"')?; - )); - } + } + serializer.update_parent_default_namespace(#default_namespace); + )); let mut sorted_values: Vec<_> = self.other_namespaces.iter().collect(); sorted_values.sort(); for (key, val) in sorted_values { 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('\"')?; + 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('>')?; )); @@ -64,45 +67,97 @@ impl<'a> Serializer { serializer.output.write_str("')?; + serializer.retrive_parent_default_namespace(); )); } pub fn process_named_field( &mut self, field: &syn::Field, - output: &'a mut TokenStream, - missing_prefixes: &'a mut BTreeSet, + body: &mut TokenStream, + attributes: &mut TokenStream, ) { let name = field.ident.as_ref().unwrap().to_string(); let field_value = field.ident.as_ref().unwrap(); - output.extend(quote!( + let declaration = quote!( let mut field = instant_xml::FieldContext { name: #name, attribute: None, }; - )); + ); - match retrieve_field_attribute(field) { - Some(FieldAttribute::Namespace(namespace_key)) => { - output.extend(quote!( - field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace_key)); + let stream_ref = match retrieve_field_attribute(field) { + Some(FieldAttribute::Namespace(namespace)) => { + body.extend(quote!( + #declaration + // // Check if such namespace already exist, if so change it to use its prefix + // match serializer.parent_namespaces.get(#namespace) { + // Some(key) => field.attribute = Some(instant_xml::FieldAttribute::Prefix(key)), + // None => field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace)), + // }; + field.attribute = Some(instant_xml::FieldAttribute::Namespace(#namespace)); )); + body } Some(FieldAttribute::PrefixIdentifier(prefix_key)) => { - output.extend(quote!( - field.attribute = Some(instant_xml::FieldAttribute::Prefix(#prefix_key)); - )); + match self.other_namespaces.get(&prefix_key) { + Some(val) => { + body.extend(quote!( + #declaration - if self.other_namespaces.get(&prefix_key).is_none() { - missing_prefixes.insert(prefix_key); + // Check if such namespace already exist, if so change its prefix to parent prefix + let prefix_key = match serializer.parent_namespaces.get(#val) { + Some(key) => key, + None => #prefix_key, + }; + )); + } + None => panic!("Prefix not defined: {}", prefix_key), }; + + body.extend(quote!( + field.attribute = Some(instant_xml::FieldAttribute::Prefix(prefix_key)); + )); + body + } + Some(FieldAttribute::Attribute) => { + attributes.extend(quote!( + #declaration + + serializer.add_attribute_key(&#name); + field.attribute = Some(instant_xml::FieldAttribute::Attribute); + )); + attributes + } + _ => { + body.extend(quote!( + #declaration + )); + body } - _ => {} }; - output.extend(quote!( - self.#field_value.serialize(serializer, Some(&field))?; + stream_ref.extend(quote!( + serializer.set_field_context(field)?; + self.#field_value.serialize(serializer)?; )); } + + pub fn namespaces_token(&self) -> TokenStream { + let mut namespaces = quote!( + let mut to_remove: Vec<&str> = Vec::new(); + ); + for (k, v) in self.other_namespaces.iter() { + 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); + // Will remove added namespaces when going "up" + to_remove.push(#v); + }; + )) + } + namespaces + } } diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 1e459fd..244ea76 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeSet, HashMap}; +use std::collections::HashMap; use std::fmt; use thiserror::Error; @@ -29,15 +29,11 @@ pub trait ToXml { fn to_xml(&self) -> Result { let mut output = String::new(); let mut serializer = Serializer::new(&mut output); - self.serialize(&mut serializer, None)?; + self.serialize(&mut serializer)?; Ok(output) } - fn serialize( - &self, - serializer: &mut Serializer, - field_context: Option<&FieldContext>, - ) -> Result<(), Error> + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> where W: fmt::Write; } @@ -45,19 +41,22 @@ pub trait ToXml { macro_rules! to_xml_for_number { ($typ:ty) => { impl ToXml for $typ { - fn serialize( - &self, - serializer: &mut Serializer, - field_context: Option<&FieldContext>, - ) -> Result<(), Error> + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> where W: fmt::Write, { - match field_context { + match serializer.consume_field_context() { Some(field_context) => { - serializer.add_open_tag(field_context)?; - write!(serializer.output, "{}", &self)?; - serializer.add_close_tag(field_context)?; + match field_context.attribute { + Some(FieldAttribute::Attribute) => { + serializer.add_attribute_value(&self.to_string()); + } + _ => { + serializer.add_open_tag(&field_context)?; + write!(serializer.output, "{}", &self)?; + serializer.add_close_tag(field_context)?; + } + } Ok(()) } None => Err(Error::UnexpectedValue), @@ -77,11 +76,7 @@ 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> + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> where W: fmt::Write, { @@ -90,11 +85,18 @@ impl ToXml for bool { false => "false", }; - match field_context { + match serializer.consume_field_context() { Some(field_context) => { - serializer.add_open_tag(field_context)?; - serializer.output.write_str(value)?; - serializer.add_close_tag(field_context)?; + match field_context.attribute { + Some(FieldAttribute::Attribute) => { + serializer.add_attribute_value(value); + } + _ => { + serializer.add_open_tag(&field_context)?; + serializer.output.write_str(value)?; + serializer.add_close_tag(field_context)?; + } + } Ok(()) } None => Err(Error::UnexpectedValue), @@ -103,19 +105,22 @@ impl ToXml for bool { } impl ToXml for String { - fn serialize( - &self, - serializer: &mut Serializer, - field_context: Option<&FieldContext>, - ) -> Result<(), Error> + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> where W: fmt::Write, { - match field_context { + match serializer.consume_field_context() { Some(field_context) => { - serializer.add_open_tag(field_context)?; - serializer.output.write_str(self)?; - serializer.add_close_tag(field_context)?; + match field_context.attribute { + Some(FieldAttribute::Attribute) => { + serializer.add_attribute_value(self); + } + _ => { + serializer.add_open_tag(&field_context)?; + serializer.output.write_str(self)?; + serializer.add_close_tag(field_context)?; + } + } Ok(()) } None => Err(Error::UnexpectedValue), @@ -127,20 +132,80 @@ pub struct Serializer<'xml, W> where W: fmt::Write, { + // For parent namespaces the key is the namespace and the value is the prefix. We are adding to map + // only if the namespaces do not exist, if it does exist then we are using an already defined parent prefix. #[doc(hidden)] - pub parent_prefixes: BTreeSet<&'xml str>, + pub parent_namespaces: HashMap<&'xml str, &'xml str>, #[doc(hidden)] pub output: &'xml mut W, + + parent_default_namespace: &'xml str, + parent_default_namespace_to_revert: &'xml str, + current_attributes: String, + next_field_context: Option>, } impl<'xml, W: std::fmt::Write> Serializer<'xml, W> { pub fn new(output: &'xml mut W) -> Self { Self { - parent_prefixes: BTreeSet::new(), + parent_namespaces: HashMap::new(), output, + parent_default_namespace: "", + parent_default_namespace_to_revert: "", + next_field_context: None, + current_attributes: String::new(), } } + pub fn consume_current_attributes(&mut self) -> Result<(), Error> { + self.output.write_str(&self.current_attributes)?; + self.current_attributes.clear(); + Ok(()) + } + + pub fn add_attribute_key(&mut self, attr_key: &str) { + self.current_attributes.push(' '); + self.current_attributes.push_str(attr_key); + self.current_attributes.push('='); + } + + pub fn add_attribute_value(&mut self, attr_value: &str) { + self.current_attributes.push('"'); + self.current_attributes.push_str(attr_value); + self.current_attributes.push('"'); + } + + pub fn set_field_context(&mut self, field_context: FieldContext<'xml>) -> Result<(), Error> { + if self.next_field_context.is_some() { + return Err(Error::UnexpectedState); + }; + + self.next_field_context = Some(field_context); + Ok(()) + } + + pub fn consume_field_context(&mut self) -> Option> { + self.next_field_context.take() + } + + pub fn set_parent_default_namespace(&mut self, namespace: &'xml str) -> Result<(), Error> { + self.parent_default_namespace = namespace; + Ok(()) + } + + pub fn parent_default_namespace(&self) -> &'xml str { + self.parent_default_namespace + } + + pub fn update_parent_default_namespace(&mut self, namespace: &'xml str) { + self.parent_default_namespace_to_revert = self.parent_default_namespace; + self.parent_default_namespace = namespace; + } + + pub fn retrive_parent_default_namespace(&mut self) { + self.parent_default_namespace = self.parent_default_namespace_to_revert; + } + fn add_open_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> { match field_context.attribute { Some(FieldAttribute::Prefix(prefix)) => { @@ -150,7 +215,9 @@ impl<'xml, W: std::fmt::Write> Serializer<'xml, W> { self.output.write_str(field_context.name)?; self.output.write_char('>')?; } - Some(FieldAttribute::Namespace(namespace)) => { + Some(FieldAttribute::Namespace(namespace)) + if self.parent_default_namespace != namespace => + { self.output.write_char('<')?; self.output.write_str(field_context.name)?; self.output.write_str(" xmlns=\"")?; @@ -166,7 +233,7 @@ impl<'xml, W: std::fmt::Write> Serializer<'xml, W> { Ok(()) } - fn add_close_tag(&mut self, field_context: &FieldContext) -> Result<(), Error> { + fn add_close_tag(&mut self, field_context: FieldContext) -> Result<(), Error> { match field_context.attribute { Some(FieldAttribute::Prefix(prefix)) => { self.output.write_str(" Serializer<'xml, W> { pub enum FieldAttribute<'xml> { Prefix(&'xml str), Namespace(&'xml str), + Attribute, } pub struct FieldContext<'xml> { diff --git a/instant-xml/tests/all.rs b/instant-xml/tests/all.rs index ed33593..3fc18f7 100644 --- a/instant-xml/tests/all.rs +++ b/instant-xml/tests/all.rs @@ -1,16 +1,5 @@ use instant_xml::{Error, FromXml, ToXml}; - -#[derive(Debug, Eq, PartialEq, ToXml)] -struct Nested { - #[xml(namespace(bar))] - flag: bool, -} - -#[derive(Debug, Eq, PartialEq, ToXml)] -struct NestedWrongPrefix { - #[xml(namespace(dar))] - flag: bool, -} +//TODO: Add compile time errors check? #[derive(Debug, Eq, PartialEq, ToXml)] struct Unit; @@ -22,7 +11,7 @@ fn unit() { } #[derive(Debug, Eq, PartialEq, ToXml)] -#[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] +#[xml(namespace(bar = "BAZ", foo = "BAR"))] struct StructWithNamedFields { flag: bool, #[xml(namespace(bar))] @@ -31,6 +20,11 @@ struct StructWithNamedFields { number: i32, } +// Tests: +// - Empty default namespace +// - Prefix namespace +// - Direct namespace + #[test] fn struct_with_named_fields() { assert_eq!( @@ -41,52 +35,104 @@ fn struct_with_named_fields() { } .to_xml() .unwrap(), - "truetest1" + "truetest1" ); } +#[derive(Debug, Eq, PartialEq, ToXml)] +#[xml(namespace("URI", dar = "BAZ", internal = "INTERNAL"))] +struct Nested { + #[xml(namespace(dar))] + flag_parent_prefix: bool, + #[xml(namespace(internal))] + flag_internal_prefix: bool, +} + #[derive(Debug, Eq, PartialEq, ToXml)] #[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] struct StructWithCustomField { + #[xml(attribute)] + int_attribute: i32, + #[xml(namespace("BAZ"))] + flag_direct_namespace_same_the_same_as_prefix: bool, + #[xml(namespace(bar))] + flag_prefix: bool, + #[xml(namespace("DIFFERENT"))] + flag_direct_namespace: bool, test: Nested, } +// Tests: +// - The same direct namespace as the one from prefix +// - Attribute handling +// - Omitting redeclared child default namespace +// - Omitting redeclared child namespace with different prefix +// - Unique direct namespace +// - Child unique prefix +// - Child repeated prefix +// - Child default namespace the same as parent #[test] fn struct_with_custom_field() { assert_eq!( StructWithCustomField { + int_attribute: 42, + flag_direct_namespace_same_the_same_as_prefix: true, + flag_prefix: false, + flag_direct_namespace: true, test: Nested { - flag: true, + flag_parent_prefix: true, + flag_internal_prefix: false, }, } .to_xml() .unwrap(), - "true" - + "truefalsetruetruefalse" ); } -#[derive(Debug, Eq, PartialEq, ToXml, FromXml)] -#[xml(namespace("URI", bar = "BAZ"))] -struct NestedDe { - #[xml(namespace(bar))] - flag: bool, +#[derive(Debug, Eq, PartialEq, ToXml)] +#[xml(namespace(dar = "BAZ", internal = "INTERNAL"))] +struct NestedDifferentNamespace { + #[xml(namespace(dar))] + flag_parent_prefix: bool, + #[xml(namespace(internal))] + flag_internal_prefix: bool, } #[derive(Debug, Eq, PartialEq, ToXml)] #[xml(namespace("URI", bar = "BAZ", foo = "BAR"))] -struct StructWithCustomFieldWrongPrefix { - test: NestedWrongPrefix, +struct StructChildNamespaces { + different_child_namespace: NestedDifferentNamespace, + same_child_namespace: Nested, } +// Tests: +// - Different child namespace +// - The same child namespace #[test] -#[should_panic] -fn struct_with_custom_field_wrong_prefix() { - StructWithCustomFieldWrongPrefix { - test: NestedWrongPrefix { flag: true }, - } - .to_xml() - .unwrap(); +fn struct_child_namespaces() { + assert_eq!( + StructChildNamespaces { + different_child_namespace: NestedDifferentNamespace { + flag_parent_prefix: true, + flag_internal_prefix: false, + }, + same_child_namespace: Nested { + flag_parent_prefix: true, + flag_internal_prefix: false, + }, + } + .to_xml() + .unwrap(), + "truefalsetruefalse" + ); +} + +#[derive(Debug, Eq, PartialEq, FromXml)] +#[xml(namespace("URI", bar = "BAZ"))] +struct NestedDe { + #[xml(namespace(bar))] + flag: bool, } #[derive(Debug, Eq, PartialEq, FromXml)] @@ -138,7 +184,7 @@ fn struct_with_custom_field_from_xml() { ); } -#[derive(Debug, Eq, PartialEq, ToXml, FromXml)] +#[derive(Debug, Eq, PartialEq, FromXml)] struct NestedWrongNamespace { flag: bool, }