use std::collections::BTreeMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(u64);
impl NodeId {
#[must_use]
pub(crate) fn new(id: u64) -> Self {
Self(id)
}
#[must_use]
pub fn raw(&self) -> u64 {
self.0
}
}
impl std::fmt::Display for NodeId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "node#{}", self.0)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElementData {
name: String,
attributes: Vec<(String, String)>,
children: Vec<NodeId>,
parent: Option<NodeId>,
}
impl ElementData {
#[must_use]
pub fn new(
name: impl Into<String>,
attributes: Vec<(String, String)>,
children: Vec<NodeId>,
parent: Option<NodeId>,
) -> Self {
Self {
name: name.into(),
attributes,
children,
parent,
}
}
#[must_use]
pub fn name(&self) -> &str {
&self.name
}
#[must_use]
pub fn attributes(&self) -> &[(String, String)] {
&self.attributes
}
#[must_use]
pub fn attribute(&self, name: &str) -> Option<&str> {
self.attributes
.iter()
.find(|(k, _)| k.eq_ignore_ascii_case(name))
.map(|(_, v)| v.as_str())
}
#[must_use]
pub fn children(&self) -> &[NodeId] {
&self.children
}
#[must_use]
pub fn parent(&self) -> Option<NodeId> {
self.parent
}
#[must_use]
pub fn has_class(&self, name: &str) -> bool {
self.attribute("class")
.is_some_and(|cls| cls.split_ascii_whitespace().any(|c| c == name))
}
#[must_use]
pub fn id(&self) -> Option<&str> {
self.attribute("id")
}
pub(crate) fn with_attributes(self, attributes: Vec<(String, String)>) -> Self {
Self { attributes, ..self }
}
pub(crate) fn with_children(self, children: Vec<NodeId>) -> Self {
Self { children, ..self }
}
pub(crate) fn with_parent(self, parent: Option<NodeId>) -> Self {
Self { parent, ..self }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextData {
content: String,
parent: Option<NodeId>,
}
impl TextData {
#[must_use]
pub fn new(content: impl Into<String>, parent: Option<NodeId>) -> Self {
Self {
content: content.into(),
parent,
}
}
#[must_use]
pub fn content(&self) -> &str {
&self.content
}
#[must_use]
pub fn parent(&self) -> Option<NodeId> {
self.parent
}
pub(crate) fn with_content(self, content: String) -> Self {
Self { content, ..self }
}
pub(crate) fn with_parent(self, parent: Option<NodeId>) -> Self {
Self { parent, ..self }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommentData {
text: String,
parent: Option<NodeId>,
}
impl CommentData {
#[must_use]
pub fn new(text: impl Into<String>, parent: Option<NodeId>) -> Self {
Self {
text: text.into(),
parent,
}
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
#[must_use]
pub fn parent(&self) -> Option<NodeId> {
self.parent
}
pub(crate) fn with_parent(self, parent: Option<NodeId>) -> Self {
Self { parent, ..self }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DocumentData {
children: Vec<NodeId>,
}
impl DocumentData {
#[must_use]
pub fn new(children: Vec<NodeId>) -> Self {
Self { children }
}
#[must_use]
pub fn children(&self) -> &[NodeId] {
&self.children
}
pub(crate) fn with_children(children: Vec<NodeId>) -> Self {
Self { children }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
Document(DocumentData),
Element(ElementData),
Text(TextData),
Comment(CommentData),
}
impl Node {
#[must_use]
pub fn parent(&self) -> Option<NodeId> {
match self {
Self::Document(_) => None,
Self::Element(e) => e.parent(),
Self::Text(t) => t.parent(),
Self::Comment(c) => c.parent(),
}
}
#[must_use]
pub fn children(&self) -> &[NodeId] {
match self {
Self::Document(d) => d.children(),
Self::Element(e) => e.children(),
Self::Text(_) | Self::Comment(_) => &[],
}
}
#[must_use]
pub fn as_element(&self) -> Option<&ElementData> {
match self {
Self::Element(e) => Some(e),
_other => None,
}
}
pub(crate) fn with_parent(self, parent: Option<NodeId>) -> Self {
match self {
Self::Document(d) => Self::Document(d),
Self::Element(e) => Self::Element(e.with_parent(parent)),
Self::Text(t) => Self::Text(t.with_parent(parent)),
Self::Comment(c) => Self::Comment(c.with_parent(parent)),
}
}
pub(crate) fn with_children(self, children: Vec<NodeId>) -> Self {
match self {
Self::Document(_) => Self::Document(DocumentData::with_children(children)),
Self::Element(e) => Self::Element(e.with_children(children)),
Self::Text(t) => Self::Text(t),
Self::Comment(c) => Self::Comment(c),
}
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Arena {
nodes: BTreeMap<NodeId, Node>,
next_id: u64,
}
impl Arena {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn alloc(self, node: Node) -> (NodeId, Self) {
let id = NodeId::new(self.next_id);
let mut next_nodes = self.nodes;
let _ = next_nodes.insert(id, node);
let next = Self {
nodes: next_nodes,
next_id: self.next_id + 1,
};
(id, next)
}
pub fn store(self, id: NodeId, node: Node) -> Result<Self, Self> {
if self.nodes.contains_key(&id) {
let mut next_nodes = self.nodes;
let _ = next_nodes.insert(id, node);
Ok(Self {
nodes: next_nodes,
next_id: self.next_id,
})
} else {
Err(self)
}
}
pub fn remove(self, id: NodeId) -> Result<Self, Self> {
if self.nodes.contains_key(&id) {
let mut next_nodes = self.nodes;
let _ = next_nodes.remove(&id);
Ok(Self {
nodes: next_nodes,
next_id: self.next_id,
})
} else {
Err(self)
}
}
#[must_use]
pub fn get(&self, id: NodeId) -> Option<&Node> {
self.nodes.get(&id)
}
#[must_use]
pub fn len(&self) -> usize {
self.nodes.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.nodes.is_empty()
}
pub fn ids(&self) -> impl Iterator<Item = NodeId> + '_ {
self.nodes.keys().copied()
}
}