use std::borrow::Cow;
use std::fmt;
use std::io::{BufRead, Write};
use quick_xml::events::{BytesEnd, BytesStart, BytesText, Event};
use crate::error::{Error, Result};
use crate::ns::{self, Namespace};
pub trait XmlSerialize {
fn xml_attributes(&self, tag: &mut BytesStart<'_>) -> Result<()> {
let _ = tag;
Ok(())
}
fn xml_children(&self, w: &mut XmlWriter<'_>) -> Result<()> {
let _ = w;
Ok(())
}
fn xml_text(&self) -> Option<Cow<'_, str>> {
None
}
}
pub trait XmlDeserialize: Sized {
fn xml_read(r: &mut XmlReader<'_>, start: &BytesStart<'_>) -> Result<Self>;
}
pub trait XmlText: Sized {
fn to_xml_text(&self) -> Cow<'_, str>;
fn from_xml_text(s: &str) -> Result<Self>;
}
#[derive(Clone, Copy, Debug)]
pub struct XmlWriteOptions {
pub declaration: bool,
pub indent: Option<usize>,
}
impl Default for XmlWriteOptions {
fn default() -> Self {
Self {
declaration: true,
indent: Some(2),
}
}
}
impl XmlWriteOptions {
pub const COMPACT: XmlWriteOptions = XmlWriteOptions {
declaration: false,
indent: None,
};
}
pub struct XmlWriter<'a> {
w: quick_xml::Writer<&'a mut (dyn Write + 'a)>,
pending_ns: Vec<Namespace>,
root_written: bool,
}
impl<'a> XmlWriter<'a> {
pub fn new(out: &'a mut (dyn Write + 'a), opts: XmlWriteOptions) -> Self {
let w = match opts.indent {
Some(n) => quick_xml::Writer::new_with_indent(out, b' ', n),
None => quick_xml::Writer::new(out),
};
Self {
w,
pending_ns: Vec::new(),
root_written: false,
}
}
pub fn declare(&mut self, namespace: Namespace) -> &mut Self {
if !self.root_written && !self.pending_ns.iter().any(|n| n.uri == namespace.uri) {
self.pending_ns.push(namespace);
}
self
}
pub fn write_declaration(&mut self) -> Result<()> {
self.w.write_event(Event::Decl(quick_xml::events::BytesDecl::new(
"1.0",
Some("UTF-8"),
None,
)))?;
Ok(())
}
pub fn write_raw(&mut self, xml: &str) -> Result<()> {
use std::io::Write;
self.w.get_mut().write_all(xml.as_bytes())?;
Ok(())
}
pub fn write_element(
&mut self,
namespace: Namespace,
local: &str,
value: &dyn XmlSerialize,
) -> Result<()> {
let qname = namespace.qualify(local);
let mut start = BytesStart::new(qname.clone());
if !self.root_written {
self.root_written = true;
let decls: Vec<(String, &'static str)> = std::mem::take(&mut self.pending_ns)
.into_iter()
.map(|n| (format!("xmlns:{}", n.prefix), n.uri))
.collect();
for (k, v) in &decls {
start.push_attribute((k.as_str(), *v));
}
}
value.xml_attributes(&mut start)?;
self.w.write_event(Event::Start(start))?;
if let Some(text) = value.xml_text() {
self.w.write_event(Event::Text(BytesText::new(text.as_ref())))?;
}
value.xml_children(self)?;
self.w.write_event(Event::End(BytesEnd::new(qname)))?;
Ok(())
}
pub fn write_optional<T: XmlSerialize>(
&mut self,
namespace: Namespace,
local: &str,
value: &Option<T>,
) -> Result<()> {
match value {
Some(v) => self.write_element(namespace, local, v),
None => Ok(()),
}
}
pub fn write_all<T: XmlSerialize>(
&mut self,
namespace: Namespace,
local: &str,
values: &[T],
) -> Result<()> {
for v in values {
self.write_element(namespace, local, v)?;
}
Ok(())
}
}
pub fn push_attr<T: XmlText>(tag: &mut BytesStart<'_>, name: &str, value: &Option<T>) {
if let Some(v) = value {
tag.push_attribute((name, v.to_xml_text().as_ref()));
}
}
pub fn push_attr_required<T: XmlText>(tag: &mut BytesStart<'_>, name: &str, value: &T) {
tag.push_attribute((name, value.to_xml_text().as_ref()));
}
pub fn push_xsi_type(tag: &mut BytesStart<'_>, xsi_type: &str) {
tag.push_attribute(("xsi:type", xsi_type));
}
pub fn to_xml_string(
value: &dyn XmlSerialize,
namespace: Namespace,
local: &str,
declare: &[Namespace],
opts: XmlWriteOptions,
) -> Result<String> {
let mut out: Vec<u8> = Vec::new();
{
let mut w = XmlWriter::new(&mut out, opts);
if opts.declaration {
w.write_declaration()?;
}
for n in declare {
w.declare(*n);
}
w.declare(namespace);
w.declare(ns::XSI);
w.write_element(namespace, local, value)?;
}
Ok(String::from_utf8(out)?)
}
#[derive(Debug)]
pub enum XmlEvent {
Start(BytesStart<'static>, Option<String>),
End(BytesEnd<'static>),
Text(String),
Eof,
}
impl XmlEvent {
pub fn namespace(&self) -> &str {
match self {
XmlEvent::Start(_, Some(uri)) => uri.as_str(),
_ => "",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Defaulted {
pub kind: &'static str,
pub name: &'static str,
pub parent: &'static str,
}
impl fmt::Display for Defaulted {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"required {} `{}` in <{}> was absent; read as its default",
self.kind, self.name, self.parent
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Repaired {
pub kind: &'static str,
pub name: String,
pub found: String,
pub used: String,
pub reason: String,
}
impl fmt::Display for Repaired {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{} `{}`: `{}` could not be read ({}); used `{}` instead",
self.kind, self.name, self.found, self.reason, self.used
)
}
}
pub struct XmlReader<'a> {
r: quick_xml::NsReader<&'a mut (dyn BufRead + 'a)>,
buf: Vec<u8>,
pending_end: Option<BytesEnd<'static>>,
pending: Option<XmlEvent>,
lenient: bool,
absent_defaults: bool,
defaulted: Vec<Defaulted>,
repaired: Vec<Repaired>,
}
impl<'a> XmlReader<'a> {
pub fn new(input: &'a mut (dyn BufRead + 'a)) -> Self {
let mut r = quick_xml::NsReader::from_reader(input);
r.config_mut().trim_text(false);
r.config_mut().expand_empty_elements = false;
Self {
r,
buf: Vec::new(),
pending_end: None,
pending: None,
lenient: false,
absent_defaults: true,
defaulted: Vec::new(),
repaired: Vec::new(),
}
}
#[must_use]
pub fn lenient(mut self) -> Self {
self.lenient = true;
self
}
pub fn is_lenient(&self) -> bool {
self.lenient
}
#[must_use]
pub fn strict(mut self) -> Self {
self.absent_defaults = false;
self
}
pub fn is_absent_defaulting(&self) -> bool {
self.absent_defaults
}
pub fn defaulted(&self) -> &[Defaulted] {
&self.defaulted
}
pub fn take_defaulted(&mut self) -> Vec<Defaulted> {
std::mem::take(&mut self.defaulted)
}
pub fn repaired(&self) -> &[Repaired] {
&self.repaired
}
pub fn take_repaired(&mut self) -> Vec<Repaired> {
std::mem::take(&mut self.repaired)
}
pub fn record_repair(&mut self, repaired: Repaired) {
self.repaired.push(repaired);
}
pub fn value<T: XmlText + Default>(&mut self, text: &str, kind: &'static str, name: &str) -> Result<T> {
match T::from_xml_text(text) {
Ok(value) => Ok(value),
Err(e) if self.lenient => {
let used = T::default();
self.repaired.push(Repaired {
kind,
name: name.to_string(),
found: text.to_string(),
used: used.to_xml_text().into_owned(),
reason: short_reason(&e),
});
Ok(used)
}
Err(e) => Err(e),
}
}
pub fn attr_required<T: XmlText + Default>(
&mut self,
start: &BytesStart<'_>,
name: &str,
parent: &'static str,
) -> Result<T> {
match attr_value(start, name)? {
Some(text) => self.value(text.as_str(), "attribute", name),
None if self.absent_defaults => {
self.defaulted.push(Defaulted {
kind: "attribute",
name: leak(name),
parent,
});
Ok(T::default())
}
None => Err(Error::MissingRequired {
kind: "attribute",
name: name.to_string(),
parent: parent.to_string(),
}),
}
}
pub fn attr_optional<T: XmlText + Default>(
&mut self,
start: &BytesStart<'_>,
name: &str,
) -> Result<Option<T>> {
match attr_value(start, name)? {
Some(text) => Ok(Some(self.value(text.as_str(), "attribute", name)?)),
None => Ok(None),
}
}
pub fn required<T: Default>(
&mut self,
slot: Option<T>,
kind: &'static str,
name: &'static str,
parent: &'static str,
) -> Result<T> {
match slot {
Some(value) => Ok(value),
None if self.absent_defaults => {
self.defaulted.push(Defaulted { kind, name, parent });
Ok(T::default())
}
None => Err(Error::MissingRequired {
kind,
name: name.to_string(),
parent: parent.to_string(),
}),
}
}
pub fn next_event(&mut self) -> Result<XmlEvent> {
if let Some(event) = self.pending.take() {
return Ok(event);
}
if let Some(end) = self.pending_end.take() {
return Ok(XmlEvent::End(end));
}
let mut text = String::new();
loop {
self.buf.clear();
let (resolved, event) = self.r.read_resolved_event_into(&mut self.buf)?;
let namespace = match resolved {
quick_xml::name::ResolveResult::Bound(ns) => {
Some(String::from_utf8_lossy(ns.as_ref()).into_owned())
}
_ => None,
};
let structural = match event {
Event::Text(e) => {
let raw = e.xml10_content()?;
text.push_str(&quick_xml::escape::unescape(&raw)?);
continue;
}
Event::CData(e) => {
text.push_str(&String::from_utf8(e.into_inner().into_owned())?);
continue;
}
Event::GeneralRef(e) => {
let name = e.decode()?;
let reference = format!("&{name};");
match quick_xml::escape::unescape(&reference) {
Ok(resolved) => text.push_str(&resolved),
Err(e) if self.lenient => {
self.repaired.push(Repaired {
kind: "entity",
name: name.to_string(),
found: reference.clone(),
used: "\u{fffd}".to_string(),
reason: e.to_string(),
});
text.push('\u{fffd}');
}
Err(e) => return Err(e.into()),
}
continue;
}
Event::Start(e) => XmlEvent::Start(e.into_owned(), namespace),
Event::Empty(e) => {
let owned = e.into_owned();
self.pending_end = Some(BytesEnd::new(
String::from_utf8_lossy(owned.name().as_ref()).into_owned(),
));
XmlEvent::Start(owned, namespace)
}
Event::End(e) => XmlEvent::End(e.into_owned()),
Event::Eof => XmlEvent::Eof,
_ => continue,
};
let trimmed = text.trim();
if trimmed.is_empty() {
return Ok(structural);
}
let value = trimmed.to_string();
self.pending = Some(structural);
return Ok(XmlEvent::Text(value));
}
}
pub fn skip_element(&mut self) -> Result<()> {
let mut depth = 1usize;
while depth > 0 {
match self.next_event()? {
XmlEvent::Start(..) => depth += 1,
XmlEvent::End(_) => depth -= 1,
XmlEvent::Text(_) => {}
XmlEvent::Eof => return Err(Error::Eof),
}
}
Ok(())
}
pub fn read_text(&mut self) -> Result<String> {
let mut out = String::new();
let mut depth = 1usize;
loop {
match self.next_event()? {
XmlEvent::Text(t) if depth == 1 => out.push_str(&t),
XmlEvent::Text(_) => {}
XmlEvent::Start(..) => depth += 1,
XmlEvent::End(_) => {
depth -= 1;
if depth == 0 {
return Ok(out);
}
}
XmlEvent::Eof => return Err(Error::Eof),
}
}
}
pub fn read_root(&mut self) -> Result<BytesStart<'static>> {
match self.next_event()? {
XmlEvent::Start(e, _) => Ok(e),
XmlEvent::Eof => Err(Error::Eof),
other => Err(Error::Xml(format!("expected a root element, found {other:?}"))),
}
}
}
pub fn local_name(start: &BytesStart<'_>) -> String {
String::from_utf8_lossy(start.local_name().as_ref()).into_owned()
}
pub fn attr_value(start: &BytesStart<'_>, name: &str) -> Result<Option<String>> {
for attr in start.attributes() {
let attr = attr?;
if attr.key.local_name().as_ref() == name.as_bytes() {
return Ok(Some(
attr.normalized_value(quick_xml::XmlVersion::default())?
.into_owned(),
));
}
}
Ok(None)
}
pub fn local_part(value: &str) -> &str {
value.rsplit([':', '.']).next().unwrap_or(value)
}
pub fn xsi_type(start: &BytesStart<'_>) -> Result<Option<String>> {
attr_value(start, "type")
}
pub fn required_attr<T: XmlText>(start: &BytesStart<'_>, name: &str, parent: &str) -> Result<T> {
match attr_value(start, name)? {
Some(v) => T::from_xml_text(&v),
None => Err(Error::MissingRequired {
kind: "attribute",
name: name.to_string(),
parent: parent.to_string(),
}),
}
}
pub fn optional_attr<T: XmlText>(start: &BytesStart<'_>, name: &str) -> Result<Option<T>> {
match attr_value(start, name)? {
Some(v) => Ok(Some(T::from_xml_text(&v)?)),
None => Ok(None),
}
}
pub fn from_xml_str<T: XmlDeserialize>(input: &str) -> Result<T> {
let mut bytes = input.as_bytes();
let mut reader = XmlReader::new(&mut bytes);
let root = reader.read_root()?;
T::xml_read(&mut reader, &root)
}
pub fn from_xml_bytes<T: XmlDeserialize>(input: &[u8]) -> Result<T> {
let mut cursor = input;
let mut reader = XmlReader::new(&mut cursor);
let root = reader.read_root()?;
T::xml_read(&mut reader, &root)
}
pub fn from_xml_str_strict<T: XmlDeserialize>(input: &str) -> Result<T> {
let mut bytes = input.as_bytes();
let mut reader = XmlReader::new(&mut bytes).strict();
let root = reader.read_root()?;
T::xml_read(&mut reader, &root)
}
pub fn from_xml_bytes_strict<T: XmlDeserialize>(input: &[u8]) -> Result<T> {
let mut cursor = input;
let mut reader = XmlReader::new(&mut cursor).strict();
let root = reader.read_root()?;
T::xml_read(&mut reader, &root)
}
pub fn from_xml_str_lenient<T: XmlDeserialize>(input: &str) -> Result<(T, Vec<Defaulted>)> {
from_xml_bytes_lenient(input.as_bytes())
}
pub fn from_xml_bytes_lenient<T: XmlDeserialize>(input: &[u8]) -> Result<(T, Vec<Defaulted>)> {
let mut cursor = input;
let mut reader = XmlReader::new(&mut cursor).lenient();
let root = reader.read_root()?;
let object = T::xml_read(&mut reader, &root)?;
Ok((object, reader.take_defaulted()))
}
pub fn join_list<T: XmlText>(items: &[T]) -> String {
let mut out = String::new();
for item in items {
if !out.is_empty() {
out.push(' ');
}
out.push_str(item.to_xml_text().as_ref());
}
out
}
fn short_reason(error: &Error) -> String {
let text = error.to_string();
match text.find(" (") {
Some(at) => text[..at].to_string(),
None => text,
}
}
fn leak(name: &str) -> &'static str {
use std::collections::HashSet;
use std::sync::{Mutex, OnceLock};
static NAMES: OnceLock<Mutex<HashSet<&'static str>>> = OnceLock::new();
let mut names = NAMES
.get_or_init(|| Mutex::new(HashSet::new()))
.lock()
.expect("the name table lock is never held across a panic");
if let Some(known) = names.get(name) {
return known;
}
let leaked: &'static str = Box::leak(name.to_string().into_boxed_str());
names.insert(leaked);
leaked
}
pub fn split_list<T: XmlText>(text: &str) -> Result<Vec<T>> {
text.split_whitespace().map(T::from_xml_text).collect()
}
macro_rules! impl_xml_scalar {
($ty:ty, $name:literal) => {
impl XmlText for $ty {
fn to_xml_text(&self) -> Cow<'_, str> {
Cow::Owned(self.to_string())
}
fn from_xml_text(s: &str) -> Result<Self> {
s.trim()
.parse::<$ty>()
.map_err(|e| Error::parse(s, $name, e.to_string()))
}
}
impl XmlSerialize for $ty {
fn xml_text(&self) -> Option<Cow<'_, str>> {
Some(XmlText::to_xml_text(self))
}
}
impl XmlDeserialize for $ty {
fn xml_read(r: &mut XmlReader<'_>, _start: &BytesStart<'_>) -> Result<Self> {
let text = r.read_text()?;
r.value::<$ty>(&text, "number", $name)
}
}
};
}
impl_xml_scalar!(i8, "byte");
impl_xml_scalar!(i16, "short");
impl_xml_scalar!(i32, "int");
impl_xml_scalar!(i64, "long");
impl_xml_scalar!(u8, "unsignedByte");
impl_xml_scalar!(u16, "unsignedShort");
impl_xml_scalar!(u32, "unsignedInt");
impl_xml_scalar!(u64, "unsignedLong");
impl_xml_scalar!(f32, "float");
impl_xml_scalar!(f64, "double");
impl XmlText for bool {
fn to_xml_text(&self) -> Cow<'_, str> {
Cow::Borrowed(if *self { "true" } else { "false" })
}
fn from_xml_text(s: &str) -> Result<Self> {
match s.trim() {
"true" | "1" => Ok(true),
"false" | "0" => Ok(false),
other => Err(Error::parse(other, "boolean", "expected true, false, 1 or 0")),
}
}
}
impl XmlSerialize for bool {
fn xml_text(&self) -> Option<Cow<'_, str>> {
Some(XmlText::to_xml_text(self))
}
}
impl XmlDeserialize for bool {
fn xml_read(r: &mut XmlReader<'_>, _start: &BytesStart<'_>) -> Result<Self> {
let text = r.read_text()?;
r.value::<bool>(&text, "boolean", "boolean")
}
}
impl XmlText for String {
fn to_xml_text(&self) -> Cow<'_, str> {
Cow::Borrowed(self.as_str())
}
fn from_xml_text(s: &str) -> Result<Self> {
Ok(s.to_string())
}
}
impl XmlSerialize for String {
fn xml_text(&self) -> Option<Cow<'_, str>> {
Some(Cow::Borrowed(self.as_str()))
}
}
impl XmlDeserialize for String {
fn xml_read(r: &mut XmlReader<'_>, _start: &BytesStart<'_>) -> Result<Self> {
r.read_text()
}
}
impl<T: XmlSerialize> XmlSerialize for Box<T> {
fn xml_attributes(&self, tag: &mut BytesStart<'_>) -> Result<()> {
(**self).xml_attributes(tag)
}
fn xml_children(&self, w: &mut XmlWriter<'_>) -> Result<()> {
(**self).xml_children(w)
}
fn xml_text(&self) -> Option<Cow<'_, str>> {
(**self).xml_text()
}
}
impl<T: XmlDeserialize> XmlDeserialize for Box<T> {
fn xml_read(r: &mut XmlReader<'_>, start: &BytesStart<'_>) -> Result<Self> {
Ok(Box::new(T::xml_read(r, start)?))
}
}