Skip to content

Commit

Permalink
Merge pull request #42 from ImJeremyHe/jh/enum
Browse files Browse the repository at this point in the history
Support enum variant represented by text element
  • Loading branch information
ImJeremyHe authored Mar 16, 2024
2 parents 5dc1a8a + debc153 commit 7579d55
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 41 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
authors = ["ImJeremyHe<[email protected]>"]
edition = "2018"
name = "xmlserde"
version = "0.7.2"
version = "0.8.0"
license = "MIT"
description = "useful tool for serializing and deserializing xml"
repository = "https://github.com/ImJeremyHe/xmlserde"
Expand All @@ -13,4 +13,4 @@ readme = "README.md"
quick-xml = {version = "0.31", features = ["serialize"]}

[dev-dependencies]
xmlserde_derives = {path = "./derives", version = "0.7.2"}
xmlserde_derives = {path = "./derives", version = "0.8.0"}
2 changes: 1 addition & 1 deletion derives/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "xmlserde_derives"
version = "0.7.2"
version = "0.8.0"
description = "macros that help xmlserde serde the xml files"
authors = ["ImJeremyHe<[email protected]>"]
license = "MIT"
Expand Down
28 changes: 24 additions & 4 deletions derives/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ impl<'a> StructField<'a> {
"text" => EleType::Text,
"sfc" => EleType::SelfClosedChild,
"untag" => EleType::Untag,
_ => panic!(""),
_ => panic!("invalid type"),
};
ty = Some(t);
}
Expand Down Expand Up @@ -227,14 +227,16 @@ impl<'a> StructField<'a> {
}

pub struct EnumVariant<'a> {
pub name: syn::LitByteStr,
pub name: Option<syn::LitByteStr>,
pub ident: &'a syn::Ident,
pub ty: Option<&'a syn::Type>,
pub ele_type: EleType,
}

impl<'a> EnumVariant<'a> {
pub fn from_ast(v: &'a Variant) -> Self {
let mut name = Option::<syn::LitByteStr>::None;
let mut ele_type = EleType::Child;
for meta_item in v
.attrs
.iter()
Expand All @@ -247,19 +249,37 @@ impl<'a> EnumVariant<'a> {
name = Some(s.clone());
}
}
_ => panic!("unexpected"),
NameValue(m) if m.path == TYPE => {
if let Ok(s) = get_lit_str(&m.value) {
let t = match s.value().as_str() {
"child" => EleType::Child,
"text" => EleType::Text,
_ => panic!("invalid type in enum, should be `text` or `child` only"),
};
ele_type = t;
}
}
_ => panic!("unexpected attribute"),
}
}
if v.fields.len() > 1 {
panic!("only support 1 field");
}
if matches!(ele_type, EleType::Text) {
if name.is_some() {
panic!("should omit the `name`");
}
} else if name.is_none() {
panic!("should have name")
}
let field = &v.fields.iter().next();
let ty = field.map(|t| &t.ty);
let ident = &v.ident;
EnumVariant {
name: name.expect("no name attr is found"),
name,
ty,
ident,
ele_type,
}
}
}
Expand Down
103 changes: 89 additions & 14 deletions derives/src/de.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ pub fn get_de_impl_block(input: DeriveInput) -> proc_macro2::TokenStream {
}

