O co chodzi w znormalizowanym UTF-8?


130

Projekt ICU (który ma teraz również bibliotekę PHP ) zawiera klasy potrzebne do normalizacji łańcuchów znaków UTF-8, aby ułatwić porównywanie wartości podczas wyszukiwania.

Jednak próbuję dowiedzieć się, co to oznacza dla aplikacji. Na przykład w jakich przypadkach chcę „Równoważność kanoniczna” zamiast „Równoważność zgodności” lub odwrotnie?


232
Kto ̸͢k̵͟n̴͘ǫw̸̛s͘ w͘͢ḩ̵a҉̡͢t okropności leżeć w ciemnym sercu Unicode ͞
ObscureRobot

@ObscureRobot Naprawdę chcę wiedzieć, czy te dodatkowe symbole mogą mieć stany, czy nie
eonil

1
@Eonil - nie jestem pewien, co oznacza stan w kontekście Unicode.
ObscureRobot

1
@ObscureRobot Na przykład, niektóre punkt kod tak: (begin curved line) (char1) (char2) … (charN) (end curved line)zamiast tego: (curved line marker prefix) (char1) (curved line marker prefix) (char2) (curved line marker prefix) (char2). Innymi słowy, minimalna jednostka, którą można renderować?
eonil

3
Już samo to brzmi jak dobre pytanie.
ObscureRobot

Odpowiedzi:


184

Wszystko, czego nigdy nie chciałeś wiedzieć o normalizacji Unicode

Normalizacja kanoniczna

Unicode obejmuje wiele sposobów kodowania niektórych znaków, w szczególności znaków akcentowanych. Normalizacja kanoniczna zmienia punkty kodowe w kanoniczną formę kodowania. Wynikowe punkty kodowe powinny wyglądać identycznie jak oryginalne, z wyjątkiem błędów w czcionkach lub silniku renderowania.

Kiedy użyć

Ponieważ wyniki wyglądają identycznie, zawsze można bezpiecznie zastosować normalizację kanoniczną do łańcucha przed jego zapisaniem lub wyświetleniem, o ile można tolerować, że wynik nie jest identyczny z bitem za bity, co dane wejściowe.

Normalizacja kanoniczna występuje w 2 formach: NFD i NFC. Obydwa są równoważne w tym sensie, że między tymi dwoma formami można przejść bez strat. Porównanie dwóch ciągów w NFC zawsze da ten sam wynik, co porównanie ich w NFD.

NFD

NFD ma postacie w pełni rozwinięte. Jest to szybsza forma normalizacji do obliczenia, ale skutkuje to większą liczbą punktów kodowych (tj. Zajmuje więcej miejsca).

Jeśli chcesz tylko porównać dwa ciągi, które nie zostały jeszcze znormalizowane, jest to preferowana forma normalizacji, chyba że wiesz, że potrzebujesz normalizacji zgodności.

NFC

NFC ponownie łączy punkty kodowe, jeśli to możliwe, po uruchomieniu algorytmu NFD. Trwa to trochę dłużej, ale skutkuje krótszymi strunami.

Normalizacja zgodności

Unicode zawiera również wiele znaków, które tak naprawdę nie należą, ale były używane w starszych zestawach znaków. Unicode dodał je, aby umożliwić przetwarzanie tekstu w tych zestawach znaków jako Unicode, a następnie konwertowanie z powrotem bez utraty.

Normalizacja zgodności konwertuje je na odpowiednią sekwencję „rzeczywistych” znaków, a także przeprowadza normalizację kanoniczną. Wyniki normalizacji zgodności mogą nie wyglądać identycznie jak oryginały.

Znaki zawierające informacje o formatowaniu są zastępowane znakami, które ich nie zawierają. Na przykład znak zostanie przekonwertowany na 9. Inne nie obejmują różnic w formatowaniu. Na przykład cyfra rzymska jest konwertowana na zwykłe litery IX.

Oczywiście po wykonaniu tej transformacji nie jest już możliwa bezstratna konwersja z powrotem do oryginalnego zestawu znaków.

Kiedy użyć

Konsorcjum Unicode sugeruje myślenie o normalizacji zgodności jak o ToUpperCasetransformacji. Jest to coś, co może się przydać w pewnych okolicznościach, ale nie powinno się jej stosować tylko chcąc nie chcąc.

