Skip to content

Commit a2b429d

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 a2b429d

103 files changed

Lines changed: 33435 additions & 16770 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: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -512,6 +512,146 @@ 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+
is_root: bool,
531+
state: XsiTypeState,
532+
}
533+
534+
#[derive(Debug)]
535+
enum XsiTypeState {
536+
/// The serializer was just created, no event was emitted yet.
537+
Init,
538+
539+
/// The opening element was not emitted yet.
540+
Pending,
541+
542+
/// The opening element was emitted, waiting for the matching closing
543+
/// element. The contained value is the current nesting depth.
544+
Open(usize),
545+
546+
/// The opening (and closing) element was emitted.
547+
Done,
548+
}
549+
550+
impl<'ser, T> XsiTypeSerializer<'ser, T> {
551+
/// Create a new [`XsiTypeSerializer`] that wraps the passed `inner`
552+
/// serializer and rewrites the emitted element to use the passed base
553+
/// element `name`.
554+
#[must_use]
555+
pub fn new(inner: T, name: &'ser str, is_root: bool) -> Self {
556+
Self {
557+
inner,
558+
name,
559+
is_root,
560+
state: XsiTypeState::Init,
561+
}
562+
}
563+
564+
/// Rewrite the opening element so that it uses the base element name and
565+
/// references its original (concrete) name via a `xsi:type` attribute.
566+
fn rewrite(&self, bytes: BytesStart<'ser>, helper: &mut SerializeHelper) -> BytesStart<'ser> {
567+
// The value uses the base element name already, so it is the base type
568+
// and does not need a `xsi:type` attribute.
569+
if bytes.name().as_ref() == self.name.as_bytes() {
570+
return bytes;
571+
}
572+
573+
let type_name = bytes.name().as_ref().to_owned();
574+
575+
let mut new = BytesStart::new(self.name);
576+
577+
if self.is_root {
578+
helper.write_xmlns(&mut new, Some(&NamespacePrefix::XSI), &Namespace::XSI);
579+
}
580+
581+
new.push_attribute((&b"xsi:type"[..], &type_name[..]));
582+
new.extend_attributes(bytes.attributes().filter_map(|attr| {
583+
let attr = attr.ok()?;
584+
585+
if attr.key.as_ref() == b"xsi:type" || attr.key.as_ref() == b"xmlns:xsi" {
586+
None
587+
} else {
588+
Some(attr)
589+
}
590+
}));
591+
592+
new
593+
}
594+
}
595+
596+
impl<'ser, T> Serializer<'ser> for XsiTypeSerializer<'ser, T>
597+
where
598+
T: Serializer<'ser>,
599+
{
600+
fn next(&mut self, helper: &mut SerializeHelper) -> Option<Result<Event<'ser>, Error>> {
601+
let event = match self.inner.next(helper)? {
602+
Ok(event) => event,
603+
Err(error) => return Some(Err(error)),
604+
};
605+
606+
if matches!(self.state, XsiTypeState::Init) {
607+
self.state = XsiTypeState::Pending;
608+
helper.begin_ns_scope();
609+
}
610+
611+
if matches!(self.state, XsiTypeState::Pending) {
612+
return Some(Ok(match event {
613+
Event::Empty(bytes) => {
614+
let bytes = self.rewrite(bytes, helper);
615+
self.state = XsiTypeState::Done;
616+
617+
Event::Empty(bytes)
618+
}
619+
Event::Start(bytes) => {
620+
let bytes = self.rewrite(bytes, helper);
621+
self.state = XsiTypeState::Open(1);
622+
623+
Event::Start(bytes)
624+
}
625+
other => {
626+
self.state = XsiTypeState::Done;
627+
628+
other
629+
}
630+
}));
631+
}
632+
633+
if let XsiTypeState::Open(depth) = &mut self.state {
634+
match &event {
635+
Event::Start(_) => *depth += 1,
636+
Event::End(_) => {
637+
*depth -= 1;
638+
639+
if *depth == 0 {
640+
self.state = XsiTypeState::Done;
641+
642+
helper.end_ns_scope();
643+
644+
return Some(Ok(Event::End(BytesEnd::new(self.name))));
645+
}
646+
}
647+
_ => (),
648+
}
649+
}
650+
651+
Some(Ok(event))
652+
}
653+
}
654+
515655
/// Trait for collecting namespaces of a type and all its sub-types.
516656
///
517657
/// 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

0 commit comments

Comments
 (0)