use std::{
io::{BufReader, Read},
num::{ParseFloatError, ParseIntError},
str::FromStr,
};
use quick_xml::events::Event;
use thiserror::Error;
#[derive(Debug, Error)]
pub enum XmlReadError {
#[error("{0}")]
Xml(#[from] quick_xml::Error),
#[error("{0}")]
Decode(#[from] quick_xml::encoding::EncodingError),
#[error("Unexpected EOF")]
UnexpectedEof,
#[error("Failed to parse integer: {0}")]
ParseInt(#[from] ParseIntError),
#[error("Failed to parse float: {0}")]
ParseFloat(#[from] ParseFloatError),
#[error("Failed to parse value: {0}")]
Parse(String),
}
pub struct XmlStreamReader<T> {
reader: quick_xml::Reader<BufReader<T>>,
buffer: Vec<u8>,
}
impl<T: Read> XmlStreamReader<T> {
pub fn new(reader: T) -> Self {
Self {
reader: quick_xml::Reader::from_reader(BufReader::new(reader)),
buffer: Vec::new(),
}
}
pub fn next_event(&mut self) -> Result<quick_xml::events::Event<'_>, XmlReadError> {
self.buffer.clear();
Ok(self.reader.read_event_into(&mut self.buffer)?)
}
pub fn skip_value(&mut self) -> Result<(), XmlReadError> {
let mut depth = 1u32;
loop {
match self.next_event()? {
Event::Start(_) => depth += 1,
Event::End(_) => {
depth -= 1;
if depth == 0 {
return Ok(());
}
}
Event::Eof => {
if depth == 1 {
return Ok(());
} else {
return Err(XmlReadError::UnexpectedEof);
}
}
_ => {}
}
}
}
pub fn consume_as_text(&mut self) -> Result<String, XmlReadError> {
let mut text: Option<String> = None;
let mut depth = 1u32;
loop {
match self.next_event()? {
Event::Start(_) => depth += 1,
Event::End(_) => {
depth -= 1;
if depth == 0 {
if let Some(mut text) = text {
let trimmed = text.trim_ascii_end();
text.truncate(trimmed.len());
return Ok(text);
} else {
return Ok(String::new());
}
}
}
Event::Text(mut e) => {
if depth != 1 {
continue;
}
if let Some(text) = text.as_mut() {
text.push_str(&e.decode()?);
} else if e.inplace_trim_start() {
continue;
} else {
text = Some(e.decode()?.into_owned());
}
}
Event::Eof => {
if depth == 1 {
if let Some(mut text) = text {
let trimmed = text.trim_ascii_end();
text.truncate(trimmed.len());
return Ok(text);
} else {
return Ok(String::new());
}
} else {
return Err(XmlReadError::UnexpectedEof);
}
}
_ => continue,
}
}
}
pub fn consume_raw(&mut self) -> Result<Vec<u8>, XmlReadError> {
let mut out = Vec::new();
let mut depth = 1u32;
loop {
let evt = self.next_event()?;
match evt {
Event::Start(s) => {
depth += 1;
out.push(b'<');
out.extend_from_slice(&s);
out.push(b'>');
}
Event::End(s) => {
depth -= 1;
if depth == 0 {
return Ok(out);
}
out.extend_from_slice(b"</");
out.extend_from_slice(&s);
out.push(b'>');
}
Event::CData(s) => {
out.extend_from_slice(b"<![CDATA[");
out.extend_from_slice(&s);
out.extend_from_slice(b"]]>");
}
Event::Comment(s) => {
out.extend_from_slice(b"<!--");
out.extend_from_slice(&s);
out.extend_from_slice(b"-->");
}
Event::Decl(s) => {
out.extend_from_slice(b"<?");
out.extend_from_slice(&s);
out.extend_from_slice(b"?>");
}
Event::DocType(s) => {
out.extend_from_slice(b"<!DOCTYPE");
out.extend_from_slice(&s);
out.push(b'>');
}
Event::Empty(s) => {
out.push(b'<');
out.extend_from_slice(&s);
out.extend_from_slice(b"/>");
}
Event::PI(s) => {
out.extend_from_slice(b"<?");
out.extend_from_slice(&s);
out.extend_from_slice(b"?>");
}
Event::Text(s) => {
out.extend_from_slice(&s);
}
Event::Eof => {
if depth == 1 {
return Ok(out);
} else {
return Err(XmlReadError::UnexpectedEof);
}
}
Event::GeneralRef(s) => {
out.push(b'&');
out.extend_from_slice(&s);
out.push(b';');
}
}
}
}
pub fn consume_content<R: FromStr>(&mut self) -> Result<R, XmlReadError>
where
XmlReadError: From<<R as FromStr>::Err>,
{
let text = self.consume_as_text()?;
Ok(text.parse()?)
}
}
#[cfg(test)]
mod test {
use std::io::Cursor;
use quick_xml::events::Event;
#[test]
fn test_xml_text_comments() {
let xml = r#"
<Foo>
Ho
<Bar>
Hello
</Bar>
Hello <!-- Comment --> there
</Foo>
"#;
let mut cursor = Cursor::new(xml.as_bytes());
let mut reader = super::XmlStreamReader::new(&mut cursor);
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::End(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Comment(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::End(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Text(_)));
assert!(matches!(reader.next_event().unwrap(), Event::Eof));
assert!(matches!(reader.next_event().unwrap(), Event::Eof));
}
#[test]
fn test_consume_as_text() {
let xml = r#"<Foo>
<Bar>
Hello
</Bar>
Hello <!-- Comment -->there
</Foo>"#;
let mut cursor = Cursor::new(xml.as_bytes());
let mut reader = super::XmlStreamReader::new(&mut cursor);
assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
assert_eq!(reader.consume_as_text().unwrap(), "Hello there");
}
#[test]
fn test_consume_content() {
let xml = r#"<Foo>
12345
</Foo>"#;
let mut cursor = Cursor::new(xml.as_bytes());
let mut reader = super::XmlStreamReader::new(&mut cursor);
assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
assert_eq!(reader.consume_content::<u32>().unwrap(), 12345);
}
#[test]
fn test_consume_raw() {
let xml = r#"<Foo>
<Bar>
Hello <!-- Comment here -->
More text
</Bar>
<Bar attr = "foo" />
<? Mystery PI ?>
</Foo>"#;
let mut cursor = Cursor::new(xml.as_bytes());
let mut reader = super::XmlStreamReader::new(&mut cursor);
assert!(matches!(reader.next_event().unwrap(), Event::Start(_)));
let raw = reader.consume_raw().unwrap();
println!("{}", String::from_utf8_lossy(&raw));
assert_eq!(&xml.as_bytes()[5..(xml.len() - 6)], &*raw);
}
}