Spis treści

Unicode dla programistów - notatki

🥑 ☕ 🧘 🐧 💻 💩 ❤️

Unicode to jeden z tematów, o których od dawna chciałam napisać: zebrać w jedno miejsce odnośniki czy definicje, których zawsze gdzieś szukam, trochę uporządkować swoją wiedzę, a przy okazji przybliżyć zagadnienie tym osobom, które chciałyby się na ten temat dowiedzieć więcej.

W tym wpisie opiszę krótko czym jest Unicode, przedstawię klika kluczowych pojęć i przedstawię na przykładach jak wygląda obsługa Unicode w dwóch językach programowania: w Javie i w JavaScript.

Zapraszam.

Unicode - standard

Unicode to standard kodowania, reprezentacji i obsługi tekstu dla większości systemów pisma (patrz: unicode faq. Dokumentacja do ostatnio wydanej wersji (w dniu 2021-05-24 jest to Unicode 13.0) standardu znajduje się pod tym odnośnikiem:

http://www.unicode.org/versions/latest/

Cztery części standardu

Standard składa się z czterech części:

  1. Specyfikacji dostępnej w postaci pojedynczego pdf-a (14 MB)
  2. Tabel ze znakami. Dostępnych jest kilka tabel (dla różnych potrzeb):
  • pełna, aktualizowana na bieżąco lista znaków podzielona - dla wygodniejszego przeglądania - na ponad 150 skryptów i na płaszczyzny (planes) oraz bloki (podział ze względu na zakresy wartości), wraz z indeksem pozwalającym na przeszukiwanie znaków po nazwie
  • w wersji 13.0.0: tabela znaków z zaznaczonymi zmianami dodanymi w tej wersji (“delta”)
  • cały zbiór znaków, dostępny w momencie publikacji wersji 13.0.0 standardu
  1. Załączników do Standardu Unicode
  2. Bazy danych znaków Unicode

Standard nie tylko zawiera same znaki, lecz również szczegółowe informacje dotyczące zasad normalizacji (niezbędnej ze względu na to, że wiele abstrakcyjnych znaków może być reprezentowanych na więcej niż jeden sposób, tj. może zostać poprawnie zakodowanych jako różne sekwencje code-pointów), dekompozycji, zasad uporządkowania w ramach “collation”, renderowania czy wyświetlania tekstów “dwukierunkowych” (np. w tekstach wielojęzycznych) w których pojawiają się zarówno fragmenty “od lewej do prawej” jak i “od prawej do lewej”.

Liczba znaków w standardzie

Każda kolejna wersja włącza w standard nowe znaki (bądź całe ich zakresy). W bieżącej wersji jest ich już prawie 144 tysiące:

Wersja Rok publikacji Liczba znaków
1.0 1991 7_161
9.0 2016 128_172
13.0 2021 143_859

Definicje

Unicode określa unikalny numer dla każdego znaku, niezależny od platformy sprzętowej, systemu operacyjnego, używanego programu czy języka.

Unicode jest więc uniwersalnym zestawiem znaków z większości systemów pisma, a każdemu ze znaków przypisuje unikalny numer (tzw. code point).

Code Point

Code point to wartość z przestrzeni wartości Unicode (od U+0000 do U+10FFFF). Nie wszyskie code pointy są przypisane do znaków.

Code pointy to liczby z zakresu od U+0000 do U+10FFFF. Zapis U+hex składa się z prefiksu U+ oraz szesnastkowej wartości hex oznaczającej indeks - położenie - code pointu w wielkiej, unikodowej tablicy znaków.

Oto przykłady: code point, znak reprezentowany przez ten code point oraz jego pełna nazwa:

Code point znak nazwa
00105 ą A WITH OGONEK, LATIN SMALL LETTER
003B4 δ GREEK SMALL LETTER DELTA
1F7E9 🟩 LARGE GREEN SQUARE
1F385 🎅 FATHER CHRISTMAS
1f49D 💝 HEART WITH RIBBON

Nie wszystkie code pointy mają przypisane jakieś znaki. W zakresie od U+0000 do U+10FFF jest dostępnych 1_141_112 wartości, a przypisanych znaków (posiadających znaczenie, widocznych jakp glyph lub widoczna spacja) jest 143_696.

Każdy code point należy do jednej z Ogólnych Kategorii: może to być m.in. litera, znak, liczba, znak przestankowy, symbol, separator.

Typy code pointów

W standardzie istnieje siedem typów code pointów: Graphic, Format, Control, Private-Use, Surrogate, Noncharacter, Reserved (patrz definicja D10a).

Pary surogatów

  • code pointy w zakresie U+D800 - U+DBFF (1024 code pointy) są nazwane high surogate code points,
  • code pointy w zakresie U+DC00 - U+DFFF (1024 code pointy) są nazwane low surogate code ponts

Następujące po sobie high-surogate a następnie low-surogate tworzą parę surogatów w kodowaniu UTF-16 i reprezentują code-pointy większe niż U+FFFF. Powyższe code pointy nie mogą być stosowane w innych kontekstach.

Noncharacters

Istnieje mały zbiór znkaów, które nazwane zostaly “nieznakami” (“noncharacters”) i choć aplikacje mogą ich używać, jeśli chcą, to nie powinny być używane. Są to:

  • code pointy z zakresu U+FDD0 - U_FDEF oraz
  • wszystkie code pointy konczące sie wartoscią FFFE lub FFFF (tj.: U+FFFF, U+1FFFE, … U+10FFFE, U+10FFFF)

Prywatne code pointy

Istnieją zakresy code pointów, które są uznawane za przypisane do znaków, jednak nie mają żadnej narzuconej przez Unicode interpretacji i można ich używać przy założeniu, że zarówno nadawca, jak i odbiorca porozumieli się do do ich znaczenia. Należą do nich:

  • Private Use Area: U+E000–U+F8FF (6_400 znaków),
  • Supplementary Private Use Area-A: U+F0000–U+FFFFD (65_534 znaków),
  • Supplementary Private Use Area-B: U+100000–U+10FFFD (65_534 znaków).

Znaki formatowania

Te nie mają widocznego kształu, ale mogą mieć wpływ na sąsiadujące znaki Np. U+200C (ZERO WIDTH NON-JOINER) czy U+200D (ZERO WIDTH JOINTER) może być użyty do zmiany kształtu (np. do usunięcia lub wprowadzeniu ligatury).

Zarezerwowane code pointy

Tu znajdują się te code pointy, które są dostępne do użycia, lecz jeszcze nie przypisane (w Unicode 13.0.0 jest ich 830_606).

Znak abstrakcyjny - abstract character

Znak abstrakcyjny lub znak jest jednostką informacji używaną do organizowania, kontroli bądź reprezentacji danych teksowych.

Znaki są w Unicode pewną abstrakcją - każdy z nich posiada określoną nazwę, np. LATIN SMALL LETTER U. Wyrenderowana forma tego znaku (tzw. glyph) wygląda tak: a.

Nie wszystkie znaki abstrakcyjne są zakodowane jako pojedyncze code pointy; niektóre znaki są kodowane w Unicode jako sekwencja dwóch lub kilku code pointów. Na przykład, występujący w języku łacińskim znak małej litery i z ogonkiem, kropką u góry i akcentem jest reprezentowana przez sekwencję U+012F, U+0307, U+0301. Są to tak zwane Unicode Named Sequences, a ich lista jest dostępna pod adresem https://unicode.org/Public/UNIDATA/NamedSequences.txt

Płaszczyzna (plane)

Płaszczyzna to zakres 65_536 (10_00016) następujących po sobie code pointów: od U+n0000 do U+nFFFF, gdzie n przyjmuje wartości od 016 do 1016)

