Articolo tecnico

HotXLS: sheet listing and lightweight workbook inspection

A volte l'unica domanda a cui una routine di intake deve rispondere è strutturale: questa cartella di lavoro ha un foglio chiamato "Mapping", oppure quante schede porta. Rispondere a questo chiamando Open è il modo costoso di farlo. Un'apertura completa gonfia la tabella delle stringhe condivise, decodifica ogni record di stile, e percorre le celle di ogni foglio di lavoro, perché non ha modo di sapere che volevi solo l'indice. Su un file grande sono centinaia di megabyte di allocazioni e diversi secondi di CPU spesi per leggere un elenco che occupa pochi kilobyte. HotXLS, la libreria nativa per fogli di calcolo Delphi di losLab, ti dà quell'elenco da solo: GetSheetNames restituisce i nomi dei fogli di lavoro, nell'ordine della cartella di lavoro, senza materializzare una singola cella

Perché il catalogo è economico da leggere

Entrambi i formati di foglio di calcolo mettono il proprio indice vicino all'inizio, il che è ciò che rende veloce una chiamata di elenco piuttosto che intelligente. Un pacchetto OOXML tiene il catalogo dei fogli in xl/workbook.xml, una parte che resta piccola sia che la cartella di lavoro contenga dieci righe sia dieci milioni. Un .xls BIFF8 memorizza i suoi record BoundSheet all'inizio dello stream globale della cartella di lavoro, prima di qualsiasi dato di cella. Quindi il lavoro che una chiamata di elenco evita non è un errore di arrotondamento rispetto a un'apertura completa. È la maggior parte del file. Leggere il catalogo costa la stessa manciata di kilobyte indipendentemente dal conteggio delle righe, mentre un'apertura completa scala con i dati, e su una cartella di lavoro multi-megabyte quel divario arriva a diversi ordini di grandezza sia in byte toccati sia in memoria allocata

HotXLS GetSheetNames in Delphi che legge solo il catalogo dei fogli di un file XLSX o XLS mentre un'apertura completa percorre ogni cella
Il catalogo siede in workbook.xml o nei record BoundSheet, così l'elencazione costa qualche kilobyte mentre un'apertura completa scala con i dati

Quel costo piatto è la proprietà attorno a cui vale la pena progettare. Un cancello di intake costruito su GetSheetNames si comporta allo stesso modo su un file da 200 righe e uno da 200 MB, quindi il file più lento in un batch non detta più il ritmo per decidere se un file valga anche solo la pena di essere elaborato

Un'unica chiamata per .xls, .xlsx e i formati template

Sulla facciata XLS, TXLSWorkbook.GetSheetNames legge più di .xls. Accetta anche i formati basati su zip .xlsx, .xlsm, .xltx e .xltm, estraendo solo workbook.xml dall'archivio. Per input .xls genuino esegue la scansione dei record BoundSheet e si ferma al primo record EOF del sottostream globale, così un grande file binario costa comunque solo i suoi kilobyte iniziali. La facciata XLSX porta una garanzia che conta più di quanto sembri a prima vista per codice di servizio a lunga esecuzione: TXLSXWorkbook.GetSheetNames non azzera né popola l'istanza della cartella di lavoro, così un'istanza che già tiene un documento aperto può sondare altri file senza disturbare quello in mano. GetODSSheetNames applica lo stesso approccio ai pacchetti OpenDocument, e ognuna di queste chiamate ha un overload per stream, che ti permette di ispezionare un caricamento che non atterra mai su disco

var
  Book: TXLSXWorkbook;
  Names: TStringList;
  I: Integer;
begin
  Names := TStringList.Create;
  Book := TXLSXWorkbook.Create;
  try
    if Book.GetSheetNames('upload-7f3a.xlsx', Names) <= 0 then
      raise Exception.Create('unreadable workbook package');
    if Names.IndexOf('Mapping') < 0 then
      raise Exception.Create('required Mapping sheet is missing');
    for I := 0 to Names.Count - 1 do
      Writeln(Format('sheet %d: %s', [I, Names[I]]));
  finally
    Book.Free;
    Names.Free;
  end;
end;

La stessa chiamata fa una buona finestra di dialogo di importazione desktop. Elenca i fogli, lascia che l'utente ne scelga uno, e paga per l'apertura completa solo dopo che la scelta è stata fatta. Con una cartella di lavoro da cinquanta fogli la differenza è visibile: un selettore che appare all'istante contro uno che si blocca mentre l'intero file si carica dietro

I file .xlsm abilitati alle macro e i formati template si elencano esattamente come un .xlsx semplice, dato che il catalogo si trova nello stesso workbook.xml che un vbaProject.bin viaggi o meno insieme nel pacchetto. Una pipeline di intake può quindi enumerare i fogli di una cartella di lavoro macro per il routing, senza mai toccare il payload macro e senza mai fare nulla che lo eseguirebbe, lasciando la decisione sulla policy delle macro allo stadio che apre davvero il file

Leggere il valore restituito senza ingannarsi

Le convenzioni sul valore restituito non sono uniformi in tutto HotXLS. Alcune chiamate restituiscono 1 in caso di successo, altre restituiscono un conteggio, quindi per le funzioni di elenco l'unico controllo che regge è trattare qualsiasi valore pari o inferiore a zero come fallimento, con la lista di stringhe svuotata. Resisti alla tentazione di leggere una lista vuota come "una cartella di lavoro senza fogli". Sia ECMA-376 sia la specifica BIFF8 richiedono almeno un foglio in una cartella di lavoro valida, quindi zero nomi significa sempre che la lettura è fallita, mai che il file sia legittimamente vuoto

