Skip to main content

oak_xml/builder/
mod.rs

1use crate::{
2    XmlElementType, XmlLanguage,
3    ast::{XmlAttribute, XmlElement, XmlRoot, XmlValue},
4    lexer::token_type::XmlTokenType,
5};
6use core::range::Range;
7use oak_core::{
8    Builder, BuilderCache, GreenNode, GreenTree, OakDiagnostics, OakError, Parser, Source,
9    builder::BuildOutput,
10    source::{SourceText, TextEdit},
11};
12
13/// XML AST builder.
14pub struct XmlBuilder;
15
16impl XmlBuilder {
17    /// Creates a new `XmlBuilder`.
18    pub fn new() -> Self {
19        Self
20    }
21
22    fn build_root<'a>(&self, green_tree: &GreenNode<'a, XmlLanguage>, source: &SourceText) -> Result<XmlRoot, OakError> {
23        let mut children = Vec::new();
24        let mut current_offset = 0;
25
26        for child in green_tree.children {
27            match child {
28                GreenTree::Node(node) => {
29                    match node.kind {
30                        XmlElementType::Prolog => {
31                            // For now we might ignore prolog or handle it if needed
32                        }
33                        XmlElementType::Element => {
34                            children.push(self.build_element(node, current_offset, source)?);
35                        }
36                        _ => {}
37                    }
38                    current_offset += node.byte_length as usize;
39                }
40                GreenTree::Leaf(leaf) => {
41                    current_offset += leaf.length as usize;
42                }
43            }
44        }
45
46        let value = if children.len() == 1 { XmlValue::Element(children.remove(0)) } else { children.into_iter().next().map(XmlValue::Element).unwrap_or(XmlValue::Text(String::new())) };
47
48        Ok(XmlRoot { value })
49    }
50
51    fn build_element<'a>(&self, node: &GreenNode<'a, XmlLanguage>, offset: usize, source: &SourceText) -> Result<XmlElement, OakError> {
52        let mut name = String::new();
53        let mut attributes = Vec::new();
54        let mut children = Vec::new();
55        let mut current_offset = offset;
56
57        for child in node.children {
58            match child {
59                GreenTree::Node(n) => {
60                    match n.kind {
61                        XmlElementType::StartTag | XmlElementType::SelfClosingTag => {
62                            let mut sub_offset = current_offset;
63                            for sub_child in n.children {
64                                match sub_child {
65                                    GreenTree::Leaf(t) if t.kind == XmlTokenType::Identifier => {
66                                        name = source.get_text_in(Range { start: sub_offset, end: sub_offset + t.length as usize }).to_string();
67                                    }
68                                    GreenTree::Node(attr_node) if attr_node.kind == XmlElementType::Attribute => {
69                                        attributes.push(self.build_attribute(attr_node, sub_offset, source)?);
70                                    }
71                                    _ => {}
72                                }
73                                sub_offset += sub_child.len() as usize;
74                            }
75                        }
76                        XmlElementType::Element => {
77                            children.push(XmlValue::Element(self.build_element(n, current_offset, source)?));
78                        }
79                        _ => {}
80                    }
81                    current_offset += n.byte_length as usize;
82                }
83                GreenTree::Leaf(t) => {
84                    match t.kind {
85                        XmlTokenType::Text => {
86                            let text = source.get_text_in(Range { start: current_offset, end: current_offset + t.length as usize });
87                            if !text.trim().is_empty() {
88                                children.push(XmlValue::Text(text.to_string()));
89                            }
90                        }
91                        _ => {}
92                    }
93                    current_offset += t.length as usize;
94                }
95            }
96        }
97
98        Ok(XmlElement { name, attributes, children, span: Range { start: offset, end: offset + node.byte_length as usize } })
99    }
100
101    fn build_attribute<'a>(&self, node: &GreenNode<'a, XmlLanguage>, offset: usize, source: &SourceText) -> Result<XmlAttribute, OakError> {
102        let mut name = String::new();
103        let mut value = String::new();
104        let mut current_offset = offset;
105
106        for child in node.children {
107            match child {
108                GreenTree::Leaf(t) => {
109                    match t.kind {
110                        XmlTokenType::Identifier => {
111                            name = source.get_text_in(Range { start: current_offset, end: current_offset + t.length as usize }).to_string();
112                        }
113                        XmlTokenType::StringLiteral => {
114                            let raw = source.get_text_in(Range { start: current_offset, end: current_offset + t.length as usize });
115                            // Strip quotes
116                            if raw.len() >= 2 {
117                                value = raw[1..raw.len() - 1].to_string();
118                            }
119                            else {
120                                value = raw.to_string();
121                            }
122                        }
123                        _ => {}
124                    }
125                    current_offset += t.length as usize;
126                }
127                GreenTree::Node(n) => {
128                    current_offset += n.byte_length as usize;
129                }
130            }
131        }
132
133        Ok(XmlAttribute { name, value, span: Range { start: offset, end: offset + node.byte_length as usize } })
134    }
135}
136
137impl Builder<XmlLanguage> for XmlBuilder {
138    fn build<'a, S: Source + ?Sized>(&self, text: &S, edits: &[TextEdit], cache: &'a mut impl BuilderCache<XmlLanguage>) -> BuildOutput<XmlLanguage> {
139        let source = SourceText::new(text.get_text_in(Range { start: 0, end: text.length() }).to_string());
140        let config = XmlLanguage::default();
141        let parser = crate::parser::XmlParser::new(&config);
142        let parse_output = parser.parse(text, edits, cache);
143
144        let mut diagnostics = Vec::new();
145        for error in parse_output.diagnostics {
146            diagnostics.push(error);
147        }
148
149        let result = parse_output.result.and_then(|green_tree| self.build_root(green_tree, &source));
150
151        OakDiagnostics { result, diagnostics }
152    }
153}