Tehnički članak

Izvoz tabela na bezbedan način po Unicode u Delphi-ju: RTF i HTML

Tabela sadrži kolonu sa imenima kupaca. Neka su na kineskom, neka na ćirilici, nekoliko njih nose nemačke umlaut znakove ili pak francuski akcenat. Vi to izvezete u CSV i otvorite taj rezultat, i vidite da je svaki karakter netaknut. Zatim vi izvezete tu istu radnu svesku (workbook) u RTF za mail-merge šablon, otvorite je u programu za obradu teksta, i vidite da su se svi ti ne-ASCII nazivi urušili u redove pune znakova pitanja. Podaci se pri tome nikada nisu promenili. Ono što se jeste promenilo je ugovor o kodiranju onog formata koji ste vi zapisali, a svaka takva putanja izvoza (export path) sa sobom nosi po jedan drugačiji takav ugovor

Ovo je tačno ona zamka koja uhvati biblioteku koja na prvi pogled izgleda kao da je potpuno svesna Unicode formata (Unicode-aware). Tekst ćelije se interno čuva kao WideString, tako da model sam po sebi apsolutno nikada ne gubi karakter. Gubitak se dešava na samoj granici, u pisaču (writer) koji mora da taj tekst serijalizuje u format koji donosi svoja sopstvena pravila o tome koji su bajtovi dozvoljeni (legal) i kako mora da se kodira bilo šta što se nađe izvan tog dozvoljenog opsega. Možete jedan pisač da uradite ispravno i svejedno da isporučite neki drugi pisač koji u potpunosti unakazi taj isti tekst. Popravka ovde nije neki jedan globalni prekidač (global switch). To je jedna odvojena i to sasvim ispravna odluka na svakoj pojedinačnoj putanji (path)

RTF je po svom dizajnu 7-bitni bezbedan format

Rich Text Format (RTF) prethodi samom Unicode-u i prvobitno je specifikovan tako da na mrežama preživi i one transporte koji propuštaju samo ispisivi ASCII. RTF dokument objavljuje svoju kodnu stranu (code page) u svom zaglavlju (header), i bilo koji karakter koji dati pisač (writer) ne može da predstavi na toj objavljenoj kodnoj strani, on obavezno mora da se emituje kao izlazna sekvenca (escape) umesto kao samo jedan sirovi bajt (raw byte). Takva relevantna izlazna sekvenca je \u, koja u sebi nosi označenu (signed) 16-bitnu kodnu jedinicu praćenu i to usput sa onim ASCII rezervnim (fallback) karakterom a sve to prvenstveno zbog čitača koji su suviše stari da bi uopšte razumeli taj escape niz

HotXLS zapisuje RTF upravo na ovaj način. Zaglavlje dokumenta se otvara tako što deklariše kodnu stranu u obliku \ansi\ansicpg1252\uc1, a pisač u sklopu lxRTF komponente prolazi kroz svaki string i emituje svaki karakter iznad običnog (plain) ASCII opsega isključivo kao \u escape sekvencu, tako da tok bajtova ostaje 7-bitno čist (7-bit clean) bez obzira na to šta deklarisana kodna strana može da zadrži. Kodna tačka poput U+4E2D postaje doslovna (literal) sekvenca \u20013?, a ne sirovi bajt koji bi pregledač potom pokušao da protumači kroz neku kodnu stranu za koju je pretpostavio da se koristi. Bez takve discipline, bilo šta van deklarisane kodne strane naprosto nema važeći (legal) bajtovski prikaz, pa pisač koji emituje sirovu vrednost na kraju zapravo i proizvodi one znakove pitanja s kojima je i započeo ovaj članak

Detalj koji treba imati na umu je da deklarisana kodna strana i escape sekvence zapravo predstavljaju dve polovine jednog istog ugovora. Deklarisanje samo kodne strane uopšte ne pomaže tekstu koji se nalazi izvan nje. S druge strane, emitovanje escape sekvenci bez deklarisane kodne strane ostavlja rezervne (fallback) karaktere nedefinisanim (ambiguous). Obe komponente moraju biti tačne istovremeno, što je i razlog zbog koga pisač koji barata sa samo jednom od njih i dalje glatko pada već na prvoj višejezičnoj radnoj svesci