Doskonałym przypadkiem użycia byłaby wyszukiwarka, ponieważ prawdopodobnie chciałbyś, 9aby pasowało zapytanie .

Jedną z rzeczy, których prawdopodobnie nie powinieneś robić, jest wyświetlanie użytkownikowi wyniku zastosowania normalizacji zgodności.

NFKC / NFKD

Formularz normalizacji zgodności występuje w dwóch formach NFKD i NFKC. Mają taki sam związek jak między NFD i C.

Każdy ciąg w NFKC jest z natury także w NFC i to samo dla NFKD i NFD. Tak więc NFKD(x)=NFD(NFKC(x))i NFKC(x)=NFC(NFKD(x))itd.

Wniosek

W razie wątpliwości przejdź do normalizacji kanonicznej. Wybierz NFC lub NFD w oparciu o odpowiedni kompromis między przestrzenią / prędkością lub w oparciu o to, czego wymaga coś, z czym współpracujesz.


44
Szybkie odniesienie do zapamiętania, co oznaczają skróty: NF = forma znormalizowana D = dekompozycja (dekompresja) , C = kompozycja (kompresja) K = zgodność (ponieważ przyjęto „C”).
Mike Spross

13
Zawsze chcesz, aby wszystkie napisy na wejściu były NFD na samym początku, a wszystkie ciągi na wyjściu NFC były na końcu. To jest dobrze znane.
tchrist

4
@tchrist: To ogólnie dobra rada, z wyjątkiem rzadkich przypadków, w których chcesz, aby dane wyjściowe były bajt po bajcie identyczne z wejściem, gdy nie są wprowadzane żadne zmiany. Istnieją inne przypadki, w których chcesz NFC w pamięci lub NFD na dysku, ale są one raczej wyjątkiem niż regułą.
Kevin Cathcart

@Kevin: Tak, wejście NFD i wyjście NFC zniszczą singletony. Nie jestem pewien, czy kogoś to obchodzi, ale możliwe.
tchrist

3
Można by pomyśleć, ale z załącznika: „Aby przekształcić ciąg znaków Unicode na dany formularz normalizacji Unicode, pierwszym krokiem jest całkowite zdekomponowanie ciągu”. W ten sposób nawet wehn pracujący z NFC, Q-Caron stałby się najpierw Q + Caron i nie mógł się zmienić, ponieważ zasady stabilności zabraniają dodawania nowego mapowania kompozycji. NFC jest efektywnie definiowane jako NFC(x)=Recompose(NFD(x)).
Kevin Cathcart

41

Niektóre znaki, na przykład litera z akcentem (powiedzmy é), można przedstawić na dwa sposoby - pojedynczy punkt kodowy U+00E9lub zwykłą literę, po której następuje łączący znak akcentu U+0065 U+0301. Zwykła normalizacja wybierze jedną z nich, aby zawsze ją reprezentować (pojedynczy punkt kodowy dla NFC, forma łącząca dla NFD).

W przypadku znaków, które mogą być reprezentowane przez wiele sekwencji znaków podstawowych i łączonych znaków (powiedzmy „s, kropka poniżej, kropka powyżej” a umieszczenie kropki powyżej, a następnie kropka poniżej lub użycie znaku podstawowego, który już ma jedną z kropek), funkcja NFD również wybierz jedną z nich (jak to się dzieje, poniżej idzie pierwsza)

Rozkład zgodności zawiera pewną liczbę znaków, które „tak naprawdę nie powinny” być znakami, ale są, ponieważ były używane w starszych kodowaniach. Zwykła normalizacja ich nie ujednolici (aby zachować integralność w obie strony - nie jest to problem dla łączonych formularzy, ponieważ żadne starsze kodowanie [z wyjątkiem kilku wietnamskich kodowań] nie używało obu), ale normalizacja zgodności będzie. Pomyśl jak znak kilograma „kg”, który pojawia się w niektórych kodowaniach wschodnioazjatyckich (lub katakana o połówkowej / pełnej szerokości i alfabet) lub ligatura „fi” w języku MacRoman.

Więcej informacji można znaleźć pod adresem http://unicode.org/reports/tr15/ .


