Articolo tecnico

Confrontare due cartelle di lavoro Excel in Delphi con HotXLS

HotXLS confronta due cartelle di lavoro tramite TXLSXWorkbookCompare, che accoppia i fogli di lavoro per nome, percorre le celle popolate di ogni coppia, e riporta ciò che differisce come elenco strutturato di record di differenza e, su richiesta, come una riga leggibile per ogni differenza. Non è coinvolta alcuna installazione di Excel, e il confronto viene eseguito interamente sul modello a oggetti caricato in Delphi o C++Builder

La necessità di solito compare la prima volta che qualcuno chiede cosa è cambiato. Una cartella di lavoro finanziaria torna da una revisione, un'esportazione notturna viene rigenerata dopo una modifica al codice, o due reparti inviano versioni dello stesso modello. Aprire entrambi affiancati funziona per un foglio e fallisce per venti. Confrontare i file byte per byte non risponde a nulla, perché due salvataggi della stessa cartella di lavoro differiscono in modi a cui nessuno tiene

Cosa conta come differenza?

Il confronto riporta otto tipi, e l'insieme è deliberatamente ridotto: un foglio aggiunto o rimosso, una cella popolata aggiunta o rimossa, una cella il cui valore è cambiato, una cella la cui formula è cambiata, e un intervallo unito aggiunto o rimosso. Tutto viene espresso rispetto alla cartella di lavoro sinistra come riferimento, quindi un elemento aggiunto esiste solo a destra e un elemento rimosso solo a sinistra

I fogli vengono accoppiati per nome piuttosto che per posizione. Riordinare i fogli di lavoro quindi non produce alcuna differenza, il che è quasi sempre il comportamento desiderato: un utente che trascina una scheda non è una modifica ai dati. Un foglio presente solo su un lato riporta una singola voce a livello di foglio anziché espandere ogni cella popolata al suo interno, il che mantiene leggibile il report di due cartelle di lavoro strutturalmente diverse invece di renderlo lungo migliaia di righe

Valore o formula, e come viene confrontato ciascuno

Ogni cella contribuisce con una firma, e la regola è semplice: una cella con una formula viene confrontata in base al testo della formula con un segno di uguale iniziale, e una cella senza formula viene confrontata in base al proprio valore convertito in testo. Questa distinzione conta più di quanto sembri a prima vista. Due celle possono contenere lo stesso numero visualizzato mentre una è un letterale e l'altra una formula, e trattarle come uguali nasconderebbe esattamente la modifica più importante da individuare in una cartella di lavoro revisionata

Significa anche che una formula il cui testo è invariato non riporta alcuna differenza anche se il suo risultato memorizzato nella cache differisce, il che è il comportamento corretto per confrontare contenuto scritto dall'utente, e quello sbagliato se si sta cercando di rilevare una deriva di ricalcolo. Per quella seconda domanda, ricalcola entrambe le cartelle di lavoro prima di confrontarle, così i valori confrontati sono quelli che le formule producono davvero oggi

Eseguire un confronto

Compare accetta le due cartelle di lavoro caricate e restituisce il numero di differenze trovate. L'elenco delle differenze è quindi disponibile per indice, o può essere riversato in qualsiasi TStrings:

uses
  lxHandleX, lxCompare;

var
  Left, Right: TXLSXWorkbook;
  Cmp: TXLSXWorkbookCompare;
  Lines: TStringList;
begin
  Left := TXLSXWorkbook.Create;
  Right := TXLSXWorkbook.Create;
  Cmp := TXLSXWorkbookCompare.Create;
  Lines := TStringList.Create;
  try
    if (Left.Open('baseline.xlsx') <> 1) or
       (Right.Open('reviewed.xlsx') <> 1) then
      Exit;

    if Cmp.Compare(Left, Right) = 0 then
      Writeln('workbooks are equivalent')
    else
    begin
      Cmp.Report(Lines);              // una riga leggibile per ogni differenza
      Lines.SaveToFile('workbook-diff.txt');
      Writeln(Format('%d difference(s) written', [Cmp.Count]));
    end;
  finally
    Lines.Free;
    Cmp.Free;
    Right.Free;
    Left.Free;
  end;
end;

