Articolo tecnico

Round-Trip XLSX senza perdita in Delphi: Theme, extLst, calcChain

HotXLS, la libreria Excel nativa per Delphi e C++Builder, è progettata per eseguire round-trip XLSX senza perdita: apri una cartella di lavoro, modifica una singola cella, salva, e il tema personalizzato dell'utente, i blocchi di estensione extLst sconosciuti e la catena di calcolo rimarranno intatti. Questo funzionamento è garantito da tre meccanismi — la cache letterale di xl/theme/theme1.xml, la riserializzazione basata su eventi di blocchi <ext> sconosciuti e una nuova xl/calcChain.xml conforme alle specifiche prodotta a ogni salvataggio di cartelle di lavoro con formule

Lo scenario alla base di questi accorgimenti è comune. Un servizio di fatturazione carica un modello progettato dall'utente in Excel — tema colore aziendale, sparkline in una colonna KPI, una regola di formattazione condizionale introdotta da una build di Excel più recente —, inserisce un totale di fattura nella cella B3 e salva. L'utente apre il file risultante e scopre che i colori del brand sono tornati al blu standard di Office, le sparkline sono sparite e Excel si offre di "riparare" il file. Nessuna istruzione nel codice ha toccato quelle funzionalità: è stata la libreria a farlo, semplicemente salvando il file

Perché i file Excel perdono la formattazione dopo le modifiche con una libreria?

I file Excel perdono la formattazione dopo le modifiche apportate da una libreria perché la maggior parte delle librerie non si limita a modificare il file, ma lo ricostruisce. Un pacchetto .xlsx è un archivio ZIP contenente varie parti XML: xl/workbook.xml, un file xl/worksheets/sheetN.xml per ciascun foglio, xl/styles.xml, xl/theme/theme1.xml, xl/calcChain.xml, e altre. Una libreria tipica analizza queste parti in un modello a oggetti all'apertura e rigenera ogni parte da tale modello al salvataggio. Qualsiasi caratteristica non supportata dal modello — un tema mai analizzato, un blocco di estensione di una versione più recente di Excel — non trova spazio in memoria, venendo omessa silenziosamente nella parte rigenerata

La specifica ECMA-376 ha previsto metà di questo problema. Lo standard SpreadsheetML definisce l'elemento extLst (ECMA-376 Parte 1, il "Future Feature Data Storage Area", §18.2.10 per l'elemento a livello di cartella di lavoro) come punto di estensione designato: i produttori più recenti inseriscono lì le nuove funzionalità, ognuna racchiusa in un elemento <ext> contenente un attributo uri che ne identifica la natura, e ai consumatori più vecchi è richiesto di preservare ciò che non comprendono. Sparkline, filtri dati e nuovi tipi di formattazione condizionale vengono gestiti in questo modo. Una libreria che scarta blocchi <ext> sconosciuti non genera solo perdite di dati, ma viola il contratto di compatibilità futura su cui è stato progettato il formato. La domanda da porre a qualsiasi libreria di fogli di calcolo in fase di valutazione è diretta: se modifico una singola cella, cos'altro cambia?

In che modo HotXLS conserva un tema personalizzato byte per byte?

HotXLS preserva il tema di una cartella di lavoro memorizzando nella cache i byte originali di xl/theme/theme1.xml al momento dell'apertura e riscrivendoli in modo letterale al salvataggio. La parte del tema (ECMA-376 Parte 1, §14.2.7) appartiene a DrawingML, non a SpreadsheetML — schemi di colori, schemi di font, schemi di formato — e un motore per fogli di calcolo non ha motivo di modellarla nel dettaglio. Le versioni precedenti di HotXLS rigeneravano un tema Office fisso a ogni salvataggio, generando l'errore descritto sopra ("colori del brand ripristinati"); a partire dalla v2.89.46, il tema del pacchetto aperto viene salvato come dato grezzo e ricreato intatto, mentre il tema Office integrato viene generato solo per le cartelle di lavoro create da zero. I byte grezzi rappresentano la massima garanzia di fedeltà: nessuna analisi, nessuna riserializzazione, nessuna possibilità di deriva dei dati

La copia letterale prevale intenzionalmente sull'accesso programmatico al tema. L'oggetto TXLSXWorkbook espone ThemeMajorFont e ThemeMinorFont per consentire la scelta dei caratteri di intestazione e corpo per le nuove cartelle di lavoro, ma quando un tema viene catturato all'apertura, tali setter non hanno effetto sul file salvato — la fedeltà del round-trip ha la priorità. Se si rende realmente necessario modificare il tema di una cartella di lavoro esistente, si consiglia di modificare il modello direttamente in Excel anziché tramite API orientate ai dati. Il caso d'uso quotidiano non richiede alcuna 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;

Cosa succede ai blocchi extLst sconosciuti al salvataggio?

HotXLS acquisisce ogni blocco <ext> a livello di foglio di lavoro che non modella nativamente e lo riproduce nell'elemento extLst del foglio salvato, in modo che le funzionalità introdotte dalle build più recenti di Excel sopravvivano intatte al round-trip. Dalla versione v2.131.0, i frammenti acquisiti sono visibili tramite la proprietà in sola lettura RawWorksheetExts, una TStringList per ciascun foglio di lavoro XLSX, il che rende la garanzia verificabile dal codice di test invece di essere un atto di fede:

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;

