Skip to content

Commit eb99d28

Browse files
committed
Implement proper support for dynamic types and substitution groups
The old implementation did only handle substitution groups correctly. If on would like to use dynamic types in combination with substitution groups, the generated code would be incorrect. This commit fixes that issue, by implementing proper support for dynamic types and substitution groups. Suitable tests have been added to cover the new functionality.
1 parent 21f7800 commit eb99d28

104 files changed

Lines changed: 33402 additions & 16785 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

xsd-parser-types/src/quick_xml/deserialize.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -949,12 +949,66 @@ impl DeserializeHelper {
949949
pub fn get_dynamic_type_name<'a>(
950950
&self,
951951
event: &'a Event<'_>,
952+
) -> Result<Option<Cow<'a, [u8]>>, Error> {
953+
if let Some(name) = self.get_dynamic_type_from_attrib(event)? {
954+
return Ok(Some(name));
955+
}
956+
957+
Ok(self.get_dynamic_type_from_tag(event))
958+
}
959+
960+
/// Extract the type name of a dynamic type from the xml tag name of the
961+
/// passed event.
962+
///
963+
/// This is used to dispatch dynamic types that are identified by the
964+
/// element name (e.g. substitution groups). Returns `None` if the event
965+
/// is not a [`Event::Start`] or [`Event::Empty`].
966+
#[must_use]
967+
pub fn get_dynamic_type_from_tag<'a>(&self, event: &'a Event<'_>) -> Option<Cow<'a, [u8]>> {
968+
let (Event::Start(b) | Event::Empty(b)) = &event else {
969+
return None;
970+
};
971+
972+
Some(Cow::Borrowed(b.name().0))
973+
}
974+
975+
/// Extract the type name of a dynamic type from the `xsi:type` attribute of
976+
/// the passed event.
977+
///
978+
/// This is used to dispatch dynamic types that are identified by their type
979+
/// (e.g. abstract types resolved via the `xsi:type` attribute). Returns
980+
/// `None` if the event is not a [`Event::Start`] or [`Event::Empty`], or if
981+
/// the tag does not carry a `xsi:type` attribute.
982+
///
983+
/// # Errors
984+
///
985+
/// Raise an error if the attributes of the tag could not be resolved.
986+
pub fn get_dynamic_type_from_attrib<'a>(
987+
&self,
988+
event: &'a Event<'_>,
952989
) -> Result<Option<Cow<'a, [u8]>>, Error> {
953990
let (Event::Start(b) | Event::Empty(b)) = &event else {
954991
return Ok(None);
955992
};
956993

957-
let attrib = b
994+
self.get_dynamic_type_from_attrib_bytes(b)
995+
}
996+
997+
/// Extract the type name of a dynamic type from the `xsi:type` attribute of
998+
/// the passed `BytesStart`.
999+
///
1000+
/// This is used to dispatch dynamic types that are identified by their type
1001+
/// (e.g. abstract types resolved via the `xsi:type` attribute). Returns
1002+
/// `None` if the tag does not carry a `xsi:type` attribute.
1003+
///
1004+
/// # Errors
1005+
///
1006+
/// Raise an error if the attributes of the tag could not be resolved.
1007+
pub fn get_dynamic_type_from_attrib_bytes<'a>(
1008+
&self,
1009+
bytes: &'a BytesStart<'_>,
1010+
) -> Result<Option<Cow<'a, [u8]>>, Error> {
1011+
let attrib = bytes
9581012
.attributes()
9591013
.find(|attrib| {
9601014
let Ok(attrib) = attrib else { return false };
@@ -969,9 +1023,7 @@ impl DeserializeHelper {
9691023
})
9701024
.transpose()?;
9711025

972-
let name = attrib.map_or_else(|| Cow::Borrowed(b.name().0), |attrib| attrib.value);
973-
974-
Ok(Some(name))
1026+
Ok(attrib.map(|attrib| attrib.value))
9751027
}
9761028

9771029
/// Initializes a deserializer from the passed `event`.