HTML escape sekvence (escaping) predstavljaju znatno više od samo ugaonih zagrada

HTML izvoz proizvodi dokument sa više listova čiji navigacioni okviri (frames) nose imena listova kao vidljivi tekst. Ta imena su stringovi koje su ispisali autori te sveske i koji naravno mogu da sadrže baš bilo koji karakter, a sasvim podrazumevano u to spadaju i oni karakteri koji u HTML-u imaju specifično značenje. List koji se doslovno zove Q1 & Q2 <draft> mora da do same te stranice stigne u formatu escape entiteta (escaped entities), u suprotnom će one tamo ugaone zagrade otvoriti neki potpuno fantomski element (tag), a onaj tamo ampersand započeće referencu nekog entiteta iako ona nikada nije tu bila ni nameravana. Ovo je inače sasvim jedno najobičnije HTML escape formatiranje (escaping), te propuštanje njegovog primenjivanja na oznaci okvira spada tačno u onu vrstu propusta koji će rutinski proći baš svaki test koji se izradi samo sa imenima listova koji na sebi nose isključivo one ASCII (ASCII-only) znakove

Pitanje oko samog kodiranja zapravo leži čitav jedan sloj ispod toga. Kada god ne-ASCII karakteri pristanu negde unutar konteksta za koji se ne garantuje da će biti poslužen (served) kao UTF-8, sigurna reprezentacija je upravo numerička referenca karaktera, te se zbog toga vrednost U+00E9 piše kao é umesto da se zapiše kao jedan sirovi (raw) bajt čije bi značenje zavisilo direktno od uzvratnog skupa karaktera (response charset). Slika u ogledalu ovog pravila se primenjuje na ulazu. Radna sveska učitana unazad iz XLSX formata nosi sa sobom deljene (shared) stringove u kojima je neki karakter možda već sačuvan kao numerički XML entitet, a taj entitet mora obavezno biti dekodiran u jedan celi (whole) karakter pre nego što uopšte pristupi u taj usputni model ćelije. Ukoliko ga vi pak dekodirate sasvim nepažljivo, razbijajući kodnu tačku u neke zasebne bajtove, samo jedan znak može onda da ponovo izađe kao komad besmislenog teksta (mojibake) koji baš niti jedan kasniji izvoz više nikada neće moći da popravi

XLSX kontejner je ZIP datoteka, a ZIP ima sasvim svoje sopstveno kodiranje imena

Jedna XLSX datoteka je ZIP arhiva, a ta arhiva u sebi skladišti ime za svakog člana koga ona sadrži. ZIP je dovoljno star tako da njegova izvorna specifikacija nije rekla baš apsolutno ništa po pitanju samog kodiranja tih imena, pa čitač koji ne pronađe apsolutno nikakav signal prosto i pretpostavlja da je to neka lokalna kodna strana te arhive. Ta pretpostavka je pogrešna istog momenta kada ime nekog člana u sebi sadrži neki ne-ASCII karakter, što se i te kako dešava tamo gde su imena listova lokalizovana, kao i sa ubačenim medijskim datotekama čija imena fajlova nose akcente ili ne-latinično pismo

Ta popravka je jedan jedini bit. Bit opšte namene 11 u svakom lokalnom zaglavlju fajla deklariše da je ime člana kodirano kao UTF-8. HotXLS proverava upravo taj bit kada čita arhivu, testirajući zastavice opšte namene naspram maske $0800, a svaki čitač ili pisač koji to ignoriše će pogrešno očitati ime koje je ispravna implementacija originalno i sačuvala kao UTF-8. Taj bit je jeftin za postavljanje i jeftin za uvažavanje (poštovanje), a predstavlja celokupnu razliku između imena člana koje preživi taj krug u odnosu na onog drugog koje stigne oštećeno pre nego što sadržaj radne sveske uopšte i bude parsiran

Pretvaranje veličine slova (case folding) i skeniranje brojeva kriju potpuno isti taj rizik

