diff --git a/src/util.rs b/src/util.rs index a90b83b0..151e9f63 100644 --- a/src/util.rs +++ b/src/util.rs @@ -15,6 +15,10 @@ use syn::{ punctuated::Punctuated, token::PathSep, Lit, LitInt, PathArguments, PathSegment, Type, TypePath, }; +use regex::Regex; +use std::sync::OnceLock; + + use anyhow::{anyhow, Result}; pub const BITS_PER_BYTE: u32 = 8; @@ -177,13 +181,35 @@ pub fn escape_brackets(s: &str) -> String { }) } +static TAG_RE: OnceLock = OnceLock::new(); + /// Escape basic html tags and brackets pub fn escape_special_chars(s: &str) -> Cow<'_, str> { - if s.contains('[') { - escape_brackets(s).into() + let tag_re= TAG_RE.get_or_init(|| Regex::new(r"<[^>]+>|&[a-zA-Z0-9#]+;").unwrap()); + + if !s.contains('[') && !s.contains('&') && !s.contains('<') && !s.contains('>') { + return s.into(); + } + + let mut last_end = 0; + let mut escaped = String::new(); + + for mat in tag_re.find_iter(s) { + let part = &s[last_end..mat.start()]; + escaped.push_str(&part.replace('&', "&").replace('<', "<").replace('>', ">")); + escaped.push_str(mat.as_str()); + last_end = mat.end(); + } + let remaining = &s[last_end..]; + + escaped.push_str(&remaining.replace('&', "&").replace('<', "<").replace('>', ">")); + + if escaped.contains('[') { + escape_brackets(&escaped).into() } else { - s.into() + escaped.into() } + } pub fn name_of(maybe_array: &MaybeArray, ignore_group: bool) -> String { @@ -513,3 +539,15 @@ fn pascalcase() { assert_eq!(to_pascal_case("FOO_BAR_1_2"), "FooBar1_2"); assert_eq!(to_pascal_case("FOO_BAR_1_2_"), "FooBar1_2_"); } + +#[test] +fn test_escape_special_chars() { + + assert_eq!(escape_special_chars("Enable & disable"), "Enable & disable"); + assert_eq!(escape_special_chars("Wait < 10"), "Wait < 10"); + assert_eq!(escape_special_chars("This is bold"), "This is bold"); + assert_eq!(escape_special_chars("Use
"), "Use
"); + assert_eq!(escape_special_chars("A & B"), "A & B"); + assert_eq!(escape_special_chars("Array[0]"), "Array\\[0\\]"); +} +