From c262c0d85929c20048c8bac75cc2308b333c44bc Mon Sep 17 00:00:00 2001 From: Noah Baldwin Date: Fri, 29 Nov 2024 08:52:55 +1100 Subject: [PATCH] feat(oxvg_optimiser): #25 remove editor ns data --- crates/oxvg_ast/src/attribute.rs | 6 + crates/oxvg_collections/src/collections.rs | 24 ++++ crates/oxvg_optimiser/src/jobs/mod.rs | 1 + .../src/jobs/remove_editors_ns_data.rs | 120 ++++++++++++++++++ ...s_ns_data__remove_editors_ns_data.snap.new | 11 ++ 5 files changed, 162 insertions(+) create mode 100644 crates/oxvg_optimiser/src/jobs/remove_editors_ns_data.rs create mode 100644 crates/oxvg_optimiser/src/jobs/snapshots/oxvg_optimiser__jobs__remove_editors_ns_data__remove_editors_ns_data.snap.new diff --git a/crates/oxvg_ast/src/attribute.rs b/crates/oxvg_ast/src/attribute.rs index 85def34..e32bbdb 100644 --- a/crates/oxvg_ast/src/attribute.rs +++ b/crates/oxvg_ast/src/attribute.rs @@ -154,6 +154,12 @@ pub trait Attributes<'a>: Debug + Clone { fn sort(&self, order: &[String], xmlns_front: bool); + /// Retains the attributes specified by the predicate. + /// + /// In other words, removes all attribute where the closure returns false. + /// + /// The arguments provided to the closure are the optional prefix, followed by the local-name, + /// and finally the value. fn retain(&self, f: F) where F: FnMut(&Self::Attribute) -> bool; diff --git a/crates/oxvg_collections/src/collections.rs b/crates/oxvg_collections/src/collections.rs index fb4ec5f..d3198f6 100644 --- a/crates/oxvg_collections/src/collections.rs +++ b/crates/oxvg_collections/src/collections.rs @@ -573,3 +573,27 @@ pub static PSEUDO_TREE_STRUCTURAL: phf::Set<&'static str> = phf_set!( pub static PSEUDO_USER_ACTION: phf::Set<&'static str> = phf_set!("active", "focus-visible", "focus-within", "focus", "hover"); pub static PSEUDO_FUNCTIONAL: phf::Set<&'static str> = phf_set!("is", "not", "where", "has"); +pub static EDITOR_NAMESPACES: phf::Set<&'static str> = phf_set!( + "http://creativecommons.org/ns#", + "http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd", + "http://ns.adobe.com/AdobeIllustrator/10.0/", + "http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/", + "http://ns.adobe.com/Extensibility/1.0/", + "http://ns.adobe.com/Flows/1.0/", + "http://ns.adobe.com/GenericCustomNamespace/1.0/", + "http://ns.adobe.com/Graphs/1.0/", + "http://ns.adobe.com/ImageReplacement/1.0/", + "http://ns.adobe.com/SaveForWeb/1.0/", + "http://ns.adobe.com/Variables/1.0/", + "http://ns.adobe.com/XPath/1.0/", + "http://purl.org/dc/elements/1.1/", + "http://schemas.microsoft.com/visio/2003/SVGExtensions/", + "http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd", + "http://taptrix.com/vectorillustrator/svg_extensions", + "http://www.bohemiancoding.com/sketch/ns", + "http://www.figma.com/figma/ns", + "http://www.inkscape.org/namespaces/inkscape", + "http://www.serif.com/", + "http://www.vector.evaxdesign.sk", + "http://www.w3.org/1999/02/22-rdf-syntax-ns#", +); diff --git a/crates/oxvg_optimiser/src/jobs/mod.rs b/crates/oxvg_optimiser/src/jobs/mod.rs index 90ea45a..f130bc0 100644 --- a/crates/oxvg_optimiser/src/jobs/mod.rs +++ b/crates/oxvg_optimiser/src/jobs/mod.rs @@ -61,6 +61,7 @@ jobs! { remove_xml_proc_inst: RemoveXMLProcInst (is_default: true), remove_comments: RemoveComments (is_default: true), remove_metadata: RemoveMetadata (is_default: true), + remove_editors_ns_data: RemoveEditorsNSData (is_default: true), cleanup_attributes: CleanupAttributes (is_default: true), merge_styles: MergeStyles (is_default: true), inline_styles: InlineStyles (is_default: true), diff --git a/crates/oxvg_optimiser/src/jobs/remove_editors_ns_data.rs b/crates/oxvg_optimiser/src/jobs/remove_editors_ns_data.rs new file mode 100644 index 0000000..7d5f0e2 --- /dev/null +++ b/crates/oxvg_optimiser/src/jobs/remove_editors_ns_data.rs @@ -0,0 +1,120 @@ +use std::{cell::RefCell, collections::BTreeSet}; + +use oxvg_ast::{ + atom::Atom, + attribute::{Attr, Attributes}, + element::Element, + visitor::{Context, Visitor}, +}; +use oxvg_collections::collections::EDITOR_NAMESPACES; +use serde::Deserialize; + +#[derive(Deserialize, Clone, Default)] +#[serde(rename_all = "camelCase")] +pub struct RemoveEditorsNSData { + additional_namespaces: Option>, + #[serde(skip_deserializing)] + prefixes: RefCell>, +} + +impl Visitor for RemoveEditorsNSData { + type Error = String; + + fn document( + &mut self, + document: &mut E, + _context: &Context<'_, '_, E>, + ) -> Result<(), Self::Error> { + document.for_each_element_child(|ref e| { + self.collect_svg_namespace(e); + self.remove_editor_attributes(e); + self.remove_editor_element(e); + }); + Ok(()) + } +} + +impl RemoveEditorsNSData { + fn collect_svg_namespace(&self, element: &E) { + if element.local_name().as_ref() != "svg" { + return; + } + + let mut prefixes = self.prefixes.borrow_mut(); + for xmlns in element + .attributes() + .into_iter() + .filter(|a| a.prefix().as_ref().is_some_and(|p| p.as_ref() == "xmlns")) + { + let value = xmlns.value(); + let value = value.as_str(); + if !EDITOR_NAMESPACES.contains(value) + && !self + .additional_namespaces + .as_ref() + .is_some_and(|n| n.contains(value)) + { + continue; + } + + let name = xmlns.local_name(); + log::debug!("Adding {name} to prefixes"); + prefixes.insert(name.to_string()); + } + } + + fn remove_editor_attributes(&self, element: &E) { + let prefixes = self.prefixes.borrow(); + element.attributes().retain(|attr| { + let Some(prefix) = attr.prefix() else { + return true; + }; + + !prefixes.contains(prefix.as_ref()) + }); + } + + fn remove_editor_element(&self, element: &E) { + let Some(prefix) = element.prefix() else { + return; + }; + + if self.prefixes.borrow().contains(prefix.as_ref()) { + log::debug!("Removing element with prefix: {prefix}"); + element.remove(); + } + } +} + +#[test] +fn remove_editors_ns_data() -> anyhow::Result<()> { + use crate::test_config; + + insta::assert_snapshot!(test_config( + r#"{ "removeEditorsNsData": {} }"#, + Some( + r#" + + ... + + + +"# + ), + )?); + + insta::assert_snapshot!(test_config( + r#"{ "removeEditorsNsData": {} }"#, + Some( + r#" + + ... + + + +"# + ), + )?); + + Ok(()) +} diff --git a/crates/oxvg_optimiser/src/jobs/snapshots/oxvg_optimiser__jobs__remove_editors_ns_data__remove_editors_ns_data.snap.new b/crates/oxvg_optimiser/src/jobs/snapshots/oxvg_optimiser__jobs__remove_editors_ns_data__remove_editors_ns_data.snap.new new file mode 100644 index 0000000..1a2df49 --- /dev/null +++ b/crates/oxvg_optimiser/src/jobs/snapshots/oxvg_optimiser__jobs__remove_editors_ns_data__remove_editors_ns_data.snap.new @@ -0,0 +1,11 @@ +--- +source: crates/oxvg_optimiser/src/jobs/remove_editors_ns_data.rs +assertion_line: 93 +expression: "test_config(r#\"{ \"removeEditorsNsData\": {} }\"#,\nSome(r#\"\n \n ...\n \n\n \n\"#),)?" +--- + + + ... + + +