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