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:
parent
6ae5ae3038
commit
e2eb3d0bde
|
@ -3,7 +3,15 @@
|
||||||
mod document;
|
mod document;
|
||||||
mod tree;
|
mod tree;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests;
|
||||||
|
|
||||||
type Error = Box<dyn std::error::Error>;
|
type Error = Box<dyn std::error::Error>;
|
||||||
|
|
||||||
pub use document::XMLDocument;
|
pub use document::XMLDocument;
|
||||||
|
pub use tree::XMLName;
|
||||||
pub use tree::XMLNode;
|
pub use tree::XMLNode;
|
||||||
|
|
||||||
|
pub const ELEMENT_NODE: u8 = 1;
|
||||||
|
pub const TEXT_NODE: u8 = 3;
|
||||||
|
pub const COMMENT_NODE: u8 = 8;
|
||||||
|
|
|
@ -5,6 +5,7 @@ use crate::xml::XMLNode;
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
use quick_xml::events::Event;
|
use quick_xml::events::Event;
|
||||||
use quick_xml::Reader;
|
use quick_xml::Reader;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// The entirety of an XML document.
|
/// The entirety of an XML document.
|
||||||
#[derive(Copy, Clone, Collect)]
|
#[derive(Copy, Clone, Collect)]
|
||||||
|
@ -75,4 +76,45 @@ impl<'gc> XMLDocument<'gc> {
|
||||||
|
|
||||||
Ok(Self(GcCell::allocate(mc, XMLDocumentData { roots })))
|
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()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
})
|
||||||
|
}
|
|
@ -1,10 +1,12 @@
|
||||||
//! XML Tree structure
|
//! XML Tree structure
|
||||||
|
|
||||||
|
use crate::xml;
|
||||||
use crate::xml::Error;
|
use crate::xml::Error;
|
||||||
use gc_arena::{Collect, GcCell, MutationContext};
|
use gc_arena::{Collect, GcCell, MutationContext};
|
||||||
use quick_xml::events::{BytesStart, BytesText};
|
use quick_xml::events::{BytesStart, BytesText};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// Represents a scoped name within XML.
|
/// 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.
|
/// Represents a node in the XML tree.
|
||||||
#[derive(Copy, Clone, Collect)]
|
#[derive(Copy, Clone, Collect)]
|
||||||
#[collect(no_drop)]
|
#[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
|
/// The returned node will always be an `Element`, and it must only contain
|
||||||
/// valid encoded UTF-8 data. (Other encoding support is planned later.)
|
/// 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>(
|
pub fn text_from_text_event<'a>(
|
||||||
mc: MutationContext<'gc, '_>,
|
mc: MutationContext<'gc, '_>,
|
||||||
bt: BytesText<'a>,
|
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>(
|
pub fn comment_from_text_event<'a>(
|
||||||
mc: MutationContext<'gc, '_>,
|
mc: MutationContext<'gc, '_>,
|
||||||
bt: BytesText<'a>,
|
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(
|
pub fn append_child(
|
||||||
&mut self,
|
&mut self,
|
||||||
mc: MutationContext<'gc, '_>,
|
mc: MutationContext<'gc, '_>,
|
||||||
|
@ -198,4 +221,95 @@ impl<'gc> XMLNode<'gc> {
|
||||||
|
|
||||||
Ok(())
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue