diff --git a/xml_schema/tests/choice.rs b/xml_schema/tests/choice.rs new file mode 100644 index 0000000..339bdfd --- /dev/null +++ b/xml_schema/tests/choice.rs @@ -0,0 +1,104 @@ +#[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: Person = from_str(xml_1).unwrap(); + + let model = Person { + firstname: Some(Firstname { + content: "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; + + 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 { + content: "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 { + 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.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..4f0b7ac --- /dev/null +++ b/xml_schema_derive/src/xsd/choice.rs @@ -0,0 +1,91 @@ +//! 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_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 fbefaa1..8d7e9cf 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; @@ -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_else(TokenStream::new); + 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_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() } } diff --git a/xml_schema_derive/src/xsd/element.rs b/xml_schema_derive/src/xsd/element.rs index 0b01e9b..126c099 100644 --- a/xml_schema_derive/src/xsd/element.rs +++ b/xml_schema_derive/src/xsd/element.rs @@ -102,6 +102,8 @@ impl Element { &self, context: &XsdContext, prefix: &Option, + multiple: bool, + optional: bool, ) -> TokenStream { if self.name.is_empty() { return quote!(); @@ -142,7 +144,7 @@ impl Element { rust_type }; - let rust_type = if !multiple && self.min_occurences == Some(0) { + let rust_type = if optional || (!multiple && 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 d22b83b..44387d6 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 proc_macro2::TokenStream; @@ -17,6 +17,8 @@ pub struct Extension { pub attributes: Vec, #[yaserde(rename = "sequence")] pub sequences: Vec, + #[yaserde(rename = "choice")] + pub choices: Vec, } impl Implementation for Extension { @@ -70,6 +72,7 @@ mod tests { base: "xs:string".to_string(), attributes: vec![], sequences: vec![], + choices: vec![], }; let context = @@ -110,6 +113,7 @@ mod tests { }, ], sequences: vec![], + choices: vec![], }; let context = diff --git a/xml_schema_derive/src/xsd/mod.rs b/xml_schema_derive/src/xsd/mod.rs index ab2e3c7..e48c257 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 + ) } }