xsd-parser-types/src/quick_xml/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ pub use self::reader::{ErrorReader, IoReader, SliceReader, XmlReader, XmlReaderS
3131
pub use self::serialize::{
3232
BoxedSerializer, CollectNamespaces, ContentSerializer, DerefIter, IterSerializer,
3333
SerializeBytes, SerializeBytesToString, SerializeHelper, SerializeSync, Serializer,
34-
WithBoxedSerializer, WithSerializeToBytes, WithSerializer,
34+
WithBoxedSerializer, WithSerializeToBytes, WithSerializer, XsiTypeSerializer,
3535
};
3636

3737
#[cfg(feature = "async")]

xsd-parser-types/src/quick_xml/serialize.rs

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,121 @@ where
512512
}
513513
}
514514

515+
/* XsiTypeSerializer */
516+
517+
/// A [`Serializer`] that wraps another (boxed) serializer to serialize a value
518+
/// of a dynamic type that is dispatched via the `xsi:type` attribute.
519+
///
520+
/// The wrapped serializer emits the value using the element name of its actual
521+
/// (concrete) type. This serializer rewrites the resulting element so that it
522+
/// uses the dynamic type's base element `name` and moves the original element
523+
/// name into a `xsi:type` attribute instead. If the value is already using the
524+
/// base element name (i.e. it is the base type itself) no `xsi:type` attribute
525+
/// is added.
526+
#[derive(Debug)]
527+
pub struct XsiTypeSerializer<'ser, T> {
528+
inner: T,
529+
name: &'ser str,
530+
state: XsiTypeState,
531+
}
532+
533+
#[derive(Debug)]
534+
enum XsiTypeState {
535+
/// The opening element was not emitted yet.
536+
Pending,
537+
538+
/// The opening element was emitted, waiting for the matching closing
539+
/// element. The contained value is the current nesting depth.
540+
Open(usize),
541+
542+
/// The opening (and closing) element was emitted.
543+
Done,
544+
}
545+
546+
impl<'ser, T> XsiTypeSerializer<'ser, T> {
547+
/// Create a new [`XsiTypeSerializer`] that wraps the passed `inner`
548+
/// serializer and rewrites the emitted element to use the passed base
549+
/// element `name`.
550+
#[must_use]
551+
pub fn new(inner: T, name: &'ser str) -> Self {
552+
Self {
553+
inner,
554+
name,
555+
state: XsiTypeState::Pending,
556+
}
557+
}
558+
559+
/// Rewrite the opening element so that it uses the base element name and
560+
/// references its original (concrete) name via a `xsi:type` attribute.
561+
fn rewrite(&self, bytes: BytesStart<'ser>) -> BytesStart<'ser> {
562+
// The value uses the base element name already, so it is the base type
563+
// and does not need a `xsi:type` attribute.
564+
if bytes.name().as_ref() == self.name.as_bytes() {
565+
return bytes;
566+
}
567+
568+
let type_name = bytes.name().as_ref().to_owned();
569+
570+
let mut new = BytesStart::new(self.name);
571+
new.push_attribute((&b"xsi:type"[..], &type_name[..]));
572+
new.extend_attributes(bytes.attributes().filter_map(Result::ok));
573+
574+
new
575+
}
576+
}
577+
578+
impl<'ser, T> Serializer<'ser> for XsiTypeSerializer<'ser, T>
579+
where
580+
T: Serializer<'ser>,
581+
{
582+
fn next(&mut self, helper: &mut SerializeHelper) -> Option<Result<Event<'ser>, Error>> {
583+
let event = match self.inner.next(helper)? {
584+
Ok(event) => event,
585+
Err(error) => return Some(Err(error)),
586+
};
587+
588+
if matches!(self.state, XsiTypeState::Pending) {
589+
return Some(Ok(match event {
590+
Event::Empty(bytes) => {
591+
let bytes = self.rewrite(bytes);
592+
self.state = XsiTypeState::Done;
593+
594+
Event::Empty(bytes)
595+
}
596+
Event::Start(bytes) => {
597+
let bytes = self.rewrite(bytes);
598+
self.state = XsiTypeState::Open(1);
599+
600+
Event::Start(bytes)
601+
}
602+
other => {
603+
self.state = XsiTypeState::Done;
604+
605+
other
606+
}
607+
}));
608+
}
609+
610+
if let XsiTypeState::Open(depth) = &mut self.state {
611+
match &event {
612+
Event::Start(_) => *depth += 1,
613+
Event::End(_) => {
614+
*depth -= 1;
615+
616+
if *depth == 0 {
617+
self.state = XsiTypeState::Done;
618+
619+
return Some(Ok(Event::End(BytesEnd::new(self.name))));
620+
}
621+
}
622+
_ => (),
623+
}
624+
}
625+
626+
Some(Ok(event))
627+
}
628+
}
629+
515630
/// Trait for collecting namespaces of a type and all its sub-types.
516631
///
517632
/// Implement this trait to allow the dynamic serialization mode to discover which