pub fn get_de_enum_impl_block(container: Container) -> proc_macro2::TokenStream {
macro_rules! event_branches {
macro_rules! children_branches {
($attrs:expr, $b:expr) => {
container.enum_variants.iter().map(|v| {
let name = &v.name;
if matches!(&v.ele_type, EleType::Text) {
return quote! {};
}
let name = v.name.as_ref().expect("should have name");
let ty = v.ty;
let ident = v.ident;
if let Some(ty) = ty {
Expand All @@ -36,15 +39,44 @@ pub fn get_de_enum_impl_block(container: Container) -> proc_macro2::TokenStream
})
};
}
let mut text_opt = None;
let mut text_ident = None;
container.enum_variants.iter().for_each(|v| {
if !matches!(&v.ele_type, EleType::Text) {
return;
}

if let Some(_) = text_opt {
panic!("should only have one `text` type")
}

text_opt = Some(v.ty.expect("expect type"));
text_ident = Some(v.ident);
});

let text_function = if let Some(text_ty) = text_opt {
let ident = text_ident.expect("should have ident for text");
quote! {
fn __deserialize_from_text(s: &str) -> Option<Self> {
Some(Self::#ident(<#text_ty as ::xmlserde::XmlValue>::deserialize(s).unwrap()))
}
}
} else {
quote! {}
};
let ident = &container.original.ident;
let (impl_generics, type_generics, where_clause) = container.original.generics.split_for_impl();
let event_start_branches = event_branches!(_s.attributes(), false);
let event_empty_branches = event_branches!(_s.attributes(), true);
let children_tags = container.enum_variants.iter().map(|v| {
let name = &v.name;
quote! {#name}
});
let exact_tags = event_branches!(attrs, is_empty);
let event_start_branches = children_branches!(_s.attributes(), false);
let event_empty_branches = children_branches!(_s.attributes(), true);
let children_tags = container
.enum_variants
.iter()
.filter(|v| matches!(v.ele_type, EleType::Child))
.map(|v| {
let name = v.name.as_ref().expect("should have `name` for `child`");
quote! {#name}
});
let exact_tags = children_branches!(attrs, is_empty);
quote! {
#[allow(unused_assignments)]
impl #impl_generics ::xmlserde::XmlDeserialize for #ident #type_generics #where_clause {
Expand Down Expand Up @@ -85,6 +117,8 @@ pub fn get_de_enum_impl_block(container: Container) -> proc_macro2::TokenStream
fn __get_children_tags() -> Vec<&'static [u8]> {
vec![#(#children_tags,)*]
}

#text_function
}
}
}
Expand All @@ -102,7 +136,7 @@ pub fn get_de_struct_impl_block(container: Container) -> proc_macro2::TokenStrea
} = summary;
let get_children_tags = if children.len() > 0 {
let names = children.iter().map(|f| {
let n = f.name.as_ref().unwrap();
let n = f.name.as_ref().expect("should have name");
quote! {#n}
});
quote! {
Expand Down Expand Up @@ -409,7 +443,7 @@ fn text_match_branch(field: StructField) -> proc_macro2::TokenStream {
};
quote! {
Ok(Event::Text(__s)) => {
use xmlserde::{XmlValue, XmlDeserialize};
use ::xmlserde::{XmlValue, XmlDeserialize};
let __r = __s.unescape().unwrap();
match #t::deserialize(&__r) {
Ok(__v) => {
Expand All @@ -424,7 +458,39 @@ fn text_match_branch(field: StructField) -> proc_macro2::TokenStream {
}
}

fn untags_match_branch(fields: Vec<StructField>) -> proc_macro2::TokenStream {
fn untag_text_enum_branches(untags: Vec<StructField>) -> proc_macro2::TokenStream {
if untags.len() == 0 {
return quote! {};
}

let mut branches: Vec<proc_macro2::TokenStream> = vec![];
untags.into_iter().for_each(|f| {
let ident = f.original.ident.as_ref().unwrap();
let ty = &f.original.ty;
let branch = match f.generic {
Generic::Vec(ty) => quote! {
if let Some(t) = #ty::__deserialize_from_text(&_str) {
#ident.push(t);
}
},
Generic::Opt(ty) => quote! {
if let Some(t) = #ty::__deserialize_from_text(&_str) {
#ident = Some(t);
}
},
Generic::None => quote! {
if let Some(t) = #ty::__deserialize_from_text(&_str) {
#ident = Some(t);
}
},
};
branches.push(branch);
});

return quote! {#(#branches)*};
}

fn untags_match_branch(fields: &[StructField]) -> proc_macro2::TokenStream {
if fields.len() == 0 {
return quote! {};
}
Expand Down Expand Up @@ -468,7 +534,7 @@ fn children_match_branch(
if !matches!(f.ty, EleType::Child) {
panic!("")
}
let tag = f.name.as_ref().unwrap();
let tag = f.name.as_ref().expect("should have name");
let ident = f.original.ident.as_ref().unwrap();
let t = &f.original.ty;
let branch = match f.generic {
Expand Down Expand Up @@ -508,7 +574,8 @@ fn children_match_branch(
};
branches.push(branch);
});
let untags_branches = untags_match_branch(untags);
let untags_branches = untags_match_branch(&untags);
let untag_text_enum = untag_text_enum_branches(untags);
quote! {
Ok(Event::Empty(s)) => {
let is_empty = true;
Expand All @@ -526,5 +593,13 @@ fn children_match_branch(
_ => {},
}
}
Ok(Event::Text(t)) => {
use ::xmlserde::{XmlValue, XmlDeserialize};
let _str = t.unescape().expect("failed to unescape string");
let _str = _str.trim();
if _str != "" {
#untag_text_enum
}
}
}
}
42 changes: 26 additions & 16 deletions derives/src/ser.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use syn::DeriveInput;

use crate::container::{Container, Derive, FieldsSummary, Generic, StructField};
use crate::container::{Container, Derive, EleType, FieldsSummary, Generic, StructField};

pub fn get_ser_impl_block(input: DeriveInput) -> proc_macro2::TokenStream {
let container = Container::from_ast(&input, Derive::Serialize);
Expand All @@ -17,32 +17,42 @@ fn get_ser_enum_impl_block(container: Container) -> proc_macro2::TokenStream {
let (impl_generics, type_generics, where_clause) = container.original.generics.split_for_impl();
let branches = container.enum_variants.iter().map(|v| {
let f = v.ident;
let name = &v.name;
let ele_ty = &v.ele_type;
if v.ty.is_none() {
let name = v.name.as_ref().expect("should have name");
quote!{
Self::#f => {
if tag == b"" {
let _t = String::from_utf8_lossy(#name);
let _t = String::from_utf8_lossy(#name);
let _ = writer.write_event(Event::Empty(BytesStart::new(_t)));
} else {
let _ = writer.write_event(Event::Start(BytesStart::new(String::from_utf8_lossy(tag))));
let _t = String::from_utf8_lossy(#name);
let _t = String::from_utf8_lossy(#name);
let _ = writer.write_event(Event::Empty(BytesStart::new(_t)));
let _ = writer.write_event(Event::End(BytesEnd::new(String::from_utf8_lossy(tag))));
}
}
}
} else {
quote! {
Self::#f(c) => {
if tag == b"" {
c.serialize(#name, writer);
} else {
let _ = writer.write_event(Event::Start(BytesStart::new(String::from_utf8_lossy(tag))));
c.serialize(#name, writer);
let _ = writer.write_event(Event::End(BytesEnd::new(String::from_utf8_lossy(tag))));
if matches!(ele_ty, EleType::Text) {
quote!{
Self::#f(c) => {
let _ = writer.write_event(Event::Text(BytesText::new(&c.serialize())));
}
},
}
} else {
let name = v.name.as_ref().expect("should have hame");
quote! {
Self::#f(c) => {
if tag == b"" {
c.serialize(#name, writer);
} else {
let _ = writer.write_event(Event::Start(BytesStart::new(String::from_utf8_lossy(tag))));
c.serialize(#name, writer);
let _ = writer.write_event(Event::End(BytesEnd::new(String::from_utf8_lossy(tag))));
}
},
}
}
}
});
Expand Down Expand Up @@ -149,7 +159,7 @@ fn get_ser_struct_impl_block(container: Container) -> proc_macro2::TokenStream {
} else {
let write_scf = self_closed_children.into_iter().map(|f| {
let ident = f.original.ident.as_ref().unwrap();
let name = f.name.as_ref().unwrap();
let name = f.name.as_ref().expect("should have name");
quote! {
if self.#ident {
let event = BytesStart::new(String::from_utf8_lossy(#name));
Expand All @@ -162,14 +172,14 @@ fn get_ser_struct_impl_block(container: Container) -> proc_macro2::TokenStream {
quote! {}
} else {
let ident = f.original.ident.as_ref().unwrap();
let name = f.name.as_ref().unwrap();
let name = f.name.as_ref().expect("should have name");
quote! {
self.#ident.serialize(#name, writer);
}
}
});
let write_untags = untags.into_iter().map(|f| {
let ident = f.original.ident.as_ref().unwrap();
let ident = f.original.ident.as_ref().expect("should have name");
quote! {
self.#ident.serialize(b"", writer);
}
Expand Down
18 changes: 15 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,10 @@ macro_rules! xml_serde_enum {
};
}

use std::io::{BufRead, Write};
use std::{
fmt::Debug,
io::{BufRead, Write},
};

// We republic the `quick_xml` here is for helping the `derives` crate import
// it easily. In this way users don't need to import the `quick-xml` on
Expand Down Expand Up @@ -191,10 +194,18 @@ pub trait XmlDeserialize {
None
}

// Used when ty = `untag`.
// A helper function used when ty = `untag`. It could help
// us to find out the children tags when deserializing
fn __get_children_tags() -> Vec<&'static [u8]> {
vec![]
}

fn __deserialize_from_text(_: &str) -> Option<Self>
where
Self: Sized,
{
None
}
}

/// `Unparsed` keeps the XML struct and will be serialized to XML with nothing change.
Expand Down Expand Up @@ -307,7 +318,7 @@ where
{
let mut writer = quick_xml::Writer::new(Vec::new());
obj.serialize(T::ser_root().expect("Expect root"), &mut writer);
String::from_utf8(writer.into_inner()).unwrap()
String::from_utf8(writer.into_inner()).expect("decode error")
}

/// The entry for deserializing. `T` should have declared the `root` by `#[xmlserde(root=b"")]`
Expand Down Expand Up @@ -393,6 +404,7 @@ impl XmlValue for bool {
}

fn deserialize(s: &str) -> Result<Self, String> {
let s = s.to_ascii_lowercase();
if s == "1" || s == "true" {
Ok(true)
} else if s == "0" || s == "false" {
Expand Down
Loading

0 comments on commit 7579d55

Please sign in to comment.