1
To jest rzeczywiście prawidłowa odpowiedź. Jeśli użyjesz tylko normalizacji kanonicznej w tekście, który pochodzi z jakiegoś starszego zestawu znaków, wynik można z powrotem przekonwertować na ten zestaw znaków bez utraty. Jeśli użyjesz dekompozycji zgodności, skończysz bez żadnych znaków zgodności, ale nie jest już możliwa konwersja z powrotem do oryginalnego zestawu znaków bez utraty.
Kevin Cathcart

13

Formy normalne (Unicode, nie bazy danych) dotyczą głównie (wyłącznie?) Znaków ze znakami diakrytycznymi. Unicode udostępnia niektóre znaki z „wbudowanymi” znakami diakrytycznymi, na przykład U + 00C0, „Latin Capital A with Grave”. Ten sam znak może zostać utworzony z „łacińskiej dużej litery A” (U + 0041) z „łączącym poważnym akcentem” (U ​​+ 0300). Oznacza to, że nawet jeśli dwie sekwencje generują ten sam wynikowy znak, bajt po bajcie porównanie pokaże, że są zupełnie inne.

Normalizacja jest próbą rozwiązania tego problemu. Normalizacja zapewnia (lub przynajmniej próbuje), że wszystkie znaki są kodowane w ten sam sposób - albo wszystkie przy użyciu oddzielnego łączącego znaku diakrytycznego, gdy jest to konieczne, albo wszystkie przy użyciu pojedynczego punktu kodowego, jeśli to możliwe. Z punktu widzenia porównania, nie ma większego znaczenia, który wybierzesz - prawie każdy znormalizowany ciąg zostanie poprawnie porównany z innym znormalizowanym ciągiem.

W tym przypadku „zgodność” oznacza zgodność z kodem, który zakłada, że ​​jeden punkt kodowy jest równy jednemu znakowi. Jeśli masz taki kod, prawdopodobnie chcesz użyć normalnej formy zgodności. Chociaż nigdy nie widziałem tego wprost, nazwy form normalnych sugerują, że konsorcjum Unicode uważa, że ​​lepiej jest używać oddzielnych, łączących znaki diakrytyczne. Wymaga to większej inteligencji, aby policzyć rzeczywiste znaki w ciągu (a także takich rzeczy, jak inteligentne łamanie ciągu), ale jest bardziej uniwersalne.

Jeśli w pełni wykorzystujesz OIOM, istnieje prawdopodobieństwo, że chcesz użyć kanonicznej formy normalnej. Jeśli próbujesz samodzielnie napisać kod, który (na przykład) zakłada, że ​​punkt kodowy jest równy znakowi, prawdopodobnie potrzebujesz normalnej formy zgodności, która sprawia, że ​​jest to prawdą tak często, jak to możliwe.


Więc to jest część, w której pojawiają się funkcje Grapheme . Nie tylko znak ma więcej bajtów niż ASCII - ale wiele sekwencji może składać się z jednego znaku, prawda? (W przeciwieństwie do funkcji ciągów MB .)
Xeoncross

4
Nie, „jeden punkt kodowy to jeden znak” odpowiada z grubsza NFC (ten ze znakami łączącymi to NFD, a żaden z nich nie jest „kompatybilny”) - Normalizacje zgodności NFKC / NFKD to inna kwestia; kompatybilność (lub jej brak) dla starszych kodowań, które np. miały oddzielne znaki dla greckiego mu i 'micro' (jest to fajne do przywołania, ponieważ wersja "kompatybilności" jest taka, która znajduje się w bloku Latin 1)
Random832

@ Random832: Ups, zgadza się. Powinienem wiedzieć, że lepiej nie wychodzić z pamięci, kiedy nie pracowałem z tym przez ostatni rok lub dwa.
Jerry Coffin

@ Random832 To nie jest prawda. Twoje „szorstko” jest zbyt tam. Rozważ dwa grafemy, ō̲̃ i ȭ̲. Istnieje wiele sposobów zapisania każdego z nich, z których dokładnie jeden to NFC, a drugi NFD, ale istnieją również inne. W żadnym wypadku nie jest to tylko jeden punkt kodowy. NFD po pierwsze to "o\x{332}\x{303}\x{304}", a NFC to "\x{22D}\x{332}". Po drugie NFD jest "o\x{332}\x{304}\x{303}"i NFC jest "\x{14D}\x{332}\x{303}". Jednak istnieje wiele niekanonicznych możliwości, które są kanonicznie równoważne z tymi. Normalizacja umożliwia binarne porównanie grafemów równoważnych kanonicznie.
tchrist

