Articol tehnic

Proces complet XLSX fără pierderi în Delphi: Temă, extLst, calcChain

HotXLS, biblioteca nativă Excel pentru Delphi și C++Builder, este concepută pentru procese complete XLSX fără pierderi: deschideți un registru de lucru, modificați o celulă, salvați, iar tema personalizată a clientului, blocurile de extensie externe extLst și lanțul de calcul supraviețuiesc toate. Trei mecanisme asigură această funcționare — stocarea textuală în cache a xl/theme/theme1.xml, reserializarea bazată pe evenimente a blocurilor <ext> necunoscute și un fișier xl/calcChain.xml nou și valid conform specificațiilor la fiecare salvare a unui registru cu formule

Scenariul care stă la baza acestor trei mecanisme este deosebit de comun. Un serviciu de facturare încarcă un șablon conceput de client în Excel — temă de culori corporative, mini-grafice (sparklines) într-o coloană KPI, o regulă de formatare condiționată adăugată de o versiune mai nouă de Excel — scrie un total al facturii în celula B3 și salvează. Clientul deschide rezultatul și constată că culorile mărcii au revenit la albastrul standard Office, mini-graficele au dispărut, iar Excel se oferă să „repare” fișierul. Nimic din cod nu a atins acele caracteristici. Biblioteca le-a afectat, pur și simplu prin salvare

De ce își pierd fișierele Excel formatarea după editările realizate prin bibliotecă?

Fișierele Excel își pierd formatarea după editările realizate prin biblioteci deoarece majoritatea acestora nu editează fișierul, ci îl reconstruiesc. Un pachet .xlsx este o arhivă ZIP formată din componente XML: xl/workbook.xml, câte un fișier xl/worksheets/sheetN.xml pentru fiecare foaie, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml și altele. O bibliotecă tipică analizează acele componente într-un model de obiecte la deschidere și le regenerează pe fiecare pe baza acelui model la salvare. Orice caracteristică pe care modelul nu o reprezintă — o temă pe care nu a analizat-o niciodată, un bloc de extensie dintr-o versiune Excel mai nouă — nu are unde să fie stocată în memorie, așa că componenta regenerată o omite în mod silențios

Standardul ECMA-376 a anticipat jumătate din această problemă. SpreadsheetML definește elementul extLst (ECMA-376 Partea 1, „Future Feature Data Storage Area”, §18.2.10 pentru elementul de la nivel de registru) ca un punct de extensie desemnat: programele de generare mai noi plasează caracteristici acolo, fiecare fiind împachetată într-un element <ext> care conține un atribut uri care identifică caracteristica, iar consumatorii mai vechi trebuie să păstreze ceea ce nu înțeleg. Mini-graficele, feliatoarele (slicers) și noile tipuri de formatare condiționată sunt transmise în acest mod. O bibliotecă care elimină blocurile <ext> necunoscute nu doar că generează pierderi de date — ea încalcă contractul de compatibilitate înainte pe baza căruia a fost conceput formatul. Întrebarea pe care trebuie să o puneți oricărei biblioteci de foi de calcul pe care o evaluați este directă: dacă modific o celulă, ce altceva se mai schimbă

Cum păstrează HotXLS o temă personalizată octet cu octet?

HotXLS păstrează tema unui registru de lucru prin stocarea în cache a octeților originali ai fișierului xl/theme/theme1.xml în momentul deschiderii și prin scrierea lor exactă înapoi la salvare. Partea de temă (ECMA-376 Partea 1, §14.2.7) este DrawingML, nu SpreadsheetML — scheme de culori, scheme de fonturi, scheme de formatare — iar un motor de foi de calcul nu are niciun motiv să o modeleze în detaliu. Versiunile anterioare ale HotXLS regenerau o temă Office fixă la fiecare salvare, ceea ce reprezenta exact eșecul revenirii la culorile standard menționat mai sus; începând cu v2.89.46, tema pachetului deschis este stocată în stare brută și re-emisă neatinsă, iar tema standard Office este generată doar pentru registrele de lucru create de la zero. Octeții bruts reprezintă cea mai puternică garanție posibilă de fidelitate: fără analiză, fără reserializare, fără risc de abateri

Copia exactă prevalează în mod deliberat în fața accesului programatic la temă. Clasa TXLSXWorkbook expune proprietățile ThemeMajorFont și ThemeMinorFont pentru a putea alege fonturile pentru titluri și text în registrele de lucru noi, însă când o temă textuală a fost capturată la deschidere, aceste setări nu au niciun efect asupra fișierului salvat — procesul complet are prioritate. Dacă trebuie neapărat să modificați tema unui registru de lucru existent, acesta este un semnal să editați șablonul direct în Excel, nu printr-un API de date. Cazul obișnuit nu are nevoie de niciun API:

var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // the one edit
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml in the output is byte-identical to the input
  finally
    Book.Free;
  end;
end;

Ce se întâmplă cu blocurile extLst necunoscute la salvare?

HotXLS capturează fiecare bloc <ext> de la nivel de foaie de calcul pe care nu îl modelează în mod nativ și îl reproduce în lista extLst a foii de calcul salvate, astfel încât caracteristicile scrise de versiuni Excel mai noi supraviețuiesc intacte. Începând cu v2.131.0, fragmentele capturate sunt vizibile prin proprietatea read-only RawWorksheetExts, un obiect TStringList pe fiecare foaie de calcul XLSX, ceea ce face ca această garanție să poată fi verificată în codul de testare, în loc să fie o chestiune de încredere:

var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  i: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('from-newer-excel.xlsx');
    Sheet := Book.Sheets[0];
    WriteLn(Format('%d foreign ext block(s) captured',
      [Sheet.RawWorksheetExts.Count]));
    for i := 0 to Sheet.RawWorksheetExts.Count - 1 do
      WriteLn(Copy(Sheet.RawWorksheetExts[i], 1, 100)); // peek at each uri
  finally
    Book.Free;
  end;