Un elenco fallito è di per sé un segnale che vale la pena conservare. Un file .xlsx che fallisce la chiamata è una di poche cose specifiche: troncato, non davvero un pacchetto OOXML (esportazioni CSV etichettate male da altri sistemi compaiono qui di continuo), oppure un contenitore cifrato. Distinguerle è il compito del controllo successivo. Registrare i primi byte del file rifiutato insieme al fallimento di solito trasforma un thread di supporto in un singolo messaggio

Rilevare contenitori cifrati prima di instradare

Un .xlsx cifrato non è uno zip. È un file composto OLE che avvolge gli stream EncryptionInfo e EncryptedPackage, quindi GetSheetNames non riesce a vederci dentro e restituisce fallimento come qualsiasi altro file illeggibile. CanReadEncrypted verifica quella forma di contenitore, il che permette all'intake di instradare deliberatamente un file cifrato invece di inghiottire un errore di lettura generico proveniente da qualche parte in profondità in un worker:

Flusso di triage in ingresso Delphi con HotXLS CanReadEncrypted e GetSheetNames che instradano gli upload a needs-password, illeggibile o normale
CanReadEncrypted gira per primo perché un file OOXML cifrato è un contenitore OLE dentro il quale le chiamate di elencazione non possono vedere
type
  TIntakeRoute = (irNormal, irNeedsPassword, irUnreadable);

function ClassifyUpload(const FileName: string; Names: TStrings): TIntakeRoute;
var
  Book: TXLSXWorkbook;
begin
  Book := TXLSXWorkbook.Create;
  try
    // L'OOXML cifrato è un contenitore OLE, non uno zip: controlla prima questo,
    // perché le chiamate di elenco non possono guardarci dentro.
    if Book.CanReadEncrypted(FileName) then
      Exit(irNeedsPassword);
    if SameText(ExtractFileExt(FileName), '.ods') then
    begin
      if Book.GetODSSheetNames(FileName, Names) <= 0 then
        Exit(irUnreadable);
    end
    else if Book.GetSheetNames(FileName, Names) <= 0 then
      Exit(irUnreadable);
    Result := irNormal;
  finally
    Book.Free;
  end;
end;

La cifratura è dove HotXLS è deliberatamente asimmetrico, quindi il routing deve rispettarlo. La cifratura .xls legacy (RC4, RC4 CryptoAPI, XOR) è leggibile: TXLSWorkbook.Open(FileName, Password) decifra con una password memorizzata, e quei file possono restare sul percorso automatizzato. I pacchetti OOXML cifrati vanno nella direzione opposta. HotXLS può scriverne uno con SaveAsEncrypted, ma non può rileggerlo. OpenEncrypted solleva EXlsxEncryptionNotImplemented quando gli viene passato un pacchetto cifrato, motivo per cui una progettazione onesta dell'intake manda gli .xlsx cifrati a una persona con Excel e mantiene nel codice gli .xls con password

Per il lavoro batch questo classificatore si guadagna il suo posto eseguendo su un'intera directory in ingresso prima che qualsiasi worker inizi l'elaborazione vera e propria, dato che ogni sonda costa circa un'apertura di file e qualche kilobyte di letture. Anticiparlo cambia la modalità di fallimento a cui le operazioni tengono davvero. Invece di un job delle 3 del mattino che muore sul file 412 di 600, ottieni 412 file in coda e 5 rifiutati all'intake con un motivo allegato a ciascuno. Stesse chiamate di libreria, storia operativa molto migliore

Le domande a cui una chiamata di elenco non può rispondere

Nomi e ordine sono tutto ciò che ottieni. Le chiamate di elenco non dicono nulla sulla visibilità, quindi i fogli nascosti e molto nascosti arrivano nella lista sembrando come qualsiasi altro. Non riportano dimensioni dell'intervallo usato, conteggi di celle, né proprietà del documento. Anche la parte docProps/core.xml è piccola, ma oggi non esiste una sonda solo-proprietà, quindi i metadati di autore e titolo costano ancora un Open completo. Il modo pulito per conviverci è lasciare che i fatti economici instradino ogni file e riservare quelli costosi ai file che sopravvivono al routing. Per i file che procedono davvero verso una lettura approfondita, una scansione in sola lettura di un grande .xls gira notevolmente più veloce con _DisableGraphics := True, che salta il parsing di OfficeArt. Solo non salvare mai da quell'istanza: il layer di disegno che ha saltato è sparito dal modello, e salvare lo eliminerebbe dal file

I file che superano il triage di solito procedono verso un'analisi più approfondita. Il workbench di audit e conversione delle cartelle di lavoro copre i contatori per foglio che vale la pena raccogliere una volta che un'apertura completa è giustificata, e la guida alle prestazioni delle cartelle di lavoro di grandi dimensioni copre come mantenere veloce quell'apertura completa

HotXLS è una libreria Object Pascal nativa per fogli di calcolo per Delphi e C++Builder; l'intera superficie API, incluse le chiamate di ispezione mostrate qui, è documentata sulla pagina prodotto di HotXLS Delphi Component