diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs new file mode 100644 index 0000000..646e0ae --- /dev/null +++ b/xml_schema/tests/choice.rs @@ -0,0 +1,101 @@ +extern crate yaserde_derive; + +use xml_schema_derive::XmlSchema; +use yaserde::de::from_str; +use yaserde::ser::to_string; + +#[test] +fn choice() { + #[derive(Debug, XmlSchema)] + #[xml_schema(source = "xml_schema/tests/choice.xsd")] + struct ChoiceTypeSchema; + + use choice_type_schema::xml_schema_types::*; + + let xml_1 = r#" + + John + + "#; + + let sample_1: Person = from_str(xml_1).unwrap(); + + let model = Person { + firstname: Some(Firstname { + base: "John".to_string(), + scope: None, + }), + lastname: None, + }; + + assert_eq!(sample_1, model); + + let data = to_string(&model).unwrap(); + assert_eq!( + data, + r#"John"# + ); +} + +#[test] +fn choice_sequence() { + #[derive(Debug, XmlSchema)] + #[xml_schema(source = "xml_schema/tests/choice_sequence.xsd")] + struct ChoiceTypeSchema; + + use choice_type_schema::xml_schema_types::*; + + let xml_1 = r#" + + Doe + John + + "#; + + let sample_1: Person = from_str(xml_1).unwrap(); + + let model = Person { + name: "Doe".to_string(), + firstname: Some(Firstname { + base: "John".to_string(), + scope: None, + }), + lastname: None, + }; + + assert_eq!(sample_1, model); + + let data = to_string(&model).unwrap(); + assert_eq!( + data, + 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 { + firstname_list: vec!["John".to_string()], + lastname_list: vec![], + }; + + 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..c1a328f --- /dev/null +++ b/xml_schema/tests/choice.xsd @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file 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/tests/choice_sequence.xsd b/xml_schema/tests/choice_sequence.xsd new file mode 100644 index 0000000..444f000 --- /dev/null +++ b/xml_schema/tests/choice_sequence.xsd @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ 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..7347116 --- /dev/null +++ b/xml_schema_derive/src/xsd/choice.rs @@ -0,0 +1,89 @@ +//! 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::info; +use proc_macro2::TokenStream; + +#[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_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, + 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, multiple, optional)) + .collect() + } +} diff --git a/xml_schema_derive/src/xsd/complex_type.rs b/xml_schema_derive/src/xsd/complex_type.rs index d531559..7ea276d 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::ToUpperCamelCase; @@ -17,6 +17,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, @@ -24,6 +25,8 @@ pub struct ComplexType { pub complex_content: Option, #[yaserde(rename = "annotation")] pub annotation: Option, + #[yaserde(rename = "choice")] + pub choice: Option, } impl Implementation for ComplexType { @@ -69,7 +72,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)) @@ -81,6 +84,18 @@ impl Implementation for ComplexType { .map(|annotation| annotation.implement(namespace_definition, prefix, context)) .unwrap_or_default(); + let choice_sub_types = self + .choice + .as_ref() + .map(|choice| choice.get_sub_types_implementation(context, &namespace_definition, prefix)) + .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 @@ -90,10 +105,12 @@ impl Implementation for ComplexType { #sequence #simple_content #complex_content + #choice_field #attributes } - #sub_types_implementation + #sequence_sub_types + #choice_sub_types } } } @@ -110,12 +127,20 @@ impl ComplexType { .as_ref() .map(|sequence| sequence.get_field_implementation(context, prefix)) .unwrap_or_default() - } 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_default() + } 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() } } diff --git a/xml_schema_derive/src/xsd/element.rs b/xml_schema_derive/src/xsd/element.rs index 17832e2..76c84cd 100644 --- a/xml_schema_derive/src/xsd/element.rs +++ b/xml_schema_derive/src/xsd/element.rs @@ -102,13 +102,16 @@ impl Element { &self, context: &XsdContext, prefix: &Option, + inheritable_multiple: bool, + optional: bool, ) -> TokenStream { if self.name.is_empty() { return quote!(); } - let multiple = self.max_occurences.is_some() - && self.max_occurences != Some(MaxOccurences::Number { value: 1 }); + let multiple = inheritable_multiple + || (self.max_occurences.is_some() + && self.max_occurences != Some(MaxOccurences::Number { value: 1 })); let name = if self.name.to_lowercase() == "type" { "kind".to_string() @@ -123,7 +126,7 @@ impl Element { } else { name }; - + let attribute_name = Ident::new(&name, Span::call_site()); let yaserde_rename = &self.name; @@ -146,17 +149,6 @@ impl Element { rust_type }; - let rust_type = if !multiple && self.min_occurences == Some(0) { - quote!(Option<#rust_type>) - } else { - rust_type - }; - - let prefix_attribute = prefix - .as_ref() - .map(|prefix| quote!(, prefix=#prefix)) - .unwrap_or_default(); - let module = (!context.is_in_sub_module() && !self .kind @@ -169,9 +161,22 @@ impl Element { .then_some(quote!(xml_schema_types::)) .unwrap_or_default(); + let rust_type = quote!(#module#rust_type); + + let rust_type = if optional || (!multiple && self.min_occurences == Some(0)) { + quote!(Option<#rust_type>) + } else { + rust_type + }; + + let prefix_attribute = prefix + .as_ref() + .map(|prefix| quote!(, prefix=#prefix)) + .unwrap_or_default(); + quote! { #[yaserde(rename=#yaserde_rename #prefix_attribute)] - pub #attribute_name: #module#rust_type, + pub #attribute_name: #rust_type, } } } diff --git a/xml_schema_derive/src/xsd/extension.rs b/xml_schema_derive/src/xsd/extension.rs index 536787f..163b63c 100644 --- a/xml_schema_derive/src/xsd/extension.rs +++ b/xml_schema_derive/src/xsd/extension.rs @@ -1,5 +1,5 @@ use crate::xsd::{ - attribute::Attribute, group::Group, rust_types_mapping::RustTypesMapping, sequence::Sequence, + attribute::Attribute, choice::Choice, group::Group, rust_types_mapping::RustTypesMapping, sequence::Sequence, Implementation, XsdContext, }; use proc_macro2::TokenStream; @@ -19,6 +19,8 @@ pub struct Extension { pub sequences: Vec, #[yaserde(rename = "group")] pub group: Option, + #[yaserde(rename = "choice")] + pub choices: Vec, } impl Implementation for Extension { @@ -91,6 +93,7 @@ mod tests { attributes: vec![], sequences: vec![], group: None, + choices: vec![], }; let context = @@ -134,6 +137,7 @@ mod tests { ], sequences: vec![], group: None, + choices: vec![], }; let context = diff --git a/xml_schema_derive/src/xsd/mod.rs b/xml_schema_derive/src/xsd/mod.rs index 43811b4..ce53212 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 a39d2f1..621280d 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::info; use proc_macro2::TokenStream; @@ -7,6 +7,8 @@ use proc_macro2::TokenStream; pub struct Sequence { #[yaserde(rename = "element")] pub elements: Vec, + #[yaserde(rename = "choice")] + pub choices: Vec, } impl Implementation for Sequence { @@ -17,11 +19,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)) - .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 + ) } } @@ -45,10 +58,21 @@ impl Sequence { context: &XsdContext, prefix: &Option, ) -> TokenStream { - self + let elements: TokenStream = self .elements .iter() - .map(|element| element.get_field_implementation(context, prefix)) - .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 + ) } }