dazzle_core/
grove.rs

1//! Grove trait definitions
2//!
3//! This module defines the abstract interface for document trees (groves),
4//! following OpenJade's grove/ architecture.
5//!
6//! The grove model is defined by the DSSSL standard (ISO/IEC 10179:1996) as an
7//! abstract representation of document structure, independent of markup syntax.
8//!
9//! ## Architecture
10//!
11//! Like OpenJade's separation between `grove/` (abstract interface) and `spgrove/`
12//! (OpenSP implementation), Dazzle defines:
13//!
14//! - **This module (`dazzle-core/grove`)**: Abstract traits (`Node`, `NodeList`, `Grove`)
15//! - **`dazzle-grove-libxml2`**: Concrete implementation using libxml2 (XML + DTD)
16//! - **`dazzle-grove-opensp` (future)**: Concrete implementation using OpenSP (full SGML)
17//!
18//! This allows the Scheme interpreter to work with any grove implementation
19//! without coupling to a specific parser.
20//!
21//! ## Key Traits
22//!
23//! - `Node`: A single node in the document tree (element, text, attribute, etc.)
24//! - `NodeList`: An ordered collection of nodes
25//! - `Grove`: The complete document grove (root + global operations)
26
27use std::fmt::Debug;
28
29/// A node in the document tree
30///
31/// Corresponds to OpenJade's `GroveImpl::NodeImpl` interface.
32///
33/// ## DSSSL Node Properties
34///
35/// DSSSL defines numerous node properties. The core ones are:
36///
37/// - **gi**: Generic identifier (element name)
38/// - **id**: ID attribute value
39/// - **data**: Text content (for text nodes)
40/// - **attributes**: Attribute nodes
41/// - **children**: Child nodes (elements only, not text)
42/// - **parent**: Parent node
43///
44/// See DSSSL spec Section 8 for complete property list.
45pub trait Node: Debug {
46    /// Get the generic identifier (element name)
47    ///
48    /// Returns `None` for non-element nodes.
49    ///
50    /// DSSSL: `gi` property
51    ///
52    /// **Design Note**: Returns owned `String` rather than `&str` because
53    /// XML parsers (libxml2) return owned strings. This avoids lifetime complexity.
54    fn gi(&self) -> Option<String>;
55
56    /// Get the ID attribute value
57    ///
58    /// Returns `None` if node has no ID attribute.
59    ///
60    /// DSSSL: `id` property
61    fn id(&self) -> Option<String>;
62
63    /// Get text content
64    ///
65    /// For text nodes, returns the text. For elements, returns
66    /// concatenated descendant text.
67    ///
68    /// DSSSL: `data` property
69    fn data(&self) -> Option<String>;
70
71    /// Get child nodes
72    ///
73    /// **Important**: In DSSSL, `children` returns only element nodes,
74    /// not text nodes. Text is accessed via `data` property.
75    ///
76    /// DSSSL: `children` property
77    fn children(&self) -> Box<dyn NodeList>;
78
79    /// Get parent node
80    ///
81    /// Returns `None` for the root node.
82    ///
83    /// DSSSL: `parent` property
84    fn parent(&self) -> Option<Box<dyn Node>>;
85
86    /// Get attribute value
87    ///
88    /// Includes DTD default values if defined.
89    ///
90    /// DSSSL: `attribute-string` primitive
91    fn attribute_string(&self, name: &str) -> Option<String>;
92
93    /// Check if this is an element node
94    fn is_element(&self) -> bool;
95
96    /// Check if this is a text node
97    fn is_text(&self) -> bool;
98
99    /// Check node equality (same node in document tree)
100    ///
101    /// Two node references are equal if they refer to the same
102    /// node in the document, not just structurally similar nodes.
103    fn node_eq(&self, other: &dyn Node) -> bool;
104}
105
106/// An ordered collection of nodes
107///
108/// Corresponds to OpenJade's `GroveImpl::NodeListImpl` interface.
109///
110/// ## Lazy Evaluation
111///
112/// Like OpenJade, node lists should support lazy evaluation - they don't
113/// need to materialize all nodes immediately. Operations like `first()`
114/// and `rest()` can be implemented efficiently as iterators.
115///
116/// DSSSL node lists are immutable and functional (cons-list style).
117pub trait NodeList: Debug {
118    /// Check if the node list is empty
119    ///
120    /// DSSSL: `node-list-empty?`
121    fn is_empty(&self) -> bool;
122
123    /// Get the first node
124    ///
125    /// Returns `None` if the list is empty.
126    ///
127    /// DSSSL: `node-list-first`
128    fn first(&self) -> Option<Box<dyn Node>>;
129
130    /// Get the rest of the node list (all but first)
131    ///
132    /// Returns an empty node list if this list has 0 or 1 elements.
133    ///
134    /// DSSSL: `node-list-rest`
135    fn rest(&self) -> Box<dyn NodeList>;
136
137    /// Get the length of the node list
138    ///
139    /// DSSSL: `node-list-length`
140    fn length(&self) -> usize;
141
142    /// Get node at index
143    ///
144    /// Returns `None` if index is out of bounds.
145    ///
146    /// DSSSL: `node-list-ref`
147    fn get(&self, index: usize) -> Option<Box<dyn Node>>;
148}
149
150/// The complete document grove
151///
152/// Corresponds to OpenJade's `GroveImpl::Grove` interface.
153///
154/// A grove holds the entire document tree and provides global operations
155/// like `element-with-id`.
156pub trait Grove: Debug {
157    /// Get the root node
158    ///
159    /// DSSSL: `grove-root` (or implicit root access)
160    fn root(&self) -> Box<dyn Node>;
161
162    /// Find element by ID
163    ///
164    /// Returns `None` if no element with the given ID exists.
165    ///
166    /// DSSSL: `element-with-id`
167    fn element_with_id(&self, id: &str) -> Option<Box<dyn Node>>;
168}
169
170/// An empty node list implementation
171///
172/// This is a concrete implementation of NodeList that represents
173/// an empty list. It's used by primitives like `empty-node-list`.
174#[derive(Debug, Clone)]
175pub struct EmptyNodeList;
176
177impl EmptyNodeList {
178    pub fn new() -> Self {
179        EmptyNodeList
180    }
181}
182
183impl Default for EmptyNodeList {
184    fn default() -> Self {
185        Self::new()
186    }
187}
188
189impl NodeList for EmptyNodeList {
190    fn is_empty(&self) -> bool {
191        true
192    }
193
194    fn first(&self) -> Option<Box<dyn Node>> {
195        None
196    }
197
198    fn rest(&self) -> Box<dyn NodeList> {
199        Box::new(EmptyNodeList::new())
200    }
201
202    fn length(&self) -> usize {
203        0
204    }
205
206    fn get(&self, _index: usize) -> Option<Box<dyn Node>> {
207        None
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214
215    #[test]
216    fn test_traits_defined() {
217        // This test just ensures the traits compile
218        // Actual tests will be in dazzle-grove-libxml2
219    }
220
221    #[test]
222    fn test_empty_node_list() {
223        let empty = EmptyNodeList::new();
224        assert!(empty.is_empty());
225        assert_eq!(empty.length(), 0);
226        assert!(empty.first().is_none());
227        assert!(empty.get(0).is_none());
228        assert!(empty.rest().is_empty());
229    }
230}