use std::collections::HashMap;
use roxmltree::Error;
use simplecss::Declaration;
use svgrtypes::FontShorthand;
use crate::svgtree::SvgAttributeValueRef;
use super::{
AId, Attribute, Document, EId, NestedNodeData, NestedNodeKind, NestedSvgDocument, NodeData,
NodeId, NodeKind, ShortRange, SvgAttributeValue,
};
pub const SVG_NS: &str = "http://www.w3.org/2000/svg";
const XLINK_NS: &str = "http://www.w3.org/1999/xlink";
const XML_NAMESPACE_NS: &str = "http://www.w3.org/XML/1998/namespace";
impl<'input> Document<'input> {
pub fn parse_tree(xml: &roxmltree::Document<'input>) -> Result<Document<'input>, Error> {
parse(xml)
}
pub(crate) fn append(&mut self, parent_id: NodeId, kind: NodeKind) -> NodeId {
self.append_with_static_hash(parent_id, kind, None)
}
pub(crate) fn append_with_static_hash(
&mut self,
parent_id: NodeId,
kind: NodeKind,
static_hash: Option<u64>,
) -> NodeId {
let new_child_id = NodeId::from(self.nodes.len());
self.nodes.push(NodeData {
parent: Some(parent_id),
next_sibling: None,
children: None,
kind,
static_hash,
});
let last_child_id = self.nodes[parent_id.get_usize()].children.map(|(_, id)| id);
if let Some(id) = last_child_id {
self.nodes[id.get_usize()].next_sibling = Some(new_child_id);
}
self.nodes[parent_id.get_usize()].children = Some(
if let Some((first_child_id, _)) = self.nodes[parent_id.get_usize()].children {
(first_child_id, new_child_id)
} else {
(new_child_id, new_child_id)
},
);
new_child_id
}
fn append_attribute(&mut self, name: AId, value: roxmltree::StringStorage<'input>) {
self.attrs.push(Attribute {
name,
value: SvgAttributeValue::StringStorage(value),
});
}
}
fn parse<'input>(xml: &roxmltree::Document<'input>) -> Result<Document<'input>, Error> {
let mut doc = Document {
nodes: Vec::new(),
attrs: Vec::new(),
links: HashMap::new(),
};
let mut id_map = HashMap::new();
for node in xml.descendants() {
if let Some(id) = node.attribute("id") {
if !id_map.contains_key(id) {
id_map.insert(id, node);
}
}
}
doc.nodes.push(NodeData {
parent: None,
next_sibling: None,
children: None,
kind: NodeKind::Root,
static_hash: None,
});
let style_sheet = resolve_css(xml);
parse_xml_node_children(
xml.root(),
xml.root(),
doc.root().id,
&style_sheet,
false,
0,
&mut doc,
&id_map,
)?;
match doc.root().first_element_child() {
Some(child) => {
if child.tag_name() != Some(EId::Svg) {
return Err(roxmltree::Error::NoRootNode);
}
}
None => return Err(roxmltree::Error::NoRootNode),
}
let mut links = HashMap::new();
for node in doc.descendants() {
if let Some(id) = node.attribute::<&str>(AId::Id) {
links.insert(id.to_string(), node.id);
}
}
doc.links = links;
fix_recursive_patterns(&mut doc);
fix_recursive_links(EId::ClipPath, AId::ClipPath, &mut doc);
fix_recursive_links(EId::Mask, AId::Mask, &mut doc);
fix_recursive_links(EId::Filter, AId::Filter, &mut doc);
fix_recursive_fe_image(&mut doc);
Ok(doc)
}
pub(crate) fn parse_tag_name(node: roxmltree::Node) -> Option<EId> {
if !node.is_element() {
return None;
}
if !matches!(node.tag_name().namespace(), None | Some(SVG_NS)) {
return None;
}
EId::from_str(node.tag_name().name())
}
fn parse_xml_node_children<'input>(
parent: roxmltree::Node<'_, 'input>,
origin: roxmltree::Node,
parent_id: NodeId,
style_sheet: &simplecss::StyleSheet,
ignore_ids: bool,
depth: u32,
doc: &mut Document<'input>,
id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
) -> Result<(), Error> {
for node in parent.children() {
parse_xml_node(
node,
origin,
parent_id,
style_sheet,
ignore_ids,
depth,
doc,
id_map,
)?;
}
Ok(())
}
fn parse_xml_node<'input>(
node: roxmltree::Node<'_, 'input>,
origin: roxmltree::Node,
parent_id: NodeId,
style_sheet: &simplecss::StyleSheet,
ignore_ids: bool,
depth: u32,
doc: &mut Document<'input>,
id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
) -> Result<(), Error> {
if depth > 1024 {
return Err(Error::NodesLimitReached);
}
let mut tag_name = match parse_tag_name(node) {
Some(id) => id,
None => return Ok(()),
};
if tag_name == EId::Style {
return Ok(());
}
if tag_name == EId::A {
tag_name = EId::G;
}
let node_id = parse_svg_element(node, parent_id, tag_name, style_sheet, ignore_ids, doc)?;
if tag_name == EId::Text {
super::text::parse_svg_text_element(node, node_id, style_sheet, doc)?;
} else if tag_name == EId::Use {
parse_svg_use_element(node, origin, node_id, style_sheet, depth + 1, doc, id_map)?;
} else {
parse_xml_node_children(
node,
origin,
node_id,
style_sheet,
ignore_ids,
depth + 1,
doc,
id_map,
)?;
}
Ok(())
}
pub(crate) fn parse_svg_element<'input>(
xml_node: roxmltree::Node<'_, 'input>,
parent_id: NodeId,
tag_name: EId,
style_sheet: &simplecss::StyleSheet,
ignore_ids: bool,
doc: &mut Document<'input>,
) -> Result<NodeId, Error> {
let attrs_start_idx = doc.attrs.len();
for attr in xml_node.attributes() {
match attr.namespace() {
None | Some(SVG_NS) | Some(XLINK_NS) | Some(XML_NAMESPACE_NS) => {}
_ => continue,
}
let aid = match AId::from_str(attr.name()) {
Some(v) => v,
None => continue,
};
if ignore_ids && aid == AId::Id {
continue;
}
if matches!(aid, AId::MixBlendMode | AId::Isolation | AId::FontKerning) {
continue;
}
append_attribute(parent_id, tag_name, aid, attr.value_storage().clone(), doc);
}
let mut insert_attribute = |aid, value: &str| {
let idx = doc.attrs[attrs_start_idx..]
.iter_mut()
.position(|a| a.name == aid);
let added = append_attribute(
parent_id,
tag_name,
aid,
roxmltree::StringStorage::new_owned(value),
doc,
);
if added {
if let Some(idx) = idx {
let last_idx = doc.attrs.len() - 1;
doc.attrs.swap(attrs_start_idx + idx, last_idx);
doc.attrs.pop();
}
}
};
let mut write_declaration = |declaration: &Declaration| {
if declaration.name == "marker" {
insert_attribute(AId::MarkerStart, declaration.value);
insert_attribute(AId::MarkerMid, declaration.value);
insert_attribute(AId::MarkerEnd, declaration.value);
} else if declaration.name == "font" {
if let Ok(shorthand) = FontShorthand::from_str(declaration.value) {
insert_attribute(AId::FontStyle, "normal");
insert_attribute(AId::FontVariant, "normal");
insert_attribute(AId::FontWeight, "normal");
insert_attribute(AId::FontStretch, "normal");
insert_attribute(AId::LineHeight, "normal");
insert_attribute(AId::FontSizeAdjust, "none");
insert_attribute(AId::FontKerning, "auto");
insert_attribute(AId::FontVariantCaps, "normal");
insert_attribute(AId::FontVariantLigatures, "normal");
insert_attribute(AId::FontVariantNumeric, "normal");
insert_attribute(AId::FontVariantEastAsian, "normal");
insert_attribute(AId::FontVariantPosition, "normal");
shorthand
.font_stretch
.map(|s| insert_attribute(AId::FontStretch, s));
shorthand
.font_weight
.map(|s| insert_attribute(AId::FontWeight, s));
shorthand
.font_variant
.map(|s| insert_attribute(AId::FontVariant, s));
shorthand
.font_style
.map(|s| insert_attribute(AId::FontStyle, s));
insert_attribute(AId::FontSize, shorthand.font_size);
insert_attribute(AId::FontFamily, shorthand.font_family);
} else {
log::warn!(
"Failed to parse {} value: '{}'",
AId::Font,
declaration.value
);
}
} else if let Some(aid) = AId::from_str(declaration.name) {
if aid.is_presentation() {
insert_attribute(aid, declaration.value);
}
}
};
for rule in &style_sheet.rules {
if rule.selector.matches(&XmlNode(xml_node)) {
for declaration in &rule.declarations {
write_declaration(declaration);
}
}
}
if let Some(value) = xml_node.attribute("style") {
for declaration in simplecss::DeclarationTokenizer::from(value) {
write_declaration(&declaration);
}
}
if doc.nodes.len() > 1_000_000 {
return Err(Error::NodesLimitReached);
}
let node_id = doc.append(
parent_id,
NodeKind::Element {
tag_name,
attributes: ShortRange::new(attrs_start_idx as u32, doc.attrs.len() as u32),
},
);
Ok(node_id)
}
pub(crate) fn append_attribute<'input>(
parent_id: NodeId,
tag_name: EId,
aid: AId,
value: roxmltree::StringStorage<'input>,
doc: &mut Document<'input>,
) -> bool {
match aid {
AId::Style |
AId::Class => return false,
_ => {}
}
if tag_name == EId::Tspan && aid == AId::Href {
return false;
}
if aid.allows_inherit_value() && &*value == "inherit" {
return resolve_inherit(parent_id, aid, doc);
}
doc.append_attribute(aid, value);
true
}
fn resolve_inherit(parent_id: NodeId, aid: AId, doc: &mut Document) -> bool {
if aid.is_inheritable() {
let node_id = doc
.get(parent_id)
.ancestors()
.find(|n| n.has_attribute(aid))
.map(|n| n.id);
if let Some(node_id) = node_id {
if let Some(attr) = doc
.get(node_id)
.attributes()
.iter()
.find(|a| a.name == aid)
.cloned()
{
doc.attrs.push(Attribute {
name: aid,
value: attr.value,
});
return true;
}
}
} else {
if let Some(attr) = doc
.get(parent_id)
.attributes()
.iter()
.find(|a| a.name == aid)
.cloned()
{
doc.attrs.push(Attribute {
name: aid,
value: attr.value,
});
return true;
}
}
let value = match aid {
AId::ImageRendering | AId::ShapeRendering | AId::TextRendering => "auto",
AId::ClipPath
| AId::Filter
| AId::MarkerEnd
| AId::MarkerMid
| AId::MarkerStart
| AId::Mask
| AId::Stroke
| AId::StrokeDasharray
| AId::TextDecoration => "none",
AId::FontStretch
| AId::FontStyle
| AId::FontVariant
| AId::FontWeight
| AId::LetterSpacing
| AId::WordSpacing => "normal",
AId::Fill | AId::FloodColor | AId::StopColor => "black",
AId::FillOpacity
| AId::FloodOpacity
| AId::Opacity
| AId::StopOpacity
| AId::StrokeOpacity => "1",
AId::ClipRule | AId::FillRule => "nonzero",
AId::BaselineShift => "baseline",
AId::ColorInterpolationFilters => "linearRGB",
AId::Direction => "ltr",
AId::Display => "inline",
AId::FontSize => "medium",
AId::Overflow => "visible",
AId::StrokeDashoffset => "0",
AId::StrokeLinecap => "butt",
AId::StrokeLinejoin => "miter",
AId::StrokeMiterlimit => "4",
AId::StrokeWidth => "1",
AId::TextAnchor => "start",
AId::Visibility => "visible",
AId::WritingMode => "lr-tb",
_ => return false,
};
doc.append_attribute(aid, roxmltree::StringStorage::Borrowed(value));
true
}
fn resolve_href<'a, 'input: 'a>(
node: roxmltree::Node<'a, 'input>,
id_map: &HashMap<&str, roxmltree::Node<'a, 'input>>,
) -> Option<roxmltree::Node<'a, 'input>> {
let link_value = node
.attribute((XLINK_NS, "href"))
.or_else(|| node.attribute("href"))?;
let link_id = svgrtypes::IRI::from_str(link_value).ok()?.0;
id_map.get(link_id).copied()
}
fn parse_svg_use_element<'input>(
node: roxmltree::Node<'_, 'input>,
origin: roxmltree::Node,
parent_id: NodeId,
style_sheet: &simplecss::StyleSheet,
depth: u32,
doc: &mut Document<'input>,
id_map: &HashMap<&str, roxmltree::Node<'_, 'input>>,
) -> Result<(), Error> {
let link = match resolve_href(node, id_map) {
Some(v) => v,
None => return Ok(()),
};
if link == node || link == origin {
log::warn!(
"Recursive 'use' detected. '{}' will be skipped.",
node.attribute((SVG_NS, "id")).unwrap_or_default()
);
return Ok(());
}
if parse_tag_name(link).is_none() {
return Ok(());
}
let mut is_recursive = false;
for link_child in link
.descendants()
.skip(1)
.filter(|n| n.has_tag_name((SVG_NS, "use")))
{
if let Some(link2) = resolve_href(link_child, id_map) {
if link2 == node || link2 == link {
is_recursive = true;
break;
}
}
}
if is_recursive {
log::warn!(
"Recursive 'use' detected. '{}' will be skipped.",
node.attribute((SVG_NS, "id")).unwrap_or_default()
);
return Ok(());
}
parse_xml_node(
link,
node,
parent_id,
style_sheet,
true,
depth + 1,
doc,
id_map,
)
}
fn resolve_css<'a>(xml: &'a roxmltree::Document<'a>) -> simplecss::StyleSheet<'a> {
let mut sheet = simplecss::StyleSheet::new();
for node in xml.descendants().filter(|n| n.has_tag_name("style")) {
match node.attribute("type") {
Some("text/css") => {}
Some(_) => continue,
None => {}
}
let text = match node.text() {
Some(v) => v,
None => continue,
};
sheet.parse_more(text);
}
sheet
}
struct XmlNode<'a, 'input: 'a>(roxmltree::Node<'a, 'input>);
impl simplecss::Element for XmlNode<'_, '_> {
fn parent_element(&self) -> Option<Self> {
self.0.parent_element().map(XmlNode)
}
fn prev_sibling_element(&self) -> Option<Self> {
self.0.prev_sibling_element().map(XmlNode)
}
fn has_local_name(&self, local_name: &str) -> bool {
self.0.tag_name().name() == local_name
}
fn attribute_matches(&self, local_name: &str, operator: simplecss::AttributeOperator) -> bool {
match self.0.attribute(local_name) {
Some(value) => operator.matches(value),
None => false,
}
}
fn pseudo_class_matches(&self, class: simplecss::PseudoClass) -> bool {
match class {
simplecss::PseudoClass::FirstChild => self.prev_sibling_element().is_none(),
_ => false, }
}
}
fn fix_recursive_patterns(doc: &mut Document) {
while let Some(node_id) = find_recursive_pattern(AId::Fill, doc) {
let idx = doc.get(node_id).attribute_id(AId::Fill).unwrap();
doc.attrs[idx].value =
SvgAttributeValue::StringStorage(roxmltree::StringStorage::Borrowed("none"));
}
while let Some(node_id) = find_recursive_pattern(AId::Stroke, doc) {
let idx = doc.get(node_id).attribute_id(AId::Stroke).unwrap();
doc.attrs[idx].value =
SvgAttributeValue::StringStorage(roxmltree::StringStorage::Borrowed("none"));
}
}
fn find_recursive_pattern(aid: AId, doc: &mut Document) -> Option<NodeId> {
for pattern_node in doc
.root()
.descendants()
.filter(|n| n.tag_name() == Some(EId::Pattern))
{
for node in pattern_node.descendants() {
let value = match node.attribute_value(aid) {
Some(SvgAttributeValueRef::Str(v)) => v,
_ => continue,
};
if let Ok(svgrtypes::Paint::FuncIRI(link_id, _)) = svgrtypes::Paint::from_str(value) {
if link_id == pattern_node.element_id() {
return Some(node.id);
} else {
if let Some(linked_node) = doc.element_by_id(link_id) {
for node2 in linked_node.descendants() {
let value2 = match node2.attribute_value(aid) {
Some(SvgAttributeValueRef::Str(v)) => v,
_ => continue,
};
if let Ok(svgrtypes::Paint::FuncIRI(link_id2, _)) =
svgrtypes::Paint::from_str(value2)
{
if link_id2 == pattern_node.element_id() {
return Some(node2.id);
}
}
}
}
}
}
}
}
None
}
fn fix_recursive_links(eid: EId, aid: AId, doc: &mut Document) {
while let Some(node_id) = find_recursive_link(eid, aid, doc) {
let idx = doc.get(node_id).attribute_id(aid).unwrap();
doc.attrs[idx].value =
SvgAttributeValue::StringStorage(roxmltree::StringStorage::Borrowed("none"));
}
}
fn find_recursive_link(eid: EId, aid: AId, doc: &Document) -> Option<NodeId> {
for node in doc
.root()
.descendants()
.filter(|n| n.tag_name() == Some(eid))
{
for child in node.descendants() {
if let Some(link) = child.node_attribute(aid) {
if link == node {
return Some(child.id);
} else {
for node2 in link.descendants() {
if let Some(link2) = node2.node_attribute(aid) {
if link2 == node {
return Some(node2.id);
}
}
}
}
}
}
}
None
}
fn fix_recursive_fe_image(doc: &mut Document) {
let mut ids = Vec::new();
for fe_node in doc
.root()
.descendants()
.filter(|n| n.tag_name() == Some(EId::FeImage))
{
if let Some(link) = fe_node.node_attribute(AId::Href) {
if let Some(filter_uri) = link.attribute::<&str>(AId::Filter) {
let filter_id = fe_node.parent().unwrap().element_id();
for func in svgrtypes::FilterValueListParser::from(filter_uri).flatten() {
if let svgrtypes::FilterValue::Url(url) = func {
if url == filter_id {
ids.push(link.id);
}
}
}
}
}
}
for id in ids {
let idx = doc.get(id).attribute_id(AId::Filter).unwrap();
doc.attrs[idx].value =
SvgAttributeValue::StringStorage(roxmltree::StringStorage::Borrowed("none"));
}
}
impl<'a> TryFrom<&'a NestedSvgDocument<'a>> for Document<'a> {
type Error = Error;
fn try_from(nested_doc: &'a NestedSvgDocument<'a>) -> Result<Self, Self::Error> {
let mut doc = Document {
nodes: Vec::new(),
attrs: Vec::new(),
links: HashMap::new(),
};
doc.nodes.push(NodeData {
parent: None,
next_sibling: None,
children: None,
kind: NodeKind::Root,
static_hash: None,
});
let parent_id = doc.root().id;
flatten_nested_svg_tree(&mut doc, nested_doc, parent_id, &nested_doc.nodes);
prepare_raw_svgtree(&mut doc)?;
Ok(doc)
}
}
fn prepare_raw_svgtree(doc: &mut Document) -> Result<(), Error> {
match doc.root().first_element_child() {
Some(child) => {
if child.tag_name() != Some(EId::Svg) {
return Err(roxmltree::Error::NoRootNode.into());
}
}
None => return Err(roxmltree::Error::NoRootNode.into()),
}
let mut links = HashMap::new();
for node in doc.descendants() {
if let Some(id) = node.attribute::<&str>(AId::Id) {
links.insert(id.to_string(), node.id);
}
}
doc.links = links;
fix_recursive_patterns(doc);
fix_recursive_links(EId::ClipPath, AId::ClipPath, doc);
fix_recursive_links(EId::Mask, AId::Mask, doc);
fix_recursive_links(EId::Filter, AId::Filter, doc);
fix_recursive_fe_image(doc);
Ok(())
}
fn flatten_nested_svg_tree<'a>(
doc: &mut Document<'a>,
nested_doc: &'a NestedSvgDocument<'a>,
parent_id: NodeId,
nodes: &'a [Option<super::NestedNodeData<'a>>],
) {
for node in nodes.iter().flatten() {
match &node.kind {
NestedNodeKind::Element { tag_name, .. } => {
append_nested_element(doc, nested_doc, node, node, parent_id, *tag_name, false);
}
NestedNodeKind::Text(value) => {
doc.append(parent_id, NodeKind::Text(value.to_string()));
}
NestedNodeKind::Root => {
let parent_id = doc.append(parent_id, NodeKind::Root);
flatten_nested_svg_tree(doc, nested_doc, parent_id, &node.children)
}
};
}
}
fn append_nested_element<'a>(
doc: &mut Document<'a>,
nested_doc: &'a NestedSvgDocument<'a>,
node: &'a NestedNodeData<'a>,
use_origin: &NestedNodeData<'a>,
parent_id: NodeId,
tag_name: EId,
ignore_ids: bool,
) {
let attrs_start_idx = doc.attrs.len() as u32;
for attr in node.attrs.iter() {
if let (AId::Style, Some(style)) = (&attr.name, &attr.value.as_ref().as_str()) {
for declaration in simplecss::DeclarationTokenizer::from(*style) {
if let Some(aid) = AId::from_str(declaration.name) {
if aid.is_presentation() {
doc.insert_attribute(
aid,
declaration.value,
attrs_start_idx as usize,
parent_id,
tag_name,
);
}
}
}
continue;
}
if attr.name == AId::Id && ignore_ids {
continue;
}
doc.attrs.push(attr.clone());
}
let attributes = ShortRange {
start: attrs_start_idx,
end: doc.attrs.len() as u32,
};
if tag_name == EId::Use {
let node_id = doc.append_with_static_hash(
parent_id,
NodeKind::Element {
tag_name,
attributes,
},
node.static_hash,
);
resolve_nested_use_element(doc, nested_doc, node_id, node, use_origin, attributes);
} else {
let node_id = doc.append_with_static_hash(
parent_id,
NodeKind::Element {
tag_name,
attributes,
},
node.static_hash,
);
flatten_nested_svg_tree(doc, nested_doc, node_id, &node.children)
}
}
fn resolve_linked_node<'a>(
href: &Attribute,
nested_doc: &'a NestedSvgDocument,
) -> Option<&'a NestedNodeData<'a>> {
let link_id = href.value.as_ref().as_str()?;
if !link_id.starts_with('#') {
return None;
}
let link_id = &link_id[1..];
let link_node = nested_doc
.nodes
.first()?
.as_ref()?
.find_recursively(&|node| {
node.attrs
.iter()
.any(|attr| attr.name == AId::Id && attr.value.as_ref().as_str() == Some(link_id))
})?;
Some(link_node)
}
fn resolve_nested_use_element<'a>(
doc: &mut Document<'a>,
nested_doc: &'a NestedSvgDocument<'a>,
parent_id: NodeId,
parent_node: &NestedNodeData<'a>,
origin: &NestedNodeData<'a>,
attributes_range: ShortRange,
) -> Option<()> {
let href = doc.attrs[attributes_range.to_urange()]
.iter()
.find(|attr| attr.name == AId::Href)?;
let link_node = resolve_linked_node(href, nested_doc)?;
if link_node == parent_node || link_node == origin {
return None;
};
let is_recursive = link_node
.find_recursively(&|node| match node.kind {
NestedNodeKind::Element { tag_name } if tag_name == EId::Use => {
let link2 = node.attrs.iter().find(|attr| attr.name == AId::Href);
if let Some(href2) = link2 {
if let Some(link_node2) = resolve_linked_node(href2, nested_doc) {
return link_node2 == parent_node || link_node2 == link_node;
}
}
false
}
_ => false,
})
.is_some();
if is_recursive {
return None;
}
match link_node.kind {
NestedNodeKind::Element { tag_name, .. } => {
append_nested_element(
doc,
nested_doc,
link_node,
parent_node,
parent_id,
tag_name,
true,
);
Some(())
}
_ => None,
}
}