Płaszczyzny dzielą code pointy na 17 równych grup:

  • Płaszczyzna 0 zawiera code pointy od U+00000 do U+0FFFF (Basic Multilingial Plane)

  • Płaszczyzna 1 zawiera code pointy od U+10000 do U+1FFFF (Supplementary Multilingual Plane)

  • Płaszczyzna 2 zawiera code pointy od U+20000 do U+2FFFF (Supplementary Ideographic Plane)

  • Płaszczyzna 15 zawiera code pointy od U+F0000 do U+FFFFF (Supplementaty Private Use Area A)

  • Płaszczyzna 16 zawiera code pointy od U+100000 do U+10FFFF (Supplementaty Private Use Area B)

Plane 0 - płaszczyzna 0 - jest szczególna, to Basic Multilingial Plane (BMP) zawierająca większość znaków ze współczesnych języków (podstawowe łacińskie, cyrylicę, znaki greckie) oraz wiele symboli.

Płaszczyzny od 1 do 16 to płaszczyzny astralne (astral planes), a code pointy w tych zakresach to astral code points. Znaki znajdujące się w zakresie U+10000 do U+10FFFF nazywane są znakami dodatkowymi (suplementary characters).

Co ciekawe, to właśnie w “płaszczyznach astralnych” znajdują się wszystkie zabawne emoji (które młodsze pokolenie nazywa “emotkami”).

