Technisch artikel

Verliesloze XLSX-rondgang in Delphi: theme en calcChain

HotXLS, de native Excel-bibliotheek voor Delphi en C++Builder, is gebouwd voor verliesloze XLSX-rondgangen: open een werkmap, wijzig één cel, sla op, en het aangepaste thema van de klant, vreemde extLst-extensieblokken en de rekenketen overleven allemaal. Drie mechanismen maken dat mogelijk — letterlijke caching van xl/theme/theme1.xml, op gebeurtenissen gebaseerde herserialisatie van onbekende <ext>-blokken, en een verse, spec-geldige xl/calcChain.xml bij elke opslag van een formulewerkmap

Drie mechanismen achter een verliesloze HotXLS XLSX-rondgang in Delphi: xl/theme/theme1.xml gecacht als ruwe bytes en byte-identiek teruggeschreven, vreemde extLst-blokken vastgelegd uit XML-gebeurtenissen en opnieuw afgespeeld, en een verse spec-geldige calcChain.xml uitgestoten bij elke formuleopslag
Elk bewaard onderdeel volgt zijn eigen pad door de opslag — letterlijke themabytes, extLst-herafspelen op gebeurtenisniveau, en een opnieuw gegenereerde rekenketen — terwijl werkblad-XML en stijlen uit het model worden herbouwd

Het scenario dat alle drie motiveert is deprimerend gewoon. Een facturatiedienst laadt een sjabloon dat de klant in Excel heeft ontworpen — bedrijfskleurenthema, sparklines in een KPI-kolom, een regel voor voorwaardelijke opmaak toegevoegd door een nieuwere Excel-build — schrijft één factuurtotaal in cel B3, en slaat op. De klant opent het resultaat en de merkkleuren zijn teruggeschoten naar het standaard Office-blauw, de sparklines zijn weg, en Excel biedt aan het bestand te "repareren". Niets in de code raakte een van die functies aan. De bibliotheek deed dat, simpelweg door op te slaan

Waarom verliezen Excel-bestanden opmaak na bewerkingen door een bibliotheek?

Excel-bestanden verliezen opmaak na bewerkingen door een bibliotheek omdat de meeste bibliotheken het bestand niet bewerken — zij herbouwen het. Een .xlsx-pakket is een ZIP van XML-onderdelen: xl/workbook.xml, één xl/worksheets/sheetN.xml per blad, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml en meer. Een typische bibliotheek parseert die onderdelen bij het openen in een objectmodel en genereert bij het opslaan elk onderdeel opnieuw uit dat model. Elke functie die het model niet vertegenwoordigt — een thema dat het nooit parseerde, een extensieblok uit een nieuwere Excel — heeft nergens in het geheugen een plek om te wonen, dus laat het opnieuw gegenereerde onderdeel haar stilzwijgend weg

ECMA-376 voorzag de helft van dit probleem. SpreadsheetML definieert extLst (ECMA-376 deel 1, het "Future Feature Data Storage Area", §18.2.10 voor het element op werkmapniveau) als een aangewezen uitbreidingspunt: nieuwere producenten parkeren functies daar, elk verpakt in een <ext>-element met een uri-attribuut dat de functie identificeert, en van oudere consumenten wordt verwacht dat zij bewaren wat zij niet begrijpen. Sparklines, slicers en nieuwere typen voorwaardelijke opmaak reizen alle op deze manier. Een bibliotheek die onbekende <ext>-blokken laat vallen is daarom niet slechts verliesgevend — zij schendt het contract van voorwaartse compatibiliteit waaromheen het formaat is ontworpen. De vraag die u aan elke spreadsheetbibliotheek die u overweegt moet stellen is bot: als ik één cel wijzig, wat verandert er dan nog meer

Hoe houdt HotXLS een aangepast thema byte voor byte vast?

HotXLS bewaart het thema van een werkmap door de originele bytes van xl/theme/theme1.xml bij het openen te cachen en ze bij het opslaan letterlijk terug te schrijven. Het thema-onderdeel (ECMA-376 deel 1, §14.2.7) is DrawingML, geen SpreadsheetML — kleurenschemas, lettertypeschemas, opmaakschemas — en een spreadsheetmotor heeft geen reden het diep te modelleren. Eerdere HotXLS-versies genereerden bij elke opslag een vast Office-thema, wat precies de hierboven beschreven mislukking van "teruggeschoten merkkleuren" is; sinds v2.89.46 wordt het thema van het geopende pakket ruw bewaard en ongemoeid opnieuw uitgestoten, en wordt het ingebouwde Office-thema alleen gegenereerd voor werkmappen die vanaf nul worden gemaakt. Ruwe bytes zijn de sterkst mogelijke garantie voor trouw: geen parse, geen herserialisatie, geen kans op drift

