From e2eb3d0bdef0a7c7fa98b883f4a357da90936a98 Mon Sep 17 00:00:00 2001 From: David Wendt Date: Fri, 20 Dec 2019 21:18:31 -0500 Subject: [PATCH] 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. --- core/src/xml.rs | 8 +++ core/src/xml/document.rs | 42 ++++++++++++++ core/src/xml/tests.rs | 36 ++++++++++++ core/src/xml/tree.rs | 116 ++++++++++++++++++++++++++++++++++++++- 4 files changed, 201 insertions(+), 1 deletion(-) create mode 100644 core/src/xml/tests.rs diff --git a/core/src/xml.rs b/core/src/xml.rs index 5c4981a64..dd510fd72 100644 --- a/core/src/xml.rs +++ b/core/src/xml.rs @@ -3,7 +3,15 @@ mod document; mod tree; +#[cfg(test)] +mod tests; + type Error = Box; 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; diff --git a/core/src/xml/document.rs b/core/src/xml/document.rs index 3ea7298cf..d79ad4b36 100644 --- a/core/src/xml/document.rs +++ b/core/src/xml/document.rs @@ -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> { + 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 { + 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() + } } diff --git a/core/src/xml/tests.rs b/core/src/xml/tests.rs new file mode 100644 index 000000000..ad2ccbcc1 --- /dev/null +++ b/core/src/xml/tests.rs @@ -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, "").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()); + }) +} diff --git a/core/src/xml/tree.rs b/core/src/xml/tree.rs index 37b553c8f..43d734a14 100644 --- a/core/src/xml/tree.rs +++ b/core/src/xml/tree.rs @@ -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 { + 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 { + 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>> { + 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 { + 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(), + } + } }