Что такое нормализованный UTF-8?

130

Проект ICU (который теперь также имеет библиотеку PHP ) содержит классы, необходимые для нормализации строк UTF-8, чтобы упростить сравнение значений при поиске.

Однако я пытаюсь понять, что это значит для приложений. Например, в каких случаях мне нужно «Каноническая эквивалентность» вместо «эквивалентности совместимости» или наоборот?

Xeoncross
источник
231
Кто ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t ужасах Лежи в Темное сердце Юникода ͞
ObscureRobot
@ObscureRobot Я действительно хочу знать, могут ли эти дополнительные символы иметь состояния или нет,
eonil
1
@Eonil - я не уверен, что означает состояние в контексте юникода.
ObscureRobot
1
@ObscureRobot Например, некоторый код точки , как это: (begin curved line) (char1) (char2) … (charN) (end curved line)а не так: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). Другими словами, минимальный блок, который можно отрендерить?
eonil
3
Это звучит как хороший вопрос сам по себе.
ObscureRobot

Ответы:

182

Все, что вы никогда не хотели знать о нормализации Unicode

Каноническая нормализация

Юникод включает несколько способов кодирования некоторых символов, в первую очередь символов с диакритическими знаками. Каноническая нормализация превращает кодовые точки в каноническую форму кодирования. Результирующие точки кода должны выглядеть идентичными исходным, за исключением каких-либо ошибок в шрифтах или механизме рендеринга.

Когда использовать

Поскольку результаты кажутся идентичными, всегда безопасно применять каноническую нормализацию к строке перед ее сохранением или отображением, если вы можете допустить, чтобы результат не был побитно идентичным входному.

Каноническая нормализация бывает двух видов: NFD и NFC. Эти две формы эквивалентны в том смысле, что можно конвертировать между этими двумя формами без потерь. Сравнение двух строк в NFC всегда будет давать тот же результат, что и их сравнение в NFD.

NFD

В NFD персонажи полностью раскрыты. Это более быстрая форма нормализации для вычисления, но приводит к большему количеству кодовых точек (т. Е. Использует больше места).

Если вы просто хотите сравнить две строки, которые еще не нормализованы, это предпочтительная форма нормализации, если вы не знаете, что вам нужна нормализация совместимости.

NFC

NFC рекомбинирует кодовые точки, когда это возможно, после запуска алгоритма NFD. Это занимает немного больше времени, но приводит к более коротким строкам.

Нормализация совместимости

Unicode также включает в себя множество символов, которые действительно не принадлежат, но использовались в устаревших наборах символов. Unicode добавил их, чтобы текст в этих наборах символов обрабатывался как Unicode, а затем конвертировался обратно без потерь.

Нормализация совместимости преобразует их в соответствующую последовательность «реальных» символов, а также выполняет каноническую нормализацию. Результаты нормализации совместимости могут не совпадать с оригиналами.

Символы, содержащие информацию о форматировании, заменяются на те, которых нет. Например, персонаж преобразуется в 9. Другие не связаны с различиями в форматировании. Например, римская цифра преобразуется в обычные буквы IX.

Очевидно, что после выполнения этого преобразования преобразование без потерь к исходному набору символов больше невозможно.

Когда использовать

Консорциум Unicode предлагает рассматривать нормализацию совместимости как ToUpperCaseпреобразование. Это то, что может быть полезно в некоторых обстоятельствах, но вы не должны применять его волей-неволей.

Отличным вариантом использования будет поисковая система, поскольку вы, вероятно, захотите, чтобы поиск 9соответствовал .

Одна вещь, которую вам, вероятно, не следует делать, - это отображать пользователю результат применения нормализации совместимости.

NFKC / NFKD

Форма нормализации совместимости бывает двух видов: NFKD и NFKC. У них такие же отношения, как между NFD и C.

Любая строка в NFKC по своей сути также находится в NFC, и то же самое для NFKD и NFD. Таким образом NFKD(x)=NFD(NFKC(x)), и NFKC(x)=NFC(NFKD(x))т. Д.

Вывод

Если сомневаетесь, используйте каноническую нормализацию. Выберите NFC или NFD на основе применимого компромисса между пространством и скоростью или на основе того, что требуется для чего-то, с чем вы взаимодействуете.

