Add a test of XMLDocument::from_str.

This test technically works, but the results were slightly surprising. If it turns out Flash works differently from `quick_xml`, we'll change this test to match Flash.
This commit is contained in:
David Wendt 2019-12-20 21:18:31 -05:00
parent 6ae5ae3038
commit e2eb3d0bde
4 changed files with 201 additions and 1 deletions

View File

@ -3,7 +3,15 @@
mod document;
mod tree;
#[cfg(test)]
mod tests;
type Error = Box<dyn std::error::Error>;
pub use document::XMLDocument;
pub use tree::XMLName;
pub use tree::XMLNode;
pub const ELEMENT_NODE: u8 = 1;
pub const TEXT_NODE: u8 = 3;
pub const COMMENT_NODE: u8 = 8;

View File

@ -5,6 +5,7 @@ use crate::xml::XMLNode;
use gc_arena::{Collect, GcCell, MutationContext};
use quick_xml::events::Event;
use quick_xml::Reader;
use std::fmt;
/// The entirety of an XML document.
#[derive(Copy, Clone, Collect)]
@ -75,4 +76,45 @@ impl<'gc> XMLDocument<'gc> {
Ok(Self(GcCell::allocate(mc, XMLDocumentData { roots })))
}
/// Returns an iterator that yields the document's root nodes.
pub fn roots(&self) -> impl Iterator<Item = XMLNode<'gc>> {
struct RootsIter<'gc> {
base: XMLDocument<'gc>,
index: usize,
};
impl<'gc> RootsIter<'gc> {
fn for_document(base: XMLDocument<'gc>) -> Self {
Self { base, index: 0 }
}
}
impl<'gc> Iterator for RootsIter<'gc> {
type Item = XMLNode<'gc>;
fn next(&mut self) -> Option<Self::Item> {
let (len, item) = {
let r = self.base.0.read();
(r.roots.len(), r.roots.get(self.index).cloned())
};
if self.index < len {
self.index += 1;
}
item
}
}
RootsIter::for_document(*self)
}
}
impl<'gc> fmt::Debug for XMLDocument<'gc> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("XMLDocument")
.field("roots", &self.0.read().roots)
.finish()
}
}

36
core/src/xml/tests.rs Normal file
View File

@ -0,0 +1,36 @@
//! XML tests
use crate::xml;
use crate::xml::{XMLDocument, XMLName};
use gc_arena::rootless_arena;
#[test]
fn parse_single_element() {
rootless_arena(|mc| {
let xml = XMLDocument::from_str(mc, "<test></test>").expect("Parsed document");
dbg!(xml);
let mut roots = xml.roots();
let root = roots.next().expect("Parsed document should have a root");
assert_eq!(root.node_type(), xml::TEXT_NODE);
assert_eq!(root.node_value(), Some("".to_string()));
let root = roots
.next()
.expect("Parsed document should have a second root");
assert_eq!(root.node_type(), xml::ELEMENT_NODE);
assert_eq!(root.tag_name(), Some(XMLName::from_str("test").unwrap()));
let mut root_children = root.children().unwrap();
let child_text = root_children
.next()
.expect("Single nodes should have an empty text child");
assert_eq!(child_text.node_type(), xml::TEXT_NODE);
assert_eq!(child_text.node_value(), Some("".to_string()));
assert!(root_children.next().is_none());
assert!(roots.next().is_none());
})
}

View File

