Műszaki cikk

Veszteségmentes XLSX oda-vissza út Delphi-ben: Theme, extLst, calcChain

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