1use std::collections::HashMap;
5
6use roxmltree::Error;
7use simplecss::Declaration;
8use svgtypes::FontShorthand;
9
10use super::{AId, Attribute, Document, EId, NodeData, NodeId, NodeKind, ShortRange};
11
12const SVG_NS: &str = "http://www.w3.org/2000/svg";
13const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
14const XML_NAMESPACE_NS: &str = "http://www.w3.org/XML/1998/namespace";
15
16impl<'input> Document<'input> {
17 pub fn parse_tree(
19 xml: &roxmltree::Document<'input>,
20 injected_stylesheet: Option<&'input str>,
21 ) -> Result<Document<'input>, Error> {
22 parse(xml, injected_stylesheet)
23 }
24
25 pub(crate) fn append(&mut self, parent_id: NodeId, kind: NodeKind) -> NodeId {
26 let new_child_id = NodeId::from(self.nodes.len());
27 self.nodes.push(NodeData {
28 parent: Some(parent_id),
29 next_sibling: None,
30 children: None,
31 kind,
32 });
33
34 let last_child_id = self.nodes[parent_id.get_usize()].children.map(|(_, id)| id);
35
36 if let Some(id) = last_child_id {
37 self.nodes[id.get_usize()].next_sibling = Some(new_child_id);
38 }
39
40 self.nodes[parent_id.get_usize()].children = Some(
41 if let Some((first_child_id, _)) = self.nodes[parent_id.get_usize()].children {
42 (first_child_id, new_child_id)
43 } else {
44 (new_child_id, new_child_id)
45 },
46 );
47
48 new_child_id
49 }
50
51 fn append_attribute(
52 &mut self,
53 name: AId,
54 value: roxmltree::StringStorage<'input>,
55 important: bool,
56 ) {
57 self.attrs.push(Attribute {
58 name,
59 value,
60 important,
61 });
62 }
63}
64
65fn parse<'input>(
66 xml: &roxmltree::Document<'input>,
67 injected_stylesheet: Option<&'input str>,
68) -> Result<Document<'input>, Error> {
69 let mut doc = Document {
70 nodes: Vec::new(),
71 attrs: Vec::new(),
72 links: HashMap::new(),
73 };
74
75 let mut id_map = HashMap::new();
77 for node in xml.descendants() {
78 if let Some(id) = node.attribute("id") {
79 if !id_map.contains_key(id) {
80 id_map.insert(id, node);
81 }
82 }
83 }
84
85 doc.nodes.push(NodeData {
87 parent: None,
88 next_sibling: None,
89 children: None,
90 kind: NodeKind::Root,
91 });
92
93 let style_sheet = resolve_css(xml, injected_stylesheet);
94
95 parse_xml_node_children(
96 xml.root(),
97 xml.root(),
98 doc.root().id,
99 &style_sheet,
100 false,
101 0,
102 &mut doc,
103 &id_map,
104 )?;
105
106 match doc.root().first_element_child() {
108 Some(child) => {
109 if child.tag_name() != Some(EId::Svg) {
110 return Err(roxmltree::Error::NoRootNode);
111 }
112 }
113 None => return Err(roxmltree::Error::NoRootNode),
114 }
115
116 let mut links = HashMap::new();
118 for node in doc.descendants() {
119 if let Some(id) = node.attribute::<&str>(AId::Id) {
120 links.insert(id.to_string(), node.id);
121 }
122 }
123 doc.links = links;
124
125 fix_recursive_patterns(&mut doc);
126 fix_recursive_links(EId::ClipPath, AId::ClipPath, &mut doc);
127 fix_recursive_links(EId::Mask, AId::Mask, &mut doc);
128 fix_recursive_links(EId::Filter, AId::Filter, &mut doc);
129 fix_recursive_fe_image(&mut doc);
130
131 Ok(doc)
132}
133
134pub(crate) fn parse_tag_name(node: roxmltree::Node) -> Option<EId> {
135 if !node.is_element() {
136 return None;
137 }
138
139 if !matches!(node.tag_name().namespace(), None | Some(SVG_NS)) {
140 return None;
141 }
142
143 EId::from_str(node.tag_name().name())
144}
145
146fn parse_xml_node_children<'input>(
147 parent: roxmltree::Node<'_, 'input>,
148 origin: roxmltree::Node,
149 parent_id: NodeId,
150 style_sheet: &simplecss::StyleSheet,
151 ignore_ids: bool,
152 depth: u32,
153 doc: &mut Document<'input>,
154 id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
155) -> Result<(), Error> {
156 for node in parent.children() {
157 parse_xml_node(
158 node,
159 origin,
160 parent_id,
161 style_sheet,
162 ignore_ids,
163 depth,
164 doc,
165 id_map,
166 )?;
167 }
168
169 Ok(())
170}
171
172fn parse_xml_node<'input>(
173 node: roxmltree::Node<'_, 'input>,
174 origin: roxmltree::Node,
175 parent_id: NodeId,
176 style_sheet: &simplecss::StyleSheet,
177 ignore_ids: bool,
178 depth: u32,
179 doc: &mut Document<'input>,
180 id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
181) -> Result<(), Error> {
182 if depth > 1024 {
183 return Err(Error::NodesLimitReached);
184 }
185
186 let mut tag_name = match parse_tag_name(node) {
187 Some(id) => id,
188 None => return Ok(()),
189 };
190
191 if tag_name == EId::Style {
192 return Ok(());
193 }
194
195 if tag_name == EId::A {
198 tag_name = EId::G;
199 }
200
201 let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, ignore_ids, doc)?;
202 if tag_name == EId::Text {
203 super::text::parse_svg_text_element(node, node_id, style_sheet, doc)?;
204 } else if tag_name == EId::Use {
205 parse_svg_use_element(node, origin, node_id, style_sheet, depth + 1, doc, id_map)?;
206 } else {
207 parse_xml_node_children(
208 node,
209 origin,
210 node_id,
211 style_sheet,
212 ignore_ids,
213 depth + 1,
214 doc,
215 id_map,
216 )?;
217 }
218
219 Ok(())
220}
221
222pub(crate) fn parse_svg_element<'input>(
223 xml_node: roxmltree::Node<'_, 'input>,
224 parent_id: NodeId,
225 tag_name: EId,
226 style_sheet: &simplecss::StyleSheet,
227 ignore_ids: bool,
228 doc: &mut Document<'input>,
229) -> Result<NodeId, Error> {
230 let attrs_start_idx = doc.attrs.len();
231 let mut href_idx: Option<usize> = None;
232
233 for attr in xml_node.attributes() {
235 match attr.namespace() {
236 None | Some(SVG_NS) | Some(XLINK_NS) | Some(XML_NAMESPACE_NS) => {}
237 _ => continue,
238 }
239
240 let aid = match AId::from_str(attr.name()) {
241 Some(v) => v,
242 None => continue,
243 };
244
245 if ignore_ids && aid == AId::Id {
248 continue;
249 }
250
251 if matches!(aid, AId::MixBlendMode | AId::Isolation | AId::FontKerning) {
253 continue;
254 } else if aid == AId::ImageRendering
255 && matches!(
256 attr.value(),
257 "smooth" | "high-quality" | "crisp-edges" | "pixelated"
258 )
259 {
260 continue;
261 }
262
263 if aid == AId::Href {
268 let is_unprefixed = attr.namespace().is_none();
269 let is_xlink = attr.namespace() == Some(XLINK_NS);
270 if !is_unprefixed && !is_xlink {
271 continue;
272 }
273
274 if let Some(idx) = href_idx {
275 if is_unprefixed {
278 doc.attrs[idx].value = attr.value_storage().clone();
279 }
280 continue;
281 }
282 }
283
284 let added = append_attribute(
285 parent_id,
286 tag_name,
287 aid,
288 attr.value_storage().clone(),
289 false,
290 doc,
291 );
292 if added && aid == AId::Href {
293 href_idx = Some(doc.attrs.len() - 1);
294 }
295 }
296
297 let mut insert_attribute = |aid, value: &str, important: bool| {
298 let idx = doc.attrs[attrs_start_idx..]
300 .iter_mut()
301 .position(|a| a.name == aid);
302
303 let added = append_attribute(
305 parent_id,
306 tag_name,
307 aid,
308 roxmltree::StringStorage::new_owned(value),
309 important,
310 doc,
311 );
312
313 if added {
315 if let Some(idx) = idx {
316 let last_idx = doc.attrs.len() - 1;
317 let existing_idx = attrs_start_idx + idx;
318
319 let has_precedence = !doc.attrs[existing_idx].important;
335
336 if has_precedence {
337 doc.attrs.swap(existing_idx, last_idx);
338 }
339
340 doc.attrs.pop();
342 }
343 }
344 };
345
346 let mut write_declaration = |declaration: &Declaration| {
347 let imp = declaration.important;
349 let val = declaration.value;
350
351 if declaration.name == "marker" {
352 insert_attribute(AId::MarkerStart, val, imp);
353 insert_attribute(AId::MarkerMid, val, imp);
354 insert_attribute(AId::MarkerEnd, val, imp);
355 } else if declaration.name == "font" {
356 if let Ok(shorthand) = FontShorthand::from_str(val) {
357 insert_attribute(AId::FontStyle, "normal", imp);
359 insert_attribute(AId::FontVariant, "normal", imp);
360 insert_attribute(AId::FontWeight, "normal", imp);
361 insert_attribute(AId::FontStretch, "normal", imp);
362 insert_attribute(AId::LineHeight, "normal", imp);
363 insert_attribute(AId::FontSizeAdjust, "none", imp);
364 insert_attribute(AId::FontKerning, "auto", imp);
365 insert_attribute(AId::FontVariantCaps, "normal", imp);
366 insert_attribute(AId::FontVariantLigatures, "normal", imp);
367 insert_attribute(AId::FontVariantNumeric, "normal", imp);
368 insert_attribute(AId::FontVariantEastAsian, "normal", imp);
369 insert_attribute(AId::FontVariantPosition, "normal", imp);
370
371 shorthand
373 .font_stretch
374 .map(|s| insert_attribute(AId::FontStretch, s, imp));
375 shorthand
376 .font_weight
377 .map(|s| insert_attribute(AId::FontWeight, s, imp));
378 shorthand
379 .font_variant
380 .map(|s| insert_attribute(AId::FontVariant, s, imp));
381 shorthand
382 .font_style
383 .map(|s| insert_attribute(AId::FontStyle, s, imp));
384 insert_attribute(AId::FontSize, shorthand.font_size, imp);
385 insert_attribute(AId::FontFamily, shorthand.font_family, imp);
386 } else {
387 log::warn!(
388 "Failed to parse {} value: '{}'",
389 AId::Font,
390 declaration.value
391 );
392 }
393 } else if let Some(aid) = AId::from_str(declaration.name) {
394 if aid.is_presentation() {
396 insert_attribute(aid, val, imp);
397 }
398 }
399 };
400
401 for rule in &style_sheet.rules {
403 if rule.selector.matches(&XmlNode(xml_node)) {
404 for declaration in &rule.declarations {
405 write_declaration(declaration);
406 }
407 }
408 }
409
410 if let Some(value) = xml_node.attribute("style") {
412 for declaration in simplecss::DeclarationTokenizer::from(value) {
413 write_declaration(&declaration);
414 }
415 }
416
417 if doc.nodes.len() > 1_000_000 {
418 return Err(Error::NodesLimitReached);
419 }
420
421 let node_id = doc.append(
422 parent_id,
423 NodeKind::Element {
424 tag_name,
425 attributes: ShortRange::new(attrs_start_idx as u32, doc.attrs.len() as u32),
426 },
427 );
428
429 Ok(node_id)
430}
431
432fn append_attribute<'input>(
433 parent_id: NodeId,
434 tag_name: EId,
435 aid: AId,
436 value: roxmltree::StringStorage<'input>,
437 important: bool,
438 doc: &mut Document<'input>,
439) -> bool {
440 match aid {
441 AId::Style |
443 AId::Class => return false,
445 _ => {}
446 }
447
448 if tag_name == EId::Tspan && aid == AId::Href {
451 return false;
452 }
453
454 if aid.allows_inherit_value() && &*value == "inherit" {
455 return resolve_inherit(parent_id, aid, doc);
456 }
457
458 doc.append_attribute(aid, value, important);
459 true
460}
461
462fn resolve_inherit(parent_id: NodeId, aid: AId, doc: &mut Document) -> bool {
463 if aid.is_inheritable() {
464 let node_id = doc
466 .get(parent_id)
467 .ancestors()
468 .find(|n| n.has_attribute(aid))
469 .map(|n| n.id);
470 if let Some(node_id) = node_id {
471 if let Some(attr) = doc
472 .get(node_id)
473 .attributes()
474 .iter()
475 .find(|a| a.name == aid)
476 .cloned()
477 {
478 doc.attrs.push(Attribute {
479 name: aid,
480 value: attr.value,
481 important: attr.important,
482 });
483
484 return true;
485 }
486 }
487 } else {
488 if let Some(attr) = doc
490 .get(parent_id)
491 .attributes()
492 .iter()
493 .find(|a| a.name == aid)
494 .cloned()
495 {
496 doc.attrs.push(Attribute {
497 name: aid,
498 value: attr.value,
499 important: attr.important,
500 });
501
502 return true;
503 }
504 }
505
506 let value = match aid {
508 AId::ImageRendering | AId::ShapeRendering | AId::TextRendering => "auto",
509
510 AId::ClipPath
511 | AId::Filter
512 | AId::MarkerEnd
513 | AId::MarkerMid
514 | AId::MarkerStart
515 | AId::Mask
516 | AId::Stroke
517 | AId::StrokeDasharray
518 | AId::TextDecoration => "none",
519
520 AId::FontStretch
521 | AId::FontStyle
522 | AId::FontVariant
523 | AId::FontWeight
524 | AId::LetterSpacing
525 | AId::WordSpacing => "normal",
526
527 AId::Fill | AId::FloodColor | AId::StopColor => "black",
528
529 AId::FillOpacity
530 | AId::FloodOpacity
531 | AId::Opacity
532 | AId::StopOpacity
533 | AId::StrokeOpacity => "1",
534
535 AId::ClipRule | AId::FillRule => "nonzero",
536
537 AId::BaselineShift => "baseline",
538 AId::ColorInterpolationFilters => "linearRGB",
539 AId::Direction => "ltr",
540 AId::Display => "inline",
541 AId::FontSize => "medium",
542 AId::Overflow => "visible",
543 AId::StrokeDashoffset => "0",
544 AId::StrokeLinecap => "butt",
545 AId::StrokeLinejoin => "miter",
546 AId::StrokeMiterlimit => "4",
547 AId::StrokeWidth => "1",
548 AId::TextAnchor => "start",
549 AId::Visibility => "visible",
550 AId::WritingMode => "lr-tb",
551 _ => return false,
552 };
553
554 doc.append_attribute(aid, roxmltree::StringStorage::Borrowed(value), false);
555 true
556}
557
558fn resolve_href<'a, 'input: 'a>(
559 node: roxmltree::Node<'a, 'input>,
560 id_map: &HashMap<&str, roxmltree::Node<'a, 'input>>,
561) -> Option<roxmltree::Node<'a, 'input>> {
562 let link_value = node
568 .attributes()
569 .find(|a| a.name() == "href" && a.namespace().is_none())
570 .or_else(|| {
571 node.attributes()
572 .find(|a| a.name() == "href" && a.namespace() == Some(XLINK_NS))
573 })
574 .map(|a| a.value())?;
575
576 let link_id = svgtypes::IRI::from_str(link_value).ok()?.0;
577
578 id_map.get(link_id).copied()
579}
580
581fn parse_svg_use_element<'input>(
582 node: roxmltree::Node<'_, 'input>,
583 origin: roxmltree::Node,
584 parent_id: NodeId,
585 style_sheet: &simplecss::StyleSheet,
586 depth: u32,
587 doc: &mut Document<'input>,
588 id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
589) -> Result<(), Error> {
590 let link = match resolve_href(node, id_map) {
591 Some(v) => v,
592 None => return Ok(()),
593 };
594
595 if link == node || link == origin {
596 log::warn!(
597 "Recursive 'use' detected. '{}' will be skipped.",
598 node.attribute((SVG_NS, "id")).unwrap_or_default()
599 );
600 return Ok(());
601 }
602
603 if parse_tag_name(link).is_none() {
605 return Ok(());
606 }
607
608 let mut is_recursive = false;
626 for link_child in link
627 .descendants()
628 .skip(1)
629 .filter(|n| n.has_tag_name((SVG_NS, "use")))
630 {
631 if let Some(link2) = resolve_href(link_child, id_map) {
632 if link2 == node || link2 == link {
633 is_recursive = true;
634 break;
635 }
636 }
637 }
638
639 if is_recursive {
640 log::warn!(
641 "Recursive 'use' detected. '{}' will be skipped.",
642 node.attribute((SVG_NS, "id")).unwrap_or_default()
643 );
644 return Ok(());
645 }
646
647 parse_xml_node(
648 link,
649 node,
650 parent_id,
651 style_sheet,
652 true,
653 depth + 1,
654 doc,
655 id_map,
656 )
657}
658
659fn resolve_css<'a>(
660 xml: &'a roxmltree::Document<'a>,
661 style_sheet: Option<&'a str>,
662) -> simplecss::StyleSheet<'a> {
663 let mut sheet = simplecss::StyleSheet::new();
664
665 if let Some(style_sheet) = style_sheet {
668 sheet.parse_more(style_sheet);
669 }
670
671 for node in xml.descendants().filter(|n| n.has_tag_name("style")) {
672 match node.attribute("type") {
673 Some("text/css") => {}
674 Some(_) => continue,
675 None => {}
676 }
677
678 let text = match node.text() {
679 Some(v) => v,
680 None => continue,
681 };
682
683 sheet.parse_more(text);
684 }
685
686 sheet
687}
688
689struct XmlNode<'a, 'input: 'a>(roxmltree::Node<'a, 'input>);
690
691impl simplecss::Element for XmlNode<'_, '_> {
692 fn parent_element(&self) -> Option<Self> {
693 self.0.parent_element().map(XmlNode)
694 }
695
696 fn prev_sibling_element(&self) -> Option<Self> {
697 self.0.prev_sibling_element().map(XmlNode)
698 }
699
700 fn has_local_name(&self, local_name: &str) -> bool {
701 self.0.tag_name().name() == local_name
702 }
703
704 fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool {
705 match self.0.attribute(local_name) {
706 Some(value) => operator.matches(value),
707 None => false,
708 }
709 }
710
711 fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool {
712 match class {
713 simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(),
714 _ => false, }
717 }
718}
719
720fn fix_recursive_patterns(doc: &mut Document) {
721 while let Some(node_id) = find_recursive_pattern(AId::Fill, doc) {
722 let idx = doc.get(node_id).attribute_id(AId::Fill).unwrap();
723 doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none");
724 }
725
726 while let Some(node_id) = find_recursive_pattern(AId::Stroke, doc) {
727 let idx = doc.get(node_id).attribute_id(AId::Stroke).unwrap();
728 doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none");
729 }
730}
731
732fn find_recursive_pattern(aid: AId, doc: &mut Document) -> Option<NodeId> {
733 for pattern_node in doc
734 .root()
735 .descendants()
736 .filter(|n| n.tag_name() == Some(EId::Pattern))
737 {
738 for node in pattern_node.descendants() {
739 let value = match node.attribute(aid) {
740 Some(v) => v,
741 None => continue,
742 };
743
744 if let Ok(svgtypes::Paint::FuncIRI(link_id, _)) = svgtypes::Paint::from_str(value) {
745 if link_id == pattern_node.element_id() {
746 return Some(node.id);
750 } else {
751 if let Some(linked_node) = doc.element_by_id(link_id) {
753 for node2 in linked_node.descendants() {
754 let value2 = match node2.attribute(aid) {
755 Some(v) => v,
756 None => continue,
757 };
758
759 if let Ok(svgtypes::Paint::FuncIRI(link_id2, _)) =
760 svgtypes::Paint::from_str(value2)
761 {
762 if link_id2 == pattern_node.element_id() {
763 return Some(node2.id);
764 }
765 }
766 }
767 }
768 }
769 }
770 }
771 }
772
773 None
774}
775
776fn fix_recursive_links(eid: EId, aid: AId, doc: &mut Document) {
777 while let Some(node_id) = find_recursive_link(eid, aid, doc) {
778 let idx = doc.get(node_id).attribute_id(aid).unwrap();
779 doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none");
780 }
781}
782
783fn find_recursive_link(eid: EId, aid: AId, doc: &Document) -> Option<NodeId> {
784 for node in doc
785 .root()
786 .descendants()
787 .filter(|n| n.tag_name() == Some(eid))
788 {
789 for child in node.descendants() {
790 if let Some(link) = child.node_attribute(aid) {
791 if link == node {
792 return Some(child.id);
796 } else {
797 for node2 in link.descendants() {
799 if let Some(link2) = node2.node_attribute(aid) {
800 if link2 == node {
801 return Some(node2.id);
802 }
803 }
804 }
805 }
806 }
807 }
808 }
809
810 None
811}
812
813fn fix_recursive_fe_image(doc: &mut Document) {
822 let mut ids = Vec::new();
823 for fe_node in doc
824 .root()
825 .descendants()
826 .filter(|n| n.tag_name() == Some(EId::FeImage))
827 {
828 if let Some(link) = fe_node.node_attribute(AId::Href) {
829 if let Some(filter_uri) = link.attribute::<&str>(AId::Filter) {
830 let filter_id = fe_node.parent().unwrap().element_id();
831 for func in svgtypes::FilterValueListParser::from(filter_uri).flatten() {
832 if let svgtypes::FilterValue::Url(url) = func {
833 if url == filter_id {
834 ids.push(link.id);
835 }
836 }
837 }
838 }
839 }
840 }
841
842 for id in ids {
843 let idx = doc.get(id).attribute_id(AId::Filter).unwrap();
844 doc.attrs[idx].value = roxmltree::StringStorage::Borrowed("none");
845 }
846}