Evaluacija formula je mesto gde Unicode bezbednost prestaje da bude priča o serijalizaciji i sasvim postaje priča o samom poređenju. Funkcija SEARCH je neosetljiva na veličinu slova (case-insensitive), što znači da mora da izvrši konverziju veličine (fold case) karaktera pre nego što potraži podstring (substring). Sasvim pogrešan način za ovu konverziju je pretvaranje kroz ANSI kodnu stranu, zato što pretvaranje ne-ASCII teksta u velika slova na takav način usmerava karaktere kroz potpuno usku kodnu stranu i u potpunosti kvari baš sve što je izvan nje. Pravi način jeste pretvaranje (uppercasing) širokih stringova (wide-string uppercasing), koje u celosti čuva kompletni i redovni UTF-16 opseg. HotXLS obavlja ovu konverziju upravo sa procedurom WideUpperCase, tako da pretraga za tekstom sa akcentima ili pak onim ne-latiničnim (non-Latin) tekstom podudara apsolutno sa onim karakterima koji su joj i dati (given), a ne sa aproksimacijom koja je oštećena (mangled) od strane kodne strane

Tokenizator formula u sebi nosi i onu sličnu obavezu koja zaista nema nikakve veze s slovima ali pak zaista u potpunosti onda ima apsolutne veze s tim gde se zapravo tačno neki token na kraju naravno završava (ends). Takozvana naučna notacija (Scientific notation) kao što je recimo 1E3 a i 2.5E-3 sasvim jeste jedan redovni i dati samo jedan jedini numerički literal, te skener apsolutno mora da prepozna E, opcioni znak, kao i cifre koje slede kao jedan jedinstven deo tog broja umesto da taj dati ulaz prelama u ime praćeno sa odvojenim brojem (separate number). Apsolutno svaki skener koji loše upravlja ovim pretvara potpuno validnu konstantu u grešku pri parsiranju ili, još i gore, u tiho pogrešan izraz. Sve ovo sasvim naravno i pripada prosto tu istoj raspravi zato što se oba slučaja tiču čitača koji mora da donese ispravnu odluku na samom nivou karaktera: jedna odluka o tome kako prilagoditi veličinu slova karaktera za potrebe poređenja, i druga o tome da li karakter nastavlja tekući token

Izgradnja i izvoz višejezične (multilingual) radne sveske

