Handle namespaces in serializer in a more principled way

This commit is contained in:
Dirkjan Ochtman 2022-09-07 12:00:31 +02:00
parent 6238ed87c5
commit 110605ba99
2 changed files with 115 additions and 57 deletions

View File

@ -22,28 +22,25 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
_ => todo!(), _ => todo!(),
}; };
let mut prefixes = TokenStream::new(); let default_namespace = match &meta.ns.uri {
for (key, val) in &meta.ns.prefixes { Some(ns) => quote!(#ns),
prefixes.extend(quote!( None => quote!(""),
if serializer.parent_namespaces.get(#val).is_none() { };
serializer.write_prefix(#key, #val)?;
}
if let ::std::collections::hash_map::Entry::Vacant(v) = serializer.parent_namespaces.entry(#val) { let cx_len = meta.ns.prefixes.len();
v.insert(#key); let mut context = quote!(
// Will remove added namespaces when going "up" let mut new = ::instant_xml::ser::Context::<#cx_len>::default();
to_remove.push(#val); new.default_ns = #default_namespace;
}; );
for (i, (prefix, ns)) in meta.ns.prefixes.iter().enumerate() {
context.extend(quote!(
new.prefixes[#i] = ::instant_xml::ser::Prefix { ns: #ns, prefix: #prefix };
)); ));
} }
let ident = &input.ident; let ident = &input.ident;
let root_name = ident.to_string(); let root_name = ident.to_string();
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let default_namespace = match &meta.ns.uri {
Some(ns) => quote!(#ns),
None => quote!(""),
};
quote!( quote!(
impl #impl_generics ToXml for #ident #ty_generics #where_clause { impl #impl_generics ToXml for #ident #ty_generics #where_clause {
@ -52,14 +49,14 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
serializer: &mut instant_xml::Serializer<W>, serializer: &mut instant_xml::Serializer<W>,
) -> Result<(), instant_xml::Error> { ) -> Result<(), instant_xml::Error> {
// Start tag // Start tag
match serializer.parent_default_namespace() == #default_namespace { match serializer.default_ns() == #default_namespace {
true => serializer.write_start(None, #root_name, None)?, true => serializer.write_start(None, #root_name, None)?,
false => serializer.write_start(None, #root_name, Some(#default_namespace))?, false => serializer.write_start(None, #root_name, Some(#default_namespace))?,
} }
serializer.update_parent_default_namespace(#default_namespace); #context
let mut to_remove: Vec<&str> = Vec::new(); let old = serializer.push(new)?;
#prefixes
#attributes #attributes
serializer.end_start()?; serializer.end_start()?;
@ -67,12 +64,7 @@ pub fn to_xml(input: &syn::DeriveInput) -> proc_macro2::TokenStream {
// Close tag // Close tag
serializer.write_close(None, #root_name)?; serializer.write_close(None, #root_name)?;
serializer.retrieve_parent_default_namespace(); serializer.pop(old);
// Removing current namespaces
for it in to_remove {
serializer.parent_namespaces.remove(it);
}
Ok(()) Ok(())
} }
@ -118,10 +110,10 @@ fn process_named_field(
self.#field_value.serialize(serializer)?; self.#field_value.serialize(serializer)?;
} }
::instant_xml::Kind::Scalar => { ::instant_xml::Kind::Scalar => {
let (prefix, ns) = match serializer.parent_default_namespace() == #ns { let (prefix, ns) = match serializer.default_ns() == #ns {
true => (None, None), true => (None, None),
false => match serializer.parent_namespaces.get(#ns) { false => match serializer.prefix(#ns) {
Some(&prefix) => (Some(prefix), None), Some(prefix) => (Some(prefix), None),
None => (None, Some(#ns)), None => (None, Some(#ns)),
}, },
}; };

View File

@ -1,27 +1,29 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap; use std::collections::HashMap;
use std::fmt::{self}; use std::fmt::{self};
use std::mem;
use super::Error; use super::Error;
use crate::ToXml; use crate::ToXml;
pub struct Serializer<'xml, W: fmt::Write + ?Sized> { pub struct Serializer<'xml, W: fmt::Write + ?Sized> {
// For parent namespaces the key is the namespace and the value is the prefix. We are adding to map
// only if the namespaces do not exist, if it does exist then we are using an already defined parent prefix.
#[doc(hidden)]
pub parent_namespaces: HashMap<&'xml str, &'xml str>,
output: &'xml mut W, output: &'xml mut W,
parent_default_namespace: &'xml str, /// Map namespace keys to prefixes.
parent_default_namespace_to_revert: &'xml str, ///
/// The prefix map is updated using `Context` types that are held on the
/// stack in the relevant `ToXml` implementation. If a prefix is already
/// defined for a given namespace, we don't update the set the new prefix.
prefixes: HashMap<&'static str, &'static str>,
default_ns: &'static str,
state: State, state: State,
} }
impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> { impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
pub fn new(output: &'xml mut W) -> Self { pub fn new(output: &'xml mut W) -> Self {
Self { Self {
parent_namespaces: HashMap::new(),
output, output,
parent_default_namespace: "", prefixes: HashMap::new(),
parent_default_namespace_to_revert: "", default_ns: "",
state: State::Element, state: State::Element,
} }
} }
@ -63,16 +65,6 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
Ok(()) Ok(())
} }
pub fn write_prefix(&mut self, prefix: &str, ns: &str) -> Result<(), Error> {
if self.state != State::Attribute {
return Err(Error::UnexpectedState);
}
self.output
.write_fmt(format_args!(" xmlns:{prefix}=\"{ns}\""))?;
Ok(())
}
pub fn write_str<V: fmt::Display + ?Sized>(&mut self, value: &V) -> Result<(), Error> { pub fn write_str<V: fmt::Display + ?Sized>(&mut self, value: &V) -> Result<(), Error> {
if !matches!(self.state, State::Element | State::Scalar) { if !matches!(self.state, State::Element | State::Scalar) {
return Err(Error::UnexpectedState); return Err(Error::UnexpectedState);
@ -106,25 +98,99 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
Ok(()) Ok(())
} }
pub fn set_parent_default_namespace(&mut self, namespace: &'xml str) -> Result<(), Error> { pub fn push<const N: usize>(&mut self, new: Context<N>) -> Result<Context<N>, Error> {
self.parent_default_namespace = namespace; if self.state != State::Attribute {
Ok(()) return Err(Error::UnexpectedState);
}
let mut old = Context::default();
let prev = mem::replace(&mut self.default_ns, new.default_ns);
let _ = mem::replace(&mut old.default_ns, prev);
let mut used = 0;
for prefix in new.prefixes.into_iter() {
if prefix.prefix.is_empty() {
continue;
}
if self.prefixes.contains_key(prefix.ns) {
continue;
}
self.output
.write_fmt(format_args!(" xmlns:{}=\"{}\"", prefix.prefix, prefix.ns))?;
let prev = match self.prefixes.entry(prefix.ns) {
Entry::Occupied(mut entry) => mem::replace(entry.get_mut(), prefix.prefix),
Entry::Vacant(entry) => {
entry.insert(prefix.prefix);
""
}
};
old.prefixes[used] = Prefix {
ns: prefix.ns,
prefix: prev,
};
used += 1;
}
Ok(old)
} }
pub fn parent_default_namespace(&self) -> &'xml str { pub fn pop<const N: usize>(&mut self, old: Context<N>) {
self.parent_default_namespace let _ = mem::replace(&mut self.default_ns, old.default_ns);
for prefix in old.prefixes.into_iter() {
if prefix.ns.is_empty() && prefix.prefix.is_empty() {
continue;
}
let mut entry = match self.prefixes.entry(prefix.ns) {
Entry::Occupied(entry) => entry,
Entry::Vacant(_) => unreachable!(),
};
match prefix.prefix {
"" => {
entry.remove();
}
prev => {
let _ = mem::replace(entry.get_mut(), prev);
}
}
}
} }
pub fn update_parent_default_namespace(&mut self, namespace: &'xml str) { pub fn prefix(&self, ns: &str) -> Option<&'static str> {
self.parent_default_namespace_to_revert = self.parent_default_namespace; self.prefixes.get(ns).copied()
self.parent_default_namespace = namespace;
} }
pub fn retrieve_parent_default_namespace(&mut self) { pub fn default_ns(&self) -> &'static str {
self.parent_default_namespace = self.parent_default_namespace_to_revert; self.default_ns
} }
} }
#[derive(Debug)]
pub struct Context<const N: usize> {
pub default_ns: &'static str,
pub prefixes: [Prefix; N],
}
impl<const N: usize> Default for Context<N> {
fn default() -> Self {
Self {
default_ns: Default::default(),
prefixes: [Prefix { prefix: "", ns: "" }; N],
}
}
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Prefix {
pub prefix: &'static str,
pub ns: &'static str,
}
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
enum State { enum State {
Attribute, Attribute,