xsd-parser-types/src/xml/nillable.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@ use std::ops::{Deref, DerefMut};
55
use quick_xml::events::{BytesStart, Event};
66

77
use crate::misc::Namespace;
8+
use crate::traits::WithNamespace;
89

910
#[cfg(feature = "quick-xml")]
1011
use crate::quick_xml::{
1112
CollectNamespaces, DeserializeHelper, Deserializer, DeserializerArtifact, DeserializerEvent,
1213
DeserializerOutput, DeserializerResult, Error, ErrorKind, SerializeHelper, Serializer,
1314
WithDeserializer, WithSerializer,
1415
};
15-
use crate::traits::WithNamespace;
1616

1717
/// Implements a nillable XML element.
1818
///

xsd-parser/src/config/generator.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ pub struct TypePostfix {
325325
/// Postfix for the type that is used as content for [`Nillable`](xsd_parser_types::xml::Nillable) elements.
326326
pub nillable_content: String,
327327

328-
/// Postfix for concrete elements in a substitution group.
328+
/// Postfix for concrete elements in a substitution group or concrete types of a dynamic type.
329329
pub dynamic_element: String,
330330
}
331331

xsd-parser/src/config/interpreter.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,11 @@ pub struct InterpreterConfig {
1616
/// See [`with_type`](crate::Interpreter::with_type) for more details.
1717
pub types: Vec<(IdentQuadruple, MetaType)>,
1818

19+
/// List of types to be interpreted as dynamic types.
20+
///
21+
/// See [`with_dynamic_type`](crate::Interpreter::with_dynamic_type) for more details.
22+
pub dynamic_types: Vec<IdentQuadruple>,
23+
1924
/// Additional flags to control the interpreter.
2025
pub flags: InterpreterFlags,
2126

@@ -30,6 +35,7 @@ impl Default for InterpreterConfig {
3035
fn default() -> Self {
3136
Self {
3237
types: vec![],
38+
dynamic_types: vec![],
3339
debug_output: None,
3440
flags: InterpreterFlags::BUILDIN_TYPES
3541
| InterpreterFlags::DEFAULT_TYPEDEFS
@@ -43,6 +49,7 @@ impl Clone for InterpreterConfig {
4349
fn clone(&self) -> Self {
4450
Self {
4551
types: self.types.clone(),
52+
dynamic_types: self.dynamic_types.clone(),
4653
debug_output: self.debug_output.clone(),
4754
flags: self.flags.clone(),
4855
naming: self.naming.as_deref().map(Naming::clone_boxed),

xsd-parser/src/config/mod.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -260,16 +260,7 @@ impl Config {
260260

261261
/// Enable code generation for [`quick_xml`] serialization.
262262
pub fn with_quick_xml_serialize(self) -> Self {
263-
self.with_render_steps([
264-
RenderStep::Types,
265-
RenderStep::Defaults,
266-
RenderStep::PrefixConstants,
267-
RenderStep::NamespaceConstants,
268-
RenderStep::QuickXmlSerialize {
269-
namespaces: NamespaceSerialization::Global,
270-
default_namespace: None,
271-
},
272-
])
263+
self.with_quick_xml_serialize_config(NamespaceSerialization::Global, None)
273264
}
274265

275266
/// Enable code generation for [`quick_xml`] serialization.
@@ -507,6 +498,33 @@ impl Config {
507498
self
508499
}
509500

501+
/// Add a list of types to be interpreted as dynamic types to the interpreter.
502+
///
503+
/// For details please refer to [`InterpreterConfig::dynamic_types`].
504+
pub fn with_dynamic_types<I>(mut self, types: I) -> Self
505+
where
506+
I: IntoIterator,
507+
I::Item: Into<IdentQuadruple>,
508+
{
509+
for ident in types {
510+
self.interpreter.dynamic_types.push(ident.into());
511+
}
512+
513+
self
514+
}
515+
516+
/// Add a type to be interpreted as a dynamic type to the interpreter.
517+
///
518+
/// For details please refer to [`InterpreterConfig::dynamic_types`].
519+
pub fn with_dynamic_type<T>(mut self, ident: T) -> Self
520+
where
521+
T: Into<IdentQuadruple>,
522+
{
523+
self.interpreter.dynamic_types.push(ident.into());
524+
525+
self
526+
}
527+
510528
/// Add a type to the interpreter that should be used to handle `xs:anySimpleType`.
511529
///
512530
/// This is a convenient method for adding support for `xs:anySimpleType` with a custom type.

xsd-parser/src/config/optimizer.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,5 +120,10 @@ bitflags! {
120120
///
121121
/// See [`replace_xs_any_type_with_any_element`](crate::Optimizer::replace_xs_any_type_with_any_element) for details.
122122
const REPLACE_XS_ANY_TYPE_WITH_ANY_ELEMENT = 1 << 16;
123+
124+
/// Whether to merge dynamic types or not.
125+
///
126+
/// See [`merge_dynamic_types`](crate::Optimizer::merge_dynamic_types) for details.
127+
const MERGE_DYNAMIC_TYPES = 1 << 17;
123128
}
124129
}

xsd-parser/src/lib.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,11 @@ pub fn exec_interpreter_with_ident_cache(
230230
interpreter = interpreter.with_type(ident, ty)?;
231231
}
232232

233+
for ident in config.dynamic_types {
234+
let ident = ident.resolve(schemas)?;
235+
interpreter = interpreter.with_dynamic_type(ident);
236+
}
237+
233238
let (types, ident_cache) = interpreter.finish()?;
234239

235240
if let Some(output) = config.debug_output {
@@ -285,7 +290,8 @@ pub fn exec_optimizer(config: OptimizerConfig, types: MetaTypes) -> Result<MetaT
285290
);
286291
exec!(REMOVE_EMPTY_ENUM_VARIANTS, remove_empty_enum_variants);
287292
exec!(REMOVE_EMPTY_ENUMS, remove_empty_enums);
288-
exec!(CONVERT_DYNAMIC_TO_CHOICE, convert_dynamic_to_choice);
293+
exec!(MERGE_DYNAMIC_TYPES, merge_dynamic_types);
294+
exec!(CONVERT_DYNAMIC_TO_CHOICE, convert_dynamics_to_choices);
289295
exec!(FLATTEN_COMPLEX_TYPES, flatten_complex_types);
290296
exec!(FLATTEN_UNIONS, flatten_unions);
291297
exec!(MERGE_ENUM_UNIONS, merge_enum_unions);
@@ -384,7 +390,11 @@ pub fn exec_generator_with_ident_cache<'types>(
384390
config.type_postfix.nillable_content,
385391
);
386392
generator = generator.with_type_postfix(
387-
IdentType::DynamicElement,
393+
IdentType::SubstitutionElement,
394+
config.type_postfix.dynamic_element.clone(),
395+
);
396+
generator = generator.with_type_postfix(
397+
IdentType::DynamicVariant,
388398
config.type_postfix.dynamic_element,
389399
);
390400

xsd-parser/src/meta_types_printer.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -191,12 +191,16 @@ impl<'a> MetaTypesPrinter<'a> {
191191
.as_ref()
192192
.map_or_else(|| String::from("None"), ToString::to_string)
193193
);
194-
indentln!("derived_types:");
195194

195+
indentln!("derived_types:");
196196
s.level += 1;
197-
198-
for meta in &*x.derived_types {
199-
indentln!("{}", meta.type_);
197+
for (key, value) in &x.derived_types {
198+
indentln!(
199+
"{key}={},relationship={:?},display_name={:?}",
200+
value.type_,
201+
value.relationship,
202+
value.display_name
203+
);
200204
}
201205

202206
s.level -= 2;
@@ -240,7 +244,7 @@ impl<'a> MetaTypesPrinter<'a> {
240244

241245
indentln!("ident={}", x.ident);
242246
indentln!("form={:?}", x.form);
243-
indentln!("nillable={:?}", x.nillable);
247+
indentln!("flags={:?}", x.flags);
244248
indentln!("min_occurs={}", x.min_occurs);
245249
indentln!("max_occurs={:?}", x.max_occurs);
246250

0 commit comments

Comments
 (0)