Trochę czasu zeszło, ale przechodząc do rzeczy: Obecnie skupiam się na analizie już napisanego tekstu. Na wejściu jest ciąg znaków elementarnych na podstawie dowolnego tekstu, a na wyjściu chcę otrzymać ciąg znaków elementarnych i złożonych, to znaczy, że chciałbym wyłapać znaki złożone i podciąg znaków elementarnych wymienić na ciąg znaków złożonych. Strukturalnie, obrabiany ciąg to jest lista, w której jedna pozycja to lista liczb całkowitych.
Przykład - dane, numery znaków w hex:
- 61
- 01F3F3
- FE0F
- 200D
- 01F308
- 62
- 6F
- 0337
- 77
- 0337
- 6E
- 0337
- 71
- 01F1EC
- 01F1E7
- 77
Przykład - wynik, jedna pozycja to jeden znak, kilka numerów to znak złożony:
- 61
- 01F3F3,FE0F,200D,01F308
- 62
- 6F,0337
- 77,0337
- 6E,0337
- 71
- 01F1EC,01F1E7
- 77
Przekształcenie składa się z 3 etapów.
Etap 1: Na podstawie Plik1 i Plik2 mam listę nazwanych znaków złożonych, między innymi większość emoji i flagi państwowe. W ciągu wejściowym wyszukuję podciągi będące ciągami znaków z wymienionych plików i wymieniam na pojedynczy znak złożony.
Etap 2: Jest tablica ComplexSuffixCodes
zawierająca wszystkie znaki będące przyrostkami, czyli znakami będącymi dodatkami do znaku poprzedzającego, tzw. "combining character". Ciąg wynikowy z pierwszego etapu jest jednocześnie wejściowy do drugiego etapu. Iteruję go od drugiej pozycji do ostatniej. Jeżeli dana pozycja jest listą jednoelementową i ten element znajduje się w tablicy ComplexSuffixCodes
, to ten numer dopisuję jako kolejny numer do poprzedniej pozycji i usuwam bieżącą pozycję.
Etap 3: Jest tablica ComplexJoinerCodes
zawierająca znaki łączące poprzedni i następny. Ciąg znaków po drugim etapie jest wejściem do etapu trzeciego. Ten ciąg iteruję od drugiej do przedostatniej pozycji. Jeżeli dana pozycja jest listą jednoelementową i ten element znajduje się w tablicy ComplexJoinerCodes
, to łączę ze sobą poprzednią pozycję, bieżącą i następną, a więc następuje zamiana trzech pozycji na jedną.
Jeżeli ciąg wejściowy nie zawiera znaków o numerach od 01F1EC do 01F1FF (znaki tworzące flagi państwowe), to pominięcie etapu 1 (czyli wykonanie tylko etapu 2 i 3) da ten sam wynik, co wykonanie wszystkich trzech etapów po kolei. W ten sposób obsługuję ponadto między innymi nienazwane emoji, których przykłady są NON-RGI, znaki diakrytyczne i selektory wariantów.
A jeżeli ciąg wejściowy zawiera same nazwane emoji bez flag państwowych, to wykonanie samego etapu 1 lub pominięcie etapu 1 i wykonanie etapów 2 i 3 da ten sam wynik.
Na podstawie znaków diakrytycznych z Wikipedii, znaków tagów i własnych prób, udało mi się hardcodować wypełnienie tablic w sposób pokrywający większość znaków.
var ComplexSuffixCodes = [];
var ComplexJoinerCodes = [];
function FillInComplexCodes()
{
// Combining Diacritical Marks
for (var I = 0x0300; I <= 0x036F; I++) { ComplexSuffixCodes.push(I); }
// Combining Cyrillic (selected chars)
for (var I = 0x0483; I <= 0x0489; I++) { ComplexSuffixCodes.push(I); }
// Combining Diacritical Marks Extended
for (var I = 0x1AB0; I <= 0x1AFF; I++) { ComplexSuffixCodes.push(I); }
// Combining Diacritical Marks Supplement
for (var I = 0x1DC0; I <= 0x1DFF; I++) { ComplexSuffixCodes.push(I); }
// Combining Diacritical Marks for Symbols
for (var I = 0x20D0; I <= 0x20FF; I++) { ComplexSuffixCodes.push(I); }
// Cyrillic Extended-A
for (var I = 0x2DE0; I <= 0x2DFF; I++) { ComplexSuffixCodes.push(I); }
// Combining Devanagari (selected chars)
for (var I = 0xA8E0; I <= 0xA8F1; I++) { ComplexSuffixCodes.push(I); }
// Variation Selectors
for (var I = 0xFE00; I <= 0xFE0F; I++) { ComplexSuffixCodes.push(I); }
// Combining Half Marks
for (var I = 0xFE20; I <= 0xFE2F; I++) { ComplexSuffixCodes.push(I); }
// Emoji Modifier Fitzpatrick (selected chars)
for (var I = 0x01F3FB; I <= 0x01F3FF; I++) { ComplexSuffixCodes.push(I); }
// Tags
for (var I = 0x0E0000; I <= 0x0E007F; I++) { ComplexSuffixCodes.push(I); }
// Variation Selectors Supplement
for (var I = 0x0E0100; I <= 0x0E01EF; I++) { ComplexSuffixCodes.push(I); }
// Zero-width joiner
ComplexJoinerCodes.push(0x200D);
}
FillInComplexCodes();
Tam, gdzie w komentarzu odnośnie ComplexSuffixCodes
jest napisane "selected chars", jest dodany pewien zakres znaków, w pozostałych przypadkach do tablicy ComplexSuffixCodes
jest dodany cały blok. Tablica ComplexJoinerCodes
zawiera jeden znak i póki co nie stwierdziłem potrzeby, żeby zawierała inne znaki.
O ile algorytm działa zgodnie z oczekiwaniem i mam żadnych pytań odnośnie jego, o tyle mam wątpliwość, na podstawie czego wypełniać tablice wykorzystywane w etapie 2 i 3.
Znalazłem stronę Combining characters, która zawiera chyba wszystkie znaki potrzebne do etapu 2. To nie jest strona od Unicode, więc pytanie, na podstawie którego lub których plików z oficjalnych plików Unicode można tą informację uzyskać? W jaki sposób autorzy mogli wygenerować tą stronę?
W chwili obecnej przetestowałem między innymi na tytułach filmów tytuł 1 i tytuł 2, algorytm zadziałał prawidłowo i akurat wszystkie potrzebne znaki już mam pokryte.
Jest dużo pojedynczych znaków, które powinny znaleźć się w tablicy ComplexSuffixCodes
, ale są rozrzucone po całym bloku zawierającym znaki z danego języka. Gdzie jest rzetelna informacja, które znaki są znakami typu "combining"?
Czy w przypadku łączników mających skleić i to, co jest po jednej stronie i po drugiej stronie, to czy faktycznie istnieje tylko jeden znak, czyli 0x200D (Zero-width joiner)? Jeśli chodzi o znaki takie, jak "Zero-width non-joiner", "Zero-width space" to oczywiście nie mogą to być łączniki, bo one właśnie wymuszają nieskładanie znaków w jeden.
Chodzi o to, żebym zaimplementował i wykonał algorytm, który wygeneruje kod funkcji FillInComplexCodes()
na podstawie oficjalnej informacji. Jak konsorcjum Unicode wypuści aktualizację standardu, to wezmę nowsze pliki źródłowe i powtórzę proces.