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
+ )
}
}