#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, doc(auto_cfg))]
#![forbid(unsafe_code)]
#![warn(missing_docs)]
#![no_std]
extern crate alloc;
#[cfg(feature = "std")]
extern crate std;
#[cfg(feature = "std")]
use std::io;
pub mod asxml;
pub mod dynxso;
pub mod error;
pub mod fromxml;
#[cfg(feature = "minidom")]
pub mod minidom_compat;
mod rxml_util;
pub mod text;
mod util {
pub const fn const_str_eq(a: &str, b: &str) -> bool {
let a = a.as_bytes();
let b = b.as_bytes();
if a.len() != b.len() {
return false;
}
let mut i = 0;
while i < a.len() {
if a[i] != b[i] {
return false;
}
i += 1;
}
true
}
}
#[doc(hidden)]
pub mod exports {
#[cfg(all(feature = "minidom", feature = "macros"))]
pub use minidom;
#[cfg(feature = "macros")]
pub use rxml;
#[cfg(not(feature = "std"))]
pub extern crate alloc;
#[cfg(feature = "std")]
pub extern crate std as alloc;
#[cfg(feature = "macros")]
pub type CoreBool = bool;
#[cfg(feature = "macros")]
pub type CoreU8 = u8;
#[cfg(feature = "macros")]
pub use super::util::const_str_eq;
}
use alloc::{borrow::Cow, boxed::Box, string::String, vec::Vec};
#[doc(inline)]
pub use fromxml::Context;
use fromxml::XmlNameMatcher;
pub use text::TextCodec;
#[doc(inline)]
pub use rxml_util::Item;
pub use asxml::PrintRawXml;
#[doc = include_str!("from_xml_doc.md")]
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::FromXml;
#[doc(inline)]
#[cfg(feature = "macros")]
pub use xso_proc::AsXml;
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be serialised as XML")]
pub trait AsXml {
type ItemIter<'x>: Iterator<Item = Result<Item<'x>, self::error::Error>>
where
Self: 'x;
fn as_xml_iter(&self) -> Result<Self::ItemIter<'_>, self::error::Error>;
fn as_xml_dyn_iter<'x>(
&'x self,
) -> Result<
Box<dyn Iterator<Item = Result<Item<'x>, self::error::Error>> + 'x>,
self::error::Error,
> {
self.as_xml_iter().map(|x| Box::new(x) as Box<_>)
}
}
pub trait FromEventsBuilder {
type Output;
fn feed(
&mut self,
ev: rxml::Event,
ctx: &Context<'_>,
) -> Result<Option<Self::Output>, self::error::Error>;
}
#[diagnostic::on_unimplemented(message = "`{Self}` cannot be parsed from XML")]
pub trait FromXml {
type Builder: FromEventsBuilder<Output = Self>;
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, self::error::FromEventsError>;
fn xml_name_matcher() -> XmlNameMatcher<'static> {
XmlNameMatcher::Any
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be parsed from XML text",
note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
)]
pub trait FromXmlText: Sized {
fn from_xml_text(data: String) -> Result<Self, self::error::Error>;
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be serialised to XML text",
note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
)]
pub trait AsXmlText {
fn as_xml_text(&self) -> Result<Cow<'_, str>, self::error::Error>;
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error> {
Ok(Some(self.as_xml_text()?))
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` cannot be serialised as XML attribute",
note = "If `{Self}` implements `core::fmt::Display` and `core::str::FromStr`, you may be able to provide a suitable implementation using `xso::convert_via_fromstr_and_display!({Self});`."
)]
pub trait AsOptionalXmlText {
fn as_optional_xml_text(&self) -> Result<Option<Cow<'_, str>>, self::error::Error>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownAttributePolicy {
#[cfg_attr(not(feature = "pedantic"), default)]
Discard,
#[cfg_attr(feature = "pedantic", default)]
Fail,
}
impl UnknownAttributePolicy {
#[doc(hidden)]
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Default)]
pub enum UnknownChildPolicy {
#[cfg_attr(not(feature = "pedantic"), default)]
Discard,
#[cfg_attr(feature = "pedantic", default)]
Fail,
}
impl UnknownChildPolicy {
#[doc(hidden)]
pub fn apply_policy(&self, msg: &'static str) -> Result<(), self::error::Error> {
match self {
Self::Fail => Err(self::error::Error::Other(msg)),
Self::Discard => Ok(()),
}
}
}
#[cfg_attr(
not(all(feature = "std", feature = "macros")),
doc = "Because the std or macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(all(feature = "std", feature = "macros"), doc = "\n```\n")]
pub fn transform<T: FromXml, F: AsXml>(from: &F) -> Result<T, self::error::Error> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
let mut iter = self::rxml_util::ItemToEvent::new(from.as_xml_iter()?);
let (qname, attrs) = match iter.next() {
Some(Ok(rxml::Event::StartElement(_, qname, attrs))) => (qname, attrs),
Some(Err(e)) => return Err(e),
_ => panic!("into_event_iter did not start with StartElement event!"),
};
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
}
Err(self::error::FromEventsError::Invalid(e)) => return Err(e),
};
for event in iter {
let event = event?;
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v);
}
}
Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
#[cfg(feature = "minidom")]
#[deprecated(
since = "0.1.3",
note = "obsolete since the transition to AsXml, which works by reference; use xso::transform instead."
)]
pub fn try_from_element<T: FromXml>(
from: minidom::Element,
) -> Result<T, self::error::FromElementError> {
let mut languages = rxml::xml_lang::XmlLangStack::new();
#[allow(deprecated)]
let (qname, attrs) = minidom_compat::make_start_ev_parts(&from)?;
languages.push_from_attrs(&attrs);
let mut sink = match T::from_events(
qname,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::FromElementError::Mismatch(from))
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(self::error::FromElementError::Invalid(e))
}
};
let mut iter = from.as_xml_iter()?;
for item in &mut iter {
let item = item?;
match item {
Item::XmlDeclaration(..) => (),
Item::ElementHeadStart(..) => (),
Item::Attribute(..) => (),
Item::ElementHeadEnd => {
break;
}
Item::Text(..) => panic!("text before end of element header"),
Item::ElementFoot => panic!("element foot before end of element header"),
}
}
let iter = self::rxml_util::ItemToEvent::new(iter);
for event in iter {
let event = event?;
languages.handle_event(&event);
if let Some(v) = sink.feed(event, &Context::empty().with_language(languages.current()))? {
return Ok(v);
}
}
unreachable!("minidom::Element did not produce enough events to complete element")
}
fn from_bytes_inner<T: FromXml>(
mut parser: rxml::Parser,
mut buf: &[u8],
) -> Result<T, self::error::Error> {
use rxml::{error::EndOrError, Parse};
let mut languages = rxml::xml_lang::XmlLangStack::new();
let (name, attrs) = loop {
match parser.parse(&mut buf, true) {
Ok(Some(rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0))) => (),
Ok(Some(rxml::Event::StartElement(_, name, attrs))) => break (name, attrs),
Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
Ok(None) | Err(EndOrError::NeedMoreData) => {
return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))))
}
Ok(Some(_)) => {
return Err(self::error::Error::Other(
"Unexpected event at start of document",
))
}
}
};
languages.push_from_attrs(&attrs);
let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(languages.current()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch);
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(e);
}
};
loop {
match parser.parse(&mut buf, true) {
Ok(Some(ev)) => {
languages.handle_event(&ev);
if let Some(v) =
builder.feed(ev, &Context::empty().with_language(languages.current()))?
{
return Ok(v);
}
}
Err(EndOrError::Error(e)) => return Err(self::error::Error::XmlError(e)),
Ok(None) | Err(EndOrError::NeedMoreData) => {
return Err(self::error::Error::XmlError(rxml::Error::InvalidEof(None)))
}
}
}
}
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
pub fn from_bytes<T: FromXml>(buf: &[u8]) -> Result<T, self::error::Error> {
let parser = rxml::Parser::new();
from_bytes_inner(parser, buf)
}
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
pub fn from_bytes_with_options<T: FromXml>(
buf: &[u8],
opts: rxml::Options,
) -> Result<T, self::error::Error> {
use rxml::WithOptions;
let parser = rxml::Parser::with_options(opts);
from_bytes_inner(parser, buf)
}
#[cfg(feature = "std")]
fn read_start_event_io(
r: &mut impl Iterator<Item = io::Result<rxml::Event>>,
) -> io::Result<(rxml::QName, rxml::AttrMap)> {
for ev in r {
match ev? {
rxml::Event::XmlDeclaration(_, rxml::XmlVersion::V1_0) => (),
rxml::Event::StartElement(_, name, attrs) => return Ok((name, attrs)),
_ => {
return Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::Other("Unexpected event at start of document"),
))
}
}
}
Err(io::Error::new(
io::ErrorKind::InvalidData,
self::error::Error::XmlError(rxml::Error::InvalidEof(Some(
rxml::error::ErrorContext::DocumentBegin,
))),
))
}
#[cfg(feature = "std")]
fn from_reader_inner<T: FromXml, R: io::BufRead>(
mut reader: rxml::XmlLangTracker<rxml::Reader<R>>,
) -> io::Result<T> {
let (name, attrs) = read_start_event_io(&mut reader)?;
let mut builder = match T::from_events(
name,
attrs,
&Context::empty().with_language(reader.language()),
) {
Ok(v) => v,
Err(self::error::FromEventsError::Mismatch { .. }) => {
return Err(self::error::Error::TypeMismatch)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
Err(self::error::FromEventsError::Invalid(e)) => {
return Err(e).map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
};
while let Some(ev) = reader.next() {
if let Some(v) = builder
.feed(ev?, &Context::empty().with_language(reader.language()))
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?
{
return Ok(v);
}
}
Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
self::error::Error::XmlError(rxml::Error::InvalidEof(None)),
))
}
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
#[cfg(feature = "std")]
pub fn from_reader<T: FromXml, R: io::BufRead>(r: R) -> io::Result<T> {
from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::new(r)))
}
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
#[cfg(feature = "std")]
pub fn from_reader_with_options<T: FromXml, R: io::BufRead>(
r: R,
options: rxml::Options,
) -> io::Result<T> {
from_reader_inner(rxml::XmlLangTracker::wrap(rxml::Reader::with_options(
r, options,
)))
}
#[cfg_attr(
not(feature = "macros"),
doc = "Because the macros feature was not enabled at doc build time, the example cannot be tested.\n\n```ignore\n"
)]
#[cfg_attr(feature = "macros", doc = "\n```\n")]
pub fn to_vec<T: AsXml>(xso: &T) -> Result<Vec<u8>, self::error::Error> {
let iter = xso.as_xml_iter()?;
let mut writer = rxml::writer::Encoder::new();
let mut buf = Vec::new();
for item in iter {
let item = item?;
writer.encode(item.as_rxml_item(), &mut buf)?;
}
Ok(buf)
}
pub fn is_xml_whitespace<T: AsRef<[u8]>>(s: T) -> bool {
s.as_ref()
.iter()
.all(|b| *b == b' ' || *b == b'\t' || *b == b'\r' || *b == b'\n')
}