use std::fmt::Write;
use std::str;
use std::collections::HashMap;
use svgparser::{
xmlparser,
style,
svg,
AttributeValue as ParserAttributeValue,
FromSpan,
PaintFallback,
Stream,
StrSpan,
};
use error::Result;
use {
AttributeId,
AttributeValue,
Document,
ElementId,
ErrorKind,
Node,
NodeType,
ParseFromSpan,
ParseOptions,
ValueId,
};
use types::{
path,
Color,
Length,
LengthUnit,
Transform,
};
use super::{
css,
text,
};
pub struct NodeSpanData<'a> {
pub node: Node,
pub span: StrSpan<'a>,
}
pub struct LinkData<'a> {
attr_id: AttributeId,
iri: &'a str,
fallback: Option<PaintFallback>,
node: Node,
}
pub struct Links<'a> {
pub list: Vec<LinkData<'a>>,
pub elems_with_id: HashMap<&'a str, Node>,
}
impl<'a> Links<'a> {
fn append(
&mut self,
id: AttributeId,
iri: &'a str,
fallback: Option<PaintFallback>,
node: &Node,
) {
self.list.push(LinkData {
attr_id: id,
iri: iri,
fallback: fallback,
node: node.clone(),
});
}
}
pub type Entities<'a> = HashMap<&'a str, StrSpan<'a>>;
pub struct PostData<'a> {
pub css_list: Vec<StrSpan<'a>>,
pub links: Links<'a>,
pub entitis: Entities<'a>,
pub class_attrs: Vec<NodeSpanData<'a>>,
pub style_attrs: Vec<NodeSpanData<'a>>,
}
pub fn parse_svg(text: &str, opt: &ParseOptions) -> Result<Document> {
let mut doc = Document::new();
let mut parent = doc.root();
let mut tokens = svg::Tokenizer::from_str(text);
let mut post_data = PostData {
css_list: Vec::new(),
links: Links {
list: Vec::new(),
elems_with_id: HashMap::new(),
},
entitis: HashMap::new(),
class_attrs: Vec::new(),
style_attrs: Vec::new(),
};
let mut node: Option<Node> = None;
while let Some(token) = tokens.next() {
process_token(&mut doc, token?,
&mut node, &mut parent,
&mut post_data, opt)?
}
if !doc.root().has_children() {
return Err(ErrorKind::EmptyDocument.into());
}
match doc.children().svg().nth(0) {
Some((id, _)) => {
if id != ElementId::Svg {
return Err(ErrorKind::NoSvgElement.into());
}
}
None => {
return Err(ErrorKind::NoSvgElement.into());
}
}
doc.drain(|n| n.is_tag_name(ElementId::Style));
if !opt.parse_unknown_elements {
doc.drain(|n|
n.node_type() == NodeType::Element && n.tag_id().is_none()
);
}
if let Err(e) = css::resolve_css(&doc, &mut post_data, opt) {
if opt.skip_invalid_css {
warn!("{}.", e);
} else {
return Err(e.into());
}
}
for d in &mut post_data.style_attrs {
parse_style_attribute(&mut d.node, d.span, &mut post_data.links,
&post_data.entitis, opt)?;
}
resolve_links(&mut post_data.links, opt)?;
text::prepare_text(&mut doc);
Ok(doc)
}
fn process_token<'a>(
doc: &mut Document,
token: svg::Token<'a>,
node: &mut Option<Node>,
parent: &mut Node,
post_data: &mut PostData<'a>,
opt: &ParseOptions,
) -> Result<()> {
macro_rules! create_node {
($nodetype:expr, $buf:expr) => ({
let e = doc.create_node($nodetype, $buf);
*node = Some(e.clone());
parent.append(&e);
})
}
match token {
svg::Token::ElementStart(tag_name) => {
let curr_node = match tag_name {
svg::Name::Xml(name) => {
doc.create_element(name)
}
svg::Name::Svg(eid) => {
doc.create_element(eid)
}
};
*node = Some(curr_node.clone());
parent.append(&curr_node);
}
svg::Token::Attribute(name, value) => {
let curr_node = node.as_mut().unwrap();
match name {
svg::Name::Xml(name) => {
if opt.parse_unknown_attributes {
if curr_node.is_svg_element() {
parse_non_svg_attribute(curr_node, name, value, post_data);
} else {
curr_node.set_attribute((name, value.to_str()));
}
}
}
svg::Name::Svg(aid) => {
if curr_node.is_svg_element() {
parse_svg_attribute(curr_node, aid, value, post_data, opt)?;
}
}
}
}
svg::Token::ElementEnd(end) => {
match end {
svg::ElementEnd::Empty => {}
svg::ElementEnd::Close(_) => {
if *parent != doc.root {
*parent = parent.parent().unwrap();
}
}
svg::ElementEnd::Open => {
if let Some(ref n) = *node {
*parent = n.clone();
}
}
}
}
svg::Token::Text(s) => {
if is_inside_style_elem(parent) {
post_data.css_list.push(s);
} else {
create_node!(NodeType::Text, s.to_str());
}
}
svg::Token::Whitespaces(s) => {
if let Some(id) = parent.tag_id() {
match id {
ElementId::Text
| ElementId::Tspan
| ElementId::Tref => create_node!(NodeType::Text, s),
_ => {}
}
}
}
svg::Token::Comment(s) => {
if opt.parse_comments {
create_node!(NodeType::Comment, s)
}
}
svg::Token::Cdata(s) => {
if is_inside_style_elem(parent) {
post_data.css_list.push(s);
} else {
create_node!(NodeType::Cdata, s.to_str());
}
}
svg::Token::Declaration(version, encoding, sa) => {
if opt.parse_declarations {
let mut s = format!("version=\"{}\"", version);
if let Some(encoding) = encoding {
write!(&mut s, " encoding=\"{}\"", encoding).unwrap();
}
if let Some(sa) = sa {
write!(&mut s, " standalone=\"{}\"", sa).unwrap();
}
create_node!(NodeType::Declaration, &s);
}
}
svg::Token::EntityDeclaration(name, value) => {
if value.to_str().trim().starts_with("<") {
let s = Stream::from_span(value);
return Err(ErrorKind::UnsupportedEntity(s.gen_error_pos()).into());
}
post_data.entitis.insert(name, value);
}
svg::Token::ProcessingInstruction(_, _) => {
}
}
if parent.node_type() == NodeType::Root {
if let Some((id, _)) = doc.children().svg().nth(0) {
if id != ElementId::Svg {
return Err(ErrorKind::NoSvgElement.into());
}
}
}
Ok(())
}
fn parse_svg_attribute<'a>(
node: &mut Node,
id: AttributeId,
value: StrSpan<'a>,
post_data: &mut PostData<'a>,
opt: &ParseOptions,
) -> Result<()> {
match id {
AttributeId::Id => {
node.set_id(value.to_str());
post_data.links.elems_with_id.insert(value.to_str(), node.clone());
}
AttributeId::Style => {
post_data.style_attrs.push(NodeSpanData {
node: node.clone(),
span: value,
})
}
AttributeId::Transform
| AttributeId::GradientTransform
| AttributeId::PatternTransform => {
let ts = Transform::from_span(value)?;
if !ts.is_default() {
node.set_attribute((id, AttributeValue::Transform(ts)));
}
}
AttributeId::D => {
let p = path::Path::from_span(value)?;
node.set_attribute((AttributeId::D, AttributeValue::Path(p)));
}
AttributeId::Class => {
let mut s = Stream::from_span(value);
while !s.at_end() {
s.skip_spaces();
let class = s.consume_bytes(|s2, _| !s2.starts_with_space());
post_data.class_attrs.push(NodeSpanData {
node: node.clone(),
span: class,
});
s.skip_spaces();
}
}
_ => {
parse_svg_attribute_value(node, id, value, &mut post_data.links,
&post_data.entitis, opt)?;
}
}
Ok(())
}
pub fn parse_svg_attribute_value<'a>(
node: &mut Node,
id: AttributeId,
span: StrSpan<'a>,
links: &mut Links<'a>,
entitis: &Entities<'a>,
opt: &ParseOptions,
) -> Result<()> {
let tag_id = node.tag_id().unwrap();
let av = match ParserAttributeValue::from_span(tag_id, id, span) {
Ok(av) => av,
Err(e) => {
return if opt.skip_invalid_attributes {
warn!("Attribute '{}' has an invalid value: '{}'.", id, span);
Ok(())
} else {
Err(e.into())
};
}
};
let val = match av {
ParserAttributeValue::String(v) => {
Some(AttributeValue::String(v.to_string()))
}
ParserAttributeValue::IRI(link) | ParserAttributeValue::FuncIRI(link) => {
links.append(id, link, None, node);
None
}
ParserAttributeValue::FuncIRIWithFallback(link, fallback) => {
links.append(id, link, Some(fallback), node);
None
}
ParserAttributeValue::Number(v) => {
Some(AttributeValue::Number(v))
}
ParserAttributeValue::NumberList(list) => {
let mut vec = Vec::new();
for number in list {
match number {
Ok(n) => vec.push(n),
Err(e) => return Err(e.into()),
}
}
if !vec.is_empty() {
Some(AttributeValue::NumberList(vec))
} else {
None
}
}
ParserAttributeValue::Length(v) => {
Some(AttributeValue::Length(Length::new(v.num, prepare_length_unit(v.unit, opt))))
}
ParserAttributeValue::LengthList(list) => {
let mut vec = Vec::new();
for number in list {
match number {
Ok(n) => vec.push(Length::new(n.num, prepare_length_unit(n.unit, opt))),
Err(e) => return Err(e.into()),
}
}
if !vec.is_empty() {
Some(AttributeValue::LengthList(vec))
} else {
None
}
}
ParserAttributeValue::Color(v) => {
Some(AttributeValue::Color(Color::new(v.red, v.green, v.blue)))
}
ParserAttributeValue::PredefValue(v) => {
Some(AttributeValue::PredefValue(v))
}
ParserAttributeValue::EntityRef(link) => {
match entitis.get(link) {
Some(link_value) => {
parse_svg_attribute_value(node, id, *link_value, links, entitis, opt)?;
None
}
None => {
let s = format!("&{};", link);
if link.as_bytes()[0] != b'#' {
warn!("Unresolved ENTITY reference: '{}'.", s);
}
Some(AttributeValue::String(s))
}
}
}
};
if let Some(v) = val {
node.set_attribute((id, v));
}
Ok(())
}
fn parse_non_svg_attribute<'a>(
node: &mut Node,
name: &str,
value: StrSpan<'a>,
post_data: &PostData<'a>,
) {
let mut stream = Stream::from_span(value);
let new_value = if stream.is_curr_byte_eq(b'&') {
if let Ok(xmlparser::Reference::EntityRef(link)) = stream.consume_reference() {
match post_data.entitis.get(link.to_str()) {
Some(link_value) => Some(*link_value),
None => {
warn!("Could not resolve ENTITY: '{}'.", link);
None
}
}
} else {
None
}
} else {
Some(stream.span())
};
if let Some(val) = new_value {
node.set_attribute((name, val.to_str()));
}
}
fn prepare_length_unit(unit: LengthUnit, opt: &ParseOptions) -> LengthUnit {
if !opt.parse_px_unit && unit == LengthUnit::Px {
return LengthUnit::None;
}
unit
}
fn parse_style_attribute<'a>(
node: &mut Node,
span: StrSpan<'a>,
links: &mut Links<'a>,
entitis: &Entities<'a>,
opt: &ParseOptions,
) -> Result<()> {
for token in style::Tokenizer::from_span(span) {
match token? {
style::Token::XmlAttribute(name, value) => {
if opt.parse_unknown_attributes {
node.set_attribute((name, value));
}
}
style::Token::SvgAttribute(id, value) => {
parse_svg_attribute_value(node, id, value, links, entitis, opt)?;
}
style::Token::EntityRef(name) => {
if let Some(value) = entitis.get(name) {
parse_style_attribute(node, *value, links, entitis, opt)?;
}
}
}
}
Ok(())
}
fn resolve_links(links: &mut Links, opt: &ParseOptions) -> Result<()> {
for mut d in &mut links.list {
match links.elems_with_id.get(d.iri) {
Some(node) => {
match d.fallback {
Some(_) => {
if opt.skip_paint_fallback {
warn!("Paint fallback is not supported.");
d.node.set_attribute_checked((d.attr_id, node.clone()))?;
} else {
let s = d.iri.to_string();
return Err(ErrorKind::UnsupportedPaintFallback(s).into());
}
}
None => d.node.set_attribute_checked((d.attr_id, node.clone()))?,
}
}
None => {
resolve_fallback(&mut d)?;
}
}
}
Ok(())
}
fn resolve_fallback(d: &mut LinkData) -> Result<()> {
match d.fallback {
Some(fallback) => {
match fallback {
PaintFallback::PredefValue(v) => {
d.node.set_attribute((d.attr_id, v));
}
PaintFallback::Color(c) => {
d.node.set_attribute((d.attr_id, Color::new(c.red, c.green, c.blue)));
}
}
}
None => {
match d.attr_id {
AttributeId::Filter => {
if d.node.is_tag_name(ElementId::Use) {
let s = d.iri.to_string();
return Err(ErrorKind::BrokenFuncIri(s).into());
}
let flag = d.node.parents().any(|n| {
n.is_tag_name(ElementId::Mask)
|| n.is_tag_name(ElementId::ClipPath)
|| n.is_tag_name(ElementId::Marker)
});
if flag {
warn!("Could not resolve IRI reference: {}.", d.iri);
} else {
warn!("Unresolved 'filter' IRI reference: {}. \
Marking the element as invisible.",
d.iri);
d.node.set_attribute((AttributeId::Visibility, ValueId::Hidden));
}
}
AttributeId::Fill => {
warn!("Could not resolve the 'fill' IRI reference: {}. \
Fallback to 'none'.",
d.iri);
d.node.set_attribute((AttributeId::Fill, ValueId::None));
}
_ => {
warn!("Could not resolve IRI reference: {}.", d.iri);
}
}
}
}
Ok(())
}
fn is_inside_style_elem(node: &Node) -> bool {
if node.is_tag_name(ElementId::Style) {
let attrs = node.attributes();
let av = attrs.get_value(AttributeId::Type);
if let Some(&AttributeValue::String(ref t)) = av {
if t != "text/css" {
return false;
}
}
return true;
}
false
}