use bacnet_types::text::{breaks_line, is_hidden_on_screen};
pub const MAX_NAME_CHARS: usize = 128;
pub fn clean_name(text: &str) -> String {
shown(text).take(MAX_NAME_CHARS).collect()
}
pub fn clean_text(text: &str) -> String {
shown(text).collect()
}
fn shown(text: &str) -> impl Iterator<Item = char> + '_ {
text.chars()
.filter(|ch| !is_hidden_on_screen(*ch))
.map(|ch| if breaks_line(ch) { ' ' } else { ch })
}
#[cfg(test)]
mod tests {
use super::*;
use bacnet_types::text::HIDDEN;
#[test]
fn controls_become_spaces_and_format_characters_go() {
assert_eq!(clean_name("Zone\nTemp\u{1b}"), "Zone Temp ");
assert_eq!(clean_name("a\u{2028}b\u{2029}c"), "a b c");
assert_eq!(clean_name("High\u{202E} 1"), "High 1");
assert_eq!(
clean_name("\u{2066}a\u{2067}b\u{2068}c\u{2069}\u{200B}d\u{FEFF}"),
"abcd"
);
let family = "\u{1F468}\u{200D}\u{1F469}";
assert_eq!(clean_name(family), family);
for &(low, high) in HIDDEN {
for ch in [low, high].into_iter().filter(|ch| *ch != '\u{200D}') {
assert_eq!(
clean_name(&format!("a{ch}b")),
"ab",
"U+{:04X}",
u32::from(ch)
);
}
}
let long = format!("{}{}", "\u{202E}".repeat(10), "é".repeat(500));
assert_eq!(clean_name(&long), "é".repeat(MAX_NAME_CHARS));
}
#[test]
fn clean_text_keeps_every_shown_character() {
let hostile = "A\u{202E}B\u{1b}[2JC\u{7}D\nE\u{2028}F\u{1F468}\u{200D}\u{1F469}";
let cleaned = "AB [2JC D E F\u{1F468}\u{200D}\u{1F469}";
assert_eq!(clean_text(hostile), cleaned);
assert_eq!(clean_name(hostile), cleaned);
let long = format!("{}{}", "\u{202E}".repeat(10), "é".repeat(500));
assert_eq!(clean_text(&long), "é".repeat(500));
}
}