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

View File

@ -1,27 +1,29 @@
use std::collections::hash_map::Entry;
use std::collections::HashMap;
use std::fmt::{self};
use std::mem;
use super::Error;
use crate::ToXml;
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,
parent_default_namespace: &'xml str,
parent_default_namespace_to_revert: &'xml str,
/// Map namespace keys to prefixes.
///
/// 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,
}
impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
pub fn new(output: &'xml mut W) -> Self {
Self {
parent_namespaces: HashMap::new(),
output,
parent_default_namespace: "",
parent_default_namespace_to_revert: "",
prefixes: HashMap::new(),
default_ns: "",
state: State::Element,
}
}
@ -63,16 +65,6 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
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> {
if !matches!(self.state, State::Element | State::Scalar) {
return Err(Error::UnexpectedState);
@ -106,25 +98,99 @@ impl<'xml, W: fmt::Write + ?Sized> Serializer<'xml, W> {
Ok(())
}
pub fn set_parent_default_namespace(&mut self, namespace: &'xml str) -> Result<(), Error> {
self.parent_default_namespace = namespace;
Ok(())
pub fn push<const N: usize>(&mut self, new: Context<N>) -> Result<Context<N>, Error> {
if self.state != State::Attribute {
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 {
self.parent_default_namespace
pub fn pop<const N: usize>(&mut self, old: Context<N>) {
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) {
self.parent_default_namespace_to_revert = self.parent_default_namespace;
self.parent_default_namespace = namespace;
pub fn prefix(&self, ns: &str) -> Option<&'static str> {
self.prefixes.get(ns).copied()
}
pub fn retrieve_parent_default_namespace(&mut self) {
self.parent_default_namespace = self.parent_default_namespace_to_revert;
pub fn default_ns(&self) -> &'static str {
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)]
enum State {
Attribute,