Articolo tecnico

Leggere e scrivere workbook binari XLSB in Delphi

HotXLS legge e scrive file Excel Binary Workbook (XLSB) da Delphi attraverso la stessa facade TXLSXWorkbook usata per XLSX: Open rileva un workbook binario dal contenuto del package, e SaveAs o SaveAsChecked con xlsxExcelBinaryWorkbook ne scrive uno. Il backend copre un subset validato e rifiuta tutto ciò che non può rappresentare prima di toccare la destinazione

Quell'ultima frase è la parte che vale la pena leggere lentamente. XLSB sembra una variazione semplice su XLSX, e in termini di packaging lo è. Il guaio vive nei dettagli che i due formati codificano diversamente, e in ciò che una libreria assume silenziosamente quando fa da ponte dall'uno all'altro. Il bug più istruttivo di questo backend non era affatto un record malformato. Era un font predefinito sparito, e una colonna di date diventata #### in Excel nonostante il valore, il codice di formato e la larghezza di colonna fossero tutti corretti

Che cos'è un file XLSB e in che cosa differisce da XLSX?

Un file XLSB è un package OPC con esattamente la struttura di parti di un workbook XLSX, ma le parti workbook, worksheet, style e shared-string conservano record binari invece di XML. Il formato è specificato in [MS-XLSB], e la codifica dei record è spesso chiamata BIFF12. Avete comunque [Content_Types].xml, parti di relazione e un contenitore ZIP; ciò che cambia è che xl/workbook.bin sostituisce xl/workbook.xml, xl/worksheets/sheet1.bin sostituisce l'XML del worksheet, e così via

Ogni record BIFF12 inizia con un header compatto a lunghezza variabile. Il tipo di record prende uno o due byte, sette bit per byte con il bit alto come flag di continuazione, così un tipo di record sta in 14 bit. La dimensione del payload segue in uno a quattro byte usando lo stesso schema a sette bit. Le stringhe sono un conteggio di caratteri a 32 bit seguito da unità di codice UTF-16. Rispetto a BIFF8 questo è un layout molto più pulito: niente spezzatura CONTINUE da 8224 byte, niente flag stringa compressa-versus-larga a metà record. Rispetto a XLSX salta del tutto la tokenizzazione XML, che è perché i file XLSB grandi tendono a essere più piccoli su disco e più veloci da aprire per Excel

Un dettaglio di packaging fa inciampare il codice di rilevamento. Excel registra la parte workbook XLSB con il content type application/vnd.ms-excel.sheet.binary.macroEnabled.main, anche per workbook che non contengono macro. HotXLS controlla prima quella registrazione in [Content_Types].xml e ricade sull'estensione di parte .bin solo quando il content type è assente, e entrambi i percorsi devono comunque passare il validatore severo dei record. Le regole generali su nomi di parte, relazioni e content type sono le stesse trattate in perché Excel ripara un XLSX valido e le regole di package OPC dietro di ciò

Quando conviene scegliere XLSB invece di XLSX?

Scegliete XLSB quando un sistema a valle lo richiede o quando scambiate workbook grandi ricchi di valori con utenti Excel che tengono al tempo di apertura; altrimenti XLSX resta il predefinito più sicuro. XLSX è uno standard ISO (ISO/IEC 29500) con un ampio supporto di strumenti, è ispezionabile con uno strumento ZIP e un editor di testo, e ogni prodotto di fogli di calcolo lo legge. XLSB è un formato Microsoft con una specifica pubblicata ma un ecosistema più ristretto

In pratica le richieste arrivano da due direzioni. Alcune pipeline di reporting e archivi hanno standardizzato su .xlsb anni fa e rifiutano qualunque altra cosa. Altri team generano workbook con centinaia di migliaia di celle numeriche e hanno trovato che gli utenti aprono la versione binaria notevolmente più in fretta. Sono entrambi motivi legittimi. «Sembra più efficiente» non lo è, perché il backend binario di HotXLS mette in scena il package completo in memoria, quindi non è lo strumento per esportazioni server a memoria limitata. Per quelle, lo scrittore XLSX in streaming è il percorso giusto

