HotXLS, biblioteca Excel nativă pentru Delphi și C++Builder, este construită pentru drumuri dus-întors XLSX fără pierderi: deschideți un registru de lucru, schimbați o celulă, salvați, iar tema personalizată a clientului, blocurile de extensie extLst străine și lanțul de calcul supraviețuiesc toate. Trei mecanisme fac asta posibilă — păstrarea în cache, cuvânt cu cuvânt, a lui xl/theme/theme1.xml, reserializarea pe bază de evenimente a blocurilor <ext> necunoscute și un xl/calcChain.xml proaspăt, valid față de specificație, la fiecare salvare a unui registru cu formule
Scenariul care motivează toate cele trei este deprimant de frecvent. Un serviciu de facturare încarcă un șablon proiectat de client în Excel — temă corporativă de culori, sparkline-uri într-o coloană de KPI, o regulă de formatare condiționată adăugată de o versiune mai nouă de Excel — scrie un total de factură în celula B3 și salvează. Clientul deschide rezultatul, iar culorile de brand au sărit înapoi la albastrul standard Office, sparkline-urile au dispărut, iar Excel se oferă să „repare” fișierul. Nimic din cod nu a atins vreuna dintre acele funcționalități. Biblioteca a făcut-o, pur și simplu prin salvare
De ce pierd fișierele Excel formatarea după editările făcute de biblioteci?
Fișierele Excel pierd formatarea după editările făcute de biblioteci pentru că majoritatea bibliotecilor nu editează fișierul — îl reconstruiesc. Un pachet .xlsx este un ZIP de părți XML: xl/workbook.xml, câte un xl/worksheets/sheetN.xml per foaie, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml și altele. O bibliotecă tipică analizează acele părți într-un model de obiecte la deschidere și regenerează fiecare parte din acel model la salvare. Orice funcționalitate pe care modelul nu o reprezintă — o temă pe care nu a analizat-o niciodată, un bloc de extensie de la un Excel mai nou — nu are unde să trăiască în memorie, așa că partea regenerată o omite în tăcere
ECMA-376 a anticipat jumătate din această problemă. SpreadsheetML definește extLst (ECMA-376 Partea 1, „Future Feature Data Storage Area”, §18.2.10 pentru elementul de la nivel de registru) ca punct de extensie desemnat: producătorii mai noi își parchează acolo funcționalitățile, fiecare învelită într-un element <ext> care poartă un atribut uri ce identifică funcționalitatea, iar consumatorii mai vechi sunt așteptați să păstreze ce nu înțeleg. Sparkline-urile, slicerele și tipurile mai noi de formatare condiționată călătoresc toate pe acest drum. O bibliotecă ce aruncă blocuri <ext> necunoscute nu este, prin urmare, doar cu pierderi — ea încalcă contractul de compatibilitate înainte în jurul căruia a fost proiectat formatul. Întrebarea de pus oricărei biblioteci de foi de calcul pe care o evaluați este brutală: dacă schimb o celulă, ce altceva se schimbă
Cum păstrează HotXLS o temă personalizată octet cu octet?
HotXLS păstrează tema unui registru de lucru punând în cache octeții originali ai lui xl/theme/theme1.xml la deschidere și scriindu-i înapoi cuvânt cu cuvânt la salvare. Partea de temă (ECMA-376 Partea 1, §14.2.7) este DrawingML, nu SpreadsheetML — scheme de culori, scheme de fonturi, scheme de format — iar un motor de foi de calcul nu are motiv să o modeleze în adâncime. Versiunile mai vechi de HotXLS regenerau o temă Office fixă la fiecare salvare, ceea ce este exact defectul „culorile de brand au sărit înapoi” de mai sus; începând cu v2.89.46, tema pachetului deschis este stocată brut și reemisă neatinsă, iar tema Office încorporată este generată doar pentru registrele create de la zero. Octeții bruți sunt cea mai puternică garanție de fidelitate posibilă: nicio analiză, nicio reserializare, nicio șansă de derivă
Copia cuvânt cu cuvânt câștigă în mod deliberat în fața accesului programatic la temă. TXLSXWorkbook expune ThemeMajorFont și ThemeMinorFont, ca să puteți alege fonturile de titlu și de corp pentru registre noi, dar când o temă cuvânt cu cuvânt a fost capturată la deschidere, acele settere nu au niciun efect asupra fișierului salvat — drumul dus-întors are prioritate. Dacă aveți cu adevărat nevoie să modificați tema unui registru existent, acesta este un semn că șablonul trebuie editat chiar în Excel, nu printr-un API orientat pe date. Cazul de zi cu zi 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; // singura editare
Book.SaveAs('branded-invoice-out.xlsx');
// theme1.xml din ieșire este identic la nivel de octet cu cel din intrare
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 pe care nu îl modelează nativ și îl redă în extLst al foii salvate, astfel încât funcționalitățile scrise de versiuni mai noi de Excel supraviețuiesc intacte drumului dus-întors. Începând cu v2.131.0, fragmentele capturate sunt vizibile prin proprietatea numai-citire RawWorksheetExts, un TStringList pe fiecare foaie XLSX, ceea ce face garanția auditabilă din codul de test, nu un act de credință:
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)); // priviți fiecare 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 copiere brută de octeți. Cititorul XML în flux al HotXLS nu expune poziții în sursă, așa că subarborele necunoscut este reconstruit din evenimente Element, Text și EndElement, pe măsură ce trec prin flux. Abordarea ascunde o capcană clasică: un element autoînchis precum <a/> declanșează doar un eveniment Element marcat ca gol și niciodată un EndElement, deci orice contor de adâncime care scade exclusiv la EndElement nu va vedea niciodată subarborele închizându-se. Tratați asta, iar fragmentul reconstruit este echivalent semantic cu originalul — modul de punere între ghilimele a atributelor și formele autoînchise sunt normalizate, deci nu este identic la nivel de octet, dar Excel citește sens, nu octeți. Două proprietăți ale ieșirii proprii a Excelului fac redarea sigură: Excel declară atributele xmlns necesare pe elementul <ext> sau în interiorul lui, deci fiecare fragment capturat este autonom din punctul de vedere al spațiilor de nume, iar aceeași autonomie este motivul pentru care duplicarea unei foi în același registru sau între registre poate purta blocurile străine cu o simplă atribuire de listă de șiruri
Scrierea lui calcChain.xml astfel încât Excel să aibă încredere în formulele dumneavoastră
HotXLS scrie xl/calcChain.xml (partea de lanț de calcul, ECMA-376 Partea 1, §12.3.1) ori de câte ori registrul salvat conține formule și alege între două ordonări. Dacă graful de dependențe între formule a fost deja construit și este la zi — ați apelat Recalculate după ultima editare — lanțul este emis în ordine topologică completă, dependențele înaintea dependenților, cu eventualii membri ai referințelor circulare adăugați la final. Altfel, celulele sunt listate în ordinea din document. Ambele sunt corecte: notele de implementare Microsoft pentru format, [MS-XLSX], tratează lanțul de calcul ca pe o sugestie pe care Excel o verifică și o reordonează la încărcare, deci orice listare completă este legală, iar HotXLS refuză deliberat să forțeze construirea grafului în interiorul lui SaveAs — construcția muchiilor este pătratică în numărul de celule, un cost ascuns inacceptabil la salvarea unui milion de celule
Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Salvat acum, calcChain.xml listează celulele cu formule în ordinea din document.
// După Recalculate graful de dependențe există, deci aceeași salvare
// emite în schimb o ordine topologică completă:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');
De ce să vă pese de o parte pe care Excel o tratează drept consultativă? Pentru că absența ei este un semnal. Unii consumatori — euristici de reparare, vizualizatoare terțe, unelte de diff — se așteaptă ca un registru cu formule să poarte un lanț de calcul, iar o bibliotecă ce aruncă în tăcere partea la salvare produce fișiere subtil diferite de orice scrie Excel. Emiterea unui lanț valid ține ieșirea în interiorul plicului față de care a fost testat restul ecosistemului, ceea ce este miezul discret și lipsit de glamour al ingineriei drumului dus-întors
Unde se termină drumul dus-întors fără pierderi
Onestitatea contează aici mai mult decât o bifă de marketing, așa că limitele merită la fel de mult spațiu. HotXLS nu copiază tot pachetul octet cu octet: XML-ul foilor, stilurile, șirurile partajate și părțile de registru sunt regenerate din modelul analizat, deci ieșirea este fidelă semantic, dar nu identică binar — doar antetele locale ZIP poartă marcaje de timp DOS proaspete. Fragmentele <ext> capturate se întorc normalizate, așa cum s-a descris mai sus. Suprascrierile programatice ale fonturilor de temă sunt ignorate când există o temă cuvânt cu cuvânt. Iar plasa de păstrare are o ochiuri definite: funcționalitățile pe care HotXLS le modelează nativ (sparkline-urile, de pildă, sunt analizate și rescrise, nu copiate orbește), plus conținutul extLst străin, plus părțile păstrate cuvânt cu cuvânt în cache. O parte care nu este nici modelată, nici aflată în interiorul unui punct de extensie — să zicem partea personalizată a unui add-in exotic — cade în afara celor trei mecanisme tratate în acest articol, deci testați-vă șabloanele reale, nu presupuneți
Munca de păstrare învecinată completează tabloul. Proiectele VBA și referințele către registre externe trec prin salvare pe aceeași filosofie de a păstra ce nu modelezi, tratată în articolul complementar despre păstrarea VBA și a legăturilor externe, iar proprietățile de document din docProps au propriul API de citire-scriere, în loc să fie aruncate în tăcere. Când evaluați orice bibliotecă de foi de calcul, rulați testul cu o singură celulă: deschideți un registru de producție bogat în funcționalități, schimbați o singură valoare, salvați și comparați părțile dezarhivate cu originalul. Ce s-a schimbat dincolo de foaia pe care ați atins-o vă spune mai multe despre bibliotecă decât orice matrice de funcționalități
Mecanismele de drum dus-întors descrise aici — păstrarea temei cuvânt cu cuvânt începând cu v2.89.46, captura extLst străin și emiterea lui calcChain.xml începând cu v2.131.0 — sunt livrate în actuala HotXLS Delphi Excel Component, a cărei pagină de produs documentează setul complet de funcționalități de citire-scriere XLSX pentru Delphi și C++Builder