Műszaki cikk

Unicode-biztos táblázat exportálás Delphiben: RTF és HTML

Egy táblázat egy oszlopnyi ügyfélnevet tartalmaz. Némelyik kínai, némelyik cirill, néhány német umlautot vagy francia ékezetet hordoz. Exportálja CSV formátumba, megnyitja az eredményt, és minden karakter érintetlen. Ugyanezt a munkafüzetet exportálja RTF-be egy körlevél-sablonhoz, megnyitja egy szövegszerkesztőben, és a nem ASCII nevek kérdőjelek sorává omlottak össze. Az adatok sosem változtak. Ami megváltozott, az a formátum kódolási szerződése, amelyet írt, és minden exportálási útvonal egy másikat hordoz

Ez a csapda foglyul ejt egy olyan könyvtárat, amely a felszínen teljesen Unicode-tudatosnak tűnik. A cellaszöveget belsőleg WideString-ként tárolja, így a modell sosem veszít el egy karaktert sem. A veszteség a határon történik, abban az íróban, amelynek szerializálnia kell a szöveget egy olyan formátumba, amelynek saját szabályai vannak arra vonatkozóan, hogy mely bájtok megengedettek, és hogyan kell kódolni mindent, ami a megengedett tartományon kívül esik. Csináljon meg egy írót jól, és még mindig kiadhat egy másikat, amely tönkreteszi ugyanazt a szöveget. A javítás nem egy globális kapcsoló. Ez egy különálló, helyes döntés minden egyes útvonalon

Az RTF tervezésénél fogva 7-bites biztonságos formátum

A Rich Text formátum megelőzi a Unicode-ot, és úgy specifikálták, hogy túlélje a csak nyomtatható ASCII-t átengedő szállításokat. Egy RTF dokumentum kódlapot deklarál a fejlécében, és minden olyan karaktert, amelyet az író nem tud ábrázolni abban a kódlapban, escape-ként kell kibocsátani nyers bájt helyett. A vonatkozó escape a \u, amely egy előjeles 16-bites kódegységet hordoz, amelyet egy ASCII tartalék karakter követ azon olvasók számára, akik túl régiek ahhoz, hogy egyáltalán megértsék az escape-et

A HotXLS így írja az RTF-et. A dokumentum fejléce a kódlap deklarálásával nyit \ansi\ansicpg1252\uc1 formában, és az lxRTF unitban lévő író végigmegy minden sztringen, és minden sima ASCII feletti karaktert \u escape-ként bocsát ki, így a bájtfolyam 7-bites tiszta marad függetlenül attól, hogy a deklarált kódlap mit képes tárolni. Egy olyan kódpont, mint az U+4E2D a \u20013? literál szekvenciává válik, nem egy nyers bájttá, amelyet egy megjelenítő megpróbálna értelmezni azon a kódlapon keresztül, amelyet éppen feltételez. E fegyelem nélkül bárminek, ami a deklarált kódlapon kívül esik, nincs érvényes bájtábrázolása, és egy író, amely a nyers értéket bocsátja ki, azokat a kérdőjeleket állítja elő, amelyekkel ez a cikk kezdődött

A részlet, amit észben kell tartani, hogy a deklarált kódlap és az escape-ek egy szerződés két fele. Maga a kódlap deklarálása nem segít az azon kívül eső szövegen. Az escape-ek kibocsátása deklarált kódlap nélkül kétértelművé teszi a tartalék karaktereket. Mindkettőnek együtt kell helyesnek lennie, ezért bukik el egy olyan író, amely csak az egyiket kezeli, már az első többnyelvű munkafüzetnél

A HTML escaping többről szól a kacsacsőröknél