Emoji

Oto link do listy Emoji.

Przegląd kategorii

Symbole

Jednym z najbardziej ekscytujących zastosowań Unicode stało się używanie Emojis. Nazwa wzięła się ze słowa “emotion”, bo początkowo emojis służyły wyrażaniu emocji osoby piszącej i ograniczały się do “buziek” uśmiechniętych lub zasmuconych.

Teraz emojis to dużo, dużo więcej. Zawierają znaki i sekwencje, dzięki którym kodowane są kotki (😺, 😽), serduszka (💖, 💔, 💌) i minki(😓, 🥱, 😳), symbole (💥, 💣, 💦), potwory (👹, 👻, 👽, 🤖), znaki czynione dłonią (🤏, 🖖), części ciała (🫁, (ups, po tym jak wstawiłam mózg, zepsuł mi się nvim…)

###Twarze

Kodowanie obrazków przedstawiające twarze różnych osób ma bardzo ciekawe aspekty: zostały utworzone takie znaki emoji, które nie sugerują płci, są “gender neural”, stąd obok znaków “man: curly hair” 👨‍🦱 czy “woman: curly hair” 👩‍🦱 znajdziemy też “person: curly hair” 🧑‍🦱; emoji reprezentujące ludzkie ciało mają również różne wersje reprezentujące różne odcienie koloru skóry.

Gesty

Na przykład: face-palm :) 🤦

Zawody

Są też przeróżne zawody, również w wariantach wielu płci: cook 🧑‍🍳, woman cook 👩‍🍳 and man cook 👨‍🍳.

I można by tak bez końca: rodziny, rośliny, zwierzęta, warzywa, pieczywo, potrawy, słodycze, napoje, nakrycia stołowe, budowle, środki transportu, zegary, fazy księżyca, pogoda, sport, święta, ubrania, torby, buty, instrumenty…

O - znalazłam nawet szczoteczkę do zębów…🪥 - to jeden z nowododanych znaków., podobnie jak mężczyzn karmiący dziecko 👨‍🍼.

Tutaj są wszystkie nowo dodane emoji

Kodowanie emoji

Ponieważ emoji leżą poza Basic Multilingual Plane, wartości ich code pointów są liczbami szesnastkowymi o ponad czterech cyfrach. Do ich zakodowania nie wystarczą dwa bajty. Oznacza to, że nie “zmieszczą się” w jednym znaku UTF-16 i muszą być zakodowane jako ciąg znaków.

