O foaie de calcul conține o coloană de nume de clienți. Unele sunt în chineză, altele în chirilică, câteva conțin umlauturi germane sau un accent francez. O exportați în CSV și deschideți rezultatul, și fiecare caracter este intact. Exportați același registru de lucru în RTF pentru un șablon de îmbinare a corespondenței, îl deschideți într-un procesor de text, iar numele non-ASCII s-au transformat în rânduri de semne de întrebare. Datele nu s-au schimbat niciodată. Ceea ce s-a schimbat este contractul de codificare al formatului pe care l-ați scris, iar fiecare cale de export are unul diferit
Aceasta este capcana care prinde o bibliotecă ce pare complet compatibilă cu Unicode la suprafață. Textul celulei este păstrat intern ca WideString, astfel încât modelul nu pierde niciodată un caracter. Pierderea are loc la limită, în scriitorul care trebuie să serializeze acel text într-un format cu propriile sale reguli despre ce octeți sunt legali și cum trebuie codificat orice este în afara intervalului legal. Puteți scrie corect un scriitor și să livrați un altul care distruge același text. Soluția nu este un comutator global. Este o decizie separată și corectă pe fiecare cale
RTF este un format sigur pe 7 biți prin design
Rich Text Format este anterior Unicode-ului și a fost specificat pentru a supraviețui transporturilor care transmit doar ASCII imprimabil. Un document RTF declară o pagină de cod în antetul său, iar orice caracter pe care scriitorul nu îl poate reprezenta în acea pagină de cod trebuie să fie emis ca o secvență de evadare în loc de un octet brut. Secvența de evadare relevantă este \u, care conține o unitate de cod pe 16 biți cu semn, urmată de un caracter ASCII de rezervă pentru cititoarele prea vechi pentru a înțelege deloc evadarea
HotXLS scrie RTF în acest fel. Antetul documentului începe prin declararea paginii de cod, sub forma \ansi\ansicpg1252\uc1, iar scriitorul din unitatea lxRTF parcurge fiecare șir emițând orice caracter mai sus de ASCII simplu ca o secvență de evadare \u, astfel încât fluxul de octeți să rămână curat pe 7 biți, indiferent de ce poate conține pagina de cod declarată. Un punct de cod precum U+4E2D devine secvența literală \u20013?, nu un octet brut pe care un vizualizator ar încerca apoi să îl interpreteze prin orice pagină de cod s-ar întâmpla să presupună. Fără această disciplină, orice este în afara paginii de cod declarate nu are o reprezentare legală în octeți, iar un scriitor care emite valoarea brută produce semnele de întrebare care au început acest articol
Detaliul de reținut este că pagina de cod declarată și evadările sunt două jumătăți ale unui singur contract. Declararea exclusivă a paginii de cod nu ajută textul care se află în afara sa. Emiterea de evadări fără o pagină de cod declarată lasă caracterele de rezervă ambigue. Ambele trebuie să fie corecte împreună, motiv pentru care un scriitor care gestionează doar una dintre ele încă eșuează la primul registru de lucru multilingv
Evadarea HTML înseamnă mai mult decât paranteze unghiulare
Exportul HTML produce un document cu mai multe foi, ale cărui cadre de navigare conțin numele foilor ca text vizibil. Aceste nume sunt șiruri controlate de autor care pot conține orice caracter, inclusiv cele semnificative pentru marcaj. O foaie numită literalmente Q1 & Q2 <draft> trebuie să ajungă pe pagină sub formă de entități evadate, altfel parantezele unghiulare deschid o etichetă fantomă, iar ampersand-ul începe o referință de entitate care nu a fost intenționată niciodată. Aceasta este o evadare HTML obișnuită, iar omiterea acesteia pe o etichetă de cadru este genul de omisiune care trece fiecare test creat din nume de foi exclusiv ASCII
Întrebarea de codificare se află cu un strat mai jos. Când caracterele non-ASCII ajung într-un context care nu este garantat a fi servit ca UTF-8, reprezentarea sigură este o referință numerică de caracter, deci U+00E9 este scris ca é în loc de un octet brut a cărui semnificație depinde de setul de caractere al răspunsului. Imaginea în oglindă a acestei reguli se aplică la intrare. Un registru de lucru citit înapoi din XLSX conține șiruri partajate în care un caracter ar putea fi deja stocat ca o entitate XML numerică, iar acea entitate trebuie decodificată într-un caracter întreg înainte de a intra în modelul celulei. Dacă îl decodificați neatent, împărțind un punct de cod în octeți separați, un single caracter reapare ca două bucăți de mojibake pe care niciun export ulterior nu le poate repara
Containerul XLSX este un fișier ZIP, iar ZIP are propria sa codificare a numelor
Un fișier XLSX este o arhivă ZIP, iar arhiva stochează un nume pentru fiecare membru pe care îl conține. ZIP este suficient de vechi încât specificația sa originală nu a spus nimic despre codificarea acelor nume, așa că un cititor care nu găsește niciun semnal presupune pagina de cod locală a arhivei. Această presupunere este greșită în momentul în care numele unui membru conține un caracter non-ASCII, ceea ce se întâmplă cu numele pieselor foilor de lucru localizate și cu media încorporată ale căror nume de fișiere conțin accente sau scripturi non-latine
Soluția este un singur bit. Bitul de uz general 11 din fiecare antet de fișier local declară că numele membrului este codificat ca UTF-8. HotXLS verifică exact acel bit atunci când citește o arhivă, testând steagurile de uz general în funcție de masca $0800, iar un cititor sau un scriitor care îl ignoră va citi greșit un nume pe care o implementare corectă l-a stocat ca UTF-8. Bitul este ieftin de setat și ieftin de onorat, și reprezintă întreaga diferență dintre numele unui membru care supraviețuiește unei călătorii dus-întors și unul care ajunge corupt înainte ca conținutul foii de calcul să fie măcar analizat
Conversia majuscule-minuscule și scanarea numerelor ascund același pericol
Evaluarea formulelor este momentul în care siguranța Unicode încetează să mai fie despre serializare și devine despre comparație. Funcția SEARCH este insensibilă la majuscule și minuscule, ceea ce înseamnă că trebuie să uniformizeze majusculele/minusculele înainte de a căuta un subșir. Modul greșit de uniformizare este prin pagina de cod ANSI, deoarece conversia textului non-ASCII în majuscule în acest fel direcționează caracterele printr-o pagină de cod restrânsă și corupe tot ce se află în afara ei. Modul corect este conversia în majuscule a șirurilor largi, care păstrează întregul interval UTF-16. HotXLS uniformizează cu WideUpperCase exact din acest motiv, astfel încât o căutare pentru text cu accente sau non-latin se potrivește cu aceleași caractere primite, în loc de o aproximație distorsionată a acestora din cauza paginii de cod
Analizorul lexical al formulelor poartă o obligație asociată care nu are nicio legătură cu literele și totul de-a face cu locul unde se termină un token. Notația științifică, cum ar fi 1E3 sau 2.5E-3, este un literal numeric singular, iar scanerul trebuie să recunoască litera E, un semn opțional, și cifrele următoare ca făcând parte din număr, în loc să rupă intrarea într-un nume urmat de un număr separat. Un scaner care gestionează greșit acest lucru transformă o constantă perfect validă într-o eroare de analiză sau, mai rău, într-o expresie greșită în mod tăcut. Acest aspect aparține aceleiași discuții deoarece ambele cazuri sunt despre un cititor care ia o decizie corectă la nivel de caracter: una despre cum să uniformizeze un caracter pentru comparație, a doua despre dacă un caracter continuă tokenul curent
Construirea și exportarea unui registru de lucru multilingv
API-ul public nu vă cere să vă gândiți la niciunul dintre aceste lucruri. Construiți registrul de lucru din valorile celulelor WideString și apelați punctul de intrare pentru export pe care îl doriți. Deciziile de codificare au loc în interiorul fiecărui scriitor. Exemplul de mai jos alimentează o foaie cu text în mai multe sisteme de scriere, apoi scrie atât un fișier RTF, cât și un fișier HTML din același registru de lucru, astfel încât cele două căi rulează pe date de intrare identice
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;
Ambele apeluri returnează o stare Integer și ambele consumă același text din memorie. Nimic din codul apelant nu declară o pagină de cod sau evită un caracter, deoarece responsabilitatea revine scriitorului care își cunoaște propriul format. Metoda SaveAsCSV la nivelul registrului de lucru urmează aceeași formă dacă aveți nevoie de un export delimitat din sursa identică
// Same workbook, a third export path with its own encoding rules.
Book.SaveAsCSV('Customers.csv');
Siguranța Unicode se aplică pe fiecare cale, nu pe fiecare bibliotecă
Lecția de reținut este că nu există un singur loc pentru a asigura compatibilitatea Unicode. RTF necesită o pagină de cod declarată, plus evadările \u. HTML necesită evadarea entităților pentru caracterele semnificative de marcare și referințe numerice atunci când setul de caractere nu este garantat, plus o decodificare corectă a entităților care ajung în șiruri partajate. Containerul ZIP are nevoie de setarea bitului general 11, astfel încât un nume de membru UTF-8 să fie citit ca UTF-8. Evaluarea formulelor necesită convertirea majusculelor/minusculelor cu șiruri largi și un analizor care păstrează notația științifică într-o singură bucată. Fiecare dintre acestea este un contract diferit, iar o bibliotecă poate respecta unul în timp ce încalcă un altul în mod neobservat. Acesta este motivul pentru care o unealtă care procesează corect CSV-urile vă poate oferi totuși un RTF plin de semne de întrebare
Dacă exporturile dumneavoastră se bazează pe formatele delimitate, compromisurile dintre ele sunt acoperite în ghidul nostru despre exportul CSV, TSV și HTML, iar atunci când sursa este un set de rezultate în loc de o foaie construită manual, modelele din exportul bazei de date pentru rapoarte Delphi se îmbină în mod natural cu regulile de codificare descrise aici. Toate acestea sunt livrate ca parte a Componentei HotXLS pentru Delphi și C++Builder, alături de API-urile de citire, formule și formatare acoperite în alte părți ale acestui blog