Il dettaglio implementativo da conoscere è che l'acquisizione avviene come riserializzazione a livello di evento, e non come copia di byte grezzi. Il lettore XML in streaming di HotXLS non espone offset sorgente, quindi il sottoalbero sconosciuto viene ricostruito a partire dagli eventi Element, Text ed EndElement mano a mano che scorrono. Questo approccio cela una classica trappola: un elemento a chiusura automatica come <a/> genera solo un evento Element contrassegnato come vuoto e mai un EndElement, per cui qualsiasi contatore di profondità che decrementa solo su EndElement non vedrà mai chiudersi il sottoalbero. Gestendo questo aspetto, il frammento ricostruito risulterà semanticamente equivalente all'originale — le virgolette degli attributi e i formati a chiusura automatica vengono normalizzati, per cui l'output non è identico a livello di byte, ma Excel ne interpreta il significato, non i singoli byte. Due proprietà dell'output stesso di Excel rendono sicura la riproduzione: Excel dichiara gli attributi xmlns necessari sull'elemento <ext> o al suo interno, per cui ogni frammento catturato è autonomo a livello di namespace, e questa stessa autonomia è il motivo per cui la duplicazione di un foglio di lavoro all'interno o tra cartelle di lavoro può trasportare i blocchi esterni tramite una semplice assegnazione di stringlist

Scrivere calcChain.xml in modo che Excel riconosca le formule

HotXLS scrive il file xl/calcChain.xml (la parte Calculation Chain, ECMA-376 Parte 1, §12.3.1) ogni volta che la cartella di lavoro salvata contiene formule, scegliendo tra due ordinamenti. Se il grafo di dipendenza delle formule è già stato costruito ed è aggiornato — avendo chiamato Recalculate dopo l'ultima modifica —, la catena viene generata in perfetto ordine topologico, con le dipendenze prima dei dipendenti e gli eventuali elementi con riferimenti circolari accodati alla fine. In caso contrario, le celle vengono elencate nell'ordine del documento. Entrambi gli approcci sono corretti: le note di implementazione di Microsoft per il formato, [MS-XLSX], considerano la catena di calcolo come un suggerimento che Excel verifica e riordina durante il caricamento, rendendo valida qualsiasi citazione completa, e HotXLS rifiuta deliberatamente di forzare la creazione del grafo all'interno di SaveAs — poiché la costruzione degli archi è quadratica rispetto al numero di celle, costo nascosto non accettabile in un salvataggio di un milione di celle

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');

Perché preoccuparsi di una parte che Excel considera solo consigliata? Perché la sua assenza funge da segnale. Alcuni consumatori — euristiche di riparazione, visualizzatori di terze parti, strumenti di confronto — si aspettano che una cartella di lavoro con formule contenga una catena di calcolo, e una libreria che omette silenziosamente questa parte al salvataggio produce file leggermente diversi da quelli scritti da Excel. Produrre una catena valida mantiene l'output conforme all'ambiente rispetto al quale è stato testato il resto dell'ecosistema, che rappresenta il fulcro silenzioso ma fondamentale del round-trip engineering

Dove finisce il round-trip senza perdita

L'onestà in questo ambito è più importante di una casella di spunta per il marketing, quindi i limiti meritano la stessa evidenza. HotXLS non copia l'intero pacchetto byte per byte: l'XML dei fogli di lavoro, gli stili, le stringhe condivise e le parti della cartella di lavoro vengono rigenerati dal modello analizzato, per cui l'output è semanticamente fedele ma non binariamente identico — le sole intestazioni locali ZIP portano nuovi timestamp DOS. I frammenti <ext> catturati vengono restituiti normalizzati, come descritto sopra. Le sostituzioni dei font del tema a livello di codice vengono ignorate in presenza di un tema letterale. Inoltre, la rete di preservazione ha maglie definite: funzionalità che HotXLS modella nativamente (le sparkline, ad esempio, vengono analizzate e riscritte anziché copiate ciecamente) più i contenuti extLst esterni e le parti salvate in cache letterale. Una parte che non è modellata né inserita in un punto di estensione — come la parte personalizzata di un componente aggiuntivo insolito — esula dai tre meccanismi trattati in questo articolo, motivo per cui si raccomanda di testare i modelli reali anziché procedere per ipotesi

Le operazioni di preservazione correlate completano il quadro. I progetti VBA e i riferimenti esterni alle cartelle di lavoro vengono mantenuti durante il salvataggio in base alla stessa filosofia del "conserva ciò che non modelli", trattata nell'articolo correlato sulla preservazione dei collegamenti esterni e VBA, e le proprietà del documento in docProps offrono le proprie API di lettura-scrittura anziché venire rimosse silenziosamente. Quando valuti una libreria per fogli di calcolo, esegui il test di modifica su una singola cella: apri una cartella di lavoro di produzione ricca di funzionalità, modifica un singolo valore, salva, ed esegui un diff delle parti decompresse rispetto all'originale. Ciò che è cambiato al di fuori del foglio toccato ti dirà sulla libreria molto più di qualsiasi matrice di funzionalità

I meccanismi di round-trip descritti in questa pagina — ritenzione del tema letterale dalla v2.89.46, acquisizione di extLst esterni ed emissione di calcChain.xml dalla v2.131.0 — sono forniti all'interno di HotXLS Delphi Excel Component, la cui pagina del prodotto illustra l'intero set di funzionalità di lettura e scrittura XLSX per Delphi e C++Builder