Na przykład, symbol pieska 🐶 (U+1F436), choć jego kod złożony jest z tylko jednego code pointa, jest kodowany jako para surogatów \uD83D\uDC36.

Niektóre emoji złożone są nie z jednego, lecz z kilku code pointów. Na przykład ów mężczyzna karmiący dziecko składa się z z aż trzech: U+1F468 U+200D U+1F37C - pierwszy to mężczyzna (👨), ostatni to butelka(🍼).

Inny przykład:👩‍❤️‍💋‍👨 - “kiss: woman, man” ma kod złożony z ośmiu code pointów! (patrz index)

Jednostka kodowa - code unit

W pamięci komputera nie znajdują się abstrakcyjne znaki ani code pointy. Komputer musi więc mieć jakąś fizyczny, ściśle określony sposób zapisywania znaków. Służy do tego pojęcie code unit.

Code unit to minimalna sekwencja bitów, jaka może zostać użyta do reprezentacji jednostki zakodowanego tekstu. Standard Unicode używa 8-bitowych code unitów w kodowaniu UTF-8, 16-bitowych code pointów w kodowaniu UTF-16 i 32-bitowych code unitów w kodowaniu UTF-32.

To właśnie kodowanie znaków (character encoding) pozwala przekształcić abstrakcyjne code pointy (liczby) w fizyczne byty (code units, czyli np. dwubajtowe chary).

Najbardziej popuralne sposoby kodowania to UTF-8, UTF-16 oraz UTF-32.

UTF-8

UTF-8 (8-bit Unicode Transformation Format) to kodowanie znaków zmiennej długości umożliwiające zakodowanie wszystkich poprawnych code pointów w Unicide przy użyciu od jednego do czterech ośmiobitowych bajtów.

UTF-8 to kodowanie domyślne w Linuksie, a także domyślne kodowanie w Internecie.

Pierwsze 128 znaków Unicode odpowiada dokładnie znakom w kodzie ASCII i jest zakodowanych w UTF-8 przy użyciu jednego bajtu o takiej samej wartości jak wartość kodu ASCII. Dlatego też poprawny tekst w ASCII jest również poprawnym tekstem Unicode zakodowanym przy użyciu UTF-8.

UTF-16

UTF-16 (16-bit Unicode Transformation Format) jest kolejnym popularnym standardem kodowania Unicode. To również kodowanie zmiennej długości. Code pointy są kodowane przy użyciu jednego lub dwóch code-unitów - w tym przypadku code unit jest szesnastobitowy). Każdy code point zajmuje więc conajmniej 2 i co najwyżej 4 bajty.

Jest to równiez kodowanie używane w Windowsie (do plików tekstowych czy w edytorach tekstu), a także kodowanie używane “wewnętrznie” w najważniejszych językach programowania: w Javie i w JavaScripcie.

Normalizacja napisów

Dość często, zwykle w kontekście porównywania, sortowania czy dopasowywania unikodowych napisów do wzorca (regex) pojawia się problem niejednoznaczności zapisu tego samego (semantycznie) znaku: widzimy ten sam grafem (graficzną reprezentację), widzimy pewien “znak”, jednak to może być on w rzeczywistości kodowany na więcej niż jeden sposób.

  • Przykład 1: w przypadku znaków posiadających akcent istnieje więcej niż jedna reprezentacja: znak é może być wyrażony jako U+00E9 albo jako kombinacja zwykłej litery e (U+0065) i znaku COMBINING ACUTE ACCENT (U+0301)
  • Przykład 2: znak ñ ma reprezentację w postaci jednego code pointa U+00F1 lub w postaci kombinacji litery n i znaku ~ (U+0303).
  • Przykład 3: à może być zakodowane jako U+0061 (a) a nasętpnie U+0300 (GRAVE ACCENT). Może być również dowodany jako jeden code point, to jest U+00E0 (A WITH GRAVE ACCENT)

