From b5e0f68239357a12044f50005f9c8ead41325ed7 Mon Sep 17 00:00:00 2001 From: Lennart <18233294+lennart-k@users.noreply.github.com> Date: Mon, 23 Dec 2024 10:46:33 +0100 Subject: [PATCH] xml: Add serialization --- crates/xml/derive/src/de/de_enum.rs | 30 ++++++++++++++++++++++++- crates/xml/derive/src/de/de_struct.rs | 32 ++++++++++++++++++++++++++- crates/xml/derive/src/de/field.rs | 19 ++++++++++++++++ crates/xml/derive/src/lib.rs | 12 ++++++++++ crates/xml/src/lib.rs | 29 ++---------------------- crates/xml/src/se.rs | 9 ++++++++ crates/xml/tests/de_enum.rs | 13 ++++++++--- crates/xml/tests/de_struct.rs | 1 - crates/xml/tests/se_struct.rs | 29 ++++++++++++++++++++++++ 9 files changed, 141 insertions(+), 33 deletions(-) create mode 100644 crates/xml/src/se.rs create mode 100644 crates/xml/tests/se_struct.rs diff --git a/crates/xml/derive/src/de/de_enum.rs b/crates/xml/derive/src/de/de_enum.rs index bcfe8c7..8e22b64 100644 --- a/crates/xml/derive/src/de/de_enum.rs +++ b/crates/xml/derive/src/de/de_enum.rs @@ -127,7 +127,7 @@ impl Enum { quote! { impl #impl_generics ::rustical_xml::XmlDeserialize for #name #type_generics #where_clause { - fn deserialize( + fn deserialize( reader: &mut quick_xml::NsReader, start: &quick_xml::events::BytesStart, empty: bool @@ -191,4 +191,32 @@ impl Enum { generics: input.generics.to_owned(), } } + + pub fn impl_se(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + + // TODO: Implement attributes + quote! { + impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { + fn serialize( + &self, + tag: Option<&[u8]>, + writer: &mut ::quick_xml::Writer + ) -> ::std::io::Result<()> { + use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; + + let tag_str = tag.map(String::from_utf8_lossy); + + if let Some(tag) = &tag_str { + writer.write_event(Event::Start(BytesStart::new(tag.to_owned())))?; + } + if let Some(tag) = &tag_str { + writer.write_event(Event::End(BytesEnd::new(tag.to_owned())))?; + } + Ok(()) + } + } + } + } } diff --git a/crates/xml/derive/src/de/de_struct.rs b/crates/xml/derive/src/de/de_struct.rs index c08bb89..7abd0da 100644 --- a/crates/xml/derive/src/de/de_struct.rs +++ b/crates/xml/derive/src/de/de_struct.rs @@ -82,7 +82,7 @@ impl NamedStruct { quote! { impl #impl_generics ::rustical_xml::XmlDeserialize for #ident #type_generics #where_clause { - fn deserialize( + fn deserialize( reader: &mut quick_xml::NsReader, start: &quick_xml::events::BytesStart, empty: bool @@ -162,4 +162,34 @@ impl NamedStruct { } } } + + pub fn impl_se(&self) -> proc_macro2::TokenStream { + let (impl_generics, type_generics, where_clause) = self.generics.split_for_impl(); + let ident = &self.ident; + let tag_writers = self.fields.iter().map(Field::tag_writer); + + // TODO: Implement attributes + quote! { + impl #impl_generics ::rustical_xml::XmlSerialize for #ident #type_generics #where_clause { + fn serialize( + &self, + tag: Option<&[u8]>, + writer: &mut ::quick_xml::Writer + ) -> ::std::io::Result<()> { + use ::quick_xml::events::{BytesEnd, BytesStart, BytesText, Event}; + + let tag_str = tag.map(String::from_utf8_lossy); + + if let Some(tag) = &tag_str { + writer.write_event(Event::Start(BytesStart::new(tag.to_owned())))?; + } + #(#tag_writers);* + if let Some(tag) = &tag_str { + writer.write_event(Event::End(BytesEnd::new(tag.to_owned())))?; + } + Ok(()) + } + } + } + } } diff --git a/crates/xml/derive/src/de/field.rs b/crates/xml/derive/src/de/field.rs index 40cbed4..fbedfae 100644 --- a/crates/xml/derive/src/de/field.rs +++ b/crates/xml/derive/src/de/field.rs @@ -218,4 +218,23 @@ impl Field { } }) } + + pub fn tag_writer(&self) -> Option { + let field_ident = self.field_ident(); + let field_name = self.xml_name(); + + match self.attrs.xml_ty { + FieldType::Attr => None, + FieldType::Text => Some(quote! { + writer.write_event(Event::Text(BytesText::new(&self.#field_ident)))?; + }), + FieldType::Tag => Some(quote! { + self.#field_ident.serialize(Some(#field_name), writer)?; + }), + FieldType::Untagged => Some(quote! { + // TODO: untag! + self.#field_ident.serialize(None, writer)?; + }), + } + } } diff --git a/crates/xml/derive/src/lib.rs b/crates/xml/derive/src/lib.rs index 437dbfc..7f533dd 100644 --- a/crates/xml/derive/src/lib.rs +++ b/crates/xml/derive/src/lib.rs @@ -16,6 +16,18 @@ pub fn derive_xml_deserialize(input: proc_macro::TokenStream) -> proc_macro::Tok .into() } +#[proc_macro_derive(XmlSerialize, attributes(xml))] +pub fn derive_xml_serialize(input: proc_macro::TokenStream) -> proc_macro::TokenStream { + let input = parse_macro_input!(input as DeriveInput); + + match &input.data { + syn::Data::Enum(e) => Enum::parse(&input, e).impl_se(), + syn::Data::Struct(s) => NamedStruct::parse(&input, s).impl_se(), + syn::Data::Union(_) => panic!("Union not supported"), + } + .into() +} + #[proc_macro_derive(XmlRoot, attributes(xml))] pub fn derive_xml_root(input: proc_macro::TokenStream) -> proc_macro::TokenStream { let input = parse_macro_input!(input as DeriveInput); diff --git a/crates/xml/src/lib.rs b/crates/xml/src/lib.rs index 860150e..b49b57b 100644 --- a/crates/xml/src/lib.rs +++ b/crates/xml/src/lib.rs @@ -2,11 +2,13 @@ use quick_xml::events::{BytesStart, Event}; use std::io::BufRead; pub mod de; +pub mod se; mod value; pub use de::XmlDeError; pub use de::XmlDeserialize; pub use de::XmlRoot; +pub use se::XmlSerialize; pub use value::Value; impl XmlDeserialize for Option { @@ -42,33 +44,6 @@ impl XmlDeserialize for Unit { } } -impl XmlDeserialize for String { - fn deserialize( - reader: &mut quick_xml::NsReader, - start: &BytesStart, - empty: bool, - ) -> Result { - if empty { - return Ok(String::new()); - } - let mut content = String::new(); - let mut buf = Vec::new(); - loop { - match reader.read_event_into(&mut buf)? { - Event::End(e) if e.name() == start.name() => { - break; - } - Event::Eof => return Err(XmlDeError::Eof), - Event::Text(text) => { - content.push_str(&text.unescape()?); - } - _a => return Err(XmlDeError::UnknownError), - }; - } - Ok(content) - } -} - // TODO: actually implement pub struct Unparsed(BytesStart<'static>); diff --git a/crates/xml/src/se.rs b/crates/xml/src/se.rs new file mode 100644 index 0000000..7db6f9b --- /dev/null +++ b/crates/xml/src/se.rs @@ -0,0 +1,9 @@ +pub use xml_derive::XmlSerialize; + +pub trait XmlSerialize { + fn serialize( + &self, + tag: Option<&[u8]>, + writer: &mut quick_xml::Writer, + ) -> std::io::Result<()>; +} diff --git a/crates/xml/tests/de_enum.rs b/crates/xml/tests/de_enum.rs index 7965494..e7634c3 100644 --- a/crates/xml/tests/de_enum.rs +++ b/crates/xml/tests/de_enum.rs @@ -1,5 +1,4 @@ use rustical_xml::{de::XmlRootParseStr, Unit, XmlDeserialize, XmlRoot}; -use std::io::BufRead; #[test] fn test_struct_tagged_enum() { @@ -15,11 +14,17 @@ fn test_struct_tagged_enum() { prop: Vec, } + #[derive(Debug, XmlDeserialize, PartialEq)] + struct Displayname { + #[xml(ty = "text")] + name: String, + } + #[derive(Debug, XmlDeserialize, PartialEq)] enum PropEnum { A, B, - Displayname(String), + Displayname(Displayname), } let doc = Propfind::parse_str( @@ -41,7 +46,9 @@ fn test_struct_tagged_enum() { prop: vec![ PropEnum::B, PropEnum::A, - PropEnum::Displayname("Hello!".to_owned()) + PropEnum::Displayname(Displayname { + name: "Hello!".to_owned() + }) ] } } diff --git a/crates/xml/tests/de_struct.rs b/crates/xml/tests/de_struct.rs index 00f7547..6cbe542 100644 --- a/crates/xml/tests/de_struct.rs +++ b/crates/xml/tests/de_struct.rs @@ -2,7 +2,6 @@ use rustical_xml::de::XmlRootParseStr; use rustical_xml::XmlRoot; use rustical_xml::{Unit, Unparsed, XmlDeserialize}; use std::collections::HashSet; -use std::io::BufRead; #[test] fn test_struct_text_field() { diff --git a/crates/xml/tests/se_struct.rs b/crates/xml/tests/se_struct.rs new file mode 100644 index 0000000..bf211ee --- /dev/null +++ b/crates/xml/tests/se_struct.rs @@ -0,0 +1,29 @@ +use rustical_xml::{XmlRoot, XmlSerialize}; +use xml_derive::XmlDeserialize; + +#[test] +fn test_struct_document() { + #[derive(Debug, XmlRoot, XmlSerialize, XmlDeserialize, PartialEq)] + #[xml(root = b"document")] + struct Document { + child: Child, + } + + #[derive(Debug, XmlDeserialize, XmlSerialize, PartialEq, Default)] + struct Child { + #[xml(ty = "text")] + text: String, + } + + let mut buf = Vec::new(); + let mut writer = quick_xml::Writer::new(&mut buf); + Document { + child: Child { + text: "asd".to_owned(), + }, + } + .serialize(Some(Document::root_tag()), &mut writer) + .unwrap(); + let out = String::from_utf8(buf).unwrap(); + dbg!(out); +}