diff --git a/strong-xml-derive/src/read/named.rs b/strong-xml-derive/src/read/named.rs index 59bf50f..2d26783 100644 --- a/strong-xml-derive/src/read/named.rs +++ b/strong-xml-derive/src/read/named.rs @@ -6,9 +6,19 @@ use crate::types::{Field, Type}; pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStream { let init_fields = fields.iter().map(|field| match field { - Field::Attribute { bind, ty, .. } - | Field::Child { bind, ty, .. } - | Field::FlattenText { bind, ty, .. } => init_value(bind, ty), + Field::Attribute { bind, ty, .. } => init_value(bind, ty, false), + Field::Child { + bind, + ty, + container_tag, + .. + } + | Field::FlattenText { + bind, + ty, + container_tag, + .. + } => init_value(bind, ty, container_tag.is_some()), Field::Text { bind, .. } => quote! { let #bind; }, }); @@ -19,12 +29,13 @@ pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStrea ty, default, .. - } - | Field::Child { + } => return_value(name, bind, ty, *default, false, &ele_name), + Field::Child { name, bind, ty, default, + container_tag, .. } | Field::FlattenText { @@ -32,9 +43,10 @@ pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStrea bind, ty, default, + container_tag, .. - } => return_value(name, bind, ty, *default, &ele_name), - Field::Text { name, bind, ty, .. } => return_value(name, bind, ty, false, &ele_name), + } => return_value(name, bind, ty, *default, container_tag.is_some(), &ele_name), + Field::Text { name, bind, ty, .. } => return_value(name, bind, ty, false, false, &ele_name), }); let read_attr_fields = fields.iter().filter_map(|field| match field { @@ -54,8 +66,16 @@ pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStrea ty, tags, name, + container_tag, .. - } => Some(read_children(tags, bind, name, ty, &ele_name)), + } => Some(read_children( + tags, + bind, + name, + ty, + container_tag.as_ref(), + &ele_name, + )), _ => None, }); @@ -65,8 +85,16 @@ pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStrea ty, tag, name, + container_tag, .. - } => Some(read_flatten_text(tag, bind, name, ty, &ele_name)), + } => Some(read_flatten_text( + tag, + bind, + name, + ty, + container_tag.as_ref(), + &ele_name, + )), _ => None, }); @@ -137,8 +165,8 @@ pub fn read(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStrea } } -fn init_value(name: &Ident, ty: &Type) -> TokenStream { - if ty.is_vec() { +fn init_value(name: &Ident, ty: &Type, is_container: bool) -> TokenStream { + if ty.is_vec() && !is_container { quote! { let mut #name = Vec::new(); } } else { quote! { let mut #name = None; } @@ -150,9 +178,10 @@ fn return_value( bind: &Ident, ty: &Type, default: bool, + is_container: bool, ele_name: &TokenStream, ) -> TokenStream { - if ty.is_vec() || ty.is_option() { + if (ty.is_vec() && !is_container) || ty.is_option() { quote! { #name: #bind } } else if default { quote! { #name: #bind.unwrap_or_default() } @@ -218,20 +247,72 @@ fn read_children( bind: &Ident, name: &TokenStream, ty: &Type, + container_tag: Option<&LitStr>, ele_name: &TokenStream, ) -> TokenStream { - let from_reader = match &ty { - Type::VecT(ty) => quote! { - #bind.push(<#ty as strong_xml::XmlRead>::from_reader(reader)?); - }, - Type::OptionT(ty) | Type::T(ty) => quote! { - #bind = Some(<#ty as strong_xml::XmlRead>::from_reader(reader)?); - }, - _ => panic!("`child` attribute only supports Vec, Option and T."), + let match_tags = match container_tag { + Some(container_tag) => quote! { #container_tag }, + None => quote! { #( #tags )|* }, + }; + + let from_reader = if let Some(container_tag) = container_tag { + let inner_reader = match &ty { + Type::VecT(ty) | Type::OptionVecT(ty) => quote! { + #bind.as_mut().unwrap().push(<#ty as strong_xml::XmlRead>::from_reader(reader)?); + }, + _ => panic!("`child` + `container` attribute only supports Vec and Option>."), + }; + + quote! { + reader.next().unwrap()?; + + if #bind.is_some() { + return Err(XmlError::DuplicateField { + field: #container_tag.to_owned(), + }); + } + + while let Some(_) = reader.find_attribute()? {}; + + #bind = Some(Vec::new()); + if let Token::ElementEnd { end: ElementEnd::Open, .. } = reader.next().unwrap()? { + while let Some(__tag) = reader.find_element_start(Some(#container_tag))? { + match __tag { + #( #tags )|* => { + strong_xml::log_start_reading_field!(#ele_name, #name); + #inner_reader + strong_xml::log_finish_reading_field!(#ele_name, #name); + } + tag => { + strong_xml::log_skip_element!(#ele_name, tag); + // skip the start tag + reader.next(); + reader.read_to_end(tag)?; + }, + } + } + } + + } + } else { + match &ty { + Type::VecT(ty) => quote! { + #bind.push(<#ty as strong_xml::XmlRead>::from_reader(reader)?); + }, + Type::OptionT(ty) | Type::T(ty) => quote! { + if #bind.is_some() { + return Err(XmlError::DuplicateField { + field: stringify!(#( #tags )|*).to_owned(), + }); + } + #bind = Some(<#ty as strong_xml::XmlRead>::from_reader(reader)?); + }, + _ => panic!("`child` attribute only supports Vec, Option and T."), + } }; quote! { - #( #tags )|* => { + #match_tags => { strong_xml::log_start_reading_field!(#ele_name, #name); #from_reader @@ -246,24 +327,64 @@ fn read_flatten_text( bind: &Ident, name: &TokenStream, ty: &Type, + container_tag: Option<&LitStr>, ele_name: &TokenStream, ) -> TokenStream { + let match_tag = container_tag.unwrap_or(tag); + let from_str = from_str(ty); - let read_text = if ty.is_vec() { + let read_text = if let Some(container_tag) = container_tag { + quote! { + if #bind.is_some() { + return Err(XmlError::DuplicateField { + field: #container_tag.to_owned(), + }); + } + + while let Some(_) = reader.find_attribute()? {}; + + #bind = Some(Vec::new()); + if let Token::ElementEnd { end: ElementEnd::Open, .. } = reader.next().unwrap()? { + while let Some(__tag) = reader.find_element_start(Some(#container_tag))? { + match __tag { + #tag => { + strong_xml::log_start_reading_field!(#ele_name, #name); + reader.next().unwrap()?; + let __value = reader.read_text(#tag)?; + #bind.as_mut().unwrap().push(#from_str); + strong_xml::log_finish_reading_field!(#ele_name, #name); + } + tag => { + strong_xml::log_skip_element!(#ele_name, tag); + // skip the start tag + reader.next(); + reader.read_to_end(tag)?; + }, + } + } + } + + } + } else if ty.is_vec() { quote! { let __value = reader.read_text(#tag)?; #bind.push(#from_str); } } else { quote! { + if #bind.is_some() { + return Err(XmlError::DuplicateField { + field: #tag.to_owned(), + }); + } let __value = reader.read_text(#tag)?; #bind = Some(#from_str); } }; quote! { - #tag => { + #match_tag => { // skip element start reader.next(); @@ -278,15 +399,17 @@ fn read_flatten_text( fn from_str(ty: &Type) -> TokenStream { match &ty { - Type::CowStr | Type::OptionCowStr | Type::VecCowStr => quote! { __value }, - Type::Bool | Type::OptionBool | Type::VecBool => quote! { + Type::CowStr | Type::OptionCowStr | Type::VecCowStr | Type::OptionVecCowStr => { + quote! { __value } + } + Type::Bool | Type::OptionBool | Type::VecBool | Type::OptionVecBool => quote! { match &*__value { "t" | "true" | "y" | "yes" | "on" | "1" => true, "f" | "false" | "n" | "no" | "off" | "0" => false, _ => ::from_str(&__value).map_err(|e| XmlError::FromStr(e.into()))? } }, - Type::T(ty) | Type::OptionT(ty) | Type::VecT(ty) => quote! { + Type::T(ty) | Type::OptionT(ty) | Type::VecT(ty) | Type::OptionVecT(ty) => quote! { <#ty as std::str::FromStr>::from_str(&__value).map_err(|e| XmlError::FromStr(e.into()))? }, } diff --git a/strong-xml-derive/src/types.rs b/strong-xml-derive/src/types.rs index 0252f9e..c1a7f2e 100644 --- a/strong-xml-derive/src/types.rs +++ b/strong-xml-derive/src/types.rs @@ -82,6 +82,7 @@ pub enum Field { ty: Type, default: bool, tags: Vec, + container_tag: Option, }, /// Text Field /// @@ -112,6 +113,7 @@ pub enum Field { default: bool, tag: LitStr, is_cdata: bool, + container_tag: Option, }, } @@ -134,6 +136,12 @@ pub enum Type { VecBool, // Option OptionBool, + // Option> + OptionVecT(syn::Type), + // Option> + OptionVecBool, + // Option>> + OptionVecCowStr, } impl Element { @@ -236,6 +244,7 @@ impl Field { let mut child_tags = Vec::new(); let mut is_text = false; let mut flatten_text_tag = None; + let mut container_tag = None; let mut is_cdata = false; for meta in field.attrs.into_iter().filter_map(get_xml_meta).flatten() { @@ -251,14 +260,6 @@ impl Field { if let Str(lit) = m.lit { if attr_tag.is_some() { panic!("Duplicate `attr` attribute."); - } else if is_text { - panic!("`attr` attribute and `text` attribute is disjoint."); - } else if is_cdata { - panic!("`attr` attribute and `cdata` attribute is disjoint.") - } else if !child_tags.is_empty() { - panic!("`attr` attribute and `child` attribute is disjoint."); - } else if flatten_text_tag.is_some() { - panic!("`attr` attribute and `flatten_text` attribute is disjoint."); } else { attr_tag = Some(lit); } @@ -269,12 +270,6 @@ impl Field { NestedMeta::Meta(Path(ref p)) if p.is_ident("text") => { if is_text { panic!("Duplicate `text` attribute."); - } else if attr_tag.is_some() { - panic!("`text` attribute and `attr` attribute is disjoint."); - } else if !child_tags.is_empty() { - panic!("`text` attribute and `child` attribute is disjoint."); - } else if flatten_text_tag.is_some() { - panic!("`text` attribute and `flatten_text` attribute is disjoint."); } else { is_text = true; } @@ -282,10 +277,6 @@ impl Field { NestedMeta::Meta(Path(ref p)) if p.is_ident("cdata") => { if is_cdata { panic!("Duplicate `cdata` attribute."); - } else if attr_tag.is_some() { - panic!("`text` attribute and `attr` attribute is disjoint."); - } else if !child_tags.is_empty() { - panic!("`text` attribute and `child` attribute is disjoint."); } else { is_cdata = true; } @@ -294,12 +285,6 @@ impl Field { if let Str(lit) = m.lit { if is_text { panic!("`child` attribute and `text` attribute is disjoint."); - } else if attr_tag.is_some() { - panic!("`child` attribute and `attr` attribute is disjoint."); - } else if is_cdata { - panic!("`child` attribute and `cdata` attribute is disjoint.") - } else if flatten_text_tag.is_some() { - panic!("`child` attribute and `flatten_text` attribute is disjoint."); } else { child_tags.push(lit); } @@ -309,13 +294,7 @@ impl Field { } NestedMeta::Meta(NameValue(m)) if m.path.is_ident("flatten_text") => { if let Str(lit) = m.lit { - if is_text { - panic!("`flatten_text` attribute and `text` attribute is disjoint."); - } else if !child_tags.is_empty() { - panic!("`flatten_text` attribute and `child` attribute is disjoint."); - } else if attr_tag.is_some() { - panic!("`flatten_text` attribute and `attr` attribute is disjoint."); - } else if flatten_text_tag.is_some() { + if flatten_text_tag.is_some() { panic!("Duplicate `flatten_text` attribute."); } else { flatten_text_tag = Some(lit); @@ -324,11 +303,33 @@ impl Field { panic!("Expected a string literal."); } } + NestedMeta::Meta(NameValue(m)) if m.path.is_ident("container") => { + if let Str(lit) = m.lit { + if container_tag.is_some() { + panic!("Duplicate `container` attribute."); + } else { + container_tag = Some(lit); + } + } else { + panic!("Expected a string literal."); + } + } _ => (), } } if let Some(tag) = attr_tag { + if is_text { + panic!("`attr` attribute and `text` attribute is disjoint."); + } else if is_cdata { + panic!("`attr` attribute and `cdata` attribute is disjoint.") + } else if !child_tags.is_empty() { + panic!("`attr` attribute and `child` attribute is disjoint."); + } else if flatten_text_tag.is_some() { + panic!("`attr` attribute and `flatten_text` attribute is disjoint."); + } else if container_tag.is_some() { + panic!("`attr` attribute and `container` attribute is disjoint."); + } Field::Attribute { name, bind, @@ -337,14 +338,29 @@ impl Field { default, } } else if !child_tags.is_empty() { + if attr_tag.is_some() { + panic!("`child` attribute and `attr` attribute is disjoint."); + } else if is_cdata { + panic!("`child` attribute and `cdata` attribute is disjoint.") + } else if flatten_text_tag.is_some() { + panic!("`child` attribute and `flatten_text` attribute is disjoint."); + } Field::Child { name, bind, ty: Type::parse(field.ty), default, tags: child_tags, + container_tag, } } else if is_text { + if !child_tags.is_empty() { + panic!("`text` attribute and `child` attribute is disjoint."); + } else if flatten_text_tag.is_some() { + panic!("`text` attribute and `flatten_text` attribute is disjoint."); + } else if container_tag.is_some() { + panic!("`text` attribute and `container` attribute is disjoint."); + } Field::Text { name, bind, @@ -352,6 +368,13 @@ impl Field { is_cdata, } } else if let Some(tag) = flatten_text_tag { + if !child_tags.is_empty() { + panic!("`flatten_text` attribute and `child` attribute is disjoint."); + } else if attr_tag.is_some() { + panic!("`flatten_text` attribute and `attr` attribute is disjoint."); + } else if is_text { + panic!("`flatten_text` attribute and `text` attribute is disjoint."); + } Field::FlattenText { name, bind, @@ -359,6 +382,7 @@ impl Field { default, tag, is_cdata, + container_tag, } } else { panic!("Field should have one of `attr`, `child`, `text` or `flatten_text` attribute."); @@ -370,7 +394,12 @@ impl Type { pub fn is_option(&self) -> bool { matches!( self, - Type::OptionCowStr | Type::OptionT(_) | Type::OptionBool + Type::OptionCowStr + | Type::OptionT(_) + | Type::OptionBool + | Type::OptionVecT(_) + | Type::OptionVecCowStr + | Type::OptionVecBool ) } @@ -461,6 +490,14 @@ impl Type { Type::OptionCowStr } else if is_bool(&ty) { Type::OptionBool + } else if let Some(ty) = is_vec(&ty) { + if is_cow_str(&ty) { + Type::OptionVecCowStr + } else if is_bool(&ty) { + Type::OptionVecBool + } else { + Type::OptionVecT(ty.clone()) + } } else { Type::OptionT(ty.clone()) } diff --git a/strong-xml-derive/src/write/named.rs b/strong-xml-derive/src/write/named.rs index 9d9fa00..46c1b13 100644 --- a/strong-xml-derive/src/write/named.rs +++ b/strong-xml-derive/src/write/named.rs @@ -23,13 +23,26 @@ pub fn write(tag: &LitStr, ele_name: TokenStream, fields: &[Field]) -> TokenStre bind, ty, is_cdata, + container_tag, .. - } => Some(write_flatten_text(tag, bind, ty, &ele_name, *is_cdata)), + } => Some(write_flatten_text( + tag, + bind, + ty, + container_tag.as_ref(), + &ele_name, + *is_cdata, + )), _ => None, }); let write_child = fields.iter().filter_map(|field| match field { - Field::Child { bind, ty, .. } => Some(write_child(bind, ty, &ele_name)), + Field::Child { + bind, + ty, + container_tag, + .. + } => Some(write_child(bind, ty, container_tag.as_ref(), &ele_name)), _ => None, }); @@ -116,34 +129,59 @@ fn write_attrs(tag: &LitStr, name: &Ident, ty: &Type, ele_name: &TokenStream) -> } } -fn write_child(name: &Ident, ty: &Type, ele_name: &TokenStream) -> TokenStream { - match ty { - Type::OptionT(_) => quote! { - strong_xml::log_start_writing_field!(#ele_name, #name); - - if let Some(ref ele) = #name { - ele.to_writer(&mut writer)?; - } - - strong_xml::log_finish_writing_field!(#ele_name, #name); - }, - Type::VecT(_) => quote! { - strong_xml::log_start_writing_field!(#ele_name, #name); - - for ele in #name { - ele.to_writer(&mut writer)?; - } +fn write_child( + name: &Ident, + ty: &Type, + container_tag: Option<&LitStr>, + ele_name: &TokenStream, +) -> TokenStream { + let write = if let Some(tag) = container_tag { + match ty { + Type::OptionVecT(_) => quote! { + if let Some(ref ele) = #name { + writer.write_element_start(#tag)?; + writer.write_element_end_open()?; + for ele in ele { + ele.to_writer(&mut writer)?; + } + writer.write_element_end_close(#tag)?; + } + }, + Type::VecT(_) => quote! { + writer.write_element_start(#tag)?; + writer.write_element_end_open()?; + for ele in #name { + ele.to_writer(&mut writer)?; + } + writer.write_element_end_close(#tag)?; + }, + _ => panic!("`child` + `container` attribute only supports Vec and Option>."), + } + } else { + match ty { + Type::OptionT(_) => quote! { + if let Some(ref ele) = #name { + ele.to_writer(&mut writer)?; + } + }, + Type::VecT(_) => quote! { + for ele in #name { + ele.to_writer(&mut writer)?; + } + }, + Type::T(_) => quote! { + &#name.to_writer(&mut writer)?; + }, + _ => panic!("`child` attribute only supports Vec, Option and T."), + } + }; - strong_xml::log_finish_writing_field!(#ele_name, #name); - }, - Type::T(_) => quote! { - strong_xml::log_start_writing_field!(#ele_name, #name); + quote! { + strong_xml::log_start_writing_field!(#ele_name, #name); - &#name.to_writer(&mut writer)?; + #write - strong_xml::log_finish_writing_field!(#ele_name, #name); - }, - _ => panic!("`child` attribute only supports Vec, Option and T."), + strong_xml::log_finish_writing_field!(#ele_name, #name); } } @@ -180,55 +218,80 @@ fn write_flatten_text( tag: &LitStr, name: &Ident, ty: &Type, + container_tag: Option<&LitStr>, ele_name: &TokenStream, is_cdata: bool, ) -> TokenStream { - let to_str = to_str(ty); - - if ty.is_vec() { - quote! { - strong_xml::log_finish_writing_field!(#ele_name, #name); - - for __value in #name { - writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + let write = if let Some(container_tag) = container_tag { + let to_str = to_str(ty); + if ty.is_option() { + quote! { + if let Some(__value) = #name { + writer.write_element_start(#container_tag)?; + writer.write_element_end_open()?; + for __value in __value { + writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + } + writer.write_element_end_close(#container_tag)?; + } } - - strong_xml::log_finish_writing_field!(#ele_name, #name); + } else if ty.is_vec() { + quote! { + writer.write_element_start(#container_tag)?; + writer.write_element_end_open()?; + for __value in #name { + writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + } + writer.write_element_end_close(#container_tag)?; + } + } else { + panic!( + "`flatten_text` + `container` attribute only supports Vec and Option>." + ) } - } else if ty.is_option() { - quote! { - strong_xml::log_finish_writing_field!(#ele_name, #name); - - if let Some(__value) = #name { + } else { + let to_str = to_str(ty); + if ty.is_vec() { + quote! { + for __value in #name { + writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + } + } + } else if ty.is_option() { + quote! { + if let Some(__value) = #name { + writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + } + } + } else { + quote! { + let __value = &#name; writer.write_flatten_text(#tag, #to_str, #is_cdata)?; } - - strong_xml::log_finish_writing_field!(#ele_name, #name); } - } else { - quote! { - strong_xml::log_finish_writing_field!(#ele_name, #name); + }; + + quote! { + strong_xml::log_finish_writing_field!(#ele_name, #name); - let __value = &#name; - writer.write_flatten_text(#tag, #to_str, #is_cdata)?; + #write - strong_xml::log_finish_writing_field!(#ele_name, #name); - } + strong_xml::log_finish_writing_field!(#ele_name, #name); } } fn to_str(ty: &Type) -> TokenStream { match &ty { - Type::CowStr | Type::OptionCowStr | Type::VecCowStr => { + Type::CowStr | Type::OptionCowStr | Type::VecCowStr | Type::OptionVecCowStr => { quote! { __value } } - Type::Bool | Type::OptionBool | Type::VecBool => quote! { + Type::Bool | Type::OptionBool | Type::VecBool | Type::OptionVecBool => quote! { match __value { true => "true", false => "false" } }, - Type::T(_) | Type::OptionT(_) | Type::VecT(_) => { + Type::T(_) | Type::OptionT(_) | Type::VecT(_) | Type::OptionVecT(_) => { quote! { &format!("{}", __value) } } } diff --git a/strong-xml/README.md b/strong-xml/README.md index f0113f6..60195ca 100644 --- a/strong-xml/README.md +++ b/strong-xml/README.md @@ -293,6 +293,21 @@ assert_eq!( ); ``` +#### `#[xml(container)]` + +`flatten_text` and `child` can be embedded in a container tag. + +```rust +#[derive(XmlRead, Debug, Clone)] +#[xml(tag = "root")] +struct Root { + #[xml(container = "cont", flatten_text = "sub", default)] + sub_values: Vec, +} +``` + +This will parse `123`. + ### License MIT diff --git a/strong-xml/src/xml_error.rs b/strong-xml/src/xml_error.rs index fe7a0bd..f891350 100644 --- a/strong-xml/src/xml_error.rs +++ b/strong-xml/src/xml_error.rs @@ -12,6 +12,7 @@ pub enum XmlError { MissingField { name: String, field: String }, UnterminatedEntity { entity: String }, UnrecognizedSymbol { symbol: String }, + DuplicateField { field: String }, FromStr(Box), } @@ -74,6 +75,7 @@ impl std::fmt::Display for XmlError { } UnterminatedEntity { entity } => write!(f, "unterminated XML entity: {}", entity), UnrecognizedSymbol { symbol } => write!(f, "unrecognized XML symbol: {}", symbol), + DuplicateField { field } => write!(f, "unexpected duplicate field: {}", field), FromStr(e) => write!(f, "error parsing XML value: {}", e), } } diff --git a/test-suite/tests/containers.rs b/test-suite/tests/containers.rs new file mode 100644 index 0000000..52d4b9f --- /dev/null +++ b/test-suite/tests/containers.rs @@ -0,0 +1,160 @@ +use std::borrow::Cow; +use strong_xml::{XmlRead, XmlResult, XmlWrite}; + +#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[xml(tag = "tag1")] +struct Tag1<'a> { + #[xml(attr = "att1")] + att1: Option>, + #[xml(text)] + content: Cow<'a, str>, +} + +#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[xml(tag = "tag2")] +struct Tag2<'a> { + #[xml(child = "tag1", container = "container", default)] + tag1: Vec>, +} + +#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[xml(tag = "tag2")] +struct Tag2Opt<'a> { + #[xml(child = "tag1", container = "container")] + tag1: Option>>, +} + +#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[xml(tag = "tag3")] +struct Tag3 { + #[xml(flatten_text = "value", container = "container", default)] + data: Vec, +} + +#[derive(XmlWrite, XmlRead, PartialEq, Debug)] +#[xml(tag = "tag3")] +struct Tag3Opt { + #[xml(flatten_text = "value", container = "container")] + data: Option>, +} + +#[test] +fn test() -> XmlResult<()> { + let _ = env_logger::builder() + .is_test(true) + .format_timestamp(None) + .try_init(); + + assert_eq!( + Tag2::from_str( + r#"foobar"# + )?, + Tag2 { + tag1: vec![ + Tag1 { + att1: Some("att1".into()), + content: "foo".into(), + }, + Tag1 { + att1: None, + content: "bar".into(), + } + ], + } + ); + + assert_eq!( + Tag3::from_str( + r#"1230"# + )?, + Tag3 { data: vec![123, 0] } + ); + + assert_eq!( + Tag2 { + tag1: vec![ + Tag1 { + att1: Some("att1".into()), + content: "foo".into(), + }, + Tag1 { + att1: None, + content: "bar".into(), + } + ], + } + .to_string()?, + r#"foobar"#, + ); + + assert_eq!( + Tag3 { data: vec![123, 0] }.to_string()?, + r#"1230"#, + ); + + Ok(()) +} + +#[test] +fn test_empty() -> XmlResult<()> { + let _ = env_logger::builder() + .is_test(true) + .format_timestamp(None) + .try_init(); + + assert_eq!(Tag2::from_str(r#""#)?, Tag2 { tag1: vec![] }); + + assert_eq!( + Tag2::from_str(r#""#)?, + Tag2 { tag1: vec![] } + ); + + assert_eq!( + Tag2::from_str(r#""#)?, + Tag2 { tag1: vec![] } + ); + + assert_eq!( + Tag2Opt::from_str(r#""#)?, + Tag2Opt { tag1: None } + ); + + assert_eq!( + Tag2Opt::from_str(r#""#)?, + Tag2Opt { tag1: Some(vec![]) } + ); + + assert_eq!( + Tag2Opt::from_str(r#""#)?, + Tag2Opt { tag1: Some(vec![]) } + ); + + assert_eq!(Tag3::from_str(r#""#)?, Tag3 { data: vec![] }); + + assert_eq!( + Tag3::from_str(r#""#)?, + Tag3 { data: vec![] } + ); + + assert_eq!( + Tag3::from_str(r#""#)?, + Tag3 { data: vec![] } + ); + + assert_eq!( + Tag3Opt::from_str(r#""#)?, + Tag3Opt { data: None } + ); + + assert_eq!( + Tag3Opt::from_str(r#""#)?, + Tag3Opt { data: Some(vec![]) } + ); + + assert_eq!( + Tag3Opt::from_str(r#""#)?, + Tag3Opt { data: Some(vec![]) } + ); + + Ok(()) +} diff --git a/test-suite/tests/struct.rs b/test-suite/tests/struct.rs index c70d1b8..ce79048 100644 --- a/test-suite/tests/struct.rs +++ b/test-suite/tests/struct.rs @@ -92,3 +92,23 @@ fn test() -> XmlResult<()> { Ok(()) } + +#[test] +fn test_duplicate() -> XmlResult<()> { + let _ = env_logger::builder() + .is_test(true) + .format_timestamp(None) + .try_init(); + + assert!(Tag3::from_str( + r#"content1content2"# + ) + .is_err()); + + assert!(Tag3::from_str( + r#"content1content2"# + ) + .is_err()); + + Ok(()) +}