minidom, tokio-xmpp: switch xml parsing to rxml

This commit is contained in:
Astro 2022-03-22 23:29:25 +01:00
commit d4a5a8247b
33 changed files with 465 additions and 811 deletions

View file

@ -17,18 +17,17 @@ use crate::error::{Error, Result};
use crate::namespaces::NSChoice;
use crate::node::Node;
use crate::prefixes::{Namespace, Prefix, Prefixes};
use crate::tree_builder::TreeBuilder;
use std::collections::{btree_map, BTreeMap};
use std::io::Write;
use std::io::{BufRead, Write};
use std::borrow::Cow;
use std::str;
use quick_xml::events::{BytesDecl, BytesEnd, BytesStart, Event};
use quick_xml::Reader as EventReader;
use quick_xml::Writer as EventWriter;
use std::io::BufRead;
use rxml::{EventRead, Lexer, PullDriver, RawParser};
use std::str::FromStr;
@ -84,8 +83,9 @@ pub struct Element {
namespace: String,
/// This is only used when deserializing. If you have to use a custom prefix use
/// `ElementBuilder::prefix`.
prefix: Option<Prefix>,
prefixes: Prefixes,
pub(crate) prefix: Option<Prefix>,
/// Namespace declarations
pub prefixes: Prefixes,
attributes: BTreeMap<String, String>,
children: Vec<Node>,
}
@ -102,8 +102,7 @@ impl FromStr for Element {
type Err = Error;
fn from_str(s: &str) -> Result<Element> {
let mut reader = EventReader::from_str(s);
Element::from_reader(&mut reader)
Element::from_reader(s.as_bytes())
}
}
@ -120,15 +119,8 @@ impl PartialEq for Element {
}
}
fn ensure_no_prefix<S: AsRef<str>>(s: &S) -> Result<()> {
match s.as_ref().split(':').count() {
1 => Ok(()),
_ => Err(Error::InvalidElement),
}
}
impl Element {
fn new<P: Into<Prefixes>>(
pub(crate) fn new<P: Into<Prefixes>>(
name: String,
namespace: String,
prefix: Option<Prefix>,
@ -136,8 +128,6 @@ impl Element {
attributes: BTreeMap<String, String>,
children: Vec<Node>,
) -> Element {
ensure_no_prefix(&name).unwrap();
// TODO: Return Result<Element> instead.
Element {
name,
namespace,
@ -310,123 +300,18 @@ impl Element {
namespace.into().compare(self.namespace.as_ref())
}
/// Parse a document from an `EventReader`.
pub fn from_reader<R: BufRead>(reader: &mut EventReader<R>) -> Result<Element> {
let mut buf = Vec::new();
/// Parse a document from a `BufRead`.
pub fn from_reader<R: BufRead>(reader: R) -> Result<Element> {
let mut tree_builder = TreeBuilder::new();
let mut driver = PullDriver::wrap(reader, Lexer::new(), RawParser::new());
while let Some(event) = driver.read()? {
tree_builder.process_event(event)?;
let mut prefixes = BTreeMap::new();
let root: Element = loop {
let e = reader.read_event(&mut buf)?;
match e {
Event::Empty(ref e) | Event::Start(ref e) => {
break build_element(reader, e, &mut prefixes)?;
}
Event::Eof => {
return Err(Error::EndOfDocument);
}
Event::Comment { .. } => {
return Err(Error::NoComments);
}
Event::Text { .. }
| Event::End { .. }
| Event::CData { .. }
| Event::Decl { .. }
| Event::PI { .. }
| Event::DocType { .. } => (), // TODO: may need more errors
}
};
let mut stack = vec![root];
let mut prefix_stack = vec![prefixes];
loop {
match reader.read_event(&mut buf)? {
Event::Empty(ref e) => {
let mut prefixes = prefix_stack.last().unwrap().clone();
let elem = build_element(reader, e, &mut prefixes)?;
// Since there is no Event::End after, directly append it to the current node
stack.last_mut().unwrap().append_child(elem);
}
Event::Start(ref e) => {
let mut prefixes = prefix_stack.last().unwrap().clone();
let elem = build_element(reader, e, &mut prefixes)?;
stack.push(elem);
prefix_stack.push(prefixes);
}
Event::End(ref e) => {
if stack.len() <= 1 {
break;
}
let prefixes = prefix_stack.pop().unwrap();
let elem = stack.pop().unwrap();
if let Some(to) = stack.last_mut() {
// TODO: check whether this is correct, we are comparing &[u8]s, not &strs
let elem_name = e.name();
let mut split_iter = elem_name.splitn(2, |u| *u == 0x3A);
let possible_prefix = split_iter.next().unwrap(); // Can't be empty.
let opening_prefix = {
let mut tmp: Option<Option<String>> = None;
for (prefix, ns) in prefixes {
if ns == elem.namespace {
tmp = Some(prefix.clone());
break;
}
}
match tmp {
Some(prefix) => prefix,
None => return Err(Error::InvalidPrefix),
}
};
match split_iter.next() {
// There is a prefix on the closing tag
Some(name) => {
// Does the closing prefix match the opening prefix?
match opening_prefix {
Some(prefix) if possible_prefix == prefix.as_bytes() => (),
_ => return Err(Error::InvalidElementClosed),
}
// Does the closing tag name match the opening tag name?
if name != elem.name().as_bytes() {
return Err(Error::InvalidElementClosed);
}
}
// There was no prefix on the closing tag
None => {
// Is there a prefix on the opening tag?
if opening_prefix.is_some() {
return Err(Error::InvalidElementClosed);
}
// Does the opening tag name match the closing one?
if possible_prefix != elem.name().as_bytes() {
return Err(Error::InvalidElementClosed);
}
}
}
to.append_child(elem);
}
}
Event::Text(s) => {
let text = s.unescape_and_decode(reader)?;
if !text.is_empty() {
let current_elem = stack.last_mut().unwrap();
current_elem.append_text_node(text);
}
}
Event::CData(s) => {
let text = s.unescape_and_decode(&reader)?;
if !text.is_empty() {
let current_elem = stack.last_mut().unwrap();
current_elem.append_text_node(text);
}
}
Event::Eof => {
break;
}
Event::Comment(_) => return Err(Error::NoComments),
Event::Decl { .. } | Event::PI { .. } | Event::DocType { .. } => (),
if let Some(root) = tree_builder.root.take() {
return Ok(root);
}
}
Ok(stack.pop().unwrap())
Err(Error::EndOfDocument)
}
/// Output a document to a `Writer`.
@ -822,68 +707,18 @@ impl Element {
})?;
self.children.remove(idx).into_element()
}
}
fn split_element_name<S: AsRef<str>>(s: S) -> Result<(Option<String>, String)> {
let name_parts = s.as_ref().split(':').collect::<Vec<&str>>();
match name_parts.len() {
2 => Ok((Some(name_parts[0].to_owned()), name_parts[1].to_owned())),
1 => Ok((None, name_parts[0].to_owned())),
_ => Err(Error::InvalidElement),
}
}
fn build_element<R: BufRead>(
reader: &EventReader<R>,
event: &BytesStart,
prefixes: &mut BTreeMap<Prefix, Namespace>,
) -> Result<Element> {
let (prefix, name) = split_element_name(str::from_utf8(event.name())?)?;
let mut local_prefixes = BTreeMap::new();
let attributes = event
.attributes()
.map(|o| {
let o = o?;
let key = str::from_utf8(o.key)?.to_owned();
let value = o.unescape_and_decode_value(reader)?;
Ok((key, value))
})
.filter(|o| match *o {
Ok((ref key, ref value)) if key == "xmlns" => {
local_prefixes.insert(None, value.clone());
prefixes.insert(None, value.clone());
false
/// Remove the leading nodes up to the first child element and
/// return it
pub fn unshift_child(&mut self) -> Option<Element> {
while self.children.len() > 0 {
if let Some(el) = self.children.remove(0).into_element() {
return Some(el);
}
Ok((ref key, ref value)) if key.starts_with("xmlns:") => {
local_prefixes.insert(Some(key[6..].to_owned()), value.to_owned());
prefixes.insert(Some(key[6..].to_owned()), value.to_owned());
false
}
_ => true,
})
.collect::<Result<BTreeMap<String, String>>>()?;
let namespace: &String = {
if let Some(namespace) = local_prefixes.get(&prefix) {
namespace
} else if let Some(namespace) = prefixes.get(&prefix) {
namespace
} else {
return Err(Error::MissingNamespace);
}
};
Ok(Element::new(
name,
namespace.clone(),
// Note that this will always be Some(_) as we can't distinguish between the None case and
// Some(None). At least we make sure the prefix has a namespace associated.
Some(prefix),
local_prefixes,
attributes,
Vec::new(),
))
None
}
}
/// An iterator over references to child elements of an `Element`.
@ -1067,9 +902,8 @@ mod tests {
#[test]
fn test_from_reader_simple() {
let xml = "<foo xmlns='ns1'></foo>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader);
let xml = b"<foo xmlns='ns1'></foo>";
let elem = Element::from_reader(&xml[..]);
let elem2 = Element::builder("foo", "ns1").build();
@ -1078,9 +912,8 @@ mod tests {
#[test]
fn test_from_reader_nested() {
let xml = "<foo xmlns='ns1'><bar xmlns='ns1' baz='qxx' /></foo>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader);
let xml = b"<foo xmlns='ns1'><bar xmlns='ns1' baz='qxx' /></foo>";
let elem = Element::from_reader(&xml[..]);
let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build();
let elem2 = Element::builder("foo", "ns1").append(nested).build();
@ -1090,9 +923,8 @@ mod tests {
#[test]
fn test_from_reader_with_prefix() {
let xml = "<foo xmlns='ns1'><prefix:bar xmlns:prefix='ns1' baz='qxx' /></foo>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader);
let xml = b"<foo xmlns='ns1'><prefix:bar xmlns:prefix='ns1' baz='qxx' /></foo>";
let elem = Element::from_reader(&xml[..]);
let nested = Element::builder("bar", "ns1").attr("baz", "qxx").build();
let elem2 = Element::builder("foo", "ns1").append(nested).build();
@ -1102,9 +934,8 @@ mod tests {
#[test]
fn test_from_reader_split_prefix() {
let xml = "<foo:bar xmlns:foo='ns1'/>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader).unwrap();
let xml = b"<foo:bar xmlns:foo='ns1'/>";
let elem = Element::from_reader(&xml[..]).unwrap();
assert_eq!(elem.name(), String::from("bar"));
assert_eq!(elem.ns(), String::from("ns1"));
@ -1118,38 +949,32 @@ mod tests {
#[test]
fn parses_spectest_xml() {
// From: https://gitlab.com/lumi/minidom-rs/issues/8
let xml = r#"
<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
let xml = br#"<rng:grammar xmlns:rng="http://relaxng.org/ns/structure/1.0">
<rng:name xmlns:rng="http://relaxng.org/ns/structure/1.0"></rng:name>
</rng:grammar>
"#;
let mut reader = EventReader::from_str(xml);
let _ = Element::from_reader(&mut reader).unwrap();
let _ = Element::from_reader(&xml[..]).unwrap();
}
#[test]
fn does_not_unescape_cdata() {
let xml = "<test xmlns='test'><![CDATA[&apos;&gt;blah<blah>]]></test>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader).unwrap();
let xml = b"<test xmlns='test'><![CDATA[&apos;&gt;blah<blah>]]></test>";
let elem = Element::from_reader(&xml[..]).unwrap();
assert_eq!(elem.text(), "&apos;&gt;blah<blah>");
}
#[test]
fn test_compare_all_ns() {
let xml = "<foo xmlns='foo' xmlns:bar='baz'><bar:meh xmlns:bar='baz' /></foo>";
let mut reader = EventReader::from_str(xml);
let elem = Element::from_reader(&mut reader).unwrap();
let xml = b"<foo xmlns='foo' xmlns:bar='baz'><bar:meh xmlns:bar='baz' /></foo>";
let elem = Element::from_reader(&xml[..]).unwrap();
let elem2 = elem.clone();
let xml3 = "<foo xmlns='foo'><bar:meh xmlns:bar='baz'/></foo>";
let mut reader3 = EventReader::from_str(xml3);
let elem3 = Element::from_reader(&mut reader3).unwrap();
let xml3 = b"<foo xmlns='foo'><bar:meh xmlns:bar='baz'/></foo>";
let elem3 = Element::from_reader(&xml3[..]).unwrap();
let xml4 = "<prefix:foo xmlns:prefix='foo'><bar:meh xmlns:bar='baz'/></prefix:foo>";
let mut reader4 = EventReader::from_str(xml4);
let elem4 = Element::from_reader(&mut reader4).unwrap();
let xml4 = b"<prefix:foo xmlns:prefix='foo'><bar:meh xmlns:bar='baz'/></prefix:foo>";
let elem4 = Element::from_reader(&xml4[..]).unwrap();
assert_eq!(elem, elem2);
assert_eq!(elem, elem3);

View file

@ -20,21 +20,12 @@ pub enum Error {
/// An error from quick_xml.
XmlError(::quick_xml::Error),
/// An UTF-8 conversion error.
Utf8Error(::std::str::Utf8Error),
/// An I/O error, from std::io.
IoError(::std::io::Error),
/// Error from rxml parsing
ParserError(rxml::Error),
/// An error which is returned when the end of the document was reached prematurely.
EndOfDocument,
/// An error which is returned when an element is closed when it shouldn't be
InvalidElementClosed,
/// An error which is returned when an elemet's name contains more colons than permitted
InvalidElement,
/// An error which is returned when an element being serialized doesn't contain a prefix
/// (be it None or Some(_)).
InvalidPrefix,
@ -42,9 +33,6 @@ pub enum Error {
/// An error which is returned when an element doesn't contain a namespace
MissingNamespace,
/// An error which is returned when a comment is to be parsed by minidom
NoComments,
/// An error which is returned when a prefixed is defined twice
DuplicatePrefix,
}
@ -53,14 +41,10 @@ impl StdError for Error {
fn cause(&self) -> Option<&dyn StdError> {
match self {
Error::XmlError(e) => Some(e),
Error::Utf8Error(e) => Some(e),
Error::IoError(e) => Some(e),
Error::ParserError(e) => Some(e),
Error::EndOfDocument => None,
Error::InvalidElementClosed => None,
Error::InvalidElement => None,
Error::InvalidPrefix => None,
Error::MissingNamespace => None,
Error::NoComments => None,
Error::DuplicatePrefix => None,
}
}
@ -70,21 +54,12 @@ impl std::fmt::Display for Error {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
Error::XmlError(e) => write!(fmt, "XML error: {}", e),
Error::Utf8Error(e) => write!(fmt, "UTF-8 error: {}", e),
Error::IoError(e) => write!(fmt, "IO error: {}", e),
Error::ParserError(e) => write!(fmt, "XML parser error: {}", e),
Error::EndOfDocument => {
write!(fmt, "the end of the document has been reached prematurely")
}
Error::InvalidElementClosed => {
write!(fmt, "the XML is invalid, an element was wrongly closed")
}
Error::InvalidElement => write!(fmt, "the XML element is invalid"),
Error::InvalidPrefix => write!(fmt, "the prefix is invalid"),
Error::MissingNamespace => write!(fmt, "the XML element is missing a namespace",),
Error::NoComments => write!(
fmt,
"a comment has been found even though comments are forbidden"
),
Error::DuplicatePrefix => write!(fmt, "the prefix is already defined"),
}
}
@ -96,15 +71,9 @@ impl From<::quick_xml::Error> for Error {
}
}
impl From<::std::str::Utf8Error> for Error {
fn from(err: ::std::str::Utf8Error) -> Error {
Error::Utf8Error(err)
}
}
impl From<::std::io::Error> for Error {
fn from(err: ::std::io::Error) -> Error {
Error::IoError(err)
impl From<rxml::Error> for Error {
fn from(err: rxml::Error) -> Error {
Error::ParserError(err)
}
}

View file

@ -83,6 +83,7 @@ pub mod error;
mod namespaces;
pub mod node;
mod prefixes;
pub mod tree_builder;
#[cfg(test)]
mod tests;

View file

@ -10,16 +10,16 @@
use crate::element::Element;
use crate::error::{Error, ParserError, Result};
use crate::tree_builder::TreeBuilder;
use bytes::BytesMut;
use quick_xml::Reader as EventReader;
use std::cell::RefCell;
use rxml::{PushDriver, RawParser};
use std::str;
/// Parser
#[derive(Debug)]
pub struct Parser {
buffer: RefCell<BytesMut>,
driver: PushDriver<RawParser>,
tree_builder: TreeBuilder,
state: ParserState,
}
@ -90,14 +90,17 @@ impl Parser {
/// Creates a new Parser
pub fn new() -> Parser {
Parser {
buffer: RefCell::new(BytesMut::new()),
driver: PushDriver::default(),
tree_builder: TreeBuilder::new(),
state: ParserState::Empty,
}
}
/// Feed bytes to the parser.
pub fn feed(&mut self, bytes: BytesMut) -> Result<()> {
self.buffer.borrow_mut().unsplit(bytes);
self.driver.feed(bytes);
bytes.clear();
let state = match self.state {
ParserState::Empty => {
// TODO: Try splitting xml prolog and stream header

View file

@ -13,9 +13,7 @@
use crate::element::Element;
use crate::error::Error;
use quick_xml::Reader;
const TEST_STRING: &'static str = r#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
const TEST_STRING: &'static [u8] = br#"<root xmlns="root_ns" a="b" xml:lang="en">meow<child c="d"/><child xmlns="child_ns" d="e" xml:lang="fr"/>nya</root>"#;
fn build_test_tree() -> Element {
let mut root = Element::builder("root", "root_ns")
@ -36,9 +34,8 @@ fn build_test_tree() -> Element {
#[test]
fn reader_works() {
let mut reader = Reader::from_str(TEST_STRING);
assert_eq!(
Element::from_reader(&mut reader).unwrap(),
Element::from_reader(TEST_STRING).unwrap(),
build_test_tree()
);
}
@ -143,7 +140,7 @@ fn writer_works() {
{
root.write_to(&mut writer).unwrap();
}
assert_eq!(String::from_utf8(writer).unwrap(), TEST_STRING);
assert_eq!(writer, TEST_STRING);
}
#[test]
@ -153,7 +150,10 @@ fn writer_with_decl_works() {
{
root.write_to_decl(&mut writer).unwrap();
}
let result = format!(r#"<?xml version="1.0" encoding="utf-8"?>{}"#, TEST_STRING);
let result = format!(
r#"<?xml version="1.0" encoding="utf-8"?>{}"#,
String::from_utf8(TEST_STRING.to_owned()).unwrap()
);
assert_eq!(String::from_utf8(writer).unwrap(), result);
}
@ -348,8 +348,7 @@ fn two_elements_with_same_arguments_different_order_are_equal() {
#[test]
fn namespace_attributes_works() {
let mut reader = Reader::from_str(TEST_STRING);
let root = Element::from_reader(&mut reader).unwrap();
let root = Element::from_reader(TEST_STRING).unwrap();
assert_eq!("en", root.attr("xml:lang").unwrap());
assert_eq!(
"fr",
@ -424,7 +423,7 @@ fn namespace_inherited_prefixed2() {
fn fail_comments() {
let elem: Result<Element, Error> = "<foo xmlns='ns1'><!-- bar --></foo>".parse();
match elem {
Err(Error::NoComments) => (),
Err(_) => (),
_ => panic!(),
};
}
@ -432,20 +431,16 @@ fn fail_comments() {
#[test]
fn xml_error() {
match "<a xmlns='ns1'></b>".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (),
Err(crate::error::Error::ParserError(rxml::Error::NotWellFormed(
rxml::error::WFError::ElementMismatch,
))) => (),
err => panic!("No or wrong error: {:?}", err),
}
match "<a xmlns='ns1'></".parse::<Element>() {
Err(crate::error::Error::XmlError(_)) => (),
err => panic!("No or wrong error: {:?}", err),
}
}
#[test]
fn invalid_element_error() {
match "<a:b:c>".parse::<Element>() {
Err(crate::error::Error::InvalidElement) => (),
Err(crate::error::Error::ParserError(rxml::Error::NotWellFormed(
rxml::error::WFError::InvalidEof(_),
))) => (),
err => panic!("No or wrong error: {:?}", err),
}
}

154
minidom/src/tree_builder.rs Normal file
View file

@ -0,0 +1,154 @@
// Copyright (c) 2022 Astro <astro@spaceboyz.net>
//! SAX events to DOM tree conversion
use crate::prefixes::{Prefix, Prefixes};
use crate::{Element, Error};
use rxml::RawEvent;
use std::collections::BTreeMap;
/// Tree-building parser state
pub struct TreeBuilder {
next_tag: Option<(Prefix, String, Prefixes, BTreeMap<String, String>)>,
/// Parsing stack
stack: Vec<Element>,
/// Namespace set stack by prefix
prefixes_stack: Vec<Prefixes>,
/// Document root element if finished
pub root: Option<Element>,
}
impl TreeBuilder {
/// Create a new one
pub fn new() -> Self {
TreeBuilder {
next_tag: None,
stack: vec![],
prefixes_stack: vec![],
root: None,
}
}
/// Stack depth
pub fn depth(&self) -> usize {
self.stack.len()
}
/// Get the top-most element from the stack but don't remove it
pub fn top(&mut self) -> Option<&Element> {
self.stack.last()
}
/// Pop the top-most element from the stack
fn pop(&mut self) -> Option<Element> {
self.prefixes_stack.pop();
self.stack.pop()
}
/// Unshift the first child of the top element
pub fn unshift_child(&mut self) -> Option<Element> {
let depth = self.stack.len();
if depth > 0 {
self.stack[depth - 1].unshift_child()
} else {
None
}
}
/// Lookup XML namespace declaration for given prefix (or no prefix)
fn lookup_prefix(&self, prefix: &Option<String>) -> Option<&str> {
for nss in self.prefixes_stack.iter().rev() {
if let Some(ns) = nss.get(prefix) {
return Some(ns);
}
}
None
}
fn process_end_tag(&mut self) -> Result<(), Error> {
if let Some(el) = self.pop() {
if self.depth() > 0 {
let top = self.stack.len() - 1;
self.stack[top].append_child(el);
} else {
self.root = Some(el);
}
}
Ok(())
}
fn process_text(&mut self, text: String) {
if self.depth() > 0 {
let top = self.stack.len() - 1;
self.stack[top].append_text_node(text);
}
}
/// Process a Event that you got out of a RawParser
pub fn process_event(&mut self, event: RawEvent) -> Result<(), Error> {
match event {
RawEvent::XMLDeclaration(_, _) => {}
RawEvent::ElementHeadOpen(_, (prefix, name)) => {
self.next_tag = Some((
prefix.map(|prefix| prefix.as_str().to_owned()),
name.as_str().to_owned(),
Prefixes::default(),
BTreeMap::new(),
))
}
RawEvent::Attribute(_, (prefix, name), value) => {
self.next_tag
.as_mut()
.map(
|(_, _, ref mut prefixes, ref mut attrs)| match (prefix, name) {
(None, xmlns) if xmlns == "xmlns" => {
prefixes.insert(None, value);
}
(Some(xmlns), prefix) if xmlns.as_str() == "xmlns" => {
prefixes.insert(Some(prefix.as_str().to_owned()), value);
}
(Some(prefix), name) => {
attrs.insert(
format!("{}:{}", prefix, name),
value.as_str().to_owned(),
);
}
(None, name) => {
attrs.insert(name.as_str().to_owned(), value.as_str().to_owned());
}
},
);
}
RawEvent::ElementHeadClose(_) => {
if let Some((prefix, name, prefixes, attrs)) = self.next_tag.take() {
self.prefixes_stack.push(prefixes.clone());
let namespace = self
.lookup_prefix(&prefix.clone().map(|prefix| prefix.as_str().to_owned()))
.ok_or(Error::MissingNamespace)?
.to_owned();
let el = Element::new(
name.as_str().to_owned(),
namespace,
Some(prefix.map(|prefix| prefix.as_str().to_owned())),
prefixes,
attrs,
vec![],
);
self.stack.push(el);
}
}
RawEvent::ElementFoot(_) => self.process_end_tag()?,
RawEvent::Text(_, text) => self.process_text(text.as_str().to_owned()),
}
Ok(())
}
}