Кевин Кэткарт
источник
43
Краткий справочник, чтобы запомнить, что означают сокращения: NF = нормализованная форма D = разложить (распаковать) , C = составить (сжать) K = совместимость (поскольку было взято "C").
Майк Спросс
13
Вы всегда хотите, чтобы NFD все строки на входе выполнялись в первую очередь, а все строки NFC выводились в последнюю очередь. Это хорошо известно.
Христос
4
@tchrist: В целом это хороший совет, за исключением тех редких случаев, когда вы хотите, чтобы вывод был байтом за байтом, идентичным входному, когда не было сделано никаких изменений. Есть и другие случаи, когда вы хотите использовать NFC в памяти или NFD на диске, но это скорее исключение, чем правило.
Кевин Кэткарт,
@Kevin: Да, вход NFD и выход NFC уничтожат синглтоны. Не уверен, что это кого-то волнует, но возможно.
Христос
3
Вы могли подумать, но из приложения: «Чтобы преобразовать строку Unicode в заданную форму нормализации Unicode, первым шагом является полное разложение строки». Таким образом, даже при использовании NFC Q-Caron сначала станет Q + Caron и не сможет перекомпоноваться, поскольку правила стабильности запрещают добавление нового сопоставления композиции. Фактически NFC определяется как NFC(x)=Recompose(NFD(x)).
Кевин Кэткарт,
40

Некоторые символы, например буква с ударением (скажем, é), могут быть представлены двумя способами - одной кодовой точкой U+00E9или простой буквой, за которой следует комбинированный знак ударения U+0065 U+0301. Обычная нормализация выберет один из них, чтобы всегда представлять его (единая кодовая точка для NFC, комбинированная форма для NFD).

Для символов, которые могут быть представлены несколькими последовательностями базовых символов и комбинированных знаков (скажем, «s, точка внизу, точка вверху», а не точка вверху, затем точка внизу или использование базового символа, у которого уже есть одна из точек), NFD будет также выберите один из них (ниже идет первым, как это бывает)

Декомпозиции совместимости включают ряд символов, которые «на самом деле не должны» быть символами, но являются потому, что они использовались в устаревших кодировках. Обычная нормализация не объединит их (для сохранения целостности двустороннего обмена - это не проблема для комбинируемых форм, потому что никакая устаревшая кодировка [кроме нескольких вьетнамских кодировок] не использовала обе), но нормализация совместимости будет. Подумайте о знаке килограмма «kg», который появляется в некоторых восточноазиатских кодировках (или катакане и алфавите половинной / полной ширины), или о лигатуре «fi» в MacRoman.

См. Http://unicode.org/reports/tr15/ для получения дополнительной информации.

Random832
источник
1
Это действительно правильный ответ. Если вы используете только каноническую нормализацию текста, который основан на каком-то устаревшем наборе символов, результат можно без потерь преобразовать обратно в этот набор символов. Если вы используете декомпозицию совместимости, вы останетесь без каких-либо символов совместимости, но больше невозможно преобразовать обратно в исходный набор символов без потерь.
Кевин Кэткарт
13

Нормальные формы (Unicode, а не базы данных) имеют дело в первую очередь (исключительно?) С символами, имеющими диакритические знаки. Unicode предоставляет некоторые символы со «встроенными» диакритическими знаками, например U + 00C0, «латинская заглавная буква A с могилой». Один и тот же символ может быть создан из «Latin Capital A» (U + 0041) с «Combining Grave Accent» (U + 0300). Это означает, что даже если две последовательности производят один и тот же результирующий символ, побайтно сравнение покажет, что они совершенно разные.

Нормализация - это попытка справиться с этим. Нормализация гарантирует (или, по крайней мере, пытается), что все символы закодированы одинаково - либо все с использованием отдельного комбинированного диакритического знака, где это необходимо, либо с использованием единой кодовой точки, где это возможно. С точки зрения сравнения, на самом деле не имеет большого значения, что вы выберете - практически любая нормализованная строка будет правильно сравниваться с другой нормализованной строкой.

В этом случае «совместимость» означает совместимость с кодом, который предполагает, что одна кодовая точка равна одному символу. Если у вас есть такой код, вы, вероятно, захотите использовать обычную форму совместимости. Хотя я никогда не видел, чтобы это прямо говорилось, названия нормальных форм подразумевают, что консорциум Unicode считает предпочтительным использовать отдельные комбинированные диакритические знаки. Это требует большего интеллекта для подсчета фактических символов в строке (а также таких вещей, как разумное прерывание строки), но это более универсально.

