use quick_xml::events::{BytesCData, BytesEnd, BytesStart, BytesText, Event};
use quick_xml::{Reader, Writer};
use std::io::{BufRead, Write};
use crate::{Error, FromXml, ParseError, Result};
#[derive(Clone, Debug, PartialEq)]
pub struct PositionedNode {
pub position: usize,
pub node: RawXmlNode,
}
impl PositionedNode {
pub fn new(position: usize, node: RawXmlNode) -> Self {
Self { position, node }
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct PositionedAttr {
pub position: usize,
pub name: String,
pub value: String,
}
impl PositionedAttr {
pub fn new(position: usize, name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
position,
name: name.into(),
value: value.into(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum RawXmlNode {
Element(RawXmlElement),
Text(String),
CData(String),
Comment(String),
}
#[derive(Clone, Debug, PartialEq)]
pub struct RawXmlElement {
pub name: String,
pub attributes: Vec<(String, String)>,
pub children: Vec<RawXmlNode>,
pub self_closing: bool,
}
impl RawXmlElement {
pub fn new(name: impl Into<String>) -> Self {
Self {
name: name.into(),
attributes: Vec::new(),
children: Vec::new(),
self_closing: false,
}
}
pub fn from_reader<R: BufRead>(reader: &mut Reader<R>, start: &BytesStart) -> Result<Self> {
let name = String::from_utf8_lossy(start.name().as_ref()).to_string();
let attributes = start
.attributes()
.filter_map(|a| a.ok())
.map(|a| {
(
String::from_utf8_lossy(a.key.as_ref()).to_string(),
String::from_utf8_lossy(&a.value).to_string(),
)
})
.collect();
let mut element = RawXmlElement {
name: name.clone(),
attributes,
children: Vec::new(),
self_closing: false,
};
let mut buf = Vec::new();
let target_name = start.name().as_ref().to_vec();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
let child = RawXmlElement::from_reader(reader, &e)?;
element.children.push(RawXmlNode::Element(child));
}
Ok(Event::Empty(e)) => {
let child = RawXmlElement::from_empty(&e);
element.children.push(RawXmlNode::Element(child));
}
Ok(Event::Text(e)) => {
let text = e.decode().unwrap_or_default();
if !text.is_empty() {
element.children.push(RawXmlNode::Text(text.to_string()));
}
}
Ok(Event::CData(e)) => {
let text = String::from_utf8_lossy(&e).to_string();
element.children.push(RawXmlNode::CData(text));
}
Ok(Event::Comment(e)) => {
let text = String::from_utf8_lossy(&e).to_string();
element.children.push(RawXmlNode::Comment(text));
}
Ok(Event::End(e)) => {
if e.name().as_ref() == target_name {
break;
}
}
Ok(Event::Eof) => {
return Err(Error::Invalid(format!(
"Unexpected EOF while parsing element '{}'",
name
)));
}
Err(e) => return Err(Error::Xml(e)),
_ => {}
}
buf.clear();
}
Ok(element)
}
pub fn from_empty(start: &BytesStart) -> Self {
let name = String::from_utf8_lossy(start.name().as_ref()).to_string();
let attributes = start
.attributes()
.filter_map(|a| a.ok())
.map(|a| {
(
String::from_utf8_lossy(a.key.as_ref()).to_string(),
String::from_utf8_lossy(&a.value).to_string(),
)
})
.collect();
RawXmlElement {
name,
attributes,
children: Vec::new(),
self_closing: true,
}
}
pub fn parse_as<T: FromXml>(&self) -> std::result::Result<T, ParseError> {
let streaming_reader = RawXmlStreamReader::new(self);
let mut reader = Reader::from_reader(streaming_reader);
let mut buf = Vec::new();
loop {
match reader.read_event_into(&mut buf) {
Ok(Event::Start(e)) => {
return T::from_xml(&mut reader, &e, false);
}
Ok(Event::Empty(e)) => {
return T::from_xml(&mut reader, &e, true);
}
Ok(Event::Eof) => {
return Err(ParseError::UnexpectedElement(
"empty XML in parse_as".to_string(),
));
}
Err(e) => return Err(ParseError::Xml(e)),
_ => {}
}
buf.clear();
}
}
pub fn write_to<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
let mut start = BytesStart::new(&self.name);
for (key, value) in &self.attributes {
start.push_attribute((key.as_str(), value.as_str()));
}
if self.self_closing && self.children.is_empty() {
writer.write_event(Event::Empty(start))?;
} else {
writer.write_event(Event::Start(start))?;
for child in &self.children {
child.write_to(writer)?;
}
writer.write_event(Event::End(BytesEnd::new(&self.name)))?;
}
Ok(())
}
}
impl RawXmlNode {
pub fn write_to<W: Write>(&self, writer: &mut Writer<W>) -> Result<()> {
match self {
RawXmlNode::Element(elem) => elem.write_to(writer),
RawXmlNode::Text(text) => {
writer.write_event(Event::Text(BytesText::new(text)))?;
Ok(())
}
RawXmlNode::CData(text) => {
writer.write_event(Event::CData(BytesCData::new(text)))?;
Ok(())
}
RawXmlNode::Comment(text) => {
writer.write_event(Event::Comment(BytesText::new(text)))?;
Ok(())
}
}
}
}
pub struct RawXmlStreamReader<'a> {
stack: Vec<(&'a RawXmlElement, usize, ElementState)>,
buffer: Vec<u8>,
pos: usize,
done: bool,
}
#[derive(Clone, Copy, PartialEq)]
enum ElementState {
Start,
Children,
End,
}
impl<'a> RawXmlStreamReader<'a> {
pub fn new(elem: &'a RawXmlElement) -> Self {
Self {
stack: vec![(elem, 0, ElementState::Start)],
buffer: Vec::with_capacity(256),
pos: 0,
done: false,
}
}
fn generate_next(&mut self) {
self.buffer.clear();
self.pos = 0;
while self.buffer.is_empty() && !self.stack.is_empty() {
let (elem, child_idx, state) = self.stack.pop().unwrap();
match state {
ElementState::Start => {
self.buffer.push(b'<');
self.buffer.extend_from_slice(elem.name.as_bytes());
for (key, value) in &elem.attributes {
self.buffer.push(b' ');
self.buffer.extend_from_slice(key.as_bytes());
self.buffer.extend_from_slice(b"=\"");
for &b in value.as_bytes() {
match b {
b'"' => self.buffer.extend_from_slice(b"""),
b'&' => self.buffer.extend_from_slice(b"&"),
b'<' => self.buffer.extend_from_slice(b"<"),
_ => self.buffer.push(b),
}
}
self.buffer.push(b'"');
}
if elem.self_closing && elem.children.is_empty() {
self.buffer.extend_from_slice(b"/>");
} else {
self.buffer.push(b'>');
self.stack.push((elem, 0, ElementState::Children));
}
}
ElementState::Children => {
if child_idx < elem.children.len() {
self.stack
.push((elem, child_idx + 1, ElementState::Children));
match &elem.children[child_idx] {
RawXmlNode::Element(child) => {
self.stack.push((child, 0, ElementState::Start));
}
RawXmlNode::Text(text) => {
for &b in text.as_bytes() {
match b {
b'&' => self.buffer.extend_from_slice(b"&"),
b'<' => self.buffer.extend_from_slice(b"<"),
b'>' => self.buffer.extend_from_slice(b">"),
_ => self.buffer.push(b),
}
}
}
RawXmlNode::CData(text) => {
self.buffer.extend_from_slice(b"<![CDATA[");
self.buffer.extend_from_slice(text.as_bytes());
self.buffer.extend_from_slice(b"]]>");
}
RawXmlNode::Comment(text) => {
self.buffer.extend_from_slice(b"<!--");
self.buffer.extend_from_slice(text.as_bytes());
self.buffer.extend_from_slice(b"-->");
}
}
} else {
self.stack.push((elem, 0, ElementState::End));
}
}
ElementState::End => {
self.buffer.extend_from_slice(b"</");
self.buffer.extend_from_slice(elem.name.as_bytes());
self.buffer.push(b'>');
}
}
}
if self.stack.is_empty() && self.buffer.is_empty() {
self.done = true;
}
}
}
impl<'a> std::io::Read for RawXmlStreamReader<'a> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
if self.pos >= self.buffer.len() {
if self.done {
return Ok(0);
}
self.generate_next();
if self.done && self.buffer.is_empty() {
return Ok(0);
}
}
let remaining = &self.buffer[self.pos..];
let to_copy = remaining.len().min(buf.len());
buf[..to_copy].copy_from_slice(&remaining[..to_copy]);
self.pos += to_copy;
Ok(to_copy)
}
}
impl<'a> BufRead for RawXmlStreamReader<'a> {
fn fill_buf(&mut self) -> std::io::Result<&[u8]> {
if self.pos >= self.buffer.len() {
if self.done {
return Ok(&[]);
}
self.generate_next();
}
Ok(&self.buffer[self.pos..])
}
fn consume(&mut self, amt: usize) {
self.pos += amt;
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::io::Cursor;
#[test]
fn test_parse_simple_element() {
let xml = r#"<w:test attr="value">content</w:test>"#;
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
if let Ok(Event::Start(e)) = reader.read_event_into(&mut buf) {
let elem = RawXmlElement::from_reader(&mut reader, &e).unwrap();
assert_eq!(elem.name, "w:test");
assert_eq!(
elem.attributes,
vec![("attr".to_string(), "value".to_string())]
);
assert_eq!(elem.children.len(), 1);
if let RawXmlNode::Text(t) = &elem.children[0] {
assert_eq!(t, "content");
} else {
panic!("Expected text node");
}
}
}
#[test]
fn test_parse_nested_elements() {
let xml = r#"<parent><child1/><child2>text</child2></parent>"#;
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
if let Ok(Event::Start(e)) = reader.read_event_into(&mut buf) {
let elem = RawXmlElement::from_reader(&mut reader, &e).unwrap();
assert_eq!(elem.name, "parent");
assert_eq!(elem.children.len(), 2);
}
}
#[test]
fn test_roundtrip() {
let xml = r#"<w:test attr="value"><w:child>text</w:child></w:test>"#;
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
if let Ok(Event::Start(e)) = reader.read_event_into(&mut buf) {
let elem = RawXmlElement::from_reader(&mut reader, &e).unwrap();
let mut output = Vec::new();
let mut writer = Writer::new(Cursor::new(&mut output));
elem.write_to(&mut writer).unwrap();
let output_str = String::from_utf8(output).unwrap();
assert_eq!(output_str, xml);
}
}
#[test]
fn test_streaming_reader() {
use std::io::Read;
let xml = r#"<parent attr="val"><child>text</child></parent>"#;
let mut reader = Reader::from_str(xml);
let mut buf = Vec::new();
if let Ok(Event::Start(e)) = reader.read_event_into(&mut buf) {
let elem = RawXmlElement::from_reader(&mut reader, &e).unwrap();
let mut stream_reader = RawXmlStreamReader::new(&elem);
let mut output = String::new();
stream_reader.read_to_string(&mut output).unwrap();
assert_eq!(output, xml);
}
}
#[test]
fn test_streaming_reader_escaping() {
use std::io::Read;
let mut elem = RawXmlElement::new("test");
elem.attributes
.push(("attr".to_string(), "val\"ue".to_string()));
elem.children
.push(RawXmlNode::Text("a < b & c > d".to_string()));
let mut stream_reader = RawXmlStreamReader::new(&elem);
let mut output = String::new();
stream_reader.read_to_string(&mut output).unwrap();
assert_eq!(
output,
r#"<test attr="val"ue">a < b & c > d</test>"#
);
}
}