5

Jeśli dwa ciągi znaków Unicode są kanonicznie równoważne, łańcuchy są naprawdę takie same, tylko używają różnych sekwencji Unicode. Na przykład Ę można przedstawić za pomocą znaku Ę lub kombinacji A i ◌̈.

Jeśli ciągi są tylko odpowiednikami zgodności, to nie zawsze są takie same, ale mogą być takie same w niektórych kontekstach. Np. Ff można uznać za to samo, co ff.

Tak więc, jeśli porównujesz łańcuchy, powinieneś użyć równoważności kanonicznej, ponieważ równoważność zgodności nie jest prawdziwą równoważnością.

Ale jeśli chcesz posortować zestaw ciągów, sensowne może być użycie równoważności zgodności, ponieważ są one prawie identyczne.


5

W rzeczywistości jest to dość proste. W rzeczywistości UTF-8 ma kilka różnych reprezentacji tego samego „znaku”. (Używam znaków w cudzysłowach, ponieważ pod względem bajtów są różne, ale praktycznie są takie same). Przykład podano w połączonym dokumencie.

Znak „Ç” można przedstawić jako sekwencję bajtów 0xc387. Ale może być również reprezentowany przez C(0x43), po którym następuje sekwencja bajtów 0xcca7. Możesz więc powiedzieć, że 0xc387 i 0x43cca7 to ten sam znak. Powodem, który działa, jest to, że 0xcca7 to znak łączący; to znaczy, że przyjmuje znak przed nim (a Ctutaj) i modyfikuje go.

Jeśli chodzi o różnicę między równoważnością kanoniczną a równoważnością zgodności, musimy ogólnie przyjrzeć się znakom.

Istnieją 2 typy znaków, te, które przekazują znaczenie poprzez wartość i te, które przyjmują inny znak i zmieniają go. 9 to znacząca postać. Superskrypt ⁹ przyjmuje to znaczenie i zmienia je poprzez prezentację. Zatem kanonicznie mają różne znaczenia, ale nadal reprezentują podstawowy charakter.

Równoważność kanoniczna występuje wtedy, gdy sekwencja bajtów renderuje ten sam znak o tym samym znaczeniu. Równoważność zgodności ma miejsce, gdy sekwencja bajtów renderuje inny znak o tym samym znaczeniu podstawowym (nawet jeśli może zostać zmieniony). 9 i ⁹ są równoważne zgodności, ponieważ oba oznaczają „9”, ale nie są równoważne kanonicznie, ponieważ nie mają tej samej reprezentacji.


@tchrist: Przeczytaj odpowiedź ponownie. Nigdy nawet nie wspomniałem o różnych sposobach przedstawiania tego samego punktu kodowego. Powiedziałem, że istnieje wiele sposobów przedstawiania tego samego drukowanego znaku (za pomocą kombinatorów i wielu znaków). Dotyczy to zarówno UTF-8, jak i Unicode. Więc twój głos przeciw i komentarz tak naprawdę nie odnoszą się do tego, co powiedziałem. Właściwie to w zasadzie robiłem to samo, co na górnym plakacie (choć nie tak dobrze) ...
ircmaxell

4

To, czy równoważność kanoniczna lub równoważność zgodności jest bardziej odpowiednia dla Ciebie, zależy od aplikacji. Sposób myślenia ASCII o porównaniach ciągów z grubsza odwzorowuje równoważność kanoniczną, ale Unicode reprezentuje wiele języków. Nie sądzę, aby można było bezpiecznie założyć, że Unicode koduje wszystkie języki w sposób, który pozwala traktować je tak, jak zachodnioeuropejskie ASCII.

Rysunki 1 i 2 przedstawiają dobre przykłady obu typów równoważności. W przypadku równoważności zgodności wygląda na to, że ta sama liczba w postaci skryptu podrzędnego i nadskryptowego byłaby równa. Ale nie jestem pewien, czy rozwiązuje ten sam problem, co kursywa arabska forma lub obrócone znaki.