Javni API pak tu naravno uopšte i sasvim onda redom ne traži od vas prosto na te i sasvim onda da prosto razmišljate o bilo čemu od usput onako ovog obim iz komande iz navedenog na nju. Vi usput izgradićete (build) prosto onda redovnu tu obim i na te radnu svesku sasvim iz samih tu i onda usputnih (WideString) tih na nju redovnih WideString sasvim naravno i prosto tu vrednosti onda iz ćelija, i prosto ćete iz onih sa tim pozvati iz niza na nju naravno onu datu tu ulaznu naravno usput i prosto onda na tu uz redovnu usput naravno usput izrada (entry point) tačku za redovni usput na te onoj iz (export) obim izvoz obim usput (you want) koji onda i usput tu naravno sasvim i redovno želite. Primer sasvim ispod izrada usput prosto tu na njoj i popunjava obimno iz (seeds) obim i prosto i to usput (seeds) onako tu (seeds) usađuje u red i to sasvim naravno usput u taj list iz teksta u usput nekoliko sasvim obim različitih sasvim redovnih tu pisama (scripts), te obim onda usput naravno usput tu i redovno piše kako prosto jedan redovan tu usput (RTF) tu onako RTF izrada iz niza obim fajl naravno tako i sasvim redovno jedan usputni (HTML) HTML obim fajl prosto redom obim sasvim i obimno usput iz te iste naravno usput redovno u procedurama naravno na komandi i onako same usput i redovno u redu naravno radne sveske, pa obe te usput putanje iz komande i sasvim usput naravno naravno se obim iz (run) onda usput izvršavaju iz komande naravno sasvim usput u procedurama naspram naravno usput sasvim i prosto usput identičnog usput na nju datog tu naravno usput i redovno sasvim na onoj iz sasvim u rad na onom s telu usput i na komandi onako ulaza (input)

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 ova poziva na onom usput i redovno naravno usput prosto u obimu naravno redovno onda (return) iz i naravno tu vraćaju sasvim jedan redovan (Integer status) na njoj usput naravno usputni Integer sasvim prosto obimno redovno status, te sasvim onda i oba prosto naravno (consume) iz i onda konzumiraju sasvim isti naravno (in-memory) tu obimno tu (in-memory) u nizu i na te naravno sasvim (in-memory) in-memory obim (text) tu naravno usput i u redovima tekst. Apsolutno ništa sasvim u datom tom (calling code) prosto naravno i usput kodu usput obim (calling) tu koji prosto na te onako poziva usputno obim i onda ne deklariše naravno kodnu stranu iz niza naravno usput niti sasvim to prosto escape-uje i usput u radu usput to onda sasvim beži tj sasvim onako i izlazi naravno od bilo kakvog redovnog obim karaktera, zato što onda prosto ta naravno (responsibility) odgovornost usput naravno i redovno sasvim iz niza tu na komandi usput naravno onako leži u samom obim pisaču usput koji onda naravno sasvim i redovno usput sasvim onda redovno tu obimno u potpunosti usput onako sasvim i naravno sasvim i te na redu tu zna sasvim naravno taj tu svoj tu redovno sopstveni usput onako i u nizu format naravno iz niza. Sama ta obimna SaveAsCSV na nju usput na sasvim onda tu nivou redovno naravno usput date naravno te radne naravno redovno tu na njoj i onako sasvim na onoj sveske sasvim naravno tu usput sasvim i prati prosto onda obim usput naravno i redovno prosto isti sasvim na tu usput i naravno usput i onako (shape) iz i tu sasvim (shape) obrazac (shape) obimno ukoliko tu usput vam naravno usput sasvim zatreba sasvim jedan redovno prosto na nju onda tu redovni ograničeni (delimited) izvoz iz sasvim prosto i te na redu tu potpuno i sasvim redovno tu identičnog usput obim na te onda prosto sasvim i te kako iz i tu usput onako na onom i obim (source) naravno i redovno usput sasvim izvora obim i na onom sasvim i redovno u nizu u tu sa komandi naravno i redom onako

// Same workbook, a third export path with its own encoding rules.
Book.SaveAsCSV('Customers.csv');

Unicode bezbednost tu jeste na nivou po putanji, a ne onda po biblioteci

Lekcija koju naravno onda obimno i sasvim usput vredi prosto sasvim na nju odneti sa sasvim na te usput onda prosto obimno i sobom jeste tu onda prosto i naravno ta da sasvim ne postoji onako tu naravno usputno obimno sasvim jedno usput jedino sasvim i to tu usput mesto na kome naravno usput onda se prosto naravno bude sasvim i naravno tu u nizu usput bezbedno onako za Unicode obim iz komande. RTF traži sasvim deklarisanu kodnu stranu plus one tu usput iz niza naravno naravno iz i usput \u escape sekvence na tu. HTML traži naravno entity sasvim onako escape-ovanje za naravno usput i karakteristične naravno HTML znakove sasvim tu i naravno i redom usput obimno i numeričke usput reference obim iz komande sasvim tamo naravno tu gde sasvim onako na onom po proceduri i obimni u onom sasvim tajcharset obim i nije redovno tu u potpunosti garantovan naravno obim, plus prosto redovno sasvim ispravno sasvim i obim dekodiranje onih entiteta koji naravno tu sasvim stižu naravno iz ubačenih sasvim deljenih stringova usput naravno izrada iz onoga. Taj obim i naravno ZIP kontejner na njoj usput onda naravno tu traži onaj bit sasvim i opšte namene redovno usput broj 11 da prosto bude sasvim i redovno tu postavljen tako da se UTF-8 ime naravno datog tu usput člana sasvim obim na nju očitava kao UTF-8. Evaluacija sasvim date obim tu formule usput tu naravno zahteva sasvim iz na te onih (wide-string case folding) na tu iz i pretvaranje obimno naravno (folding) veličine obim i sasvim naravno (case) onako slova (wide-string) širokih naravno stringova obim i naravno sasvim i to prosto usput tu jedan (tokenizer) tokenizator usput naravno i tu naravno sasvim iz onih koji naravno iz niza to u onom drži obim sasvim i (scientific notation) tu i sasvim naučnu naravno tu obim notaciju obim naravno sasvim i redovno (in one piece) obimno tu naravno sasvim na onoj uz naravno u sasvim i usput naravno u jednom tu naravno datom redovno komadu obim. Apsolutno sasvim tu i svaki obimno onda od navedenog usput obim sasvim naravno prosto onako i od sasvim ovih u uvid je izrada u rad jedan tu potpuno usput naravno tu sasvim (different) usput i prosto naravno drugačiji usput naravno i redovno ugovor onako, te biblioteka sasvim naravno obim i redovno može usput prosto naravno da zadovolji usput jedan dok usput tiho sasvim naravno usput tu onda krši (violating) neki sasvim naravno usput drugi iz niza to usput na redu. Upravo je tu onda to i usputni naravno razlog sasvim onako i sasvim usput naravno zbog kojeg alat naravno tu prosto usput i naravno usput koji to uradi onako prosto (CSV right) sasvim to (CSV) sasvim prosto (right) kako treba obim i naravno (CSV) kod CSV-a onda usput može sasvim sasvim svejedno usput da naravno sasvim vama tu uruči (hand you) usput sasvim jedan naravno RTF sasvim prepun obim sasvim tu naravno onih na onoj znakova naravno (question marks) pitanja usput onako

