use unicode_normalization::UnicodeNormalization;
pub fn is_zero_width_format(c: char) -> bool {
matches!(
c,
'\u{200B}' | '\u{200C}' | '\u{200D}' | '\u{FEFF}' | '\u{2060}'
) || matches!(c, '\u{FE00}'..='\u{FE0F}')
}
pub fn canonical(text: &str) -> String {
let mut out = String::with_capacity(text.len());
let mut pending_separator = false;
for c in text.nfc() {
if is_zero_width_format(c) {
continue;
}
for lc in c.to_lowercase() {
if lc.is_alphanumeric() {
if pending_separator && !out.is_empty() {
out.push(' ');
}
pending_separator = false;
out.push(lc);
} else {
pending_separator = true;
}
}
}
out
}
pub fn structural(text: &str) -> String {
let mut out = String::with_capacity(text.len());
for c in text.nfc() {
if is_zero_width_format(c) {
continue;
}
out.push(c);
}
out
}
pub fn words(text: &str) -> Vec<String> {
canonical(text)
.split(' ')
.filter(|w| !w.is_empty())
.map(str::to_string)
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn strips_zero_width_and_format() {
let dirty = "he\u{200B}llo\u{FEFF} wor\u{2060}ld";
assert_eq!(canonical(dirty), "hello world");
}
#[test]
fn lowercases_and_collapses_whitespace() {
assert_eq!(canonical("Hello WORLD\t\nFoo"), "hello world foo");
}
#[test]
fn punctuation_separates_tokens() {
assert_eq!(canonical("foo, bar; baz."), "foo bar baz");
assert_eq!(canonical("foo,bar"), "foo bar");
}
#[test]
fn no_leading_or_trailing_space() {
assert_eq!(canonical(" ...hi! "), "hi");
}
#[test]
fn nfc_equivalence() {
let precomposed = "caf\u{00E9}";
let decomposed = "cafe\u{0301}";
assert_eq!(canonical(precomposed), canonical(decomposed));
}
#[test]
fn structural_keeps_punctuation_and_case() {
let s = "Hello, World! A test.";
assert_eq!(structural(s), s);
}
#[test]
fn structural_strips_zero_width() {
assert_eq!(structural("a\u{200D}b."), "ab.");
}
#[test]
fn words_tokenizes() {
assert_eq!(
words("The quick, brown fox."),
vec!["the", "quick", "brown", "fox"]
);
}
#[test]
fn vector_canonical() {
let input = "The Quick\u{200B} Brown Fox — jumps!";
assert_eq!(canonical(input), "the quick brown fox jumps");
}
}