Unicode Text Normalisation & Slugify
fuzzy and search_index.tokenize only lowercase, and OCR
find_text_matches only .lower() + substring — so "Café" (NFC) versus
"Café" (NFD) versus OCR "cafe" compare unequal. This adds the
canonicalisation layer they should run before matching.
Pure standard library (unicodedata / re); imports no PySide6. Every
function is pure (text in, text out), so it is fully deterministic in CI.
Headless API
from je_auto_control import (
normalize_text, deaccent, slugify, normalize_quotes, fold_whitespace,
)
normalize_text("CAFÉ Menu") # "café menu" (NFKC + casefold + ws)
deaccent("résumé") # "resume"
slugify("Café Menu! 2026") # "cafe-menu-2026"
normalize_quotes("“Hi” — it’s…") # '"Hi" - it\'s...'
normalize_text applies a Unicode form (default NFKC), optional
casefolding, and whitespace folding, so the same text in different code-point
forms compares equal. deaccent strips combining marks; fold_whitespace
collapses runs to single spaces; normalize_quotes maps smart quotes, dashes,
ellipsis and NBSP to ASCII; slugify produces an ASCII slug (de-accent,
lowercase, join alphanumeric runs with a separator). Run normalize_text
before fuzzy/search/OCR matching to make matches accent- and form-insensitive.
Executor commands
AC_normalize_text returns {text} (with optional form / casefold
/ collapse_ws); AC_slugify returns {slug}. Both are exposed as MCP
tools (ac_normalize_text / ac_slugify) and as Script Builder commands
under Data.