Handle namespaces in serializer in a more principled way
This commit is contained in:
parent
6238ed87c5
commit
110605ba99
|
@ -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)?;
|
||||
}
|
||||
|
||||
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 default_namespace = match &meta.ns.uri {
|
||||
Some(ns) => quote!(#ns),
|
||||
None => quote!(""),
|
||||
};
|
||||
|
||||
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)),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -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,23 +98,97 @@ 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);
|
||||
}
|
||||
|
||||
pub fn parent_default_namespace(&self) -> &'xml str {
|
||||
self.parent_default_namespace
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
if self.prefixes.contains_key(prefix.ns) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pub fn retrieve_parent_default_namespace(&mut self) {
|
||||
self.parent_default_namespace = self.parent_default_namespace_to_revert;
|
||||
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 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 prefix(&self, ns: &str) -> Option<&'static str> {
|
||||
self.prefixes.get(ns).copied()
|
||||
}
|
||||
|
||||
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)]
|
||||
|
|
Loading…
Reference in New Issue