end;

Detaliul de implementare care merită știut este că această captură este o reserializare la nivel de eveniment, nu o copie brută de octeți. Cititorul XML în flux din HotXLS nu expune decalaje sursă, așa că subarborele necunoscut este reconstruit din evenimentele Element, Text și EndElement pe măsură ce acestea sunt parcurse. Această abordare ascunde o capcană clasică: un element cu auto-închidere precum <a/> declanșează doar un eveniment Element marcat ca gol și niciodată un EndElement, astfel încât orice contor de adâncime care scade exclusiv la EndElement nu va detecta niciodată închiderea subarborelui. Odată gestionat acest aspect, fragmentul reconstruit este echivalent din punct de vedere semantic cu cel original — ghilimelele atributelor și formele cu auto-închidere sunt normalizate, deci nu este identic la nivel de octet, dar Excel citește semnificația, nu octeții. Două proprietăți ale rezultatului Excel fac ca această reproducere să fie sigură: Excel declară atributele xmlns necesare pe elementul <ext> sau în interiorul acestuia, astfel încât fiecare fragment capturat este independent din punct de vedere al spațiului de nume, iar această independență este și motivul pentru care duplicarea unei foii de calcul în interiorul sau între registrele de lucru poate transfera blocurile externe printr-o simplă atribuire de listă de șiruri

Scrierea calcChain.xml astfel încât Excel să aibă încredere în formulele dvs

HotXLS scrie componenta xl/calcChain.xml (partea lanțului de calcul, ECMA-376 Partea 1, §12.3.1) ori de câte ori registrul de lucru salvat conține formule, alegând între două ordonări. Dacă graficul de dependență a formulelor a fost deja construit și este actualizat — adică ați apelat Recalculate după ultima editare — lanțul este emis în ordine topologică completă, dependențele înaintea elementelor dependente, elementele cu referințe circulare fiind adăugate la final. În caz contrar, celulele sunt enumerate în ordinea documentului. Ambele variante sunt corecte: notele de implementare Microsoft pentru acest format, [MS-XLSX], tratează lanțul de calcul ca pe o sugestie pe care Excel o verifică și o reordonează la încărcare, astfel încât orice enumerare completă este validă, iar HotXLS refuză în mod deliberat să forțeze o construire a graficului în interiorul SaveAs — construirea conexiunilor este pătratică în raport cu numărul de celule, un cost ascuns inacceptabil pentru salvarea unui milion de celule

Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Saved now, calcChain.xml lists formula cells in document order.
// After Recalculate the dependency graph exists, so the same save
// emits a full topological order instead:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

De ce aș dori să salvez calcChain.xml?

De ce ar trebui să ne pese de o componentă pe care Excel o consideră opțională? Deoarece absența sa este un semnal. Unii consumatori — euristici de reparare, vizualizatoare terțe, instrumente de diff — se așteaptă ca un registru cu formule să conțină un lanț de calcul, iar o bibliotecă ce elimină în mod silențios această componentă la salvare produce fișiere care diferă subtil de ceea ce scrie Excel. Emiterea unui lanț valid păstrează rezultatul în limitele cu care a fost testat restul ecosistemului, ceea ce reprezintă esența simplă a ingineriei proceselor complete

Unde se termină procesul complet fără pierderi

Corectitudinea contează mai mult decât o casetă de selectare pentru marketing, așa că limitele merită aceeași atenție. HotXLS nu copiază întregul pachet octet cu octet: fișierele XML ale foilor, stilurile, șirurile partajate și componentele registrului sunt regenerate din modelul analizat, astfel încât rezultatul este fidel din punct de vedere semantic, dar nu este identic binar — antetele locale ZIP conțin mărci temporale DOS noi. Fragmentele <ext> capturate sunt returnate normalizate, așa cum s-a descris mai sus. Suprascrierile programatice ale fonturilor temei sunt ignorate când este prezentă o temă textuală. Iar plasa de siguranță are o anumită structură: caracteristici pe care HotXLS le modelează nativ (mini-graficele, de exemplu, sunt analizate și rescrise, nu copiate orbește) plus conținutul extern extLst plus componentele stocate textual în cache. O componentă care nu este nici modelată, nici aflată într-un punct de extensie — de exemplu, o componentă personalizată a unui add-in exotic — se află în afara celor trei mecanisme prezentate în acest articol, așa că testați șabloanele reale în loc să faceți presupuneri

Activitățile conexe de conservare completează acest tablou. Proiectele VBA și referințele externe la registre de lucru trec prin procesul de salvare pe baza aceleiași filozofii de păstrare a ceea ce nu este modelat, prezentată în articolul asociat despre conservarea VBA și a legăturilor externe, iar proprietățile documentului din docProps au propria lor interfață API de citire-scriere în loc să fie eliminate în mod silențios. Când evaluați orice bibliotecă de foi de calcul, rulați testul unei singure celule: deschideți un registru de lucru de producție complex, modificați o singură valoare, salvați și comparați (diff) componentele dezarhivate cu cele originale. Modificările care au apărut în afara foii pe care ați editat-o vă spun mai multe despre acea bibliotecă decât orice matrice de caracteristici

Mecanismele de procesare completă descrise aici — păstrarea exactă a temei începând cu v2.89.46, capturarea elementelor externe extLst și emiterea calcChain.xml începând cu v2.131.0 — sunt livrate în versiunea actuală a HotXLS Delphi Excel Component, a cărei pagină de produs documentează întregul set de caracteristici de citire-scriere XLSX pentru Delphi și C++Builder