use std::borrow::Cow;
use quick_xml::{
Reader as QReader,
errors::Error,
events::{BytesEnd, BytesStart, Event as QEvent},
};
pub enum Event<'r> {
Start(BytesStart<'r>),
End(BytesEnd<'r>),
Empty(BytesStart<'r>),
}
pub struct Reader<'r> {
reader: QReader<&'r [u8]>,
peeked: Option<Event<'r>>,
xml: &'r [u8],
}
impl<'r> Reader<'r> {
pub fn new(xml: &'r [u8]) -> Self {
Self {
reader: QReader::from_reader(xml),
peeked: None,
xml,
}
}
#[inline]
fn read_inner(&mut self) -> Result<Option<Event<'r>>, Error> {
Ok(loop {
break match self.reader.read_event()? {
QEvent::Start(bytes_start) => Some(Event::Start(bytes_start)),
QEvent::End(bytes_end) => Some(Event::End(bytes_end)),
QEvent::Empty(bytes_start) => Some(Event::Empty(bytes_start)),
QEvent::Eof => None,
_ => continue,
};
})
}
pub fn read(&mut self) -> Result<Option<Event<'r>>, Error> {
if let Some(event) = self.peeked.take() {
return Ok(Some(event));
}
self.read_inner()
}
pub fn read_text(&mut self, start: &BytesStart<'_>) -> Result<Cow<'r, str>, Error> {
self.reader.read_text(start.to_end().name())
}
pub fn find_before<B, F, H>(&mut self, mut f: F, mut halt: H) -> Result<Option<B>, Error>
where
F: FnMut(Event<'r>) -> Option<B>,
H: FnMut(&Event<'r>) -> bool,
{
let Some(mut current) = self.read()? else {
return Ok(None);
};
loop {
if halt(¤t) {
self.peeked = Some(current);
return Ok(None);
} else {
if let Some(mapped) = f(current) {
return Ok(Some(mapped));
}
current = match self.read_inner()? {
Some(e) => e,
None => return Ok(None),
}
}
}
}
fn find_matching_tag(
&mut self,
mut f: impl FnMut(&[u8]) -> bool,
) -> Result<Option<BytesStart<'r>>, Error> {
self.find_before(
|event| match event {
Event::Start(bytes_start) if f(bytes_start.name().0) => Some(bytes_start),
_ => None,
},
|_| false,
)
}
pub fn find_text_matching_tag(
&mut self,
f: impl FnMut(&[u8]) -> bool,
) -> Result<Option<Cow<'r, str>>, Error> {
if let Some(bytes_start) = self.find_matching_tag(f)? {
Ok(Some(self.read_text(&bytes_start)?))
} else {
Ok(None)
}
}
pub fn find_raw_matching_tag(
&mut self,
f: impl FnMut(&[u8]) -> bool,
) -> Result<Option<&'r [u8]>, Error> {
if let Some(bytes_start) = self.find_matching_tag(f)? {
let span = self.reader.read_to_end(bytes_start.to_end().name())?;
Ok(self.xml.get(span.start as usize..span.end as usize))
} else {
Ok(None)
}
}
}