1#![allow(unused_variables)]
15
16use alloc::{boxed::Box, collections::BTreeMap, string::String, vec::Vec};
17use core::fmt;
18#[cfg(feature = "std")]
19use std::path::Path;
20
21#[cfg(feature = "svg")]
22pub mod svg;
23
24fn decode_xml_entities(s: &str) -> std::borrow::Cow<'_, str> {
29 if !s.contains('&') {
31 return std::borrow::Cow::Borrowed(s);
32 }
33 decode_xml_entities_slow(s)
34}
35
36fn decode_xml_entities_slow(s: &str) -> std::borrow::Cow<'_, str> {
37 let mut result = String::with_capacity(s.len());
38 let mut chars = s.chars().peekable();
39
40 while let Some(c) = chars.next() {
41 if c == '&' {
42 let mut entity = String::new();
44 let mut found_semicolon = false;
45
46 while let Some(&next) = chars.peek() {
47 if next == ';' {
48 chars.next();
49 found_semicolon = true;
50 break;
51 }
52 if !next.is_alphanumeric() && next != '#' {
53 break;
54 }
55 entity.push(chars.next().unwrap());
56 if entity.len() > 10 {
57 break;
59 }
60 }
61
62 if found_semicolon {
63 match entity.as_str() {
65 "lt" => result.push('<'),
66 "gt" => result.push('>'),
67 "amp" => result.push('&'),
68 "apos" => result.push('\''),
69 "quot" => result.push('"'),
70 "nbsp" => result.push('\u{00A0}'),
71 s if s.starts_with('#') => {
72 let num_str = &s[1..];
74 let code_point = if num_str.starts_with('x') || num_str.starts_with('X') {
75 u32::from_str_radix(&num_str[1..], 16).ok()
77 } else {
78 num_str.parse::<u32>().ok()
80 };
81 if let Some(cp) = code_point {
82 if let Some(ch) = char::from_u32(cp) {
83 result.push(ch);
84 } else {
85 result.push('&');
87 result.push_str(&entity);
88 result.push(';');
89 }
90 } else {
91 result.push('&');
93 result.push_str(&entity);
94 result.push(';');
95 }
96 }
97 _ => {
98 result.push('&');
100 result.push_str(&entity);
101 result.push(';');
102 }
103 }
104 } else {
105 result.push('&');
107 result.push_str(&entity);
108 }
109 } else {
110 result.push(c);
111 }
112 }
113
114 std::borrow::Cow::Owned(result)
115}
116
117pub use azul_core::xml::*;
118use azul_core::{dom::Dom, impl_from, styled_dom::StyledDom, window::StringPairVec};
119#[cfg(feature = "parser")]
120use azul_css::parser2::CssParseError;
121use azul_css::{css::Css, AzString, OptionString, U8Vec};
122use xmlparser::Tokenizer;
123
124#[cfg(feature = "xml")]
125#[must_use] pub fn domxml_from_str(xml: &str, component_map: &ComponentMap) -> DomXml {
126 let error_css = Css::empty();
127
128 let parsed = match parse_xml_string(xml) {
129 Ok(parsed) => parsed,
130 Err(e) => {
131 return DomXml {
132 parsed_dom: {
133 let mut dom = Dom::create_body()
134 .with_children(vec![Dom::create_text(format!("{e}"))].into());
135 StyledDom::create(&mut dom, error_css)
136 },
137 };
138 }
139 };
140
141 let parsed_dom = match str_to_dom(parsed.as_ref(), component_map, None) {
142 Ok(o) => o,
143 Err(e) => {
144 return DomXml {
145 parsed_dom: {
146 let mut dom = Dom::create_body()
147 .with_children(vec![Dom::create_text(format!("{e}"))].into());
148 StyledDom::create(&mut dom, error_css)
149 },
150 };
151 }
152 };
153
154 DomXml { parsed_dom }
155}
156
157#[allow(clippy::needless_pass_by_value)]
164#[must_use] pub fn dom_from_parsed_xml(xml: Xml) -> Dom {
165 let component_map = ComponentMap::with_builtin();
166 match str_to_dom_unstyled(xml.root.as_ref(), &component_map) {
167 Ok(dom) => dom,
168 Err(e) => Dom::create_body().with_children(vec![Dom::create_text(format!("{e}"))].into()),
169 }
170}
171
172pub fn parse_xml_to_fast_dom(xml: &str) -> Result<azul_core::dom::FastDom, XmlError> {
187 let (fast_dom, _css) = parse_xml_to_fast_dom_with_css(xml)?;
188 Ok(fast_dom)
189}
190
191#[allow(clippy::cast_precision_loss)] pub fn parse_xml_to_styled_dom(xml: &str) -> Result<StyledDom, XmlError> {
197 static MEM_ENABLED: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
205 let mem_on = *MEM_ENABLED.get_or_init(azul_core::profile::memory_enabled);
206
207 let rss0 = if mem_on { peak_rss_bytes() } else { 0 };
208 let (mut fast_dom, css) = parse_xml_to_fast_dom_with_css(xml)?;
209 if mem_on {
210 let rss1 = peak_rss_bytes();
211 eprintln!(
212 "[XML] tokenize+fast_dom : +{:.2} MiB",
213 (rss1.saturating_sub(rss0)) as f64 / 1024.0 / 1024.0,
214 );
215 }
216
217 let rss1 = if mem_on { peak_rss_bytes() } else { 0 };
218 if !css.is_empty() {
220 let combined_css = Css::new(css.into_iter()
221 .flat_map(|c| c.rules.into_library_owned_vec())
222 .collect());
223 fast_dom.css = vec![azul_core::dom::CssWithNodeId {
224 node_id: 0, css: combined_css,
226 }].into();
227 }
228 if mem_on {
229 let rss2 = peak_rss_bytes();
230 eprintln!(
231 "[XML] css attach : +{:.2} MiB",
232 (rss2.saturating_sub(rss1)) as f64 / 1024.0 / 1024.0,
233 );
234 }
235
236 crate::probe::hint_purge_allocator();
241
242 let rss2 = if mem_on { peak_rss_bytes() } else { 0 };
243 let styled = StyledDom::create_from_fast_dom(fast_dom);
244
245 crate::probe::hint_purge_allocator();
250
251 if mem_on {
252 let rss3 = peak_rss_bytes();
253 eprintln!(
254 "[XML] create_from_fast_dom : +{:.2} MiB",
255 (rss3.saturating_sub(rss2)) as f64 / 1024.0 / 1024.0,
256 );
257 }
258
259 Ok(styled)
260}
261
262#[cfg(all(unix, feature = "probe"))]
267fn peak_rss_bytes() -> u64 {
268 let mut usage: libc::rusage = unsafe { std::mem::zeroed() };
269 if unsafe { libc::getrusage(libc::RUSAGE_SELF, &mut usage) } != 0 {
270 return 0;
271 }
272 let ru = usage.ru_maxrss as u64;
273 #[cfg(target_os = "macos")]
275 { ru }
276 #[cfg(not(target_os = "macos"))]
277 { ru.saturating_mul(1024) }
278}
279
280#[cfg(not(all(unix, feature = "probe")))]
281const fn peak_rss_bytes() -> u64 {
282 0
283}
284
285#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] #[allow(clippy::too_many_lines, clippy::cognitive_complexity)] fn parse_xml_to_fast_dom_with_css(xml: &str) -> Result<(azul_core::dom::FastDom, Vec<Css>), XmlError> {
289 use xmlparser::{ElementEnd::{Open, Empty, Close}, Token::{ElementStart, Attribute, ElementEnd, Text}, Tokenizer};
290 use azul_core::dom::{NodeData, NodeType, IdOrClass, TabIndex};
291 use azul_core::xml::CompactDomBuilder;
292
293 const ESTIMATED_BYTES_PER_NODE: usize = 20;
294
295 const VOID_ELEMENTS: &[&str] = &[
296 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta",
297 "param", "source", "track", "wbr",
298 ];
299
300 fn lowercase_into(dst: &mut String, src: &str) {
304 dst.clear();
305 if src.bytes().all(|b| !b.is_ascii_uppercase()) {
306 dst.push_str(src);
307 } else {
308 dst.reserve(src.len());
309 for b in src.bytes() {
310 dst.push(b.to_ascii_lowercase() as char);
311 }
312 }
313 }
314
315 let xml = xml.strip_prefix('\u{FEFF}').unwrap_or(xml);
317 let mut xml = xml.trim();
318
319 if xml.starts_with("<?") {
321 if let Some(pos) = xml.find("?>") {
322 xml = &xml[(pos + 2)..];
323 }
324 }
325
326 let mut xml = xml.trim();
328 if xml.len() > 9 && xml[..9].to_ascii_lowercase().starts_with("<!doctype") {
329 if let Some(pos) = xml.find('>') {
330 xml = &xml[(pos + 1)..];
331 }
332 } else if xml.starts_with("<!--") {
333 if let Some(end) = xml.find("-->") {
334 xml = &xml[(end + 3)..];
335 xml = xml.trim();
336 }
337 }
338
339 let tokenizer = Tokenizer::from_fragment(xml, 0..xml.len());
340
341 let estimated_nodes = xml.len() / ESTIMATED_BYTES_PER_NODE;
342 let mut builder = CompactDomBuilder::with_capacity(estimated_nodes);
343 let mut collected_css: Vec<Css> = Vec::new();
344 let mut inside_style_tag = false;
345 let mut style_text = String::new();
346 let mut head_depth: usize = 0;
349
350 let mut current_tag: String = String::new();
352 let mut current_attrs: Vec<(String, String)> = Vec::new();
353 let mut pending_open = false;
354
355 let css_key_map = azul_css::props::property::get_css_key_map();
357
358 let mut str_arena = azul_css::corety::StringArena::new();
364
365 let finalize_open = |
368 builder: &mut CompactDomBuilder,
369 str_arena: &mut azul_css::corety::StringArena,
370 tag: &str,
371 attrs: &[(String, String)],
372 css_key_map: &azul_css::props::property::CssKeyMap,
373 | {
374 let node_type = tag_to_node_type(tag);
375 let mut nd = NodeData::create_node(node_type);
376
377 let mut attr_vec: Vec<azul_core::dom::AttributeType> = Vec::new();
380 for (key, value) in attrs {
381 match key.as_str() {
382 "id" => {
383 for id in value.split_whitespace() {
384 attr_vec.push(azul_core::dom::AttributeType::Id(str_arena.intern(id)));
385 }
386 }
387 "class" => {
388 for class in value.split_whitespace() {
389 attr_vec.push(azul_core::dom::AttributeType::Class(str_arena.intern(class)));
390 }
391 }
392 "focusable" => {
393 if let Some(f) = parse_bool(value.as_str()) {
394 nd.set_tab_index(if f { TabIndex::Auto } else { TabIndex::NoKeyboardFocus });
395 }
396 }
397 "tabindex" => {
398 if let Ok(ti) = value.parse::<isize>() {
399 match ti {
400 0 => nd.set_tab_index(TabIndex::Auto),
401 i if i > 0 => nd.set_tab_index(TabIndex::OverrideInParent(i as u32)),
402 _ => nd.set_tab_index(TabIndex::NoKeyboardFocus),
403 }
404 }
405 }
406 "style" => {
407 let mut css_attrs = Vec::new();
408 for s in value.split(';') {
409 let mut s = s.split(':');
410 let Some(key) = s.next() else { continue };
411 let Some(val) = s.next() else { continue };
412 drop(azul_css::parser2::parse_css_declaration(
415 key.trim(), val.trim(),
416 azul_css::parser2::ErrorLocationRange::default(),
417 css_key_map, &mut Vec::new(), &mut css_attrs,
418 ));
419 }
420 let props = css_attrs.into_iter().filter_map(|s| {
421 use azul_css::css::CssDeclaration;
422 use azul_css::dynamic_selector::CssPropertyWithConditions;
423 match s {
424 CssDeclaration::Static(s) => Some(CssPropertyWithConditions::simple(s)),
425 CssDeclaration::Dynamic(_) => None,
426 }
427 }).collect::<Vec<_>>();
428 if !props.is_empty() {
429 nd.set_css_props(props.into());
430 }
431 }
432 "contenteditable" => {
433 if parse_bool(value.as_str()).unwrap_or(false) {
434 nd.set_contenteditable(true);
435 }
436 }
437 _ => {}
438 }
439 }
440 if !attr_vec.is_empty() {
441 nd.set_attributes(attr_vec.into());
442 }
443
444 builder.open_node(nd);
445 };
446
447 let mut last_was_void = false;
448 let mut tag_stack: Vec<String> = Vec::new(); for token in tokenizer {
451 let token = token.map_err(|e| XmlError::ParserError(translate_xmlparser_error(e)))?;
452 match token {
453 ElementStart { local, .. } => {
454 if pending_open {
456 let is_void = VOID_ELEMENTS.contains(¤t_tag.as_str());
457 if current_tag == "head" { head_depth += 1; }
458 if head_depth == 0 {
459 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
460 if is_void { builder.close_node(); }
461 }
462 if !is_void {
463 tag_stack.push(core::mem::take(&mut current_tag));
464 }
465 }
466
467 lowercase_into(&mut current_tag, local.as_str());
470 current_attrs.clear();
471 pending_open = true;
472 last_was_void = VOID_ELEMENTS.contains(¤t_tag.as_str());
473 }
474 Attribute { local, value, .. } => {
475 current_attrs.push((local.to_string(), decode_xml_entities(value.as_str()).into_owned()));
482 }
483 ElementEnd { end: Open, .. } => {
484 if pending_open {
485 let is_void = VOID_ELEMENTS.contains(¤t_tag.as_str());
486 if current_tag == "style" {
487 inside_style_tag = true;
488 style_text.clear();
489 }
490 if current_tag == "head" { head_depth += 1; }
491 if head_depth == 0 {
492 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
493 if is_void { builder.close_node(); }
494 }
495 if !is_void {
496 tag_stack.push(core::mem::take(&mut current_tag));
500 }
501 pending_open = false;
502 }
503 }
504 ElementEnd { end: Empty, .. } => {
505 if pending_open {
507 if current_tag == "head" { head_depth += 1; }
508 if head_depth == 0 {
509 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
510 builder.close_node();
511 }
512 if current_tag == "head" && head_depth > 0 { head_depth -= 1; }
513 pending_open = false;
514 }
515 }
516 ElementEnd { end: Close(_, close_value), .. } => {
517 if pending_open {
518 let is_void = VOID_ELEMENTS.contains(¤t_tag.as_str());
519 if current_tag == "head" { head_depth += 1; }
520 if head_depth == 0 {
521 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
522 if is_void { builder.close_node(); }
523 }
524 if !is_void {
525 tag_stack.push(core::mem::take(&mut current_tag));
526 }
527 pending_open = false;
528 }
529
530 let close_lower = close_value.as_str().to_ascii_lowercase();
531 let close_str = close_lower.as_str();
532 if VOID_ELEMENTS.contains(&close_str) {
533 continue;
534 }
535
536 if close_str == "style" && inside_style_tag {
538 if !style_text.is_empty() {
539 let parsed_css = Css::from_string(core::mem::take(&mut style_text).into());
540 collected_css.push(parsed_css);
541 }
542 inside_style_tag = false;
543 }
544
545 while let Some(top) = tag_stack.last() {
547 let is_match = top == close_str;
548 let was_head = top == "head";
549 let popped = tag_stack.pop().unwrap();
551 if popped == "head" && head_depth > 0 { head_depth -= 1; }
552 if head_depth == 0 && !was_head {
553 builder.close_node();
554 }
555 if is_match { break; }
556 }
557 }
558 Text { text } => {
559 if pending_open {
560 let is_void = VOID_ELEMENTS.contains(¤t_tag.as_str());
561 if current_tag == "style" {
562 inside_style_tag = true;
563 style_text.clear();
564 }
565 if current_tag == "head" { head_depth += 1; }
566 if head_depth == 0 {
567 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
568 if is_void { builder.close_node(); }
569 }
570 if !is_void {
571 tag_stack.push(current_tag.clone());
572 }
573 pending_open = false;
574 }
575
576 let text_str = text.as_str();
577 if !text_str.is_empty() {
578 if inside_style_tag {
579 style_text.push_str(text_str);
580 } else if head_depth == 0 {
581 let inside_body = tag_stack.iter().any(|t| t == "body");
584 if inside_body || !text_str.trim().is_empty() {
585 let decoded = decode_xml_entities(text_str);
586 builder.add_leaf(NodeData::create_text(str_arena.intern(&decoded)));
587 }
588 }
589 }
590 }
591 _ => {}
592 }
593 }
594
595 if pending_open {
597 finalize_open(&mut builder, &mut str_arena, ¤t_tag, ¤t_attrs, &css_key_map);
598 }
599 while tag_stack.pop().is_some() {
600 builder.close_node();
601 }
602
603 drop(str_arena);
606
607 Ok((builder.finish(), collected_css))
608}
609
610#[cfg(all(feature = "std", feature = "xml"))]
616pub fn domxml_from_file<I: AsRef<Path>>(
617 file_path: I,
618 component_map: &ComponentMap,
619) -> DomXml {
620 use std::fs;
621
622 let error_css = Css::empty();
623
624 let xml = match fs::read_to_string(file_path.as_ref()) {
625 Ok(xml) => xml,
626 Err(e) => {
627 return DomXml {
628 parsed_dom: {
629 let mut dom = Dom::create_body()
630 .with_children(
631 vec![Dom::create_text(format!(
632 "Error reading: \"{}\": {}",
633 file_path.as_ref().to_string_lossy(),
634 e
635 ))]
636 .into(),
637 );
638 StyledDom::create(&mut dom, error_css)
639 },
640 };
641 }
642 };
643
644 domxml_from_str(&xml, component_map)
645}
646
647#[cfg(feature = "xml")]
654#[allow(clippy::too_many_lines)] pub fn parse_xml_string(xml: &str) -> Result<Vec<XmlNodeChild>, XmlError> {
659 use xmlparser::{ElementEnd::{Empty, Close}, Token::{ElementStart, ElementEnd, Attribute, Text}, Tokenizer};
660
661 use self::XmlParseError::*;
662
663 const VOID_ELEMENTS: &[&str] = &[
666 "area", "base", "br", "col", "embed", "hr", "img", "input", "link", "meta", "param",
667 "source", "track", "wbr",
668 ];
669
670 const AUTO_CLOSE_RULES: &[(&str, &[&str])] = &[
673 ("li", &["li"]),
675 ("td", &["td", "th", "tr"]),
677 ("th", &["td", "th", "tr"]),
678 ("tr", &["tr"]),
679 (
681 "p",
682 &[
683 "address",
684 "article",
685 "aside",
686 "blockquote",
687 "div",
688 "dl",
689 "fieldset",
690 "footer",
691 "form",
692 "h1",
693 "h2",
694 "h3",
695 "h4",
696 "h5",
697 "h6",
698 "header",
699 "hr",
700 "main",
701 "nav",
702 "ol",
703 "p",
704 "pre",
705 "section",
706 "table",
707 "ul",
708 ],
709 ),
710 ("option", &["option", "optgroup"]),
712 ("optgroup", &["optgroup"]),
713 ("dd", &["dd", "dt"]),
715 ("dt", &["dd", "dt"]),
716 ];
717
718 let mut root_node = XmlNode::default();
719
720 let xml = xml.strip_prefix('\u{FEFF}').unwrap_or(xml);
722
723 let mut xml = xml.trim();
725 if xml.starts_with("<?") {
726 let pos = xml.find("?>").ok_or(XmlError::MalformedHierarchy(
727 MalformedHierarchyError {
728 expected: "<?xml".into(),
729 got: "?>".into(),
730 },
731 ))?;
732 xml = &xml[(pos + 2)..];
733 }
734
735 let mut xml = xml.trim();
737 if xml.len() > 9 && xml[..9].to_ascii_lowercase().starts_with("<!doctype") {
738 let pos = xml.find('>').ok_or(XmlError::MalformedHierarchy(
739 MalformedHierarchyError {
740 expected: "<!DOCTYPE".into(),
741 got: ">".into(),
742 },
743 ))?;
744 xml = &xml[(pos + 1)..];
745 } else if xml.starts_with("<!--") {
746 if let Some(end) = xml.find("-->") {
748 xml = &xml[(end + 3)..];
749 xml = xml.trim();
750 }
751 }
752
753 let tokenizer = Tokenizer::from_fragment(xml, 0..xml.len());
754
755 let mut node_stack: Vec<*mut XmlNode> = vec![&raw mut root_node];
761
762 let mut last_was_void = false;
764
765 for token in tokenizer {
766 let token = token.map_err(|e| XmlError::ParserError(translate_xmlparser_error(e)))?;
767 match token {
768 ElementStart { local, .. } => {
769 let tag_name = local.to_string();
770 let is_void_element = VOID_ELEMENTS.contains(&tag_name.as_str());
771
772 if last_was_void {
775 node_stack.pop();
776 last_was_void = false;
777 }
778
779 if node_stack.len() > 1 {
781 let current_element = unsafe { &*node_stack[node_stack.len() - 1] };
783 let current_tag = current_element.node_type.as_str();
784
785 for (element, closes_on) in AUTO_CLOSE_RULES {
787 if current_tag == *element && closes_on.contains(&tag_name.as_str()) {
788 node_stack.pop();
790 break;
791 }
792 }
793 }
794
795 if let Some(¤t_parent_ptr) = node_stack.last() {
797 let current_parent = unsafe { &mut *current_parent_ptr };
798
799 current_parent.children.push(XmlNodeChild::Element(XmlNode {
800 node_type: tag_name.into(),
801 attributes: StringPairVec::new().into(),
802 children: Vec::new().into(),
803 }));
804
805 let children_len = current_parent.children.len();
807 if let Some(XmlNodeChild::Element(ref mut new_child)) = current_parent.children.as_mut().get_mut(children_len - 1) {
808 node_stack.push(std::ptr::from_mut::<XmlNode>(new_child));
809 }
810
811 last_was_void = is_void_element;
812 }
813 }
814 ElementEnd { end: Empty, .. } => {
815 if node_stack.len() > 1 {
817 node_stack.pop();
818 }
819 last_was_void = false;
820 }
821 ElementEnd {
822 end: Close(_, close_value),
823 ..
824 } => {
825 if last_was_void {
827 node_stack.pop();
828 last_was_void = false;
829 }
830
831 let is_void_element = VOID_ELEMENTS.contains(&close_value.as_str());
833 if is_void_element {
834 continue;
836 }
837
838 let close_value_str = close_value.as_str();
841
842 let mut found_idx = None;
844 for i in (1..node_stack.len()).rev() {
845 let node = unsafe { &*node_stack[i] };
847 if node.node_type.as_str() == close_value_str {
848 found_idx = Some(i);
849 break;
850 }
851 }
852
853 if let Some(idx) = found_idx {
854 node_stack.truncate(idx);
856 }
857 last_was_void = false;
860 }
861 Attribute { local, value, .. } => {
862 if let Some(&last_ptr) = node_stack.last() {
864 let last = unsafe { &mut *last_ptr };
865 last.attributes.push(azul_core::window::AzStringPair {
868 key: local.to_string().into(),
869 value: AzString::from(&*decode_xml_entities(value.as_str())),
870 });
871 }
872 }
873 Text { text } => {
874 if last_was_void {
876 node_stack.pop();
877 last_was_void = false;
878 }
879
880 let text_str = text.as_str();
888
889 if !text_str.is_empty() {
890 if let Some(¤t_parent_ptr) = node_stack.last() {
892 let current_parent = unsafe { &mut *current_parent_ptr };
893 let decoded_text = decode_xml_entities(text_str);
895 current_parent
897 .children
898 .push(XmlNodeChild::Text(AzString::from(&*decoded_text)));
899 }
900 }
901 }
902 _ => {}
903 }
904 }
905
906 if last_was_void {
908 node_stack.pop();
909 }
910
911 Ok(root_node.children.into())
912}
913
914#[cfg(feature = "xml")]
915pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
919 Ok(Xml {
920 root: parse_xml_string(s)?.into(),
921 })
922}
923
924#[cfg(not(feature = "xml"))]
925pub fn parse_xml(s: &str) -> Result<Xml, XmlError> {
926 Err(XmlError::NoParserAvailable)
927}
928
929#[cfg(feature = "xml")]
932#[must_use] pub fn translate_roxmltree_expandedname(
933 e: roxmltree::ExpandedName<'_, '_>,
934) -> XmlQualifiedName {
935 let ns: Option<AzString> = e.namespace().map(|e| e.to_string().into());
936 XmlQualifiedName {
937 local_name: e.name().to_string().into(),
938 namespace: ns.into(),
939 }
940}
941
942#[cfg(feature = "xml")]
943fn translate_roxmltree_attribute(e: roxmltree::Attribute<'_, '_>) -> XmlQualifiedName {
944 XmlQualifiedName {
945 local_name: e.name().to_string().into(),
946 namespace: e.namespace().map(|e| e.to_string().into()).into(),
947 }
948}
949
950#[cfg(feature = "xml")]
951fn translate_xmlparser_streamerror(e: xmlparser::StreamError) -> XmlStreamError {
952 match e {
953 xmlparser::StreamError::UnexpectedEndOfStream => XmlStreamError::UnexpectedEndOfStream,
954 xmlparser::StreamError::InvalidName => XmlStreamError::InvalidName,
955 xmlparser::StreamError::InvalidReference => XmlStreamError::InvalidReference,
956 xmlparser::StreamError::InvalidExternalID => XmlStreamError::InvalidExternalID,
957 xmlparser::StreamError::InvalidCommentData => XmlStreamError::InvalidCommentData,
958 xmlparser::StreamError::InvalidCommentEnd => XmlStreamError::InvalidCommentEnd,
959 xmlparser::StreamError::InvalidCharacterData => XmlStreamError::InvalidCharacterData,
960 xmlparser::StreamError::NonXmlChar(c, tp) => XmlStreamError::NonXmlChar(NonXmlCharError {
961 ch: c.into(),
962 pos: translate_xmlparser_textpos(tp),
963 }),
964 xmlparser::StreamError::InvalidChar(a, b, tp) => {
965 XmlStreamError::InvalidChar(InvalidCharError {
966 expected: a,
967 got: b,
968 pos: translate_xmlparser_textpos(tp),
969 })
970 }
971 xmlparser::StreamError::InvalidCharMultiple(a, b, tp) => {
972 XmlStreamError::InvalidCharMultiple(InvalidCharMultipleError {
973 expected: a,
974 got: b.to_vec().into(),
975 pos: translate_xmlparser_textpos(tp),
976 })
977 }
978 xmlparser::StreamError::InvalidQuote(a, tp) => {
979 XmlStreamError::InvalidQuote(InvalidQuoteError {
980 got: a,
981 pos: translate_xmlparser_textpos(tp),
982 })
983 }
984 xmlparser::StreamError::InvalidSpace(a, tp) => {
985 XmlStreamError::InvalidSpace(InvalidSpaceError {
986 got: a,
987 pos: translate_xmlparser_textpos(tp),
988 })
989 }
990 xmlparser::StreamError::InvalidString(a, tp) => {
991 XmlStreamError::InvalidString(InvalidStringError {
992 got: a.to_string().into(),
993 pos: translate_xmlparser_textpos(tp),
994 })
995 }
996 }
997}
998
999#[cfg(feature = "xml")]
1000fn translate_xmlparser_error(e: xmlparser::Error) -> XmlParseError {
1001 match e {
1002 xmlparser::Error::InvalidDeclaration(se, tp) => {
1003 XmlParseError::InvalidDeclaration(XmlTextError {
1004 stream_error: translate_xmlparser_streamerror(se),
1005 pos: translate_xmlparser_textpos(tp),
1006 })
1007 }
1008 xmlparser::Error::InvalidComment(se, tp) => XmlParseError::InvalidComment(XmlTextError {
1009 stream_error: translate_xmlparser_streamerror(se),
1010 pos: translate_xmlparser_textpos(tp),
1011 }),
1012 xmlparser::Error::InvalidPI(se, tp) => XmlParseError::InvalidPI(XmlTextError {
1013 stream_error: translate_xmlparser_streamerror(se),
1014 pos: translate_xmlparser_textpos(tp),
1015 }),
1016 xmlparser::Error::InvalidDoctype(se, tp) => XmlParseError::InvalidDoctype(XmlTextError {
1017 stream_error: translate_xmlparser_streamerror(se),
1018 pos: translate_xmlparser_textpos(tp),
1019 }),
1020 xmlparser::Error::InvalidEntity(se, tp) => XmlParseError::InvalidEntity(XmlTextError {
1021 stream_error: translate_xmlparser_streamerror(se),
1022 pos: translate_xmlparser_textpos(tp),
1023 }),
1024 xmlparser::Error::InvalidElement(se, tp) => XmlParseError::InvalidElement(XmlTextError {
1025 stream_error: translate_xmlparser_streamerror(se),
1026 pos: translate_xmlparser_textpos(tp),
1027 }),
1028 xmlparser::Error::InvalidAttribute(se, tp) => {
1029 XmlParseError::InvalidAttribute(XmlTextError {
1030 stream_error: translate_xmlparser_streamerror(se),
1031 pos: translate_xmlparser_textpos(tp),
1032 })
1033 }
1034 xmlparser::Error::InvalidCdata(se, tp) => XmlParseError::InvalidCdata(XmlTextError {
1035 stream_error: translate_xmlparser_streamerror(se),
1036 pos: translate_xmlparser_textpos(tp),
1037 }),
1038 xmlparser::Error::InvalidCharData(se, tp) => XmlParseError::InvalidCharData(XmlTextError {
1039 stream_error: translate_xmlparser_streamerror(se),
1040 pos: translate_xmlparser_textpos(tp),
1041 }),
1042 xmlparser::Error::UnknownToken(tp) => {
1043 XmlParseError::UnknownToken(translate_xmlparser_textpos(tp))
1044 }
1045 }
1046}
1047
1048#[cfg(feature = "xml")]
1049#[must_use] pub fn translate_roxmltree_error(e: roxmltree::Error) -> XmlError {
1050 match e {
1051 roxmltree::Error::InvalidXmlPrefixUri(s) => {
1052 XmlError::InvalidXmlPrefixUri(translate_roxml_textpos(s))
1053 }
1054 roxmltree::Error::UnexpectedXmlUri(s) => {
1055 XmlError::UnexpectedXmlUri(translate_roxml_textpos(s))
1056 }
1057 roxmltree::Error::UnexpectedXmlnsUri(s) => {
1058 XmlError::UnexpectedXmlnsUri(translate_roxml_textpos(s))
1059 }
1060 roxmltree::Error::InvalidElementNamePrefix(s) => {
1061 XmlError::InvalidElementNamePrefix(translate_roxml_textpos(s))
1062 }
1063 roxmltree::Error::DuplicatedNamespace(s, tp) => {
1064 XmlError::DuplicatedNamespace(DuplicatedNamespaceError {
1065 ns: s.into(),
1066 pos: translate_roxml_textpos(tp),
1067 })
1068 }
1069 roxmltree::Error::UnknownNamespace(s, tp) => {
1070 XmlError::UnknownNamespace(UnknownNamespaceError {
1071 ns: s.into(),
1072 pos: translate_roxml_textpos(tp),
1073 })
1074 }
1075 roxmltree::Error::UnexpectedCloseTag(expected, actual, pos) => {
1076 XmlError::UnexpectedCloseTag(UnexpectedCloseTagError {
1077 expected: expected.into(),
1078 actual: actual.into(),
1079 pos: translate_roxml_textpos(pos),
1080 })
1081 }
1082 roxmltree::Error::UnexpectedEntityCloseTag(s) => {
1083 XmlError::UnexpectedEntityCloseTag(translate_roxml_textpos(s))
1084 }
1085 roxmltree::Error::UnknownEntityReference(s, tp) => {
1086 XmlError::UnknownEntityReference(UnknownEntityReferenceError {
1087 entity: s.into(),
1088 pos: translate_roxml_textpos(tp),
1089 })
1090 }
1091 roxmltree::Error::MalformedEntityReference(s) => {
1092 XmlError::MalformedEntityReference(translate_roxml_textpos(s))
1093 }
1094 roxmltree::Error::EntityReferenceLoop(s) => {
1095 XmlError::EntityReferenceLoop(translate_roxml_textpos(s))
1096 }
1097 roxmltree::Error::InvalidAttributeValue(s) => {
1098 XmlError::InvalidAttributeValue(translate_roxml_textpos(s))
1099 }
1100 roxmltree::Error::DuplicatedAttribute(s, tp) => {
1101 XmlError::DuplicatedAttribute(DuplicatedAttributeError {
1102 attribute: s.into(),
1103 pos: translate_roxml_textpos(tp),
1104 })
1105 }
1106 roxmltree::Error::NoRootNode => XmlError::NoRootNode,
1107 roxmltree::Error::DtdDetected => XmlError::DtdDetected,
1108 roxmltree::Error::UnclosedRootNode => XmlError::UnclosedRootNode,
1109 roxmltree::Error::UnexpectedDeclaration(tp) => {
1110 XmlError::UnexpectedDeclaration(translate_roxml_textpos(tp))
1111 }
1112 roxmltree::Error::NodesLimitReached => XmlError::NodesLimitReached,
1113 roxmltree::Error::AttributesLimitReached => XmlError::AttributesLimitReached,
1114 roxmltree::Error::NamespacesLimitReached => XmlError::NamespacesLimitReached,
1115 roxmltree::Error::InvalidName(tp) => XmlError::InvalidName(translate_roxml_textpos(tp)),
1116 roxmltree::Error::NonXmlChar(_, tp) => XmlError::NonXmlChar(translate_roxml_textpos(tp)),
1117 roxmltree::Error::InvalidChar(_, _, tp) => {
1118 XmlError::InvalidChar(translate_roxml_textpos(tp))
1119 }
1120 roxmltree::Error::InvalidChar2(_, _, tp) => {
1121 XmlError::InvalidChar2(translate_roxml_textpos(tp))
1122 }
1123 roxmltree::Error::InvalidString(_, tp) => {
1124 XmlError::InvalidString(translate_roxml_textpos(tp))
1125 }
1126 roxmltree::Error::InvalidExternalID(tp) => {
1127 XmlError::InvalidExternalID(translate_roxml_textpos(tp))
1128 }
1129 roxmltree::Error::InvalidComment(tp) => {
1130 XmlError::InvalidComment(translate_roxml_textpos(tp))
1131 }
1132 roxmltree::Error::InvalidCharacterData(tp) => {
1133 XmlError::InvalidCharacterData(translate_roxml_textpos(tp))
1134 }
1135 roxmltree::Error::UnknownToken(tp) => XmlError::UnknownToken(translate_roxml_textpos(tp)),
1136 roxmltree::Error::UnexpectedEndOfStream => XmlError::UnexpectedEndOfStream,
1137 roxmltree::Error::EntityResolver(tp, s) => {
1138 XmlError::UnknownEntityReference(UnknownEntityReferenceError {
1141 entity: s.into(),
1142 pos: translate_roxml_textpos(tp),
1143 })
1144 }
1145 }
1146}
1147
1148#[cfg(feature = "xml")]
1149#[inline]
1150const fn translate_xmlparser_textpos(o: xmlparser::TextPos) -> XmlTextPos {
1151 XmlTextPos {
1152 row: o.row,
1153 col: o.col,
1154 }
1155}
1156
1157#[cfg(feature = "xml")]
1158#[inline]
1159const fn translate_roxml_textpos(o: roxmltree::TextPos) -> XmlTextPos {
1160 XmlTextPos {
1161 row: o.row,
1162 col: o.col,
1163 }
1164}
1165
1166#[cfg(feature = "xml")]
1172pub trait DomXmlExt {
1173 fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom;
1184}
1185
1186#[cfg(feature = "xml")]
1187impl DomXmlExt for Dom {
1188 fn from_xml_string<S: AsRef<str>>(xml: S) -> StyledDom {
1189 let component_map = ComponentMap::with_builtin();
1190 let dom_xml = domxml_from_str(xml.as_ref(), &component_map);
1191 dom_xml.parsed_dom
1192 }
1193}