A HTML exportálás egy többlapos dokumentumot hoz létre, amelynek navigációs keretei a lapok neveit látható szövegként hordozzák. Ezek a nevek szerző által vezérelt sztringek, amelyek bármilyen karaktert tartalmazhatnak, beleértve a jelölés szempontjából jelentőseket is. Egy szó szerint Q1 & Q2 <draft> nevű lapnak escape-elt entitásokként kell elérnie az oldalt, különben a kacsacsőrök egy fantom címkét nyitnak, és az "és" jel (ampersand) egy soha nem tervezett entitáshivatkozást indít el. Ez egy közönséges HTML escaping, és ennek kihagyása egy keretcímkén az a fajta mulasztás, amely minden csak ASCII lapnevekre épített teszten átmegy

A kódolási kérdés egy réteggel ez alatt helyezkedik el. Amikor a nem ASCII karakterek olyan kontextusba kerülnek, ahol nem garantált a UTF-8 kiszolgálása, a biztonságos ábrázolás egy numerikus karakterhivatkozás, így az U+00E9 é formában íródik, nem pedig nyers bájtként, amelynek jelentése a válasz karakterkészletétől függ. Ennek a szabálynak a tükörképe érvényes befelé jövet. Egy XLSX-ből visszaolvasott munkafüzet olyan megosztott sztringeket hordoz, amelyekben egy karakter már numerikus XML entitásként lehet tárolva, és ezt az entitást egy egész karakterré kell dekódolni, mielőtt bekerülne a cellamodellbe. Ha óvatlanul dekódolja, külön bájtokra osztva egy kódpontot, és egyetlen karakter két darab mojibake-ként (krixkraxként) jelenik meg újra, amelyet semmilyen későbbi exportálás nem tud megjavítani

Az XLSX konténer egy ZIP, és a ZIP saját névkódolással rendelkezik

Egy XLSX fájl egy ZIP archívum, és az archívum minden általa tartalmazott taghoz tárol egy nevet. A ZIP elég régi ahhoz, hogy eredeti specifikációja semmit sem mondott ezen nevek kódolásáról, így egy olvasó, amely nem talál jelzést, feltételezi az archívum helyi kódlapját. Ez a feltételezés abban a pillanatban helytelen lesz, amint egy tagnév nem ASCII karaktert tartalmaz, ami honosított munkalap alkatrésznevek és beágyazott média esetén történik, amelyek fájlnevei ékezeteket vagy nem latin betűket hordoznak

A javítás egyetlen bit. Az általános célú 11-es bit minden helyi fájl fejlécében deklarálja, hogy a tagnév UTF-8-ként van kódolva. A HotXLS pontosan ezt a bitet ellenőrzi, amikor egy archívumot olvas, tesztelve az általános célú jelzőket a $0800 maszk ellen, és egy olvasó vagy író, amely figyelmen kívül hagyja, rosszul fogja olvasni azt a nevet, amelyet egy helyes implementáció UTF-8-ként tárolt. Ezt a bitet olcsó beállítani és olcsó tiszteletben tartani, és ez az egész különbség egy olyan tagnév között, amely túléli az oda-vissza utat, és egy olyan között, amely korruptan érkezik meg, mielőtt még a táblázat tartalmát egyáltalán elemeznék

A kis- és nagybetűsítés, valamint a számolvasás ugyanazt a veszélyt rejti

A képletek kiértékelésénél a Unicode biztonság már nem a szerializálásról, hanem az összehasonlításról szól. A SEARCH (KERES) függvény kis- és nagybetűkre érzéketlen, ami azt jelenti, hogy egységesítenie kell a kis- és nagybetűket, mielőtt egy alsztringet keres. A helytelen módja ennek az ANSI kódlapon keresztül történő konvertálás, mert a nem ASCII szöveg ily módon történő nagybetűsítése egy szűk kódlapon vezeti át a karaktereket, és mindent megrongál, ami ezen kívül esik. A helyes út a wide-string nagybetűsítés, amely megőrzi a teljes UTF-16 tartományt. A HotXLS pontosan ebből az okból a WideUpperCase segítségével végzi az egységesítést, így az ékezetes vagy nem latin szöveg keresése pontosan azokkal a karakterekkel egyezik meg, amelyeket megadtak neki, nem pedig a kódlap által megrongált közelítésükkel

