use tokio::io::{AsyncRead, AsyncReadExt};
use crate::error::ParseError;
use crate::parser::{ParseOptions, PushParser};
use crate::tree::Document;
const DEFAULT_BUF_SIZE: usize = 8192;
pub async fn parse_async<R: AsyncRead + Unpin>(reader: R) -> Result<Document, ParseError> {
parse_async_with_options(reader, ParseOptions::default()).await
}
pub async fn parse_async_with_options<R: AsyncRead + Unpin>(
mut reader: R,
options: ParseOptions,
) -> Result<Document, ParseError> {
let mut parser = PushParser::with_options(options);
let mut buf = vec![0u8; DEFAULT_BUF_SIZE];
loop {
let n = reader.read(&mut buf).await.map_err(|e| ParseError {
message: format!("I/O error: {e}"),
location: crate::error::SourceLocation {
line: 0,
column: 0,
byte_offset: 0,
},
diagnostics: vec![],
})?;
if n == 0 {
break;
}
parser.push(&buf[..n]);
}
parser.finish()
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[tokio::test]
async fn test_parse_async_from_bytes() {
let data = b"<root><child>Hello</child></root>";
let cursor = std::io::Cursor::new(data);
let doc = parse_async(cursor).await.unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("root"));
assert_eq!(doc.text_content(root), "Hello");
}
#[tokio::test]
async fn test_parse_async_empty_document() {
let data = b"<root/>";
let cursor = std::io::Cursor::new(data);
let doc = parse_async(cursor).await.unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("root"));
}
#[tokio::test]
async fn test_parse_async_with_options() {
let data = b"<root><child>text</child></root>";
let cursor = std::io::Cursor::new(data);
let opts = ParseOptions::default().recover(true);
let doc = parse_async_with_options(cursor, opts).await.unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.node_name(root), Some("root"));
}
#[tokio::test]
async fn test_parse_async_malformed() {
let data = b"<root><</root>";
let cursor = std::io::Cursor::new(data);
let result = parse_async(cursor).await;
assert!(result.is_err());
}
#[tokio::test]
async fn test_parse_async_small_reads() {
let data = b"<root>Hello</root>";
let cursor = SlowReader { data, pos: 0 };
let doc = parse_async(cursor).await.unwrap();
let root = doc.root_element().unwrap();
assert_eq!(doc.text_content(root), "Hello");
}
struct SlowReader {
data: &'static [u8],
pos: usize,
}
impl AsyncRead for SlowReader {
fn poll_read(
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
buf: &mut tokio::io::ReadBuf<'_>,
) -> std::task::Poll<std::io::Result<()>> {
if self.pos >= self.data.len() {
return std::task::Poll::Ready(Ok(()));
}
buf.put_slice(&self.data[self.pos..=self.pos]);
self.pos += 1;
std::task::Poll::Ready(Ok(()))
}
}
}