Un backend di reportistica Delphi che ha emesso .xlsx per anni riceve un nuovo requisito: le regole di appalto di un cliente del settore pubblico impongono output OpenDocument Spreadsheet, e gli analisti di quell'account rimandano indietro le loro modifiche come file .ods salvati da LibreOffice. Quindi ora lo stesso codice deve scrivere ODS e anche leggerlo. HotXLS, la libreria Object Pascal nativa di losLab per fogli di calcolo per Delphi e C++Builder, gestisce entrambe le direzioni senza Excel o LibreOffice installati da nessuna parte. Le due direzioni sono simili ma non identiche. Da HotXLS 2.88.229 il percorso di importazione riporta le formule come formule vive accanto ai loro risultati in cache, e le versioni attuali leggono anche stili, formati numerici, intervalli uniti, commenti, immagini, grafici e formati condizionali. Ciò che ancora non sopravvive sono la semantica OpenFormula completa e una manciata di dettagli a livello di pacchetto, quindi un team che presuma uno specchio perfetto incontrerà comunque casi limite da qualche parte tra la revisione del cliente e il report successivo, di solito senza alcun errore a cui puntare
Il supporto ODS vive sulla facciata XLSX, non su quella XLS
HotXLS include due gerarchie di classi indipendenti in un unico pacchetto: TXLSWorkbook nella unit lxHandle per i file .xls binari BIFF8, e TXLSXWorkbook nella unit lxHandleX per i pacchetti OOXML .xlsx. Ogni punto di ingresso OpenDocument - OpenODS, SaveAsODS, GetODSSheetNames - pende da TXLSXWorkbook. Il posizionamento non è arbitrario. Un pacchetto ODS, come specificato in OASIS ODF 1.3, è un archivio zip che porta un membro mimetype, un manifest e un corpo content.xml, il che lo rende un cugino strutturale dello zip OOXML; BIFF8 è un flusso di record binario degli anni '90 senza nulla in comune
Questo posizionamento ha un risvolto pratico: una cartella di lavoro .xls legacy non può diventare .ods in un'unica chiamata. Devi prima fare da ponte tra il contenuto BIFF e il modello XLSX, con SaveXLSWorkbookAsXLSX dalla unit lxXlsxExport, riaprire il risultato tramite TXLSXWorkbook, e poi esportare da lì. Il ponte non è privo di perdite, ed è bene conoscerne i limiti prima di costruirci sopra. Copia valori, formule, formati numerici, font, riempimenti, bordi, intervalli uniti, larghezze di colonna e altezze di riga. Scarta commenti, grafici e formattazione condizionale. Una sorgente .xls con formattazione pesante arriverà a ODS con un aspetto più spoglio di come è partita, e questa è una proprietà del ponte, non del writer ODS
Il rilevamento sul lato importazione è automatico. Il metodo Open semplice riconosce un pacchetto ODS dal suo membro mimetype, ricorrendo a un controllo di content.xml a livello superiore quando quel membro è assente, così un percorso di codice generico "apri qualsiasi cosa l'utente abbia caricato" non ha bisogno di alcun controllo dell'estensione proprio. Dopo l'apertura, la proprietà SourceFormat riporta quale ramo è scattato
Esportare in ODS con TODSExportOptions
La chiamata di esportazione in sé è una riga; l'oggetto delle opzioni attorno ad essa porta le decisioni che un revisore chiederà più avanti:
var
Book: TXLSXWorkbook;
Opts: TODSExportOptions;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('quarterly-report.xlsx');
Opts := TODSExportOptions.Create; // il chiamante possiede e libera questo oggetto
try
Opts.Generator := 'ReportService 4.2'; // override di meta:generator
Opts.IncludeCharts := True;
Opts.IncludeImages := True;
Book.SaveAsODS('quarterly-report.ods', Opts);
finally
Opts.Free;
end;
finally
Book.Free;
end;
end;
L'oggetto delle opzioni è di proprietà del chiamante. HotXLS non lo libererà, motivo per cui il try..finally interno c'è e non è opzionale. Le due proprietà che cambiano l'output, invece di limitarsi a etichettarlo, meritano uno sguardo più attento. Impostare IncludeCharts := False fa più che nascondere i grafici: rimuove i sotto-documenti dei grafici e le loro voci di manifest dal pacchetto, esattamente ciò che vuoi quando il consumatore è una pipeline di dati che ci inciamperebbe. Generator sovrascrive la stringa ODF meta:generator, che altrimenti riporta HotXLS/<version>; sovrascrivila quando gli strumenti a valle fanno fingerprinting dei produttori di file per instradare il supporto. Se nulla di questo si applica, salta del tutto l'oggetto delle opzioni. Chiamare SaveAs(FileName, xlsxOpenDocumentSpreadsheet) è lo stesso di SaveAsODS con i default, e gli overload per stream su entrambi ti permettono di scrivere il pacchetto direttamente in una risposta HTTP senza file temporaneo
Cosa legge il percorso di importazione - e cosa salta ancora
Leggi questa parte con attenzione prima di promettere a chiunque fedeltà di andata e ritorno. L'importazione ODS in HotXLS è nata come percorso leggero che conservava solo valori scalari e risultati di formula in cache, e da lì è cresciuta molto. Le versioni attuali leggono content.xml insieme a styles.xml e settings.xml: valori scalari e risultati di formula in cache, righe e colonne ripetute espanse nella griglia, intervalli uniti, rich text, collegamenti ipertestuali, commenti, immagini e grafici incorporati, stili di cella ereditati e predefiniti, formati numerici e di data, altezze di riga e larghezze di colonna, intervalli con nome, riquadri bloccati, intervalli AutoFilter, convalide dei dati e formati condizionali. Ciò che OpenODS ancora non converte sono la semantica OpenFormula completa, le chiavi di protezione ODS, la geometria dei commenti, la formattazione avanzata dei commenti e le funzionalità del pacchetto esterne al contenuto del foglio di calcolo
Le formule sono la parte con più probabilità di mordere. Una cella ODF memorizza due cose affiancate: l'espressione della formula, scritta nel dialetto OpenFormula definito in ODF 1.3 Part 4, e l'ultimo valore che l'applicazione produttrice ha calcolato per essa. Le versioni precedenti leggevano solo il valore in cache. Da HotXLS 2.88.229 OpenODS importa anche l'espressione: una cella scritta con prefisso of:= o oooc:= conserva il proprio valore in cache e ottiene una formula Excel normalizzata, con i riferimenti tra parentesi quadre come [.A1:.B1] e i separatori di argomento a punto e virgola convertiti nella sintassi Excel. Da 2.384.32 l'importazione converte anche le righe di array inline ({1;2|3;4} diventa {1,2;3,4}) e l'operatore di unione di riferimenti ~, e da 2.384.55 tale unione viene rilegata senza una seconda coppia di parentesi. Tradurre OpenFormula nella sintassi Excel resta un problema di conversione tra dialetti con veri casi limite attorno ai vocabolari di funzioni, alla sintassi dei riferimenti e ai modelli di errore, ed è per questo che il valore in cache viaggia insieme: i numeri che importi sono esattamente i numeri che il mittente ha visto per ultimo
La modalità di fallimento attorno a cui progettare si è di conseguenza ristretta. Prima di 2.88.229, un foglio di calcolo i cui totali erano corretti quando LibreOffice lo ha salvato per l'ultima volta veniva importato con numeri corretti ormai diventati costanti: modificavi una cella di input, ricalcolavi, e niente si muoveva. Nelle versioni attuali le formule tornano vive, quindi il rischio residuo è un costrutto che il traduttore non modella, che può lasciare una formula che si ricalcola diversamente dall'applicazione produttrice mentre il valore in cache mostra ancora il risultato del mittente. Confronta i risultati ricalcolati con i valori in cache importati per le formule da cui dipende la tua pipeline, e ristabilisci dalle tue regole di business ogni formula in disaccordo, tramite Cell.Formula, che sulla facciata XLSX prende l'espressione senza segno di uguale iniziale
Progettare attorno ai restanti vuoti del round trip
L'esportazione renderizza dal modello completo della cartella di lavoro in memoria: valori, formule, stili e, se li richiedi, grafici e immagini. L'esportazione delle formule è stata raffinata versione dopo versione. Da 2.384.55 array inline, unioni di riferimenti e riferimenti assoluti conservano il loro significato in OpenFormula; da 2.384.56 HotXLS dichiara i namespace dei propri prefissi di formula, così LibreOffice valuta le formule invece di mostrare #VALUE! in ogni cella con formula; e da 2.384.65 i riferimenti a intere colonne e righe vengono scritti nella forma OpenFormula [.A:.A], così Excel non li apre più come #NAME?. I formati condizionali hanno seguito: da 2.384.66 le regole basate su formula vengono scritte come formula-is(...) con la propria cella base, cosa che LibreOffice riconosce, e da 2.384.69 le regole su valore e formula vengono scritte anche come condizioni style:map standard, l'unica forma che Excel legge, mentre il lato importazione legge entrambe. Un ciclo completo da .xlsx a .ods e di nuovo a .xlsx su una versione attuale conserva quindi valori, formule, stili e formati condizionali; ciò che può ancora perdere sta nei vuoti elencati nella sezione precedente
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // formato rilevato automaticamente
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Da HotXLS 2.88.229 le formule tornano vive accanto ai loro
// risultati in cache; ristabilisci prima di salvare ogni formula
// di cui la pipeline a valle non può fidarsi come tradotta.
Book.Sheets[0].Cells[2, 5].Formula := 'SUM(B2:D2)';
Book.SaveAs('vendor-revision.xlsx');
end;
finally
Book.Free;
end;
Il pattern architetturale che ne deriva: tratta i file .ods in ingresso come feed di dati, non come documenti da modificare sul posto. Mantieni la cartella di lavoro canonica in .xlsx, leggi i valori dalle revisioni dei clienti, ed emetti ODS fresco su richiesta dalla copia canonica. La verifica appartiene a entrambi i fronti - apri i file esportati in LibreOffice Calc, il consumatore ODF di riferimento, e in Excel, che legge ODS da anni ma diverge da LibreOffice ai margini del supporto per grafici e stili. Il conteggio dei fogli, una manciata di celle chiave e la presenza dei grafici costituiscono uno smoke check sufficiente per profilo di esportazione
Fare il triage di un file ODS prima di impegnarsi in un'importazione
Quando un endpoint accetta caricamenti, elencare i nomi dei fogli è molto più economico di un parsing completo e cattura presto le sorprese strutturali:
Names := TStringList.Create;
Book := TXLSXWorkbook.Create;
try
if Book.GetODSSheetNames('incoming.ods', Names) <= 0 then
raise Exception.Create('not a readable ODS package');
if Names.IndexOf('Data') < 0 then
raise Exception.Create('revision is missing the Data sheet');
finally
Book.Free;
Names.Free;
end;
La convenzione del valore restituito trae in inganno: le chiamate di HotXLS in genere restituiscono un conteggio positivo o 1 in caso di successo e -1 in caso di fallimento, svuotando la lista quando falliscono, quindi verifica <= 0 invece di confrontare con un singolo valore positivo specifico. GetODSSheetNames non azzera né popola l'istanza della cartella di lavoro, quindi un singolo oggetto sonda può verificare un'intera cartella di file in arrivo. Controlli strutturali come questo catturano il fallimento più comune nel mondo reale - un analista che rinomina o elimina un foglio prima di rimandare indietro la revisione - al cancello, dove il messaggio di errore può ancora nominare il file e il foglio mancante invece di emergere come un riferimento nil tre livelli più in profondità
Se stai costruendo una pipeline di conversione più ampia attorno a questo, il pattern del workbench di audit e conversione delle cartelle di lavoro mostra come inventariare le funzionalità di un file prima di scegliere un formato di destinazione, e la guida alle prestazioni delle cartelle di lavoro di grandi dimensioni mantiene le esportazioni batch entro limiti di memoria ragionevoli
HotXLS è una libreria nativa per fogli di calcolo per Delphi e C++Builder con codice sorgente completo; l'elenco completo delle funzionalità e i dettagli di licenza si trovano sulla pagina prodotto di HotXLS Delphi Component