From 5c8182f9712dfb540553b8064a929a0ad9a35e5d Mon Sep 17 00:00:00 2001 From: marcelbuesing Date: Tue, 21 Dec 2021 13:38:12 +0100 Subject: [PATCH 1/5] initial choice type support --- xml_schema/tests/choice.rs | 41 ++++++++++++++ xml_schema/tests/choice.xsd | 25 +++++++++ xml_schema_derive/src/xsd/choice.rs | 68 +++++++++++++++++++++++ xml_schema_derive/src/xsd/complex_type.rs | 19 ++++++- xml_schema_derive/src/xsd/element.rs | 3 +- xml_schema_derive/src/xsd/extension.rs | 4 +- xml_schema_derive/src/xsd/mod.rs | 1 + xml_schema_derive/src/xsd/sequence.rs | 4 +- 8 files changed, 159 insertions(+), 6 deletions(-) create mode 100644 xml_schema/tests/choice.rs create mode 100644 xml_schema/tests/choice.xsd create mode 100644 xml_schema_derive/src/xsd/choice.rs diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs new file mode 100644 index 0000000..17280fb --- /dev/null +++ b/xml_schema/tests/choice.rs @@ -0,0 +1,41 @@ +#[macro_use] +extern crate yaserde_derive; + +use log::debug; +use std::io::prelude::*; +use xml_schema_derive::XmlSchema; +use yaserde::de::from_str; +use yaserde::ser::to_string; +use yaserde::{YaDeserialize, YaSerialize}; + +#[test] +fn choice() { + #[derive(Debug, XmlSchema)] + #[xml_schema(source = "xml_schema/tests/choice.xsd")] + struct ChoiceTypeSchema; + + let xml_1 = r#" + + + John + + "#; + + let sample_1: Parent = from_str(xml_1).unwrap(); + + let model = Parent { + x_firstname: Some(Firstname { + content: "John".to_string(), + scope: None, + }), + x_lastname: None, + }; + + assert_eq!(sample_1, model); + + let data = to_string(&model).unwrap(); + assert_eq!( + data, + r#"John"# + ); +} diff --git a/xml_schema/tests/choice.xsd b/xml_schema/tests/choice.xsd new file mode 100644 index 0000000..9b126fd --- /dev/null +++ b/xml_schema/tests/choice.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xml_schema_derive/src/xsd/choice.rs b/xml_schema_derive/src/xsd/choice.rs new file mode 100644 index 0000000..4121768 --- /dev/null +++ b/xml_schema_derive/src/xsd/choice.rs @@ -0,0 +1,68 @@ +//! The children of a choice are mapped to Option fields. +//! Generating an enum would have been the better way but the choice element +//! may not have a name, so it's impossible to name the generated Rust enum. +//! The enum would have been nice to avoid runtime checks that only a single choice element is used. + +use crate::xsd::{ + annotation::Annotation, attribute::Attribute, element::Element, max_occurences::MaxOccurences, + Implementation, XsdContext, +}; +use log::{debug, info}; +use proc_macro2::TokenStream; +use std::io::prelude::*; +use yaserde::YaDeserialize; + +#[derive(Clone, Default, Debug, PartialEq, YaDeserialize)] +#[yaserde( + rename = "choice" + prefix = "xs", + namespace = "xs: http://www.w3.org/2001/XMLSchema" +)] +pub struct Choice { + #[yaserde(attribute)] + pub id: Option, + #[yaserde(rename = "attribute")] + pub attributes: Vec, + #[yaserde(rename = "minOccurs", attribute)] + pub min_occurences: Option, + #[yaserde(rename = "maxOccurs", attribute)] + pub max_occurences: Option, + #[yaserde(rename = "annotation")] + pub annotation: Option, + #[yaserde(rename = "element")] + pub element: Vec, +} + +impl Implementation for Choice { + fn implement( + &self, + namespace_definition: &TokenStream, + prefix: &Option, + context: &XsdContext, + ) -> TokenStream { + let elements: TokenStream = self + .element + .iter() + .map(|element| element.implement(&namespace_definition, prefix, context)) + .collect(); + + quote! { + #elements + } + } +} + +impl Choice { + pub fn get_field_implementation( + &self, + context: &XsdContext, + prefix: &Option, + ) -> TokenStream { + info!("Generate choice elements"); + self + .element + .iter() + .map(|element| element.get_field_implementation(context, prefix, false, true)) + .collect() + } +} diff --git a/xml_schema_derive/src/xsd/complex_type.rs b/xml_schema_derive/src/xsd/complex_type.rs index 162e0dd..c13c059 100644 --- a/xml_schema_derive/src/xsd/complex_type.rs +++ b/xml_schema_derive/src/xsd/complex_type.rs @@ -1,5 +1,5 @@ use crate::xsd::{ - annotation::Annotation, attribute::Attribute, complex_content::ComplexContent, + annotation::Annotation, attribute::Attribute, choice::Choice, complex_content::ComplexContent, sequence::Sequence, simple_content::SimpleContent, Implementation, XsdContext, }; use heck::CamelCase; @@ -27,6 +27,8 @@ pub struct ComplexType { pub complex_content: Option, #[yaserde(rename = "annotation")] pub annotation: Option, + #[yaserde(rename = "choice")] + pub choice: Option, } impl Implementation for ComplexType { @@ -84,6 +86,18 @@ impl Implementation for ComplexType { .map(|annotation| annotation.implement(&namespace_definition, prefix, context)) .unwrap_or_else(TokenStream::new); + let choice = self + .choice + .as_ref() + .map(|choice| choice.implement(&namespace_definition, prefix, context)) + .unwrap_or_else(TokenStream::new); + + let choice_field = self + .choice + .as_ref() + .map(|choice| choice.get_field_implementation(context, prefix)) + .unwrap_or_else(TokenStream::new); + quote! { #docs @@ -93,10 +107,13 @@ impl Implementation for ComplexType { #sequence #simple_content #complex_content + #choice_field #attributes } #sub_types_implementation + + #choice } } } diff --git a/xml_schema_derive/src/xsd/element.rs b/xml_schema_derive/src/xsd/element.rs index afd53b3..09f2673 100644 --- a/xml_schema_derive/src/xsd/element.rs +++ b/xml_schema_derive/src/xsd/element.rs @@ -106,6 +106,7 @@ impl Element { context: &XsdContext, prefix: &Option, multiple: bool, + optional: bool, ) -> TokenStream { if self.name == "" { return quote!(); @@ -143,7 +144,7 @@ impl Element { rust_type }; - let rust_type = if self.min_occurences == Some(0) { + let rust_type = if optional || self.min_occurences == Some(0) { quote!(Option<#rust_type>) } else { rust_type diff --git a/xml_schema_derive/src/xsd/extension.rs b/xml_schema_derive/src/xsd/extension.rs index 0e4db29..82abf17 100644 --- a/xml_schema_derive/src/xsd/extension.rs +++ b/xml_schema_derive/src/xsd/extension.rs @@ -1,6 +1,6 @@ use crate::xsd::{ - attribute::Attribute, rust_types_mapping::RustTypesMapping, sequence::Sequence, Implementation, - XsdContext, + attribute::Attribute, choice::Choice, rust_types_mapping::RustTypesMapping, sequence::Sequence, + Implementation, XsdContext, }; use log::debug; use proc_macro2::TokenStream; diff --git a/xml_schema_derive/src/xsd/mod.rs b/xml_schema_derive/src/xsd/mod.rs index 59696ac..53ef46e 100644 --- a/xml_schema_derive/src/xsd/mod.rs +++ b/xml_schema_derive/src/xsd/mod.rs @@ -1,6 +1,7 @@ mod annotation; mod attribute; mod attribute_group; +mod choice; mod complex_content; mod complex_type; mod element; diff --git a/xml_schema_derive/src/xsd/sequence.rs b/xml_schema_derive/src/xsd/sequence.rs index 110e583..9496783 100644 --- a/xml_schema_derive/src/xsd/sequence.rs +++ b/xml_schema_derive/src/xsd/sequence.rs @@ -22,7 +22,7 @@ impl Implementation for Sequence { self .elements .iter() - .map(|element| element.get_field_implementation(context, prefix, false)) + .map(|element| element.get_field_implementation(context, prefix, false, false)) .collect() } } @@ -50,7 +50,7 @@ impl Sequence { self .elements .iter() - .map(|element| element.get_field_implementation(context, prefix, true)) + .map(|element| element.get_field_implementation(context, prefix, true, false)) .collect() } } From 89c2fc8bbf717a56e48ced09cb548e9e4dd1fcfe Mon Sep 17 00:00:00 2001 From: marcelbuesing Date: Tue, 21 Dec 2021 20:39:07 +0100 Subject: [PATCH 2/5] support choice in sequences --- xml_schema/tests/choice.rs | 34 +++++++++++++++++++++ xml_schema/tests/choice_sequence.xsd | 28 ++++++++++++++++++ xml_schema_derive/src/xsd/complex_type.rs | 1 + xml_schema_derive/src/xsd/extension.rs | 2 ++ xml_schema_derive/src/xsd/sequence.rs | 36 +++++++++++++++++++---- 5 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 xml_schema/tests/choice_sequence.xsd diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs index 17280fb..9b7bf27 100644 --- a/xml_schema/tests/choice.rs +++ b/xml_schema/tests/choice.rs @@ -39,3 +39,37 @@ fn choice() { r#"John"# ); } + +#[test] +fn choice_sequence() { + #[derive(Debug, XmlSchema)] + #[xml_schema(source = "xml_schema/tests/choice_sequence.xsd")] + struct ChoiceTypeSchema; + + let xml_1 = r#" + + + Doe + John + + "#; + + let sample_1: Parent = from_str(xml_1).unwrap(); + + let model = Parent { + name: "Doe".to_string(), + x_firstname: Some(Firstname { + content: "John".to_string(), + scope: None, + }), + x_lastname: None, + }; + + assert_eq!(sample_1, model); + + let data = to_string(&model).unwrap(); + assert_eq!( + data, + r#"DoeJohn"# + ); +} diff --git a/xml_schema/tests/choice_sequence.xsd b/xml_schema/tests/choice_sequence.xsd new file mode 100644 index 0000000..7eabaa8 --- /dev/null +++ b/xml_schema/tests/choice_sequence.xsd @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/xml_schema_derive/src/xsd/complex_type.rs b/xml_schema_derive/src/xsd/complex_type.rs index c13c059..1775fe7 100644 --- a/xml_schema_derive/src/xsd/complex_type.rs +++ b/xml_schema_derive/src/xsd/complex_type.rs @@ -20,6 +20,7 @@ pub struct ComplexType { pub name: String, #[yaserde(rename = "attribute")] pub attributes: Vec, + #[yaserde(rename = "sequence")] pub sequence: Option, #[yaserde(rename = "simpleContent")] pub simple_content: Option, diff --git a/xml_schema_derive/src/xsd/extension.rs b/xml_schema_derive/src/xsd/extension.rs index 82abf17..8c4cd58 100644 --- a/xml_schema_derive/src/xsd/extension.rs +++ b/xml_schema_derive/src/xsd/extension.rs @@ -20,6 +20,8 @@ pub struct Extension { pub attributes: Vec, #[yaserde(rename = "sequence")] pub sequences: Vec, + #[yaserde(rename = "choice")] + pub choices: Vec, } impl Implementation for Extension { diff --git a/xml_schema_derive/src/xsd/sequence.rs b/xml_schema_derive/src/xsd/sequence.rs index 9496783..53ec805 100644 --- a/xml_schema_derive/src/xsd/sequence.rs +++ b/xml_schema_derive/src/xsd/sequence.rs @@ -1,4 +1,4 @@ -use crate::xsd::{element::Element, Implementation, XsdContext}; +use crate::xsd::{choice::Choice, element::Element, Implementation, XsdContext}; use log::{debug, info}; use proc_macro2::TokenStream; use std::io::prelude::*; @@ -9,6 +9,8 @@ use yaserde::YaDeserialize; pub struct Sequence { #[yaserde(rename = "element")] pub elements: Vec, + #[yaserde(rename = "choice")] + pub choices: Vec, } impl Implementation for Sequence { @@ -19,11 +21,22 @@ impl Implementation for Sequence { context: &XsdContext, ) -> TokenStream { info!("Generate elements"); - self + let elements: TokenStream = self .elements .iter() .map(|element| element.get_field_implementation(context, prefix, false, false)) - .collect() + .collect(); + + let choices: TokenStream = self + .choices + .iter() + .map(|choice| choice.get_field_implementation(context, prefix)) + .collect(); + + quote!( + #elements + #choices + ) } } @@ -47,10 +60,21 @@ impl Sequence { context: &XsdContext, prefix: &Option, ) -> TokenStream { - self + let elements: TokenStream = self .elements .iter() - .map(|element| element.get_field_implementation(context, prefix, true, false)) - .collect() + .map(|element| element.get_field_implementation(context, prefix, false, false)) + .collect(); + + let choices: TokenStream = self + .choices + .iter() + .map(|choice| choice.get_field_implementation(context, prefix)) + .collect(); + + quote!( + #elements + #choices + ) } } From 8242788be7b8d4a161b2481c0b93bc17a9bf71b7 Mon Sep 17 00:00:00 2001 From: marcelbuesing Date: Thu, 23 Dec 2021 13:07:22 +0100 Subject: [PATCH 3/5] Cleanup choice sequence example --- xml_schema/tests/choice.rs | 18 +++++++++--------- xml_schema/tests/choice_sequence.xsd | 25 +++++++++++++------------ 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs index 9b7bf27..ef539bc 100644 --- a/xml_schema/tests/choice.rs +++ b/xml_schema/tests/choice.rs @@ -48,21 +48,21 @@ fn choice_sequence() { let xml_1 = r#" - - Doe - John - + + Doe + John + "#; - let sample_1: Parent = from_str(xml_1).unwrap(); + let sample_1: Person = from_str(xml_1).unwrap(); - let model = Parent { + let model = Person { name: "Doe".to_string(), - x_firstname: Some(Firstname { + firstname: Some(Firstname { content: "John".to_string(), scope: None, }), - x_lastname: None, + lastname: None, }; assert_eq!(sample_1, model); @@ -70,6 +70,6 @@ fn choice_sequence() { let data = to_string(&model).unwrap(); assert_eq!( data, - r#"DoeJohn"# + r#"DoeJohn"# ); } diff --git a/xml_schema/tests/choice_sequence.xsd b/xml_schema/tests/choice_sequence.xsd index 7eabaa8..444f000 100644 --- a/xml_schema/tests/choice_sequence.xsd +++ b/xml_schema/tests/choice_sequence.xsd @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ - + @@ -15,14 +15,15 @@ - - - - - - - - - - + + + + + + + + + + + \ No newline at end of file From fc7a03252a3f52a225148e65cc41c41b757f7500 Mon Sep 17 00:00:00 2001 From: marcelbuesing Date: Thu, 23 Dec 2021 13:50:22 +0100 Subject: [PATCH 4/5] Fix regenerating choice child element structs --- xml_schema/tests/choice.rs | 18 +++++++++--------- xml_schema/tests/choice.xsd | 12 ++++++------ xml_schema_derive/src/xsd/choice.rs | 14 ++++++++++++++ xml_schema_derive/src/xsd/complex_type.rs | 11 +++++------ xml_schema_derive/src/xsd/extension.rs | 2 ++ 5 files changed, 36 insertions(+), 21 deletions(-) diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs index ef539bc..9aba064 100644 --- a/xml_schema/tests/choice.rs +++ b/xml_schema/tests/choice.rs @@ -16,19 +16,19 @@ fn choice() { let xml_1 = r#" - - John - + + John + "#; - let sample_1: Parent = from_str(xml_1).unwrap(); + let sample_1: Person = from_str(xml_1).unwrap(); - let model = Parent { - x_firstname: Some(Firstname { + let model = Person { + firstname: Some(Firstname { content: "John".to_string(), scope: None, }), - x_lastname: None, + lastname: None, }; assert_eq!(sample_1, model); @@ -36,7 +36,7 @@ fn choice() { let data = to_string(&model).unwrap(); assert_eq!( data, - r#"John"# + r#"John"# ); } @@ -70,6 +70,6 @@ fn choice_sequence() { let data = to_string(&model).unwrap(); assert_eq!( data, - r#"DoeJohn"# + r#"DoeJohn"# ); } diff --git a/xml_schema/tests/choice.xsd b/xml_schema/tests/choice.xsd index 9b126fd..c1a328f 100644 --- a/xml_schema/tests/choice.xsd +++ b/xml_schema/tests/choice.xsd @@ -1,5 +1,5 @@ - + @@ -7,7 +7,7 @@ - + @@ -16,10 +16,10 @@ - - - - + + + + \ No newline at end of file diff --git a/xml_schema_derive/src/xsd/choice.rs b/xml_schema_derive/src/xsd/choice.rs index 4121768..dbb56db 100644 --- a/xml_schema_derive/src/xsd/choice.rs +++ b/xml_schema_derive/src/xsd/choice.rs @@ -53,6 +53,20 @@ impl Implementation for Choice { } impl Choice { + pub fn get_sub_types_implementation( + &self, + context: &XsdContext, + namespace_definition: &TokenStream, + prefix: &Option, + ) -> TokenStream { + info!("Generate choice sub types implementation"); + self + .element + .iter() + .map(|element| element.get_subtypes_implementation(namespace_definition, prefix, context)) + .collect() + } + pub fn get_field_implementation( &self, context: &XsdContext, diff --git a/xml_schema_derive/src/xsd/complex_type.rs b/xml_schema_derive/src/xsd/complex_type.rs index 1775fe7..4ba4f72 100644 --- a/xml_schema_derive/src/xsd/complex_type.rs +++ b/xml_schema_derive/src/xsd/complex_type.rs @@ -75,7 +75,7 @@ impl Implementation for ComplexType { .map(|attribute| attribute.implement(&namespace_definition, prefix, context)) .collect(); - let sub_types_implementation = self + let sequence_sub_types = self .sequence .as_ref() .map(|sequence| sequence.get_sub_types_implementation(context, &namespace_definition, prefix)) @@ -87,10 +87,10 @@ impl Implementation for ComplexType { .map(|annotation| annotation.implement(&namespace_definition, prefix, context)) .unwrap_or_else(TokenStream::new); - let choice = self + let choice_sub_types = self .choice .as_ref() - .map(|choice| choice.implement(&namespace_definition, prefix, context)) + .map(|choice| choice.get_sub_types_implementation(context, &namespace_definition, prefix)) .unwrap_or_else(TokenStream::new); let choice_field = self @@ -112,9 +112,8 @@ impl Implementation for ComplexType { #attributes } - #sub_types_implementation - - #choice + #sequence_sub_types + #choice_sub_types } } } diff --git a/xml_schema_derive/src/xsd/extension.rs b/xml_schema_derive/src/xsd/extension.rs index 8c4cd58..03e266d 100644 --- a/xml_schema_derive/src/xsd/extension.rs +++ b/xml_schema_derive/src/xsd/extension.rs @@ -74,6 +74,7 @@ mod tests { base: "xs:string".to_string(), attributes: vec![], sequences: vec![], + choices: vec![], }; let context = @@ -109,6 +110,7 @@ mod tests { }, ], sequences: vec![], + choices: vec![], }; let context = From 77c20b186bdd8930b01ce059e5e6feac8b7ffb1e Mon Sep 17 00:00:00 2001 From: marcelbuesing Date: Thu, 23 Dec 2021 14:25:51 +0100 Subject: [PATCH 5/5] Handle min and maxOccurences in choice --- xml_schema/tests/choice.rs | 29 +++++++++++++++++++++++ xml_schema/tests/choice_multiple.xsd | 10 ++++++++ xml_schema_derive/src/xsd/choice.rs | 11 ++++++++- xml_schema_derive/src/xsd/complex_type.rs | 10 +++++++- 4 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 xml_schema/tests/choice_multiple.xsd diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs index 9aba064..339bdfd 100644 --- a/xml_schema/tests/choice.rs +++ b/xml_schema/tests/choice.rs @@ -73,3 +73,32 @@ fn choice_sequence() { r#"DoeJohn"# ); } + +#[test] +fn choice_multiple() { + #[derive(Debug, XmlSchema)] + #[xml_schema(source = "xml_schema/tests/choice_multiple.xsd")] + struct ChoiceTypeSchema; + + let xml_1 = r#" + + + John + + "#; + + let sample_1: Person = from_str(xml_1).unwrap(); + + let model = Person { + firstnames: vec!["John".to_string()], + lastnames: vec![], + }; + + assert_eq!(sample_1, model); + + let data = to_string(&model).unwrap(); + assert_eq!( + data, + r#"John"# + ); +} diff --git a/xml_schema/tests/choice_multiple.xsd b/xml_schema/tests/choice_multiple.xsd new file mode 100644 index 0000000..8bd03d6 --- /dev/null +++ b/xml_schema/tests/choice_multiple.xsd @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/xml_schema_derive/src/xsd/choice.rs b/xml_schema_derive/src/xsd/choice.rs index dbb56db..4f0b7ac 100644 --- a/xml_schema_derive/src/xsd/choice.rs +++ b/xml_schema_derive/src/xsd/choice.rs @@ -73,10 +73,19 @@ impl Choice { prefix: &Option, ) -> TokenStream { info!("Generate choice elements"); + + let multiple = matches!(self.min_occurences, Some(min_occurences) if min_occurences > 1) + || matches!(self.max_occurences, Some(MaxOccurences::Unbounded)) + || matches!(self.max_occurences, Some(MaxOccurences::Number{value}) if value > 1); + + // Element fields are by default declared as Option type due to the nature of the choice element. + // Since a vector can also be empty, use Vec<_>, rather than Option>. + let optional = !multiple; + self .element .iter() - .map(|element| element.get_field_implementation(context, prefix, false, true)) + .map(|element| element.get_field_implementation(context, prefix, multiple, optional)) .collect() } } diff --git a/xml_schema_derive/src/xsd/complex_type.rs b/xml_schema_derive/src/xsd/complex_type.rs index 4ba4f72..afe1f00 100644 --- a/xml_schema_derive/src/xsd/complex_type.rs +++ b/xml_schema_derive/src/xsd/complex_type.rs @@ -130,12 +130,20 @@ impl ComplexType { .as_ref() .map(|sequence| sequence.get_field_implementation(context, prefix)) .unwrap_or_else(TokenStream::new) - } else { + } else if self.simple_content.is_some() { self .simple_content .as_ref() .map(|simple_content| simple_content.get_field_implementation(context, prefix)) .unwrap_or_else(TokenStream::new) + } else if self.choice.is_some() { + self + .choice + .as_ref() + .map(|choice| choice.get_field_implementation(context, prefix)) + .unwrap_or_else(TokenStream::new) + } else { + TokenStream::new() } }