Skip to main content

revelo_core/
element.rs

1//! Element trace tree — transliteration of MediaInfoLib's
2//! `Element[Element_Level]` stack and `element_details::Element_Node` tree.
3//!
4//! In the C++ side this serves two roles: (1) building the `--trace` output
5//! and (2) scoping sub-element parsing. This crate only handles the trace
6//! tree role for now; the offset-scoping role is tied to the
7//! `Header_Parse`/`Data_Parse` parser callback architecture and is handled
8//! separately when that lands.
9
10#[derive(Clone, Debug, Default, PartialEq)]
11pub struct ElementInfo {
12    /// `Some(name)` for field reads recorded via `Param` (i.e. `Get_B4(Size, "Size")`),
13    /// `None` for ad-hoc info added via `Element_Info`.
14    pub name: Option<String>,
15    pub value: String,
16    pub measure: Option<String>,
17}
18
19#[derive(Clone, Debug, Default)]
20pub struct ElementNode {
21    pub name: String,
22    pub size: u64,
23    pub infos: Vec<ElementInfo>,
24    pub children: Vec<ElementNode>,
25    pub has_error: bool,
26}
27
28impl ElementNode {
29    pub fn new(name: impl Into<String>) -> Self {
30        ElementNode {
31            name: name.into(),
32            size: 0,
33            infos: Vec::new(),
34            children: Vec::new(),
35            has_error: false,
36        }
37    }
38}
39
40/// Stack-based element tree builder. Begin/end pairs must balance; pushing
41/// a child on `End` makes the tree append-only from the consumer's view.
42pub struct ElementTree {
43    stack: Vec<ElementNode>,
44}
45
46impl ElementTree {
47    pub fn new() -> Self {
48        ElementTree { stack: vec![ElementNode::new("")] }
49    }
50
51    pub fn element_begin(&mut self, name: impl Into<String>) {
52        self.stack.push(ElementNode::new(name));
53    }
54
55    pub fn element_end(&mut self) {
56        if self.stack.len() <= 1 {
57            return;
58        }
59        let child = self.stack.pop().expect("len > 1 checked above");
60        self.stack.last_mut().expect("len > 0 invariant").children.push(child);
61    }
62
63    pub fn element_name(&mut self, name: impl Into<String>) {
64        if let Some(last) = self.stack.last_mut() {
65            last.name = name.into();
66        }
67    }
68
69    pub fn element_info(&mut self, value: impl Into<String>, measure: Option<&str>) {
70        if let Some(last) = self.stack.last_mut() {
71            let value = value.into();
72            // Match the C++ heuristic: value="NOK" or measure="Error" flags
73            // the element as containing an error.
74            if value == "NOK" || measure == Some("Error") {
75                last.has_error = true;
76            }
77            last.infos.push(ElementInfo { name: None, value, measure: measure.map(String::from) });
78        }
79    }
80
81    /// Record a field read (called by `Get_B*` / `Get_L*` / etc).
82    pub fn param(&mut self, name: impl Into<String>, value: impl Into<String>) {
83        if let Some(last) = self.stack.last_mut() {
84            last.infos.push(ElementInfo {
85                name: Some(name.into()),
86                value: value.into(),
87                measure: None,
88            });
89        }
90    }
91
92    pub fn element_level(&self) -> usize {
93        // C++ defines level 0 as "the implicit root", so depth = stack.len() - 1.
94        self.stack.len().saturating_sub(1)
95    }
96
97    pub fn set_current_size(&mut self, size: u64) {
98        if let Some(last) = self.stack.last_mut() {
99            last.size = size;
100        }
101    }
102
103    /// Returns the root node. Only meaningful when all Begin/End pairs have
104    /// balanced (i.e. `Element_Level() == 0`).
105    pub fn root(&self) -> &ElementNode {
106        &self.stack[0]
107    }
108
109    /// Mutable access to the current (top-of-stack) element. Used by
110    /// `FileAnalyze::Get_B*` etc. to record param entries.
111    pub fn current_mut(&mut self) -> &mut ElementNode {
112        self.stack.last_mut().expect("stack invariant: root always present")
113    }
114}
115
116impl Default for ElementTree {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125
126    #[test]
127    fn new_tree_has_root_at_level_0() {
128        let t = ElementTree::new();
129        assert_eq!(t.element_level(), 0);
130        assert_eq!(t.root().children.len(), 0);
131    }
132
133    #[test]
134    fn begin_end_pair_appends_child_to_root() {
135        let mut t = ElementTree::new();
136        t.element_begin("atom");
137        assert_eq!(t.element_level(), 1);
138        t.element_end();
139        assert_eq!(t.element_level(), 0);
140        assert_eq!(t.root().children.len(), 1);
141        assert_eq!(t.root().children[0].name, "atom");
142    }
143
144    #[test]
145    fn nested_begin_end_builds_tree() {
146        let mut t = ElementTree::new();
147        t.element_begin("moov");
148        t.element_begin("trak");
149        t.element_begin("tkhd");
150        t.element_end();
151        t.element_begin("mdia");
152        t.element_end();
153        t.element_end();
154        t.element_end();
155
156        let root = t.root();
157        assert_eq!(root.children.len(), 1);
158        let moov = &root.children[0];
159        assert_eq!(moov.name, "moov");
160        assert_eq!(moov.children.len(), 1);
161        let trak = &moov.children[0];
162        assert_eq!(trak.children.len(), 2);
163        assert_eq!(trak.children[0].name, "tkhd");
164        assert_eq!(trak.children[1].name, "mdia");
165    }
166
167    #[test]
168    fn element_info_records_value_and_measure() {
169        let mut t = ElementTree::new();
170        t.element_begin("tkhd");
171        t.element_info("1000", Some("ms"));
172        t.element_info("42", None);
173        t.element_end();
174        let tkhd = &t.root().children[0];
175        assert_eq!(tkhd.infos.len(), 2);
176        assert_eq!(tkhd.infos[0].name, None);
177        assert_eq!(tkhd.infos[0].value, "1000");
178        assert_eq!(tkhd.infos[0].measure.as_deref(), Some("ms"));
179        assert_eq!(tkhd.infos[1].measure, None);
180    }
181
182    #[test]
183    fn param_records_named_field_read() {
184        let mut t = ElementTree::new();
185        t.element_begin("mvhd");
186        t.param("Version", "0");
187        t.param("Flags", "0x000000");
188        t.element_end();
189        let mvhd = &t.root().children[0];
190        assert_eq!(mvhd.infos.len(), 2);
191        assert_eq!(mvhd.infos[0].name.as_deref(), Some("Version"));
192        assert_eq!(mvhd.infos[0].value, "0");
193        assert_eq!(mvhd.infos[1].name.as_deref(), Some("Flags"));
194    }
195
196    #[test]
197    fn nok_marks_element_as_error() {
198        let mut t = ElementTree::new();
199        t.element_begin("bad");
200        t.element_info("NOK", None);
201        t.element_end();
202        assert!(t.root().children[0].has_error);
203    }
204
205    #[test]
206    fn measure_error_marks_element_as_error() {
207        let mut t = ElementTree::new();
208        t.element_begin("bad");
209        t.element_info("0x1234", Some("Error"));
210        t.element_end();
211        assert!(t.root().children[0].has_error);
212    }
213
214    #[test]
215    fn element_name_renames_current_frame() {
216        let mut t = ElementTree::new();
217        t.element_begin("");
218        t.element_name("renamed");
219        t.element_end();
220        assert_eq!(t.root().children[0].name, "renamed");
221    }
222
223    #[test]
224    fn extra_element_end_is_noop() {
225        let mut t = ElementTree::new();
226        t.element_end();
227        t.element_end();
228        assert_eq!(t.element_level(), 0);
229    }
230}