Skip to main content

oak_xml/builder/
mod.rs

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