È abbanza divertente capire come funzionano le cose che usiamo ogni giorno. Prendiamo[1] una stringa ASCII:
"abcde".size #=> 5
ora sostituiamo la "e" con la "è" accentata e otteniamo una stringa UTF-8:
"abcdè".size #=> 6
come si vede, la seconda stringa pur avendo lo stesso numero di caratteri della prima "pesa" un byte in più, questo significa che mentre [a,b,c,d,e] sono rappresentati con un byte, per rappresentare la lettera è sono necessari 2 byte.
In pratica, per andare al sodo, UTF-8 utilizza un numero variabile di byte per rappresentare un singolo carattere, seguendo questo schema:
| 1° Byte | 2° Byte | 3° Byte | Numero di bits liberi | Caratteri rappresentabili |
|---|---|---|---|---|
| 0xxxxxxx | 7 | 127 (0 ~ 127) | ||
| 110xxxxx | 10xxxxxx | (5+6)=11 | 2047 (194 ~ 223) | |
| 1110xxxx | 10xxxxxx | 10xxxxxx | (4+6+6)=16 | 65535 (224 ~ 239) |
In vero UTF-8 utilizza i bits che rimangono liberi da questa maschera (che può potenzialmente estendersi fino a 6 bytes per un singolo carattere, ma che viene limitata a 4).
Torniamo alla nostra stringa:
"abcdè".each_byte do |c|
puts c
end
97
98
99
100
195
168
Quelli che vengono stampati non sono altro che i valori ascii di ogni singolo carattere o meglio di ogni singolo byte, quindi avremo queste corrispondenze:
| Valore/i ASCII | Carattere |
|---|---|
| 97 | a |
| 98 | b |
| 99 | c |
| 100 | d |
| 195 e 168 | è |
Si prenda la tabella di prima e si osservi che abcd occupano un byte e hanno valori inferiori a 128, mentre "è" il cui primo byte ricade nell'intervallo successivo occupa 2 byte.
Ora, il carattere è può anche essere espresso in HTML con l'entità numerica è e (magia, magia) questo valore può essere mutuato dalla sequenza di bits liberi usati da UTF-8 (cfr. Tbl. 1), vediamo come.
1. I valori decimali dei byte che compongono è
"è".each_byte do |c|
puts c
end
195
168
2. In base 2
195.to_s(2) #=> "11000011" 168.to_s(2) #=> "10101000"
3. Applichiamo la maschera
110 00011 10 101000
Compongo la sequenza di bits che rappresenta è, ovvero solo i bits liberi:
00011 101000
Se lo facessi con Ruby potrei:
byte_1 = "11000011" byte_2 = "10101000" utf8_bits = byte_1[3..7]+byte_2[2..7] puts utf8_bits #=> 00011101000
4. Ottengo il valore decimale
puts utf8_bits.to_i(2) #=> 232
... ops! Esattemente il valore che ci aspettavamo! Ora basta esprimerlo come entità HTML per ottenere il risultato desiderato è!
Ora che sappiamo come convertire un carattere UTF-8 sarebbe interessante capire come convertire un'intera stringa. Non è difficile, basta analizzare la stringa byte per byte e capire quale maschera applicare.
Ad esempio: se trovo un byte < 128 posso lasciarlo così come si trova perchè si tratta di un carattere ASCII, se trovo un byte che ha valore compreso tra 194 e 223 so che a quel byte e a quello seguente dovrò applicare la maschera a 2 byte, se ne trovo uno con valore tra 224 e 240 so che a quel byte e ai due seguenti dovrò applicare la maschera a 3 byte e così via. Il valore a cui ci si riferisce è quello decimale del primo byte della sequenza.
Note al documento
Userò in tutto l'articolo Ruby, ma è possibile applicare lo stesso procedimento praticamente con qualsiasi linguaggio.