A HotXLS, a natív Delphi és C++Builder Excel könyvtár, veszteségmentes XLSX oda-vissza utakra készült: nyissa meg a munkafüzetet, módosítson egy cellát, mentse el, és az ügyfél egyedi témája, idegen extLst kiterjesztési blokkjai, valamint a számítási lánc mind túlélnek; három mechanizmus teszi ezt lehetővé — a xl/theme/theme1.xml szó szerinti gyorsítótárazása, az ismeretlen <ext> blokkok eseményalapú újra-sorosítása és egy friss, specifikációnak megfelelő xl/calcChain.xml minden képlet-munkafüzet mentésekor
A mindhármat motiváló helyzet lehangolóan gyakori; egy számlázó szolgáltatás betölt egy sablont, amelyet az ügyfél Excelben tervezett — vállalati színtéma, sparkline-ok egy KPI oszlopban, egy újabb Excel verzió által hozzáadott feltételes formázási szabály —, beír egy számlaösszeget a B3 cellába, és elmenti; az ügyfél megnyitja az eredményt, és a márka színei visszaálltak az alapértelmezett Office kékre, a sparkline-ok eltűntek, és az Excel felajánlja a fájl „javítását”; a kódban semmi sem nyúlt ezekhez a funkciókhoz; a könyvtár tette meg, egyszerűen a mentéssel
Miért veszítik el az Excel fájlok a formázást a könyvtári szerkesztések után?
Az Excel fájlok azért veszítik el a formázást a könyvtári szerkesztések után, mert a legtöbb könyvtár nem szerkeszti a fájlt — hanem újraépíti; egy .xlsx csomag az XML részek ZIP-je: xl/workbook.xml, laponként egy xl/worksheets/sheetN.xml, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml, és így tovább; egy tipikus könyvtár megnyitáskor beolvassa ezeket a részeket egy objektummodellbe, mentéskor pedig ebből a modellből újraépíti az összes részt; bármely olyan funkció, amelyet a modell nem reprezentál — egy téma, amelyet soha nem elemzett, egy kiterjesztési blokk egy újabb Excelből —, nem tud hol lakni a memóriában, így az újraépített rész csendben elhagyja azt
Az ECMA-376 előre látta a probléma felét; a SpreadsheetML az extLst-et (ECMA-376 1. rész, „Future Feature Data Storage Area”, a munkafüzet-szintű elemhez §18.2.10) kijelölt kiterjesztési pontként határozza meg: az újabb előállítók ott parkoltatják a funkciókat, mindegyiket egy <ext> elembe csomagolva, amely a funkciót azonosító uri attribútumot hordoz, és a régebbi felhasználóktól elvárják, hogy megőrizzék azt, amit nem értenek; a sparkline-ok, a szeletelők és az újabb feltételes formázási típusok mind így utaznak; az a könyvtár, amely eldobja az ismeretlen <ext> blokkokat, ezért nemcsak veszteséges — hanem megsérti azt a kompatibilitási szerződést is, amely köré a formátumot tervezték; a kérdés, amelyet bármely táblázatkezelő könyvtárnak fel kell tenni, nyers: ha módosítok egy cellát, mi egyéb változik
Hogyan őrzi meg a HotXLS az egyedi témát bájtról bájtra?
A HotXLS a xl/theme/theme1.xml eredeti bájtjainak megnyitáskor történő gyorsítótárazásával és mentéskor történő szó szerinti visszaírásával őrzi meg a munkafüzet témáját; a témarész (ECMA-376 1. rész, §14.2.7) DrawingML, nem SpreadsheetML — színsémák, betűtípus-sémák, formátumsémák —, és a táblázatkezelő motornak nincs oka arra, hogy mélyen modellezze azt; a korábbi HotXLS verziók egy fix Office témát generáltak újra minden mentéskor, ami pontosan a fenti „márkaszínek visszaálltak” hibát eredményezte; a v2.89.46 óta a megnyitott csomag témáját nyersen tárolják és érintetlenül bocsátják ki újra, a beépített Office témát pedig csak a semmiből létrehozott munkafüzetekhez generálják; a nyers bájtok a lehető legerősebb hűség-garanciát jelentik: nincs elemzés, nincs újra-sorosítás, nincs esély az elcsúszásra
A szó szerinti másolat szándékosan győz a programozott téma-hozzáféréssel szemben; a TXLSXWorkbook elérhetővé teszi a ThemeMajorFont és ThemeMinorFont tulajdonságokat, így kiválaszthatja a fejlécek és a szövegtörzs betűtípusát az új munkafüzetekhez, de ha megnyitáskor szó szerinti téma rögzítésre került, ezeknek a beállítóknak nincs hatásuk a mentett fájra — az oda-vissza út élvez prioritást; ha valóban meg kell változtatnia egy meglévő munkafüzet témáját, az egy jelzés arra, hogy magát a sablont szerkessze Excelben, ne pedig egy adatorientált API-n keresztül; a mindennapi esetnek egyáltalán nincs szüksége API-ra:
var
Book: TXLSXWorkbook;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('branded-invoice.xlsx');
Book.Sheets[0].Cells[3, 2].Value := 42750.00; // az egyetlen szerkesztés
Book.SaveAs('branded-invoice-out.xlsx');
// a kimenetben lévő theme1.xml bájt-azonos a bemenettel
finally
Book.Free;
end;
end;
Mi történik az ismeretlen extLst blokkokkal a mentéskor?
A HotXLS minden olyan munkalap-szintű <ext> blokkot rögzít, amelyet natívan nem modellez, és visszajátssza azt a mentett munkalap extLst elemébe, így az újabb Excel verziók által írt funkciók épségben túlélik az oda-vissza utat; a v2.131.0 verziótól kezdve a rögzített töredékek láthatók az olvasásra alkalmas RawWorksheetExts tulajdonságon keresztül, ami munkalaponként egy TStringList, ezáltal a garancia ellenőrizhető a tesztkódból a vak hit helyett:
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)); // bepillantás minden egyes uri-be
finally
Book.Free;
end;
end;
A megvalósítási részlet, amit érdemes tudni, az az, hogy a rögzítés eseményszintű újra-sorosítás, nem pedig nyers bájtmásolás; a HotXLS streaming XML olvasója nem tesz közzé forrás-eltolásokat, így az ismeretlen részfa az elhaladó Element, Text és EndElement eseményekből épül fel újra; ez a megközelítés elrejt egy klasszikus csapdát: az olyan önzáró elem, mint a <a/>, csak egy üresnek jelölt Element eseményt vált ki, és soha nem vált ki EndElement eseményt, így minden olyan mélységszámláló, amely kizárólag az EndElement eseményre csökken, soha nem fogja látni a részfa lezárását; ha kezeljük, az újraépített töredék szemantikailag egyenértékű az eredetivel — az attribútumok idézőjelezése és az önzáró formák normalizálódnak, így nem bájt-azonos, de az Excel a jelentést olvassa, nem a bájtokat; az Excel saját kimenetének két tulajdonsága teszi biztonságossá a visszajátszást: az Excel deklarálja a szükséges xmlns attribútumokat az <ext> elemen vagy azon belül, így minden rögzített töredék névtér-szempontból önálló, és ugyanez a függetlenség az oka annak, hogy a munkalap munkafüzeten belüli vagy munkafüzetek közötti duplikálása képes hordozni az idegen blokkokat egy egyszerű string-list hozzárendeléssel
A calcChain.xml írása, hogy az Excel bízzon a képleteiben
A HotXLS megírja a xl/calcChain.xml-t (Számítási lánc rész, ECMA-376 1. rész, §12.3.1), amikor a mentett munkafüzet képleteket tartalmaz, és két rendezés között választ; ha a képlet-függőségi grafikon már felépült és aktuális — a legutóbbi szerkesztés után meghívta a Recalculate-ot —, a lánc teljes topológiai sorrendben kerül kibocsátásra, függőségek a függők előtt, a végére fűzve a körkörös hivatkozások tagjait; ellenkező esetben a cellák dokumentum-sorrendben kerülnek felsorolásra; mindkettő helyes: a Microsoft formátumra vonatkozó megvalósítási megjegyzései, a [MS-XLSX], a számítási láncot olyan tippként kezelik, amelyet az Excel ellenőriz és átrendez a betöltés során, így minden teljes felsorolás legális, a HotXLS pedig szándékosan nem kényszeríti ki a grafikonépítést a SaveAs-en belül — az élek felépítése a cellaszám négyzetével arányos, ami elfogadhatatlan rejtett költség egy egymilliós cellaszámú mentésnél
Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Most mentve a calcChain.xml a képletcellákat dokumentum-sorrendben listázza
// A Recalculate után a függőségi grafikon létezik, így ugyanaz a mentés
// helyette teljes topológiai sorrendet bocsát ki:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');
Miért kell törődni azzal a résszel, amelyet az Excel tanácsadó jellegűként kezel? Mert a hiánya jelzés; egyes felhasználók — javítási heurisztikák, harmadik féltől származó megjelenítők, diff eszközök — elvárják, hogy a képlet-munkafüzet számítási láncot hordozzon, és az a könyvtár, amely mentéskor csendben elhagyja ezt a részt, olyan fájlokat hoz létre, amelyek némileg eltérnek attól, amit az Excel ír; az érvényes lánc kibocsátása a kimenetet az ökoszisztéma többi részében tesztelt kereteken belül tartja, ami az oda-vissza út tervezésének csendes, nem túl látványos magja
Hol ér véget a veszteségmentes oda-vissza út?
Az őszinteség itt többet számít, mint a marketing-pipa, így a határok megérdemlik a figyelmet; a HotXLS nem másolja le a teljes csomagot bájtról bájtra: a munkalap XML-je, a stílusok, a megosztott karakterláncok és a munkafüzet részei az értelmezett modellből generálódnak újra, így a kimenet szemantikailag hű, de nem binárisan azonos — már a ZIP helyi fejlécek önmagukban friss DOS időbélyegeket hordoznak; a rögzített <ext> töredékek normalizálva jönnek vissza, a fent leírtak szerint; a programozott téma-betűtípus felülírásokat figyelmen kívül hagyja, ha szó szerinti téma van jelen; és a megőrzési hálónak meghatározott szemei vannak: a HotXLS által natívan modellezett funkciók (a sparkline-okat például elemzi és újraírja, nem pedig vakon másolja) plusz az idegen extLst tartalom plusz a szó szerint gyorsítótárazott részek; az a rész, amely nincs modellezve és nem is kiterjesztési ponton belül van — mondjuk egy különleges beépülő modul egyedi része —, kívül esik az e cikkben tárgyalt három mechanizmuson, ezért tesztelje a tényleges sablonjait ahelyett, hogy feltételezésekre hagyatkozna
A kapcsolódó megőrzési munka teljessé teszi a képet; a VBA projektek és a külső munkafüzet-hivatkozások a mentés során ugyanazon az „őrizd meg, amit nem modellezel” elven alapulnak, amelyet a VBA és a külső linkek megőrzéséről szóló társcikk tárgyal, és a docProps-ban lévő dokumentum-tulajdonságoknak is megvan a saját írható-olvasható API-juk ahelyett, hogy csendben elvesznének; amikor kiértékel egy táblázatkezelő könyvtárat, futtassa le az egycellás tesztet: nyisson meg egy funkciókban gazdag éles munkafüzetet, módosítson egyetlen értéket, mentse el, és hasonlítsa össze a kicsomagolt részeket az eredetivel; ami az Ön által megérintett lapon kívül megváltozott, az többet mond el a könyvtárról, mint bármilyen funkció-mátrix
Az itt leírt oda-vissza út mechanizmusok — a szó szerinti téma megőrzése a v2.89.46 óta, az idegen extLst rögzítése és a calcChain.xml kibocsátása a v2.131.0 óta — a jelenlegi HotXLS Delphi Excel Component termékben találhatók meg, amelynek termékoldala dokumentálja a teljes XLSX írási-olvasási funkciókészletet Delphi és C++Builder rendszerekhez