De letterlijke kopie wint bewust van programmatische thematoegang. TXLSXWorkbook stelt ThemeMajorFont en ThemeMinorFont beschikbaar zodat u kop- en broodtekstletters voor nieuwe werkmappen kunt kiezen, maar wanneer bij het openen een letterlijk thema is vastgelegd hebben die setters geen effect op het opgeslagen bestand — de rondgang heeft voorrang. Moet u werkelijk het thema van een bestaande werkmap wijzigen, dan is dat een signaal om het sjabloon in Excel zelf te bewerken in plaats van via een datagerichte API. Het alledaagse geval heeft helemaal geen API nodig:

HotXLS cachet bij het openen de ruwe bytes van xl/theme/theme1.xml en schrijft ze bij het opslaan byte-identiek terug, terwijl een bibliotheek die uit het model herbouwt een standaard Office-thema opnieuw genereert en de merkkleuren van de klant terugschiet
theme1.xml letterlijk cachen heeft helemaal geen themamodel nodig, en ThemeMajorFont met ThemeMinorFont stylen alleen werkmappen die geen vastgelegd thema dragen
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('branded-invoice.xlsx');
    Book.Sheets[0].Cells[3, 2].Value := 42750.00;  // de ene bewerking
    Book.SaveAs('branded-invoice-out.xlsx');
    // theme1.xml in de uitvoer is byte-identiek aan de invoer
  finally
    Book.Free;
  end;
end;

Wat gebeurt er bij het opslaan met onbekende extLst-blokken?

HotXLS legt elk <ext>-blok op werkbladniveau vast dat het niet natief modelleert en speelt het opnieuw af in de extLst van het opgeslagen werkblad, zodat functies die door nieuwere Excel-builds zijn geschreven de rondgang intact overleven. Sinds v2.131.0 zijn de vastgelegde fragmenten zichtbaar via de alleen-lezen eigenschap RawWorksheetExts, een TStringList op elk XLSX-werkblad, wat de garantie controleerbaar maakt vanuit testcode in plaats van een daad van vertrouwen:

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)); // gluur naar elke uri
  finally
    Book.Free;
  end;
end;

Het implementatiedetail dat het kennen waard is, is dat de vastlegging een herserialisatie op gebeurtenisniveau is, geen ruwe bytekopie. De streaming XML-lezer van HotXLS stelt geen bronoffsets beschikbaar, dus de onbekende deelboom wordt herbouwd uit Element-, Text- en EndElement-gebeurtenissen zoals ze voorbijstromen. Die aanpak verbergt één klassieke valstrik: een zelfsluitend element zoals <a/> vuurt alleen een Element-gebeurtenis af met de vlag leeg en nooit een EndElement, dus elke diepteteller die uitsluitend op EndElement aftelt zal de deelboom nooit zien sluiten. Vang dat af, en het herbouwde fragment is semantisch gelijkwaardig aan het origineel — attribuutcitering en zelfsluitende vormen worden genormaliseerd, dus het is niet byte-identiek, maar Excel leest betekenis, geen bytes. Twee eigenschappen van de uitvoer van Excel zelf maken het herafspelen veilig: Excel declareert de benodigde xmlns-attributen op het <ext>-element of erbinnen, zodat elk vastgelegd fragment op namespace-gebied zelfstandig is, en diezelfde zelfstandigheid is waarom een werkblad dupliceren binnen of tussen werkmappen de vreemde blokken met een gewone toewijzing van de stringlijst kan meenemen

calcChain.xml schrijven zodat Excel uw formules vertrouwt

HotXLS schrijft xl/calcChain.xml (het onderdeel Calculation Chain, ECMA-376 deel 1, §12.3.1) telkens wanneer de opgeslagen werkmap formules bevat, en kiest daarbij tussen twee ordeningen. Als de afhankelijkheidsgrafiek van de formules al is opgebouwd en actueel is — u riep Recalculate aan na uw laatste bewerking — wordt de keten in volledige topologische volgorde uitgestoten, afhankelijkheden vóór afhankelijken, met eventuele leden van een kringverwijzing achteraan toegevoegd. Anders worden cellen in documentvolgorde opgesomd. Beide zijn correct: de implementatienotities van Microsoft voor het formaat, [MS-XLSX], behandelen de rekenketen als een hint die Excel tijdens het laden verifieert en herordent, dus elke volledige opsomming is legaal, en HotXLS weigert bewust een grafiekopbouw binnen SaveAs af te dwingen — het construeren van randen is kwadratisch in het aantal cellen, een onaanvaardbare verborgen kostenpost bij het opslaan van een miljoen cellen

