1extern crate alloc;
8use alloc::collections::BTreeMap;
9use alloc::string::{String, ToString};
10use alloc::vec::Vec;
11use crate::os_lib::dom::{Node, NodeType};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
15pub enum SupportedMimeType {
16 Html,
17 Xml,
18 Svg,
19 Xhtml,
20 Unsupported,
21}
22
23pub fn parse_mime_type(mime: &str) -> SupportedMimeType {
25 let lower = mime.trim().to_ascii_lowercase();
26 match lower.as_str() {
27 "text/html" => SupportedMimeType::Html,
28 "text/xml" | "application/xml" => SupportedMimeType::Xml,
29 "image/svg+xml" => SupportedMimeType::Svg,
30 "application/xhtml+xml" => SupportedMimeType::Xhtml,
31 _ => SupportedMimeType::Unsupported,
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
37pub struct XmlError {
38 pub message: String,
39 pub line: usize,
40 pub column: usize,
41}
42
43pub fn parse_xml(input: &str) -> Result<Node, XmlError> {
45 let chars: Vec<char> = input.chars().collect();
46 let mut pos = 0;
47 let len = chars.len();
48
49 skip_whitespace_and_misc(&chars, &mut pos, len);
51
52 let mut stack: Vec<Node> = Vec::new();
53 let mut root_node: Option<Node> = None;
54
55 while pos < len {
56 skip_whitespace_chars(&chars, &mut pos, len);
57 if pos >= len {
58 break;
59 }
60
61 if chars[pos] == '<' {
62 if pos + 1 < len && chars[pos + 1] == '/' {
63 pos += 2;
65 let tag_name = read_name(&chars, &mut pos, len);
66 skip_whitespace_chars(&chars, &mut pos, len);
67 if pos < len && chars[pos] == '>' {
68 pos += 1;
69 }
70 let Some(popped) = stack.pop() else {
71 return Err(XmlError {
72 message: alloc::format!("Unexpected closing tag </{}> without open tag", tag_name),
73 line: 1,
74 column: pos,
75 });
76 };
77 let popped_tag = match &popped.node_type {
78 NodeType::Element { tag_name: t, .. } => t.clone(),
79 _ => String::new(),
80 };
81 if popped_tag != tag_name {
82 return Err(XmlError {
83 message: alloc::format!("Mismatched closing tag: expected </{}>, got </{}>", popped_tag, tag_name),
84 line: 1,
85 column: pos,
86 });
87 }
88 if let Some(parent) = stack.last_mut() {
89 parent.children.push(popped);
90 } else {
91 if root_node.is_some() {
92 return Err(XmlError {
93 message: "Multiple root elements in XML document".to_string(),
94 line: 1,
95 column: pos,
96 });
97 }
98 root_node = Some(popped);
99 }
100 } else if pos + 3 < len && chars[pos + 1] == '!' && chars[pos + 2] == '-' && chars[pos + 3] == '-' {
101 pos += 4;
103 while pos + 2 < len {
104 if chars[pos] == '-' && chars[pos + 1] == '-' && chars[pos + 2] == '>' {
105 pos += 3;
106 break;
107 }
108 pos += 1;
109 }
110 } else if pos + 8 < len && input_matches_at(&chars, pos, "<![CDATA[") {
111 pos += 9;
113 let cdata_start = pos;
114 while pos + 2 < len {
115 if chars[pos] == ']' && chars[pos + 1] == ']' && chars[pos + 2] == '>' {
116 break;
117 }
118 pos += 1;
119 }
120 let cdata_text: String = chars[cdata_start..pos].iter().collect();
121 if pos + 2 < len {
122 pos += 3;
123 }
124 let text_node = Node::new_text(cdata_text);
125 if let Some(parent) = stack.last_mut() {
126 parent.children.push(text_node);
127 }
128 } else {
129 pos += 1;
131 let tag_name = read_name(&chars, &mut pos, len);
132 if tag_name.is_empty() {
133 return Err(XmlError {
134 message: "Invalid empty element name".to_string(),
135 line: 1,
136 column: pos,
137 });
138 }
139 let attrs = read_attributes(&chars, &mut pos, len)?;
140 skip_whitespace_chars(&chars, &mut pos, len);
141
142 let is_self_closing = if pos + 1 < len && chars[pos] == '/' && chars[pos + 1] == '>' {
143 pos += 2;
144 true
145 } else if pos < len && chars[pos] == '>' {
146 pos += 1;
147 false
148 } else {
149 return Err(XmlError {
150 message: alloc::format!("Unclosed element <{}>", tag_name),
151 line: 1,
152 column: pos,
153 });
154 };
155
156 let node = Node::new_element_preserve_case(tag_name, attrs);
157 if is_self_closing {
158 if let Some(parent) = stack.last_mut() {
159 parent.children.push(node);
160 } else {
161 if root_node.is_some() {
162 return Err(XmlError {
163 message: "Multiple root elements in XML document".to_string(),
164 line: 1,
165 column: pos,
166 });
167 }
168 root_node = Some(node);
169 }
170 } else {
171 stack.push(node);
172 }
173 }
174 } else {
175 let text_start = pos;
177 while pos < len && chars[pos] != '<' {
178 pos += 1;
179 }
180 let raw_text: String = chars[text_start..pos].iter().collect();
181 let unescaped = unescape_xml_entities(&raw_text);
182 if !unescaped.trim().is_empty() || !raw_text.is_empty() {
183 let text_node = Node::new_text(unescaped);
184 if let Some(parent) = stack.last_mut() {
185 parent.children.push(text_node);
186 }
187 }
188 }
189 }
190
191 if let Some(unclosed) = stack.pop() {
192 let tag = match &unclosed.node_type {
193 NodeType::Element { tag_name, .. } => tag_name.clone(),
194 _ => String::new(),
195 };
196 return Err(XmlError {
197 message: alloc::format!("Unclosed tag <{}> at end of document", tag),
198 line: 1,
199 column: pos,
200 });
201 }
202
203 match root_node {
204 Some(r) => Ok(r),
205 None => Err(XmlError {
206 message: "No root element found in XML document".to_string(),
207 line: 1,
208 column: pos,
209 }),
210 }
211}
212
213impl Node {
215 pub fn new_element_preserve_case(name: String, attrs: BTreeMap<String, String>) -> Self {
216 let id = attrs.get("id").cloned();
217 let classes = match attrs.get("class") {
218 Some(class_str) => class_str.split_whitespace().map(String::from).collect(),
219 None => Vec::new(),
220 };
221
222 Node {
223 node_type: NodeType::Element {
224 tag_name: name,
225 attributes: attrs,
226 classes,
227 id,
228 },
229 children: Vec::new(),
230 }
231 }
232}
233
234pub fn unescape_xml_entities(s: &str) -> String {
236 let mut out = String::with_capacity(s.len());
237 let chars: Vec<char> = s.chars().collect();
238 let mut i = 0;
239 while i < chars.len() {
240 if chars[i] == '&' {
241 if let Some(end) = chars[i..].iter().position(|&c| c == ';') {
242 let entity: String = chars[i + 1..i + end].iter().collect();
243 match entity.as_str() {
244 "lt" => out.push('<'),
245 "gt" => out.push('>'),
246 "amp" => out.push('&'),
247 "quot" => out.push('"'),
248 "apos" => out.push('\''),
249 _ => {
250 out.push('&');
251 out.push_str(&entity);
252 out.push(';');
253 }
254 }
255 i += end + 1;
256 continue;
257 }
258 }
259 out.push(chars[i]);
260 i += 1;
261 }
262 out
263}
264
265fn skip_whitespace_chars(chars: &[char], pos: &mut usize, len: usize) {
266 while *pos < len && chars[*pos].is_ascii_whitespace() {
267 *pos += 1;
268 }
269}
270
271fn skip_whitespace_and_misc(chars: &[char], pos: &mut usize, len: usize) {
272 loop {
273 skip_whitespace_chars(chars, pos, len);
274 if *pos + 1 < len && chars[*pos] == '<' && chars[*pos + 1] == '?' {
275 *pos += 2;
277 while *pos + 1 < len {
278 if chars[*pos] == '?' && chars[*pos + 1] == '>' {
279 *pos += 2;
280 break;
281 }
282 *pos += 1;
283 }
284 } else if *pos + 3 < len && chars[*pos] == '<' && chars[*pos + 1] == '!' && chars[*pos + 2] == '-' && chars[*pos + 3] == '-' {
285 *pos += 4;
286 while *pos + 2 < len {
287 if chars[*pos] == '-' && chars[*pos + 1] == '-' && chars[*pos + 2] == '>' {
288 *pos += 3;
289 break;
290 }
291 *pos += 1;
292 }
293 } else {
294 break;
295 }
296 }
297}
298
299fn read_name(chars: &[char], pos: &mut usize, len: usize) -> String {
300 let mut name = String::new();
301 while *pos < len {
302 let c = chars[*pos];
303 if c.is_ascii_alphanumeric() || c == '_' || c == '-' || c == ':' {
304 name.push(c);
305 *pos += 1;
306 } else {
307 break;
308 }
309 }
310 name
311}
312
313fn read_attributes(chars: &[char], pos: &mut usize, len: usize) -> Result<BTreeMap<String, String>, XmlError> {
314 let mut attrs = BTreeMap::new();
315 loop {
316 skip_whitespace_chars(chars, pos, len);
317 if *pos >= len || chars[*pos] == '>' || chars[*pos] == '/' {
318 break;
319 }
320 let attr_name = read_name(chars, pos, len);
321 if attr_name.is_empty() {
322 break;
323 }
324 skip_whitespace_chars(chars, pos, len);
325 let mut attr_val = String::new();
326 if *pos < len && chars[*pos] == '=' {
327 *pos += 1;
328 skip_whitespace_chars(chars, pos, len);
329 if *pos < len && (chars[*pos] == '"' || chars[*pos] == '\'') {
330 let quote = chars[*pos];
331 *pos += 1;
332 let val_start = *pos;
333 while *pos < len && chars[*pos] != quote {
334 *pos += 1;
335 }
336 attr_val = unescape_xml_entities(&chars[val_start..*pos].iter().collect::<String>());
337 if *pos < len {
338 *pos += 1;
339 }
340 }
341 }
342 attrs.insert(attr_name, attr_val);
343 }
344 Ok(attrs)
345}
346
347fn input_matches_at(chars: &[char], pos: usize, pat: &str) -> bool {
348 let pat_chars: Vec<char> = pat.chars().collect();
349 if pos + pat_chars.len() > chars.len() {
350 return false;
351 }
352 for (i, &c) in pat_chars.iter().enumerate() {
353 if chars[pos + i] != c {
354 return false;
355 }
356 }
357 true
358}
359
360pub fn create_parsererror_node(err: &XmlError) -> Node {
362 let mut attrs = BTreeMap::new();
363 attrs.insert("xmlns".to_string(), "http://www.mozilla.org/newlayout/xml/parsererror.xml".to_string());
364 let mut err_node = Node::new_element_preserve_case("parsererror".to_string(), attrs);
365 err_node.children.push(Node::new_text(alloc::format!("XML Parsing Error: {}", err.message)));
366 err_node
367}