aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorQuentin Dufour <quentin@deuxfleurs.fr>2024-03-08 18:23:23 +0100
committerQuentin Dufour <quentin@deuxfleurs.fr>2024-03-08 18:23:23 +0100
commit7459f50b5486a137bc90b7e6e04e915d82230e28 (patch)
tree174a44858b3a539a9e9233feef95b0f4ff2ebfd6
parentb786573e08c78b672880cd212db45fc58ab82c4c (diff)
downloadaerogramme-7459f50b5486a137bc90b7e6e04e915d82230e28.tar.gz
aerogramme-7459f50b5486a137bc90b7e6e04e915d82230e28.zip
WIP implem cal decoder
-rw-r--r--aero-dav/src/caldecoder.rs411
-rw-r--r--aero-dav/src/calencoder.rs13
-rw-r--r--aero-dav/src/caltypes.rs21
-rw-r--r--aero-dav/src/decoder.rs4
-rw-r--r--aero-dav/src/error.rs1
-rw-r--r--aero-dav/src/xml.rs18
6 files changed, 439 insertions, 29 deletions
diff --git a/aero-dav/src/caldecoder.rs b/aero-dav/src/caldecoder.rs
index 3aae4ad..49d1c9e 100644
--- a/aero-dav/src/caldecoder.rs
+++ b/aero-dav/src/caldecoder.rs
@@ -1,68 +1,431 @@
+use quick_xml::events::Event;
+use chrono::NaiveDateTime;
+
use super::types as dav;
use super::caltypes::*;
-use super::xml;
-use super::error;
+use super::xml::{QRead, IRead, Reader, Node, CAL_URN};
+use super::error::ParsingError;
// ---- ROOT ELEMENTS ---
-impl<E: dav::Extension> xml::QRead<MkCalendar<E>> for MkCalendar<E> {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
- unreachable!();
+impl<E: dav::Extension> QRead<MkCalendar<E>> for MkCalendar<E> {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "mkcalendar").await?;
+ let set = xml.find().await?;
+ xml.close().await?;
+ Ok(MkCalendar(set))
}
}
-impl<E: dav::Extension, N: xml::Node<N>> xml::QRead<MkCalendarResponse<E,N>> for MkCalendarResponse<E,N> {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl<E: dav::Extension, N: Node<N>> QRead<MkCalendarResponse<E,N>> for MkCalendarResponse<E,N> {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
-impl<E: dav::Extension> xml::QRead<CalendarQuery<E>> for CalendarQuery<E> {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl<E: dav::Extension> QRead<CalendarQuery<E>> for CalendarQuery<E> {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
-impl<E: dav::Extension> xml::QRead<CalendarMultiget<E>> for CalendarMultiget<E> {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl<E: dav::Extension> QRead<CalendarMultiget<E>> for CalendarMultiget<E> {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
-impl xml::QRead<FreeBusyQuery> for FreeBusyQuery {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl QRead<FreeBusyQuery> for FreeBusyQuery {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
// ---- EXTENSIONS ---
-impl xml::QRead<Violation> for Violation {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl QRead<Violation> for Violation {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
-impl xml::QRead<Property> for Property {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
- unreachable!();
+impl QRead<Property> for Property {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ if xml.maybe_open(CAL_URN, "calendar-description").await?.is_some() {
+ let lang = xml.prev_attr("xml:lang");
+ let text = xml.tag_string().await?;
+ xml.close().await?;
+ return Ok(Property::CalendarDescription { lang, text })
+ }
+
+ if xml.maybe_open(CAL_URN, "calendar-timezone").await?.is_some() {
+ let tz = xml.tag_string().await?;
+ xml.close().await?;
+ return Ok(Property::CalendarTimezone(tz))
+ }
+
+ if xml.maybe_open(CAL_URN, "supported-calendar-component-set").await?.is_some() {
+ let comp = xml.collect().await?;
+ xml.close().await?;
+ return Ok(Property::SupportedCalendarComponentSet(comp))
+ }
+
+ if xml.maybe_open(CAL_URN, "supported-calendar-data").await?.is_some() {
+ let mime = xml.collect().await?;
+ xml.close().await?;
+ return Ok(Property::SupportedCalendarData(mime))
+ }
+
+ if xml.maybe_open(CAL_URN, "max-resource-size").await?.is_some() {
+ let sz = xml.tag_string().await?.parse::<u64>()?;
+ xml.close().await?;
+ return Ok(Property::MaxResourceSize(sz))
+ }
+
+ if xml.maybe_open(CAL_URN, "max-date-time").await?.is_some() {
+ let dtstr = xml.tag_string().await?;
+ let dt = NaiveDateTime::parse_from_str(dtstr.as_str(), ICAL_DATETIME_FMT)?.and_utc();
+ xml.close().await?;
+ return Ok(Property::MaxDateTime(dt))
+ }
+
+ if xml.maybe_open(CAL_URN, "max-instances").await?.is_some() {
+ let sz = xml.tag_string().await?.parse::<u64>()?;
+ xml.close().await?;
+ return Ok(Property::MaxInstances(sz))
+ }
+
+ if xml.maybe_open(CAL_URN, "max-attendees-per-instance").await?.is_some() {
+ let sz = xml.tag_string().await?.parse::<u64>()?;
+ xml.close().await?;
+ return Ok(Property::MaxAttendeesPerInstance(sz))
+ }
+
+ if xml.maybe_open(CAL_URN, "supported-collation-set").await?.is_some() {
+ let cols = xml.collect().await?;
+ xml.close().await?;
+ return Ok(Property::SupportedCollationSet(cols))
+ }
+
+ let mut dirty = false;
+ let mut caldata: Option<CalendarDataPayload> = None;
+ xml.maybe_read(&mut caldata, &mut dirty).await?;
+ if let Some(cal) = caldata {
+ return Ok(Property::CalendarData(cal))
+ }
+
+ Err(ParsingError::Recoverable)
}
}
-impl xml::QRead<PropertyRequest> for PropertyRequest {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl QRead<PropertyRequest> for PropertyRequest {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
-impl xml::QRead<ResourceType> for ResourceType {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl QRead<ResourceType> for ResourceType {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
// ---- INNER XML ----
-impl xml::QRead<SupportedCollation> for SupportedCollation {
- async fn qread(_xml: &mut xml::Reader<impl xml::IRead>) -> Result<Self, error::ParsingError> {
+impl QRead<SupportedCollation> for SupportedCollation {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "supported-collation").await?;
+ let col = Collation::new(xml.tag_string().await?);
+ xml.close().await?;
+ Ok(SupportedCollation(col))
+ }
+}
+
+impl QRead<CalendarDataPayload> for CalendarDataPayload {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "calendar-data").await?;
+ let mime = CalendarDataSupport::qread(xml).await.ok();
+ let payload = xml.tag_string().await?;
+ xml.close().await?;
+ Ok(CalendarDataPayload { mime, payload })
+ }
+}
+
+impl QRead<CalendarDataSupport> for CalendarDataSupport {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ let ct = xml.prev_attr("content-type");
+ let vs = xml.prev_attr("version");
+ match (ct, vs) {
+ (Some(content_type), Some(version)) => Ok(Self { content_type, version }),
+ _ => Err(ParsingError::Recoverable),
+ }
+ }
+}
+
+impl QRead<CalendarDataRequest> for CalendarDataRequest {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "calendar-data").await?;
+ let mime = CalendarDataSupport::qread(xml).await.ok();
+
+ let (mut comp, mut recurrence, mut limit_freebusy_set) = (None, None, None);
+
+ loop {
+ let mut dirty = false;
+ xml.maybe_read(&mut comp, &mut dirty).await?;
+ xml.maybe_read(&mut recurrence, &mut dirty).await?;
+ xml.maybe_read(&mut limit_freebusy_set, &mut dirty).await?;
+
+ if !dirty {
+ match xml.peek() {
+ Event::End(_) => break,
+ _ => xml.skip().await?,
+ };
+ }
+
+ }
+
+ xml.close().await?;
+ Ok(Self { mime, comp, recurrence, limit_freebusy_set })
+ }
+}
+
+impl QRead<CalendarDataEmpty> for CalendarDataEmpty {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "calendar-data").await?;
+ let mime = CalendarDataSupport::qread(xml).await.ok();
+ xml.close().await?;
+ Ok(Self(mime))
+ }
+}
+
+impl QRead<Comp> for Comp {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "comp").await?;
+ let name = Component::new(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ let additional_rules = Box::pin(xml.maybe_find()).await?;
+ xml.close().await?;
+ Ok(Self { name, additional_rules })
+ }
+}
+
+impl QRead<CompInner> for CompInner {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ let (mut prop_kind, mut comp_kind) = (None, None);
+
+ loop {
+ let mut dirty = false;
+
+ xml.maybe_read(&mut prop_kind, &mut dirty).await?;
+ xml.maybe_read(&mut comp_kind, &mut dirty).await?;
+
+ if !dirty {
+ match xml.peek() {
+ Event::End(_) => break,
+ _ => xml.skip().await?,
+ };
+ }
+ };
+
+ match (prop_kind, comp_kind) {
+ (Some(prop_kind), Some(comp_kind)) => Ok(Self { prop_kind, comp_kind }),
+ _ => Err(ParsingError::MissingChild),
+ }
+ }
+}
+
+impl QRead<CompSupport> for CompSupport {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ xml.open(CAL_URN, "comp").await?;
+ let inner = Component::new(xml.prev_attr("name").ok_or(ParsingError::MissingAttribute)?);
+ xml.close().await?;
+ Ok(Self(inner))
+ }
+}
+
+impl QRead<CompKind> for CompKind {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ let mut comp = Vec::new();
+ loop {
+ let mut dirty = false;
+
+ if xml.maybe_open(CAL_URN, "allcomp").await?.is_some() {
+ xml.close().await?;
+ return Ok(CompKind::AllComp)
+ }
+
+ xml.maybe_push(&mut comp, &mut dirty).await?;
+
+ if !dirty {
+ match xml.peek() {
+ Event::End(_) => break,
+ _ => xml.skip().await?,
+ };
+ }
+ }
+ Ok(CompKind::Comp(comp))
+ }
+}
+
+impl QRead<PropKind> for PropKind {
+ async fn qread(xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ let mut prop = Vec::new();
+ loop {
+ let mut dirty = false;
+
+ if xml.maybe_open(CAL_URN, "allprop").await?.is_some() {
+ xml.close().await?;
+ return Ok(PropKind::AllProp)
+ }
+
+ xml.maybe_push(&mut prop, &mut dirty).await?;
+
+ if !dirty {
+ match xml.peek() {
+ Event::End(_) => break,
+ _ => xml.skip().await?,
+ };
+ }
+ }
+ Ok(PropKind::Prop(prop))
+ }
+}
+
+impl QRead<RecurrenceModifier> for RecurrenceModifier {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<Expand> for Expand {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<LimitRecurrenceSet> for LimitRecurrenceSet {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<LimitFreebusySet> for LimitFreebusySet {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl<E: dav::Extension> QRead<CalendarSelector<E>> for CalendarSelector<E> {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<CompFilter> for CompFilter {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<CompFilterRules> for CompFilterRules {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<CompFilterMatch> for CompFilterMatch {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<PropFilter> for PropFilter {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<PropFilterRules> for PropFilterRules {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<PropFilterMatch> for PropFilterMatch {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<TimeOrText> for TimeOrText {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
unreachable!();
}
}
+
+impl QRead<TextMatch> for TextMatch {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<ParamFilterMatch> for ParamFilterMatch {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<TimeZone> for TimeZone {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<Filter> for Filter {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<TimeRange> for TimeRange {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+impl QRead<CalProp> for CalProp {
+ async fn qread(_xml: &mut Reader<impl IRead>) -> Result<Self, ParsingError> {
+ unreachable!();
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ //use chrono::{FixedOffset, TimeZone};
+ use crate::realization::Calendar;
+ //use quick_reader::NsReader;
+
+ async fn deserialize<T: Node<T>>(src: &str) -> T {
+ let mut rdr = Reader::new(quick_xml::NsReader::from_reader(src.as_bytes())).await.unwrap();
+ rdr.find().await.unwrap()
+ }
+
+ #[tokio::test]
+ async fn basic_mkcalendar() {
+ let expected = MkCalendar(dav::Set(dav::PropValue(vec![
+ dav::Property::DisplayName("Lisa's Events".into()),
+ ])));
+
+ let src = r#"
+<?xml version="1.0" encoding="utf-8" ?>
+<C:mkcalendar xmlns:D="DAV:"
+ xmlns:C="urn:ietf:params:xml:ns:caldav">
+ <D:set>
+ <D:prop>
+ <D:displayname>Lisa's Events</D:displayname>
+ </D:prop>
+ </D:set>
+ </C:mkcalendar>
+"#;
+ let got = deserialize::<MkCalendar<Calendar>>(src).await;
+ assert_eq!(got, expected)
+ }
+}
diff --git a/aero-dav/src/calencoder.rs b/aero-dav/src/calencoder.rs
index a25d767..55778db 100644
--- a/aero-dav/src/calencoder.rs
+++ b/aero-dav/src/calencoder.rs
@@ -5,7 +5,6 @@ use super::caltypes::*;
use super::xml::{Node, QWrite, IWrite, Writer};
use super::types::Extension;
-const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
// ==================== Calendar Types Serialization =========================
@@ -300,6 +299,12 @@ impl QWrite for Collation {
}
}
+impl QWrite for CalendarDataSupport {
+ async fn qwrite(&self, _xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
+ unreachable!();
+ }
+}
+
impl QWrite for CalendarDataPayload {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut start = xml.create_cal_element("calendar-data");
@@ -348,6 +353,12 @@ impl QWrite for CalendarDataEmpty {
}
}
+impl QWrite for CompInner {
+ async fn qwrite(&self, _xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
+ unreachable!();
+ }
+}
+
impl QWrite for Comp {
async fn qwrite(&self, xml: &mut Writer<impl IWrite>) -> Result<(), QError> {
let mut start = xml.create_cal_element("comp");
diff --git a/aero-dav/src/caltypes.rs b/aero-dav/src/caltypes.rs
index 9b9091e..d04c67a 100644
--- a/aero-dav/src/caltypes.rs
+++ b/aero-dav/src/caltypes.rs
@@ -4,6 +4,8 @@ use chrono::{DateTime,Utc};
use super::types as dav;
use super::xml;
+pub const ICAL_DATETIME_FMT: &str = "%Y%m%dT%H%M%SZ";
+
//@FIXME ACL (rfc3744) is missing, required
//@FIXME Versioning (rfc3253) is missing, required
//@FIXME WebDAV sync (rfc6578) is missing, optional
@@ -1418,6 +1420,18 @@ impl Component {
Self::Unknown(c) => c,
}
}
+ pub fn new(v: String) -> Self {
+ match v.as_str() {
+ "VCALENDAR" => Self::VCalendar,
+ "VJOURNAL" => Self::VJournal,
+ "VFREEBUSY" => Self::VFreeBusy,
+ "VEVENT" => Self::VEvent,
+ "VTODO" => Self::VTodo,
+ "VALARM" => Self::VAlarm,
+ "VTIMEZONE" => Self::VTimeZone,
+ _ => Self::Unknown(v),
+ }
+ }
}
/// name="VERSION", name="SUMMARY", etc.
@@ -1450,4 +1464,11 @@ impl Collation {
Self::Unknown(c) => c.as_str(),
}
}
+ pub fn new(v: String) -> Self {
+ match v.as_str() {
+ "i;ascii-casemap" => Self::AsciiCaseMap,
+ "i;octet" => Self::Octet,
+ _ => Self::Unknown(v),
+ }
+ }
}
diff --git a/aero-dav/src/decoder.rs b/aero-dav/src/decoder.rs
index 766d19c..de04dd4 100644
--- a/aero-dav/src/decoder.rs
+++ b/aero-dav/src/decoder.rs
@@ -551,7 +551,9 @@ impl QRead<LockScope> for LockScope {
if xml.maybe_open(DAV_URN, "exclusive").await?.is_some() {
xml.close().await?;
break LockScope::Exclusive
- } else if xml.maybe_open(DAV_URN, "shared").await?.is_some() {
+ }
+
+ if xml.maybe_open(DAV_URN, "shared").await?.is_some() {
xml.close().await?;
break LockScope::Shared
}
diff --git a/aero-dav/src/error.rs b/aero-dav/src/error.rs
index 78c6d6b..f1b5cba 100644
--- a/aero-dav/src/error.rs
+++ b/aero-dav/src/error.rs
@@ -4,6 +4,7 @@ use quick_xml::events::attributes::AttrError;
pub enum ParsingError {
Recoverable,
MissingChild,
+ MissingAttribute,
NamespacePrefixAlreadyUsed,
WrongToken,
TagNotFound,
diff --git a/aero-dav/src/xml.rs b/aero-dav/src/xml.rs
index f9e04eb..e021543 100644
--- a/aero-dav/src/xml.rs
+++ b/aero-dav/src/xml.rs
@@ -55,6 +55,7 @@ impl<T: IWrite> Writer<T> {
pub struct Reader<T: IRead> {
pub rdr: NsReader<T>,
cur: Event<'static>,
+ prev: Event<'static>,
parents: Vec<Event<'static>>,
buf: Vec<u8>,
}
@@ -63,8 +64,9 @@ impl<T: IRead> Reader<T> {
let mut buf: Vec<u8> = vec![];
let cur = rdr.read_event_into_async(&mut buf).await?.into_owned();
let parents = vec![];
+ let prev = Event::Eof;
buf.clear();
- Ok(Self { cur, parents, rdr, buf })
+ Ok(Self { cur, prev, parents, rdr, buf })
}
/// read one more tag
@@ -72,8 +74,8 @@ impl<T: IRead> Reader<T> {
async fn next(&mut self) -> Result<Event<'static>, ParsingError> {
let evt = self.rdr.read_event_into_async(&mut self.buf).await?.into_owned();
self.buf.clear();
- let old_evt = std::mem::replace(&mut self.cur, evt);
- Ok(old_evt)
+ self.prev = std::mem::replace(&mut self.cur, evt);
+ Ok(self.prev.clone())
}
/// skip a node at current level
@@ -252,6 +254,16 @@ impl<T: IRead> Reader<T> {
}
}
+ pub fn prev_attr(&self, attr: &str) -> Option<String> {
+ match &self.prev {
+ Event::Start(bs) | Event::Empty(bs) => match bs.try_get_attribute(attr) {
+ Ok(Some(attr)) => attr.decode_and_unescape_value(&self.rdr).ok().map(|v| v.into_owned()),
+ _ => None,
+ }
+ _ => None,
+ }
+ }
+
// find stop tag
pub async fn close(&mut self) -> Result<Event<'static>, ParsingError> {
//println!("close tag {:?}", self.parents.last());