Ukoliko se vaši izvozi pak onako prosto usput i oslanjaju na one date ograničene (delimited) formate, onda su sasvim tu (trade-offs) tu i oni ustupci onako prosto usput i kompromisi obimno iz (trade-offs) usput izrada iz onoga i (trade-offs) i naravno naravno redovno razlike između naravno svih obim i sasvim i naravno njih usput pokriveni tu sasvim i u našem prolazu tj vodiču onako za sasvim dati tu iz niza i prosto obimni (CSV, TSV and HTML export) sasvim obimni usput CSV, TSV naravno usput redovno kao i HTML tu naravno naravno redom onako izvoz, a naravno usput i kada naravno onda usput izrada i prosto sasvim i tu izvor (source) na njoj (is a result set) tu i jeste usput u procedurama i naravno onaj set rezultat na njoj (result set) u nizu po onoj po ubačenoj umesto tu neke prosto ručno građene obim (hand-built) obimno tu listove, sami ti tu na redu onda i sasvim naravno naravno usput i obrasci usput u obimnoj iz onih i prosto naravno izrada naravno tu bazi (database export) redovno iz onih (database) podataka usput naravno (database export) pri izvozu za (delphi reports) naravno sasvim i Delphi izveštaje usput se obim naravno tu i potpuno naravno (pair naturally) usput usput usput obim (pair) sasvim na njoj usput naravno i spajaju (pair) i (naturally) usput i sasvim i naravno prirodno onako sa redovno onda ovim sasvim naravno usput i redovno tu i objašnjenim tu prosto i navedenim sasvim pravilima o samom usput onako i kodiranju iz. Apsolutno naravno sasvim onda sve naravno iz onih to sasvim na njoj (ships) naravno dolazi tj usput i naravno (ships as part of the) redovno prosto i naravno sasvim u radu onda naravno se tu sasvim obim i isporučuje naravno kao sam usput tu (part of the) naravno i redovno usput obim i deo usput na onoj iz HotXLS usput i redovno u radu sasvim komponente (Component) za sasvim naravno Delphi usput i obim C++Builder obim iz komande usput naravno redovno, naravno usput (alongside) tu obim i sasvim uz prosto usput i naravno na komandi pored naravno obim API-ja naravno i redovno za obim čitanje naravno sasvim usput (formula) tu usput i naravno formule (and formatting) iz niza usput i naravno formatiranje iz komande usput koji onda naravno i tu i prosto usput i redovno jesu objašnjeni na na njoj usput usput (elsewhere) na na njemu obimno drugom sasvim naravno usput i redovno tu na te usput na onom po proceduri naravno nekom mestu onako (on this blog) sasvim onda na usput ovom tu naravno usput usput blogu sasvim obimno i redovno i prosto