# PB029: Textové soubory

Jedním z nejjednodušších elektronických dokumentů je prostý textový soubor. I když to tak nemusí na první pohled vypadat, i prosté textové soubory skrývají některé záludnosti, pokud je chceme editovat a přenášet mezi různými platformami. Jelikož jsou i webové stránky a TeXové zdrojové texty čistě textové soubory, je dobré být si těchto úskalí vědom.

## Znak konce řádků

Znak, který v textovém souboru signalizuje konec řádku (často označováno EOL), je na různých platformách různý, z těch nejrozšířenějších:

- Windows: dvojice znaků `CR`+`LF`
- Unix (tedy včetně Apple MacOS od verze X): znak `LF`
- Apple MacOS do verze 9 včetně: znak `CR`

Znaky `CR` a `LF` mají kořeny v technologii dálnopisných terminálů:

- Znak `LF` (Line Feed; ASCII kód `0x0A`, v programovacích jazycích často reprezentován sekvencí `\n`) byl interpretován jako posun papíru o řádek vzhůru.
- Znak `CR` (Carriage Return; ASCII kód `0x0D`, v programovacích jazycích často reprezentován sekvencí `\r`) byl interpretován jako návrat vozíku s papírem nebo tiskovou hlavou.

### Praktické úkoly k procvičení

Vytvořte si v libovolném textovém editoru několikařádkový textový soubor. Ověřte, jaký znak konce řádků je použit:

- Linux: `$ cat plaintext.txt
  prvni radek souboru
  druhy radek souboru
  $ hexdump -C plaintext.txt
  00000000 70 72 76 6e 69 20 72 61 64 65 6b 20 73 6f 75 62 |prvni radek soub|
  00000010 6f 72 75 0a 64 72 75 68 79 20 72 61 64 65 6b 20 |oru.druhy radek |
  00000020 73 6f 75 62 6f 72 75 0a |souboru.|
  00000028` Z výpisu nástroje `hexdump` je vidět, že soubor byl pravděpodobně vytvořen na unixovém systému, protože konce řádků jsou reprezentovány bajty s hodnotou `0a` (ve výpisu zvýrazněné).
- Ve Windows je možné nad souborem `plaintext.txt` použít například integrovaný prohlížeč správce souborů Altap Salamander: `Nabídka Start → All Programs → Altap Salamander
  vybrat soubor plaintext.txt
  Soubory → Zobrazit (F3)
  Zobrazit → Šestnáctkově (Ctrl + H)`

Následně soubor konvertujte do podoby obvyklé na ostatních platformách.

- Linux: použijte konverzí programy `dos2unix`, `mac2unix`, `unix2dos` a `unix2mac`.
- Windows: použijte Altap Salamander: `Nabídka Start → All Programs → Altap Salamander
  vybrat soubor plaintext.txt
  Soubory → Převést (Ctrl + K)`

Vyzkoušejte si, jaké problémy může přinést použití chybného znaku konce řádku:

- V libovolném textovém editoru vytvořte jednoduchý skript pro unixový shell Bash: `#!/bin/bash
  echo 'Hello World!'
  exit 0`
- Uložte skript v kódování UTF-8 s Windows reprezentací konců řádků znaky `CR`+`LF` do souboru `skript.sh`.
- Na unixovém systému nastavte souboru práva pro spuštění a pokuste se skript spustit: `$ chmod u+x skript.sh
  $ ./skript.sh
  bash: ./skript.sh: /bin/bash^M: chybný interpretr: Adresář nebo soubor neexistuje` Proč došlo k uvedené chybě?

TeX se k znakům konce řádku staví rozumně a `CR`+`LF`, `LF` i `CR` jsou během čtení souboru nahrazeny znakem, který se nachází v registru `\endlinechar` (implicitně `CR`). HTML je k použitému znaku konce řádku rovněž lhostejný.

## Kódování textu

Daleko větší problémy než různé znaky ukončení řádků však přináší rozdíly v kódování textu, tzn. interpretaci významu bajtů v textovém souboru.

### Jednobajtová kódování

