From 75694082454c0139f785e11aba1c2f88176ee31e Mon Sep 17 00:00:00 2001 From: Marc-Antoine Arnaud Date: Sun, 7 Jun 2020 15:16:46 +0200 Subject: [PATCH] support flatten attributes --- yaserde/src/lib.rs | 30 +++- yaserde/src/ser/mod.rs | 13 +- yaserde/tests/flatten.rs | 40 +++++ yaserde/tests/serializer.rs | 16 +- yaserde_derive/src/ser/expand_enum.rs | 4 +- yaserde_derive/src/ser/expand_struct.rs | 166 ++++++++++-------- .../src/ser/implement_deserializer.rs | 43 ----- .../src/ser/implement_serializer.rs | 87 +++++++++ yaserde_derive/src/ser/mod.rs | 2 +- 9 files changed, 272 insertions(+), 129 deletions(-) delete mode 100644 yaserde_derive/src/ser/implement_deserializer.rs create mode 100644 yaserde_derive/src/ser/implement_serializer.rs diff --git a/yaserde/src/lib.rs b/yaserde/src/lib.rs index 6eedc66..ef049d8 100644 --- a/yaserde/src/lib.rs +++ b/yaserde/src/lib.rs @@ -23,8 +23,20 @@ pub trait YaDeserialize: Sized { } /// A **data structure** that can be serialized into any data format supported by YaSerDe. -pub trait YaSerialize: Sized { +pub trait YaSerialize<'a>: Sized { fn serialize(&self, writer: &mut ser::Serializer) -> Result<(), String>; + + fn serialize_attributes( + &self, + attributes: Vec, + namespace: xml::namespace::Namespace, + ) -> Result< + ( + Vec, + xml::namespace::Namespace, + ), + String, + >; } /// A **visitor** that can be implemented to retrieve information from source file. @@ -83,13 +95,27 @@ pub trait Visitor<'de>: Sized { macro_rules! serialize_type { ($type:ty) => { - impl YaSerialize for $type { + impl<'a> YaSerialize<'a> for $type { fn serialize(&self, writer: &mut ser::Serializer) -> Result<(), String> { let content = format!("{}", self); let event = XmlEvent::characters(&content); let _ret = writer.write(event); Ok(()) } + + fn serialize_attributes( + &self, + attributes: Vec, + namespace: xml::namespace::Namespace, + ) -> Result< + ( + Vec, + xml::namespace::Namespace, + ), + String, + > { + Ok((attributes, namespace)) + } } }; } diff --git a/yaserde/src/ser/mod.rs b/yaserde/src/ser/mod.rs index 4d57c87..1f9797d 100644 --- a/yaserde/src/ser/mod.rs +++ b/yaserde/src/ser/mod.rs @@ -7,21 +7,24 @@ use std::str; use xml::writer::XmlEvent; use xml::{EmitterConfig, EventWriter}; -pub fn to_string(model: &T) -> Result { +pub fn to_string<'a, T: YaSerialize<'a>>(model: &T) -> Result { let buf = Cursor::new(Vec::new()); let cursor = serialize_with_writer(model, buf, &Config::default())?; let data = str::from_utf8(cursor.get_ref()).expect("Found invalid UTF-8"); Ok(String::from(data)) } -pub fn to_string_with_config(model: &T, config: &Config) -> Result { +pub fn to_string_with_config<'a, T: YaSerialize<'a>>( + model: &T, + config: &Config, +) -> Result { let buf = Cursor::new(Vec::new()); let cursor = serialize_with_writer(model, buf, config)?; let data = str::from_utf8(cursor.get_ref()).expect("Found invalid UTF-8"); Ok(String::from(data)) } -pub fn serialize_with_writer( +pub fn serialize_with_writer<'a, W: Write, T: YaSerialize<'a>>( model: &T, writer: W, _config: &Config, @@ -33,14 +36,14 @@ pub fn serialize_with_writer( } } -pub fn to_string_content(model: &T) -> Result { +pub fn to_string_content<'a, T: YaSerialize<'a>>(model: &T) -> Result { let buf = Cursor::new(Vec::new()); let cursor = serialize_with_writer_content(model, buf)?; let data = str::from_utf8(cursor.get_ref()).expect("Found invalid UTF-8"); Ok(String::from(data)) } -pub fn serialize_with_writer_content( +pub fn serialize_with_writer_content<'a, W: Write, T: YaSerialize<'a>>( model: &T, writer: W, ) -> Result { diff --git a/yaserde/tests/flatten.rs b/yaserde/tests/flatten.rs index 71274d6..13c7eb6 100644 --- a/yaserde/tests/flatten.rs +++ b/yaserde/tests/flatten.rs @@ -144,3 +144,43 @@ fn root_flatten_enum() { let content = "string"; serialize_and_validate!(model, content); } + +#[test] +fn flatten_attribute() { + #[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)] + struct HtmlText { + #[yaserde(flatten)] + text_attributes: TextAttributes, + #[yaserde(attribute)] + display: String, + } + + #[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)] + struct TextAttributes { + #[yaserde(attribute)] + bold: bool, + #[yaserde(flatten)] + font: FontAttributes, + } + + #[derive(Default, PartialEq, Debug, YaDeserialize, YaSerialize)] + #[yaserde(namespace = "ns: http://www.sample.com/ns/domain")] + pub struct FontAttributes { + #[yaserde(attribute, prefix = "ns")] + size: u32, + } + + let model = HtmlText { + text_attributes: TextAttributes { + bold: true, + font: FontAttributes { size: 24 }, + }, + display: "block".to_string(), + }; + + let content = r#" + "#; + + serialize_and_validate!(model, content); + deserialize_and_validate!(content, model, HtmlText); +} diff --git a/yaserde/tests/serializer.rs b/yaserde/tests/serializer.rs index 01b7921..da4020d 100644 --- a/yaserde/tests/serializer.rs +++ b/yaserde/tests/serializer.rs @@ -293,7 +293,7 @@ fn ser_custom() { value: i32, } - impl YaSerialize for Day { + impl<'a> YaSerialize<'a> for Day { fn serialize(&self, writer: &mut yaserde::ser::Serializer) -> Result<(), String> { let _ret = writer.write(xml::writer::XmlEvent::start_element("DoubleDay")); let _ret = writer.write(xml::writer::XmlEvent::characters( @@ -302,6 +302,20 @@ fn ser_custom() { let _ret = writer.write(xml::writer::XmlEvent::end_element()); Ok(()) } + + fn serialize_attributes( + &self, + attributes: Vec, + namespace: xml::namespace::Namespace, + ) -> Result< + ( + Vec, + xml::namespace::Namespace, + ), + String, + > { + Ok((attributes, namespace)) + } } let model = Date { diff --git a/yaserde_derive/src/ser/expand_enum.rs b/yaserde_derive/src/ser/expand_enum.rs index eb9521c..e00a666 100644 --- a/yaserde_derive/src/ser/expand_enum.rs +++ b/yaserde_derive/src/ser/expand_enum.rs @@ -1,5 +1,5 @@ use crate::common::{Field, YaSerdeAttribute, YaSerdeField}; -use crate::ser::{implement_deserializer::implement_deserializer, label::build_label_name}; +use crate::ser::{implement_serializer::implement_serializer, label::build_label_name}; use proc_macro2::TokenStream; use syn::DataEnum; use syn::Fields; @@ -13,7 +13,7 @@ pub fn serialize( ) -> TokenStream { let inner_enum_inspector = inner_enum_inspector(data_enum, name, root_attributes); - implement_deserializer( + implement_serializer( name, root, root_attributes, diff --git a/yaserde_derive/src/ser/expand_struct.rs b/yaserde_derive/src/ser/expand_struct.rs index 1201df4..a92c16d 100644 --- a/yaserde_derive/src/ser/expand_struct.rs +++ b/yaserde_derive/src/ser/expand_struct.rs @@ -1,6 +1,6 @@ use crate::common::{Field, YaSerdeAttribute, YaSerdeField}; -use crate::ser::{element::*, implement_deserializer::implement_deserializer}; +use crate::ser::{element::*, implement_serializer::implement_serializer}; use proc_macro2::TokenStream; use syn::DataStruct; use syn::Ident; @@ -11,45 +11,20 @@ pub fn serialize( root: &str, root_attributes: &YaSerdeAttribute, ) -> TokenStream { - let build_attributes: TokenStream = data_struct + let append_attributes: TokenStream = data_struct .fields .iter() .map(|field| YaSerdeField::new(field.clone())) - .filter(|field| field.is_attribute()) + .filter(|field| field.is_attribute() || field.is_flatten()) .map(|field| { let label = field.label(); - let label_name = field.renamed_label(root_attributes); - match field.get_type() { - Field::FieldString - | Field::FieldBool - | Field::FieldI8 - | Field::FieldU8 - | Field::FieldI16 - | Field::FieldU16 - | Field::FieldI32 - | Field::FieldU32 - | Field::FieldI64 - | Field::FieldU64 - | Field::FieldF32 - | Field::FieldF64 => Some(field.ser_wrap_default_attribute( - Some(quote!(self.#label.to_string())), - quote!({ - struct_start_event.attr(#label_name, &yaserde_inner) - }), - )), - Field::FieldOption { data_type } => match *data_type { - Field::FieldString => Some(field.ser_wrap_default_attribute( - None, - quote!({ - if let Some(ref value) = self.#label { - struct_start_event.attr(#label_name, value) - } else { - struct_start_event - } - }), - )), - Field::FieldBool + if field.is_attribute() { + let label_name = field.renamed_label(root_attributes); + + match field.get_type() { + Field::FieldString + | Field::FieldBool | Field::FieldI8 | Field::FieldU8 | Field::FieldI16 @@ -59,55 +34,96 @@ pub fn serialize( | Field::FieldI64 | Field::FieldU64 | Field::FieldF32 - | Field::FieldF64 => Some(field.ser_wrap_default_attribute( - Some(quote!(self.#label.map_or_else(|| String::new(), |v| v.to_string()))), + | Field::FieldF64 => field.ser_wrap_default_attribute( + Some(quote!(self.#label.to_string())), quote!({ - if let Some(ref value) = self.#label { - struct_start_event.attr(#label_name, &yaserde_inner) - } else { - struct_start_event - } + struct_start_event.attr(#label_name, &yaserde_inner) }), - )), - Field::FieldVec { .. } => { - let item_ident = Ident::new("yaserde_item", field.get_span()); - let inner = enclose_formatted_characters(&item_ident, label_name); - - Some(field.ser_wrap_default_attribute( + ), + Field::FieldOption { data_type } => match *data_type { + Field::FieldString => field.ser_wrap_default_attribute( None, quote!({ - if let Some(ref yaserde_list) = self.#label { - for yaserde_item in yaserde_list.iter() { - #inner - } + if let Some(ref value) = self.#label { + struct_start_event.attr(#label_name, value) + } else { + struct_start_event } }), - )) - } - Field::FieldStruct { .. } => Some(field.ser_wrap_default_attribute( - Some(quote!(self.#label - .as_ref() - .map_or_else(|| Ok(String::new()), |v| yaserde::ser::to_string_content(v))?)), + ), + Field::FieldBool + | Field::FieldI8 + | Field::FieldU8 + | Field::FieldI16 + | Field::FieldU16 + | Field::FieldI32 + | Field::FieldU32 + | Field::FieldI64 + | Field::FieldU64 + | Field::FieldF32 + | Field::FieldF64 => field.ser_wrap_default_attribute( + Some(quote!(self.#label.map_or_else(|| String::new(), |v| v.to_string()))), + quote!({ + if let Some(ref value) = self.#label { + struct_start_event.attr(#label_name, &yaserde_inner) + } else { + struct_start_event + } + }), + ), + Field::FieldVec { .. } => { + let item_ident = Ident::new("yaserde_item", field.get_span()); + let inner = enclose_formatted_characters(&item_ident, label_name); + + field.ser_wrap_default_attribute( + None, + quote!({ + if let Some(ref yaserde_list) = self.#label { + for yaserde_item in yaserde_list.iter() { + #inner + } + } + }), + ) + } + Field::FieldStruct { .. } => field.ser_wrap_default_attribute( + Some(quote!(self.#label + .as_ref() + .map_or_else(|| Ok(String::new()), |v| yaserde::ser::to_string_content(v))?)), + quote!({ + if let Some(ref yaserde_struct) = self.#label { + struct_start_event.attr(#label_name, &yaserde_inner) + } else { + struct_start_event + } + }), + ), + Field::FieldOption { .. } => unimplemented!(), + }, + Field::FieldStruct { .. } => field.ser_wrap_default_attribute( + Some(quote!(yaserde::ser::to_string_content(&self.#label)?)), quote!({ - if let Some(ref yaserde_struct) = self.#label { - struct_start_event.attr(#label_name, &yaserde_inner) - } else { - struct_start_event - } + struct_start_event.attr(#label_name, &yaserde_inner) }), - )), - Field::FieldOption { .. } => unimplemented!(), - }, - Field::FieldStruct { .. } => Some(field.ser_wrap_default_attribute( - Some(quote!(yaserde::ser::to_string_content(&self.#label)?)), - quote!({ - struct_start_event.attr(#label_name, &yaserde_inner) - }), - )), - Field::FieldVec { .. } => None, + ), + Field::FieldVec { .. } => { + // TODO + quote!() + } + } + } else { + match field.get_type() { + Field::FieldStruct { .. } => { + quote!( + let (attributes, namespace) = self.#label.serialize_attributes(vec![], xml::namespace::Namespace::empty())?; + child_attributes_namespace.extend(&namespace); + child_attributes.extend(attributes); + ) + } + _ => quote!() + } } }) - .filter_map(|x| x) .collect(); let struct_inspector: TokenStream = data_struct @@ -267,11 +283,11 @@ pub fn serialize( .filter_map(|x| x) .collect(); - implement_deserializer( + implement_serializer( name, root, root_attributes, - build_attributes, + append_attributes, struct_inspector, ) } diff --git a/yaserde_derive/src/ser/implement_deserializer.rs b/yaserde_derive/src/ser/implement_deserializer.rs deleted file mode 100644 index 645b4ed..0000000 --- a/yaserde_derive/src/ser/implement_deserializer.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::common::YaSerdeAttribute; -use crate::ser::namespace::generate_namespaces_definition; -use proc_macro2::Ident; -use proc_macro2::TokenStream; - -pub fn implement_deserializer( - name: &Ident, - root: &str, - attributes: &YaSerdeAttribute, - attributes_inspector: TokenStream, - inner_inspector: TokenStream, -) -> TokenStream { - let namespaces_definition = generate_namespaces_definition(attributes); - let flatten = attributes.flatten; - - quote! { - use xml::writer::XmlEvent; - - impl YaSerialize for #name { - #[allow(unused_variables)] - fn serialize(&self, writer: &mut yaserde::ser::Serializer) - -> Result<(), String> { - let skip = writer.skip_start_end(); - - if !#flatten && !skip { - let yaserde_label = writer.get_start_event_name().unwrap_or_else(|| #root.to_string()); - let struct_start_event = XmlEvent::start_element(yaserde_label.as_ref())#namespaces_definition; - #attributes_inspector - writer.write(struct_start_event).map_err(|e| e.to_string())?; - } - - #inner_inspector - - if !#flatten && !skip { - let struct_end_event = XmlEvent::end_element(); - writer.write(struct_end_event).map_err(|e| e.to_string())?; - } - - Ok(()) - } - } - } -} diff --git a/yaserde_derive/src/ser/implement_serializer.rs b/yaserde_derive/src/ser/implement_serializer.rs new file mode 100644 index 0000000..3b7df82 --- /dev/null +++ b/yaserde_derive/src/ser/implement_serializer.rs @@ -0,0 +1,87 @@ +use crate::common::YaSerdeAttribute; +use crate::ser::namespace::generate_namespaces_definition; +use proc_macro2::Ident; +use proc_macro2::TokenStream; + +pub fn implement_serializer( + name: &Ident, + root: &str, + attributes: &YaSerdeAttribute, + append_attributes: TokenStream, + inner_inspector: TokenStream, +) -> TokenStream { + let namespaces_definition = generate_namespaces_definition(attributes); + let flatten = attributes.flatten; + + quote! { + use xml::writer::XmlEvent; + + impl<'a> YaSerialize<'a> for #name { + #[allow(unused_variables)] + fn serialize(&self, writer: &mut yaserde::ser::Serializer) + -> Result<(), String> { + let skip = writer.skip_start_end(); + + if !#flatten && !skip { + let mut child_attributes = vec![]; + let mut child_attributes_namespace = xml::namespace::Namespace::empty(); + + let yaserde_label = writer.get_start_event_name().unwrap_or_else(|| #root.to_string()); + let struct_start_event = XmlEvent::start_element(yaserde_label.as_ref())#namespaces_definition; + #append_attributes + + let event : xml::writer::events::XmlEvent = struct_start_event.into(); + + if let xml::writer::events::XmlEvent::StartElement{name, attributes, namespace} = event { + let mut attributes: Vec = attributes.into_owned().to_vec().iter().map(|k| k.to_owned()).collect(); + attributes.extend(child_attributes); + + let all_attributes = attributes.iter().map(|ca| ca.borrow()).collect(); + + let mut all_namespaces = namespace.into_owned(); + all_namespaces.extend(&child_attributes_namespace); + + writer.write(xml::writer::events::XmlEvent::StartElement{ + name, + attributes: std::borrow::Cow::Owned(all_attributes), + namespace: std::borrow::Cow::Owned(all_namespaces) + }).map_err(|e| e.to_string())?; + } else { + unreachable!() + } + } + + #inner_inspector + + if !#flatten && !skip { + let struct_end_event = XmlEvent::end_element(); + writer.write(struct_end_event).map_err(|e| e.to_string())?; + } + + Ok(()) + } + + fn serialize_attributes(&self, mut source_attributes: Vec, mut source_namespace: xml::namespace::Namespace) -> Result<(Vec, xml::namespace::Namespace), String> { + let mut child_attributes : Vec = vec![]; + let mut child_attributes_namespace = xml::namespace::Namespace::empty(); + + let struct_start_event = XmlEvent::start_element("temporary_element_to_generate_attributes")#namespaces_definition; + #append_attributes + let event : xml::writer::events::XmlEvent = struct_start_event.into(); + + if let xml::writer::events::XmlEvent::StartElement{attributes, namespace, ..} = event { + source_namespace.extend(&namespace.into_owned()); + source_namespace.extend(&child_attributes_namespace); + + let a: Vec = attributes.into_owned().to_vec().iter().map(|k| k.to_owned()).collect(); + source_attributes.extend(a); + source_attributes.extend(child_attributes); + + Ok((source_attributes, source_namespace)) + } else { + unreachable!(); + } + } + } + } +} diff --git a/yaserde_derive/src/ser/mod.rs b/yaserde_derive/src/ser/mod.rs index 85c5e0c..4e725a2 100644 --- a/yaserde_derive/src/ser/mod.rs +++ b/yaserde_derive/src/ser/mod.rs @@ -1,7 +1,7 @@ pub mod element; pub mod expand_enum; pub mod expand_struct; -pub mod implement_deserializer; +pub mod implement_serializer; pub mod label; pub mod namespace;