Skip to main content

merman_render/svg/pipeline/builtin/
css_override.rs

1use 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}