Spaces:
Running
Running
Download src/unicode_preprocessing_v2.py from mohamedmady/deberta-conpara: direct link, hf CLI and curl.
- Browser
- Download file 20.1 kB
-
https://huggingface.co/spaces/mohamedmady/deberta-conpara/resolve/main/src/unicode_preprocessing_v2.py
- Command line
-
hf download hf://spaces/mohamedmady/deberta-conpara/src/unicode_preprocessing_v2.py
-
curl -L -o unicode_preprocessing_v2.py https://huggingface.co/spaces/mohamedmady/deberta-conpara/resolve/main/src/unicode_preprocessing_v2.py
20.1 kB
| #!/usr/bin/env python3 | |
| """ | |
| Unicode Preprocessing v2 โ Optimized for RAID Adversarial Robustness | |
| ===================================================================== | |
| Drop-in replacement for `unicode_normalize()` in test_preprocessing_effect.py. | |
| Three-layer defence: | |
| Layer 1 โ Explicit homoglyph confusables (Cyrillic/Greek/Fullwidth โ Latin) | |
| Layer 2 โ NFKD decomposition + combining-mark strip (accented Latin โ ASCII) | |
| Layer 3 โ Typographic normalization (smart quotes, dashes, symbols โ ASCII) | |
| Layer 4 โ Strip invisible / format characters (ZWSP, Cf category) | |
| Author : Mohamed Mady | |
| Date : 2026-03-10 | |
| """ | |
| import unicodedata | |
| import re | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # LAYER 1 โ EXPLICIT HOMOGLYPH CONFUSABLES | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Only characters that are visually similar to Latin characters but come from | |
| # other scripts. NFKD cannot fix these because they are canonically distinct. | |
| # | |
| # CORRECTIONS from v1: | |
| # ฤฑ (U+0131) โ 'i' (was 'd' โ Turkish dotless i looks like i, not d) | |
| # ะฝ (U+043D) โ 'h' (was 'e' โ Cyrillic EN looks like lowercase h) | |
| # ะป (U+043B) โ 'n' (was 'e' โ Cyrillic EL looks like inverted-V / n in many fonts) | |
| # ะผ (U+043C) โ 'm' (was 'e' โ Cyrillic EM is visually m) | |
| # ะ (U+041B) โ 'L' (was 'e' โ Cyrillic capital EL) | |
| # ฮ (U+0396) โ 'Z' (was 'z' โ it's uppercase, map to uppercase) | |
| # ฯ (U+03C2) โ 's' (was 'f' โ final sigma looks like s, not f) | |
| # ั (U+0444) โ 'f' (was 'e' โ Cyrillic EF looks like f in many fonts) | |
| CONFUSABLES = { | |
| # โโ Cyrillic โ Latin (lowercase) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u0430': 'a', # ะฐ | |
| '\u0435': 'e', # ะต | |
| '\u043e': 'o', # ะพ | |
| '\u0440': 'p', # ั (VISUAL: looks like Latin p) | |
| '\u0423': 'Y', # ะฃ (VISUAL: Latin Y) | |
| '\u0455': 's', # ั (VISUAL: Latin s) | |
| '\u0458': 'j', # ั (VISUAL: Latin j) | |
| '\u04bb': 'h', # าป (VISUAL: Latin h) | |
| '\u0441': 'c', # ั | |
| '\u0445': 'x', # ั | |
| '\u0456': 'i', # ั | |
| '\u0443': 'y', # ั | |
| '\u0451': 'e', # ั | |
| '\u0438': 'i', # ะธ (NEW โ count=2,119) | |
| '\u043d': 'h', # ะฝ (FIXED: was 'e' โ 'h') | |
| '\u0432': 'v', # ะฒ (NEW โ count=1,383) | |
| '\u0442': 't', # ั (NEW โ count=1,305) | |
| '\u043b': 'n', # ะป (FIXED: was 'e' โ 'n') | |
| '\u043a': 'k', # ะบ (NEW โ count=794) | |
| '\u043c': 'm', # ะผ (FIXED: was 'e' โ 'm') | |
| '\u0447': 'c', # ั (NEW โ count=603) | |
| '\u0434': 'd', # ะด (NEW โ count=585) | |
| '\u043f': 'p', # ะฟ (NEW โ count=363) | |
| '\u044c': "'", # ั soft sign (NEW โ count=342) | |
| '\u0433': 'g', # ะณ (NEW โ count=320) | |
| '\u044b': 'y', # ั (NEW โ count=310) | |
| '\u0439': 'i', # ะน short I (NEW โ count=444) | |
| '\u044f': 'ya', # ั (NEW โ count=439) | |
| '\u0431': 'b', # ะฑ (NEW โ count=258) | |
| '\u0437': 'z', # ะท (NEW โ count=249) | |
| '\u0436': 'zh', # ะถ (NEW โ count=111) | |
| '\u0446': 'ts', # ั (NEW โ count=120) | |
| '\u0448': 'sh', # ั (NEW โ count=133) | |
| '\u044e': 'yu', # ั (NEW โ count=132) | |
| '\u0444': 'f', # ั (FIXED: was 'e' โ 'f', count=52) | |
| '\u045b': 't', # ั tshe (NEW โ count=68) | |
| # โโ Cyrillic โ Latin (uppercase) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u0406': 'I', # ะ | |
| '\u0410': 'A', # ะ | |
| '\u0412': 'B', # ะ | |
| '\u0415': 'E', # ะ | |
| '\u041a': 'K', # ะ | |
| '\u041c': 'M', # ะ | |
| '\u041d': 'H', # ะ | |
| '\u041e': 'O', # ะ | |
| '\u0420': 'P', # ะ | |
| '\u0421': 'C', # ะก | |
| '\u0422': 'T', # ะข | |
| '\u0425': 'X', # ะฅ | |
| '\u0418': 'I', # ะ (NEW โ count=104) | |
| '\u0411': 'B', # ะ (NEW โ count=159) | |
| '\u041b': 'L', # ะ (FIXED: was 'e' โ 'L', count=128) | |
| '\u041f': 'P', # ะ (NEW โ count=119) | |
| '\u0414': 'D', # ะ (NEW โ count=88) | |
| '\u0428': 'Sh', # ะจ (NEW โ count=64) | |
| # โโ Greek โ Latin (lowercase) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u03b1': 'a', # ฮฑ | |
| '\u03b5': 'e', # ฮต | |
| '\u03b7': 'n', # ฮท | |
| '\u03b9': 'i', # ฮน | |
| '\u03ba': 'k', # ฮบ | |
| '\u03bd': 'v', # ฮฝ | |
| '\u03bf': 'o', # ฮฟ | |
| '\u03c1': 'p', # ฯ (visually similar to p) | |
| '\u03c5': 'u', # ฯ | |
| '\u03c7': 'x', # ฯ | |
| '\u03c4': 't', # ฯ (NEW โ count=1,668) | |
| '\u03bc': 'm', # ฮผ (NEW โ count=1,401) | |
| '\u03c3': 's', # ฯ (NEW โ count=971) | |
| '\u03c0': 'p', # ฯ (NEW โ count=962) | |
| '\u03bb': 'l', # ฮป (NEW โ count=857) | |
| '\u03c2': 's', # ฯ final sigma (FIXED: was 'f' โ 's', count=784) | |
| '\u03c9': 'o', # ฯ (NEW โ count=523) | |
| '\u03b4': 'd', # ฮด (NEW โ count=487) | |
| '\u03b3': 'g', # ฮณ (NEW โ count=467) | |
| '\u03b8': 'th', # ฮธ (NEW โ count=464) | |
| '\u03b2': 'b', # ฮฒ (NEW โ count=463) | |
| '\u03b6': 'z', # ฮถ (NEW โ count=124) | |
| '\u03c6': 'ph', # ฯ (NEW โ count=272) | |
| '\u03c8': 'ps', # ฯ (NEW โ count=108) | |
| '\u03be': 'x', # ฮพ (NEW โ count=86) | |
| '\u03d5': 'ph', # ฯ phi symbol (NEW โ count=52) | |
| # Greek with tonos (accented) | |
| '\u03af': 'i', # ฮฏ (NEW โ count=514) | |
| '\u03ac': 'a', # ฮฌ (NEW โ count=435) | |
| '\u03cc': 'o', # ฯ (NEW โ count=364) | |
| '\u03ad': 'e', # ฮญ (NEW โ count=339) | |
| '\u03ae': 'e', # ฮฎ (NEW โ count=269) | |
| '\u03cd': 'u', # ฯ (NEW โ count=193) | |
| '\u03ce': 'o', # ฯ (NEW โ count=155) | |
| # โโ Greek โ Latin (uppercase) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u0391': 'A', # ฮ | |
| '\u0392': 'B', # ฮ | |
| '\u0395': 'E', # ฮ | |
| '\u0397': 'H', # ฮ | |
| '\u0399': 'I', # ฮ | |
| '\u039a': 'K', # ฮ | |
| '\u039c': 'M', # ฮ | |
| '\u039d': 'N', # ฮ | |
| '\u039f': 'O', # ฮ | |
| '\u03a1': 'P', # ฮก | |
| '\u03a4': 'T', # ฮค | |
| '\u03a7': 'X', # ฮง | |
| '\u0396': 'Z', # ฮ (FIXED: was lowercase 'z' โ 'Z', count=35,053) | |
| '\u0393': 'G', # ฮ (NEW โ count=114) | |
| '\u0394': 'D', # ฮ (NEW โ count=160) | |
| '\u039b': 'L', # ฮ (NEW โ count=62) | |
| '\u03a0': 'P', # ฮ (NEW โ count=65) | |
| '\u03a3': 'S', # ฮฃ (NEW โ count=176) | |
| '\u03a6': 'Ph', # ฮฆ (NEW โ count=66) | |
| '\u03a9': 'O', # ฮฉ (NEW โ count=202) | |
| # โโ Fullwidth Latin โ ASCII โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\uff41': 'a', '\uff42': 'b', '\uff43': 'c', '\uff44': 'd', '\uff45': 'e', | |
| '\uff46': 'f', '\uff47': 'g', '\uff48': 'h', '\uff49': 'i', '\uff4a': 'j', | |
| '\uff4b': 'k', '\uff4c': 'l', '\uff4d': 'm', '\uff4e': 'n', '\uff4f': 'o', | |
| '\uff50': 'p', '\uff51': 'q', '\uff52': 'r', '\uff53': 's', '\uff54': 't', | |
| '\uff55': 'u', '\uff56': 'v', '\uff57': 'w', '\uff58': 'x', '\uff59': 'y', | |
| '\uff5a': 'z', | |
| '\uff21': 'A', '\uff22': 'B', '\uff23': 'C', '\uff24': 'D', '\uff25': 'E', | |
| '\uff26': 'F', '\uff27': 'G', '\uff28': 'H', '\uff29': 'I', '\uff2a': 'J', | |
| '\uff2b': 'K', '\uff2c': 'L', '\uff2d': 'M', '\uff2e': 'N', '\uff2f': 'O', | |
| '\uff30': 'P', '\uff31': 'Q', '\uff32': 'R', '\uff33': 'S', '\uff34': 'T', | |
| '\uff35': 'U', '\uff36': 'V', '\uff37': 'W', '\uff38': 'X', '\uff39': 'Y', | |
| '\uff3a': 'Z', | |
| # Fullwidth digits | |
| '\uff10': '0', '\uff11': '1', '\uff12': '2', '\uff13': '3', '\uff14': '4', | |
| '\uff15': '5', '\uff16': '6', '\uff17': '7', '\uff18': '8', '\uff19': '9', | |
| # Fullwidth punctuation | |
| '\uff01': '!', '\uff0c': ',', '\uff0e': '.', '\uff1a': ':', '\uff1b': ';', | |
| '\uff1f': '?', '\uff08': '(', '\uff09': ')', | |
| # โโ Latin Extended look-alikes โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u0131': 'i', # ฤฑ dotless i (FIXED: was 'd' โ 'i', count=5,734) | |
| '\u0130': 'I', # ฤฐ dotted I (NEW โ count=249) | |
| '\u0142': 'l', # ล (NEW โ count=307) | |
| '\u00f0': 'd', # รฐ eth (count=174 โ better mapped to 'd' than 'e') | |
| '\u00fe': 'th', # รพ thorn (count=58) | |
| '\u00de': 'Th', # ร Thorn (count=95) | |
| '\u00df': 'ss', # ร sharp s (count=368) | |
| '\u00e6': 'ae', # รฆ (count=347) | |
| '\u00c6': 'Ae', # ร (count=215) | |
| '\u0153': 'oe', # ล (count=101) | |
| '\u0152': 'Oe', # ล | |
| '\u0259': 'e', # ษ schwa (count=285) | |
| '\u026a': 'i', # ษช small capital I (count=153) | |
| '\u025b': 'e', # ษ open E (count=145) | |
| '\u0254': 'o', # ษ open O (count=89) | |
| '\u0251': 'a', # ษ Latin alpha (count=87) | |
| '\u0250': 'a', # ษ turned a (count=64) | |
| '\u028a': 'u', # ส upsilon (count=57) | |
| '\u0283': 'sh', # ส esh (count=61) | |
| '\u0292': 'zh', # ส ezh (count=58) | |
| '\u0281': 'r', # ส inverted R (count=63) | |
| '\u2113': 'l', # โ script small L (count=76) | |
| # โโ Letterlike symbols โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| '\u2103': 'C', # โ degree Celsius | |
| '\u2109': 'F', # โ degree Fahrenheit | |
| '\u212b': 'A', # ร angstrom | |
| } | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # LAYER 3 โ TYPOGRAPHIC NORMALIZATION | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # These are NOT homoglyphs โ they are normal typographic characters. | |
| # But they affect tokenization (DeBERTa treats U+2019 โ U+0027), so | |
| # normalizing them removes an uncontrolled variable. | |
| TYPOGRAPHIC = { | |
| # Quotes | |
| '\u2018': "'", # ' left single | |
| '\u2019': "'", # ' right single (count=78,314 โ #1 by frequency!) | |
| '\u201a': "'", # โ single low-9 | |
| '\u201b': "'", # โ single high-reversed-9 | |
| '\u201c': '"', # " left double | |
| '\u201d': '"', # " right double | |
| '\u201e': '"', # โ double low-9 | |
| '\u201f': '"', # โ double high-reversed-9 | |
| '\u00ab': '"', # ยซ left guillemet | |
| '\u00bb': '"', # ยป right guillemet | |
| '\u2039': "'", # โน single left guillemet | |
| '\u203a': "'", # โบ single right guillemet | |
| '\u0060': "'", # ` grave accent (sometimes used as quote) | |
| '\u00b4': "'", # ยด acute accent | |
| # Dashes | |
| '\u2013': '-', # โ en dash (count=55,032) | |
| '\u2014': '--', # โ em dash (count=24,725) | |
| '\u2015': '--', # โ horizontal bar | |
| '\u2012': '-', # โ figure dash | |
| '\u2212': '-', # โ minus sign (count=818) | |
| '\uff0d': '-', # ๏นฃ fullwidth hyphen-minus | |
| # Ellipsis | |
| '\u2026': '...', # โฆ (count=25,340) | |
| # Spaces | |
| '\u00a0': ' ', # no-break space (count=5,407) | |
| '\u2002': ' ', # en space | |
| '\u2003': ' ', # em space | |
| '\u2009': ' ', # thin space | |
| '\u200a': ' ', # hair space | |
| '\u202f': ' ', # narrow no-break space | |
| '\u205f': ' ', # medium mathematical space | |
| # Bullets / dots | |
| '\u2022': '*', # โข bullet (count=6,029) | |
| '\u00b7': '.', # ยท middle dot (count=1,000) | |
| # Math / symbols | |
| '\u00d7': 'x', # ร multiplication (count=1,221) | |
| '\u00f7': '/', # รท division | |
| '\u2260': '!=', # โ | |
| '\u2264': '<=', # โค | |
| '\u2265': '>=', # โฅ | |
| '\u2248': '~', # โ | |
| # Fractions โ text (avoids unknown tokens) | |
| '\u00bd': '1/2', # ยฝ (count=9,508) | |
| '\u00bc': '1/4', # ยผ (count=5,211) | |
| '\u00be': '3/4', # ยพ (count=2,200) | |
| '\u2153': '1/3', # โ | |
| '\u2154': '2/3', # โ | |
| # Misc | |
| '\u00b0': ' degrees', # ยฐ degree sign (count=35,269) | |
| '\ufffd': '', # ๏ฟฝ replacement character โ pure noise, strip it | |
| } | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # COMBINED NORMALIZATION FUNCTION | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Build a single lookup table for maximum speed | |
| _COMBINED_MAP = {} | |
| _COMBINED_MAP.update(CONFUSABLES) | |
| _COMBINED_MAP.update(TYPOGRAPHIC) | |
| # Pre-build translation table for single-char mappings (faster than dict lookup) | |
| _SINGLE_CHAR = {ord(k): v for k, v in _COMBINED_MAP.items() if len(v) <= 1} | |
| _MULTI_CHAR = {k: v for k, v in _COMBINED_MAP.items() if len(v) > 1} | |
| def unicode_normalize(text: str) -> str: | |
| """ | |
| Four-layer Unicode normalization for adversarial robustness. | |
| Layer 1: Explicit homoglyph substitution (Cyrillic/Greek/Fullwidth โ Latin) | |
| Layer 2: NFKD decomposition + combining-mark strip (รฉ โ e, รผ โ u, etc.) | |
| Layer 3: Typographic normalization (smart quotes, dashes, fractions โ ASCII) | |
| Layer 4: Strip invisible format characters (ZWSP, ZWNJ, BOM, etc.) | |
| This function is idempotent: applying it twice gives the same result. | |
| """ | |
| if not text: | |
| return text | |
| # โโ Layer 1 + 3: Apply all explicit mappings โโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # Single-char mappings via str.translate (C-speed) | |
| text = text.translate(_SINGLE_CHAR) | |
| # Multi-char mappings (e.g. ร โ ss, โ โ --, ฮธ โ th) | |
| for src, dst in _MULTI_CHAR.items(): | |
| if src in text: | |
| text = text.replace(src, dst) | |
| # โโ Layer 2: NFKD decomposition + strip combining marks โโโโโโโโโโโโโโ | |
| # This handles ALL accented Latin characters automatically: | |
| # รฉ โ e + โฬ โ strip mark โ e | |
| # รผ โ u + โฬ โ strip mark โ u | |
| # รฑ โ n + โฬ โ strip mark โ n | |
| # Also normalizes compatibility forms (e.g. ๏ฌ โ fi, ยฒ โ 2) | |
| text = unicodedata.normalize('NFKD', text) | |
| text = ''.join(c for c in text if unicodedata.category(c) != 'Mn') | |
| # Re-compose any remaining valid sequences | |
| text = unicodedata.normalize('NFC', text) | |
| # โโ Layer 4: Strip invisible format characters (Cf category) โโโโโโโโโโ | |
| # Catches: ZWSP (U+200B), ZWNJ (U+200C), ZWJ (U+200D), BOM (U+FEFF), | |
| # LRM (U+200E), RLM (U+200F), soft hyphen (U+00AD), etc. | |
| text = ''.join(c for c in text if unicodedata.category(c) != 'Cf') | |
| # โโ Cleanup: collapse multiple spaces โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| text = re.sub(r' +', ' ', text) | |
| return text | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # LEGACY WRAPPER (drop-in for old code) | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def unicode_normalize_v1(text: str) -> str: | |
| """Original v1 normalization โ kept for A/B comparison.""" | |
| CONFUSABLES_V1 = { | |
| '\u0430': 'a', '\u0435': 'e', '\u043e': 'o', '\u0440': 'p', | |
| '\u0441': 'c', '\u0445': 'x', '\u0456': 'i', '\u0443': 'y', | |
| '\u0451': 'e', '\u0406': 'I', '\u0410': 'A', '\u0412': 'B', | |
| '\u0415': 'E', '\u041a': 'K', '\u041c': 'M', '\u041d': 'H', | |
| '\u041e': 'O', '\u0420': 'P', '\u0421': 'C', '\u0422': 'T', | |
| '\u0425': 'X', '\u03b1': 'a', '\u03b5': 'e', '\u03b7': 'n', | |
| '\u03b9': 'i', '\u03ba': 'k', '\u03bd': 'v', '\u03bf': 'o', | |
| '\u03c1': 'p', '\u03c5': 'u', '\u03c7': 'x', '\u0391': 'A', | |
| '\u0392': 'B', '\u0395': 'E', '\u0397': 'H', '\u0399': 'I', | |
| '\u039a': 'K', '\u039c': 'M', '\u039d': 'N', '\u039f': 'O', | |
| '\u03a1': 'P', '\u03a4': 'T', '\u03a7': 'X', | |
| '\uff41': 'a', '\uff45': 'e', '\uff49': 'i', '\uff4f': 'o', '\uff55': 'u', | |
| '\uff21': 'A', '\uff25': 'E', '\uff29': 'I', '\uff2f': 'O', '\uff35': 'U', | |
| } | |
| text = ''.join(CONFUSABLES_V1.get(c, c) for c in text) | |
| text = ''.join(c for c in text if unicodedata.category(c) != 'Cf') | |
| return text | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| # STATS / TESTING | |
| # โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ | |
| def count_surviving_nonascii(text: str) -> dict: | |
| """Count non-ASCII characters remaining after normalization.""" | |
| counts = {} | |
| for c in text: | |
| if ord(c) > 127: | |
| counts[c] = counts.get(c, 0) + 1 | |
| return counts | |
| def get_normalization_stats(raw_text: str) -> dict: | |
| """Compare v1 vs v2 normalization on a single text.""" | |
| v1 = unicode_normalize_v1(raw_text) | |
| v2 = unicode_normalize(raw_text) | |
| raw_non = sum(1 for c in raw_text if ord(c) > 127) | |
| v1_non = sum(1 for c in v1 if ord(c) > 127) | |
| v2_non = sum(1 for c in v2 if ord(c) > 127) | |
| return { | |
| 'raw_nonascii': raw_non, | |
| 'v1_nonascii': v1_non, | |
| 'v2_nonascii': v2_non, | |
| 'v1_fixed': raw_non - v1_non, | |
| 'v2_fixed': raw_non - v2_non, | |
| 'v2_improvement': (v1_non - v2_non), | |
| } | |
| if __name__ == '__main__': | |
| # Quick self-test | |
| tests = [ | |
| ("Hะตllo wะพrld", "Hello world", "Basic Cyrillic homoglyphs"), | |
| ("It\u2019s a tะตst", "It's a test", "Smart quote + Cyrillic"), | |
| ("cafรฉ rรฉsumรฉ naรฏve", "cafe resume naive", "Accented Latin (NFKD)"), | |
| ("ยฝ cup of ๏ฌour", "1/2 cup of flour", "Fraction + ligature"), | |
| ("zero\u200Bwidth", "zerowidth", "Zero-width space"), | |
| ("ฮero day", "Zero day", "Greek Zeta (U+0396)"), | |
| ("tรผrkรงe ฤฑลฤฑk", "turkce isik", "Turkish chars"), | |
| ("naรฏve\u00A0cafรฉ", "naive cafe", "NBSP + accents"), | |
| ("Straรe Mรผnchen", "Strasse Munchen", "German special chars"), | |
| ] | |
| print("=" * 70) | |
| print(" Unicode Preprocessing v2 โ Self-Test") | |
| print("=" * 70) | |
| all_pass = True | |
| for raw, expected, desc in tests: | |
| result = unicode_normalize(raw) | |
| ok = result == expected | |
| status = "PASS" if ok else "FAIL" | |
| if not ok: | |
| all_pass = False | |
| print(f" [{status}] {desc}") | |
| if not ok: | |
| print(f" Input : {repr(raw)}") | |
| print(f" Expected: {repr(expected)}") | |
| print(f" Got : {repr(result)}") | |
| print(f"\n Table size: {len(CONFUSABLES)} homoglyphs + {len(TYPOGRAPHIC)} typographic") | |
| print(f" Total mappings: {len(_COMBINED_MAP)}") | |
| print(f" Result: {'ALL PASSED' if all_pass else 'SOME FAILED'}\n") | |