Este foarte probabil să fi văzut o excepție Ruby precum UndefinedConversionError sau IncompatibleCharacterEncodings. Este mai puțin probabil să fi înțeles ce înseamnă excepția. Acest articol vă va ajuta. Veți afla cum funcționează codările de caractere și cum sunt implementate în Ruby. Până la final, veți putea înțelege și remedia aceste erori mult mai ușor.

codarea

Deci, ce este oricum o „codificare a caracterelor”?

În fiecare limbaj de programare, lucrați cu șiruri. Uneori le procesați ca intrare, alteori le afișați ca ieșire. Dar computerul dvs. nu înțelege „șirurile”. Înțelege doar biții: 1s și 0s. Procesul de transformare a șirurilor în biți se numește codificare de caractere.

Dar codificarea caracterelor nu aparține doar epocii computerelor. Putem învăța dintr-un proces mai simplu înainte de a avea computere: codul Morse.

Codul Morse

Codul Morse este foarte simplu în definiția sa. Aveți două simboluri sau modalități de a produce un semnal (scurt și lung). Cu aceste două simboluri, reprezentați un alfabet englezesc simplu. De exemplu:

  • A este .- (o notă scurtă și o notă lungă)
  • E este. (o notă scurtă)
  • O este - (trei puncte lungi)

Acest sistem a fost inventat în jurul anului 1837 și a permis, cu doar două simboluri sau semnale, să fie codificat întregul alfabet.

În imagine puteți vedea un „codificator”, o persoană responsabilă cu codificarea și decodarea mesajelor. Acest lucru se va schimba în curând odată cu sosirea computerelor.

De la codarea manuală la cea automată

Pentru a codifica un mesaj, aveți nevoie de o persoană care să traducă manual caracterele în simboluri urmând algoritmul codului Morse.

Similar codului Morse, computerele folosesc doar două „simboluri”: 1 și 0. Puteți stoca doar o secvență a acestora în computer și, atunci când sunt citite, trebuie interpretate într-un mod care are sens pentru utilizator.

Procesul funcționează astfel în ambele cazuri:

SOS în cod Morse ar fi:

O mare schimbare cu computerele și alte tehnologii a fost că procesul de codificare și decodare a fost automatizat, așa că nu mai aveam nevoie de oameni care să traducă informațiile.

Când au fost inventate computerele, unul dintre primele standarde create pentru a transforma automat caracterele în 1s și 0s (deși nu primul) a fost ASCII.

ASCII reprezintă codul standard american pentru schimbul de informații. Partea „americană” a jucat un rol important în modul în care computerele au funcționat cu informații de ceva timp; vom vedea de ce în secțiunea următoare.

ASCII (1963)

Bazat pe cunoașterea codurilor telegrafice precum codul Morse și computerele foarte timpurii, un standard pentru codificarea și decodarea caracterelor într-un computer a fost creat în jurul anului 1963. Acest sistem a fost relativ simplu, deoarece acoperea doar 127 de caractere la început, alfabetul englezesc, plus simboluri suplimentare.

ASCII a funcționat prin asocierea fiecărui caracter cu un număr zecimal care ar putea fi tradus în cod binar. Să vedem un exemplu:

„A” este 65 în ASCII, deci trebuie să traducem 65 în cod binar.

Dacă nu știți cum funcționează, iată o modalitate rapidă: începem să împărțim 65 la 2 și continuăm până obținem 0. Dacă împărțirea nu este exactă, adăugăm 1 ca rest:

Acum, luăm resturile și le punem în ordine inversă:

Deci, am stoca „A” ca „1000001” cu codificarea ASCII originală, cunoscută acum sub numele de US-ASCII. În zilele noastre, cu computerele de 8 biți obișnuite, ar fi 01000001 (8 biți = 1 octet).

Urmăm același proces pentru fiecare caracter, deci cu 7 biți putem stoca până la 2 ^ 7 caractere = 127.

Iată tabelul complet:


(De la http://www.plcdev.com/ascii_chart)

Problema cu ASCII

Ce s-ar întâmpla dacă am vrea să adăugăm un alt personaj, cum ar fi francezul ç sau caracterul japonez 大?

Da, am avea o problemă.

După ASCII, oamenii au încercat să rezolve această problemă prin crearea propriilor sisteme de codificare. Au folosit mai mulți biți, dar în cele din urmă acest lucru a provocat o altă problemă.

Problema principală a fost că, atunci când citiți un fișier, nu știați dacă aveți un anumit sistem de codificare. Încercarea de a o interpreta cu o codificare incorectă a avut ca rezultat o evidență generoasă precum „ ” sau „Ã, ÂÃ⠀ šÃ‚Â".

Evoluția acestor sisteme de codificare a fost mare și largă. În funcție de limbă, aveați sisteme diferite. Limbile cu mai multe caractere, precum chineza, au trebuit să dezvolte sisteme mai complexe pentru a-și codifica alfabetele.

După mulți ani de luptă cu acest lucru, a fost creat un nou standard: Unicode. Acest standard a definit modul în care computerele moderne codifică și decodează informațiile.

Unicode (1988)

Scopul Unicode este foarte simplu. Conform site-ului său oficial:
„Pentru a oferi un număr unic pentru fiecare personaj, indiferent de platformă, program sau limbă.”

Deci, fiecare caracter dintr-o limbă are atribuit un cod unic, cunoscut și sub numele de punct de cod. În prezent există peste 137.000 de caractere.

Ca parte a standardului Unicode, avem diferite moduri de a codifica acele valori sau puncte de cod, dar UTF-8 este cel mai extins.

Aceiași oameni care au creat limbajul de programare Go, Rob Pike și Ken Thompson, au creat și UTF-8. A reușit, deoarece este eficient și inteligent în modul în care codifică aceste numere. Să vedem exact de ce.

UTF-8: Format de transformare Unicode (1993)

UTF-8 este acum codificarea de facto pentru site-uri web (mai mult de 94% dintre site-urile web folosesc codificarea respectivă). Este, de asemenea, codificarea implicită pentru multe limbaje de programare și fișiere. Deci, de ce a avut atât de mult succes și cum funcționează?

UTF-8, ca și alte sisteme de codificare, transformă numerele definite în Unicode în binar pentru a le stoca în computer.

Există două aspecte foarte importante ale UTF-8:
- Este eficient atunci când stochează biți, deoarece un caracter poate dura de la 1 la 4 octeți.
- Prin utilizarea Unicode și o cantitate dinamică de octeți, este compatibil cu codificarea ASCII, deoarece primele 127 de caractere iau 1 octeți. Aceasta înseamnă că puteți deschide un fișier ASCII ca UTF-8.

Să descompunem cum funcționează UTF-8.

UTF-8 cu 1 octet

În funcție de valoarea din tabelul Unicode, UTF-8 folosește un număr diferit de caractere.

Cu primele 127, folosește următorul șablon:
Rugina1
0_______

Deci 0 va fi mereu acolo, urmat de numărul binar care reprezintă valoarea în Unicode (care va fi și ASCII). De exemplu: A = 65 = 1000001.

Să verificăm acest lucru cu Ruby folosind metoda de despachetare în String:

B înseamnă că dorim mai întâi formatul binar cu cel mai semnificativ bit. În acest context, asta înseamnă bitul cu cea mai mare valoare.
Asteriscul îi spune lui Ruby să continue până când nu vor mai exista biți. Dacă am folosi în schimb un număr, am obține doar biții până la acel număr:

UTF-8 cu 2 octeți

Dacă avem un caracter a cărui valoare sau punct de cod în Unicode depășește 127, până în 2047, vom folosi doi octeți cu următorul șablon:

Deci avem 11 biți goi pentru valoarea în Unicode. Să vedem un exemplu:

À este 192 în Unicode, deci în binar este 11000000, luând 8 biți. Nu se încadrează în primul șablon, așa că îl folosim pe cel de-al doilea:

Începem să umplem spațiile de la dreapta la stânga:

Ce se întâmplă cu biții goi de acolo? Tocmai punem 0, deci rezultatul final este: 11000011 10000000.

Putem începe să vedem un model aici. Dacă începem să citim de la stânga la dreapta, primul grup de 8 biți are două 1s la început. Aceasta implică faptul că personajul va lua 2 octeți:

Din nou, putem verifica acest lucru cu Ruby:

Un mic sfat aici este că putem formata mai bine rezultatul cu:

Obținem o matrice de la „À” .unpack („B8 B8”) și apoi unim elementele cu un spațiu pentru a obține un șir. 8 din parametrul de despachetare îi spune lui Ruby să obțină 8 biți în 2 grupuri.

UTF-8 cu 3 octeți

Dacă valoarea în Unicode pentru un caracter nu se încadrează în cei 11 biți disponibili în șablonul anterior, avem nevoie de un octet suplimentar:

Din nou, cei trei 1 de la începutul șablonului ne spun că suntem pe punctul de a citi un caracter de 3 octeți.

Același proces ar fi aplicat acestui șablon; transformă valoarea Unicode în binar și începe să umple sloturile de la dreapta la stânga. Dacă avem câteva spații goale după aceea, umpleți-le cu 0.

UTF-8 cu 4 octeți

Unele valori iau chiar mai mult decât cei 11 biți goali pe care îi aveam în șablonul anterior. Să vedem un exemplu cu emoji?, Care pentru Unicode poate fi văzut și ca un caracter precum „a” sau „大”.

Valoarea sau punctul de cod „?” din Unicode este 128578. Acest număr în binar este: 11111011001000010, 17 biți. Aceasta înseamnă că nu se potrivește în șablonul de 3 octeți, deoarece aveam doar 16 sloturi goale, deci trebuie să folosim un șablon nou care să preia 4 octeți în memorie:

Începem din nou prin completarea acestuia cu numărul în binar:
Rugina1
11110___ 10_11111 10011001 10000010

Și acum, umplem restul cu 0:
Rugina1
1111000 10011111 10011001 10000010

Să vedem cum arată asta în Ruby.

Deoarece știm deja că acest lucru va dura 4 octeți, putem optimiza pentru o mai bună lizibilitate în ieșire:

Dar dacă nu am face-o, am putea folosi:

De asemenea, am putea folosi metoda șirului „octeți” pentru extragerea octeților într-o matrice:

Și apoi, am putea mapa elementele în binare cu:

Și dacă am dori un șir, am putea folosi join:

UTF-8 are mai mult spațiu decât este necesar pentru Unicode

Un alt aspect important al UTF-8 este că poate include toate valorile Unicode (sau punctele de cod) - și nu numai cele care există astăzi, ci și cele care vor exista în viitor.

Acest lucru se datorează faptului că în UTF-8, cu șablonul de 4 octeți, avem 21 de sloturi de umplut. Asta înseamnă că am putea stoca până la 2 ^ 21 (= 2.097.152) valori, mult mai mult decât cea mai mare cantitate de valori Unicode pe care le vom avea vreodată cu standardul, în jur de 1,1 milioane.

Aceasta înseamnă că putem folosi UTF-8 cu încrederea că nu va trebui să trecem la un alt sistem de codificare în viitor pentru a aloca noi caractere sau limbi.

Lucrul cu diferite codificări în Ruby

În Ruby, putem vedea imediat codificarea unui șir dat procedând astfel:

De asemenea, am putea codifica un șir cu un alt sistem de codare. De exemplu:

Dacă transformarea nu este compatibilă, vom primi o eroare în mod implicit. Să presupunem că vrem să convertim „salut?” de la UTF-8 la ASCII. De când emojiul „?” nu se încadrează în ASCII, nu putem. Ruby ridică o eroare în acest caz:

Dar Ruby ne permite să avem excepții în cazul în care, dacă un caracter nu poate fi codat, îl putem înlocui cu „?”.

De asemenea, avem opțiunea de a înlocui anumite caractere cu un caracter valid în noua codificare:

Inspectarea codării unui script în Ruby

Pentru a vedea codificarea fișierului script la care lucrați, fișierul „.rb”, puteți face următoarele:

De la Ruby 2.0, codificarea implicită pentru scripturile Ruby este UTF-8, dar puteți schimba acest lucru cu un comentariu în prima linie:

Dar este mai bine să respectați standardul UTF-8, cu excepția cazului în care aveți un motiv foarte bun pentru al schimba.

Câteva sfaturi pentru lucrul cu codificări în Ruby

Puteți vedea întreaga listă de codificări acceptate în Ruby cu Encoding.name_list. Aceasta va returna o gamă mare:

Celălalt aspect important atunci când lucrați cu caractere în afara limbii engleze este că, înainte de Ruby 2.4, unele metode precum majuscule sau revers nu funcționau așa cum era de așteptat. De exemplu, în Ruby 2.3, majusculele nu funcționează așa cum ați crede:

Soluția a fost folosirea ActiveSupport, de la Rails sau o altă bijuterie externă, dar de la Ruby 2.4 avem o mapare completă a cazurilor Unicode:

Ceva distractiv cu emojis

Să vedem cum funcționează emojis în Unicode și Ruby:

Aceasta este „mâna ridicată cu parte între degetele mijlocii și inelare”, cunoscută și sub numele de emoji „Vulcan Salute”. Dacă avem același emoji, dar într-un alt ton de piele care nu este implicit, se întâmplă ceva interesant:

Deci, în loc să fim doar un personaj, avem două pentru un singur emoji.

Ce s-a întâmplat acolo?

Ei bine, unele caractere din Unicode sunt definite ca fiind combinația mai multor caractere. În acest caz, dacă computerul vede aceste două caractere împreună, acesta arată doar unul cu tonul pielii aplicat.

Există un alt exemplu distractiv pe care îl putem vedea cu steaguri.

În Unicode, emoji-urile sunt reprezentate intern de niște caractere Unicode abstracte numite „simboluri regionale ale indicatorului”, cum ar fi sau?. De obicei nu sunt utilizate în afara steagurilor și, atunci când computerul vede cele două simboluri împreună, afișează steagul dacă există unul pentru acea combinație.

Pentru a vedea personal, încercați să copiați acest lucru și să eliminați virgula din orice editor de text sau câmp:

Concluzie

Sper că această recenzie a modului în care funcționează Unicode și UTF-8 și cum au legătură cu Ruby și potențiale erori v-a fost utilă.

Cea mai importantă lecție de luat este să vă amintiți că atunci când lucrați cu orice tip de text aveți un sistem de codare asociat și este important să îl mențineți prezent atunci când îl stocați sau îl schimbați. Dacă puteți, utilizați un sistem modern de codificare precum UTF-8, astfel încât să nu mai aveți nevoie să-l schimbați în viitor.

Notă despre lansările Ruby

Am folosit Ruby 2.6.5 pentru toate exemplele din acest articol. Le puteți încerca într-un REPL online sau local, mergând la terminalul dvs. și executând irb dacă aveți Ruby instalat.

Deoarece suportul Unicode a fost îmbunătățit în ultimele versiuni, am ales să folosesc cea mai recentă versiune, astfel încât acest articol să rămână relevant. În orice caz, cu Ruby 2.4 și versiuni ulterioare, toate exemplele ar trebui să funcționeze așa cum se arată aici.