Aprire e salvare XLSB con TXLSXWorkbook

TXLSXWorkbook.Open non ha bisogno di un indizio di formato: la stessa chiamata carica XLSX, XLSM o XLSB, e dopo un caricamento binario riuscito TXLSXWorkbook.SourceFormat riporta xlsxExcelBinaryWorkbook. La scrittura è esplicita. SaveAs(FileName) senza formato produce sempre un workbook Open XML, anche quando il nome file finisce in .xlsb, quindi l'output binario nomina sempre il formato

uses
  SysUtils, lxHandleX;

procedure ConvertToXlsb(const Source, Target: string);
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // Open rileva XLSX, XLSM o XLSB dal package stesso
    if Book.Open(Source) <> 1 then
      raise Exception.Create('Input is unsupported or invalid');

    // L'estensione da sola non seleziona mai l'output binario
    if Book.SaveAsChecked(Target, xlsxExcelBinaryWorkbook) <> 1 then
      raise Exception.Create('XLSB output is unsupported or failed');
  finally
    Book.Free;
  end;
end;

L'output su stream passa per SaveAs(Stream, xlsxExcelBinaryWorkbook) o l'overload su stream di SaveAsChecked. Internamente entrambi instradano ai metodi privati SaveAsXlsbStream e SaveAsXlsbFile; voi non li chiamate mai direttamente. L'output su file viene messo in scena in un file temporaneo fratello e commesso con un'unica sostituzione finale. Una destinazione su stream deve essere leggibile e posizionabile, perché lo scrittore fa uno snapshot dei suoi byte, dimensione e posizione e li ripristina se la copia finale fallisce. TXLSXWorkbook.LastSaveUsedStreaming riporta False dopo un salvataggio binario, il che è accurato: l'intero package esiste in memoria prima che un solo byte raggiunga la vostra destinazione

Perché un subset validato invece della piena fedeltà?

HotXLS non mantiene un secondo modello a oggetti per XLSB. All'apertura, XlsbToOpenXml converte il package binario in un package Open XML in memoria e il normale parser XML lo carica; al salvataggio, lo scrittore XML costruisce un package e OpenXmlToXlsb lo codifica in record binari. Ogni funzionalità deve quindi sopravvivere a due traduzioni, e una funzionalità che il ponte non può tradurre esattamente viene rifiutata invece che approssimata

L'insieme supportato a partire da HotXLS 2.384.113 è abbastanza ampio per la maggior parte dei workbook di dati: valori tipizzati, stringhe condivise e inline, font comuni, riempimenti, bordi e formati numerici, altezze di riga e larghezze di colonna, formule di cella ordinarie con risultati in cache, nomi definiti, celle unite, hyperlink, riquadri bloccati e divisi, intervalli AutoFilter con criteri personalizzati semplici, e tabelle di worksheet ordinarie. Fuori da esso stanno le formule condivise e matriciali, le funzioni future e definite dall'utente, i riferimenti a workbook esterni, grafici e disegni, PivotTable e slicer, VBA, formattazione condizionale, convalida dati e impostazioni di calcolo non predefinite come calcolo manuale o iterazione

Il rifiuto è obbligatorio, non consultivo. TXLSXWorkbook.GetConversionReport(xlsConversionExcelBinaryWorkbook) elenca le funzionalità non supportate prima di salvare, e quelle voci fanno fallire il salvataggio anche con AAllowLossy = True o un SaveAs non controllato. È deliberato. Buttar via silenziosamente una PivotTable da una copia binaria è il tipo di perdita che nessuno nota finché il workbook non è davanti a un cliente

uses
  Classes, lxHandleX, lxDiagnostics;

function CanSaveAsXlsb(Book: TXLSXWorkbook; Log: TStrings): Boolean;
var
  Report: TXLSConversionReport;
  I: Integer;
