dom-cat 0.1.0

Persistent DOM model: arena-backed Node tree with mutation API and CSS-selector matching. Consumes html-cat trees; selectors via css-cat. No mut, no Rc/Arc, no interior mutability, no panics, exhaustive matches. Third sub-crate of a Servo-replacement webview runtime targeting Tauri.
//! Node identifiers and the `Node` enum.

use std::collections::BTreeMap;

/// An opaque, monotonically allocated node identifier.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct NodeId(u64);

impl NodeId {
    /// Build a `NodeId` from a raw integer.  Internal use; the arena is
    /// the only legitimate source of `NodeId`s.
    #[must_use]
    pub(crate) fn new(id: u64) -> Self {
        Self(id)
    }

    /// The raw underlying 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)
    }
}

/// Element data: tag name, attributes (preserving order), and the
/// children id list.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ElementData {
    name: String,
    attributes: Vec<(String, String)>,
    children: Vec<NodeId>,
    parent: Option<NodeId>,
}

impl ElementData {
    /// Build element data.
    #[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,
        }
    }

    /// The tag name (lower-cased per HTML5).
    #[must_use]
    pub fn name(&self) -> &str {
        &self.name
    }

    /// The attribute pairs in source order.
    #[must_use]
    pub fn attributes(&self) -> &[(String, String)] {
        &self.attributes
    }

    /// Look up an attribute (ASCII-case-insensitive).
    #[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())
    }

    /// The children in source order.
    #[must_use]
    pub fn children(&self) -> &[NodeId] {
        &self.children
    }

    /// The parent id, or `None` for the root.
    #[must_use]
    pub fn parent(&self) -> Option<NodeId> {
        self.parent
    }

    /// Whether this element has the class `name` in its `class` attribute.
    #[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))
    }

    /// The element's `id` attribute, if any.
    #[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 }
    }
}

/// Text node data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct TextData {
    content: String,
    parent: Option<NodeId>,
}

impl TextData {
    /// Build text data.
    #[must_use]
    pub fn new(content: impl Into<String>, parent: Option<NodeId>) -> Self {
        Self {
            content: content.into(),
            parent,
        }
    }

    /// The text content.
    #[must_use]
    pub fn content(&self) -> &str {
        &self.content
    }

    /// The parent id.
    #[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 }
    }
}

/// Comment node data.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CommentData {
    text: String,
    parent: Option<NodeId>,
}

impl CommentData {
    /// Build comment data.
    #[must_use]
    pub fn new(text: impl Into<String>, parent: Option<NodeId>) -> Self {
        Self {
            text: text.into(),
            parent,
        }
    }

    /// The comment text.
    #[must_use]
    pub fn text(&self) -> &str {
        &self.text
    }

    /// The parent id.
    #[must_use]
    pub fn parent(&self) -> Option<NodeId> {
        self.parent
    }

    pub(crate) fn with_parent(self, parent: Option<NodeId>) -> Self {
        Self { parent, ..self }
    }
}

/// Document-root data.  The document is treated as the parent of the
/// root `<html>` element so that ancestor traversal terminates at it.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DocumentData {
    children: Vec<NodeId>,
}

impl DocumentData {
    /// Build document data.
    #[must_use]
    pub fn new(children: Vec<NodeId>) -> Self {
        Self { children }
    }

    /// The top-level children of the document (typically just the html
    /// element).
    #[must_use]
    pub fn children(&self) -> &[NodeId] {
        &self.children
    }

    pub(crate) fn with_children(children: Vec<NodeId>) -> Self {
        Self { children }
    }
}

/// One arena node.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Node {
    /// The document root.
    Document(DocumentData),
    /// An element node.
    Element(ElementData),
    /// A text node.
    Text(TextData),
    /// A comment node.
    Comment(CommentData),
}

impl Node {
    /// The parent id, if any.
    #[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(),
        }
    }

    /// The children ids (empty for text/comment).
    #[must_use]
    pub fn children(&self) -> &[NodeId] {
        match self {
            Self::Document(d) => d.children(),
            Self::Element(e) => e.children(),
            Self::Text(_) | Self::Comment(_) => &[],
        }
    }

    /// The element data if this node is an element.
    #[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),
        }
    }
}

/// The arena: a flat id→node map plus a monotonic allocator.
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Arena {
    nodes: BTreeMap<NodeId, Node>,
    next_id: u64,
}

impl Arena {
    /// An empty arena.
    #[must_use]
    pub fn new() -> Self {
        Self::default()
    }

    /// Allocate `node`, returning its id and the resulting arena.
    #[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)
    }

    /// Replace the node at `id`.  Returns the original arena as `Err`
    /// when the id is not present.
    ///
    /// # Errors
    ///
    /// Returns the original arena as `Err(self)` when `id` is missing.
    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)
        }
    }

    /// Drop the node at `id`.  Returns the original arena as `Err` when
    /// the id is not present.
    ///
    /// # Errors
    ///
    /// Returns the original arena as `Err(self)` when `id` is missing.
    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)
        }
    }

    /// Look up the node at `id`.
    #[must_use]
    pub fn get(&self, id: NodeId) -> Option<&Node> {
        self.nodes.get(&id)
    }

    /// Number of nodes currently in the arena.
    #[must_use]
    pub fn len(&self) -> usize {
        self.nodes.len()
    }

    /// Whether the arena is empty.
    #[must_use]
    pub fn is_empty(&self) -> bool {
        self.nodes.is_empty()
    }

    /// All ids in allocation order.
    pub fn ids(&self) -> impl Iterator<Item = NodeId> + '_ {
        self.nodes.keys().copied()
    }
}