Trudna prawda o przetwarzaniu tekstu w standardzie Unicode polega na tym, że musisz głęboko przemyśleć wymagania dotyczące przetwarzania tekstu w aplikacji, a następnie zająć się nimi najlepiej, jak potrafisz, za pomocą dostępnych narzędzi. To nie dotyczy bezpośrednio twojego pytania, ale bardziej szczegółowa odpowiedź wymagałaby ekspertów lingwistycznych dla każdego z języków, które chcesz obsługiwać.


1

Problem porównywania łańcuchów : dwa łańcuchy z treścią, która jest równoważna do celów większości aplikacji, mogą zawierać różne sekwencje znaków.

Zobacz równoważność kanoniczna Unicode : jeśli algorytm porównania jest prosty (lub musi być szybki), równoważność Unicode nie jest wykonywana. Ten problem występuje na przykład w porównaniu kanonicznym XML, patrz http://www.w3.org/TR/xml-c14n

Aby uniknąć tego problemu ... Jakiego standardu użyć? „rozszerzony UTF8” czy „kompaktowy UTF8”?
Użyj „ç” lub „c + ◌̧.”?

W3C i inne (np. Nazwy plików ) sugerują użycie „skomponowanych jako kanonicznych” (weź pod uwagę C „najbardziej zwarte” krótsze ciągi) ... Więc,

Standardem jest C ! w razie wątpliwości użyj NFC

W celu zapewnienia współdziałania i wyborów typu „konwencja zamiast konfiguracji” zaleca się użycie NFC w celu „kanonizowania” zewnętrznych ciągów. Na przykład, aby przechowywać kanoniczny kod XML, należy go zapisać w „FORM_C”. CSV W3C w internetowej grupie roboczej również zaleca NFC (sekcja 7.2).

PS: de „FORM_C” jest domyślną formą w większości bibliotek. Dawny. w normalizer.isnormalized () PHP .


Terminu „ forma kompozycyjna ” ( FORM_C) używa się zarówno do określenia „ciąg znaków w formie kanonicznej C” (będącej wynikiem transformacji NFC), jak i do stwierdzenia, że ​​używany jest algorytm przekształcania ... Zobacz : http: //www.macchiato.com/unicode/nfc-faq

(...) każda z poniższych sekwencji (dwie pierwsze to sekwencje jednoznakowe) reprezentują ten sam znak:

  1. U + 00C5 (Å) ŁACIŃSKA WIELKA LITERA A Z PIERŚCIENIEM POWYŻEJ
  2. U + 212B (Å) ANGSTROM SIGN
  3. U + 0041 (A) ŁACIŃSKA WIELKA LITERA A + U + 030A (̊) PIERŚCIEŃ ŁĄCZĄCY POWYŻEJ

Te sekwencje nazywane są kanonicznie równoważnymi. Pierwsza z tych form nosi nazwę NFC - dla formularza normalizacji C, gdzie C oznacza kompozycję . (...) Funkcja przekształcająca ciąg znaków S w postać NFC może być skracana jako toNFC(S), podczas gdy funkcja sprawdzająca, czy S jest w NFC, jest skracana do isNFC(S).


Uwaga: aby przetestować normalizację małych ciągów znaków (czyste odniesienia UTF-8 lub XML-encje), możesz użyć tego konwertera test / normalizacja online .


Jestem zmieszany. Poszedłem na tę stronę testera online i wpisałem tam: „TÖST MÉ pleasé”. i wypróbuj wszystkie 4 z podanych normalizacji - żadna nie zmienia w żaden sposób mojego tekstu, cóż, poza tym, że zmienia kody używane do prezentowania tych znaków. Czy błędnie myślę, że „normalizacja” oznacza „usunięcie wszystkich znaków diakrytycznych i podobnych”, a tak naprawdę oznacza - po prostu zmienić kodowanie utf poniżej?
userfuser

Cześć @userfuser, może potrzebujesz stanowiska, o aplikacji: czy chcesz porównać, czy ujednolicić swój tekst? Mój post tutaj dotyczy tylko "standaryzacji" aplikacji. PS: kiedy cały świat używa standardu, problem porównania znika.
Peter Krauss,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.