Technical Article

Unicode atžvilgiu saugus skaičiuoklės eksportas Delphi aplinkoje: RTF ir HTML

Skaičiuoklė turi klientų vardų stulpelį. Kai kurie yra kinų kalba, kai kurie – kirilica, keli turi vokiečių kalbos umlautus arba prancūzišką kirtį. Eksportuojate jį į CSV ir atidarote rezultatą – kiekvienas simbolis yra nepažeistas. Eksportuojate tą pačią darbaknygę į RTF, skirtą laiškų suliejimo šablonui, atidarote tekstų rengyklėje – ne ASCII vardai sugriuvo į klaustukų eilutes. Duomenys niekada nesikeitė. Pasikeitė jūsų parašyto formato kodavimo sutartis, ir kiekvienas eksporto kelias neša skirtingą

Tai spąstai, kurie pagauna biblioteką, kuri iš pažiūros atrodo visiškai suderinama su Unicode. Langelyje tekstas viduje laikomas kaip WideString, todėl modelis niekada nepraranda simbolio. Praradimas įvyksta ties riba, rašytuve, kuris turi serializuoti tą tekstą į formatą su jo paties taisyklėmis apie tai, kurie baitai yra teisėti ir kaip turi būti užkoduotas bet kas už leistino diapazono ribų. Sutvarkykite vieną rašytuvą ir vis tiek galite išsiųsti kitą, kuris sugadina tą patį tekstą. Pataisymas nėra visuotinis jungiklis. Tai atskiras, teisingas sprendimas kiekviename kelyje

RTF yra saugus 7 bitų formatas pagal konstrukciją

Raiškiojo teksto formatas (angl. Rich Text Format arba RTF) atsirado anksčiau nei Unicode ir buvo sukurtas išgyventi transportavimą, kuris praleidžia tik spausdinamą ASCII. RTF dokumentas savo antraštėje deklaruoja kodų lentelę, o bet koks simbolis, kurio rašytuvas negali pavaizduoti toje kodų lentelėje, turi būti išvedamas kaip pabėgimo (angl. escape) seka, o ne kaip neapdorotas baitas. Atitinkama seka yra \u, kuri neša pasirašytą 16 bitų kodo vienetą, po kurio seka ASCII atsarginis simbolis skaitytuvams, per seniems suprasti pačią seką

HotXLS rašo RTF šiuo būdu. Dokumento antraštė atsidaro deklaruojant kodų lentelę, forma \ansi\ansicpg1252\uc1, o rašytuvas lxRTF modulyje vaikšto per kiekvieną eilutę, išvesdamas bet kurį simbolį virš paprasto ASCII kaip \u seką, kad baitų srautas išliktų 7 bitų švarus nepriklausomai nuo to, ką gali talpinti deklaruota kodų lentelė. Kodo taškas, toks kaip U+4E2D, tampa pažodine seka  3?, o ne neapdorotu baitu, kurį peržiūros programa bandytų interpretuoti per bet kurią kodų lentelę, kurią ji prisiėmė. Be šios tvarkos bet kas už deklaruotos kodų lentelės ribų neturi teisėto baitų atvaizdavimo, o rašytuvas, kuris išveda neapdorotą reikšmę, sukuria klaustukus, nuo kurių prasidėjo šis straipsnis

Detalė, kurią reikia turėti omenyje, yra ta, kad deklaruota kodų lentelė ir sekos yra dvi tos pačios sutarties pusės. Vien tik kodų lentelės deklaravimas nepadeda tekstui, kuris yra už jos ribų. Sekų išvedimas be deklaruotos kodų lentelės palieka atsarginius simbolius dviprasmiškus. Abu turi būti teisingi kartu, todėl rašytuvas, kuris tvarko tik vieną iš jų, vis tiek sugenda ties pirmąja daugiakalbe darbaknyge

HTML pabėgimas yra susijęs su daugiau nei tik kampiniais skliaustais

HTML eksportas sukuria kelių lapų dokumentą, kurio navigacijos rėmeliai neša lapų pavadinimus kaip matomą tekstą. Šie pavadinimai yra autoriaus kontroliuojamos eilutės, kuriose gali būti bet koks simbolis, įskaitant reikšmingus žymėjimui. Lapas, pažodžiui pavadintas Q1 & Q2 <draft>, turi pasiekti puslapį kaip pabėgę subjektai (angl. entities), kitaip kampiniai skliaustai atidaro vaiduoklišką žymą, o ampersandas pradeda subjekto nuorodą, kuri niekada nebuvo numatyta. Tai yra įprastas HTML pabėgimas, o jo praleidimas lapo etiketėje yra tas trūkumas, kuris praeina kiekvieną testą, sukurtą iš tik ASCII lapų pavadinimų

Kodavimo klausimas stovi vienu sluoksniu žemiau

Kai ne ASCII simboliai patenka į kontekstą, kuris nėra garantuotai pateikiamas kaip UTF-8, safe vaizdas yra skaitinio simbolio nuoroda, todėl U+00E9 rašomas kaip é, o ne kaip neapdorotas baitas, kurio reikšmė priklauso nuo atsakymo simbolių rinkinio. Šios taisyklės veidrodinis vaizdas taikomas pakeliui į vidų. Darbaknygė, nuskaityta atgal iš XLSX, neša bendrinamas eilutes, kuriose simbolis jau gali būti saugomas kaip skaitinis XML subjektas, ir šis subjektas turi būti iškoduotas į vieną sveiką simbolį prieš patenkant į langelio modelį. Iškoduokite jį nerūpestingai, padalydami kodo tašką į atskirus baitus, ir vienas simbolis vėl pasirodys kaip dvi mojibake dalys, kurių joks vėlesnis eksportas negalės pataisyti