begin
  Report := Book.GetConversionReport(xlsConversionExcelBinaryWorkbook);
  try
    Result := True;
    for I := 0 to Report.Count - 1 do
      if Report[I].Fidelity = xlsConversionUnsupported then
      begin
        Result := False;
        Log.Add(Report[I].SheetName + ' ' + Report[I].Location + ': ' +
          Report[I].Feature + ' - ' + Report[I].Message);
      end;
  finally
    Report.Free;   // il report appartiene a chi chiama
  end;
end;

Il report copre il modello vivo. La validazione finale del package resta comunque l'ultimo cancello per le cose che il modello non vede mai, come record malformati o attributi sconosciuti nel file sorgente, quindi un report pulito seguito da un salvataggio fallito è possibile, e il risultato del salvataggio è ciò su cui agite

Perché sono spariti il font predefinito e il formato 0.00?

Il font predefinito e i formati numerici built-in sono spariti perché il lettore di stili XML faceva due assunzioni che valgono per i file che HotXLS scrive da sé ma non per i workbook che scrive Excel. Il ponte conservava entrambe le risorse perfettamente; la perdita accadeva un livello più su, nel percorso pubblico Open. Quel divario è la lezione principale di questo backend: un ponte di formato corretto non garantisce un round trip pubblico fedele

La prima assunzione riguardava fonts[0]. Quando HotXLS salva XLSX, scrive un font predefinito implicito nello slot zero, e il suo lettore XML salta quello slot così la collezione Fonts vista dall'utente parte dall'indice zero. Un XLSB scritto da Excel porta il proprio predefinito esplicito in quello slot, e saltarlo buttava via quella scelta. La seconda assunzione riguardava i formati numerici. Il lettore mappava in NumberFormats solo i valori numFmtId personalizzati (164 e oltre); gli id built-in restavano a zero. Una cella vuota con stile che porta il formato built-in 2, che è 0.00, tornava come General

La correzione vive interamente nel percorso di importazione binario. Dopo che l'intero package passa il preflight, l'apertura XLSB imposta una modalità di stile privata solo per il parse XML sincrono. In quella modalità il lettore carica ogni font incluso lo slot zero e sposta ogni fontId di uno, converte ogni id built-in supportato in un codice di formato esplicito nel modello, e usa ancora il numFmtId originale per il rilevamento delle date, così un formato data built-in conserva la sua semantica di data. Un blocco finally azzera sempre la modalità, e una successiva Open XLSX o ODS semplice azzera anche lo stato del font predefinito trattenuto

In che modo fonts[0] influenza le larghezze di colonna di Excel?

Excel calcola le larghezze di colonna dal font predefinito del workbook in fonts[0], non dai font applicati alle celle di quella colonna. La width della colonna è conservata in unità carattere della larghezza massima della cifra del font predefinito, così cambiare fonts[0] cambia la larghezza in pixel di ogni colonna, comprese le colonne le cui celle usano tutte un font diverso

Questo ha prodotto il sintomo più confondente di tutta l'indagine. Il workbook risalvato aveva valori di cella identici, codici di formato identici e attributi width identici, il font della cella e lo stile Normal puntavano entrambi al font conservato, e Excel renderizzava comunque una colonna di date come ####. Il fonts[0] scritto era tornato a Calibri 11, la larghezza massima della cifra si era ristretta, e le date non entravano più. HotXLS ora conserva il font predefinito della sorgente e lo riscrive come fonts[0], ricadendo su Calibri 11 solo per i workbook che non hanno mai avuto una sorgente binaria. L'aritmetica completa della larghezza sta in larghezza colonna Excel e max digit width (MDW) in Delphi

I formati built-in dipendenti dal locale vengono rifiutati, non indovinati