V současné době je nejzákladnějším standardem pro kódování textu na nejrozšířenějších platformách kód ASCII. Ten však nepokrývá potřeby většiny jazyků krom angličtiny, má jen velmi omezené pokrytí speciálních znaků apod.

Tip: Pomocí ASCII escape sekvencí ve zdrojovém textu umožňuje TeX sázet i různé cizokrajné znaky. V případě nedostupnosti vhodného znaku v použitém fontu je však výsledek vytvořen složením samotatného znaku písmene (např. `d`) a znaku akcentu (např. `ˇ`). Výsledek strojové kombinace obou znaků však často nemusí dávat optimální optický výsledek v porovnání s ručně optimalizovanou kresbou znaku (nevhodné zarovnání na osu znaku, potřeba zcela odlišné kresby háčku při kombinaci s písmenem `d` než při kombinaci s písmenem `c` apod.). K tomu řešení se tedy přistupuje jen v nouzi.

Historicky se pro potřeby neanglických jazyků začala nejdříve používat různá jednobajtová kódování. Vznikly různé standardní kódové stránky, které specifikovaly, jak interpretovat který bajt v textovém souboru. Pro češtinu se mimo jiné používalo kódování

- CP1250 na Windows,
- ISO-8859-2 na unixových systémech,
- Mac CE na starších verzích Mac OS.

Problém je, že jednobajtové kódování má vždy k dispozici pro jeden znak právě jeden bajt. Není tedy možné v jedné kódové stránce mít více než 256 různých znaků, což je pro některé jazyky značně omezující (extrémním příkladem může být například tradiční čínština používající tisíce různých znaků).

Informace o použité kódové stránce navíc není v čistě textovém souboru nikde uvedena. Zpracovatel tedy musí vědět, která kódová stránka byla použita, jinak dojde k chybné interpretaci textu.

Tip: Existují nástroje, které mohou zkusit kódování textu odhadnout. Jedním z nich je např. program `enca`.
Jinou možností pro detekci kódování je např. `file -bi`.

Kvůli chybějící indikaci není ani možné používat více kódových stránek v jednom souboru (např. pro zakódování textu ve dvou různých jazycích, kdy každý z nich vyžaduje jiné jednobajtové kódování).

### Unicode

Problémy jednobajtových kódování řeší standard Unicode. Zjednodušeně si jej můžeme představit jako seznam dvojic číslo:znak, kde je každému znaku přiřazeno nějaké jednoznačné identifikační („katalogové“) číslo. Například znak `š` má přiděleno číslo 353, znak `–` (pomlčka) číslo 8211 atd.

Tip: Velmi užitečnou aplikací pro vyhledávání informací o Unicode znacích a hledání znaků samotných je webová aplikace UniView.

Povšimněte si, že počet znaků, které Unicode pokrývá, mnohonásobně převyšuje 256 možností zakódovatelných do jediného bajtu – například ona pomlčka (`–`) už je spolehlivě mimo tento rozsah. Musí tedy být definován i nějaký způsob, jak vysoká „katalogová“ čísla znaků uložit jako posloupnost bajtů do souboru (k zakódování čísel větších než 255 už nám nestačí jeden bajt, takové číslo musí být uloženo v několika bajtech). Existuje několik způsobů Unicode kódování:

- Nejjednodušší přístup je vyhradit pro každý znak pevný a dostatečně velký počet bajtů, aby tak bylo možné uložit i velká „katalogová“ čísla znaků. Tento postup reprezentuje např. Unicode Transformation Format UTF-32, kde je každý znak reprezentován právě čtyřmi bajty (tzn. 32 bity, odtud název UTF-32).
  - Tento postup však značně plýtvá místem, i nízká „katalogová“ čísla znaků jsou uložena na celých čtyřech bajtech, z nich většina je ale nulová.
  - Je problém s různou endianitou různých platforem – protože je číslo uloženo ve více bajtech, musí být možné nějak zjistit, na které straně čtveřice bajtů v souboru je uložen nejvíce/nejméně významný bajt čísla. Tento problém se řeší přidáváním BOM na začátek souboru, což ale může vést k problémům při zpracování souboru programy, které na to nejsou připravené.

