Tabela sadrži kolonu sa imenima klijenata. Neka su na kineskom, neka na ćirilici, nekoliko nosi nemačke umlaute ili francuski akcenat. Izvezete je u CSV i otvorite rezultat, i svaki karakter je netaknut. Izvezete istu radnu svesku u RTF za šablon spajanja pisama, otvorite je u procesoru teksta, a ne-ASCII imena su se pretvorila u redove znakova pitanja. Podaci se nikada nisu promenili. Ono što se promenilo jeste ugovor o kodiranju formata koji ste pisali, a svaka putanja izvoza nosi drugačiji ugovor.
Ovo je zamka koja hvata biblioteku koja na površini izgleda potpuno svesna Unicode-a. Tekst ćelije se interno čuva kao WideString, tako da model nikada ne gubi karakter. Gubitak se dešava na granici, u piscu koji mora da serijalizuje taj tekst u format sa sopstvenim pravilima o tome koji su bajtovi dozvoljeni i kako sve izvan dozvoljenog opsega mora biti kodirano. Ako ispravite jednog pisca, i dalje možete isporučiti drugog koji kvari isti tekst. Rešenje nije globalni prekidač. To je zasebna, ispravna odluka na svakoj putanji.
RTF je po dizajnu 7-bitno bezbedan format
Rich Text Format je nastao pre Unicode-a i specifikovan je tako da preživi prenose koji propuštaju samo ASCII znakove koji se mogu štampati. RTF dokument deklariše kodnu stranicu u svom zaglavlju, a svaki karakter koji pisac ne može da predstavi u toj kodnoj stranici mora biti emitovan kao escape sekvenca umesto kao sirovi bajt. Relevantna escape sekvenca je \u, koja nosi označenu 16-bitnu kodnu jedinicu praćenu ASCII rezervnim karakterom za čitače koji su prestari da bi uopšte razumeli tu sekvencu.
HotXLS piše RTF na ovaj način. Zaglavlje dokumenta se otvara deklarisanjem kodne stranice u obliku \ansi\ansicpg1252\uc1, a pisac u jedinici lxRTF prolazi kroz svaki tekstualni niz emitujući svaki karakter iznad običnog ASCII-ja kao escape sekvencu \u tako da tok bajtova ostaje 7-bitno čist bez obzira na to šta deklarisana kodna stranica može da sadrži. Kodna tačka kao što je U+4E2D postaje doslovni niz 3?, a ne sirovi bajt koji bi čitač potom pokušao da protumači kroz bilo koju kodnu stranicu za koju se pretpostavlja da je aktivna. Bez te discipline, bilo šta izvan deklarisane kodne stranice nema legalnu reprezentaciju bajtova, a pisac koji emituje sirovu vrednost proizvodi znakove pitanja koji su započeli ovaj članak.
Detalj koji treba imati na umu jeste da su deklarisana kodna stranica i escape sekvence dve polovine jednog ugovora. Samo deklarisanje kodne stranice ne pomaže tekstu koji se nalazi izvan nje. Emitovanje sekvenci bez deklarisane kodne stranice ostavlja rezervne karaktere dvosmislenim. Both have to be correct together, which is why a writer that handles only one of them still fails on the first multilingual workbook.
HTML izbegavanje je više od izlomljenih zagrada
HTML izvoz proizvodi dokument sa više listova čiji navigacioni okviri nose nazive listova kao vidljiv tekst. Ti nazivi su tekstualni nizovi pod kontrolom autora koji mogu sadržati bilo koji karakter, uključujući i one značajne za označavanje. List koji se doslovno zove Q1 & Q2 <draft> mora stići na stranicu kao izbegnuti entiteti, inače izlomljene zagrade otvaraju fantomski tag, a ampersand započinje referencu entiteta koja nikada nije bila nameravana. Ovo je obično HTML izbegavanje, a njegovo preskakanje na oznaci okvira je vrsta propusta koja prolazi svaki test napravljen sa nazivima listova koji sadrže samo ASCII znakove.
Kada ne-ASCII karakteri slete u kontekst za koji nije garantovano da će biti poslužen kao UTF-8, sigurna reprezentacija je numerička referenca karaktera, tako da se U+00E9 piše kao é, a ne kao sirovi bajt čije značenje zavisi od skupa karaktera odgovora. Slika u ogledalu ovog pravila se primenjuje na ulazu. Radna sveska pročitana nazad iz XLSX formata nosi deljene tekstualne nizove u kojima karakter već može biti sačuvan kao numerički XML entitet, i taj entitet se mora dekodirati u jedan ceo karakter pre nego što uđe u model ćelije. Dekodirajte ga nepažljivo, deleći kodnu tačku na zasebne bajtove, i jedan karakter se ponovo pojavljuje kao dva dela iskrivljenog teksta koje nijedan kasniji izvoz ne može da popravi.
XLSX kontejner je ZIP, a ZIP ima sopstveno kodiranje imena
XLSX datoteka je ZIP arhiva, a arhiva čuva ime za svakog člana kojeg sadrži. ZIP je dovoljno star da njegova originalna specifikacija nije govorila ništa o kodiranju tih imena, tako da čitač koji ne pronađe signal pretpostavlja lokalnu kodnu stranicu arhive. Ta pretpostavka je pogrešna onog trenutka kada ime člana sadrži ne-ASCII karakter, što se dešava sa lokalizovanim nazivima delova radnog lista i sa ugrađenim medijima čija imena datoteka nose akcente ili ne-latinično pismo.
Rešenje je jedan bit. Bit opšte namene 11 u svakom lokalnom zaglavlju datoteke deklariše da je ime člana kodirano kao UTF-8. HotXLS proverava tačno taj bit kada čita arhivu, testirajući zastavice opšte namene u odnosu na masku $0800, a čitač ili pisac koji ga ignoriše pogrešno će pročitati ime koje je ispravna implementacija sačuvala kao UTF-8. Taj bit je jeftin za postavljanje i jeftin za poštovanje, i to čini celu razliku između imena člana koje preživljava povratno putovanje i onog koje stiže oštećeno pre nego što se sadržaj tabele uopšte parsira.
Poređenje veličine slova i skeniranje brojeva kriju istu opasnost
Procena formule je mesto gde bezbednost Unicode-a prestaje da se bavi serijalizacijom i počinje da se bavi poređenjem. Funkcija SEARCH je neosetljiva na velika i mala slova, što znači da mora da izjednači veličinu slova pre nego što potraži podniz. Pogrešan način za izjednačavanje veličine slova je kroz ANSI kodnu stranicu, jer pretvaranje ne-ASCII teksta u velika slova na taj način usmerava karaktere kroz usku kodnu stranicu i kvari bilo šta izvan nje. Ispravan način je pretvaranje u velika slova širokih tekstualnih nizova, što čuva pun UTF-16 opseg. HotXLS vrši izjednačavanje sa WideUpperCase upravo iz tog razloga, tako da pretraga za akcentovanim ili ne-latiničnim tekstom odgovara istim karakterima koji su joj dati, a ne aproksimaciji koja je pokvarena kodnom stranicom.
Tokenizer formula nosi srodnu obavezu koja nema nikakve veze sa slovima, a ima sa tim gde se token završava. Naučna notacija kao što je 1E3 ili 2.5E-3 jeste jedan numerički literal, i skener mora da prepozna E, opcioni znak i cifre koje slede kao deo broja, umesto da razbije ulaz na ime praćeno zasebnim brojem. Skener koji loše rukuje ovim pretvara potpuno ispravnu konstantu u grešku parsiranja ili, još gore, tiho pogrešan izraz. To pripada istoj raspravi jer se u oba slučaja radi o tome da čitač donosi ispravnu odluku na nivou karaktera: u jednom o tome kako da uporedi karakter, a u drugom o tome da li karakter nastavlja trenutni token.
Izgradnja i izvoz višejezične radne sveske
Javni API od vas ne traži da razmišljate o bilo čemu od ovoga. Vi gradite radnu svesku iz WideString vrednosti ćelija i pozivate izvoznu pristupnu tačku koju želite. Odluke o kodiranju se dešavaju unutar svakog pisca. Primer ispod popunjava list tekstom na nekoliko pisama, a zatim piše i RTF datoteku i HTML datoteku iz iste radne sveske, tako da se obe putanje pokreću na identičnom ulazu.
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';
// Cell text is held as WideString, so every script survives the 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: the lxRTF writer declares the code page and emits every
// non-ASCII character as a \u escape, keeping the file 7-bit clean.
Book.SaveAsRTF('Customers.rtf');
// HTML: sheet names are HTML-escaped and non-ASCII text is written
// so it does not depend on a guessed response charset.
Book.SaveAsHTML('Customers.html');
finally
Book := nil;
end;
end;
Oba poziva vraćaju status tipa Integer, i oba troše isti tekst u memoriji. Ništa u pozivnom kodu ne deklariše kodnu stranicu niti izbegava karakter, jer odgovornost leži na piscu koji poznaje sopstveni format. Metoda SaveAsCSV na nivou radne sveske prati isti oblik ako vam je potreban razgraničeni izvoz iz identičnog izvora.
// Same workbook, a third export path with its own encoding rules.
Book.SaveAsCSV('Customers.csv');
Unicode bezbednost je po putanji, a ne po biblioteci
Pouka koju vredi poneti jeste da ne postoji jedno mesto gde možete biti Unicode-bezbedni. RTF-u je potrebna deklarisana kodna stranica plus \u escape sekvence. HTML-u je potrebno izbegavanje entiteta za karaktere značajne za označavanje i numeričke reference tamo gde skup karaktera nije garantovan, plus ispravno dekodiranje entiteta koji stižu u deljenim tekstualnim nizovima. ZIP kontejneru je potreban postavljen bit opšte namene 11 kako bi se ime člana u UTF-8 formatu čitalo kao UTF-8. Procena formule zahteva poređenje veličine slova širokih tekstualnih nizova i tokenizer koji drži naučnu notaciju u jednom delu. Svaki od njih je različit ugovor, i biblioteka može da zadovolji jedan dok tiho narušava drugi. To je razlog zašto alatka koja ispravno radi CSV i dalje može da vam preda RTF pun znakova pitanja.
Ako se vaši izvozi oslanjaju na razgraničene formate, kompromisi između njih su pokriveni u našem vodiču kroz CSV, TSV i HTML izvoz, a kada je izvor skup rezultata, a ne ručno napravljen list, šabloni u izvozu baze podataka za Delphi izveštaje prirodno se uparuju sa ovde opisanim pravilima kodiranja. Sve to se isporučuje kao deo HotXLS komponente za Delphi i C++Builder, zajedno sa API-jima za čitanje, formule i formatiranje koji su pokriveni na drugim mestima na ovom blogu.