HotXLS stoot xl/calcChain.xml uit in topologische volgorde wanneer Recalculate de afhankelijkheidsgrafiek heeft opgebouwd, en anders in documentvolgorde; Excel behandelt elke volledige opsomming als een hint en herordent haar tijdens het laden
Beide ordeningen blijven legaal omdat Excel de keten bij het laden opnieuw verifieert, en HotXLS dwingt de grafiekopbouw met kwadratische kosten nooit binnen SaveAs af
Book.Open('model.xlsx');
Book.Sheets[0].Cells[10, 4].Formula := '=SUM(D2:D9)';
// Nu opgeslagen somt calcChain.xml de formulecellen in documentvolgorde op.
// Na Recalculate bestaat de afhankelijkheidsgrafiek, dus dezelfde opslag
// stoot in plaats daarvan een volledige topologische volgorde uit:
Book.Recalculate;
Book.SaveAs('model-out.xlsx');

Waarom u druk maken om een onderdeel dat Excel als advies behandelt? Omdat de afwezigheid ervan een signaal is. Sommige consumenten — reparatieheuristieken, viewers van derden, diff-gereedschappen — verwachten dat een formulewerkmap een rekenketen draagt, en een bibliotheek die het onderdeel bij het opslaan stilzwijgend laat vallen produceert bestanden die subtiel anders zijn dan alles wat Excel schrijft. Een geldige keten uitstoten houdt de uitvoer binnen de enveloppe waartegen de rest van het ecosysteem is getest, wat de stille, glansloze kern van rondgangtechniek is

Waar de verliesloze rondgang ophoudt

Eerlijkheid telt hier meer dan een marketingvinkje, dus de grenzen verdienen evenveel ruimte. HotXLS kopieert het hele pakket niet byte voor byte: werkblad-XML, stijlen, gedeelde tekenreeksen en werkmaponderdelen worden opnieuw gegenereerd uit het geparseerde model, dus de uitvoer is semantisch trouw maar niet binair identiek — alleen al de lokale ZIP-headers dragen verse DOS-tijdstempels. Vastgelegde <ext>-fragmenten komen genormaliseerd terug, zoals hierboven beschreven. Programmatische overschrijvingen van themalettertypen worden genegeerd wanneer een letterlijk thema aanwezig is. En het bewaarnet heeft een gedefinieerde maaswijdte: functies die HotXLS natief modelleert (sparklines bijvoorbeeld worden geparseerd en herschreven in plaats van blind gekopieerd) plus vreemde extLst-inhoud plus de letterlijk gecachte onderdelen. Een onderdeel dat noch gemodelleerd is noch binnen een uitbreidingspunt valt — een eigen onderdeel van een exotische invoegtoepassing, bijvoorbeeld — valt buiten de drie mechanismen die dit artikel behandelt, dus test uw werkelijke sjablonen in plaats van iets aan te nemen

Aangrenzend bewaarwerk maakt het beeld compleet. VBA-projecten en externe werkmapverwijzingen rijden op dezelfde filosofie van behoud-wat-u-niet-modelleert door de opslag heen, behandeld in het begeleidende artikel over het bewaren van VBA en externe koppelingen, en documenteigenschappen in docProps hebben hun eigen lees-schrijf-API in plaats van stilzwijgend te worden weggelaten. Wanneer u een spreadsheetbibliotheek beoordeelt, draai dan de test met één cel: open een functierijke productiewerkmap, wijzig één enkele waarde, sla op, en vergelijk de uitgepakte onderdelen met het origineel. Wat er buiten het blad dat u aanraakte veranderde vertelt u meer over de bibliotheek dan welke functiematrix ook

De hier beschreven rondgangmechanismen — letterlijk themabehoud sinds v2.89.46, vastlegging van vreemde extLst en uitstoot van calcChain.xml sinds v2.131.0 — worden geleverd in het huidige HotXLS Delphi Excel Component, waarvan de productpagina de volledige XLSX-lees-en-schrijffunctieset voor Delphi en C++Builder documenteert