Příklad – endianita: Mějmě (desítkově zapsaná) čísla (mezery ve všech zápisech v příkladu jsou přidány jen pro přehlednost):

- 1 245 391 901
- 379 976 145

Šestnácková reprezentace čísel pak je:

- 4A 3B 2C 1D
- 16 A5 F9 D1

Binární reprezentace čísel pak je:

- 0100 1010 0011 1011 0010 1100 0001 1101
- 0001 0110 1010 0101 1111 1001 1101 0001

Little-endian posloupnost bajtů v souboru při zápisu obou čísel za sebou:

- `1D 2C 3B 4A D1 F9 A5 16`

Big-endian posloupnost bajtů v souboru při zápisu obou čísel za sebou:

- `4A 3B 2C 1D 16 A5 F9 D1`

- Propracovaným, a asi nejrozšířenějším Unicode kódováním textových souborů je kódování UTF-8.
  - Šetří místo – jednotlivé znaky jsou ukládány na jednom až čtyřech bajtech tak, aby nebyly zbytečně ukládány vedoucí nulové bajty. Počet bajtů použitých pro kódování jednoho znaku je signalizován několikabitovým prefixem ve všech bajtech použitých pro kódování daného znaku (viz např. příklad na Wikipedii).
    - Takový postup navíc pomáhá s hledáním hranic jednotlivých znaků při náhodném přístupu doprostřed textového souboru, při ztrátě části obsahu souboru (vinou chyby při přenosu po síti, chyby při čtení z disku, …) apod.
    - Díky tomu je UTF-8 také neutrální z hlediska endianity, čímž odpadají problémy s BOM. (Pozor, zejména na Windows některé programy vkládají BOM značky i do UTF-8 kódovaných souborů, což často způsobuje problémy, protože BOM u UTF-8 nemá žádný praktický význam a mnoho programů zpracovávajících UTF-8 soubory s BOM nepočítá.)
  - Prvních 128 znaků v UTF-8 binárně jedna ku jedné odpovídá základnímu ASCII, což je výhodné z hlediska kompatibility – různé jednoduché textové soubory (např. konfigurační soubory), které si vystačí se základním ASCII, jsou „zdarma“ korektně kódovány v UTF-8 a naopak.

Příklad – kódování UTF-8: Mějme následující Unicode znak:

- Ǻ – Latin Capital Letter A with Ring Above and Acute

Tento znak má několik možných reprezentací. Jedna možná reprezentace sestává ze tří samostatných grafémů:

- A – Latin Capital Letter A (číslo grafému: 65)
- ◌̊ – Combining Ring Above (číslo grafému: 778)
- ◌́ – Combining Acute Accent (číslo grafému: 769)

Binární reprezentace čísel jednotlivých grafémů je následující:

- 100 0001
- 11 0000 1010
- 11 0000 0001

Kódování UTF-8 nám jednoznačné zadává, jak binární reprezentaci grafémů zakódovat:

- 0100 0001
- 1100 1100 1000 1010
- 1100 1100 1000 0001

Znak Ǻ tedy zakódujeme následující posloupností pěti bajtů:

- `41 CC 8A CC 81`

Jak jsme zmínili, existuje několik reprezentací znaku Ǻ. Namísto výše uvedené trojice grafémů můžeme např. přímo použít grafém Latin Capital Letter A with Ring Above and Acute (grafém číslo 506), který zakódujeme pomocí dvou bajtů `C7 BA`.

Toto nám činí problémy např. tehdy, když chceme v textu najít konkrétní slovo. Prohlížeče a editory textových dokumentů totiž většinou pracují na úrovni grafémů, ne znaků, a jinak reprezentované slovo nemusí najít. Abychom se tomuto problému vyhnuli, můžeme reprezentaci znaků v dokumentu sjednotit pomocí tzv. Unicode normalizace implementované např. nástrojem `uconv`: `$ printf '\x41\xCC\x8A\xCC\x81' | hexdump -C
00000000 41 cc 8a cc 81 |A....|
00000005
$ printf '\x41\xCC\x8A\xCC\x81' | uconv -x any-nfc | hexdump -C
00000000 c7 ba |..|
00000002`

