Znakové sady v praxi – konverze

17. prosince 2003

Jak jsme si již ukázali, znakové sady nelze brát na lehkou váhu. Přesto se programátor dříve či později dostane do situace, kdy je třeba provádět konverze mezi znakovými sadami, a já vám prozradím, jak na to.

Tento článek je ukázkou několika metod, jak provádět tyto konverze ve vlastní režii. Metody jsou vysvětleny v obecné rovině a čtenář je může aplikovat libovolným způsobem dle svého uvážení. Jako ukázkový programovací jazyk jsem zvolil JavaScript, kvůli jeho univerzálnosti a názornosti.

Konvertujeme text mezi znakovými sadami

Pokud chceme text převést do jiného druhu kódování (jiné znakové sady) jednorázově, postačuje nám obvykle vhodný software. Nejčastěji jde o překódování HTML či jiných zdrojových souborů – k tomu vám poslouží velká spousta jednoúčelových utilitek, které obvykle umí pracovat s větším množstvím souborů současně. S textem uloženým jinak (například v databázi) je situace složitější – vhodné nástroje nemusí být snadno k nalezení nebo nejsou zdarma, pak bývá lepší pustit se do programování a problém vyřešit samostatně.

Předem je třeba říci, že se budeme zabývat převodem mezi osmibitovými znakovými sadami (například iso-8859-2 nebo windows-1250) a dále pak kódováním utf-8 (v následujícím díle). Ostatní druhy kódování (například UCS-2) nejsou příliš běžné a konverzní algoritmy lze odvodit dle níže popsané metodiky.

Znak a jeho kód

Naším cílem je sestavení algoritmu, který nám převede jeden znak z jednoho kódování do jiného. Iterací tohoto algoritmu pak můžeme aplikovat konverzní mechanismus na libovolně dlouhý text. Přitom budeme předpokládat, že všechny ASCII znaky (tedy ty s kódem menším než 128) zůstanou nedotčeny, jejich reprezentace je totiž u všech uvažovaných znakových sad totožná.

Při libovolné konverzi je potřeba, aby bylo možné zdrojová data uložit v cílovém formátu. Zde tedy potřebujeme, aby daný znak, který jsme si přečetli ve zdrojovém textu, existoval také v cílové znakové sadě. Ta by měla obsahovat všechny znaky použité v převáděném textu. Ostatní znaky se budou muset vypustit nebo vhodně nahradit, nejlépe HTML entitami (samozřejmě pouze v textech v HTML). Pro český text se většinou bude jednat o konverzi z windows-1250 do iso-8859-2 a naopak.

Pro určení hledané binární reprezentace znaku dle cílové znakové sady je nutno mít nějaký společný jmenovatel, kterým je znaková sada Unicode. Ta je totiž nadmnožinou všech osmibitových znakových sad. Pro náš převáděný znak tedy musíme nejdříve zjistit jeho Unicode kód a posléze vygenerovat jeho reprezentaci v cílové znakové sadě.

Osmibitové znakové sady a Unicode

Při osmibitovém kódování je každý znak vyjádřen jedním bajtem. Pro nalezení jeho Unicode hodnoty potřebujeme nějakou převodní tabulku, kterou si musíme obstarat. Využít můžeme tzv. Unicode Character Database, ve které lze nalézt převodní tabulky mezi Unicode a mnoha osmibitovými znakovými sadami. Jejich nalezení a použití je poměrně nezáživné a já vám chci ukázat mnohem jednodušší možnost, jak si takovou tabulku můžete vygenerovat sami pro libovolnou znakovou sadu, a to přímo ve formátu vhodném pro váš algoritmus.

Vytváření konverzních tabulek

Prvním úkolem tedy bude získat konverzní tabulku podobnou té následující. Přitom nás nezajímají tabulková data, pro algoritmus potřebujeme získat pole, které zajistí promítnutí osmibitového kódu naší znakové sady do Unicode.

Osmibitový kód Unicode Znak
128 8364
129 129 
130 8218

Převodní tabulka z Windows-1250 do Unicode

Prohlížeč MSIE a mnoho dalších vnitřně pracuje s textem ve znakové sadě Unicode. Po načtení si stránku převede z uvedeného kódování do UCS-2 a takto ji uchovává v paměti. Pokud se pak pokoušíme přečíst kód libovolného znaku JavaScriptem, dozvíme se právě onen Unicode kód. Toho využijeme v následujícím skriptu. Základem bude HTML stránka s nastaveným kódováním, které nás zajímá. Uvnitř stránky nebude nic jiného, než skript generující pole potřebných Unicode kódů.