Если вы в полной мере используете ICU, скорее всего, вы захотите использовать каноническую нормальную форму. Если вы пытаетесь написать код самостоятельно, который (например) предполагает, что кодовая точка равна символу, то вам, вероятно, понадобится нормальная форма совместимости, которая делает это как можно чаще.

Джерри Гроб
источник
Итак, это та часть, где появляются функции графемы . Мало того, что у символа больше байтов, чем у ASCII, но и несколько последовательностей могут быть одним символом, верно? (В отличие от строковых функций MB .)
Xeoncross
4
Нет, «одна кодовая точка - это один символ» примерно соответствует NFC (тот, у которого есть объединяющие метки, - это NFD, и ни один из них не является «совместимостью»). Нормализации совместимости NFKC / NFKD - это другой вопрос; совместимость (или ее отсутствие) с устаревшими кодировками, в которых, например, были отдельные символы для греческих mu и 'micro' (это забавно, потому что версия «совместимости» - это та, которая находится в блоке Latin 1)
Random832
@ Random832: Ой, совершенно верно. Я должен знать, что лучше не ходить по памяти, если я не работал с ней последние год или два.
Джерри Коффин
@ Random832 Это неправда. Ваше «примерно» слишком много. Рассмотрим две графемы, ō̲̃ и ȭ̲. Есть много способов записать каждый из них, из которых ровно по одному - NFC и один NFD, но существуют и другие. Не в том дело, что только одна кодовая точка. NFD для первого есть "o\x{332}\x{303}\x{304}", а NFC есть "\x{22D}\x{332}". Для второго NFD есть "o\x{332}\x{304}\x{303}"и NFC есть "\x{14D}\x{332}\x{303}". Однако существует множество неканонических возможностей, которые канонически им эквивалентны. Нормализация позволяет бинарное сравнение канонически эквивалентных графем.
Христос
5

Если две строки Unicode канонически эквивалентны, строки действительно одинаковы, только с использованием разных последовательностей Unicode. Например, Ä можно представить с помощью символа Ä или комбинации A и ◌̈.

Если строки эквивалентны только совместимости, строки не обязательно должны быть одинаковыми, но они могут быть одинаковыми в некоторых контекстах. Например, ff можно считать тем же, что и ff.

Итак, если вы сравниваете строки, вы должны использовать каноническую эквивалентность, потому что эквивалентность совместимости не является реальной эквивалентностью.

Но если вы хотите отсортировать набор строк, может иметь смысл использовать эквивалент совместимости, поскольку они почти идентичны.

NikiC
источник
5

На самом деле это довольно просто. UTF-8 фактически имеет несколько различных представлений одного и того же «символа». (Я использую символы в кавычках, поскольку побайтно они разные, но практически одинаковы). Пример приведен в связанном документе.

Символ «Ç» можно представить как последовательность байтов 0xc387. Но он также может быть представлен C(0x43), за которым следует последовательность байтов 0xcca7. Таким образом, вы можете сказать, что 0xc387 и 0x43cca7 - это один и тот же символ. Причина, по которой это работает, заключается в том, что 0xcca7 - это знак объединения; то есть он берет символ перед ним ( Cздесь) и изменяет его.

Теперь, что касается разницы между канонической эквивалентностью и эквивалентностью совместимости, нам нужно взглянуть на символы в целом.

Есть 2 типа символов: те, которые передают значение через значение , и те, которые принимают другой символ и изменяют его. 9 - значимый персонаж. Суперсценарий ⁹ принимает это значение и изменяет его путем представления. Таким образом, канонически они имеют разные значения, но все же представляют собой базовый символ.

Каноническая эквивалентность - это когда последовательность байтов отображает один и тот же символ с одинаковым значением. Эквивалентность совместимости - это когда последовательность байтов отображает другой символ с тем же базовым значением (даже если он может быть изменен). 9 и ⁹ эквивалентны по совместимости, так как оба они означают «9», но канонически не эквивалентны, так как имеют разные представления.

ircmaxell
источник
@tchrist: Прочтите ответ еще раз. Я даже не упомянул о разных способах представления одной и той же кодовой точки. Я сказал, что существует несколько способов представления одного и того же печатного символа (с помощью комбинаторов и нескольких символов). Это относится как к UTF-8, так и к Unicode. Так что ваш отрицательный голос и комментарий на самом деле не имеют отношения к тому, что я сказал. Фактически, я в основном
говорил о
4