A képlet tokenizer (szófejtő) egy hasonló kötelezettséget hordoz, aminek semmi köze a betűkhöz, és mindennel ahhoz van köze, hogy hol végződik egy token. A tudományos jelölés, mint például a 1E3 vagy a 2.5E-3 egyetlen numerikus literál, és a scannernek (leolvasónak) fel kell ismernie az E-t, egy opcionális előjelet, és a következő számjegyeket a szám részeként, ahelyett, hogy a bemenetet egy névre és egy azt követő különálló számra bontaná. Egy scanner, amely ezt helytelenül kezeli, egy tökéletesen érvényes konstanst parse (elemzési) hibává, vagy ami még rosszabb, egy csendesen hibás kifejezéssé változtat. Ez ugyanabba a vitába tartozik, mert mindkét eset arról szól, hogy egy olvasó helyes karakterszintű döntést hoz: az egyik arról, hogyan egységesítsen egy karaktert az összehasonlításhoz, a másik pedig arról, hogy egy karakter folytatja-e a jelenlegi tokent

Többnyelvű munkafüzet építése és exportálása

A nyilvános API nem kéri öntől, hogy mindezen gondolkodjon. Ön WideString cellaértékekből építi fel a munkafüzetet, és meghívja a kívánt exportálási belépési pontot. A kódolási döntések minden egyes írón belül történnek. Az alábbi példa egy lapot több különböző írásrendszerű szöveggel lát el, majd ír egy RTF fájlt és egy HTML fájlt is ugyanabból a munkafüzetből, így a két útvonal azonos bemenettel fut le

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;

Mindkét hívás egy Integer státuszt ad vissza, és mindkettő ugyanazt a memóriában lévő szöveget használja. A hívó kódban semmi sem deklarál kódlapot vagy escape-el egy karaktert, mert a felelősség azon az írón nyugszik, amely ismeri a saját formátumát. A munkafüzet szintű SaveAsCSV ugyanezt a formát követi, ha tagolt exportálásra van szüksége ugyanabból a forrásból

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

A Unicode biztonság útvonalankénti, nem könyvtárankénti

A lecke, amit érdemes magunkkal vinni, hogy nincs egyetlen hely, ahol Unicode-biztosnak kell lenni. Az RTF-nek deklarált kódlapra és \u escape-ekre van szüksége. A HTML-nek entitás escape-elésre van szüksége a jelölés szempontjából jelentős karakterekhez, és numerikus hivatkozásokra ott, ahol a karakterkészlet nem garantált, valamint a megosztott sztringekben érkező entitások helyes dekódolására. A ZIP konténernek szüksége van az általános célú 11-es bit beállítására, hogy egy UTF-8 tagnév UTF-8-ként legyen olvasva. A képletek kiértékelésének wide-string kis- és nagybetű egységesítésre és egy olyan tokenizer-re van szüksége, amely egyben tartja a tudományos jelölést. Ezek mindegyike egy különböző szerződés, és egy könyvtár kielégítheti az egyiket, miközben csendben megsérti a másikat. Ez az oka annak, hogy egy eszköz, amely jól csinálja a CSV-t, még mindig átadhat önnek egy kérdőjelekkel teli RTF-et

Ha az ön exportálásai a tagolt formátumokra támaszkodnak, a köztük lévő kompromisszumokat lefedi a CSV, TSV és HTML exportálást bemutató útmutatónk, és ha a forrás egy eredményhalmaz, nem pedig egy kézzel épített lap, a Delphi jelentésekhez készült adatbázis exportálás mintái természetesen párosulnak az itt leírt kódolási szabályokkal. Mindez a Delphi és C++Builder rendszerekhez készült HotXLS komponens részeként érkezik, az olvasási, képlet- és formázási API-k mellett, amelyekről a blog más részein esik szó