Nejprve si připravíme řetězec, který bude obsahovat všechny znaky s kódem od 128 do 255. Pokud váš editor používá kódování windows-1250, můžete si tento řetězec bez obav zkopírovat přímo z následujícího příkladu přes schránku, jinak k jeho vygenerování dobře poslouží například jednoduchý ASP či PHP skript nebo si raději stáhněte ukázkový soubor. Tento řetězec budeme procházet znak po znaku a od každého vypisovat jeho Unicode hodnotu do textu stránky (jednotlivé kódy budou odděleny čárkami).

<html>
  <head>
    <meta http-equiv=“Content-Type“
     content=“text/html; charset=windows-1250″ />
  </head>
  <body>
    <script language=“javascript“>
var zn = „€ ‚ „…†‡ ‰Š‹ŚŤŽŹ ‘’“”•–— ™š›śťžź“ +
         “ Ą˘Ł¤ĽŚ§¨ŠŞŤŹ­ŽŻ°ą˛ł´ľś·¸šşťź˝žż“ +
         „ŔÁÂĂÄĹĆÇČÉĘËĚÍÎĎĐŃŇÓÔŐÖ×ŘŮÚŰÜÝŢß“ +
         „ŕáâăäĺćçčéęëěíîďđńňóôőö÷řůúűüýţ˙“;
for(var i=0; i<zn.length; i++)
  document.write(zn.charCodeAt(i) + „, „);
    </script>
  </body>
</html>

Takovou stránku pak stačí zobrazit v prohlížeči a pokud vše proběhlo správně, zobrazí se posloupnost jednotlivých Unicode kódů pro všechny znaky v dané znakové sadě. (Pro windows-1250 je to 8364, 129, 8218, 131, 8222, 8230, 8224, 8225 atd.) Tuto posloupnost pak stačí překopírovat do zdrojového kódu konverzního programu, a to tak, aby vzniklo pole, které mapuje danou znakovou sadu do Unicode – například takto: var A = new Array(8364, 129, 8218, ... );. Těchto polí si vytvoříme několik, pro všechny osmibitové znakové sady, které potřebujeme konvertovat.

Jdeme na věc

Vezměme znak a převeďme jej na číslo. Toto číslo bude osmibitové, s hodnotou od 0 do 255, pokud je však jeho hodnota nižší než 128, pak se jedná o standardní ASCII, jeho převod není nutný a můžeme jej přímo použít v cílovém textu. V opačném případě vyhledáme v konverzní tabulce pro zdrojovou znakovou sadu Unicode kód. To je jednoduché, postačí vyhledání pomocí indexu pole. Přitom musíme pamatovat na to, že první znak v tabulce má kód 128, takže nejdříve musíme od kódu znaku odečíst hodnotu 127 nebo 128, podle toho, zda má první prvek v poli index 0 nebo 1 (to záleží na konkrétním programovacím jazyce). Tedy například UniCode = A[SourceCode - 128];. Tím máme polovinu práce za sebou.

Ve druhém (a posledním) kroku je třeba nalézt daný znak ve druhé tabulce. To můžeme zařídit procházením pole a hledáním potřebné Unicode hodnoty. Nakonec získaný kód převedeme na znak.

for(var j=0; j<128; j++) { // Pro každý národní znak:
  if (B [j] == UniCode) {  // pokud je to hledaný znak,
    targetCode = j + 128;  // použijeme jeho kód
    j = 128;               // a ukončíme smyčku
  }
}

Může se stát, že hledaný Unicode kód prostě v konverzní tabulce není, tedy jej vůbec není možné danou znakovou sadou vyjádřit. S tím je nutno počítat a situaci patřičně ošetřit, buď vyvoláním výjimky, nahrazením za jiný znak (například otazník) nebo nahrazením za alternativní vyjádření (například HTML entitu). Každý znak je totiž možné v HTML vyjádřit na základě jeho Unicode hodnoty, a to takto: &#KódZnaku;, přičemž kód se zapisuje normálně dekadicky. Takže například písmeno Ř lze zapsat &#344; a bude zobrazeno správně bez ohledu na nastavené kódování stránky.