Un id di formato numerico built-in senza un codice di formato esplicito viene accettato solo quando il suo significato è fisso; gli id dipendenti dal locale vengono rifiutati durante il preflight del package. HotXLS mappa gli id da 0 a 22, da 37 a 40 e da 45 a 49 in codici noti come 0.00, 0%, mm:ss e @. Gli id restanti sotto 164 appartengono a formati il cui rendering dipende dalle impostazioni regionali dell'installazione di Excel, e l'importazione li rifiuta con il messaggio «A locale-dependent number format requires an explicit format code»

Inventare un codice per quegli id sarebbe peggio che fallire. Un pattern data dell'Asia orientale indovinato sembra plausibile, fa il round trip senza errori e mostra la cosa sbagliata a metà dei vostri utenti. Anche il tempismo conta: il controllo gira nel preflight dell'intero package, prima che il workbook esistente venga azzerato, così un file rifiutato lascia intatti il modello caricato in precedenza, il nome file e il formato sorgente. L'articolo sul seriale data Excel e numFmt copre come gli id data built-in interagiscono con i sistemi di data 1900 e 1904

procedure LoadBinaryWorkbook(Book: TXLSXWorkbook; const FileName: string);
var
  I: Integer;
begin
  // L'input binario richiede caricamento completo eager; le modalità selettive rifiutano subito
  Book.MetadataOnly := False;
  Book.SelectedSheets.Clear;

  if Book.Open(FileName) <> 1 then
  begin
    for I := 0 to Book.Diagnostics.Count - 1 do
      Writeln(Book.Diagnostics[I].Message);
    Exit;   // il workbook caricato in precedenza è ancora intatto
  end;

  // L'output delle formule ha bisogno di una cache a meno che il salvataggio ricalcoli
  Book.RecalcBeforeSave := xrmAlways;
end;

Regole pratiche per XLSB nel codice di produzione

Il backend binario è sicuro su cui contare quando trattate i suoi rifiuti come dati invece che come errori da sopprimere. Alcune regole seguono dal comportamento sopra

  • Nominate il formato a ogni salvataggio binario; la sola estensione .xlsb produce comunque XLSX
  • Eseguite GetConversionReport(xlsConversionExcelBinaryWorkbook) prima di salvare workbook modificati dagli utenti e mostrate all'utente le funzionalità non supportate
  • Tenete MetadataOnly spento e SelectedSheets vuoto per l'input binario, dato che il caricamento lazy richiederebbe di trattenere un archivio convertito
  • Date alle formule un risultato in cache o ricalcolate prima di salvare, perché una cache mancante rifiuta sotto xrmManual e sotto un xrmIfDirty pulito
  • Mettete in budget la memoria per il package convertito completo più l'output binario; usate XLSX in streaming quando la memoria deve restare limitata

Quando testate, confrontate con Excel attraverso la API pubblica invece che a livello di ponte. Le regressioni del font predefinito e del 0.00 passarono entrambe i test del ponte, e entrambe comparvero immediatamente in un workbook scritto da Excel aperto e risalvato attraverso Open e SaveAsChecked

FAQ

HotXLS rileva i file XLSB automaticamente?

Sì. TXLSXWorkbook.Open ispeziona il content type registrato per la parte workbook e valida i record binari, così la stessa chiamata carica XLSX e XLSB, e SourceFormat riporta quale è stato letto

Perché SaveAs con un nome file .xlsb scrive comunque XLSX?

HotXLS non deduce mai il formato di output dall'estensione. Passate xlsxExcelBinaryWorkbook a SaveAs o SaveAsChecked per scrivere un workbook binario

Cosa accade quando un workbook usa funzionalità fuori dal subset XLSB?

Il salvataggio fallisce prima che la destinazione cambi, anche con il salvataggio lossy consentito. GetConversionReport elenca in anticipo le funzionalità bloccanti, e Diagnostics spiega il fallimento dopo

Il supporto XLSB, il report di conversione e il resto della API del workbook vengono spediti in HotXLS, il componente di fogli di calcolo nativo Delphi e C++Builder