Unicode

Kort internopplæring i Unicode

En kort introduksjon til Unicode.

1   8-bits

Det tradisjonelle tegnsettet i Cerebrum har vært ISO-8859-1 (AKA Latin 1). Dette tegnsettet bruker åtte bits til hvert tegn, og det er enkelt å finne tabeller over ulike tegn.

2   Unicode-terminologi

Unicode er et prosjekt som lager standarder (ISO/IEC 10646). Disse standardene bestemmer hvordan alle tegn i hele verden kan representeres på datamaskiner.

Unicode bruker ordet «code point» om et «tegn». Jeg bruker anførsel rundt «tegn» ettersom et visuelt tegn kan representeres med ett eller flere code points, f.eks. kan man representere en bokstav med aksent ved å bruke kodepunkter for grunnbokstaven, efterfulgt av kodepunkter for kombinerende aksenttegn. I tillegg er det flere code points som ikke er visuelle tegn.

Et kodepunkt er tilknyttet et unikt tall i Unicode, gjerne skrevet som U+XXXX hvor X'ene er heksadesimale sifre. En streng er en sekvens av kodepunkter, men tanken er at minnerepresentasjonen er abstrahert vekk.

For å skrive til filer eller nettverk, trengs et tegnsett. Unicode definerer flere ulike, som alle kan brukes til å representere sekvensen med tegn, bl.a.:

  • UTF-8,
  • UTF-16 og
  • UTF-32/UCS-4

Disse spesifiserer da hvordan kodepunkter representeres digitalt.

3   Unicodedatabase

Alle unicodetegn har et navn og en del attributter. Se modulen unicodedata:

>>> print(unicodedata.lookup('latin capital letter a with ring above'))
Å
>>> print(u'\N{latin capital letter a with ring above}')
Å
>>> unicodedata.name(u'Å')
'LATIN CAPITAL LETTER A WITH RING ABOVE'

Av andre attributter er det sannsynligvis bare combining vi kan ha direkte bruk for, men andre biblioteker bruker attributtene mere ekstensivt. Combining er 0 for grunnbokstaver, og ikke null for kombinerende tegn (se under):

>>> unicodedata.combining(u'\N{latin capital letter a}')
0
>>> unicodedata.combining(u'\N{combining ring above}')
230

4   Kombinasjoner

Noen Unicode-tegn kan uttrykkes på forskjellige måter. Dette har sitt utspring i at Unicode inkluderer alle eksisterende tegnsett. Bokstaven «Å» kan f.eks. uttrykkes på tre måter:

>>> print(u'\N{latin capital letter a with ring above}')
Å
>>> print(u'\N{angstrom sign}')
Å
>>> print(u'\N{latin capital letter a}\N{combining ring above}')
Å

Den første varienten kommer fra tegnsettet latin 1; den midterste kommer fra et annet tegnsett, og den siste er sammensatt av en «A» + ring over.

5   Sammenligning

Disse tegnene skal være ekvivalente, men Python vil ikke si det:

>>> u'\N{latin capital letter a with ring above}' == u'\N{angstrom sign}'
False

For å rette opp dette, må unicodestrenger normaliseres før sammenligning. Unicode definerer fire normaliseringer, men dette er en kombinasjon av to forskjellige parametre:

  • D-normalisering vil bruke dekomponerte tegn hvis mulig, i.e. grunntegn etterfulgt av kombinerende tegn. (Tredje variant av Å over)
  • C-normalisering (combining) bruker ett tegn hvis mulig. (Første variant av Å)
  • K-normalisering (kompitabilitet) vil i tillegg erstatte en del tegn med annen variant, f.eks. superscript two → 2. Dette er da tegn som mer eller mindre er markup.
>>> test = (u'\N{angstrom sign}'
...         u'\N{latin capital letter a with ring above}'
...         u'A\N{combining ring above}'
...         u'\N{superscript two}')
>>> test
'ÅÅŲ'
>>> for form in ('NFC', 'NFD', 'NFKC', 'NFKD'):
...     print(form)
...     print(u'*', u'\n* '.join(
...               map(unicodedata.name,
...                   unicodedata.normalize(form, test))))
...
NFC
* LATIN CAPITAL LETTER A WITH RING ABOVE
* LATIN CAPITAL LETTER A WITH RING ABOVE
* LATIN CAPITAL LETTER A WITH RING ABOVE
* SUPERSCRIPT TWO
NFD
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* SUPERSCRIPT TWO
NFKC
* LATIN CAPITAL LETTER A WITH RING ABOVE
* LATIN CAPITAL LETTER A WITH RING ABOVE
* LATIN CAPITAL LETTER A WITH RING ABOVE
* DIGIT TWO
NFKD
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* LATIN CAPITAL LETTER A
* COMBINING RING ABOVE
* DIGIT TWO

I praksis vil vi bruke NFC (C-normalisering uten K), da denne er mest kompatibel med latin-1, som vi allerede har. (Men det hadde kanskje vært mest ideellt med en dekomponert normalisering.)

6   Unicode sandwich

IO er alltid bytes, men vi ønsker å bruke Unicode internt. Strategien er å konvertere til unicode og normalisere så tidlig som mulig, samt å konvertere til bytes så sent som mulig. Vi bør lage funksjonalitet som konverterer så automatisk som mulig, og som også tar hensyn til normalisering. Gjerne også wrappere rundt strømmer, slik at filtilgang, databasetilgang og nettverk kan leses direkte inn til unicode.

7   Postgres

Jeg har sjekket hva postgres gjør: Den vil ikke gjøre noen normalisering, og den vil derfor også ikke matche strenger med annen input enn hva som finnes i basen. Det er derfor viktig at alt er normalisert i basen.

8   Sortering

Unicode har laget en sorteringsalgoritme, som det er naturlig å bruke til output for mennesker. Denne algoritmen er dog dyr, så om det ikke er viktig, kan man sortere etter kodepunkter. Ellers finnes det to implementasjoner:

  • pyuca er skrevet i ren python, men kan ikke konfigureres
  • pyicu er en python-binding til ICU i C++, og støtter forskjellige sprog

Ulempen med f.eks. pyuca, er f.eks. at å sorterer før æ og ø (siden dette er standard i dansk.

Her er eksempler. Det er dyrt å lage collator, så disse bør tas vare på i det virkelige liv.:

>>> import pyuca
>>> sorted([...], key=pyuca.Collator().sort_key)
>>> import pyicu
>>> sorted([...], key=pyicu.Collator.createInstance(pyicu.Locale('nb_NO')).getSortKey)

I praksis kan også sortering gjøres i klienter ved behov.

9   six

Six er en pakke for å lage kode som kjører både på Python 2 og 3. Den har mange fine funksjoner:

  • six.text_type er unicodestring.
  • bytes fungerer på begge versjoner av Python.
  • @six.python_2_unicode_compatible fikser klasser med __str__ som returnerer et unicodeobjekt
Av hamar
Publisert 8. jan. 2018 14:20