use anyhow::{Context, Result};
use quick_xml::events::{BytesStart, Event};
use quick_xml::reader::Reader;
use quick_xml::writer::Writer;
pub fn strip_images(src: &str) -> Result<(String, bool)> {
let mut reader = Reader::from_str(src);
let mut writer = Writer::new(Vec::new());
let mut removed = false;
loop {
match reader.read_event().context("parsing SVG")? {
Event::Eof => break,
Event::Start(e) if is_image(&e) => {
reader
.read_to_end(e.name())
.context("unterminated <image> element")?;
removed = true;
}
Event::Empty(e) if is_image(&e) => removed = true,
other => writer.write_event(other)?,
}
}
Ok((String::from_utf8(writer.into_inner())?, removed))
}
fn is_image(e: &BytesStart) -> bool {
e.local_name().as_ref() == "image"
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_images_and_keeps_the_artwork() {
let src = r#"<svg xmlns="http://www.w3.org/2000/svg"><path d="M0 0h8v8z"/><image x="1" y="2" href="data:image/png;base64,iVBORw0KGgo="/><circle r="4"/></svg>"#;
let (out, removed) = strip_images(src).unwrap();
assert!(removed);
assert!(!out.contains("<image"));
assert!(!out.contains("base64"));
assert!(out.contains(r#"<path d="M0 0h8v8z"/>"#));
assert!(out.contains(r#"<circle r="4"/>"#));
}
#[test]
fn strips_a_nested_image_with_its_children() {
let src = r#"<svg><defs><pattern id="p"><image href="a.png"><title>t</title></image></pattern></defs><rect fill="url(#p)"/></svg>"#;
let (out, removed) = strip_images(src).unwrap();
assert!(removed);
assert_eq!(
out,
r#"<svg><defs><pattern id="p"></pattern></defs><rect fill="url(#p)"/></svg>"#
);
}
#[test]
fn strips_empty_and_prefixed_images() {
let (out, removed) = strip_images(r#"<svg><image href="a.png"/><rect/></svg>"#).unwrap();
assert!(removed);
assert_eq!(out, "<svg><rect/></svg>");
let (out, removed) =
strip_images(r#"<svg:svg><svg:image href="a.png"/><svg:rect/></svg:svg>"#).unwrap();
assert!(removed);
assert_eq!(out, "<svg:svg><svg:rect/></svg:svg>");
}
#[test]
fn path_artwork_is_left_alone() {
let src = r##"<svg xmlns="http://www.w3.org/2000/svg" image-rendering="optimizeSpeed"><path d="M0 0h8v8z" fill="#000"/></svg>"##;
let (out, removed) = strip_images(src).unwrap();
assert!(!removed);
assert_eq!(out, src);
}
#[test]
fn unterminated_image_is_an_error() {
assert!(strip_images(r#"<svg><image href="a.png">"#).is_err());
}
}