Una riga prodotta da Report si legge come value changed: Data!A2: 10 -> 99, il che è sufficiente per un revisore ed è sufficiente per un messaggio di commit. Questa è la superficie rivolta all'utente umano. La superficie programmatica è il record di differenza stesso, ed è quella da usare quando il confronto alimenta una decisione anziché un documento

Guidare la logica dalle differenze strutturate

Ogni differenza espone il proprio tipo, il nome del foglio, riga e colonna a base uno per le voci a livello di cella, un riferimento A1 per le voci a livello di unione, e il testo sinistro e destro. Le voci a livello di foglio e a livello di unione riportano riga e colonna come zero, ed è così che si distinguono senza ispezionare il tipo:

var
  I: Integer;
  D: TlxCompareDiff;
  FormulaEdits: Integer;
begin
  FormulaEdits := 0;
  for I := 0 to Cmp.Count - 1 do
  begin
    D := Cmp.Diff(I);
    case D.Kind of
      lckFormulaChanged:
        begin
          Inc(FormulaEdits);
          Writeln(Format('%s R%dC%d: %s => %s',
            [D.Sheet, D.Row, D.Col, D.LeftText, D.RightText]));
        end;
      lckSheetAdded, lckSheetRemoved:
        Writeln(Format('structure: %s', [D.Describe]));
      lckMergeAdded, lckMergeRemoved:
        Writeln(Format('layout: %s at %s', [D.Describe, D.Ref]));
    end;
  end;

  // Una policy di revisione che blocca solo sulle modifiche alle formule
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

Due proprietà dell'output vale la pena conoscerle prima di scrivere asserzioni su di esso. L'ordine delle voci a livello di cella segue l'ordine di traversata interno dello store delle celle, quindi i test dovrebbero essere scritti in modo indipendente dall'ordine. E la firma della formula porta il proprio segno di uguale iniziale, il che significa che una stringa di descrizione costruita per concatenazione può mostrare un == raddoppiato; controlla i valori dei campi anziché analizzare la riga descrittiva quando il risultato guida la logica

Dove il confronto tra cartelle di lavoro ripaga

Tre usi giustificano la funzionalità da soli. Test di regressione di un generatore di report: mantieni una cartella di lavoro nota come corretta, rigenera, confronta, e fai fallire la build su qualsiasi differenza inattesa. Revisione delle modifiche: consegna a un revisore il report leggibile invece di due file. E verifica della migrazione: dopo aver convertito un lotto di cartelle di lavoro legacy, confronta ogni risultato con la propria sorgente per dimostrare che nulla è andato perso

Quel terzo caso si abbina naturalmente ai passaggi di inventario e audit descritti in il banco di audit e conversione delle cartelle di lavoro, dove contare cosa contiene una cartella di lavoro avviene prima della conversione e il confronto avviene dopo. Se le tue differenze si concentrano attorno a righe inserite, le regole di riscrittura dei riferimenti in adeguamento dei riferimenti di formula su inserimento ed eliminazione spiegano perché formule che sembrano invariate vengono riportate come cambiate

I limiti, dichiarati apertamente

Il confronto copre valori, formule, unioni e presenza dei fogli. Non confronta formati numerici, font, riempimenti, regole di formattazione condizionale, convalide dei dati, grafici, immagini o nomi definiti. Una cella il cui valore è identico ma il cui formato è cambiato da Generale a Valuta non riporta alcuna differenza, il che è corretto per un confronto di dati e insufficiente per una revisione della formattazione

Le celle con valori data meritano un'avvertenza specifica: vengono confrontate secondo la loro conversione testuale, quindi una cartella di lavoro memorizzata con il sistema di date 1904 e una con il sistema 1900 possono risultare uguali o diverse in modi sorprendenti se i numeri seriali sottostanti differiscono. Le regole del sistema di date sono trattate in numeri seriali di data e il sistema 1904. Quando la formattazione o la fedeltà a livello di oggetto fanno parte della domanda, combina il diff con un passaggio di audit che conta quelle funzionalità su ciascun lato

Confronto, audit e conversione delle cartelle di lavoro funzionano tutti sullo stesso motore per Delphi e C++Builder; l'elenco completo delle funzionalità si trova nella pagina del componente foglio di calcolo Delphi HotXLS