Нормалізатор Unicode

Unicode має чотири форми нормалізації, і вони не взаємозамінні. NFC і NFD перетворюють той самий символ між однією кодовою точкою і базовою літерою з комбінувальним знаком; NFKC і NFKD йдуть далі і переписують символи, які лише виглядають однаково — лігатуру fi, широку A, обведену ①, римську цифру Ⅻ. Ця сторінка пропускає той самий текст через усі чотири, виводить рядок на кожен символ з поясненням, що змінилося і чому, порівнює графеми з кодовими точками і байтами UTF-8 і порівнює два рядки за формою, яку ви оберете. Друга половина стосується символів, які взагалі не відображаються: пробіли нульової ширини, з'єднувачі, двонаправлені керуючі символи, селектори варіацій, керуючі символи C0 і C1, кодові точки приватного використання та зламані половини сурогатів, кожен із позицією і назвою, а також опція видалити їх з копії тексту.
Уся обробка відбувається локально у вашому браузері — ваші дані ніколи не вивантажуються на жоден сервер.

Не впевнені, що таке нормалізація? Нормалізація Unicode простими словами — чому é може бути одним символом або двома і чому це ламає вхід в обліковий запис.
Форма для звіту і порівняння:
Порівняти два поля після:
NFC (ще нічого)
NFD (ще нічого)
NFKC (ще нічого)
NFKD (ще нічого)
Ще нічого не порівняно. Вставте два рядки і натисніть Порівняти два рядки.
перший рядок, нормалізований (ще нічого)
другий рядок, нормалізований (ще нічого)
Ще нічого не просканована. Натисніть Перевірити на невидимі й небезпечні символи, потім Видалити, якщо хочете текст без цих символів.
текст без вибраних класів (ще нічого)
Ctrl+Enter у першому полі запускає активний режим.
Оберіть режим і натисніть його кнопку дії. Нормалізувати записує всі чотири форми вашого тексту і рядок на кожен символ; Порівняти каже, чи є два рядки однаковими після нормалізації; Сканувати перелічує символи, що не відображаються, і може їх видалити. Кнопка самоперевірки повторно запускає кожен задокументований випадок у файлі даних цієї сторінки, кожен символ із таблиць невидимих і керуючих символів і показує тут кількість.
Кожен символ, по одному рядку:що це за символ, що з ним роблять чотири форми і чому. Затінений рядок — це символ, який хоча б одна форма змінює; стовпець NFD показує, у що символ розкладається, коли взагалі розкладається.
Скільки це символів?Той самий текст, підрахований чотирма способами: кластери графем (те, що порахує читач), кодові точки (що призначає Unicode), одиниці UTF-16 (за якими індексує JavaScript) і байти UTF-8 (що передаються). Нормалізація переміщує числа між другим і третім стовпцем, не змінюючи першого.
Що знайдено, у порядку появи:позиція рахує кодові точки, клас — це той, до якого належить символ, а примітка каже, що цей символ робить з тим, що читає текст далі.
Класи, які ця сторінка вважає підозрілими:таблицю виведено з самого сканера, тож слова на цій сторінці і поведінка кнопок не можуть розійтися. Серйозність стосується впливу на порівняння, а не того, наскільки ймовірно, що символ безневинний.
Символи приватного використання перелічено як підозрілі з іншої причини, ніж решта: вони відображаються, але не мають узгодженого значення, тож та сама кодова точка — це інша картинка в кожному шрифті, який її визначає.
Яким великим може бути вхід на цій сторінці і чому:нормалізація — це перебудова всього рядка, а ця сторінка виконує кілька таких перебудов плюс сканування і підрахунок графем, тож вартість зростає прямо пропорційно довжині вхідних даних. Обмеження нижче існують, бо таблиця по символах малюється в документі, а таблиця з мільйоном рядків і є тим, що реально зупиняє браузер.
Що таке нормалізація Unicode?

Unicode дає більше одного способу записати деякі символи. Літеру é можна записати одним символом, U+00E9, або звичайною e, за якою йде комбінувальний гострий наголос, U+0065 U+0301. На екрані вони однакові, друкуються однаково, а як рядки різні: мають різну довжину, різні байти UTF-8, і 'é' === 'é' дорівнює false.

Це не курйоз, а клас помилок. Отримаєте ви одну форму чи іншу, залежить від розкладки клавіатури, операційної системи, методу введення і того, чи текст колись копіювався через текстовий процесор. Перевірка пароля, унікальний індекс бази даних, чорний список або ключ кешу, що порівнює рядки побайтово, сприйматиме два написання як два різні значення — тож користувач може встановити пароль на одному пристрої й не увійти з іншого, а те саме слово може з'явитися двічі у списку «вже зайнятих» імен.