### Praktické úkoly k procvičení

Prohlédněte si odkazované články o jednobajtových kódováních a porovnejte rozdíly mezi kódovými stránkami.

Vyzkoušejte si, jak vypadá chybná interpretace textu při chybně zvoleném kódování v programu text zpracovávajícím.

- Vytvořte si v libovolném textovém editoru (plain textovém editoru, tzn. programy typu Poznámkový blok, Vim, Visual Studio Code apod., nikoliv v textovém procesoru typu MS Word, LibreOffice Writer apod., které text ukládají ve složitém binárním formátu) soubor `plaintext-cp1250.txt` s jediným řádkem obsahujícím text `Šíleně žluťoučký kůň úpěl ďábelské ódy.` a uložte jen v kódování Windows CP1250.
  - Visual Studio Code pod Windows: `Soubor → Nový textový soubor
    V pravém dolním rohu: UTF-8 → Central European (Windows 1250)
    Soubor → Uložit jako → plaintext-cp1250.txt`
  - Vim na libovolné platformě: `:w ++enc=cp1250`
- Otevřete si takto vytvořený soubor ve webovém prohlížeči, kde je snadné ručně zvolit kódování zpracovávaného souboru. Např. v prohlížeči Google Chrome si za tímto účelem můžete nainstalovat rozšíření Set Character Encoding. Vyberte nejdříve kódování CP1250 (další možné názvy zahrnují „Windows 1250“ a „Central European“). Text by měl být zobrazen správně. Následně vyberte jiná kódování a podívejte se, jak je obsah souboru prohlížečem interpretován.

Převeďte soubor `plaintext-cp1250.txt` do Unicode v kódování UTF-8.

- Prohlédněte si, jak je vypadá binární reprezentace českého textu v `plaintext-cp1250.txt` v kódování CP1250. Viz výše.
- Převeďte soubor do UTF-8.
  - Visual Studio Code pod Windows: `Soubor → Otevřít soubor… → plaintext-cp1250.txt
    V pravém dolním rohu: UTF-8 → Znovu otevřít s kódováním → Central European (Windows 1250)
    V pravém dolním rohu: Central European (Windows 1250) → Uložit s kódováním → UTF-8
    Soubor → Uložit jako → plaintext-utf8.txt`
  - Vim na libovolné platformě: `:e ++enc=cp1250
    :w ++enc=utf8`
  - Linux: `iconv -f cp1250 -t utf8 --output plaintext-utf8.txt plaintext-cp1250.txt`
  - macOS: `iconv -f CP1250 -t UTF-8 < plaintext-cp1250.txt > plaintext-utf8.txt`
- Prohlédněte si, jak vypadá binární reprezentace českého textu v `plaintext-utf8.txt` po převodu do UTF-8. Viz výše.
- Vyzkoušejte také další jednobajtová/Unicode kódování a prohlédněte si výslednou binární reprezentaci souboru.
- Vyzkoušejte chování různých konverzních programů při pokusu o konverzi textu obsahujícího znaky, které v cílovém (jednobajtovém) kódování nemají reprezentaci (cílová kódová stránka nemá pro daný znak žádnou reprezentaci).
- Pokud pracujete na Unixu, vyzkoušejte si, jaké problémy může přinést použití BOM u textového souboru v kódování UTF-8.
  - Soubor `skript.sh` z předchozího cvičení uložte s unixovou reprezentací znaků konce řádků, ale s BOM.
    - Vim na libovolné platformě: `:set fileformat=unix
      :set bomb
      :w ++enc=utf8`
  - Na unixovém systému nastavte souboru práva pro spuštění a pokuste se skript spustit: `$ chmod u+x skript.sh
    $ ./skript.sh
    ./skript.sh: řádek 1: ﻿#!/bin/bash: Adresář nebo soubor neexistuje
    Hello World!` Proč došlo k uvedené chybě?