Skip to content
Open
101 changes: 101 additions & 0 deletions xml_schema/tests/choice.rs
Original file line number Diff line number Diff line change
@@ -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#"<?xml version="1.0" encoding="UTF-8"?>
<person>
<firstname>John</firstname>
</person>
"#;

let sample_1: Person = from_str(xml_1).unwrap();

let model = Person {
firstname: Some(Firstname {
base: "John".to_string(),
scope: None,
}),
lastname: None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In that case I expect more to retrieve an enum here.
The test is not easy to understand as a person has basically a first name and a last name, so it's not a choice, more a combination.

I think the example here is more relevant: https://www.w3schools.com/xml/el_choice.asp
Where. Person may have role employee or member.

So for me the expected code will be:

let model = Person::Employee("John".to_string());

// Generated part by xml-schema
enum Person {
  Employee(String),
  Member(String),
}

};

assert_eq!(sample_1, model);

let data = to_string(&model).unwrap();
assert_eq!(
data,
r#"<?xml version="1.0" encoding="utf-8"?><Person><firstname>John</firstname></Person>"#
);
}

#[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#"<?xml version="1.0" encoding="UTF-8"?>
<person>
<name>Doe</name>
<firstname>John</firstname>
</person>
"#;

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#"<?xml version="1.0" encoding="utf-8"?><Person><name>Doe</name><firstname>John</firstname></Person>"#
);
}

#[test]
fn choice_multiple() {
#[derive(Debug, XmlSchema)]
#[xml_schema(source = "xml_schema/tests/choice_multiple.xsd")]
struct ChoiceTypeSchema;

let xml_1 = r#"<?xml version="1.0" encoding="UTF-8"?>
<person>
<firstname>John</firstname>
</person>
"#;

let sample_1: Person = from_str(xml_1).unwrap();

let model = Person {
firstname_list: vec!["John".to_string()],
lastname_list: vec![],
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in that case I expect to have:

Person {
  parents: vec![Parent::Firstname("John".to_string())],
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup exactly, I just patched this in one commit just to get it to compile. I am currently working on making this a vector.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh sorry I just implemented:

  let model = Person {
    firstname_list: vec!["John".to_string()],
    lastname_list: vec![],
  };

which i notice now is a bit different from what you imagined.

Copy link
Contributor Author

@matzipan matzipan Jan 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have some attempts which try to implement this as enum, you can see the last commit here: 740c37b

Unforuntately the test fails with:

thread 'choice_multiple' panicked at xml_schema/tests/choice.rs:99:3:
assertion `left == right` failed
  left: Gift { content: [] }
 right: Gift { content: [price_history(1), price_history(3)] }

So I probably got the yaserde config wrong :(

I will pause this work for now.


assert_eq!(sample_1, model);

let data = to_string(&model).unwrap();
assert_eq!(
data,
r#"<?xml version="1.0" encoding="utf-8"?><Person><firstname>John</firstname></Person>"#
);
}
25 changes: 25 additions & 0 deletions xml_schema/tests/choice.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="firstname">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="scope" type="xs:anyURI" use="optional" default="http://example.com#elements"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="lastname">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="scope" type="xs:anyURI" use="optional" default="http://example.com#elements"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>


<xs:complexType name="person">
<xs:choice minOccurs="0">
<xs:element name="firstname" type="firstname"/>
<xs:element name="lastname" type="lastname"/>
</xs:choice>
</xs:complexType>
</xs:schema>
10 changes: 10 additions & 0 deletions xml_schema/tests/choice_multiple.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="person">
<xs:complexType name="parents">
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element name="firstname" type="xs:string"/>
<xs:element name="lastname" type="xs:string"/>
</xs:choice>
</xs:complexType>
</xs:element>
</xs:schema>
29 changes: 29 additions & 0 deletions xml_schema/tests/choice_sequence.xsd
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:complexType name="firstname">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="scope" type="xs:anyURI" use="optional" default="http://example.com#elements"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:complexType name="lastname">
<xs:simpleContent>
<xs:extension base="xs:string">
<xs:attribute name="scope" type="xs:anyURI" use="optional" default="http://example.com#elements"/>
</xs:extension>
</xs:simpleContent>
</xs:complexType>

<xs:element name="person">
<xs:complexType name="parent">
<xs:sequence>
<xs:element name="name" type="xs:string" minOccurs="1"/>
<xs:choice>
<xs:element name="firstname" type="firstname"/>
<xs:element name="lastname" type="lastname"/>
</xs:choice>
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>
89 changes: 89 additions & 0 deletions xml_schema_derive/src/xsd/choice.rs
Original file line number Diff line number Diff line change
@@ -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<String>,
#[yaserde(rename = "attribute")]
pub attributes: Vec<Attribute>,
#[yaserde(rename = "minOccurs", attribute)]
pub min_occurences: Option<u64>,
#[yaserde(rename = "maxOccurs", attribute)]
pub max_occurences: Option<MaxOccurences>,
#[yaserde(rename = "annotation")]
pub annotation: Option<Annotation>,
#[yaserde(rename = "element")]
pub element: Vec<Element>,
}

impl Implementation for Choice {
fn implement(
&self,
namespace_definition: &TokenStream,
prefix: &Option<String>,
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<String>,
) -> 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<String>,
) -> 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<Vec<_>>.
let optional = !multiple;

self
.element
.iter()
.map(|element| element.get_field_implementation(context, prefix, multiple, optional))
.collect()
}
}
33 changes: 29 additions & 4 deletions xml_schema_derive/src/xsd/complex_type.rs
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -17,13 +17,16 @@ pub struct ComplexType {
pub name: String,
#[yaserde(rename = "attribute")]
pub attributes: Vec<Attribute>,
#[yaserde(rename = "sequence")]
pub sequence: Option<Sequence>,
#[yaserde(rename = "simpleContent")]
pub simple_content: Option<SimpleContent>,
#[yaserde(rename = "complexContent")]
pub complex_content: Option<ComplexContent>,
#[yaserde(rename = "annotation")]
pub annotation: Option<Annotation>,
#[yaserde(rename = "choice")]
pub choice: Option<Choice>,
}

impl Implementation for ComplexType {
Expand Down Expand Up @@ -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))
Expand All @@ -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

Expand All @@ -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
}
}
}
Expand All @@ -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()
}
}

Expand Down
Loading