Версія на 30 секунд: NFC і NFD — той самий текст, записаний двома способами: NFC складає (e + акцент стає одним символом), NFD розкладає (один символ стає e + акцент). Вони призначені бути без втрат і є двома формами, які слід використовувати для порівняння та зберігання. NFKC і NFKD роблять усе, що роблять перші дві, а потім ще замінюють символи сумісності: лігатура fi стає двома літерами, ½ стає 1⁄2, римська цифра ⅲ стає XII, широка A стає звичайною A. Саме це потрібно індексу пошуку або фільтру ключових слів і саме те, що ніколи не можна застосовувати до пароля.
Чому чотири форми, а не одна

Поділ існує, бо служило двом різним цілям. Складання і розкладання тексту — це канонічна зміна: результат означає те саме і, для перших двох форм, нічого не втрачається. Згортання лігатур, верхніх індексів, обведених літер і широких форм — це сумісна зміна: її роблять для зручності зіставлення, і вона справді знищує відмінність між двома рядками, які Unicode вважає різними. Чотири форми — це всі комбінації двох рішень.

Таблиця вище — це список випадків із файлу даних цієї сторінки, перевірений за власним String.prototype.normalize платформи, а самоперевірка перевіряє кожен її рядок у вас на очах. Читайте перші два рядки разом: це той самий текст, записаний двома способами, і після нормалізації це той самий рядок.

Робочі приклади

Це рядки, які проходить кнопка «Зразок», нормалізовані наживо сторінкою, а не введені як відповіді. Кожен обрано тому, що форма робить з ним щось видиме.

Символи сумісності: чого коштує NFKC

NFKC і NFKD є втратними, і втрата навмисна. Вони відповідають на питання «це те саме слово», а не на «це той самий рядок». Звідси випливають три наслідки, і всі три легко зробити неправильно у продакшн-коді:

  • Ніколи не зберігайте пароль, токен або підпис у формі сумісності. Два різні вхідні дані звелися б до одного збереженого значення, а перевірка довжини чи символів стала б слабшою. Нормалізуйте паролі через NFC (або взагалі не нормалізуйте) і порівнюйте їх як байти, виконавши точно ту саму дію з обох боків.
  • Перетворення сумісності може перетворити один символ на кілька. Лігатура fi — це одна кодова точка, що стає двома літерами, тож поле, перевірене за довжиною до нормалізації, може стати довшим після неї. Спершу нормалізуйте, потім перевіряйте, потім зберігайте.
  • NFKC не видаляє все невидиме. Селектор варіацій переживає NFKC і NFKD, так само як з'єднувач нульової ширини. Тому ця сторінка має режим сканування: нормалізація відповідає на питання про форму і ідентичність символу, а сканер — на питання, чи взагалі щось там є.
Графеми, кодові точки і байти

Нормалізація змінює другий із цих підрахунків і зазвичай не чіпає перший. Усі три числа — це «довжина тексту», і всі вони різні, тому поле форми з лімітом 20 символів може належати рядку з 20 кодовими точками, 30 одиницями UTF-16 і 47 байтами.

Число в стовпці графем — це те, що порахує людина. Різниця між ним і кількістю кодових точок — саме те, про що йдеться у сканері: ім'я з 12 кодових точок може відображатися як 12 літер, як 3 літери з 9 невидимими знаками між ними або як один емодзі.

Символи, яких ви не бачите

Пробіл нульової ширини не займає місця, не змінює гліф і є справжнім символом із реальною кодовою точкою. Два ідентифікатори, що на екрані виглядають однаково, можуть відрізнятися одним із них — так ім'я у списку може бути самозванцем, що сортується, шукається і автентифікується інакше, ніж ім'я поруч. Те саме стосується двонаправлених керуючих символів, які можуть змусити рядок відображатися в порядку, відмінному від порядку запису, — прийом, яким маскували імена файлів.

Сканер у третьому режимі перелічує кожен із них із позицією: символи нульової ширини й з'єднувачі, двонаправлені керуючі символи, селектори варіацій, незвичайні пробіли, керуючі символи C0 і C1, кодові точки приватного використання і сурогат, що не є половиною дійсної пари, — а це взагалі не символ. Таблиця класів вище на цій сторінці пояснює, що робить кожен клас, а кнопка видалення прибирає ці символи з копії, не змінюючи вхідних даних.