XLSX konteineris yra ZIP, o ZIP turi savo pavadinimų kodavimą

XLSX failas yra ZIP archyvas, o archyvas saugo pavadinimą kiekvienam nariui, kurį laiko. ZIP yra pakankamai senas, kad jo originali specifikacija nieko nesakė apie šių pavadinimų kodavimą, zodžiu, skaitytuvas, neradęs jokio signalo, prisiima archyvo vietinę kodų lentelę. Ši prielaida yra neteisinga tą akimirką, kai nario pavadinime yra ne ASCII simbolis, kas nutinka su lokalizuotais darbalapio dalių pavadinimais ir su įterpta medija, kurios failų pavadinimai neša kirtis arba ne lotynų raštą

Pataisymas yra vienas bitas. Bendrosios paskirties bitas 11 kiekvienoje vietinėje failo antraštėje deklaruoja, kad nario pavadinimas užkoduotas kaip UTF-8. HotXLS tikrina būtent šį bitą, kai skaito archyvą, testuodamas bendrosios paskirties vėliavėles prieš kaukę $0800, o skaitytuvas ar rašytuvas, kuris jį ignoruoja, klaidingai perskaitys pavadinimą, kurį teisingas įgyvendinimas išsaugojo kaip UTF-8. Bitas yra pigus nustatyti ir pigus gerbti, ir tai yra visas skirtumas tarp nario pavadinimo, kuris išgyvena kelionę pirmyn ir atgal, ir to, kuris atvyksta sugadintas dar prieš analizuojant skaičiuoklės turinį

Dydžio keitimas ir skaičių nuskaitymas slepia tą patį pavojų

Formula įvertinimas yra ta vieta, kur Unicode saugumas nustoja būti apie serializaciją ir tampa apie palyginimą. SEARCH funkcija yra nejautri didžiosioms ir mažosioms raidėms, o tai reiškia, kad ji turi sulankstyti dydį (angl. case folding) prieš ieškodama poeilutės. Neteisingas būdas sulankstyti yra per ANSI kodų lentelę, nes taip rašant ne ASCII tekstą simboliai nukreipiami per siaurą kodų lentelę ir sugadinama viskas už jos ribų. Teisingas būdas yra plačios eilutės (angl. wide-string) didžiųjų raidžių keitimas, kuris išlaiko visą UTF-16 diapazoną. HotXLS sulanksto su WideUpperCase būtent dėl šios priežasties, todėl ieškant akcentuoto ar ne lotynų teksto atitinka tie patys simboliai, kurie buvo duoti, o ne kodų lentelės sugadintas jų priartinimas

Formulės leksinės analizės įrankis (angl. tokenizer) neša susijusį įsipareigojimą, kuris neturi nieko bendra su raidėmis ir visko su tuo, kur baigiasi leksema (angl. token). Mokslinė notacija, tokia kaip 1E3 arba 2.5E-3, yra vienas skaitinis literatūra (angl. literal), ir skaitytuvas turi atpažinti E, pasirinktinį ženklą ir vėlesnius skaitmenis kaip skaičiaus dalį, užuot suskaidęs įvestį į pavadinimą, po kurio seka atskiras skaičius. Skaitytuvas, kuris netinkamai tai tvarko, paverčia visiškai teisingą konstantą analizavimo klaida arba, dar blogiau, tyliai neteisinga išraiška. Tai priklauso tai pačiai diskusijai, nes abu atvejai yra apie skaitytuvą, priimantį teisingą simbolių lygio sprendimą: vieną apie tai, kaip sulankstyti simbolį palyginimui, kitą apie tai, ar simbolis tęsia dabartinę leksemą

Daugiakalbės darbaknygės kūrimas ir eksportavimas

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;

Viešasis API neprašo jūsų apie tai galvoti. Kuriate darbaknygę iš WideString langelių reikšmių ir iškviečiate norimą eksporto įėjimo tašką. Kodavimo sprendimai vyksta kiekvieno rašytuvo viduje. Žemiau pateiktas pavyzdys užsėja lapą tekstu keliomis rašto sistemomis, o tada rašo ir RTF failą, ir HTML failą iš tos pačios darbaknygės, kad abu keliai veiktų prieš identišką įvestį

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

Abu iškvietimai grąžina Integer būseną ir abu sunaudoja tą patį atmintyje esantį tekstą. Niekas kviečiančiame kode nedeklaruoja kodų lentelės ir neišsuka simbolio, nes atsakomybė tenka rašytuvui, kuris žino savo formatą. Darbaknygės lygio SaveAsCSV seka tą pačią formą, jei jums reikia eksporto su skirtukais iš identiško šaltinio

Unicode saugumas yra pagal kelią, o ne pagal biblioteką

Jei jungiate savo domenų matematiką į tą patį variklį, tvarkyklės registravimo ir reikšmių grąžinimo mechanika aprašyta mūsų straipsnyje apie formulės variklio išplėtimą pasirinktinėmis funkcijomis, o kai tos formulės turi pasiekti lapus pagal pavadinimą, o ne pagal langelio adresą, apžvalga apie apibrėžtus pavadinimus ir kryžmines formules rodo, jak išsprendžiamos nuorodos. Čia aprašytos inžinerinės funkcijos tiekiamos kaip dalis HotXLS komponento, skirto Delphi ir C++Builder, kartu su skaitymo, formulių ir formatavimo API, aptariamomis kitur šiame tinklaraštyje