Proračunska tablica sadrži stupac s imenima kupaca. Neka su na kineskom, neka na ćirilici, nekoliko nosi njemačke prijeglase (umlaut) ili francuski naglasak. Izvezete to u CSV i otvorite rezultat, i svaki je znak netaknut. Izvezete istu radnu knjigu u RTF za predložak spajanja pošte (mail-merge), otvorite ga u programu za obradu teksta, i ne-ASCII imena su se srušila u retke s upitnicima. Podaci se nikada nisu promijenili. Ono što se promijenilo je ugovor o kodiranju (encoding contract) formata koji ste pisali, a svaka putanja izvoza nosi drugačiji
Ovo je zamka u koju upada biblioteka koja na površini izgleda potpuno svjesna Unicodea. Tekst ćelije interno se čuva kao WideString, pa model nikada ne izgubi znak. Gubitak se događa na granici, u pisaču (writer) koji mora serijalizirati taj tekst u format s vlastitim pravilima o tome koji su bajtovi legalni i kako bilo što izvan legalnog raspona mora biti kodirano. Neka jedan pisač bude točan, a vi i dalje možete isporučiti drugi koji kvari isti tekst. Popravak nije globalni prekidač (switch). To je zasebna, ispravna odluka na svakoj putanji
RTF je dizajnom format siguran za 7 bita
Format obogaćenog teksta (Rich Text Format) prethodi Unicodeu i specificiran je da preživi prijenose (transports) koji propuštaju samo ispisivi ASCII. RTF dokument deklarira kodnu stranicu (code page) u svom zaglavlju, a svaki znak koji pisač ne može predstaviti u toj kodnoj stranici mora biti emitiran kao izbjegavajući znak (escape) umjesto kao sirovi (raw) bajt. Relevantni escape je \u, koji nosi predznačenu (signed) 16-bitnu kodnu jedinicu nakon koje slijedi znak povlačenja (fallback) iz ASCII-ja za čitače koji su prestari da uopće razumiju escape
HotXLS piše RTF na ovaj način. Zaglavlje dokumenta otvara se deklariranjem kodne stranice u obliku \ansi\ansicpg1252\uc1, a pisač u jedinici (unit) lxRTF prolazi kroz svaki niz (string) emitirajući svaki znak iznad običnog ASCII-ja kao \u escape, tako da tok bajtova (byte stream) ostaje 7-bitno čist bez obzira na to što deklarirana kodna stranica može sadržavati. Kodna točka (code point) kao što je U+4E2D postaje doslovni niz \u20013?, a ne sirovi bajt koji bi preglednik (viewer) onda pokušao interpretirati kroz koju god kodnu stranicu bi slučajno pretpostavio. Bez te discipline, bilo što izvan deklarirane kodne stranice nema legalnu reprezentaciju bajta, a pisač koji emitira sirovu vrijednost proizvodi upitnike s kojima je ovaj članak započeo
Detalj koji treba imati na umu je taj da su deklarirana kodna stranica i izbjegavajući znakovi (escapes) dvije polovice jednog ugovora. Samo deklariranje kodne stranice ne pomaže tekstu koji leži izvan nje. Emitiranje escape znakova bez deklarirane kodne stranice ostavlja fallback znakove dvosmislenima. Oboje moraju biti ispravni zajedno, što je razlog zašto pisač koji upravlja samo jednim od njih i dalje ne uspijeva na prvoj višejezičnoj radnoj knjizi
HTML izbjegavanje znakova odnosi se na više od kutnih zagrada
HTML izvoz proizvodi dokument s više listova (multi-sheet) čiji navigacijski okviri (frames) nose imena listova kao vidljivi tekst. Ta su imena nizovi koje kontrolira autor i koji mogu sadržavati bilo koji znak, uključujući one bitne za označavanje (markup-significant). List koji je doslovno nazvan Q1 & Q2 <draft> mora stići na stranicu kao izbjegnuti entiteti (escaped entities), inače kutne zagrade otvaraju fantomsku oznaku (tag), a ampersand započinje referencu entiteta koja nikada nije bila namjeravana. Ovo je obično HTML izbjegavanje (escaping), a preskakanje istog na oznaci (label) okvira je vrsta propusta koja prolazi svaki test izgrađen od imena listova koja su isključivo u ASCII-ju
Pitanje kodiranja sjedi jedan sloj ispod toga. Kada ne-ASCII znakovi slete u kontekst za koji nije zajamčeno da će biti poslužen (served) kao UTF-8, sigurna reprezentacija je numerička referenca znaka, pa se U+00E9 piše kao é, a ne kao sirovi bajt čije značenje ovisi o znakovnom skupu (charset) odgovora. Zrcalna slika ovog pravila primjenjuje se na putu unutra. Radna knjiga pročitana natrag iz XLSX-a nosi dijeljene nizove (shared strings) u kojima znak može već biti pohranjen kao numerički XML entitet, i taj entitet mora biti dekodiran u jedan cijeli znak prije nego što uđe u model ćelije. Dekodirajte to nepažljivo, razdvajajući kodnu točku u zasebne bajtove, i jedan jedini znak ponovno se pojavljuje kao dva komada mojibakea koje nijedan kasniji izvoz ne može popraviti
XLSX spremnik je ZIP, a ZIP ima vlastito kodiranje imena
XLSX datoteka je ZIP arhiva, a arhiva pohranjuje ime za svakog člana kojeg drži. ZIP je dovoljno star da njegova originalna specifikacija nije govorila ništa o kodiranju tih imena, pa čitač koji ne pronađe signal pretpostavlja lokalnu kodnu stranicu arhive. Ta je pretpostavka pogrešna u trenutku kada ime člana sadrži ne-ASCII znak, što se događa kod lokaliziranih naziva dijelova radnog lista (worksheet part) i s ugrađenim medijima čija imena datoteka nose naglaske (accents) ili nelatinično pismo
Popravak je jedan jedini bit. Bit opće namjene (general-purpose bit) 11 u svakom lokalnom zaglavlju datoteke deklarira da je ime člana kodirano kao UTF-8. HotXLS provjerava točno taj bit kada čita arhivu, testirajući zastavice opće namjene (general-purpose flags) protiv maske $0800, a čitač ili pisač koji ga ignorira pogrešno će pročitati ime koje je ispravna implementacija pohranila kao UTF-8. Taj bit je jeftin za postavljanje i jeftin za poštivanje (honour), i on čini cijelu razliku između imena člana koje preživi kružno putovanje (round trip) i onog koje stiže oštećeno prije nego što se sadržaj proračunske tablice uopće parsira
Preklapanje malih i velikih slova (case folding) i skeniranje brojeva skrivaju istu opasnost
Ocjenjivanje (evaluation) formula je mjesto gdje sigurnost Unicodea prestaje biti vezana uz serijalizaciju i postaje vezana uz usporedbu. Funkcija SEARCH ne osjeti razliku između velikih i malih slova (case-insensitive), što znači da mora preklopiti velika i mala slova (fold case) prije nego što potraži podniz (substring). Pogrešan način preklapanja je kroz ANSI kodnu stranicu, jer pretvaranje ne-ASCII teksta u velika slova na taj način preusmjerava znakove kroz usku kodnu stranicu i oštećuje sve što je izvan nje. Pravi način je pretvaranje širokog niza (wide-string) u velika slova, što čuva puni raspon UTF-16. HotXLS preklapa (folds) s WideUpperCase točno iz ovog razloga, tako da pretraživanje teksta s naglascima ili nelatiničnog teksta podudara iste znakove koji su mu zadani, a ne njihovu približnu verziju unakaženu (mangled) kodnom stranicom
Tokenizator (tokenizer) formula nosi srodnu obvezu koja nema nikakve veze sa slovima, a ima itekako veze s tim gdje token završava. Znanstvena notacija kao što je 1E3 ili 2.5E-3 jedan je numerički literal, a skener mora prepoznati E, neobavezni predznak (sign), te znamenke koje slijede kao dio broja umjesto da prelama unos na ime nakon kojeg slijedi zaseban broj. Skener koji s ovim pogrešno rukuje pretvara savršeno valjanu konstantu u pogrešku parsiranja ili, još gore, u tiho pogrešan izraz. To pripada istoj raspravi jer se oba slučaja bave time da čitač donosi ispravnu odluku na razini znaka: jedna je o tome kako preklopiti (fold) znak za usporedbu, a druga o tome nastavlja li znak trenutni token
Izgradnja i izvoz višejezične radne knjige
Javni API od vas ne traži da razmišljate o bilo čemu od ovoga. Gradite radnu knjigu od WideString vrijednosti ćelija i pozivate ulaznu točku izvoza koju želite. Odluke o kodiranju donose se unutar svakog pisača. Primjer u nastavku posijava list (seeds a sheet) tekstom na nekoliko pisama (scripts), a zatim piše i RTF datoteku i HTML datoteku iz iste radne knjige, tako da se obje putanje pokreću protiv istog ulaza
uses
lxHandle;
procedure ExportMultilingualWorkbook;
var
Book: IXLSWorkbook;
Sheet: IXLSWorksheet;
begin
Book := TXLSWorkbook.Create;
try
Sheet := Book.Sheets.Add('Customers');
Sheet.Cells[1, 1].Value := 'Name';
Sheet.Cells[1, 2].Value := 'City';
// Tekst ćelije čuva se kao WideString, tako da svako pismo preživi model.
Sheet.Cells[2, 1].Value := '王伟'; // Chinese
Sheet.Cells[2, 2].Value := '北京';
Sheet.Cells[3, 1].Value := 'Müller'; // German umlaut
Sheet.Cells[3, 2].Value := 'Köln';
Sheet.Cells[4, 1].Value := 'Иванов'; // Cyrillic
Sheet.Cells[4, 2].Value := 'Москва';
Sheet.Cells[5, 1].Value := 'Désirée'; // French accents
Sheet.Cells[5, 2].Value := 'Montréal';
// RTF: pisač lxRTF deklarira kodnu stranicu i emitira svaki
// ne-ASCII znak kao \u escape, čuvajući datoteku 7-bitno čistom.
Book.SaveAsRTF('Customers.rtf');
// HTML: imena listova su HTML-escaped, a ne-ASCII tekst je zapisan
// tako da ne ovisi o nagađanom znakovnom skupu odgovora.
Book.SaveAsHTML('Customers.html');
finally
Book := nil;
end;
end;
Oba poziva vraćaju status Integer, i oba konzumiraju isti tekst u memoriji. Ništa u pozivnom kodu ne deklarira kodnu stranicu ili izbjegava (escapes) znak, jer odgovornost leži na pisaču (writer) koji poznaje svoj vlastiti format. SaveAsCSV na razini radne knjige slijedi isti oblik ako trebate ograničeni (delimited) izvoz iz identičnog izvora
// Ista radna knjiga, treća putanja izvoza sa svojim vlastitim pravilima kodiranja.
Book.SaveAsCSV('Customers.csv');
Sigurnost Unicodea je po putanji, a ne po biblioteci
Lekcija koju vrijedi ponijeti sa sobom je da ne postoji jedno jedino mjesto za biti siguran s Unicodeom. RTF-u treba deklarirana kodna stranica plus \u escape znakovi. HTML-u je potrebno izbjegavanje entiteta (entity escaping) za znakove bitne za označavanje i numeričke reference tamo gdje znakovni skup nije zajamčen, uz ispravno dekodiranje entiteta koji pristižu u dijeljenim nizovima. ZIP spremniku je potreban postavljen bit 11 opće namjene kako bi se UTF-8 ime člana čitalo kao UTF-8. Ocjenjivanje formula treba preklapanje malih i velikih slova na razini širokog niza i tokenizator koji drži znanstvenu notaciju u jednom komadu. Svaki od ovih je različit ugovor, i biblioteka može zadovoljiti jedan dok tiho krši drugi. To je razlog zbog kojeg vam alat koji ispravno radi CSV i dalje može predati RTF pun upitnika
Ako se vaši izvozi oslanjaju na ograničene (delimited) formate, kompromisi (trade-offs) među njima obrađeni su u našem pregledu izvoza u CSV, TSV i HTML, a kada je izvor skup rezultata, a ne ručno izgrađen list, obrasci u izvozu baze podataka za Delphi izvješća prirodno se uparuju s pravilima kodiranja opisanim ovdje. Sve se to isporučuje kao dio HotXLS komponente za Delphi i C++Builder, uz API-je za čitanje, formule i formatiranje koji su obrađeni na drugim mjestima na ovom blogu