use alloc::boxed::Box;
use crate::error::{Error, FromEventsError};
use crate::{FromEventsBuilder, FromXml};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum XmlNameMatcher<'x> {
Any,
InNamespace(&'x str),
Specific(&'x str, &'x str),
}
impl<'x> XmlNameMatcher<'x> {
pub const fn superset(self, other: Self) -> Self {
match self {
Self::Any => Self::Any,
Self::InNamespace(my_namespace) => match other {
Self::Any => Self::Any,
Self::InNamespace(other_namespace) | Self::Specific(other_namespace, _) => {
if crate::util::const_str_eq(my_namespace, other_namespace) {
Self::InNamespace(my_namespace)
} else {
Self::Any
}
}
},
Self::Specific(my_namespace, my_name) => match other {
Self::Any => Self::Any,
Self::InNamespace(other_namespace) => {
if crate::util::const_str_eq(my_namespace, other_namespace) {
Self::InNamespace(my_namespace)
} else {
Self::Any
}
}
Self::Specific(other_namespace, other_name) => {
if crate::util::const_str_eq(my_namespace, other_namespace) {
if crate::util::const_str_eq(my_name, other_name) {
Self::Specific(my_name, other_name)
} else {
Self::InNamespace(my_namespace)
}
} else {
Self::Any
}
}
},
}
}
pub fn matches(&self, qname: &rxml::QName) -> bool {
match self {
Self::Any => true,
Self::InNamespace(ns) => qname.0.as_str() == *ns,
Self::Specific(ns, name) => qname.0.as_str() == *ns && qname.1.as_str() == *name,
}
}
}
#[derive(Debug)]
#[doc(hidden)]
pub struct Context<'x> {
language: Option<&'x str>,
}
impl<'x> Context<'x> {
pub fn empty() -> Self {
Self { language: None }
}
pub fn with_language(mut self, language: Option<&'x str>) -> Self {
self.language = language;
self
}
pub fn language(&self) -> Option<&str> {
self.language
}
}
pub struct OptionBuilder<T: FromEventsBuilder>(T);
impl<T: FromEventsBuilder> FromEventsBuilder for OptionBuilder<T> {
type Output = Option<T::Output>;
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev, ctx).map(|ok| ok.map(Some))
}
}
impl<T: FromXml> FromXml for Option<T> {
type Builder = OptionBuilder<T::Builder>;
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(OptionBuilder(T::from_events(name, attrs, ctx)?))
}
}
pub struct BoxBuilder<T: FromEventsBuilder + ?Sized>(Box<T>);
impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for BoxBuilder<T> {
type Output = Box<T::Output>;
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
self.0.feed(ev, ctx).map(|ok| ok.map(Box::new))
}
}
impl<T: FromXml + ?Sized> FromXml for Box<T> {
type Builder = BoxBuilder<T::Builder>;
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(BoxBuilder(Box::new(T::from_events(name, attrs, ctx)?)))
}
}
impl<T: FromEventsBuilder + ?Sized> FromEventsBuilder for Box<T> {
type Output = T::Output;
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
(**self).feed(ev, ctx)
}
}
#[derive(Debug)]
enum FallibleBuilderInner<T: FromEventsBuilder, E> {
Processing { depth: usize, builder: T },
Failed { depth: usize, err: Option<E> },
Done,
}
#[derive(Debug)]
pub struct FallibleBuilder<T: FromEventsBuilder, E>(FallibleBuilderInner<T, E>);
impl<T: FromEventsBuilder, E: From<Error>> FromEventsBuilder for FallibleBuilder<T, E> {
type Output = Result<T::Output, E>;
fn feed(&mut self, ev: rxml::Event, ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match self.0 {
FallibleBuilderInner::Processing {
ref mut depth,
ref mut builder,
} => {
let new_depth = match ev {
rxml::Event::StartElement(..) => match depth.checked_add(1) {
None => {
self.0 = FallibleBuilderInner::Done;
return Err(Error::Other("maximum XML nesting depth exceeded"));
}
Some(v) => Some(v),
},
rxml::Event::EndElement(..) => depth.checked_sub(1),
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => Some(*depth),
};
match builder.feed(ev, ctx) {
Ok(Some(v)) => {
self.0 = FallibleBuilderInner::Done;
return Ok(Some(Ok(v)));
}
Ok(None) => {
}
Err(e) => {
match new_depth {
Some(depth) => {
self.0 = FallibleBuilderInner::Failed {
depth,
err: Some(e.into()),
};
return Ok(None);
}
None => {
self.0 = FallibleBuilderInner::Done;
return Ok(Some(Err(e.into())));
}
}
}
};
*depth = match new_depth {
Some(v) => v,
None => unreachable!("fallible parsing continued beyond end of element"),
};
Ok(None)
}
FallibleBuilderInner::Failed {
ref mut depth,
ref mut err,
} => {
*depth = match ev {
rxml::Event::StartElement(..) => match depth.checked_add(1) {
None => {
self.0 = FallibleBuilderInner::Done;
return Err(Error::Other("maximum XML nesting depth exceeded"));
}
Some(v) => v,
},
rxml::Event::EndElement(..) => match depth.checked_sub(1) {
Some(v) => v,
None => {
let err = err.take().expect("fallible parsing somehow lost its error");
self.0 = FallibleBuilderInner::Done;
return Ok(Some(Err(err)));
}
},
rxml::Event::XmlDeclaration(..) | rxml::Event::Text(..) => *depth,
};
Ok(None)
}
FallibleBuilderInner::Done => {
panic!("FromEventsBuilder called after it returned a value")
}
}
}
}
impl<T: FromXml, E: From<Error>> FromXml for Result<T, E> {
type Builder = FallibleBuilder<T::Builder, E>;
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
match T::from_events(name, attrs, ctx) {
Ok(builder) => Ok(FallibleBuilder(FallibleBuilderInner::Processing {
depth: 0,
builder,
})),
Err(FromEventsError::Mismatch { name, attrs }) => {
Err(FromEventsError::Mismatch { name, attrs })
}
Err(FromEventsError::Invalid(e)) => Ok(FallibleBuilder(FallibleBuilderInner::Failed {
depth: 0,
err: Some(e.into()),
})),
}
}
}
#[derive(Debug, Default)]
pub struct Discard {
depth: usize,
}
impl Discard {
pub fn new() -> Self {
Self::default()
}
}
impl FromEventsBuilder for Discard {
type Output = ();
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match ev {
rxml::Event::StartElement(..) => {
self.depth = match self.depth.checked_add(1) {
Some(v) => v,
None => return Err(Error::Other("maximum XML nesting depth exceeded")),
};
Ok(None)
}
rxml::Event::EndElement(..) => match self.depth.checked_sub(1) {
None => Ok(Some(())),
Some(v) => {
self.depth = v;
Ok(None)
}
},
_ => Ok(None),
}
}
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub struct EmptyBuilder {
childerr: &'static str,
texterr: &'static str,
}
#[cfg(feature = "macros")]
impl FromEventsBuilder for EmptyBuilder {
type Output = ();
fn feed(&mut self, ev: rxml::Event, _ctx: &Context<'_>) -> Result<Option<Self::Output>, Error> {
match ev {
rxml::Event::EndElement(..) => Ok(Some(())),
rxml::Event::StartElement(..) => Err(Error::Other(self.childerr)),
rxml::Event::Text(..) => Err(Error::Other(self.texterr)),
_ => Err(Error::Other(
"unexpected content in supposed-to-be-empty element",
)),
}
}
}
#[doc(hidden)]
#[cfg(feature = "macros")]
pub struct Empty {
pub attributeerr: &'static str,
pub childerr: &'static str,
pub texterr: &'static str,
}
#[cfg(feature = "macros")]
impl Empty {
pub fn start(self, attr: rxml::AttrMap) -> Result<EmptyBuilder, Error> {
if !attr.is_empty() {
return Err(Error::Other(self.attributeerr));
}
Ok(EmptyBuilder {
childerr: self.childerr,
texterr: self.texterr,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::borrow::ToOwned;
use rxml::{parser::EventMetrics, Event, Namespace, NcName};
macro_rules! null_builder {
($name:ident for $output:ident) => {
#[derive(Debug)]
enum $name {}
impl FromEventsBuilder for $name {
type Output = $output;
fn feed(
&mut self,
_: Event,
_: &Context<'_>,
) -> Result<Option<Self::Output>, Error> {
unreachable!();
}
}
};
}
null_builder!(AlwaysMismatchBuilder for AlwaysMismatch);
null_builder!(InitialErrorBuilder for InitialError);
#[derive(Debug)]
struct AlwaysMismatch;
impl FromXml for AlwaysMismatch {
type Builder = AlwaysMismatchBuilder;
fn from_events(
name: rxml::QName,
attrs: rxml::AttrMap,
_ctx: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Err(FromEventsError::Mismatch { name, attrs })
}
}
#[derive(Debug)]
struct InitialError;
impl FromXml for InitialError {
type Builder = InitialErrorBuilder;
fn from_events(
_: rxml::QName,
_: rxml::AttrMap,
_: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Err(FromEventsError::Invalid(Error::Other("some error")))
}
}
#[derive(Debug)]
struct FailOnContentBuilder;
impl FromEventsBuilder for FailOnContentBuilder {
type Output = FailOnContent;
fn feed(&mut self, _: Event, _: &Context<'_>) -> Result<Option<Self::Output>, Error> {
Err(Error::Other("content error"))
}
}
#[derive(Debug)]
struct FailOnContent;
impl FromXml for FailOnContent {
type Builder = FailOnContentBuilder;
fn from_events(
_: rxml::QName,
_: rxml::AttrMap,
_: &Context<'_>,
) -> Result<Self::Builder, FromEventsError> {
Ok(FailOnContentBuilder)
}
}
fn qname() -> rxml::QName {
(Namespace::NONE, NcName::try_from("test").unwrap())
}
fn attrs() -> rxml::AttrMap {
rxml::AttrMap::new()
}
#[test]
fn fallible_builder_mismatch_passthrough() {
match Result::<AlwaysMismatch, Error>::from_events(qname(), attrs(), &Context::empty()) {
Err(FromEventsError::Mismatch { .. }) => (),
other => panic!("unexpected result: {:?}", other),
}
}
#[test]
fn fallible_builder_initial_error_capture() {
let ctx = Context::empty();
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
}
#[test]
fn fallible_builder_initial_error_capture_allows_nested_stuff() {
let ctx = Context::empty();
let mut builder = match Result::<InitialError, Error>::from_events(qname(), attrs(), &ctx) {
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("some error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
}
#[test]
fn fallible_builder_content_error_capture() {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
}
#[test]
fn fallible_builder_content_error_capture_with_more_content() {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
}
#[test]
fn fallible_builder_content_error_capture_with_nested_content() {
let ctx = Context::empty();
let mut builder = match Result::<FailOnContent, Error>::from_events(qname(), attrs(), &ctx)
{
Ok(v) => v,
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::StartElement(EventMetrics::zero(), qname(), attrs()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(
Event::Text(EventMetrics::zero(), "hello world!".to_owned()),
&ctx,
) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(None) => (),
other => panic!("unexpected result: {:?}", other),
};
match builder.feed(Event::EndElement(EventMetrics::zero()), &ctx) {
Ok(Some(Err(Error::Other("content error")))) => (),
other => panic!("unexpected result: {:?}", other),
};
}
}