use std::collections::{BTreeMap, BTreeSet};
use crate::NodeId;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub enum NodeData<Ext = ()> {
Element {
tag: String,
attrs: BTreeMap<String, String>,
classes: BTreeSet<String>,
ext: Ext,
},
Text {
data: String,
},
Comment {
data: String,
},
Fragment,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
#[non_exhaustive]
pub enum NodeType {
Element = 1,
Text = 3,
Comment = 8,
Fragment = 11,
}
impl NodeType {
pub fn canonical_name(self) -> &'static str {
match self {
NodeType::Element => "", NodeType::Text => "#text",
NodeType::Comment => "#comment",
NodeType::Fragment => "#document-fragment",
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Node<Ext = ()> {
pub(crate) parent: Option<NodeId>,
pub(crate) first_child: Option<NodeId>,
pub(crate) last_child: Option<NodeId>,
pub(crate) prev_sibling: Option<NodeId>,
pub(crate) next_sibling: Option<NodeId>,
pub(crate) data: NodeData<Ext>,
}
impl<Ext> Node<Ext> {
pub(crate) fn new(data: NodeData<Ext>) -> Self {
Self {
parent: None,
first_child: None,
last_child: None,
prev_sibling: None,
next_sibling: None,
data,
}
}
pub(crate) fn tag_name(&self) -> Option<&str> {
match &self.data {
NodeData::Element { tag, .. } => Some(tag),
_ => None,
}
}
pub(crate) fn node_type(&self) -> NodeType {
match &self.data {
NodeData::Element { .. } => NodeType::Element,
NodeData::Text { .. } => NodeType::Text,
NodeData::Comment { .. } => NodeType::Comment,
NodeData::Fragment => NodeType::Fragment,
}
}
#[allow(dead_code)] pub(crate) fn unlink(&mut self) {
self.parent = None;
self.first_child = None;
self.last_child = None;
self.prev_sibling = None;
self.next_sibling = None;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn spec_numeric_values_match_mdn() {
assert_eq!(NodeType::Element as u8, 1);
assert_eq!(NodeType::Text as u8, 3);
assert_eq!(NodeType::Comment as u8, 8);
assert_eq!(NodeType::Fragment as u8, 11);
}
#[test]
fn canonical_names_match_browser() {
assert_eq!(NodeType::Text.canonical_name(), "#text");
assert_eq!(NodeType::Comment.canonical_name(), "#comment");
assert_eq!(NodeType::Fragment.canonical_name(), "#document-fragment");
}
#[test]
fn new_element_node_starts_unlinked() {
let n: Node<()> = Node::new(NodeData::Element {
tag: "div".into(),
attrs: BTreeMap::new(),
classes: BTreeSet::new(),
ext: (),
});
assert!(n.parent.is_none());
assert!(n.first_child.is_none());
assert!(n.last_child.is_none());
assert!(n.prev_sibling.is_none());
assert!(n.next_sibling.is_none());
assert_eq!(n.node_type(), NodeType::Element);
assert_eq!(n.tag_name(), Some("div"));
}
#[test]
fn non_element_tag_name_is_none() {
let n: Node<()> = Node::new(NodeData::Text { data: "hi".into() });
assert_eq!(n.tag_name(), None);
assert_eq!(n.node_type(), NodeType::Text);
}
}