ddex_builder/ast.rs
1//! Abstract Syntax Tree for DDEX XML generation
2
3use ddex_core::models::{Comment, CommentPosition};
4use indexmap::IndexMap;
5// Remove unused serde imports since we're not serializing AST
6
7/// Abstract Syntax Tree representation of DDEX XML
8///
9/// The AST represents the complete structure of a DDEX XML document,
10/// including the root element, namespace declarations, and schema location.
11/// This structure is used internally by the builder to construct
12/// deterministic XML output with proper canonicalization.
13#[derive(Debug, Clone)]
14pub struct AST {
15 /// Root element of the document
16 pub root: Element,
17 /// XML namespaces used in the document (prefix -> URI)
18 /// Uses IndexMap to ensure deterministic ordering
19 pub namespaces: IndexMap<String, String>,
20 /// XSD schema location if specified
21 pub schema_location: Option<String>,
22}
23
24/// XML element in the AST
25///
26/// Represents a single XML element with its name, namespace, attributes,
27/// and child nodes. The structure maintains deterministic ordering
28/// of attributes using IndexMap to ensure consistent XML output.
29#[derive(Debug, Clone)]
30pub struct Element {
31 /// Element name (local name without prefix)
32 pub name: String,
33 /// Namespace URI if element is namespaced
34 pub namespace: Option<String>,
35 /// Element attributes (name -> value)
36 /// Uses IndexMap to ensure deterministic attribute ordering
37 pub attributes: IndexMap<String, String>,
38 /// Child nodes (elements, text, comments)
39 pub children: Vec<Node>,
40}
41
42/// Node types in the AST
43///
44/// Represents the different types of nodes that can appear as children
45/// of an XML element. Supports elements, text content, and comments
46/// with both structured and simple comment formats.
47#[derive(Debug, Clone)]
48pub enum Node {
49 /// Element node
50 Element(Element),
51 /// Text content node
52 Text(String),
53 /// Structured comment node with position information
54 Comment(Comment),
55 /// Legacy comment support for backward compatibility
56 SimpleComment(String),
57}
58
59impl Element {
60 /// Create a new element with the given name
61 ///
62 /// # Arguments
63 /// * `name` - The element name (local name without namespace prefix)
64 ///
65 /// # Example
66 /// ```
67 /// use ddex_builder::ast::Element;
68 /// let element = Element::new("Title");
69 /// ```
70 pub fn new(name: impl Into<String>) -> Self {
71 Self {
72 name: name.into(),
73 namespace: None,
74 attributes: IndexMap::new(),
75 children: Vec::new(),
76 }
77 }
78
79 /// Set the namespace for this element
80 ///
81 /// # Arguments
82 /// * `ns` - The namespace URI
83 ///
84 /// # Example
85 /// ```
86 /// use ddex_builder::ast::Element;
87 /// let element = Element::new("Title")
88 /// .with_namespace("http://ddex.net/xml/ern/43");
89 /// ```
90 pub fn with_namespace(mut self, ns: impl Into<String>) -> Self {
91 self.namespace = Some(ns.into());
92 self
93 }
94
95 /// Add an attribute to this element
96 ///
97 /// # Arguments
98 /// * `key` - The attribute name
99 /// * `value` - The attribute value
100 ///
101 /// # Example
102 /// ```
103 /// use ddex_builder::ast::Element;
104 /// let element = Element::new("Title")
105 /// .with_attr("LanguageAndScriptCode", "en");
106 /// ```
107 pub fn with_attr(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
108 self.attributes.insert(key.into(), value.into());
109 self
110 }
111
112 /// Add text content to this element
113 ///
114 /// # Arguments
115 /// * `text` - The text content
116 ///
117 /// # Example
118 /// ```
119 /// use ddex_builder::ast::Element;
120 /// let element = Element::new("Title")
121 /// .with_text("My Song Title");
122 /// ```
123 pub fn with_text(mut self, text: impl Into<String>) -> Self {
124 self.children.push(Node::Text(text.into()));
125 self
126 }
127
128 /// Add a child element
129 ///
130 /// # Arguments
131 /// * `child` - The child element to add
132 ///
133 /// # Example
134 /// ```
135 /// use ddex_builder::ast::Element;
136 /// let mut parent = Element::new("Release");
137 /// let child = Element::new("Title").with_text("My Song");
138 /// parent.add_child(child);
139 /// ```
140 pub fn add_child(&mut self, child: Element) {
141 self.children.push(Node::Element(child));
142 }
143
144 /// Add text content as a child node
145 ///
146 /// # Arguments
147 /// * `text` - The text content to add
148 ///
149 /// # Example
150 /// ```
151 /// use ddex_builder::ast::Element;
152 /// let mut element = Element::new("Title");
153 /// element.add_text("My Song Title");
154 /// ```
155 pub fn add_text(&mut self, text: impl Into<String>) {
156 self.children.push(Node::Text(text.into()));
157 }
158
159 /// Add a structured comment to this element
160 ///
161 /// # Arguments
162 /// * `comment` - The structured comment with position information
163 ///
164 /// # Example
165 /// ```
166 /// use ddex_builder::ast::Element;
167 /// use ddex_core::models::{Comment, CommentPosition};
168 /// let mut element = Element::new("Release");
169 /// let comment = Comment::new("Release generated by system".to_string(), CommentPosition::Before);
170 /// element.add_comment(comment);
171 /// ```
172 pub fn add_comment(&mut self, comment: Comment) {
173 self.children.push(Node::Comment(comment));
174 }
175
176 /// Add a simple comment (for backward compatibility)
177 ///
178 /// # Arguments
179 /// * `comment` - The comment text
180 ///
181 /// # Example
182 /// ```
183 /// use ddex_builder::ast::Element;
184 /// let mut element = Element::new("Release");
185 /// element.add_simple_comment("This is a simple comment");
186 /// ```
187 pub fn add_simple_comment(&mut self, comment: impl Into<String>) {
188 self.children.push(Node::SimpleComment(comment.into()));
189 }
190
191 /// Add a comment with a specific position
192 ///
193 /// # Arguments
194 /// * `content` - The comment content
195 /// * `position` - The position where the comment should appear
196 ///
197 /// # Example
198 /// ```
199 /// use ddex_builder::ast::Element;
200 /// use ddex_core::models::CommentPosition;
201 /// let element = Element::new("Release")
202 /// .with_comment("Release comment".to_string(), CommentPosition::Before);
203 /// ```
204 pub fn with_comment(mut self, content: String, position: CommentPosition) -> Self {
205 let comment = Comment::new(content, position);
206 self.children.push(Node::Comment(comment));
207 self
208 }
209}