diff --git a/instant-xml-macros/src/lib.rs b/instant-xml-macros/src/lib.rs index 91149d6..6971722 100644 --- a/instant-xml-macros/src/lib.rs +++ b/instant-xml-macros/src/lib.rs @@ -6,7 +6,7 @@ mod se; use std::collections::HashMap; use proc_macro2::TokenStream; -use quote::quote; +use quote::{quote, ToTokens}; use syn::{parse_macro_input, Lit, Meta, NestedMeta}; use crate::se::Serializer; @@ -104,6 +104,8 @@ fn retrieve_attr_list(attributes: &Vec) -> Option<(Option proc_macro::TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); let ident = &ast.ident; + let generics = (&ast.generics).into_token_stream(); + let root_name = ident.to_string(); let mut serializer = Serializer::new(&ast.attrs); @@ -133,7 +135,7 @@ pub fn to_xml(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let current_namespaces = serializer.namespaces_token(); proc_macro::TokenStream::from(quote!( - impl ToXml for #ident { + impl #generics ToXml for #ident #generics { fn serialize(&self, serializer: &mut instant_xml::Serializer) -> Result<(), instant_xml::Error> where W: std::fmt::Write, diff --git a/instant-xml-macros/src/se.rs b/instant-xml-macros/src/se.rs index dc93699..4e6ee1f 100644 --- a/instant-xml-macros/src/se.rs +++ b/instant-xml-macros/src/se.rs @@ -91,11 +91,6 @@ impl<'a> Serializer { 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 @@ -125,7 +120,7 @@ impl<'a> Serializer { attributes.extend(quote!( #declaration - serializer.add_attribute_key(&#name); + serializer.add_attribute_key(&#name)?; field.attribute = Some(instant_xml::FieldAttribute::Attribute); )); attributes diff --git a/instant-xml/src/impls.rs b/instant-xml/src/impls.rs index 3069184..433e15a 100644 --- a/instant-xml/src/impls.rs +++ b/instant-xml/src/impls.rs @@ -1,6 +1,10 @@ +use std::borrow::Cow; +use std::fmt; use std::str::FromStr; -use crate::{Deserializer, EntityType, Error, FromXml, TagName, Visitor}; +use crate::{ + Deserializer, EntityType, Error, FieldAttribute, FromXml, Serializer, TagName, ToXml, Visitor, +}; struct BoolVisitor; @@ -25,3 +29,150 @@ impl<'xml> FromXml<'xml> for bool { } } } + +// Serializer +struct DisplayToXml<'a, T: fmt::Display>(pub &'a T); + +impl<'a, T> ToXml for DisplayToXml<'a, T> +where + T: fmt::Display, +{ + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + let field_context = match serializer.consume_field_context() { + Some(field_context) => field_context, + None => return Err(Error::UnexpectedValue), + }; + + match field_context.attribute { + Some(FieldAttribute::Attribute) => { + serializer.add_attribute_value(&self.0)?; + } + _ => { + serializer.add_open_tag(&field_context)?; + write!(serializer.output, "{}", self.0)?; + serializer.add_close_tag(field_context)?; + } + } + Ok(()) + } +} + +macro_rules! to_xml_for_number { + ($typ:ty) => { + impl ToXml for $typ { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + DisplayToXml(self).serialize(serializer) + } + } + }; +} + +to_xml_for_number!(i8); +to_xml_for_number!(i16); +to_xml_for_number!(i32); +to_xml_for_number!(i64); +to_xml_for_number!(isize); +to_xml_for_number!(u8); +to_xml_for_number!(u16); +to_xml_for_number!(u32); +to_xml_for_number!(u64); +to_xml_for_number!(usize); +to_xml_for_number!(f32); +to_xml_for_number!(f64); + +impl ToXml for bool { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + let value = match self { + true => "true", + false => "false", + }; + + DisplayToXml(&value).serialize(serializer) + } +} + +impl ToXml for String { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + DisplayToXml(&escape(self)?).serialize(serializer) + } +} + +impl ToXml for char { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + let mut tmp = [0u8; 4]; + DisplayToXml(&escape(&*self.encode_utf8(&mut tmp))?).serialize(serializer) + } +} + +impl ToXml for &str { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + DisplayToXml(&escape(self)?).serialize(serializer) + } +} + +impl ToXml for Cow<'_, str> { + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + DisplayToXml(&escape(self)?).serialize(serializer) + } +} + +impl ToXml for Option +where + T: ToXml, +{ + fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> + where + W: fmt::Write, + { + match self { + Some(v) => v.serialize(serializer), + None => Ok(()), + } + } +} + +fn escape(input: &str) -> Result, Error> { + let mut result = String::with_capacity(input.len()); + let mut last_end = 0; + for (start, c) in input.chars().enumerate() { + let to = match c { + '&' => "&", + '"' => """, + '<' => "<", + '>' => ">", + '\'' => "'", + _ => continue, + }; + result.push_str(input.get(last_end..start).unwrap()); + result.push_str(to); + last_end = start + 1; + } + + if result.is_empty() { + return Ok(Cow::Borrowed(input)); + } + + result.push_str(input.get(last_end..input.len()).unwrap()); + Ok(Cow::Owned(result)) +} diff --git a/instant-xml/src/lib.rs b/instant-xml/src/lib.rs index 244ea76..83b2cc3 100644 --- a/instant-xml/src/lib.rs +++ b/instant-xml/src/lib.rs @@ -1,5 +1,6 @@ use std::collections::HashMap; use std::fmt; +use std::fmt::Write; use thiserror::Error; pub use xmlparser; @@ -38,96 +39,6 @@ pub trait ToXml { W: fmt::Write; } -macro_rules! to_xml_for_number { - ($typ:ty) => { - impl ToXml for $typ { - fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> - where - W: fmt::Write, - { - match serializer.consume_field_context() { - Some(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), - } - } - } - }; -} - -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) -> Result<(), Error> - where - W: fmt::Write, - { - let value = match self { - true => "true", - false => "false", - }; - - match serializer.consume_field_context() { - Some(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), - } - } -} - -impl ToXml for String { - fn serialize(&self, serializer: &mut Serializer) -> Result<(), Error> - where - W: fmt::Write, - { - match serializer.consume_field_context() { - Some(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), - } - } -} - pub struct Serializer<'xml, W> where W: fmt::Write, @@ -163,16 +74,24 @@ impl<'xml, W: std::fmt::Write> Serializer<'xml, W> { Ok(()) } - pub fn add_attribute_key(&mut self, attr_key: &str) { + pub fn add_attribute_key(&mut self, attr_key: &T) -> Result<(), Error> + where + T: fmt::Display, + { self.current_attributes.push(' '); - self.current_attributes.push_str(attr_key); + write!(self.current_attributes, "{}", attr_key)?; self.current_attributes.push('='); + Ok(()) } - pub fn add_attribute_value(&mut self, attr_value: &str) { + pub fn add_attribute_value(&mut self, attr_value: &T) -> Result<(), Error> + where + T: fmt::Display, + { self.current_attributes.push('"'); - self.current_attributes.push_str(attr_value); + write!(self.current_attributes, "{}", attr_value)?; self.current_attributes.push('"'); + Ok(()) } pub fn set_field_context(&mut self, field_context: FieldContext<'xml>) -> Result<(), Error> { diff --git a/instant-xml/tests/all.rs b/instant-xml/tests/all.rs index 3fc18f7..fb35ced 100644 --- a/instant-xml/tests/all.rs +++ b/instant-xml/tests/all.rs @@ -1,4 +1,7 @@ +use std::borrow::Cow; + use instant_xml::{Error, FromXml, ToXml}; + //TODO: Add compile time errors check? #[derive(Debug, Eq, PartialEq, ToXml)] @@ -377,3 +380,81 @@ fn direct_namespaces() { Error::WrongNamespace ); } + +#[derive(Debug, PartialEq, ToXml)] +#[xml(namespace("URI"))] +struct StructDeserializerScalars<'a, 'b> { + bool_type: bool, + i8_type: i8, + u32_type: u32, + string_type: String, + str_type_a: &'a str, + str_type_b: &'b str, + char_type: char, + f32_type: f32, + cow: Cow<'a, str>, + option: Option<&'a str>, +} + +#[test] +fn scalars() { + // Option some + assert_eq!( + StructDeserializerScalars{ + bool_type: true, + i8_type: 1, + u32_type: 42, + string_type: "string".to_string(), + str_type_a: "lifetime a", + str_type_b: "lifetime b", + char_type: 'c', + f32_type: 1.20, + cow: Cow::from("123"), + option: Some("asd"), + } + .to_xml() + .unwrap(), + "true142stringlifetime alifetime bc1.2123" + ); + + // Option none + assert_eq!( + StructDeserializerScalars{ + bool_type: true, + i8_type: 1, + u32_type: 42, + string_type: "string".to_string(), + str_type_a: "lifetime a", + str_type_b: "lifetime b", + char_type: 'c', + f32_type: 1.20, + cow: Cow::from("123"), + option: None, + } + .to_xml() + .unwrap(), + "true142stringlifetime alifetime bc1.2123" + ); +} + +#[derive(Debug, PartialEq, Eq, ToXml)] +#[xml(namespace("URI"))] +struct StructSpecialEntities<'a> { + string_type: String, + str_type_a: &'a str, + cow: Cow<'a, str>, +} + +#[test] +fn special_entities() { + assert_eq!( + StructSpecialEntities{ + string_type: "&\"<>\'aa".to_string(), + str_type_a: "&\"<>\'bb", + cow: Cow::from("&\"<>\'cc"), + } + .to_xml() + .unwrap(), + "&"<>'aa&"<>'bb&"<>'cc" + ); +}