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::any::Any;
28use std::fmt::Debug;
29
30/// A node in the document tree
31///
32/// Corresponds to OpenJade's `GroveImpl::NodeImpl` interface.
33///
34/// ## DSSSL Node Properties
35///
36/// DSSSL defines numerous node properties. The core ones are:
37///
38/// - **gi**: Generic identifier (element name)
39/// - **id**: ID attribute value
40/// - **data**: Text content (for text nodes)
41/// - **attributes**: Attribute nodes
42/// - **children**: Child nodes (elements only, not text)
43/// - **parent**: Parent node
44///
45/// See DSSSL spec Section 8 for complete property list.
46pub trait Node: Debug + Any {
47    /// Clone this node into a new Box
48    ///
49    /// This is required because trait objects (Box<dyn Node>) cannot implement Clone directly.
50    /// Each Node implementation must provide its own cloning logic.
51    fn clone_node(&self) -> Box<dyn Node>;
52
53    /// Get the generic identifier (element name)
54    ///
55    /// Returns `None` for non-element nodes.
56    ///
57    /// DSSSL: `gi` property
58    ///
59    /// **Design Note**: Returns owned `String` rather than `&str` because
60    /// XML parsers (libxml2) return owned strings. This avoids lifetime complexity.
61    fn gi(&self) -> Option<String>;
62
63    /// Get the ID attribute value
64    ///
65    /// Returns `None` if node has no ID attribute.
66    ///
67    /// DSSSL: `id` property
68    fn id(&self) -> Option<String>;
69
70    /// Get text content
71    ///
72    /// For text nodes, returns the text. For elements, returns
73    /// concatenated descendant text.
74    ///
75    /// DSSSL: `data` property
76    fn data(&self) -> Option<String>;
77
78    /// Get child nodes
79    ///
80    /// **Important**: In DSSSL, `children` returns only element nodes,
81    /// not text nodes. Text is accessed via `data` property.
82    ///
83    /// DSSSL: `children` property
84    fn children(&self) -> Box<dyn NodeList>;
85
86    /// Get parent node
87    ///
88    /// Returns `None` for the root node.
89    ///
90    /// DSSSL: `parent` property
91    fn parent(&self) -> Option<Box<dyn Node>>;
92
93    /// Get attribute value
94    ///
95    /// Includes DTD default values if defined.
96    ///
97    /// DSSSL: `attribute-string` primitive
98    fn attribute_string(&self, name: &str) -> Option<String>;
99
100    /// Check if this is an element node
101    fn is_element(&self) -> bool;
102
103    /// Check if this is a text node
104    fn is_text(&self) -> bool;
105
106    /// Check node equality (same node in document tree)
107    ///
108    /// Two node references are equal if they refer to the same
109    /// node in the document, not just structurally similar nodes.
110    fn node_eq(&self, other: &dyn Node) -> bool;
111
112    /// Get a unique identifier for this node
113    ///
114    /// Returns a value that uniquely identifies this node within its document.
115    /// Two nodes with the same `node_id` are the same node.
116    fn node_id(&self) -> usize;
117}
118
119/// An ordered collection of nodes
120///
121/// Corresponds to OpenJade's `GroveImpl::NodeListImpl` interface.
122///
123/// ## Lazy Evaluation
124///
125/// Like OpenJade, node lists should support lazy evaluation - they don't
126/// need to materialize all nodes immediately. Operations like `first()`
127/// and `rest()` can be implemented efficiently as iterators.
128///
129/// DSSSL node lists are immutable and functional (cons-list style).
130pub trait NodeList: Debug {
131    /// Check if the node list is empty
132    ///
133    /// DSSSL: `node-list-empty?`
134    fn is_empty(&self) -> bool;
135
136    /// Get the first node
137    ///
138    /// Returns `None` if the list is empty.
139    ///
140    /// DSSSL: `node-list-first`
141    fn first(&self) -> Option<Box<dyn Node>>;
142
143    /// Get the rest of the node list (all but first)
144    ///
145    /// Returns an empty node list if this list has 0 or 1 elements.
146    ///
147    /// DSSSL: `node-list-rest`
148    fn rest(&self) -> Box<dyn NodeList>;
149
150    /// Get the length of the node list
151    ///
152    /// DSSSL: `node-list-length`
153    fn length(&self) -> usize;
154
155    /// Get node at index
156    ///
157    /// Returns `None` if index is out of bounds.
158    ///
159    /// DSSSL: `node-list-ref`
160    fn get(&self, index: usize) -> Option<Box<dyn Node>>;
161}
162
163/// The complete document grove
164///
165/// Corresponds to OpenJade's `GroveImpl::Grove` interface.
166///
167/// A grove holds the entire document tree and provides global operations
168/// like `element-with-id`.
169pub trait Grove: Debug {
170    /// Get the root node
171    ///
172    /// DSSSL: `grove-root` (or implicit root access)
173    fn root(&self) -> Box<dyn Node>;
174
175    /// Find element by ID
176    ///
177    /// Returns `None` if no element with the given ID exists.
178    ///
179    /// DSSSL: `element-with-id`
180    fn element_with_id(&self, id: &str) -> Option<Box<dyn Node>>;
181}
182
183/// An empty node list implementation
184///
185/// This is a concrete implementation of NodeList that represents
186/// an empty list. It's used by primitives like `empty-node-list`.
187#[derive(Debug, Clone)]
188pub struct EmptyNodeList;
189
190impl EmptyNodeList {
191    pub fn new() -> Self {
192        EmptyNodeList
193    }
194}
195
196impl Default for EmptyNodeList {
197    fn default() -> Self {
198        Self::new()
199    }
200}
201
202impl NodeList for EmptyNodeList {
203    fn is_empty(&self) -> bool {
204        true
205    }
206
207    fn first(&self) -> Option<Box<dyn Node>> {
208        None
209    }
210
211    fn rest(&self) -> Box<dyn NodeList> {
212        Box::new(EmptyNodeList::new())
213    }
214
215    fn length(&self) -> usize {
216        0
217    }
218
219    fn get(&self, _index: usize) -> Option<Box<dyn Node>> {
220        None
221    }
222}
223
224/// A node list backed by a vector with shared ownership
225///
226/// Used for creating filtered node lists (e.g., from select-elements).
227/// Uses Rc to share the vector without cloning nodes.
228#[derive(Debug)]
229pub struct VecNodeList {
230    nodes: std::rc::Rc<Vec<Box<dyn Node>>>,
231    offset: usize,
232}
233
234impl VecNodeList {
235    pub fn new(nodes: Vec<Box<dyn Node>>) -> Self {
236        VecNodeList {
237            nodes: std::rc::Rc::new(nodes),
238            offset: 0,
239        }
240    }
241
242    fn from_rc(nodes: std::rc::Rc<Vec<Box<dyn Node>>>, offset: usize) -> Self {
243        VecNodeList { nodes, offset }
244    }
245}
246
247impl NodeList for VecNodeList {
248    fn is_empty(&self) -> bool {
249        self.offset >= self.nodes.len()
250    }
251
252    fn first(&self) -> Option<Box<dyn Node>> {
253        self.nodes.get(self.offset).map(|n| n.clone_node())
254    }
255
256    fn rest(&self) -> Box<dyn NodeList> {
257        if self.offset + 1 >= self.nodes.len() {
258            Box::new(EmptyNodeList::new())
259        } else {
260            Box::new(VecNodeList::from_rc(self.nodes.clone(), self.offset + 1))
261        }
262    }
263
264    fn length(&self) -> usize {
265        self.nodes.len().saturating_sub(self.offset)
266    }
267
268    fn get(&self, index: usize) -> Option<Box<dyn Node>> {
269        self.nodes.get(self.offset + index).map(|n| n.clone_node())
270    }
271}
272
273#[cfg(test)]
274mod tests {
275    use super::*;
276
277    #[test]
278    fn test_traits_defined() {
279        // This test just ensures the traits compile
280        // Actual tests will be in dazzle-grove-libxml2
281    }
282
283    #[test]
284    fn test_empty_node_list() {
285        let empty = EmptyNodeList::new();
286        assert!(empty.is_empty());
287        assert_eq!(empty.length(), 0);
288        assert!(empty.first().is_none());
289        assert!(empty.get(0).is_none());
290        assert!(empty.rest().is_empty());
291    }
292}