@ -1,10 +1,12 @@
//! XML Tree structure
use crate::xml;
use crate::xml::Error;
use gc_arena::{Collect, GcCell, MutationContext};
use quick_xml::events::{BytesStart, BytesText};
use std::borrow::Cow;
use std::collections::BTreeMap;
use std::fmt;
/// Represents a scoped name within XML.
///
@ -59,6 +61,15 @@ impl XMLName {
}
}
impl fmt::Debug for XMLName {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("XMLName")
.field("namespace", &self.namespace)
.field("name", &self.name)
.finish()
}
}
/// Represents a node in the XML tree.
#[derive(Copy, Clone, Collect)]
#[collect(no_drop)]
@ -119,7 +130,7 @@ impl<'gc> XMLNode<'gc> {
)))
}
/// Construct an XML node from a `quick_xml` `BytesStart` event.
/// Construct an XML Element node from a `quick_xml` `BytesStart` event.
///
/// The returned node will always be an `Element`, and it must only contain
/// valid encoded UTF-8 data. (Other encoding support is planned later.)
@ -150,6 +161,10 @@ impl<'gc> XMLNode<'gc> {
)))
}
/// Construct an XML Text node from a `quick_xml` `BytesText` event.
///
/// The returned node will always be `Text`, and it must only contain
/// valid encoded UTF-8 data. (Other encoding support is planned later.)
pub fn text_from_text_event<'a>(
mc: MutationContext<'gc, '_>,
bt: BytesText<'a>,
@ -167,6 +182,10 @@ impl<'gc> XMLNode<'gc> {
)))
}
/// Construct an XML Comment node from a `quick_xml` `BytesText` event.
///
/// The returned node will always be `Comment`, and it must only contain
/// valid encoded UTF-8 data. (Other encoding support is planned later.)
pub fn comment_from_text_event<'a>(
mc: MutationContext<'gc, '_>,
bt: BytesText<'a>,
@ -184,6 +203,10 @@ impl<'gc> XMLNode<'gc> {
)))
}
/// Append a child element to an Element node.
///
/// This function yields an error if appending to a Node that cannot accept
/// children. In that case, no modification will be made to the node.
pub fn append_child(
&mut self,
mc: MutationContext<'gc, '_>,
@ -198,4 +221,95 @@ impl<'gc> XMLNode<'gc> {
Ok(())
}
/// Returns the type of this node as an integer.
///
/// This is primarily intended to match W3C DOM L1 specifications and
/// should not be used in lieu of a proper `match` statement.
pub fn node_type(&self) -> u8 {
match &*self.0.read() {
XMLNodeData::Element { .. } => xml::ELEMENT_NODE,
XMLNodeData::Text { .. } => xml::TEXT_NODE,
XMLNodeData::Comment { .. } => xml::COMMENT_NODE,
}
}
/// Returns the tagname, if the element has one.
pub fn tag_name(&self) -> Option<XMLName> {
match &*self.0.read() {
XMLNodeData::Element { ref tag_name, .. } => Some(tag_name.clone()),
_ => None,
}
}
/// Returns the string contents of the node, if the element has them.
pub fn node_value(&self) -> Option<String> {
match &*self.0.read() {
XMLNodeData::Text { ref contents } => Some(contents.clone()),
XMLNodeData::Comment { ref contents } => Some(contents.clone()),
_ => None,
}
}
/// Returns an iterator that yields child nodes.
///
/// Yields None if this node cannot accept children.
pub fn children(&self) -> Option<impl Iterator<Item = XMLNode<'gc>>> {
struct ChildIter<'gc> {
base: XMLNode<'gc>,
index: usize,
};
impl<'gc> ChildIter<'gc> {
fn for_node(base: XMLNode<'gc>) -> Self {
Self { base, index: 0 }
}
}
impl<'gc> Iterator for ChildIter<'gc> {
type Item = XMLNode<'gc>;
fn next(&mut self) -> Option<Self::Item> {
match &*self.base.0.read() {
XMLNodeData::Element { ref children, .. } if self.index < children.len() => {
let item = children.get(self.index).cloned();
self.index += 1;
item
}
_ => None,
}
}
}
match &*self.0.read() {
XMLNodeData::Element { .. } => Some(ChildIter::for_node(*self)),
_ => return None,
}
}
}
impl<'gc> fmt::Debug for XMLNode<'gc> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match &*self.0.read() {
XMLNodeData::Text { ref contents } => f
.debug_struct("XMLNodeData::Text")
.field("contents", contents)
.finish(),
XMLNodeData::Comment { ref contents } => f
.debug_struct("XMLNodeData::Comment")
.field("contents", contents)
.finish(),
XMLNodeData::Element {
ref tag_name,
ref attributes,
ref children,
} => f
.debug_struct("XMLNodeData::Element")
.field("tag_name", tag_name)
.field("attributes", attributes)
.field("children", children)
.finish(),
}
}
}