Shrňme si tedy základní průběh konverze:

  1. připravíme převodní tabulku A: zdrojová sada → Unicode; je to pole, jehož indexem je kód znaku (128 – 255) a obsahem prvků je příslušný Unicode kód
  2. připravíme totožnou převodní tabulku B pro cílovou znakovou sadu
  3. pro každý znak ve zdrojovém textu zjistíme jeho Unicode hodnotu, tu vyhledáme v tabulce B a dle indexu získáme odpovídající kód v cílové znakové sadě

Optimalizace výkonu

Možná vás napadlo, že slabinou uvedeného způsobu konverze je právě ono hledání Unicode kódu v tabulce B. Tento postup se bude provádět vícekrát, než je nezbytné. Řešením je vytvořit si pro tento účel zpětnou konverzní tabulku Unicode → 8bitová sada, díky které hledaný kód znaku nalezneme pomocí jednoduchého příkazu. Toto pole již bude trochu větší, ovšem to by nemělo představovat problém. Postup jeho vytvoření:

  1. Deklarujeme si jednorozměrné pole POM o dostatečné velikosti. Pokud můžeme plýtvat, použijeme 65 536 prvků. Jinak si můžeme zjistit nejvyšší kód z tabulky B.
  2. Každý prvek v poli POM přednastavíme na hodnotu, jakou má mít znak v případě jeho neexistence v cílové znakové sadě.
  3. Každý prvek v tabulce B použijeme jako index v POM a jeho hodnotu naplníme indexem v poli B. Tedy např. v javascriptové syntaxi for (i=0; i<B.length; i++) POM[B[i]] = i;

Nyní již pro zjištění osmibitového kódu znaku stačí použít Unicode kód jako index v poli POM.

Dosud jsme uvažovali případ, kdy převádíme text z libovolné sady do jiné, avšak pokud víme předem, že budeme převádět například ze znakové sady windows-1250 do iso-8859-2, je nejlepší si připravit pole mapující přímo tyto znakové sady mezi sebou. Postup bude následující:

  1. Připravíme si pole A a B dle výše uvedeného postupu.
  2. Deklarujeme si jednorozměrné pole POM o velikosti 256 prvků.
  3. Prvních 128 prvků tohoto pole naplníme jejich indexy, zhruba takto: for (i=0; i<128; i++) POM[i] = i;
  4. Každý ze zbývajících 128 prvků v POM vygenerujeme tak, že nalezneme index prvku v poli B, který odpovídá danému prvku v poli A. Přitom pamatujme, že pole A a B mají svůj začátek posunutý o 128, avšak POM nikoli. Tedy pro názornost:

    for (i=0; i<128; i++) {
      POM [i+128] = 63;   // Kód náhradního znaku (zde „?“)
      for (j=0; j<128; j++) {  // Pro každý uvažovaný znak…
        if (A[i] == B[j]) {  // Pokud je to hledaný znak
          POM [i+128] = j+128;    // Zapíšeme daný převod
          j = 128;    // a přejdeme na další znak
        }
      }
    }

    Tímto jsme vlastně přímo získali tabulku zobrazení indexů pole A do pole B.

  5. Pro každý převáděný znak textu stačí přímo využít pole POM: Kod2 = POM [Kod1].

Uvedené postupy berte, prosím, jako ukázkové, samozřejmě jsou možné další úpravy. Například v poli POM může být vhodnější ukládat ne kódy znaků, ale přímo samotné znaky. Také není nutné dodržovat nastíněné rozdělení znakové sady do ASCII (0 – 127) a národních znaků (128 – 255), veškeré práce mohou počítat s celou znakovou sadou jako s národními znaky, tak jako u samotné konverze pomocí pole POM.

A co UTF?

V případě kódování utf-8 je konverze úplně odlišná, toto kódování totiž jako znakovou sadu využívá Unicode, ale kódy národních znaků zapisuje speciálním způsobem do dvou až šesti bajtů. Konverzní tabulky z osmibitových sad do Unicode tedy stále využijeme, avšak pro zápis a čtení znaků utf-8 a další práci s nimi je nutné používat zvláštní postupy. Kódováním utf-8, jeho vlastnostmi a prací s ním se budeme zabývat v příštím článku.

Starší komentáře ke článku

Pokud máte zájem o starší komentáře k tomuto článku, naleznete je zde.

Předchozí článek gradua.cz
Štítky: Články

Mohlo by vás také zajímat

Nejnovější

Napsat komentář

Vaše e-mailová adresa nebude zveřejněna. Vyžadované informace jsou označeny *