diff --git a/src/node/blob.rs b/src/node/blob.rs index 69766db..29c337e 100644 --- a/src/node/blob.rs +++ b/src/node/blob.rs @@ -30,7 +30,11 @@ impl fmt::Display for Blob { } } -impl Node for Blob {} +impl Node for Blob { + fn get_name(&self) -> &'static str { + "blob" + } +} impl super::NodeDefaultHash for Blob { #[inline] diff --git a/src/node/comment.rs b/src/node/comment.rs index 8761de3..d52ca53 100644 --- a/src/node/comment.rs +++ b/src/node/comment.rs @@ -45,6 +45,10 @@ impl Node for Comment { U: Into, { } + + fn get_name(&self) -> &str { + "comment" + } } impl super::NodeDefaultHash for Comment { diff --git a/src/node/element/mod.rs b/src/node/element/mod.rs index 7f3e4a6..ae4f222 100644 --- a/src/node/element/mod.rs +++ b/src/node/element/mod.rs @@ -108,6 +108,26 @@ impl Node for Element { { self.attributes.insert(name.into(), value.into()); } + + fn get_attribute(&self, k: &str) -> Option<&Value> { + self.attributes.get(k) + } + + fn set_attribute(&mut self, name: String, value: Value) { + self.attributes.insert(name, value); + } + + fn get_name(&self) -> &str { + &self.name + } + + fn iter_children(&self) -> super::ChildrenIter { + self.children.iter() + } + + fn iter_children_mut(&mut self) -> super::ChildrenIterMut { + self.children.iter_mut() + } } macro_rules! implement { @@ -410,6 +430,8 @@ fn escape(value: &str) -> String { #[cfg(test)] mod tests { + use std::ops::Deref; + use super::{Element, Rectangle, Style, Title}; use crate::node::{element, Node}; @@ -494,4 +516,29 @@ mod tests { &[""], ); } + + #[test] + fn node_traversal() { + let group = element::Group::new().add( + element::Text::new("foo").set("font-family", "Times New Roman") + ).add( + element::Text::new("bar").set("font-family", "Arial, Helvetica, sans-serif") + ).add( + element::Text::new("baz").set("font-family", "Comic Sans") + ); + + for (i, child) in group.iter_children().enumerate() { + assert_eq!(child.get_name(), "text"); + if i == 0 { + assert_eq!(child.get_attribute("font-family").unwrap().deref(), "Times New Roman"); + assert_eq!(child.iter_children().next().unwrap().to_string(), "foo"); + } else if i == 1 { + assert_eq!(child.get_attribute("font-family").unwrap().deref(), "Arial, Helvetica, sans-serif"); + assert_eq!(child.iter_children().next().unwrap().to_string(), "bar"); + } else if i == 2 { + assert_eq!(child.get_attribute("font-family").unwrap().deref(), "Comic Sans"); + assert_eq!(child.iter_children().next().unwrap().to_string(), "baz"); + } + } + } } diff --git a/src/node/mod.rs b/src/node/mod.rs index 6f127c2..2c3f4f2 100644 --- a/src/node/mod.rs +++ b/src/node/mod.rs @@ -20,6 +20,12 @@ pub type Attributes = HashMap; /// Child nodes. pub type Children = Vec>; +pub type ChildrenIter<'a> = std::slice::Iter<'a, Box>; +pub type ChildrenIterMut<'a> = std::slice::IterMut<'a, Box>; + +const FAKE_CHILDREN: &[Box] = &[]; +static mut FAKE_CHILDREN_MUT: &mut [Box] = &mut []; + /// A node. pub trait Node: 'static + fmt::Debug + fmt::Display + NodeClone + NodeDefaultHash + Send + Sync @@ -43,6 +49,37 @@ pub trait Node: { } + #[allow(unused_variables)] + /// Get a reference to the [`Value`] associated with attribute name `k`, + /// if it is present. + fn get_attribute(&self, k: &str) -> Option<&Value> + { + None + } + + #[allow(unused)] + /// Assign an attribute with a value explicitly. + /// + /// Ergonomically, [`Node::assign`] is easier to use + /// but is not dispatch-safe, while this method is. + fn set_attribute(&mut self, name: String, value: Value) {} + + /// Get the name of the node type. + fn get_name(&self) -> &'static str; + + /// Iterate over references to the children of this [`Node`]. + fn iter_children(&self) -> ChildrenIter { + FAKE_CHILDREN.iter() + } + + /// Iterate over mutable references to the children of this [`Node`] + fn iter_children_mut(&mut self) -> ChildrenIterMut { + // The default implementation uses a global shared mutable slice + // which requires using unsafe. Since this slice is private and is never + // filled with anything, this is reasonable to do. + unsafe { FAKE_CHILDREN_MUT.iter_mut() } + } + #[doc(hidden)] fn is_bare(&self) -> bool { false @@ -102,6 +139,7 @@ macro_rules! node( ($struct_name:ident::$field_name:ident) => ( node!($struct_name::$field_name []); ); + // ($struct_name:ident::$field_name:ident [$($indicator_name:ident),*]) ($struct_name:ident::$field_name:ident [$($indicator_name:ident),*]) => ( impl $struct_name { /// Append a node. @@ -134,6 +172,22 @@ macro_rules! node( self.$field_name.append(node); } + fn get_attribute(&self, k: &str) -> Option<&Value> { + self.$field_name.get_attribute(k) + } + + fn get_name(&self) -> &str { + self.$field_name.get_name() + } + + fn iter_children(&self) -> crate::node::ChildrenIter { + self.$field_name.iter_children() + } + + fn iter_children_mut(&mut self) -> crate::node::ChildrenIterMut { + self.$field_name.iter_children_mut() + } + #[inline] fn assign(&mut self, name: T, value: U) where @@ -143,6 +197,12 @@ macro_rules! node( self.$field_name.assign(name, value); } + #[inline] + fn set_attribute(&mut self, name: String, value: Value) + { + self.$field_name.set_attribute(name, value); + } + $( #[inline] fn $indicator_name(&self) -> bool { diff --git a/src/node/text.rs b/src/node/text.rs index 0a47a41..9da18f1 100644 --- a/src/node/text.rs +++ b/src/node/text.rs @@ -35,6 +35,10 @@ impl Node for Text { fn is_bare(&self) -> bool { true } + + fn get_name(&self) -> &str { + "text-node" + } } impl super::NodeDefaultHash for Text { diff --git a/src/node/value.rs b/src/node/value.rs index 7c794c0..49889e5 100644 --- a/src/node/value.rs +++ b/src/node/value.rs @@ -2,9 +2,15 @@ use std::fmt; use std::ops::Deref; /// A value of an attribute. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, PartialEq)] pub struct Value(String); +impl PartialEq<&str> for Value { + fn eq(&self, other: &&str) -> bool { + self.0 == *other + } +} + impl Deref for Value { type Target = str;