Znak U+0310 (acute accent) oraz znak U+0300 (grave accent) to tzw. znaki łączące (combining marks). W Unicode znak może być reprezentowany przez code point nie będący combining mark oraz następujący po nim ciąg code pointów, które są combining marks.

Unicode dopuszcza taką wieloznaczność z powodów głównie historycznych - pewne pradawne (legacy) zestawy znaków posiadały już mapowanie jeden-do-jeden (znak na code unit) dla znaków z “upiększeniami” (akcentami, ogonkami itp.), więc Unicode Consortium uznało, że warto również takie mapowanie zachować w standardzie.

Rozwiązaniem problemu niejednoznaczności reprezentacji jest normalizacja (zdefiniowana w załączniku Unicode Standard ANnex #15).

Sposoby normalizacji tekstu w Javie i Javascript opiszę w akapitach poświęconych tym językom (patrz niżej).

Języki programowania

Kodowanie wewnętrzne a kodowanie domyślne

Wewnętrznie używane kodowanie to kodowanie używane przez JVM (w przypadku Javy) lub środowisko uruchomieniowe JavaScript (nodejs, deno). Trzeba je odróżnić od np. domyślnego kodowania znaków w programie napisanym w danym języku programowania. Na przykład w Javie napisy (obiekty typu String) są na heapie zakodowane właśnie jako UTF-16 (to właśnie “użytek wewnętrzny”; ta wewnętrzna reprezentacja została “zoptymalizowana” w Javie 9, patrz JEP 254: Compact Strings), jednak metoda String.getBytes() zwraca bajty zakodowane przy pomocy tzw. domyślnego zestawu znaków (default charset), którym jest - w przypadku Javy - UTF-8.

Java

Kodowanie źródeł

Źródła Javy mogą być zakodowane w dowolnym unikodowym kodowaniu, np. UTF-8 czy UTF-16. Zgodnie ze specyfikacją: Chapter 3 Lexical Structure:

Programs are written using the Unicode character set.

Unicode Escapes

W źródłach można używać unikodowych sekwencji unikowych (Uniocode escapes) postaci \uXXXX (patrz: Section 3.2 Lexical Translations):

Unicode escape of the form \uxxxx, where xxxx is a hexadecimal value, represents the UTF-16 code unit whose encoding is xxxx.

Unicode escapes w źródłach są czasami źródłem ciekawych pytań dotyczących Javy (czy Java może uruchamiać komentarze), warto więc czasem zwrócić więszką uwagę na ukryte sekwencje unikowe.

JLS określa sposób przekształcenia kodu źródłowego w kodowaniu Unicode na źródło w kodowaniu ASCII (dzięki czemu źródła mogą być przetwarzane przez narzędzia operujące jedynie na zestawie znaków ASCII; tę skonwertowaną postać rozumie również kompilator) i jest to sposób odwracalny, dzięki czemu można również uzyskać ponownie tekst zakodowany kodowaniem Unicode.

API

Warto przyjrzeć się bliżej, jakim API dysponuje programista, który chce np. używać znaków rozszerzonych. W Javie 5 rozszerzono w tym celu klasę Character: “nowe” API to konwertery między typem char a wartościami code pointów oraz metody weryfikujące poprawność lub mapujące chary na code pointy.

Metody konwertujące w klasie Character

Metoda Opis
toChars(int codePoint, char[] dst, int dstIndex) konweruje podany unikodowy code point do reprezentacji UTF-16 i umieszcza go w tablicy dst pod podanym indeksem
toCodePoint(char high, char low) konwertuje parę surogatów do wartości code pointa reprezentującego znak pomocniczy
codePointAt(char[] a, int index), codePointAt(char[] a, int index, int limit), codePointAt(CharSequence seq, int index) zwracają unikodowy code point na podanym indeksie. Druga metoda nakłada ograniczenie na indeks, trzecia metoda bierze jako parametr CharSequence.
codePointBefore(char[] a, int index), codePointBefore(char[] a, int index, int start), codePointBefore(CharSequence seq, int index), codePointBefore(char[], int, int) metody zwrcają code point przed podanym indeksem
charCount(int codePoint) zwraca wartość 1 jeśli code point może być zareprezentowny jako pojedynczy char, a wartość 2 jeśli podano znak pomocniczy, który wymaga dwóch bajtów

Weryfikacja i mapowanie

Ponieważ metody operujące na typie podstwowym char (takie jak np. isLowerCase(char) czy isDigit(char) okazały się niewystarczające, dodano metody operujące na typie int. Te nowe metody nie sprawdzają poprawności przekazanej wartości, dlatego dodano również:

Metoda Opis
isValidCodePont(int) metodę sprawdzającą poprawność wartości typu int czyli w przedziale 0x0000 do 0x10FFFF
isSupplementaryCodePoint(int) metodę sprawdzającą, czy wartość jest znakiem pomocniczym czyli w przedziale od 0x10000 do 0x10FFFF włącznie
isHighSurrogate(char) metodę sprawdzającą, czy znak jest poprawnym high surrogate czyli od \uD800 do \uDBFF
isLowSurrogate(char) metodę sprawdzającą, czy znak jest poprawnym Low surrogate czyli od \uDC00 do \uDFFF
codePointCount(CharSequence, int, int), codePointCount(char[], int, int) metodę zwracającą liczbę code pointów w sekwencji znaków lub tablicy charów

Metody w klasie String

Do obsługi Unicode pojawił się w klasach String, StringBuilder czy StringBuffer nowe konstruktory i metody działające na znakach rozszerzonych: codePointAt(int), codePontBefore(int), codePointCount(int beginIdx, int endIdx), appendCodePoint(int) czy offsetByCodePonts(int index, int codePointOffset).

Przykład

To przykładowy programik, w którym rozkładam napis na code pointy i znaki, badam ich długość:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
import java.util.Arrays;
import java.util.List;

import java.util.stream.IntStream;
import java.util.stream.Collectors;
public class UnicodePlay{

  static void fmt(String fmt, Object... rest) {
    System.out.format(fmt, rest);
  }

  static String formattedCodePoint(int codePoint) {
    return String.format("U+%04X", codePoint);
  }
  
  static String formattedCodePoints(String str) {
    return str.codePoints()
      .mapToObj(UnicodePlay::formattedCodePoint)
      .collect(Collectors.joining(" "));
  }


  static void checkUnicode(String str) {
    var codePointsCount = Character.codePointCount(str, 0, str.length());
    fmt("%n%nChecking string %s%n", str);
    fmt("String length is: %s%n", str.length());
    fmt("Code point count is: %s%n", codePointsCount);

    fmt("List of code points: %s%n", formattedCodePoints(str));

    

    var header =   "|%3s|%4s|%8X|%5s|%5s|%16s|%n"; 
    var strheader ="|%3s|%4s|%8s|%5s|%5s|%16s|%n"; 
    fmt(strheader, "idx", "znak", "int", "is hs", "is ls", "codepointAt(idx)"); 
    IntStream.range(0, str.length())
      .forEach(i -> 
          fmt(header, 
            i, 
            Character.toString(str.codePointAt(i)), 
            (int) str.charAt(i), 
            Character.isHighSurrogate(str.charAt(i)),
            Character.isLowSurrogate(str.charAt(i)),
            formattedCodePoint(str.codePointAt(i))));


   
    fmt ("Iterate over chars%n");
    str.chars().forEach(i -> fmt("%04X ", i));
  }

  public static void main(String[] args) {
    var strings = List.of(
        "👩‍❤️‍💋‍👨",  
        "道", 
        "zażółć",
        "👨‍🍼");

    strings.forEach(UnicodePlay::checkUnicode);
  }
  
}

A to przykładowe wyjście z programu dla napisu “👨‍🍼”:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13

Checking string 👨‍🍼
String length is: 5
Code point count is: 3
List of code points: U+1F468 U+200D U+1F37C
|idx|znak|     int|is hs|is ls|codepointAt(idx)|
|  0|  👨|    D83D| true|false|         U+1F468|
|  1|   ?|    DC68|false| true|          U+DC68|
|  2||    200D|false|false|          U+200D|
|  3|  🍼|    D83C| true|false|         U+1F37C|
|  4|   ?|    DF7C|false| true|          U+DF7C|
Iterate over chars
D83D DC68 200D D83C DF7C

Normalizacja w Javie

Do normalizacji napisów w programie napisanym w Javie można użyć klasy java.text.Normalizer, a listę formatów/typów normalizacji można znależć w enumie java.text.Normalizer.Form

W3C rekomenduje użycie normalizacji NFC, ponieważ większość starych kodowań znaków również używa “skomponowanych” wariantów znaków i nie koduje oddzielnie combibing marks.

Tekst warto normalizować tylko wówczas, gdy istnieje taka potrzeba. Można łatwo sprwdzić, czy tekst jest znormalizowany - służy do tego metoda isNormalized(). Jeśli zwróci ona false, należy użyć metody normalize(), która normalizuje wartości char zgodnie z podanym typem normalizacji:

1
var normalized_string = Normalizer.normalize(target_chars, Normalizer.Form.NFC);

Gotowy program demonstrujący użycie różnych formatów (typów) normalizacji znajduje się na stronie Oracle: NormSample.java

JavaScript

Kodowanie plików źródłowych

Przeglądarki domyślnie zakładają, że źródła programu w JavaScript są zapisane w lokalnym kodowaniu, dlatego, aby uniknąć nieprzewidzianego zachowania, należy wyspecyfikować kodowanie źródeł JS. Jak to zrobić?

Jest kilka sposobów:

  • ustawić BOM jako pierwszy znak w pliku (nie zalecane dla UTF-8)
  • jeśli plik jest ściągany z sieci, można ustawić nagłówek Content-Type, np. Content-Type: application/javascript; charset=utf-8
  • w przypadku braku nagłówka sprawdzany jest atrybut charset w tagu script:
1
<script src="./app.js" charset="utf-8">
  • w przypadku jego braku przyjmuje się charset z tagu meta:
1
2
3
<head>
<meta charset="utf-8" />
</head>

Kodowane wewnętrzne

Silnik JavaScript po wczytaniu źródła JS konwertuje je wewnętrznie na UTF-16, zgodnie ze standardem ECMAScript:

When a String contains actual textual data, each element is considered to be a single UTF-16 code unit.

Używanie Unicode w napisie

Podobnie jak w Javie, literał napisowy może zawierać sekwecję w formacie \uXXXX:

1
2
3
4
5
6
7
8
const s1 = '\u00E9' //é
const s2 = '\u0065\u0301' //é
const s3 = 'e\u0301' //é
s1.length === 1 //true
s2.length === 2 //true
s3.length === 2 //true
s2 === s3 //true
s1 !== s3 //true

Normalizacja w JavaScript

Normalizacja to proces pozbywania się niejednoznaczności w reprezentacji napisu. ES6/ES2015 wprowadziła do metod Stringa funkcję normalize():

1
s1.normalize() === s3.normalize() //true

Zakończenie

Temat Unicode jest ogromny. W tym artykule przedstawiłam podstawowe definicje związane z Unicodem i bardzo zachęcam do sięgnięcia do źródeł, w szczególności do kanonicznego już tekstu Joela Spolsky’ego.

Myślę, że warto mieć podstawowe pojęcie o tym, czym Unicode jest oraz poczytać o różnych sposobach kodowania znaków. Tekst, znaki, kodowanie - to nasze, programistów, “tworzywo”, którym posługujemy się na co dzień i dobrze jest choć trochę znać materię, z którą się pracuje.

Źródła: