1#![allow(unused_variables)]
2
3use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
4use core::fmt;
5#[cfg(feature = "std")]
6use std::path::Path;
7
8#[cfg(feature = "svg")]
9pub mod svg;
10
11fn decode_xml_entities(s: &str) -> String {
15 let mut result = String::with_capacity(s.len());
16 let mut chars = s.chars().peekable();
17
18 while let Some(c) = chars.next() {
19 if c == '&' {
20 let mut entity = String::new();
22 let mut found_semicolon = false;
23
24 while let Some(&next) = chars.peek() {
25 if next == ';' {
26 chars.next();
27 found_semicolon = true;
28 break;
29 }
30 if !next.is_alphanumeric() && next != '#' {
31 break;
32 }
33 entity.push(chars.next().unwrap());
34 if entity.len() > 10 {
35 break;
37 }
38 }
39
40 if found_semicolon {
41 match entity.as_str() {
43 "lt" => result.push('<'),
44 "gt" => result.push('>'),
45 "amp" => result.push('&'),
46 "apos" => result.push('\''),
47 "quot" => result.push('"'),
48 "nbsp" => result.push('\u{00A0}'),
49 s if s.starts_with('#') => {
50 let num_str = &s[1..];
52 let code_point = if num_str.starts_with('x') || num_str.starts_with('X') {
53 u32::from_str_radix(&num_str[1..], 16).ok()
55 } else {
56 num_str.parse::<u32>().ok()
58 };
59 if let Some(cp) = code_point {
60 if let Some(ch) = char::from_u32(cp) {
61 result.push(ch);
62 } else {
63 result.push('&');
65 result.push_str(&entity);
66 result.push(';');
67 }
68 } else {
69 result.push('&');
71 result.push_str(&entity);
72 result.push(';');
73 }
74 }
75 _ => {
76 result.push('&');
78 result.push_str(&entity);
79 result.push(';');
80 }
81 }
82 } else {
83 result.push('&');
85 result.push_str(&entity);
86 }
87 } else {
88 result.push(c);
89 }
90 }
91
92 result
93}
94
95pub use azul_core::xml::*;
96use azul_core::{dom::Dom, impl_from, styled_dom::StyledDom, window::StringPairVec};
97#[cfg(feature = "parser")]
98use azul_css::parser2::CssParseError;
99use azul_css::{css::Css, AzString, OptionString, U8Vec};
100use xmlparser::Tokenizer;
101
102#[cfg(feature = "xml")]
103pub fn domxml_from_str(xml: &str, component_map: &mut XmlComponentMap) -> DomXml {
104 let error_css = Css::empty();
105
106 let parsed = match parse_xml_string(&xml) {
107 Ok(parsed) => parsed,
108 Err(e) => {
109 return DomXml {
110 parsed_dom: Dom::create_body()
111 .with_children(vec![Dom::create_text(format!("{}", e))].into())
112 .style(error_css.clone()),
113 };
114 }
115 };
116
117 let parsed_dom = match str_to_dom(parsed.as_ref(), component_map, None) {
118 Ok(o) => o,
119 Err(e) => {
120 return DomXml {
121 parsed_dom: Dom::create_body()
122 .with_children(vec![Dom::create_text(format!("{}", e))].into())
123 .style(error_css.clone()),
124 };
125 }
126 };
127
128 DomXml { parsed_dom }
129}
130
131#[cfg(all(feature = "std", feature = "xml"))]
137pub fn domxml_from_file<I: AsRef<Path>>(
138 file_path: I,
139 component_map: &mut XmlComponentMap,
140) -> DomXml {
141 use std::fs;
142
143 let error_css = Css::empty();
144
145 let xml = match fs::read_to_string(file_path.as_ref()) {
146 Ok(xml) => xml,
147 Err(e) => {
148 return DomXml {
149 parsed_dom: Dom::create_body()
150 .with_children(
151 vec![Dom::create_text(format!(
152 "Error reading: \"{}\": {}",
153 file_path.as_ref().to_string_lossy(),
154 e
155 ))]
156 .into(),
157 )
158 .style(error_css.clone()),
159 };
160 }
161 };
162
163 domxml_from_str(&xml, component_map)
164}
165
166#[cfg(feature = "xml")]
173pub fn parse_xml_string(xml: &str) -> Result<Vec<XmlNodeChild>, XmlError> {
174 use xmlparser::{ElementEnd::*, Token::*, Tokenizer};
175
176 use self::XmlParseError::*;
177
178 let mut root_node = XmlNode::default();
179
180 let mut xml = xml.trim();
182 if xml.starts_with("<?") {
183 let pos = xml.find("?>").ok_or(XmlError::MalformedHierarchy(
184 azul_core::xml::MalformedHierarchyError {
185 expected: "<?xml".into(),
186 got: "?>".into(),
187 },
188 ))?;
189 xml = &xml[(pos + 2)..];
190 }
191
192 let mut xml = xml.trim();
194 if xml.len() > 9 && xml[..9].to_ascii_lowercase().starts_with("<!doctype") {
195 let pos = xml.find(">").ok_or(XmlError::MalformedHierarchy(
196 azul_core::xml::MalformedHierarchyError {
197 expected: "<!DOCTYPE".into(),
198 got: ">".into(),
199 },
200 ))?;
201 xml = &xml[(pos + 1)..];
202 } else if xml.starts_with("<!--") {
203 if let Some(end) = xml.find("-->") {
205 xml = &xml[(end + 3)..];
206 xml = xml.trim();
207 }
208 }
209
210 let tokenizer = Tokenizer::from_fragment(xml, 0..xml.len());
211
212 let mut node_stack: Vec<*mut XmlNode> = vec![&mut root_node as *mut XmlNode];
218
219 const VOID_ELEMENTS: &[&str] = &[
222 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
223 "source", "track", "wbr",
224 ];
225
226 const AUTO_CLOSE_RULES: &[(&str, &[&str])] = &[
229 ("li", &["li"]),
231 ("td", &["td", "th", "tr"]),
233 ("th", &["td", "th", "tr"]),
234 ("tr", &["tr"]),
235 (
237 "p",
238 &[
239 "address",
240 "article",
241 "aside",
242 "blockquote",
243 "div",
244 "dl",
245 "fieldset",
246 "footer",
247 "form",
248 "h1",
249 "h2",
250 "h3",
251 "h4",
252 "h5",
253 "h6",
254 "header",
255 "hr",
256 "main",
257 "nav",
258 "ol",
259 "p",
260 "pre",
261 "section",
262 "table",
263 "ul",
264 ],
265 ),
266 ("option", &["option", "optgroup"]),
268 ("optgroup", &["optgroup"]),
269 ("dd", &["dd", "dt"]),
271 ("dt", &["dd", "dt"]),
272 ];
273
274 let mut last_was_void = false;
276
277 for token in tokenizer {
278 let token = token.map_err(|e| XmlError::ParserError(translate_xmlparser_error(e)))?;
279 match token {
280 ElementStart { local, .. } => {
281 let tag_name = local.to_string();
282 let is_void_element = VOID_ELEMENTS.contains(&tag_name.as_str());
283
284 if last_was_void {
287 node_stack.pop();
288 last_was_void = false;
289 }
290
291 if node_stack.len() > 1 {
293 let current_element = unsafe { &*node_stack[node_stack.len() - 1] };
295 let current_tag = current_element.node_type.as_str();
296
297 for (element, closes_on) in AUTO_CLOSE_RULES {
299 if current_tag == *element && closes_on.contains(&tag_name.as_str()) {
300 node_stack.pop();
302 break;
303 }
304 }
305 }
306
307 if let Some(¤t_parent_ptr) = node_stack.last() {
309 let current_parent = unsafe { &mut *current_parent_ptr };
310
311 current_parent.children.push(XmlNodeChild::Element(XmlNode {
312 node_type: tag_name.into(),
313 attributes: StringPairVec::new().into(),
314 children: Vec::new().into(),
315 }));
316
317 let children_len = current_parent.children.len();
319 if let Some(XmlNodeChild::Element(ref mut new_child)) = current_parent.children.as_mut().get_mut(children_len - 1) {
320 node_stack.push(new_child as *mut XmlNode);
321 }
322
323 last_was_void = is_void_element;
324 }
325 }
326 ElementEnd { end: Empty, .. } => {
327 if node_stack.len() > 1 {
329 node_stack.pop();
330 }
331 last_was_void = false;
332 }
333 ElementEnd {
334 end: Close(_, close_value),
335 ..
336 } => {
337 if last_was_void {
339 node_stack.pop();
340 last_was_void = false;
341 }
342
343 let is_void_element = VOID_ELEMENTS.contains(&close_value.as_str());
345 if is_void_element {
346 continue;
348 }
349
350 let close_value_str = close_value.as_str();
353
354 let mut found_idx = None;
356 for i in (1..node_stack.len()).rev() {
357 let node = unsafe { &*node_stack[i] };
359 if node.node_type.as_str() == close_value_str {
360 found_idx = Some(i);
361 break;
362 }
363 }
364
365 if let Some(idx) = found_idx {
366 node_stack.truncate(idx);
368 }
369 last_was_void = false;
372 }
373 Attribute { local, value, .. } => {
374 if let Some(&last_ptr) = node_stack.last() {
376 let last = unsafe { &mut *last_ptr };
377 last.attributes.push(azul_core::window::AzStringPair {
380 key: local.to_string().into(),
381 value: decode_xml_entities(value.as_str()).into(),
382 });
383 }
384 }
385 Text { text } => {
386 if last_was_void {
388 node_stack.pop();
389 last_was_void = false;
390 }
391
392 let text_str = text.as_str();
400
401 if !text_str.is_empty() {
402 if let Some(¤t_parent_ptr) = node_stack.last() {
404 let current_parent = unsafe { &mut *current_parent_ptr };
405 let decoded_text = decode_xml_entities(text_str);
407 current_parent
409 .children
410 .push(XmlNodeChild::Text(decoded_text.into()));
411 }
412 }
413 }
414 _ => {}
415 }
416 }
417
418 if last_was_void {
420 node_stack.pop();
421 }
422
423 Ok(root_node.children.into())
424}
425
426#[cfg(feature = "xml")]
427pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
428 Ok(Xml {
429 root: parse_xml_string(s)?.into(),
430 })
431}
432
433#[cfg(not(feature = "xml"))]
434pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
435 Err(XmlError::NoParserAvailable)
436}
437
438#[cfg(feature = "xml")]
441pub fn translate_roxmltree_expandedname<'a, 'b>(
442 e: roxmltree::ExpandedName<'a, 'b>,
443) -> XmlQualifiedName {
444 let ns: Option<AzString> = e.namespace().map(|e| e.to_string().into());
445 XmlQualifiedName {
446 local_name: e.name().to_string().into(),
447 namespace: ns.into(),
448 }
449}
450
451#[cfg(feature = "xml")]
452fn translate_roxmltree_attribute(e: roxmltree::Attribute) -> XmlQualifiedName {
453 XmlQualifiedName {
454 local_name: e.name().to_string().into(),
455 namespace: e.namespace().map(|e| e.to_string().into()).into(),
456 }
457}
458
459#[cfg(feature = "xml")]
460fn translate_xmlparser_streamerror(e: xmlparser::StreamError) -> XmlStreamError {
461 match e {
462 xmlparser::StreamError::UnexpectedEndOfStream => XmlStreamError::UnexpectedEndOfStream,
463 xmlparser::StreamError::InvalidName => XmlStreamError::InvalidName,
464 xmlparser::StreamError::InvalidReference => XmlStreamError::InvalidReference,
465 xmlparser::StreamError::InvalidExternalID => XmlStreamError::InvalidExternalID,
466 xmlparser::StreamError::InvalidCommentData => XmlStreamError::InvalidCommentData,
467 xmlparser::StreamError::InvalidCommentEnd => XmlStreamError::InvalidCommentEnd,
468 xmlparser::StreamError::InvalidCharacterData => XmlStreamError::InvalidCharacterData,
469 xmlparser::StreamError::NonXmlChar(c, tp) => XmlStreamError::NonXmlChar(NonXmlCharError {
470 ch: c.into(),
471 pos: translate_xmlparser_textpos(tp),
472 }),
473 xmlparser::StreamError::InvalidChar(a, b, tp) => {
474 XmlStreamError::InvalidChar(InvalidCharError {
475 expected: a,
476 got: b,
477 pos: translate_xmlparser_textpos(tp),
478 })
479 }
480 xmlparser::StreamError::InvalidCharMultiple(a, b, tp) => {
481 XmlStreamError::InvalidCharMultiple(InvalidCharMultipleError {
482 expected: a,
483 got: b.to_vec().into(),
484 pos: translate_xmlparser_textpos(tp),
485 })
486 }
487 xmlparser::StreamError::InvalidQuote(a, tp) => {
488 XmlStreamError::InvalidQuote(InvalidQuoteError {
489 got: a.into(),
490 pos: translate_xmlparser_textpos(tp),
491 })
492 }
493 xmlparser::StreamError::InvalidSpace(a, tp) => {
494 XmlStreamError::InvalidSpace(InvalidSpaceError {
495 got: a.into(),
496 pos: translate_xmlparser_textpos(tp),
497 })
498 }
499 xmlparser::StreamError::InvalidString(a, tp) => {
500 XmlStreamError::InvalidString(InvalidStringError {
501 got: a.to_string().into(),
502 pos: translate_xmlparser_textpos(tp),
503 })
504 }
505 }
506}
507
508#[cfg(feature = "xml")]
509fn translate_xmlparser_error(e: xmlparser::Error) -> XmlParseError {
510 match e {
511 xmlparser::Error::InvalidDeclaration(se, tp) => {
512 XmlParseError::InvalidDeclaration(XmlTextError {
513 stream_error: translate_xmlparser_streamerror(se),
514 pos: translate_xmlparser_textpos(tp),
515 })
516 }
517 xmlparser::Error::InvalidComment(se, tp) => XmlParseError::InvalidComment(XmlTextError {
518 stream_error: translate_xmlparser_streamerror(se),
519 pos: translate_xmlparser_textpos(tp),
520 }),
521 xmlparser::Error::InvalidPI(se, tp) => XmlParseError::InvalidPI(XmlTextError {
522 stream_error: translate_xmlparser_streamerror(se),
523 pos: translate_xmlparser_textpos(tp),
524 }),
525 xmlparser::Error::InvalidDoctype(se, tp) => XmlParseError::InvalidDoctype(XmlTextError {
526 stream_error: translate_xmlparser_streamerror(se),
527 pos: translate_xmlparser_textpos(tp),
528 }),
529 xmlparser::Error::InvalidEntity(se, tp) => XmlParseError::InvalidEntity(XmlTextError {
530 stream_error: translate_xmlparser_streamerror(se),
531 pos: translate_xmlparser_textpos(tp),
532 }),
533 xmlparser::Error::InvalidElement(se, tp) => XmlParseError::InvalidElement(XmlTextError {
534 stream_error: translate_xmlparser_streamerror(se),
535 pos: translate_xmlparser_textpos(tp),
536 }),
537 xmlparser::Error::InvalidAttribute(se, tp) => {
538 XmlParseError::InvalidAttribute(XmlTextError {
539 stream_error: translate_xmlparser_streamerror(se),
540 pos: translate_xmlparser_textpos(tp),
541 })
542 }
543 xmlparser::Error::InvalidCdata(se, tp) => XmlParseError::InvalidCdata(XmlTextError {
544 stream_error: translate_xmlparser_streamerror(se),
545 pos: translate_xmlparser_textpos(tp),
546 }),
547 xmlparser::Error::InvalidCharData(se, tp) => XmlParseError::InvalidCharData(XmlTextError {
548 stream_error: translate_xmlparser_streamerror(se),
549 pos: translate_xmlparser_textpos(tp),
550 }),
551 xmlparser::Error::UnknownToken(tp) => {
552 XmlParseError::UnknownToken(translate_xmlparser_textpos(tp))
553 }
554 }
555}
556
557#[cfg(feature = "xml")]
558pub fn translate_roxmltree_error(e: roxmltree::Error) -> XmlError {
559 match e {
560 roxmltree::Error::InvalidXmlPrefixUri(s) => {
561 XmlError::InvalidXmlPrefixUri(translate_roxml_textpos(s))
562 }
563 roxmltree::Error::UnexpectedXmlUri(s) => {
564 XmlError::UnexpectedXmlUri(translate_roxml_textpos(s))
565 }
566 roxmltree::Error::UnexpectedXmlnsUri(s) => {
567 XmlError::UnexpectedXmlnsUri(translate_roxml_textpos(s))
568 }
569 roxmltree::Error::InvalidElementNamePrefix(s) => {
570 XmlError::InvalidElementNamePrefix(translate_roxml_textpos(s))
571 }
572 roxmltree::Error::DuplicatedNamespace(s, tp) => {
573 XmlError::DuplicatedNamespace(DuplicatedNamespaceError {
574 ns: s.into(),
575 pos: translate_roxml_textpos(tp),
576 })
577 }
578 roxmltree::Error::UnknownNamespace(s, tp) => {
579 XmlError::UnknownNamespace(UnknownNamespaceError {
580 ns: s.into(),
581 pos: translate_roxml_textpos(tp),
582 })
583 }
584 roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => {
585 XmlError::UnexpectedCloseTag(UnexpectedCloseTagError {
586 expected: expected.into(),
587 actual: actual.into(),
588 pos: translate_roxml_textpos(pos),
589 })
590 }
591 roxmltree::Error::UnexpectedEntityCloseTag(s) => {
592 XmlError::UnexpectedEntityCloseTag(translate_roxml_textpos(s))
593 }
594 roxmltree::Error::UnknownEntityReference(s, tp) => {
595 XmlError::UnknownEntityReference(UnknownEntityReferenceError {
596 entity: s.into(),
597 pos: translate_roxml_textpos(tp),
598 })
599 }
600 roxmltree::Error::MalformedEntityReference(s) => {
601 XmlError::MalformedEntityReference(translate_roxml_textpos(s))
602 }
603 roxmltree::Error::EntityReferenceLoop(s) => {
604 XmlError::EntityReferenceLoop(translate_roxml_textpos(s))
605 }
606 roxmltree::Error::InvalidAttributeValue(s) => {
607 XmlError::InvalidAttributeValue(translate_roxml_textpos(s))
608 }
609 roxmltree::Error::DuplicatedAttribute(s, tp) => {
610 XmlError::DuplicatedAttribute(DuplicatedAttributeError {
611 attribute: s.into(),
612 pos: translate_roxml_textpos(tp),
613 })
614 }
615 roxmltree::Error::NoRootNode => XmlError::NoRootNode,
616 roxmltree::Error::DtdDetected => XmlError::DtdDetected,
617 roxmltree::Error::UnclosedRootNode => XmlError::UnclosedRootNode,
618 roxmltree::Error::UnexpectedDeclaration(tp) => {
619 XmlError::UnexpectedDeclaration(translate_roxml_textpos(tp))
620 }
621 roxmltree::Error::NodesLimitReached => XmlError::NodesLimitReached,
622 roxmltree::Error::AttributesLimitReached => XmlError::AttributesLimitReached,
623 roxmltree::Error::NamespacesLimitReached => XmlError::NamespacesLimitReached,
624 roxmltree::Error::InvalidName(tp) => XmlError::InvalidName(translate_roxml_textpos(tp)),
625 roxmltree::Error::NonXmlChar(_, tp) => XmlError::NonXmlChar(translate_roxml_textpos(tp)),
626 roxmltree::Error::InvalidChar(_, _, tp) => {
627 XmlError::InvalidChar(translate_roxml_textpos(tp))
628 }
629 roxmltree::Error::InvalidChar2(_, _, tp) => {
630 XmlError::InvalidChar2(translate_roxml_textpos(tp))
631 }
632 roxmltree::Error::InvalidString(_, tp) => {
633 XmlError::InvalidString(translate_roxml_textpos(tp))
634 }
635 roxmltree::Error::InvalidExternalID(tp) => {
636 XmlError::InvalidExternalID(translate_roxml_textpos(tp))
637 }
638 roxmltree::Error::InvalidComment(tp) => {
639 XmlError::InvalidComment(translate_roxml_textpos(tp))
640 }
641 roxmltree::Error::InvalidCharacterData(tp) => {
642 XmlError::InvalidCharacterData(translate_roxml_textpos(tp))
643 }
644 roxmltree::Error::UnknownToken(tp) => XmlError::UnknownToken(translate_roxml_textpos(tp)),
645 roxmltree::Error::UnexpectedEndOfStream => XmlError::UnexpectedEndOfStream,
646 roxmltree::Error::EntityResolver(tp, s) => {
647 XmlError::UnknownEntityReference(UnknownEntityReferenceError {
650 entity: s.into(),
651 pos: translate_roxml_textpos(tp),
652 })
653 }
654 }
655}
656
657#[cfg(feature = "xml")]
658#[inline(always)]
659const fn translate_xmlparser_textpos(o: xmlparser::TextPos) -> XmlTextPos {
660 XmlTextPos {
661 row: o.row,
662 col: o.col,
663 }
664}
665
666#[cfg(feature = "xml")]
667#[inline(always)]
668const fn translate_roxml_textpos(o: roxmltree::TextPos) -> XmlTextPos {
669 XmlTextPos {
670 row: o.row,
671 col: o.col,
672 }
673}
674
675#[cfg(feature = "xml")]
681pub trait DomXmlExt {
682 fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom;
693}
694
695#[cfg(feature = "xml")]
696impl DomXmlExt for Dom {
697 fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom {
698 let mut component_map = XmlComponentMap::default();
699 let dom_xml = domxml_from_str(xml.as_ref(), &mut component_map);
700 dom_xml.parsed_dom
701 }
702}