merman_render/svg/pipeline/builtin/
css_override.rs1use crate::Result;
2use regex::Regex;
3use std::borrow::Cow;
4use std::sync::OnceLock;
5
6use crate::svg::pipeline::{SvgPostprocessContext, SvgPostprocessor};
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
9pub enum CssOverridePolicy {
10 #[default]
11 Preserve,
12 StripExistingImportant,
13}
14
15#[derive(Debug, Clone, Copy, Default)]
16pub struct CssOverridePostprocessor {
17 policy: CssOverridePolicy,
18}
19
20impl CssOverridePostprocessor {
21 pub fn new(policy: CssOverridePolicy) -> Self {
22 Self { policy }
23 }
24
25 pub fn strip_existing_important() -> Self {
26 Self::new(CssOverridePolicy::StripExistingImportant)
27 }
28
29 pub fn policy(&self) -> CssOverridePolicy {
30 self.policy
31 }
32}
33
34impl SvgPostprocessor for CssOverridePostprocessor {
35 fn name(&self) -> &'static str {
36 "css-override"
37 }
38
39 fn process<'a>(
40 &self,
41 svg: Cow<'a, str>,
42 _ctx: &SvgPostprocessContext<'_>,
43 ) -> Result<Cow<'a, str>> {
44 match self.policy {
45 CssOverridePolicy::Preserve => Ok(svg),
46 CssOverridePolicy::StripExistingImportant => {
47 Ok(Cow::Owned(strip_css_important(svg.as_ref())))
48 }
49 }
50 }
51}
52
53pub(crate) fn strip_css_important(svg: &str) -> String {
54 static RE: OnceLock<Regex> = OnceLock::new();
55 let re = RE.get_or_init(|| Regex::new(r"(?i)\s*!important\b").expect("valid important regex"));
56 re.replace_all(svg, "").into_owned()
57}
58
59#[cfg(test)]
60mod tests {
61 use super::*;
62 use crate::svg::pipeline::SvgPipeline;
63
64 #[test]
65 fn css_override_strips_important_only_when_requested() {
66 let svg = r#"<svg><style>.node{fill:red !important;}</style></svg>"#;
67
68 let preserve = SvgPipeline::parity()
69 .with_postprocessor(CssOverridePostprocessor::new(CssOverridePolicy::Preserve))
70 .process_to_string(svg)
71 .unwrap();
72 let strip = SvgPipeline::parity()
73 .with_postprocessor(CssOverridePostprocessor::strip_existing_important())
74 .process_to_string(svg)
75 .unwrap();
76
77 assert!(preserve.contains("!important"));
78 assert!(!strip.contains("!important"));
79 }
80}