facet_dom/
event.rs

1//! DOM event types for tree-based parsing.
2
3use std::borrow::Cow;
4
5/// Events emitted by a DOM parser.
6///
7/// These events represent the structure of a tree-based document (HTML/XML).
8/// The event stream follows this pattern for each element:
9///
10/// ```text
11/// NodeStart { tag: "div" }
12///   Attribute { name: "class", value: "container" }
13///   Attribute { name: "id", value: "main" }
14///   ChildrenStart
15///     Text("Hello ")
16///     NodeStart { tag: "strong" }
17///       ChildrenStart
18///         Text("world")
19///       ChildrenEnd
20///     NodeEnd
21///     Text("!")
22///   ChildrenEnd
23/// NodeEnd
24/// ```
25#[derive(Debug, Clone, PartialEq)]
26pub enum DomEvent<'a> {
27    /// Start of an element node.
28    ///
29    /// Followed by zero or more `Attribute` events, then `ChildrenStart`.
30    NodeStart {
31        /// The tag name (e.g., "div", "p", "my-component").
32        tag: Cow<'a, str>,
33        /// Optional namespace URI.
34        namespace: Option<Cow<'a, str>>,
35    },
36
37    /// An attribute on the current element.
38    ///
39    /// Only valid between `NodeStart` and `ChildrenStart`.
40    Attribute {
41        /// Attribute name.
42        name: Cow<'a, str>,
43        /// Attribute value.
44        value: Cow<'a, str>,
45        /// Optional namespace URI.
46        namespace: Option<Cow<'a, str>>,
47    },
48
49    /// Start of the children section.
50    ///
51    /// After this, expect `Text`, `NodeStart`, or `ChildrenEnd`.
52    ChildrenStart,
53
54    /// End of the children section.
55    ChildrenEnd,
56
57    /// End of an element node.
58    ///
59    /// Must be preceded by `ChildrenEnd` (or directly by attributes for void elements).
60    NodeEnd,
61
62    /// Text content.
63    ///
64    /// Only valid between `ChildrenStart` and `ChildrenEnd`.
65    Text(Cow<'a, str>),
66
67    /// A comment (usually ignored during deserialization).
68    Comment(Cow<'a, str>),
69
70    /// A processing instruction (XML) or DOCTYPE (HTML).
71    ProcessingInstruction {
72        /// Target (e.g., "xml" for `<?xml ...?>`).
73        target: Cow<'a, str>,
74        /// Data content.
75        data: Cow<'a, str>,
76    },
77
78    /// DOCTYPE declaration (HTML5).
79    Doctype(Cow<'a, str>),
80}
81
82impl<'a> DomEvent<'a> {
83    /// Returns true if this is a `NodeStart` event.
84    pub fn is_node_start(&self) -> bool {
85        matches!(self, DomEvent::NodeStart { .. })
86    }
87
88    /// Returns true if this is a `NodeEnd` event.
89    pub fn is_node_end(&self) -> bool {
90        matches!(self, DomEvent::NodeEnd)
91    }
92
93    /// Returns true if this is an `Attribute` event.
94    pub fn is_attribute(&self) -> bool {
95        matches!(self, DomEvent::Attribute { .. })
96    }
97
98    /// Returns true if this is a `Text` event.
99    pub fn is_text(&self) -> bool {
100        matches!(self, DomEvent::Text(_))
101    }
102
103    /// Returns true if this is `ChildrenStart`.
104    pub fn is_children_start(&self) -> bool {
105        matches!(self, DomEvent::ChildrenStart)
106    }
107
108    /// Returns true if this is `ChildrenEnd`.
109    pub fn is_children_end(&self) -> bool {
110        matches!(self, DomEvent::ChildrenEnd)
111    }
112
113    /// Wrap this event for XML-like trace formatting.
114    pub fn trace(&self) -> TraceFmt<'_, 'a> {
115        TraceFmt(self)
116    }
117}
118
119/// Newtype for XML-like trace formatting of DOM events.
120pub struct TraceFmt<'r, 'a>(pub &'r DomEvent<'a>);
121
122impl std::fmt::Display for TraceFmt<'_, '_> {
123    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
124        use owo_colors::OwoColorize;
125
126        match self.0 {
127            DomEvent::NodeStart { tag, namespace } => {
128                if let Some(ns) = namespace {
129                    write!(f, "NodeStart {}<{}:{}>", "<".cyan(), ns, tag.cyan())
130                } else {
131                    write!(f, "NodeStart {}{}{}", "<".cyan(), tag.cyan(), ">".cyan())
132                }
133            }
134            DomEvent::NodeEnd => write!(f, "NodeEnd {}", "</>".cyan()),
135            DomEvent::Attribute {
136                name,
137                value,
138                namespace,
139            } => {
140                if let Some(ns) = namespace {
141                    write!(
142                        f,
143                        "Attribute {}{}:{}={}{}{}",
144                        "@".yellow(),
145                        ns,
146                        name.yellow(),
147                        "\"".yellow(),
148                        value,
149                        "\"".yellow()
150                    )
151                } else {
152                    write!(
153                        f,
154                        "Attribute {}{}={}{}{}",
155                        "@".yellow(),
156                        name.yellow(),
157                        "\"".yellow(),
158                        value,
159                        "\"".yellow()
160                    )
161                }
162            }
163            DomEvent::ChildrenStart => write!(f, "{}", "ChildrenStart".dimmed()),
164            DomEvent::ChildrenEnd => write!(f, "{}", "ChildrenEnd".dimmed()),
165            DomEvent::Text(t) => {
166                let preview: String = t.chars().take(40).collect();
167                if t.len() > 40 {
168                    write!(
169                        f,
170                        "Text {}{}{}",
171                        "\"".green(),
172                        preview.green(),
173                        "...\"".green()
174                    )
175                } else {
176                    write!(
177                        f,
178                        "Text {}{}{}",
179                        "\"".green(),
180                        preview.green(),
181                        "\"".green()
182                    )
183                }
184            }
185            DomEvent::Comment(c) => {
186                let preview: String = c.chars().take(20).collect();
187                write!(
188                    f,
189                    "Comment {}{}{}",
190                    "<!--".dimmed(),
191                    preview.dimmed(),
192                    "-->".dimmed()
193                )
194            }
195            DomEvent::ProcessingInstruction { target, data } => {
196                write!(f, "ProcessingInstruction <?{target} {data}?>")
197            }
198            DomEvent::Doctype(d) => write!(f, "Doctype <!DOCTYPE {d}>"),
199        }
200    }
201}