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. Ciò che non fa è rendere le due direzioni simmetriche. L'esportazione porta molto più di quanto l'importazione recuperi, e un team che presuma il contrario vedrà formule e formattazione evaporare da qualche parte tra la revisione del cliente e il report successivo, 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 e larghezze di colonna. Scarta bordi, intervalli uniti, 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 deliberatamente
Leggi questa parte con attenzione prima di promettere a chiunque fedeltà di andata e ritorno. L'importazione ODS in HotXLS è deliberatamente un percorso leggero. Preserva i valori scalari delle celle e il risultato in cache che ogni formula portava al momento del salvataggio, ed espande righe e colonne ripetute nella griglia. Non porta con sé stili, espressioni di formula ODS o disegni
La scelta sulle formule è quella con più probabilità di mordere, ed è stata fatta apposta. 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. Tradurre OpenFormula nella sintassi delle formule Excel è un problema di conversione tra dialetti a sé stante, con veri casi limite attorno ai vocabolari di funzioni, alla sintassi dei riferimenti e ai modelli di errore. Leggere invece il valore in cache elude tutta quella classe di traduzioni errate silenziose, così i numeri che importi sono esattamente i numeri che il mittente ha visto per ultimo. Il costo è che arrivano come numeri, non come le formule vive che li hanno prodotti
La modalità di fallimento attorno a cui progettare segue direttamente: un foglio di calcolo i cui totali erano corretti quando LibreOffice lo ha salvato per l'ultima volta viene importato con numeri corretti, ma quei numeri ora sono costanti. Modifica una cella di input, ricalcola, e niente si muove - la formula è sparita, ne resta solo il risultato finale. Se il flusso di lavoro ha bisogno di formule vive dopo l'importazione, ristabiliscile a livello di programma dalle tue regole di business tramite Cell.Formula, che sulla facciata XLSX prende l'espressione senza segno di uguale iniziale
Progettare attorno all'andata e ritorno asimmetrica
L'esportazione renderizza dal modello completo della cartella di lavoro in memoria: valori, stili e, se li richiedi, grafici e immagini. L'importazione restituisce solo valori. Quindi il tratto da .xlsx a .ods è ad alta fedeltà, e il tratto da .ods a .xlsx riporta indietro valori e risultati in cache ma niente stile e niente formule vive. Concatena i due e l'asimmetria si somma. Un ciclo completo da .xlsx a .ods e di nuovo a .xlsx scrive tutto fedelmente in uscita e perde stili e formule al rientro, anche se nulla è andato storto in nessuno dei due passaggi
Book := TXLSXWorkbook.Create;
try
Book.Open('vendor-revision.ods'); // formato rilevato automaticamente
if Book.SourceFormat = xlsxOpenDocumentSpreadsheet then
begin
// Dopo un'importazione ODS sono presenti i valori e i risultati
// delle formule in cache; stili e formule vive no. Ricostruisci ciò
// da cui dipende la pipeline a valle prima di salvare.
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