Что вам больше подходит - каноническая эквивалентность или эквивалентность совместимости, зависит от вашего приложения. ASCII-образное представление о сравнении строк примерно соответствует канонической эквивалентности, но Unicode представляет множество языков. Я не думаю, что можно безопасно предполагать, что Unicode кодирует все языки таким образом, чтобы вы могли обращаться с ними так же, как с западноевропейским ASCII.

На рисунках 1 и 2 представлены хорошие примеры двух типов эквивалентности. С точки зрения эквивалентности совместимости, похоже, что одно и то же число в форме суб- и надскрипта будет равнозначно. Но я не уверен, что это решит ту же проблему, что и курсивная арабская форма или повернутые символы.

Жесткая правда обработки текста Unicode заключается в том, что вы должны глубоко подумать о требованиях вашего приложения к обработке текста, а затем решить их, как можно лучше, с помощью доступных инструментов. Это не касается напрямую вашего вопроса, но для более подробного ответа потребуются лингвистические эксперты для каждого из языков, которые вы ожидаете поддерживать.

ObscureRobot
источник
1

Проблема сравнения строк : две строки с содержимым, эквивалентным для большинства приложений, могут содержать разные последовательности символов.

См . Каноническую эквивалентность Unicode : если алгоритм сравнения простой (или должен быть быстрым), эквивалентность Unicode не выполняется. Эта проблема возникает, например, при каноническом сравнении XML, см. Http://www.w3.org/TR/xml-c14n

Чтобы избежать этой проблемы ... Какой стандарт использовать? "расширенный UTF8" или "компактный UTF8"?
Используйте «ç» или «c + ◌̧.»?

W3C и другие (например, имена файлов ) предлагают использовать «составленные как канонические» (не забывайте о «самых компактных» более коротких строках) ... Итак,

Стандарт - C ! сомневаетесь в использовании NFC

Для совместимости и для выбора «соглашение по конфигурации» рекомендуется использовать NFC для «канонизации» внешних строк. Например, чтобы сохранить канонический XML, сохраните его в «FORM_C». Рабочая группа W3C CSV в Интернете также рекомендует NFC (раздел 7.2).

PS: de "FORM_C" - это форма по умолчанию в большинстве библиотек. Ex. в PHP normalizer.isnormalized () .


Термин « форма композиции » ( FORM_C) используется как для того, чтобы сказать, что «строка находится в C-канонической форме» (результат преобразования NFC), так и для того, чтобы сказать, что используется алгоритм преобразования ... См. Http: //www.macchiato.com/unicode/nfc-faq

(...) каждая из следующих последовательностей (первые две являются последовательностями из одного символа) представляют один и тот же символ:

  1. U + 00C5 (Å) ЗАГЛАВНАЯ ЛАТИНСКАЯ БУКВА A С КОЛЬЦОМ ВЫШЕ
  2. U + 212B (Å) ЗНАК АНГСТРОМ
  3. U + 0041 (A) ЗАГЛАВНАЯ ЛАТИНСКАЯ БУКВА A + U + 030A (̊) СОЕДИНИТЕЛЬНОЕ КОЛЬЦО ВЫШЕ

Эти последовательности называются канонически эквивалентными. Первая из этих форм называется NFC - для формы нормализации C, где C - для композиции . (...) Функция, преобразующая строку S в форму NFC, может быть сокращена как toNFC(S), а функция , которая проверяет, находится ли S в NFC, сокращенно как isNFC(S).


Примечание: для проверки нормализации маленьких строк (чистый UTF-8 или ссылки на XML-сущности) вы можете использовать этот онлайн-конвертер тестирования / нормализации .

Питер Краусс
источник
Я запутался. Я зашел на эту страницу онлайн-тестера и ввел там: «TÖST MÉ Pleasé». и попробуйте все 4 из приведенных нормализаций - никто не меняет мой текст каким-либо образом, ну, за исключением того, что он меняет коды, используемые для представления этих символов. Неправильно ли я думаю, что «нормализация» означает «удалить все диакритические знаки и тому подобное», а на самом деле это означает - просто изменить кодировку utf ниже?
userfuser
Привет, @userfuser, возможно, вам нужна позиция о приложении: для сравнения или стандартизации вашего текста? Мой пост здесь только о «стандартизации» приложений. PS: когда весь мир использует стандарт, проблема сравнения исчезает.
Питер Краусс