use std::ops::Coroutine; use std::pin::Pin; use quick_xml::events::Event; use quick_xml::name::ResolveResult; use quick_xml::NsReader; use anyhow::Result; mod ignore; pub use ignore::Ignore; /// Types which can be parsed from an XML input stream. /// /// Example: /// ``` /// #![feature(type_alias_impl_trait)] /// #![feature(impl_trait_in_assoc_type)] /// #![feature(coroutines)] /// # use proto_xmpp::xml::FromXml; /// # use quick_xml::events::Event; /// # use quick_xml::name::ResolveResult; /// # use proto_xmpp::xml::Parser; /// # use anyhow::Result; /// /// struct MyStruct; /// impl FromXml for MyStruct { /// type P = impl Parser>; /// /// fn parse() -> Self::P { /// |(mut namespace, mut event): (ResolveResult<'static>, &'static Event<'static>)| -> Result { /// (namespace, event) = yield; /// Ok(MyStruct) /// } /// } /// } /// ``` pub trait FromXml: Sized { /// The type of parser instances. /// /// If the result type of the [parse] is anonymous, this type member can be defined by using `impl Trait`. type P: Parser>; /// Creates a new instance of a parser with an initial state. fn parse() -> Self::P; } pub trait ToXml: Sized { fn serialize(&self, events: &mut Vec>); } pub trait FromXmlTag: FromXml { const NAME: &'static str; const NS: &'static str; } /// A stateful parser instance which consumes XML events until the parsing is complete. /// /// Usually implemented with the experimental coroutine syntax, which yields to consume the next XML event, /// and returns the final result when the parsing is done. pub trait Parser: Sized { type Output; /// Advance the parsing by one XML event. /// /// This method consumes `self`, but if the parsing is incomplete, /// it will return the next state of the parser in the returned result. /// Otherwise, it will return the final result of parsing. fn consume<'a>(self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation; } impl Parser for T where T: Coroutine<(ResolveResult<'static>, &'static Event<'static>), Yield = (), Return = Out> + Unpin, { type Output = Out; fn consume<'a>(mut self: Self, namespace: ResolveResult, event: &Event<'a>) -> Continuation { let s = Pin::new(&mut self); // this is a very rude workaround fixing the fact that rust coroutines // 1. don't support higher-kinded lifetimes (i.e. no `impl for <'a> Coroutine>) // 2. don't track borrows across yield points and lack thereof // implementors of Parser should manually check that inputs are not used across yields match s.resume(unsafe { std::mem::transmute((namespace, event)) }) { std::ops::CoroutineState::Yielded(()) => Continuation::Continue(self), std::ops::CoroutineState::Complete(res) => Continuation::Final(res), } } } /// The result of a single parser iteration. pub enum Continuation { /// The parsing is complete and the final result is available. Final(Res), /// The parsing is not complete and more XML events are required. Continue(Parser), } pub fn parse(input: &str) -> Result { let mut reader = NsReader::from_reader(input.as_bytes()); let mut buf = vec![]; let (ns, event) = reader.read_resolved_event_into(&mut buf)?; let mut parser: Continuation<_, std::result::Result> = T::parse().consume(ns, &event); loop { match parser { Continuation::Final(res) => break res, Continuation::Continue(next) => { let (ns, event) = reader.read_resolved_event_into(&mut buf)?; parser = next.consume(ns, &event); } } } } macro_rules! fail_fast { ($errorable: expr) => { match $errorable { Ok(i) => i, Err(e) => return Continuation::Final(Err(e.into())), } }; } #[macro_export] macro_rules! delegate_parsing { ($parser: ty, $namespace: expr, $event: expr) => {{ let mut parser = <$parser as FromXml>::parse().consume($namespace, $event); loop { match parser { Continuation::Final(Ok(res)) => break Ok(res.into()), Continuation::Final(Err(err)) => break Err(err), Continuation::Continue(p) => { ($namespace, $event) = yield; parser = p.consume($namespace, $event); } } } }}; } #[macro_export] macro_rules! match_parser { ($name: expr, $ns: expr, $event: expr; $subtype: ty, $fin: block) => { if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) { delegate_parsing!($subtype, $ns, $event) } else { $fin } }; ($name: expr, $ns: expr, $event: expr; $subtype: ty, $($rest: ty),+, $fin: block) => { if $name.0 == <$subtype as FromXmlTag>::NAME.as_bytes() && $ns == ResolveResult::Bound(Namespace(<$subtype as FromXmlTag>::NS.as_bytes())) { delegate_parsing!($subtype, $ns, $event) } else { match_parser!($name, $ns, $event; $($rest),*, $fin) } }; } pub use delegate_parsing; pub(crate) use fail_fast; pub use match_parser;