use std::pin::Pin;
use std::task::{Context, Poll};
use oxixml_xml::errors::Error;
use oxixml_xml::events::{BytesEnd, BytesStart, Event};
use oxixml_xml::reader::Reader;
use tokio::io::{self, AsyncBufRead, AsyncRead, ReadBuf};
struct OneByteAsyncReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> OneByteAsyncReader<'a> {
fn new(data: &'a [u8]) -> Self {
Self { data, pos: 0 }
}
}
impl AsyncRead for OneByteAsyncReader<'_> {
fn poll_read(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
out: &mut ReadBuf<'_>,
) -> Poll<io::Result<()>> {
let this = self.get_mut();
let end = (this.pos + 1).min(this.data.len());
let chunk = &this.data[this.pos..end];
let n = chunk.len().min(out.remaining());
out.put_slice(&chunk[..n]);
this.pos += n;
Poll::Ready(Ok(()))
}
}
impl AsyncBufRead for OneByteAsyncReader<'_> {
fn poll_fill_buf(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<&[u8]>> {
let this = self.get_mut();
let end = (this.pos + 1).min(this.data.len());
Poll::Ready(Ok(&this.data[this.pos..end]))
}
fn consume(self: Pin<&mut Self>, amt: usize) {
let this = self.get_mut();
this.pos = (this.pos + amt).min(this.data.len());
}
}
fn collect_borrowed(xml: &str) -> Result<Vec<Event<'static>>, Error> {
let mut reader = Reader::from_str(xml);
let mut events = Vec::new();
loop {
let event = reader.read_event()?;
let is_eof = matches!(event, Event::Eof);
events.push(event.into_owned());
if is_eof {
break;
}
}
Ok(events)
}
async fn collect_one_byte_async(xml: &str) -> Result<Vec<Event<'static>>, Error> {
let mut reader = Reader::from_reader(OneByteAsyncReader::new(xml.as_bytes()));
let mut buf = Vec::new();
let mut events = Vec::new();
loop {
let event = reader.read_event_into_async(&mut buf).await?;
let is_eof = matches!(event, Event::Eof);
events.push(event.into_owned());
if is_eof {
break;
}
buf.clear();
}
Ok(events)
}
fn corpus() -> Vec<&'static str> {
vec![
"<?xml version=\"1.0\"?><root a=\"1\" b='2'><child>text</child><!--c--></root>",
"<a>&<0</a>",
"<![CDATA[ raw ]] ]]> data ]]><x/>",
"<!-- comment with -> inside --><y/>",
"<?target data with ? and > ?><z/>",
"<a>line1\r\nline2</a>",
"<!DOCTYPE r [ <!ENTITY e \"a > b\"> <!ATTLIST r a CDATA #IMPLIED> ]><r/>",
]
}
#[tokio::test]
async fn one_byte_async_chunks_match_borrowed() -> Result<(), Error> {
for doc in corpus() {
let borrowed = collect_borrowed(doc)?;
let chunked = collect_one_byte_async(doc).await?;
assert_eq!(borrowed, chunked, "mismatch for doc: {doc:?}");
}
Ok(())
}
#[tokio::test]
async fn read_event_into_async_basic_start_end() -> Result<(), Error> {
let mut reader = Reader::from_reader("<a></a>".as_bytes());
let mut buf = Vec::new();
assert_eq!(
reader.read_event_into_async(&mut buf).await?,
Event::Start(BytesStart::new("a"))
);
buf.clear();
assert_eq!(
reader.read_event_into_async(&mut buf).await?,
Event::End(BytesEnd::new("a"))
);
buf.clear();
assert_eq!(reader.read_event_into_async(&mut buf).await?, Event::Eof);
Ok(())
}
#[tokio::test]
async fn read_to_end_into_async_skips_nested_content() -> Result<(), Error> {
let mut reader = Reader::from_reader("<a><inner/></a><tail/>".as_bytes());
let mut buf = Vec::new();
assert_eq!(
reader.read_event_into_async(&mut buf).await?,
Event::Start(BytesStart::new("a"))
);
buf.clear();
let end = BytesStart::new("a").to_end().into_owned();
reader.read_to_end_into_async(end.name(), &mut buf).await?;
buf.clear();
assert_eq!(
reader.read_event_into_async(&mut buf).await?,
Event::Empty(BytesStart::new("tail"))
);
Ok(())
}