Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions xsd-parser-types/src/quick_xml/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,30 @@ impl SerializeHelper {
self.add_namespace_entry(bytes, prefix.map(|x| &x.0[..]), namespace.clone());
}

/// Write a suitable `xmlns` attribute for the given `namespace`, deriving
/// the prefix from the element `tag`.
///
/// If `tag` contains a colon (e.g. `"tns:Element"`), the part before the
/// colon is used as the namespace prefix (`xmlns:tns="..."`). Otherwise, a
/// default namespace declaration is emitted (`xmlns="..."`).
pub fn write_xmlns_for_tag(
&mut self,
bytes: &mut BytesStart<'_>,
tag: &str,
namespace: &Namespace,
) {
match tag.split_once(':') {
Some((prefix, _)) => {
let prefix = NamespacePrefix::from(prefix.as_bytes().to_vec());

self.write_xmlns(bytes, Some(&prefix), namespace);
}
None => {
self.write_xmlns(bytes, None, namespace);
}
}
}

/// Write the passed `attrib` to the passed `bytes` object.
///
/// # Errors
Expand Down
163 changes: 163 additions & 0 deletions xsd-parser/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1047,3 +1047,166 @@ impl Schemas {
None
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn with_quick_xml_serialize_called_twice_no_duplicates() {
let config = Config::default()
.with_quick_xml_serialize()
.with_quick_xml_serialize();

assert_eq!(
count_step(&config, "Types"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "Defaults"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "PrefixConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "NamespaceConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "QuickXmlSerialize"),
1,
"steps: {:#?}",
step_names(&config)
);
}

#[test]
fn with_quick_xml_deserialize_called_twice_no_duplicates() {
let config = Config::default()
.with_quick_xml_deserialize()
.with_quick_xml_deserialize();

assert_eq!(
count_step(&config, "Types"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "Defaults"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "NamespaceConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "QuickXmlDeserialize"),
1,
"steps: {:#?}",
step_names(&config)
);
}

#[test]
fn with_quick_xml_called_twice_no_duplicates() {
let config = Config::default().with_quick_xml().with_quick_xml();

assert_eq!(
count_step(&config, "Types"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "Defaults"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "PrefixConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "NamespaceConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "QuickXmlSerialize"),
1,
"steps: {:#?}",
step_names(&config)
);
assert_eq!(
count_step(&config, "QuickXmlDeserialize"),
1,
"steps: {:#?}",
step_names(&config)
);
}

#[test]
fn with_advanced_enums_called_twice_no_duplicates() {
let config = Config::default()
.with_advanced_enums()
.with_advanced_enums();

assert_eq!(
count_step(&config, "EnumConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
}

#[test]
fn with_render_step_replaces_same_step() {
let config = Config::default()
.with_render_step(RenderStep::PrefixConstants)
.with_render_step(RenderStep::PrefixConstants);

assert_eq!(
count_step(&config, "PrefixConstants"),
1,
"steps: {:#?}",
step_names(&config)
);
}

fn step_names(config: &Config) -> Vec<String> {
config
.renderer
.steps
.iter()
.map(|s| format!("{s:?}"))
.collect()
}

fn count_step(config: &Config, name: &str) -> usize {
config
.renderer
.steps
.iter()
.filter(|s| format!("{s:?}").starts_with(name))
.count()
}
}
9 changes: 8 additions & 1 deletion xsd-parser/src/config/renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,8 @@ impl RenderStepConfig for RenderStep {

fn is_mutual_exclusive_to(&self, other: &dyn RenderStepConfig) -> bool {
use crate::pipeline::renderer::{
DefaultsRenderStep, NamespaceConstantsRenderStep, QuickXmlCollectNamespacesRenderStep,
DefaultsRenderStep, EnumConstantsRenderStep, NamespaceConstantsRenderStep,
PrefixConstantsRenderStep, QuickXmlCollectNamespacesRenderStep,
QuickXmlDeserializeRenderStep, QuickXmlSerializeRenderStep,
SerdeQuickXmlTypesRenderStep, SerdeXmlRsV7TypesRenderStep, SerdeXmlRsV8TypesRenderStep,
TypesRenderStep, WithNamespaceTraitRenderStep,
Expand All @@ -318,6 +319,8 @@ impl RenderStepConfig for RenderStep {
(Self::TypesSerdeQuickXml, Some(Self::TypesSerdeQuickXml)) => true,
(Self::Defaults, Some(Self::Defaults)) => true,
(Self::NamespaceConstants, Some(Self::NamespaceConstants)) => true,
(Self::EnumConstants, Some(Self::EnumConstants)) => true,
(Self::PrefixConstants, Some(Self::PrefixConstants)) => true,
(Self::WithNamespaceTrait, Some(Self::WithNamespaceTrait)) => true,
(Self::QuickXmlSerialize { .. }, Some(Self::QuickXmlSerialize { .. })) => true,
(Self::QuickXmlDeserialize { .. }, Some(Self::QuickXmlDeserialize { .. })) => true,
Expand Down Expand Up @@ -351,6 +354,8 @@ impl RenderStepConfig for RenderStep {
(Self::QuickXmlDeserialize { .. }, None) => {
other_id == TypeId::of::<QuickXmlDeserializeRenderStep>()
}
(Self::EnumConstants, None) => other_id == TypeId::of::<EnumConstantsRenderStep>(),
(Self::PrefixConstants, None) => other_id == TypeId::of::<PrefixConstantsRenderStep>(),
(Self::QuickXmlCollectNamespaces, None) => {
other_id == TypeId::of::<QuickXmlCollectNamespacesRenderStep>()
}
Expand All @@ -370,6 +375,8 @@ impl RenderStep {
)
| (Self::Defaults, Self::Defaults)
| (Self::NamespaceConstants, Self::NamespaceConstants)
| (Self::EnumConstants, Self::EnumConstants)
| (Self::PrefixConstants, Self::PrefixConstants)
| (Self::WithNamespaceTrait, Self::WithNamespaceTrait)
| (Self::QuickXmlSerialize { .. }, Self::QuickXmlSerialize { .. })
| (Self::QuickXmlDeserialize { .. }, Self::QuickXmlDeserialize { .. })
Expand Down
87 changes: 61 additions & 26 deletions xsd-parser/src/pipeline/renderer/steps/quick_xml/serialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,29 +689,36 @@ impl ComplexBase<'_> {
let mut xmlns = Vec::new();
let mut cache = HashSet::new();

let element_module = self
.tag_name
.as_ref()
.and_then(|tag| {
let module = tag.module?;
let form = if matches!((&default_namespace, &module.namespace), (Some(a), Some(b)) if a == b) {
FormChoiceType::Unqualified
} else {
tag.form
};
// Emit the element's own namespace using write_xmlns_for_tag so
// the prefix is derived from the tag name at runtime.
if let Some(module) = self.tag_name.as_ref().and_then(|tag| {
let module = tag.module?;
let dominated_by_default = matches!(
(&default_namespace, &module.namespace),
(Some(a), Some(b)) if a == b
);

(!dominated_by_default || tag.form == FormChoiceType::Qualified)
.then_some(module)
}) {
cache.insert(module.namespace_id);

if let Some(path) = module.make_ns_const() {
let ns_const = ctx.resolve_type_for_serialize_module(&path);

xmlns.push(quote! {
helper.write_xmlns_for_tag(&mut bytes, self.name, &#ns_const);
});
}
}

Some((form, module))
})
.into_iter();
let attribute_modules = attributes
.iter()
.filter_map(|attrib| {
(attrib.tag_name.form == FormChoiceType::Qualified)
.then_some(attrib.tag_name.module?)
})
.map(|module| (FormChoiceType::Qualified, module));
// Emit attribute namespaces with their own fixed prefix.
let attribute_modules = attributes.iter().filter_map(|attrib| {
(attrib.tag_name.form == FormChoiceType::Qualified)
.then_some(attrib.tag_name.module?)
});

for (form, module) in element_module.chain(attribute_modules) {
for module in attribute_modules {
if !cache.insert(module.namespace_id) {
continue;
}
Expand All @@ -721,9 +728,8 @@ impl ComplexBase<'_> {
};

let ns_const = ctx.resolve_type_for_serialize_module(&path);
let prefix_const = (form == FormChoiceType::Qualified)
.then(|| module.make_prefix_const())
.flatten()
let prefix_const = module
.make_prefix_const()
.map(|path| ctx.resolve_type_for_serialize_module(&path))
.map_or_else(|| quote!(None), |x| quote!(Some(&#x)));

Expand All @@ -750,6 +756,10 @@ impl ComplexBase<'_> {
let mut collector = collector.borrow_mut();

let xmlns = self.tag_name.as_ref().and_then(|x| x.render_xmlns(ctx));
let root_xmlns = self
.tag_name
.as_ref()
.and_then(|x| x.render_root_xmlns(ctx));

let global_xmlns = collector
.get_namespaces(ctx.types, ctx.ident, default_namespace.as_ref())
Expand All @@ -770,9 +780,10 @@ impl ComplexBase<'_> {
}
})
.collect::<Vec<_>>();
let global_xmlns = global_xmlns.is_empty().not().then(|| {
let global_xmlns = (root_xmlns.is_some() || !global_xmlns.is_empty()).then(|| {
quote! {
if self.is_root {
#root_xmlns
#( #global_xmlns )*
}
}
Expand All @@ -787,6 +798,10 @@ impl ComplexBase<'_> {
}
NamespaceSerialization::Dynamic => {
let xmlns = self.tag_name.as_ref().and_then(|x| x.render_xmlns(ctx));
let root_xmlns = self
.tag_name
.as_ref()
.and_then(|x| x.render_root_xmlns(ctx));

let collect_namespaces = resolve_quick_xml_ident!(
ctx,
Expand All @@ -796,6 +811,7 @@ impl ComplexBase<'_> {
Some(quote! {
#xmlns
if self.is_root {
#root_xmlns
#collect_namespaces::collect_namespaces(self.value, helper, &mut bytes);
}
})
Expand Down Expand Up @@ -1399,7 +1415,26 @@ impl TagName<'_> {
let ns = module.make_ns_const()?;
let ns_const = ctx.resolve_type_for_serialize_module(&ns);

Some(quote!(helper.write_xmlns(&mut bytes, None, &#ns_const);))
Some(quote!(helper.write_xmlns_for_tag(&mut bytes, self.name, &#ns_const);))
}

/// Generates a namespace declaration for unqualified elements that should
/// only be emitted on the root element.
///
/// In XSD, global (root) elements are always namespace-qualified regardless
/// of `elementFormDefault`. This method returns a token stream that writes
/// the default namespace (`xmlns="..."`) when the element has an unqualified
/// form but still needs to declare its namespace on the root element.
fn render_root_xmlns(&self, ctx: &mut Context<'_, '_>) -> Option<TokenStream> {
if self.form == FormChoiceType::Qualified {
return None;
}

let module = self.module?;
let ns = module.make_ns_const()?;
let ns_const = ctx.resolve_type_for_serialize_module(&ns);

Some(quote!(helper.write_xmlns_for_tag(&mut bytes, self.name, &#ns_const);))
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -381,7 +381,12 @@ pub mod quick_xml_serialize {
match &mut *self.state {
AnyTypeSerializerState::Init__ => {
*self.state = AnyTypeSerializerState::Done__;
let bytes = ::xsd_parser_types::quick_xml::BytesStart::new(self.name);
let mut bytes = ::xsd_parser_types::quick_xml::BytesStart::new(self.name);
helper.begin_ns_scope();
if self.is_root {
helper.write_xmlns_for_tag(&mut bytes, self.name, &super::NS_XS);
}
helper.end_ns_scope();
return Ok(Some(::xsd_parser_types::quick_xml::Event::Empty(bytes)));
}
AnyTypeSerializerState::Done__ => return Ok(None),
Expand Down
3 changes: 2 additions & 1 deletion xsd-parser/tests/feature/any_simple_type/example/custom.xml
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<Foo xmlns:xs="http://www.w3.org/2001/XMLSchema"
<Foo xmlns="http://example.com"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
TestAttrib="87654321">
<Value xsi:type="xs:string">Test</Value>
Expand Down
Loading
Loading