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}