Помилки, для виявлення яких створено цю сторінку
  • Порівняння двох рядків без нормалізації. 'é' === 'é' дає false. Нормалізуйте обидві сторони однією формою перед порівнянням і використовуйте ту саму форму в індексі, з яким порівнюєте.
  • Нормалізація лише з одного боку. Пошук, який нормалізує запит, але не збережене значення, нічого не знаходить і виглядає як проблема з даними, а не з нормалізацією.
  • Вважати, що форма сумісності є безпечною для NFC. NFKC, застосований до пароля, скорочує простір можливих паролів і зливає вхідні дані, які мали бути різними.
  • Припущення, що форма нормалізації усуває підробку. Вона не видаляє з'єднувач нульової ширини, селектор варіацій чи гомогліф — кирилична а і латинська a є різними кодовими точками, які NFC не змінює.
  • Довіра до підрахунку символів. Кількість, яку бачить користувач, кількість, яку забезпечує стовпець бази даних, і кількість, яку забезпечує ліміт байтів, — це три різні числа, і рядки вище показують, наскільки вони можуть розходитися.
  • Припущення, що ви отримаєте NFC. Текст, що надходить з методу введення, файлу або іншої системи, має ту форму, яку створила ця система. Ця сторінка починає з NFC, бо саме цю форму очікує веб, а не тому, що саме така форма у вашому вводі.
Короткі відповіді

“Чому два рядки не рівні, хоча виглядають однаково?”

Тому що один із них записаний складеним символом, а інший — базовою літерою з комбінувальним знаком, або один містить символ, який взагалі нічого не малює. Перейдіть у режим порівняння і поставте по одному рядку в кожне поле: сторінка повідомить першу позицію, де нормалізовані рядки відрізняються, і що стоїть на цій позиції в кожному з них.

“Яку форму використовувати в застосунку?”

NFC для всього, що людина прочитає, відобразить, збереже і знайде, — це форма, яку W3C рекомендує для веб. NFD є правильною відповіддю, коли код очікує базові літери і окремі знаки, що часто в обробці тексту та в деяких файлових системах macOS. Використовуйте NFKC або NFKD лише там, де ви зіставляєте, а не зберігаєте: індекси пошуку, фільтри ключових слів, виявлення дублікатів.

“Чи залишається рядок, який уже нормалізовано, нормалізованим після того, як я додам до нього текст?”

Не обов'язково. Нормалізація не зберігається при конкатенації: з'єднання двох рядків NFC може дати послідовність, яку NFC записав би інакше, бо комбінувальний знак на початку другого рядка може прикріпитися до символу в кінці першого. Нормалізуйте після складання, а не лише до нього.

“Чи нормалізація видаляє невидимі символи?”

Ні, з однією частковою винятком: NFKC і NFKD замінюють нерозривний пробіл на звичайний пробіл, що є видимим результатом перетворення сумісності. Усе інше — пробіли нульової ширини, з'єднувачі, двонаправлені керуючі символи, селектори варіацій — проходить через усі чотири форми без змін. Саме для цього існує третій режим.

“Чому кількість байтів змінюється, коли кількість символів не змінюється?”

Тому що дві форми побудовані з різних кодових точок, які в UTF-8 займають різну кількість байтів. Попередньо складене é — два байти, а розкладена пара — три. У таблиці підрахунків вище виведено всі чотири числа поруч для вхідних даних і для кожної форми, тож різниця видима, а не описана.

“Звідки я знаю, що результати на цій сторінці правильні?”

Натисніть Самоперевірка. Вона нормалізує кожен випадок із файлу даних цієї сторінки, перевіряє чотири форми одну відносно одної за задокументованими співвідношеннями (NFC є ідемпотентним, NFD рядка NFC збігається з NFD оригіналу тощо), пропускає через сканер кожен символ із таблиць невидимих і керуючих символів і порівнює випадки графем з Intl.Segmenter.

Стандарти та авторство

Чотири форми та їх визначення взято зі стандарту Unicode, Додаток 15 (Unicode Normalization Forms). Кодові точки, що використовуються як приклади розкладання, — ті, які призначає стандарт; ця сторінка сама не реалізує таблиці розкладання — вона викликає власний String.prototype.normalize браузера, тобто ту саму реалізацію, яку платформа використовує для введення тексту, сортування і порівняння, тож тут показано те, що решта системи робить з тим самим рядком. Кластери графем беруться з Intl.Segmenter, де він є в браузері, а там, де його немає, — з описаного наближення.

Текст сторінки написано для цього сайту. Кожен робочий приклад на цій сторінці створено власним кодом сторінки, а потім перевірено за записаним списком випадків файлу даних, який, у свою чергу, перевірено за даними NormalizationTest Unicode для тих самих символів. Таблиці невидимих і небезпечних символів — це курований підмножина, призначена для попереджень, зрозумілих людям, а не повний профіль безпеки Unicode (UTS #39), і сторінка прямо про це каже, а не створює враження повноти, якої немає.