use std::borrow::Cow;
use std::fmt::Write as _;
use deser_core::ext::Number;
use deser_core::hints::Layout;
use deser_core::ser::SerializeRef;
use deser_core::ser::{self, Describe, EventSink, SerializeDriver};
use deser_core::{Atom, BytesFormat, Error, ErrorKind, Event, Serialize, State};
use crate::Names;
use crate::de::XML_NAMESPACE;
use crate::mixed::KeepsWhitespace;
use crate::root::{Declarations, RootData};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
#[non_exhaustive]
pub enum Indent {
#[default]
None,
Spaces(usize),
Tab,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SerializerConfig {
names: Names,
root: Option<&'static str>,
declaration: bool,
indent: Indent,
context: deser_core::Context,
}
impl Default for SerializerConfig {
fn default() -> SerializerConfig {
SerializerConfig::new()
}
}
impl SerializerConfig {
pub const fn new() -> SerializerConfig {
SerializerConfig {
names: Names::new(),
root: None,
declaration: false,
indent: Indent::None,
context: deser_core::Context::new(),
}
}
pub const fn builder() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
pub const fn into_builder(self) -> SerializerConfigBuilder {
SerializerConfigBuilder { value: self }
}
pub fn set_context(&mut self, context: deser_core::Context) {
self.context = context;
}
pub fn context(&self) -> &deser_core::Context {
&self.context
}
#[inline]
fn apply_context(&self, driver: &mut SerializeDriver<'_>) {
if !self.context.is_empty() {
driver.set_default_context(self.context.clone());
}
}
pub const fn set_root(&mut self, name: &'static str) {
self.root = Some(name);
}
pub const fn set_attribute_prefix(&mut self, prefix: &'static str) {
self.names.attribute_prefix = prefix;
}
pub const fn set_text_key(&mut self, key: &'static str) {
self.names.text_key = key;
}
pub const fn namespaces(
mut self,
namespaces: &'static [(&'static str, &'static str)],
) -> SerializerConfig {
self.names.namespaces = namespaces;
self
}
pub const fn set_declaration(&mut self, yes: bool) {
self.declaration = yes;
}
pub const fn set_indent(&mut self, indent: Indent) {
self.indent = indent;
}
pub const fn set_pretty(&mut self, indent: Indent) {
self.set_indent(indent);
}
pub fn to_string<T: Serialize + ?Sized>(&self, value: &T) -> Result<String, Error> {
self.to_string_ref(SerializeRef::new(&value))
}
pub fn to_string_with<F, T: Serialize + ?Sized>(
&self,
value: &T,
setup: F,
) -> Result<String, Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
let mut driver = SerializeDriver::new(&value);
setup(&mut driver);
self.apply_context(&mut driver);
self.serialize_driver(&mut driver)
}
fn to_string_ref(&self, value: SerializeRef<'_>) -> Result<String, Error> {
let mut driver = SerializeDriver::from_ref(value);
self.apply_context(&mut driver);
self.serialize_driver(&mut driver)
}
fn serialize_driver(&self, driver: &mut SerializeDriver<'_>) -> Result<String, Error> {
let mut writer = Writer::new(self, BytesFormat::of(driver.state()));
driver.drive_sink(&mut writer)?;
writer.finish()?;
Ok(writer.out)
}
pub(crate) fn serialize_part(
&self,
value: &mut Option<Box<Writer>>,
driver: &mut SerializeDriver<'_>,
out: &mut String,
limit: usize,
) -> Result<bool, Error> {
if value.is_none() && limit == usize::MAX {
let document = self.serialize_driver(driver)?;
if out.is_empty() {
*out = document;
} else {
out.push_str(&document);
}
return Ok(true);
}
let mut writer = value
.take()
.unwrap_or_else(|| Box::new(Writer::new(self, BytesFormat::of(driver.state()))));
let done = if limit == usize::MAX {
driver.drive_sink(&mut *writer)?;
true
} else {
writer.limit = limit;
driver.drive_until(&mut *writer)?
};
if let Some(err) = writer.error.take() {
return Err(err);
}
if done {
writer.finish()?;
out.push_str(&writer.out);
return Ok(true);
}
writer.pass_on(out);
*value = Some(writer);
Ok(false)
}
}
#[derive(Debug, Clone)]
#[must_use]
pub struct SerializerConfigBuilder {
value: SerializerConfig,
}
impl SerializerConfigBuilder {
pub const fn new() -> SerializerConfigBuilder {
SerializerConfigBuilder {
value: SerializerConfig::new(),
}
}
pub const fn root(mut self, name: &'static str) -> SerializerConfigBuilder {
self.value.set_root(name);
self
}
pub const fn attribute_prefix(mut self, prefix: &'static str) -> SerializerConfigBuilder {
self.value.set_attribute_prefix(prefix);
self
}
pub const fn text_key(mut self, key: &'static str) -> SerializerConfigBuilder {
self.value.set_text_key(key);
self
}
pub const fn declaration(mut self, yes: bool) -> SerializerConfigBuilder {
self.value.set_declaration(yes);
self
}
pub const fn indent(mut self, indent: Indent) -> SerializerConfigBuilder {
self.value.set_indent(indent);
self
}
pub const fn pretty(mut self, indent: Indent) -> SerializerConfigBuilder {
self.value.set_pretty(indent);
self
}
pub fn context(mut self, context: deser_core::Context) -> SerializerConfigBuilder {
self.value.set_context(context);
self
}
pub const fn build(self) -> SerializerConfig {
let value = unsafe { core::ptr::read(&self.value) };
core::mem::forget(self);
value
}
}
impl Default for SerializerConfigBuilder {
fn default() -> SerializerConfigBuilder {
SerializerConfigBuilder::new()
}
}
pub struct Serializer {
config: SerializerConfig,
out: String,
written: bool,
document: Option<Box<Writer>>,
in_progress: bool,
}
impl Default for Serializer {
fn default() -> Serializer {
Serializer::new()
}
}
impl Clone for Serializer {
fn clone(&self) -> Serializer {
Serializer {
config: self.config.clone(),
out: self.out.clone(),
written: self.written,
document: None,
in_progress: self.in_progress,
}
}
}
impl std::fmt::Debug for Serializer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Serializer")
.field("config", &self.config)
.field("output", &self.out)
.field("written", &self.written)
.field("in_progress", &self.in_progress)
.finish()
}
}
impl Serializer {
pub fn new() -> Serializer {
Serializer::with_config(SerializerConfig::new())
}
pub fn with_config(config: SerializerConfig) -> Serializer {
Serializer {
config,
out: String::new(),
written: false,
document: None,
in_progress: false,
}
}
pub fn config(&self) -> &SerializerConfig {
&self.config
}
pub fn written(&self) -> bool {
self.written
}
pub fn serialize<T: Serialize + ?Sized>(&mut self, value: &T) -> Result<(), Error> {
ser::Serializer::serialize(self, value)
}
pub fn serialize_with<F, T: Serialize + ?Sized>(
&mut self,
value: &T,
setup: F,
) -> Result<(), Error>
where
F: FnOnce(&mut SerializeDriver<'_>),
{
ser::Serializer::serialize_with(self, value, setup)
}
pub fn as_str(&self) -> &str {
&self.out
}
pub fn finish(self) -> String {
self.out
}
}
impl ser::Serializer for Serializer {
fn drive(&mut self, driver: &mut SerializeDriver<'_>) -> Result<(), Error> {
if self.in_progress {
return Err(Error::in_progress());
}
ser::StreamSerializer::drive_partial(self, driver, usize::MAX).map(|_| ())
}
}
impl ser::StreamSerializer for Serializer {
fn output(&self) -> &[u8] {
self.out.as_bytes()
}
fn clear_output(&mut self) {
self.out.clear();
}
fn supports_partial(&self) -> bool {
true
}
fn drive_partial(
&mut self,
driver: &mut SerializeDriver<'_>,
limit: usize,
) -> Result<bool, Error> {
if !self.config.context.is_empty() {
driver.set_default_context(self.config.context.clone());
}
if self.document.is_none() {
if self.in_progress {
return Err(Error::in_progress());
}
if self.written {
return Err(Error::new(
ErrorKind::InvalidState,
"an XML document holds a single root element",
));
}
}
if !self
.config
.serialize_part(&mut self.document, driver, &mut self.out, limit)?
{
self.in_progress = true;
return Ok(false);
}
self.in_progress = false;
self.written = true;
Ok(true)
}
fn in_progress(&self) -> bool {
self.in_progress
}
}
#[cfg(feature = "io")]
impl SerializerConfig {
pub fn writer<W: std::io::Write>(&self, writer: W) -> deser_core::io::Writer<W, Serializer> {
deser_core::io::Writer::new(writer, Serializer::with_config(self.clone()))
}
pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
&self,
writer: W,
value: &T,
) -> Result<(), Error> {
self.writer(writer).write(value)
}
}
#[cfg(feature = "io")]
pub fn to_writer<W: std::io::Write, T: Serialize + ?Sized>(
writer: W,
value: &T,
) -> Result<(), Error> {
SerializerConfig::new().to_writer(writer, value)
}
pub fn to_string<T: Serialize + ?Sized>(value: &T) -> Result<String, Error> {
SerializerConfig::new().to_string(value)
}
enum Frame {
Element(Element),
Items {
name: String,
compact: bool,
started: bool,
},
}
#[derive(Clone, Copy, PartialEq, Eq)]
enum Lines {
Pending,
Indented,
Inline,
}
struct Element {
name: String,
attrs_at: usize,
content: bool,
late: String,
attrs: Attrs,
fields: Option<(&'static [&'static str], usize)>,
bindings: usize,
lines: Lines,
expanded: bool,
}
enum Attrs {
Unknown,
Until(usize),
Done,
}
enum Key {
Attribute {
name: String,
last: bool,
},
Text,
Element(String),
}
pub(crate) struct Writer {
config: SerializerConfig,
bytes: BytesFormat,
out: String,
base: usize,
limit: usize,
error: Option<Error>,
stack: Vec<Frame>,
key: Option<Key>,
root_bindings: Vec<(String, String)>,
root_declared: bool,
root_declarations: Option<usize>,
local_bindings: Vec<(String, String)>,
depth: usize,
}
#[derive(Default)]
struct TypeName(Option<String>);
impl Describe for TypeName {
fn structure(&mut self, name: &str) {
self.0.get_or_insert_with(|| name.to_string());
}
fn newtype(&mut self, name: &str) {
self.0.get_or_insert_with(|| name.to_string());
}
fn tuple_struct(&mut self, name: &str) {
self.0.get_or_insert_with(|| name.to_string());
}
fn unit_struct(&mut self, name: &str) {
self.0.get_or_insert_with(|| name.to_string());
}
}
#[derive(Default)]
struct Fields {
names: Option<&'static [&'static str]>,
variant: bool,
}
impl Describe for Fields {
fn structure(&mut self, _name: &str) {
self.names = None;
}
fn fields(&mut self, names: &'static [&'static str]) {
self.names = Some(names);
}
fn variant(&mut self, _variant: &deser_core::ser::Variant<'_>) {
self.variant = true;
}
}
impl EventSink for Writer {
const DESCRIBED: bool = true;
fn event(
&mut self,
event: Event<'_>,
value: SerializeRef<'_>,
state: &mut State,
) -> Result<(), Error> {
Writer::event(self, event, value, state)
}
fn pause(&mut self) -> bool {
if self.final_until() - self.base < self.limit {
return false;
}
if let Err(err) = self.final_len() {
self.error = Some(err);
}
true
}
}
impl Writer {
fn new(config: &SerializerConfig, bytes: BytesFormat) -> Writer {
let mut out = String::new();
if config.declaration {
out.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
if config.indent != Indent::None {
out.push('\n');
}
}
Writer {
config: config.clone(),
bytes,
out,
base: 0,
limit: usize::MAX,
error: None,
stack: Vec::new(),
key: None,
root_bindings: std::iter::once(("xml", XML_NAMESPACE))
.chain(config.names.namespaces.iter().copied())
.map(|(prefix, uri)| (prefix.to_string(), uri.to_string()))
.collect(),
root_declared: false,
root_declarations: None,
local_bindings: Vec::new(),
depth: 0,
}
}
fn event(
&mut self,
event: Event<'_>,
value: SerializeRef<'_>,
state: &State,
) -> Result<(), Error> {
match self.stack.last_mut() {
None => self.root(event, value, state)?,
Some(Frame::Items { .. }) => self.item(event, value, state)?,
Some(Frame::Element(element)) => match self.key.take() {
None => {
if matches!(event, Event::Atom(_)) && keeps_whitespace(state) {
element.lines = Lines::Inline;
}
self.key(event)?
}
Some(key) => self.entry(key, event, value, state)?,
},
}
Ok(())
}
fn root(
&mut self,
event: Event<'_>,
value: SerializeRef<'_>,
state: &State,
) -> Result<(), Error> {
let root = state.event::<RootData>();
if let Some(root) = root {
self.bind_root(&root.namespaces)?;
}
let name = match root.and_then(|root| root.name.clone()) {
Some(name) => name,
None => {
let mut name = TypeName::default();
value.describe(&mut name);
name.0
.or_else(|| self.config.root.map(str::to_string))
.ok_or_else(|| {
Error::new(
ErrorKind::UnsupportedType,
"the name of the root element is unknown (see deser_xml::Root)",
)
})?
}
};
check_element_name(&name)?;
match event {
Event::MapStart(_) => self.open_element(&name, value, state),
Event::Atom(atom) => self.atom_element(&name, &atom, true, state),
_ => Err(Error::new(
ErrorKind::UnsupportedType,
"the root element must be a map or a single value",
)),
}
}
fn bind_root(&mut self, namespaces: &[(String, String)]) -> Result<(), Error> {
let mut bindings = vec![self.root_bindings[0].clone()];
for (prefix, uri) in namespaces {
check_prefix(prefix)?;
if !bindings.iter().any(|(x, _)| x == prefix) {
bindings.push((prefix.clone(), uri.clone()));
}
}
for binding in self.root_bindings.drain(1..) {
if !bindings.iter().any(|(x, _)| *x == binding.0) {
bindings.push(binding);
}
}
self.root_bindings = bindings;
Ok(())
}
fn key(&mut self, event: Event<'_>) -> Result<(), Error> {
let key = match event {
Event::MapEnd => return self.close_element(),
Event::Atom(ref atom) => match self.text(atom)? {
Some(key) => key.into_owned(),
None => return Err(unsupported_key()),
},
_ => return Err(unsupported_key()),
};
let Some(Frame::Element(element)) = self.stack.last_mut() else {
unreachable!()
};
let mut is_last = false;
if let Some((names, cursor)) = &mut element.fields {
match names[*cursor..].iter().position(|name| *name == key) {
Some(offset) => {
*cursor += offset + 1;
if let Attrs::Until(last) = element.attrs {
is_last = *cursor == last + 1;
if *cursor > last + 1 {
element.attrs = Attrs::Done;
}
}
}
None => {
element.fields = None;
if matches!(element.attrs, Attrs::Until(_)) {
element.attrs = Attrs::Unknown;
}
}
}
}
let names = &self.config.names;
self.key = Some(if key == names.text_key {
Key::Text
} else if let Some(name) = attribute_name(names, &key) {
check_element_name(name)?;
Key::Attribute {
name: name.to_string(),
last: is_last,
}
} else {
check_element_name(&key)?;
Key::Element(key)
});
self.settle();
Ok(())
}
fn entry(
&mut self,
key: Key,
event: Event<'_>,
value: SerializeRef<'_>,
state: &State,
) -> Result<(), Error> {
match (key, event) {
(Key::Attribute { name, last }, Event::Atom(atom)) => {
if let Some(text) = self.text(&atom)? {
let text = text.into_owned();
self.attribute(&name, &text)?;
}
if last && let Some(Frame::Element(element)) = self.stack.last_mut() {
element.attrs = Attrs::Done;
self.settle();
}
Ok(())
}
(Key::Text, Event::Atom(atom)) => {
if let Some(text) = self.text(&atom)? {
let text = text.into_owned();
self.close_start_tag();
if !text.is_empty() {
let Some(Frame::Element(element)) = self.stack.last_mut() else {
unreachable!()
};
element.lines = Lines::Inline;
}
escape(&text, false, &mut self.out)?;
}
Ok(())
}
(Key::Element(name), Event::Atom(atom)) => {
self.atom_element(&name, &atom, false, state)
}
(Key::Element(name), Event::MapStart(_)) => {
self.before_child();
self.open_element(&name, value, state)
}
(Key::Element(name), Event::SeqStart(_)) => {
self.stack.push(Frame::Items {
name,
compact: Layout::of(state) == Layout::Compact,
started: false,
});
Ok(())
}
(Key::Attribute { name, .. }, _) => Err(Error::new(
ErrorKind::UnsupportedType,
format!("attribute `{name}` must be a single value"),
)),
(Key::Text, _) => Err(Error::new(
ErrorKind::UnsupportedType,
"the text of an element must be a single value",
)),
(Key::Element(_), _) => unreachable!("ends are handled by the frames"),
}
}
fn item(
&mut self,
event: Event<'_>,
value: SerializeRef<'_>,
state: &State,
) -> Result<(), Error> {
let Some(Frame::Items { name, .. }) = self.stack.last() else {
unreachable!()
};
let name = name.clone();
match event {
Event::SeqEnd => {
self.stack.pop();
Ok(())
}
Event::Atom(Atom::Null) => {
self.before_child();
let bindings = self.local_bindings.len();
self.start_tag(&name, false, state)?;
self.out.push_str("/>");
self.local_bindings.truncate(bindings);
Ok(())
}
Event::Atom(atom) => self.atom_element(&name, &atom, false, state),
Event::MapStart(_) => {
self.before_child();
self.open_element(&name, value, state)
}
_ => Err(Error::new(
ErrorKind::UnsupportedType,
"sequences in sequences are not supported",
)),
}
}
fn attribute(&mut self, name: &str, value: &str) -> Result<(), Error> {
let mut declarations = String::new();
let name = self.qualify(name, true, &mut declarations)?;
let Some(Frame::Element(element)) = self.stack.last_mut() else {
unreachable!()
};
let out = if !element.content {
&mut self.out
} else if element.attrs_at >= self.base {
&mut element.late
} else {
return Err(Error::new(
ErrorKind::InvalidValue,
format!(
"attribute `{name}` comes after the start tag of the element was written \
(the fields the value described are not the keys it has)"
),
));
};
out.push_str(&declarations);
out.push(' ');
out.push_str(&name);
out.push_str("=\"");
escape(value, true, out)?;
out.push('"');
if !element.content {
element.attrs_at = self.base + self.out.len();
}
self.settle();
Ok(())
}
fn settle(&mut self) {
let Some(Frame::Element(element)) = self.stack.last_mut() else {
return;
};
if !matches!(element.attrs, Attrs::Done) || element.late.is_empty() {
return;
}
let late = std::mem::take(&mut element.late);
let at = element.attrs_at;
self.insert(at, &late);
}
fn insert(&mut self, at: usize, text: &str) {
self.out.insert_str(at - self.base, text);
for frame in &mut self.stack {
if let Frame::Element(element) = frame
&& element.attrs_at >= at
{
element.attrs_at += text.len();
}
}
}
fn atom_element(
&mut self,
name: &str,
atom: &Atom<'_>,
is_root: bool,
state: &State,
) -> Result<(), Error> {
let text = match self.text(atom)? {
Some(text) => text.into_owned(),
None if is_root => String::new(),
None => return Ok(()),
};
self.before_child();
let bindings = self.local_bindings.len();
let name = self.start_tag(name, is_root, state)?;
if text.is_empty() {
self.out.push_str("/>");
} else {
self.out.push('>');
escape(&text, false, &mut self.out)?;
write!(self.out, "</{name}>").unwrap();
}
self.local_bindings.truncate(bindings);
Ok(())
}
fn open_element(
&mut self,
name: &str,
value: SerializeRef<'_>,
state: &State,
) -> Result<(), Error> {
let bindings = self.local_bindings.len();
let name = self.start_tag(name, self.stack.is_empty(), state)?;
let (fields, attrs) = self.fields_of(value);
let layout = Layout::of(state);
let lines = if self.config.indent == Indent::None
|| self.in_line()
|| layout == Layout::Compact
|| keeps_whitespace(state)
{
Lines::Inline
} else {
Lines::Pending
};
self.stack.push(Frame::Element(Element {
name,
attrs_at: self.base + self.out.len(),
content: false,
late: String::new(),
attrs,
fields: fields.map(|names| (names, 0)),
bindings,
lines,
expanded: layout == Layout::Expanded,
}));
self.depth += 1;
Ok(())
}
fn fields_of(&self, value: SerializeRef<'_>) -> (Option<&'static [&'static str]>, Attrs) {
let mut fields = Fields::default();
value.describe(&mut fields);
let Some(names) = fields.names.filter(|_| !fields.variant) else {
return (None, Attrs::Unknown);
};
let names_config = &self.config.names;
let last = names.iter().rposition(|name| {
*name != names_config.text_key && attribute_name(names_config, name).is_some()
});
let attrs = match last {
Some(last) => Attrs::Until(last),
None => Attrs::Done,
};
(Some(names), attrs)
}
fn in_line(&self) -> bool {
match self.stack.last() {
None => false,
Some(Frame::Items { compact: true, .. }) => true,
Some(Frame::Items { .. }) => match self.stack.iter().rev().nth(1) {
Some(Frame::Element(element)) => element.lines == Lines::Inline,
_ => unreachable!("sequences are in elements"),
},
Some(Frame::Element(element)) => element.lines == Lines::Inline,
}
}
fn before_child(&mut self) {
self.close_start_tag();
if self.config.indent == Indent::None {
return;
}
let text_key = self.config.names.text_key;
let mut frames = self.stack.iter_mut().rev();
let element = match frames.next() {
None => return,
Some(Frame::Items {
compact, started, ..
}) => {
if std::mem::replace(started, true) && *compact {
return;
}
match frames.next() {
Some(Frame::Element(element)) => element,
_ => unreachable!("sequences are in elements"),
}
}
Some(Frame::Element(element)) => element,
};
if element.lines == Lines::Pending {
let text_ahead = !element.expanded
&& element
.fields
.is_some_and(|(names, cursor)| names[cursor..].contains(&text_key));
element.lines = if text_ahead {
Lines::Inline
} else {
Lines::Indented
};
}
if element.lines == Lines::Indented {
self.newline(self.depth);
}
}
fn newline(&mut self, depth: usize) {
self.out.push('\n');
let (unit, count) = match self.config.indent {
Indent::None => return,
Indent::Spaces(width) => (' ', width * depth),
Indent::Tab => ('\t', depth),
};
self.out.extend(std::iter::repeat_n(unit, count));
}
fn start_tag(&mut self, name: &str, is_root: bool, state: &State) -> Result<String, Error> {
let mut declarations = String::new();
if !is_root && let Some(Declarations(namespaces)) = state.event::<Declarations>() {
for (prefix, uri) in namespaces {
check_prefix(prefix)?;
if prefix == "xml" {
continue;
}
declare(prefix, uri, &mut declarations)?;
self.local_bindings.push((prefix.clone(), uri.clone()));
}
}
let name = self.qualify(name, false, &mut declarations)?;
self.out.push('<');
self.out.push_str(&name);
if is_root {
self.root_declarations = Some(self.base + self.out.len());
}
self.out.push_str(&declarations);
Ok(name)
}
fn qualify(
&mut self,
name: &str,
is_attribute: bool,
declarations: &mut String,
) -> Result<String, Error> {
let Some((uri, local)) = split_name(name)? else {
return Ok(name.to_string());
};
let usable = |(prefix, bound): &&(String, String)| {
bound == uri && !(is_attribute && prefix.is_empty())
};
let scoped = &self.local_bindings;
let bound = scoped
.iter()
.enumerate()
.rev()
.find(|(index, binding)| {
usable(binding) && !scoped[index + 1..].iter().any(|(x, _)| *x == binding.0)
})
.map(|(_, binding)| binding)
.or_else(|| {
self.root_bindings
.iter()
.find(|binding| usable(binding) && !scoped.iter().any(|(x, _)| *x == binding.0))
});
let prefix = match bound {
Some((prefix, _)) => prefix,
None => {
let prefix = (0..)
.map(|n| format!("ns{n}"))
.find(|prefix| {
self.root_bindings
.iter()
.chain(&self.local_bindings)
.all(|(x, _)| x != prefix)
})
.unwrap();
let bindings = if self.root_declared {
declare(&prefix, uri, declarations)?;
&mut self.local_bindings
} else {
&mut self.root_bindings
};
bindings.push((prefix, uri.to_string()));
&bindings.last().unwrap().0
}
};
Ok(if prefix.is_empty() {
local.to_string()
} else {
format!("{prefix}:{local}")
})
}
fn declare_root(&mut self) -> Result<(), Error> {
if self.root_declared {
return Ok(());
}
self.root_declared = true;
let Some(at) = self.root_declarations else {
return Ok(());
};
let mut declarations = String::new();
for (prefix, uri) in &self.root_bindings[1..] {
declare(prefix, uri, &mut declarations)?;
}
self.insert(at, &declarations);
Ok(())
}
fn final_until(&self) -> usize {
for frame in &self.stack {
if let Frame::Element(element) = frame
&& !matches!(element.attrs, Attrs::Done)
{
return element.attrs_at;
}
}
self.base + self.out.len()
}
fn final_len(&mut self) -> Result<usize, Error> {
let mut until = self.final_until();
if !self.root_declared && self.root_declarations.is_some_and(|at| at < until) {
self.declare_root()?;
until = self.final_until();
}
Ok(until - self.base)
}
fn pass_on(&mut self, out: &mut String) {
let len = self.final_until() - self.base;
out.push_str(&self.out[..len]);
self.out.drain(..len);
self.base += len;
}
fn finish(&mut self) -> Result<(), Error> {
if !self.stack.is_empty() || self.depth > 0 {
return Err(Error::new(ErrorKind::InvalidState, "incomplete document"));
}
self.declare_root()
}
fn close_start_tag(&mut self) {
let element = self.stack.iter_mut().rev().find_map(|frame| match frame {
Frame::Element(element) => Some(element),
Frame::Items { .. } => None,
});
if let Some(element) = element
&& !element.content
{
element.content = true;
self.out.push('>');
}
}
fn close_element(&mut self) -> Result<(), Error> {
if let Some(Frame::Element(element)) = self.stack.last_mut() {
element.attrs = Attrs::Done;
}
self.settle();
let Some(Frame::Element(element)) = self.stack.pop() else {
unreachable!()
};
self.depth -= 1;
if element.lines == Lines::Indented {
self.newline(self.depth);
}
if element.content {
write!(self.out, "</{}>", element.name).unwrap();
} else {
self.out.push_str("/>");
}
self.local_bindings.truncate(element.bindings);
Ok(())
}
fn text<'a>(&self, atom: &'a Atom<'_>) -> Result<Option<Cow<'a, str>>, Error> {
Ok(Some(match *atom {
Atom::Null => return Ok(None),
Atom::Implicit(ref value) => {
return Ok(self
.text(&value.value().to_atom())?
.map(|text| Cow::Owned(text.into_owned())));
}
Atom::Bool(value) => Cow::Borrowed(if value { "true" } else { "false" }),
Atom::Str(ref value) | Atom::Lexical(ref value) => Cow::Borrowed(&**value),
Atom::Char(value) => Cow::Owned(value.to_string()),
Atom::U64(value) => Cow::Owned(value.to_string()),
Atom::I64(value) => Cow::Owned(value.to_string()),
Atom::F32(value) => Cow::Owned(float_text(value)),
Atom::F64(value) => Cow::Owned(float_text(value)),
Atom::Bytes(ref bytes) => {
let format = bytes.fallback.copied().unwrap_or(self.bytes);
Cow::Owned(
format
.encode(bytes)
.or_else(|| BytesFormat::BASE64.encode(bytes))
.unwrap_or_default(),
)
}
Atom::Ext(ref ext) => {
if let Some(number) = ext.downcast_value_ref::<Number>() {
Cow::Owned(number.as_str().to_string())
} else if let Some(value) = ext.downcast_ref::<u128>() {
Cow::Owned(value.to_string())
} else if let Some(value) = ext.downcast_ref::<i128>() {
Cow::Owned(value.to_string())
} else {
match ext.fallback() {
Atom::Ext(_) => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("XML does not support {}", ext.name()),
));
}
fallback => match self.text(&fallback)? {
Some(text) => Cow::Owned(text.into_owned()),
None => return Ok(None),
},
}
}
}
_ => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("XML does not support {}", atom.name()),
));
}
}))
}
}
fn keeps_whitespace(state: &State) -> bool {
state
.event::<KeepsWhitespace>()
.is_some_and(|keeps| keeps.0)
}
fn float_text<F: zmij::Float + Into<f64>>(value: F) -> String {
let wide: f64 = value.into();
if wide.is_nan() {
"NaN".into()
} else if wide.is_infinite() {
if wide > 0.0 { "INF" } else { "-INF" }.into()
} else {
zmij::Buffer::new().format_finite(value).into()
}
}
fn escape(text: &str, attribute: bool, out: &mut String) -> Result<(), Error> {
for c in text.chars() {
match c {
'&' => out.push_str("&"),
'<' => out.push_str("<"),
'>' => out.push_str(">"),
'"' if attribute => out.push_str("""),
'\t' if attribute => out.push_str("	"),
'\n' if attribute => out.push_str(" "),
'\r' => out.push_str(" "),
'\t' | '\n' => out.push(c),
c if (c as u32) < 0x20 || c == '\u{fffe}' || c == '\u{ffff}' => {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("the character {c:?} cannot be written in XML"),
));
}
c => out.push(c),
}
}
Ok(())
}
fn attribute_name<'a>(names: &Names, key: &'a str) -> Option<&'a str> {
key.strip_prefix(names.attribute_prefix)
.filter(|_| !names.attribute_prefix.is_empty())
}
fn declare(prefix: &str, uri: &str, out: &mut String) -> Result<(), Error> {
out.push_str(" xmlns");
if !prefix.is_empty() {
out.push(':');
out.push_str(prefix);
}
out.push_str("=\"");
escape(uri, true, out)?;
out.push('"');
Ok(())
}
fn split_name(name: &str) -> Result<Option<(&str, &str)>, Error> {
let Some(rest) = name.strip_prefix('{') else {
return Ok(None);
};
match rest.split_once('}') {
Some((uri, local)) if !uri.is_empty() => Ok(Some((uri, local))),
_ => Err(Error::new(
ErrorKind::UnsupportedType,
format!("`{name}` is not a name in XML"),
)),
}
}
fn check_element_name(name: &str) -> Result<(), Error> {
match split_name(name)? {
Some((_, local)) if local.contains(':') => Err(Error::new(
ErrorKind::UnsupportedType,
format!("`{name}` is not a name in XML"),
)),
Some((_, local)) => check_name(local),
None => check_name(name),
}
}
fn check_prefix(prefix: &str) -> Result<(), Error> {
if prefix.is_empty() {
return Ok(());
}
check_name(prefix)?;
if prefix.contains(':') {
return Err(Error::new(
ErrorKind::UnsupportedType,
format!("`{prefix}` is not a prefix in XML"),
));
}
Ok(())
}
fn check_name(name: &str) -> Result<(), Error> {
let mut chars = name.chars();
let valid = match chars.next() {
Some(c) if c.is_alphabetic() || c == '_' || c == ':' => {
chars.all(|c| c.is_alphanumeric() || matches!(c, '_' | ':' | '-' | '.' | '\u{b7}'))
}
_ => false,
};
if valid {
Ok(())
} else {
Err(Error::new(
ErrorKind::UnsupportedType,
format!("`{name}` is not a name in XML"),
))
}
}
#[cold]
fn unsupported_key() -> Error {
Error::new(
ErrorKind::UnsupportedType,
"the keys of elements must be names",
)
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use deser::Serialize;
use super::*;
fn pieces<T: Serialize + ?Sized>(
config: &SerializerConfig,
value: &T,
) -> Result<Vec<String>, Error> {
let mut pieces = Vec::new();
let mut driver = SerializeDriver::new(&value);
let mut progress = None;
loop {
let mut out = String::new();
let done = config.serialize_part(&mut progress, &mut driver, &mut out, 1)?;
pieces.push(out);
if done {
return Ok(pieces);
}
}
}
#[derive(Serialize)]
struct Feed {
#[deser(rename = "@id")]
id: u32,
title: &'static str,
entry: Vec<Entry>,
}
#[derive(Serialize)]
struct Entry {
#[deser(rename = "$text")]
text: &'static str,
#[deser(rename = "@n")]
n: u32,
#[deser(skip_serializing_if = Option::is_none)]
note: Option<&'static str>,
}
fn feed() -> Feed {
Feed {
id: 1,
title: "t",
entry: vec![
Entry {
text: "a",
n: 1,
note: None,
},
Entry {
text: "b",
n: 2,
note: Some("x"),
},
],
}
}
#[test]
fn test_structs_stream() {
assert_eq!(
pieces(&SerializerConfig::new(), &feed()).unwrap(),
[
"<Feed id=\"1\"",
"><title>t</title>",
"<entry",
" n=\"1\">a",
"</entry>",
"<entry",
" n=\"2\">b",
"<note>x</note>",
"</entry>",
"</Feed>",
]
);
}
#[test]
fn test_maps_are_buffered() {
let config = SerializerConfig::builder()
.root("m")
.declaration(true)
.build();
let map = BTreeMap::from([
("a", BTreeMap::from([("$text", "1"), ("@x", "2")])),
("b", BTreeMap::from([("$text", "3"), ("@y", "4")])),
]);
assert_eq!(
pieces(&config, &map).unwrap(),
[
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><m",
"><a x=\"2\">1</a><b y=\"4\">3</b></m>",
]
);
let map = BTreeMap::from([("$text", "x"), ("@a", "1"), ("b", "2")]);
assert_eq!(
pieces(&config, &map).unwrap(),
[
"<?xml version=\"1.0\" encoding=\"UTF-8\"?><m",
" a=\"1\">x<b>2</b></m>",
]
);
}
#[test]
fn test_same_output() {
let map = BTreeMap::from([("$text", "x"), ("@a", "1"), ("b", "2")]);
let nested = BTreeMap::from([("a", BTreeMap::from([("@x", "1"), ("b", "2")]))]);
let feed = feed();
let values: [SerializeRef<'_>; 4] = [
SerializeRef::new(&feed),
SerializeRef::new(&map),
SerializeRef::new(&nested),
SerializeRef::new(&Some(42)),
];
for config in [
SerializerConfig::builder().root("r").build(),
SerializerConfig::builder()
.root("r")
.declaration(true)
.indent(Indent::Spaces(2))
.build(),
] {
for value in values {
assert_eq!(
pieces(&config, &value).unwrap().concat(),
config.to_string(&value).unwrap()
);
}
}
}
#[test]
fn test_indent_streams() {
let config = SerializerConfig::builder()
.indent(Indent::Spaces(2))
.build();
assert_eq!(
pieces(&config, &feed()).unwrap(),
[
"<Feed id=\"1\"",
">\n <title>t</title>",
"\n <entry",
" n=\"1\">a",
"</entry>",
"\n <entry",
" n=\"2\">b",
"<note>x</note>",
"</entry>",
"\n</Feed>",
]
);
}
#[test]
fn test_namespaces_stream() {
#[derive(Serialize)]
#[deser(rename = "{urn:root}root")]
struct Root {
#[deser(rename = "{urn:a}a")]
a: Vec<Child>,
}
#[derive(Serialize)]
struct Child {
#[deser(rename = "@{urn:b}b")]
b: u32,
#[deser(rename = "{urn:a}c")]
c: u32,
}
let root = Root {
a: vec![Child { b: 1, c: 2 }, Child { b: 3, c: 4 }],
};
let config = SerializerConfig::new();
assert_eq!(
config.to_string(&root).unwrap(),
"<ns0:root xmlns:ns0=\"urn:root\" xmlns:ns1=\"urn:a\" xmlns:ns2=\"urn:b\">\
<ns1:a ns2:b=\"1\"><ns1:c>2</ns1:c></ns1:a>\
<ns1:a ns2:b=\"3\"><ns1:c>4</ns1:c></ns1:a></ns0:root>"
);
assert_eq!(
pieces(&config, &root).unwrap().concat(),
"<ns0:root xmlns:ns0=\"urn:root\" xmlns:ns1=\"urn:a\">\
<ns1:a xmlns:ns2=\"urn:b\" ns2:b=\"1\"><ns1:c>2</ns1:c></ns1:a>\
<ns1:a xmlns:ns2=\"urn:b\" ns2:b=\"3\"><ns1:c>4</ns1:c></ns1:a>\
</ns0:root>"
);
let config = SerializerConfig::new().namespaces(&[("r", "urn:root"), ("a", "urn:a")]);
assert_eq!(
pieces(&config, &root).unwrap().concat(),
"<r:root xmlns:r=\"urn:root\" xmlns:a=\"urn:a\">\
<a:a xmlns:ns0=\"urn:b\" ns0:b=\"1\"><a:c>2</a:c></a:a>\
<a:a xmlns:ns0=\"urn:b\" ns0:b=\"3\"><a:c>4</a:c></a:a></r:root>"
);
}
#[test]
fn test_nested_declarations_stream() {
const RESOLVE: crate::DeserializerConfig = crate::DeserializerConfig::builder()
.resolve_namespaces(true)
.build();
let input = r#"<a:r xmlns:a="urn:a"><b xmlns:a="urn:b"><x>1</x></b><a:y>2</a:y></a:r>"#;
let mut value: deser_value::Value = RESOLVE.from_str(input).unwrap();
let config = SerializerConfig::new();
assert_eq!(pieces(&config, &value).unwrap().concat(), input);
let b = value.as_map_mut().unwrap().get_mut("b").unwrap();
b.as_map_mut().unwrap().insert("{urn:a}z", "3");
let xml = config.to_string(&value).unwrap();
assert_eq!(
xml,
"<a:r xmlns:a=\"urn:a\" xmlns:ns0=\"urn:a\"><b xmlns:a=\"urn:b\"><x>1</x>\
<ns0:z>3</ns0:z></b><a:y>2</a:y></a:r>"
);
assert_eq!(pieces(&config, &value).unwrap().concat(), xml);
}
#[test]
fn test_wrong_fields() {
struct Wrong(BTreeMap<&'static str, &'static str>);
impl Serialize for Wrong {
fn describe(_value: &Self, d: &mut dyn Describe) {
d.structure("Wrong");
d.fields(&["$text"]);
}
fn serialize<'a>(
value: &'a Self,
state: &mut deser_core::State,
) -> Result<deser_core::ser::Emit<'a>, Error> {
BTreeMap::<&str, &str>::serialize(&value.0, state)
}
}
let wrong = Wrong(BTreeMap::from([("$text", "x"), ("@a", "1")]));
let config = SerializerConfig::new();
assert_eq!(
config.to_string(&wrong).unwrap(),
r#"<Wrong a="1">x</Wrong>"#
);
let err = pieces(&config, &wrong).unwrap_err();
assert!(
err.message()
.starts_with("attribute `a` comes after the start tag")
);
}
}