diff --git a/xsd-parser/src/models/data/complex.rs b/xsd-parser/src/models/data/complex.rs index 6e908c1d..e57514fd 100644 --- a/xsd-parser/src/models/data/complex.rs +++ b/xsd-parser/src/models/data/complex.rs @@ -204,6 +204,11 @@ pub struct ComplexDataContent<'types> { /// Additional attributes that will be added to the element. pub extra_attributes: Vec, + + /// Default value for the content field, used when the element is present + /// but has no text content. Derived from the `default` attribute of the + /// enclosing `xs:element` in the XSD schema. + pub default_value: Option, } /// Contains the details of an XML element. diff --git a/xsd-parser/src/models/meta/complex.rs b/xsd-parser/src/models/meta/complex.rs index 3a2b9f78..7069ae1c 100644 --- a/xsd-parser/src/models/meta/complex.rs +++ b/xsd-parser/src/models/meta/complex.rs @@ -46,6 +46,13 @@ pub struct ComplexMeta { /// List of attributes defined for this complex type. pub attributes: AttributesMeta, + + /// Default value for the simple content of this complex type. + /// + /// This is set when an `xs:element` with a `default` attribute has an + /// inline complex type with `xs:simpleContent`. It defines the value to + /// use when the element is present but has no text content. + pub default: Option, } /* GroupMeta */ @@ -194,6 +201,7 @@ impl Default for ComplexMeta { is_dynamic: false, is_mixed: false, attributes: AttributesMeta::default(), + default: None, } } } @@ -208,6 +216,7 @@ impl TypeEq for ComplexMeta { is_dynamic, is_mixed: mixed_content, attributes, + default, } = self; base.type_hash(hasher, types); @@ -217,6 +226,7 @@ impl TypeEq for ComplexMeta { is_dynamic.hash(hasher); mixed_content.hash(hasher); attributes.type_hash(hasher, types); + default.hash(hasher); } fn type_eq(&self, other: &Self, types: &MetaTypes) -> bool { @@ -228,6 +238,7 @@ impl TypeEq for ComplexMeta { is_dynamic, is_mixed: mixed_content, attributes, + default, } = self; base.type_eq(&other.base, types) @@ -237,5 +248,6 @@ impl TypeEq for ComplexMeta { && is_dynamic.eq(&other.is_dynamic) && mixed_content.eq(&other.is_mixed) && attributes.type_eq(&other.attributes, types) + && default.eq(&other.default) } } diff --git a/xsd-parser/src/pipeline/generator/data/complex.rs b/xsd-parser/src/pipeline/generator/data/complex.rs index baeabc39..747c9a92 100644 --- a/xsd-parser/src/pipeline/generator/data/complex.rs +++ b/xsd-parser/src/pipeline/generator/data/complex.rs @@ -59,6 +59,7 @@ impl<'types> ComplexData<'types> { MaxOccurs::Bounded(1), &[], &meta.elements, + None, ) } @@ -76,6 +77,7 @@ impl<'types> ComplexData<'types> { MaxOccurs::Bounded(1), &[], &meta.elements, + None, ) } @@ -93,6 +95,7 @@ impl<'types> ComplexData<'types> { MaxOccurs::Bounded(1), &[], &meta.elements, + None, ) } @@ -137,6 +140,7 @@ impl<'types> ComplexData<'types> { meta.max_occurs, &meta.attributes, elements, + meta.default.as_deref(), ) } @@ -150,11 +154,18 @@ impl<'types> ComplexData<'types> { max_occurs: MaxOccurs, attributes: &'types [AttributeMeta], elements: &'types [ElementMeta], + default: Option<&str>, ) -> Result { match type_mode { - TypeMode::Simple { simple_type } => { - Self::new_simple(ctx, form, simple_type, min_occurs, max_occurs, attributes) - } + TypeMode::Simple { simple_type } => Self::new_simple( + ctx, + form, + simple_type, + min_occurs, + max_occurs, + attributes, + default, + ), TypeMode::Choice => Self::new_enum( ctx, form, mixed_mode, min_occurs, max_occurs, attributes, elements, ), @@ -171,6 +182,7 @@ impl<'types> ComplexData<'types> { min_occurs: MinOccurs, max_occurs: MaxOccurs, attributes: &'types [AttributeMeta], + default: Option<&str>, ) -> Result { let base = ComplexBase::new(ctx, form, ComplexFlags::empty())?; let occurs = Occurs::from_occurs(min_occurs, max_occurs); @@ -187,6 +199,10 @@ impl<'types> ComplexData<'types> { }) .collect::, _>>()?; + let default_value = default + .map(|default| ctx.make_value_renderer(simple_type, default, ValueGeneratorMode::Value)) + .transpose()?; + let content = ComplexDataContent { occurs, simple_type: Some(simple_type), @@ -195,6 +211,7 @@ impl<'types> ComplexData<'types> { field_ident, target_type, extra_attributes: Vec::new(), + default_value, }; let type_ = ComplexDataStruct { @@ -321,6 +338,7 @@ impl<'types> ComplexData<'types> { field_ident, target_type, extra_attributes: Vec::new(), + default_value: None, }; StructMode::Content { content } @@ -492,6 +510,7 @@ impl<'types> ComplexData<'types> { field_ident, target_type, extra_attributes: Vec::new(), + default_value: None, }; StructMode::Content { content } diff --git a/xsd-parser/src/pipeline/interpreter/state/generate_types/variant_processor.rs b/xsd-parser/src/pipeline/interpreter/state/generate_types/variant_processor.rs index 8ac81284..04e5d680 100644 --- a/xsd-parser/src/pipeline/interpreter/state/generate_types/variant_processor.rs +++ b/xsd-parser/src/pipeline/interpreter/state/generate_types/variant_processor.rs @@ -242,6 +242,17 @@ impl<'a, 'state, 'schema> VariantProcessor<'a, 'state, 'schema> { if has_content { ret?; + + // If the element has a default value, propagate it to the + // inline complex type so the deserializer can use it when + // the element is present but has no text content. + if let Some(default) = &ty.default { + let type_meta = self.owner.get_type_mut(&type_); + if let MetaTypeVariant::ComplexType(ref mut meta) = type_meta.variant { + meta.default = Some(default.clone()); + } + } + init_any!(self, Reference, ReferenceMeta::new(type_), true); } else { // No actual type content found, default to `xs:anyType` diff --git a/xsd-parser/src/pipeline/renderer/steps/defaults.rs b/xsd-parser/src/pipeline/renderer/steps/defaults.rs index f72b4fa0..e1dc2d83 100644 --- a/xsd-parser/src/pipeline/renderer/steps/defaults.rs +++ b/xsd-parser/src/pipeline/renderer/steps/defaults.rs @@ -1,7 +1,10 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; -use crate::models::data::{ComplexData, ComplexDataAttribute, ComplexDataStruct, DataTypeVariant}; +use crate::models::data::{ + ComplexData, ComplexDataAttribute, ComplexDataContent, ComplexDataStruct, DataTypeVariant, + StructMode, +}; use super::super::{Context, RenderStep, RenderStepType}; @@ -52,20 +55,28 @@ impl ComplexData<'_> { impl ComplexDataStruct<'_> { pub(crate) fn render_defaults(&self, ctx: &mut Context<'_, '_>) { let type_ident = &self.type_ident; - let mut has_attributes = false; + let attribute_defaults = self .attributes .iter() .filter_map(|attrib| attrib.render_default_fn(ctx)) - .inspect(|_| has_attributes = true); + .collect::>(); + + let content_default = match &self.mode { + StructMode::Content { content } => content.render_default_content_fn(ctx), + _ => None, + }; + + let has_defaults = !attribute_defaults.is_empty() || content_default.is_some(); let impl_ = quote! { impl #type_ident { #( #attribute_defaults )* + #content_default } }; - if has_attributes { + if has_defaults { ctx.current_module().append(impl_); } } @@ -85,3 +96,17 @@ impl ComplexDataAttribute<'_> { }) } } + +impl ComplexDataContent<'_> { + fn render_default_content_fn(&self, ctx: &Context<'_, '_>) -> Option { + let default = self.default_value.as_ref()?.render(ctx); + let target_ident = ctx.resolve_type_for_module(&self.target_type); + + Some(quote! { + #[must_use] + pub fn default_content() -> #target_ident { + #default + } + }) + } +} diff --git a/xsd-parser/src/pipeline/renderer/steps/quick_xml/deserialize.rs b/xsd-parser/src/pipeline/renderer/steps/quick_xml/deserialize.rs index 2068f67b..0c1bd72c 100644 --- a/xsd-parser/src/pipeline/renderer/steps/quick_xml/deserialize.rs +++ b/xsd-parser/src/pipeline/renderer/steps/quick_xml/deserialize.rs @@ -1606,9 +1606,20 @@ impl ComplexDataStruct<'_> { } }); + // When simple content has a default value, add a `ContentInit__` state to + // intercept `Event::End` before any text has been seen (i.e. ``) + // and fall back to the default instead of trying to parse an empty string. + let content_init = + (content.is_simple() && content.default_value.is_some()).then(|| { + quote! { + ContentInit__(<#target_type as #with_deserializer>::Deserializer), + } + }); + quote! { Init__, #next + #content_init Content__(<#target_type as #with_deserializer>::Deserializer), #done Unknown__, @@ -1954,11 +1965,25 @@ impl ComplexDataStruct<'_> { Ok(()) } } - StructMode::Content { .. } => { + StructMode::Content { content } => { + let type_ident = &self.type_ident; + + // When a `ContentInit__` state exists (simple content + default value), handle + // the case where `finish_state` is called before any text content was received. + let content_init_arm = (content.is_simple() && content.default_value.is_some()) + .then(|| { + quote! { + else if let #deserializer_state_ident::ContentInit__(_) = state { + self.store_content(super::#type_ident::default_content())?; + } + } + }); + quote! { if let #deserializer_state_ident::Content__(deserializer) = state { self.store_content(deserializer.finish(helper)?)?; } + #content_init_arm Ok(()) } @@ -2103,7 +2128,7 @@ impl ComplexDataStruct<'_> { } StructMode::Content { content } => { if content.is_simple() { - self.render_deserializer_fn_next_content_simple(ctx) + self.render_deserializer_fn_next_content_simple(ctx, content) } else { self.render_deserializer_fn_next_content_complex(ctx, content) } @@ -2162,8 +2187,13 @@ impl ComplexDataStruct<'_> { } } - fn render_deserializer_fn_next_content_simple(&self, ctx: &Context<'_, '_>) -> TokenStream { + fn render_deserializer_fn_next_content_simple( + &self, + ctx: &Context<'_, '_>, + content: &ComplexDataContent<'_>, + ) -> TokenStream { let deserializer_state_ident = &self.deserializer_state_ident; + let type_ident = &self.type_ident; let replace = resolve_quick_xml_ident!(ctx, "::core::mem::replace"); let content_deserializer = @@ -2171,18 +2201,99 @@ impl ComplexDataStruct<'_> { ctx.add_quick_xml_deserialize_usings(true, ["::xsd_parser_types::quick_xml::Deserializer"]); - quote! { - use #deserializer_state_ident as S; + let has_default = content.default_value.is_some(); + + if has_default { + // When a default value is present, we need to handle two self-closing forms: + // 1. `` → `Event::Empty` — intercepted in `S::Init__` + // 2. `` → `Event::Start` then `Event::End` with no text + // — intercepted in the new `S::ContentInit__` state + // + // The `S::ContentInit__` state is entered from `S::Init__` when we receive a + // `Event::Start` (not `Event::Empty`). While in that state, a subsequent + // `Event::End` without any text uses `default_content()` rather than trying to + // parse an empty byte slice (which would fail for types like `bool`). + let event = resolve_quick_xml_ident!(ctx, "::xsd_parser_types::quick_xml::Event"); + let deserializer_event = + resolve_quick_xml_ident!(ctx, "::xsd_parser_types::quick_xml::DeserializerEvent"); + let deserializer_output = + resolve_quick_xml_ident!(ctx, "::xsd_parser_types::quick_xml::DeserializerOutput"); + let deserializer_artifact = resolve_quick_xml_ident!( + ctx, + "::xsd_parser_types::quick_xml::DeserializerArtifact" + ); + + // Shared fragment: store the default and finish the deserializer. + let use_default = quote! { + self.store_content(super::#type_ident::default_content())?; + let data = self.finish(helper)?; + return Ok(#deserializer_output { + artifact: #deserializer_artifact::Data(data), + event: #deserializer_event::None, + allow_any: false, + }); + }; - match #replace(&mut *self.state__, S::Unknown__) { - S::Unknown__ => unreachable!(), - S::Init__ => { - let output = #content_deserializer::init(helper, event)?; - self.handle_content(helper, output) + quote! { + use #deserializer_state_ident as S; + + match #replace(&mut *self.state__, S::Unknown__) { + S::Unknown__ => unreachable!(), + S::Init__ => { + // Self-closing tag: `` — use default immediately. + if matches!(&event, #event::Empty(_)) { + #use_default + } + // Opening tag: `` — enter ContentInit__ so that a + // bare `` can still fall back to the default. + let output = #content_deserializer::init(helper, event)?; + let #deserializer_output { artifact, event, allow_any } = output; + match artifact { + #deserializer_artifact::Deserializer(deserializer) => { + *self.state__ = S::ContentInit__(deserializer); + Ok(#deserializer_output { + artifact: #deserializer_artifact::Deserializer(self), + event, + allow_any, + }) + } + artifact => self.handle_content( + helper, + #deserializer_output { artifact, event, allow_any }, + ), + } + } + S::ContentInit__(deserializer) => { + // First event after the opening tag. + // If it's an immediate `` with no text, use the default. + if matches!(&event, #event::End(_)) { + #use_default + } + // Otherwise delegate to the inner deserializer; `handle_content` + // will transition to `S::Content__` once text starts accumulating. + let output = deserializer.next(helper, event)?; + self.handle_content(helper, output) + } + S::Content__(deserializer) => { + let output = deserializer.next(helper, event)?; + self.handle_content(helper, output) + } } - S::Content__(deserializer) => { - let output = deserializer.next(helper, event)?; - self.handle_content(helper, output) + } + } else { + quote! { + use #deserializer_state_ident as S; + + match #replace(&mut *self.state__, S::Unknown__) { + S::Unknown__ => unreachable!(), + S::Init__ => { + let output = #content_deserializer::init(helper, event)?; + self.handle_content(helper, output) + } + S::Content__(deserializer) => { + let output = deserializer.next(helper, event)?; + self.handle_content(helper, output) + } } } } diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/example/empty.xml b/xsd-parser/tests/feature/element_default_with_simple_content/example/empty.xml new file mode 100644 index 00000000..582b4627 --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/example/empty.xml @@ -0,0 +1,4 @@ + + + + diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/example/empty_explicit.xml b/xsd-parser/tests/feature/element_default_with_simple_content/example/empty_explicit.xml new file mode 100644 index 00000000..00b88ca9 --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/example/empty_explicit.xml @@ -0,0 +1,4 @@ + + + + diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/example/with_content.xml b/xsd-parser/tests/feature/element_default_with_simple_content/example/with_content.xml new file mode 100644 index 00000000..e72ae2ab --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/example/with_content.xml @@ -0,0 +1,4 @@ + + + true + diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/expected/default.rs b/xsd-parser/tests/feature/element_default_with_simple_content/expected/default.rs new file mode 100644 index 00000000..7c5f54b5 --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/expected/default.rs @@ -0,0 +1,10 @@ +pub type Foo = FooType; +#[derive(Debug)] +pub struct FooType { + pub bar: Option, +} +#[derive(Debug)] +pub struct FooBarType { + pub baz: String, + pub content: bool, +} diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/expected/quick_xml.rs b/xsd-parser/tests/feature/element_default_with_simple_content/expected/quick_xml.rs new file mode 100644 index 00000000..0796ae83 --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/expected/quick_xml.rs @@ -0,0 +1,527 @@ +use xsd_parser_types::{ + misc::{Namespace, NamespacePrefix}, + quick_xml::{Error, WithDeserializer, WithSerializer}, +}; +pub const NS_XS: Namespace = Namespace::new_const(b"http://www.w3.org/2001/XMLSchema"); +pub const NS_XML: Namespace = Namespace::new_const(b"http://www.w3.org/XML/1998/namespace"); +pub const NS_XSI: Namespace = Namespace::new_const(b"http://www.w3.org/2001/XMLSchema-instance"); +pub const PREFIX_XS: NamespacePrefix = NamespacePrefix::new_const(b"xs"); +pub const PREFIX_XML: NamespacePrefix = NamespacePrefix::new_const(b"xml"); +pub const PREFIX_XSI: NamespacePrefix = NamespacePrefix::new_const(b"xsi"); +pub type Foo = FooType; +#[derive(Debug)] +pub struct FooType { + pub bar: Option, +} +impl WithSerializer for FooType { + type Serializer<'x> = quick_xml_serialize::FooTypeSerializer<'x>; + fn serializer<'ser>( + &'ser self, + name: Option<&'ser str>, + is_root: bool, + ) -> Result, Error> { + Ok(quick_xml_serialize::FooTypeSerializer { + value: self, + state: Box::new(quick_xml_serialize::FooTypeSerializerState::Init__), + name: name.unwrap_or("Foo"), + is_root, + }) + } +} +impl WithDeserializer for FooType { + type Deserializer = quick_xml_deserialize::FooTypeDeserializer; +} +#[derive(Debug)] +pub struct FooBarType { + pub baz: String, + pub content: bool, +} +impl FooBarType { + #[must_use] + pub fn default_content() -> bool { + false + } +} +impl WithSerializer for FooBarType { + type Serializer<'x> = quick_xml_serialize::FooBarTypeSerializer<'x>; + fn serializer<'ser>( + &'ser self, + name: Option<&'ser str>, + is_root: bool, + ) -> Result, Error> { + Ok(quick_xml_serialize::FooBarTypeSerializer { + value: self, + state: Box::new(quick_xml_serialize::FooBarTypeSerializerState::Init__), + name: name.unwrap_or("FooBar"), + is_root, + }) + } +} +impl WithDeserializer for FooBarType { + type Deserializer = quick_xml_deserialize::FooBarTypeDeserializer; +} +pub mod quick_xml_deserialize { + use core::mem::replace; + use xsd_parser_types::quick_xml::{ + BytesStart, ContentDeserializer, DeserializeHelper, Deserializer, DeserializerArtifact, + DeserializerEvent, DeserializerOutput, DeserializerResult, ElementHandlerOutput, Error, + ErrorKind, Event, RawByteStr, WithDeserializer, + }; + #[derive(Debug)] + pub struct FooTypeDeserializer { + bar: Option, + state__: Box, + } + #[derive(Debug)] + enum FooTypeDeserializerState { + Init__, + Bar(Option<::Deserializer>), + Done__, + Unknown__, + } + impl FooTypeDeserializer { + fn from_bytes_start( + helper: &mut DeserializeHelper, + bytes_start: &BytesStart<'_>, + ) -> Result { + for attrib in helper.filter_xmlns_attributes(bytes_start) { + let attrib = attrib?; + helper.raise_unexpected_attrib_checked(&attrib)?; + } + Ok(Self { + bar: None, + state__: Box::new(FooTypeDeserializerState::Init__), + }) + } + fn finish_state( + &mut self, + helper: &mut DeserializeHelper, + state: FooTypeDeserializerState, + ) -> Result<(), Error> { + use FooTypeDeserializerState as S; + match state { + S::Bar(Some(deserializer)) => self.store_bar(deserializer.finish(helper)?)?, + _ => (), + } + Ok(()) + } + fn store_bar(&mut self, value: super::FooBarType) -> Result<(), Error> { + if self.bar.is_some() { + Err(ErrorKind::DuplicateElement(RawByteStr::from_slice(b"Bar")))?; + } + self.bar = Some(value); + Ok(()) + } + fn handle_bar<'de>( + &mut self, + helper: &mut DeserializeHelper, + output: DeserializerOutput<'de, super::FooBarType>, + fallback: &mut Option, + ) -> Result, Error> { + use FooTypeDeserializerState as S; + let DeserializerOutput { + artifact, + event, + allow_any, + } = output; + if artifact.is_none() { + *self.state__ = S::Done__; + return Ok(ElementHandlerOutput::from_event(event, allow_any)); + } + if let Some(fallback) = fallback.take() { + self.finish_state(helper, fallback)?; + } + match artifact { + DeserializerArtifact::None => unreachable!(), + DeserializerArtifact::Data(data) => { + self.store_bar(data)?; + *self.state__ = S::Done__; + Ok(ElementHandlerOutput::from_event(event, allow_any)) + } + DeserializerArtifact::Deserializer(deserializer) => { + fallback.get_or_insert(S::Bar(Some(deserializer))); + *self.state__ = S::Done__; + Ok(ElementHandlerOutput::from_event(event, allow_any)) + } + } + } + } + impl<'de> Deserializer<'de, super::FooType> for FooTypeDeserializer { + fn init( + helper: &mut DeserializeHelper, + event: Event<'de>, + ) -> DeserializerResult<'de, super::FooType> { + helper.init_deserializer_from_start_event(event, Self::from_bytes_start) + } + fn next( + mut self, + helper: &mut DeserializeHelper, + event: Event<'de>, + ) -> DeserializerResult<'de, super::FooType> { + use FooTypeDeserializerState as S; + let mut event = event; + let mut fallback = None; + let mut allow_any_element = false; + let (event, allow_any) = loop { + let state = replace(&mut *self.state__, S::Unknown__); + event = match (state, event) { + (S::Unknown__, _) => unreachable!(), + (S::Bar(Some(deserializer)), event) => { + let output = deserializer.next(helper, event)?; + match self.handle_bar(helper, output, &mut fallback)? { + ElementHandlerOutput::Continue { event, allow_any } => { + allow_any_element = allow_any_element || allow_any; + event + } + ElementHandlerOutput::Break { event, allow_any } => { + break (event, allow_any) + } + } + } + (_, Event::End(_)) => { + if let Some(fallback) = fallback.take() { + self.finish_state(helper, fallback)?; + } + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(self.finish(helper)?), + event: DeserializerEvent::None, + allow_any: false, + }); + } + (S::Init__, event) => { + fallback.get_or_insert(S::Init__); + *self.state__ = S::Bar(None); + event + } + (S::Bar(None), event @ (Event::Start(_) | Event::Empty(_))) => { + let output = + helper.init_start_tag_deserializer(event, None, b"Bar", false)?; + match self.handle_bar(helper, output, &mut fallback)? { + ElementHandlerOutput::Continue { event, allow_any } => { + allow_any_element = allow_any_element || allow_any; + event + } + ElementHandlerOutput::Break { event, allow_any } => { + break (event, allow_any) + } + } + } + (S::Done__, event) => { + *self.state__ = S::Done__; + break (DeserializerEvent::Continue(event), allow_any_element); + } + (state, event) => { + *self.state__ = state; + break (DeserializerEvent::Break(event), false); + } + } + }; + if let Some(fallback) = fallback { + *self.state__ = fallback; + } + Ok(DeserializerOutput { + artifact: DeserializerArtifact::Deserializer(self), + event, + allow_any, + }) + } + fn finish(mut self, helper: &mut DeserializeHelper) -> Result { + let state = replace(&mut *self.state__, FooTypeDeserializerState::Unknown__); + self.finish_state(helper, state)?; + Ok(super::FooType { bar: self.bar }) + } + } + #[derive(Debug)] + pub struct FooBarTypeDeserializer { + baz: String, + content: Option, + state__: Box, + } + #[derive(Debug)] + enum FooBarTypeDeserializerState { + Init__, + ContentInit__(::Deserializer), + Content__(::Deserializer), + Unknown__, + } + impl FooBarTypeDeserializer { + fn from_bytes_start( + helper: &mut DeserializeHelper, + bytes_start: &BytesStart<'_>, + ) -> Result { + let mut baz: Option = None; + for attrib in helper.filter_xmlns_attributes(bytes_start) { + let attrib = attrib?; + if attrib.key.local_name().as_ref() == b"Baz" { + helper.read_attrib(&mut baz, b"Baz", &attrib.value)?; + } else { + helper.raise_unexpected_attrib_checked(&attrib)?; + } + } + Ok(Self { + baz: baz.ok_or_else(|| ErrorKind::MissingAttribute("Baz".into()))?, + content: None, + state__: Box::new(FooBarTypeDeserializerState::Init__), + }) + } + fn finish_state( + &mut self, + helper: &mut DeserializeHelper, + state: FooBarTypeDeserializerState, + ) -> Result<(), Error> { + if let FooBarTypeDeserializerState::Content__(deserializer) = state { + self.store_content(deserializer.finish(helper)?)?; + } else if let FooBarTypeDeserializerState::ContentInit__(_) = state { + self.store_content(super::FooBarType::default_content())?; + } + Ok(()) + } + fn store_content(&mut self, value: bool) -> Result<(), Error> { + if self.content.is_some() { + Err(ErrorKind::DuplicateContent)?; + } + self.content = Some(value); + Ok(()) + } + fn handle_content<'de>( + mut self, + helper: &mut DeserializeHelper, + output: DeserializerOutput<'de, bool>, + ) -> DeserializerResult<'de, super::FooBarType> { + use FooBarTypeDeserializerState as S; + let DeserializerOutput { + artifact, + event, + allow_any, + } = output; + match artifact { + DeserializerArtifact::None => Ok(DeserializerOutput { + artifact: DeserializerArtifact::None, + event, + allow_any, + }), + DeserializerArtifact::Data(data) => { + self.store_content(data)?; + let data = self.finish(helper)?; + Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(data), + event, + allow_any, + }) + } + DeserializerArtifact::Deserializer(deserializer) => { + *self.state__ = S::Content__(deserializer); + Ok(DeserializerOutput { + artifact: DeserializerArtifact::Deserializer(self), + event, + allow_any, + }) + } + } + } + } + impl<'de> Deserializer<'de, super::FooBarType> for FooBarTypeDeserializer { + fn init( + helper: &mut DeserializeHelper, + event: Event<'de>, + ) -> DeserializerResult<'de, super::FooBarType> { + let (Event::Start(x) | Event::Empty(x)) = &event else { + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::None, + event: DeserializerEvent::Break(event), + allow_any: false, + }); + }; + Self::from_bytes_start(helper, x)?.next(helper, event) + } + fn next( + mut self, + helper: &mut DeserializeHelper, + event: Event<'de>, + ) -> DeserializerResult<'de, super::FooBarType> { + use FooBarTypeDeserializerState as S; + match replace(&mut *self.state__, S::Unknown__) { + S::Unknown__ => unreachable!(), + S::Init__ => { + if matches!(&event, Event::Empty(_)) { + self.store_content(super::FooBarType::default_content())?; + let data = self.finish(helper)?; + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(data), + event: DeserializerEvent::None, + allow_any: false, + }); + } + let output = ContentDeserializer::init(helper, event)?; + let DeserializerOutput { + artifact, + event, + allow_any, + } = output; + match artifact { + DeserializerArtifact::Deserializer(deserializer) => { + *self.state__ = S::ContentInit__(deserializer); + Ok(DeserializerOutput { + artifact: DeserializerArtifact::Deserializer(self), + event, + allow_any, + }) + } + artifact => self.handle_content( + helper, + DeserializerOutput { + artifact, + event, + allow_any, + }, + ), + } + } + S::ContentInit__(deserializer) => { + if matches!(&event, Event::End(_)) { + self.store_content(super::FooBarType::default_content())?; + let data = self.finish(helper)?; + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(data), + event: DeserializerEvent::None, + allow_any: false, + }); + } + let output = deserializer.next(helper, event)?; + self.handle_content(helper, output) + } + S::Content__(deserializer) => { + let output = deserializer.next(helper, event)?; + self.handle_content(helper, output) + } + } + } + fn finish(mut self, helper: &mut DeserializeHelper) -> Result { + let state = replace(&mut *self.state__, FooBarTypeDeserializerState::Unknown__); + self.finish_state(helper, state)?; + Ok(super::FooBarType { + baz: self.baz, + content: helper.finish_content(self.content)?, + }) + } + } +} +pub mod quick_xml_serialize { + use xsd_parser_types::quick_xml::{ + BytesEnd, BytesStart, Error, Event, IterSerializer, SerializeHelper, Serializer, + WithSerializer, + }; + #[derive(Debug)] + pub struct FooTypeSerializer<'ser> { + pub(super) value: &'ser super::FooType, + pub(super) state: Box>, + pub(super) name: &'ser str, + pub(super) is_root: bool, + } + #[derive(Debug)] + pub(super) enum FooTypeSerializerState<'ser> { + Init__, + Bar(IterSerializer<'ser, Option<&'ser super::FooBarType>, super::FooBarType>), + End__, + Done__, + Phantom__(&'ser ()), + } + impl<'ser> FooTypeSerializer<'ser> { + fn next_event( + &mut self, + helper: &mut SerializeHelper, + ) -> Result>, Error> { + loop { + match &mut *self.state { + FooTypeSerializerState::Init__ => { + *self.state = FooTypeSerializerState::Bar(IterSerializer::new( + self.value.bar.as_ref(), + Some("Bar"), + false, + )); + let bytes = BytesStart::new(self.name); + return Ok(Some(Event::Start(bytes))); + } + FooTypeSerializerState::Bar(x) => match x.next(helper).transpose()? { + Some(event) => return Ok(Some(event)), + None => *self.state = FooTypeSerializerState::End__, + }, + FooTypeSerializerState::End__ => { + *self.state = FooTypeSerializerState::Done__; + return Ok(Some(Event::End(BytesEnd::new(self.name)))); + } + FooTypeSerializerState::Done__ => return Ok(None), + FooTypeSerializerState::Phantom__(_) => unreachable!(), + } + } + } + } + impl<'ser> Serializer<'ser> for FooTypeSerializer<'ser> { + fn next(&mut self, helper: &mut SerializeHelper) -> Option, Error>> { + match self.next_event(helper) { + Ok(Some(event)) => Some(Ok(event)), + Ok(None) => None, + Err(error) => { + *self.state = FooTypeSerializerState::Done__; + Some(Err(error)) + } + } + } + } + #[derive(Debug)] + pub struct FooBarTypeSerializer<'ser> { + pub(super) value: &'ser super::FooBarType, + pub(super) state: Box>, + pub(super) name: &'ser str, + pub(super) is_root: bool, + } + #[derive(Debug)] + pub(super) enum FooBarTypeSerializerState<'ser> { + Init__, + Content__(::Serializer<'ser>), + End__, + Done__, + Phantom__(&'ser ()), + } + impl<'ser> FooBarTypeSerializer<'ser> { + fn next_event( + &mut self, + helper: &mut SerializeHelper, + ) -> Result>, Error> { + loop { + match &mut *self.state { + FooBarTypeSerializerState::Init__ => { + *self.state = FooBarTypeSerializerState::Content__( + WithSerializer::serializer(&self.value.content, None, false)?, + ); + let mut bytes = BytesStart::new(self.name); + helper.write_attrib(&mut bytes, "Baz", &self.value.baz)?; + return Ok(Some(Event::Start(bytes))); + } + FooBarTypeSerializerState::Content__(x) => match x.next(helper).transpose()? { + Some(event) => return Ok(Some(event)), + None => *self.state = FooBarTypeSerializerState::End__, + }, + FooBarTypeSerializerState::End__ => { + *self.state = FooBarTypeSerializerState::Done__; + return Ok(Some(Event::End(BytesEnd::new(self.name)))); + } + FooBarTypeSerializerState::Done__ => return Ok(None), + FooBarTypeSerializerState::Phantom__(_) => unreachable!(), + } + } + } + } + impl<'ser> Serializer<'ser> for FooBarTypeSerializer<'ser> { + fn next(&mut self, helper: &mut SerializeHelper) -> Option, Error>> { + match self.next_event(helper) { + Ok(Some(event)) => Some(Ok(event)), + Ok(None) => None, + Err(error) => { + *self.state = FooBarTypeSerializerState::Done__; + Some(Err(error)) + } + } + } + } +} diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/mod.rs b/xsd-parser/tests/feature/element_default_with_simple_content/mod.rs new file mode 100644 index 00000000..0301dfdc --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/mod.rs @@ -0,0 +1,88 @@ +use xsd_parser::{Config, IdentType}; + +use crate::utils::{generate_test, ConfigEx}; + +fn config() -> Config { + Config::test_default().with_generate([(IdentType::Element, "Foo")]) +} + +/* default */ + +#[test] +fn generate_default() { + generate_test( + "tests/feature/element_default_with_simple_content/schema.xsd", + "tests/feature/element_default_with_simple_content/expected/default.rs", + config(), + ); +} + +#[cfg(not(feature = "update-expectations"))] +mod default { + #![allow(unused_imports)] + + include!("expected/default.rs"); +} + +/* quick_xml */ + +#[test] +fn generate_quick_xml() { + generate_test( + "tests/feature/element_default_with_simple_content/schema.xsd", + "tests/feature/element_default_with_simple_content/expected/quick_xml.rs", + config().with_quick_xml(), + ); +} + +#[test] +#[cfg(not(feature = "update-expectations"))] +fn read_quick_xml_empty_element_uses_default() { + use quick_xml::Foo; + + let obj = crate::utils::quick_xml_read_test::( + "tests/feature/element_default_with_simple_content/example/empty.xml", + ); + + let bar = obj.bar.expect("bar should be present"); + assert!(!bar.content, "expected default value false"); + assert_eq!(bar.baz, "xxxx"); +} + +#[test] +#[cfg(not(feature = "update-expectations"))] +fn read_quick_xml_empty_explicit_element_uses_default() { + use quick_xml::Foo; + + let obj = crate::utils::quick_xml_read_test::( + "tests/feature/element_default_with_simple_content/example/empty_explicit.xml", + ); + + let bar = obj.bar.expect("bar should be present"); + assert!( + !bar.content, + "expected default value false for explicit empty tags" + ); + assert_eq!(bar.baz, "xxxx"); +} + +#[test] +#[cfg(not(feature = "update-expectations"))] +fn read_quick_xml_with_content() { + use quick_xml::Foo; + + let obj = crate::utils::quick_xml_read_test::( + "tests/feature/element_default_with_simple_content/example/with_content.xml", + ); + + let bar = obj.bar.expect("bar should be present"); + assert!(bar.content, "expected explicit value true"); + assert_eq!(bar.baz, "xxxx"); +} + +#[cfg(not(feature = "update-expectations"))] +mod quick_xml { + #![allow(unused_imports)] + + include!("expected/quick_xml.rs"); +} diff --git a/xsd-parser/tests/feature/element_default_with_simple_content/schema.xsd b/xsd-parser/tests/feature/element_default_with_simple_content/schema.xsd new file mode 100644 index 00000000..012899b2 --- /dev/null +++ b/xsd-parser/tests/feature/element_default_with_simple_content/schema.xsd @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/xsd-parser/tests/feature/mod.rs b/xsd-parser/tests/feature/mod.rs index 3f2c472a..233e0455 100644 --- a/xsd-parser/tests/feature/mod.rs +++ b/xsd-parser/tests/feature/mod.rs @@ -25,6 +25,7 @@ mod duplicate_idents; mod dynamic_type; mod dynamic_type_substitution_group; mod dynamic_type_substitution_group_overlap; +mod element_default_with_simple_content; mod element_refs_with_ns; mod element_without_type; mod empty_string; diff --git a/xsd-parser/tests/schema/onix/expected/quick_xml.rs b/xsd-parser/tests/schema/onix/expected/quick_xml.rs index ebd5faa8..7ba2b440 100644 --- a/xsd-parser/tests/schema/onix/expected/quick_xml.rs +++ b/xsd-parser/tests/schema/onix/expected/quick_xml.rs @@ -43546,6 +43546,12 @@ pub mod onix { pub sourcetype: Option, pub content: List219, } + impl CopyrightTypeElementType { + #[must_use] + pub fn default_content() -> List219 { + List219::C + } + } impl WithSerializer for CopyrightTypeElementType { type Serializer<'x> = quick_xml_serialize::CopyrightTypeElementTypeSerializer<'x>; fn serializer<'ser>( @@ -222498,6 +222504,7 @@ pub mod onix { #[derive(Debug)] enum CopyrightTypeElementTypeDeserializerState { Init__, + ContentInit__(::Deserializer), Content__(::Deserializer), Unknown__, } @@ -222559,6 +222566,8 @@ pub mod onix { ) -> Result<(), Error> { if let CopyrightTypeElementTypeDeserializerState::Content__(deserializer) = state { self.store_content(deserializer.finish(helper)?)?; + } else if let CopyrightTypeElementTypeDeserializerState::ContentInit__(_) = state { + self.store_content(super::CopyrightTypeElementType::default_content())?; } Ok(()) } @@ -222631,7 +222640,51 @@ pub mod onix { match replace(&mut *self.state__, S::Unknown__) { S::Unknown__ => unreachable!(), S::Init__ => { + if matches!(&event, Event::Empty(_)) { + self.store_content(super::CopyrightTypeElementType::default_content())?; + let data = self.finish(helper)?; + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(data), + event: DeserializerEvent::None, + allow_any: false, + }); + } let output = ContentDeserializer::init(helper, event)?; + let DeserializerOutput { + artifact, + event, + allow_any, + } = output; + match artifact { + DeserializerArtifact::Deserializer(deserializer) => { + *self.state__ = S::ContentInit__(deserializer); + Ok(DeserializerOutput { + artifact: DeserializerArtifact::Deserializer(self), + event, + allow_any, + }) + } + artifact => self.handle_content( + helper, + DeserializerOutput { + artifact, + event, + allow_any, + }, + ), + } + } + S::ContentInit__(deserializer) => { + if matches!(&event, Event::End(_)) { + self.store_content(super::CopyrightTypeElementType::default_content())?; + let data = self.finish(helper)?; + return Ok(DeserializerOutput { + artifact: DeserializerArtifact::Data(data), + event: DeserializerEvent::None, + allow_any: false, + }); + } + let output = deserializer.next(helper, event)?; self.handle_content(helper, output) } S::Content__(deserializer) => {