Teknisk artikel

Diff to Excel-arbejdsbøger i Delphi med HotXLS

HotXLS sammenligner to arbejdsbøger via TXLSXWorkbookCompare, som parrer regneark efter navn, gennemløber de befolkede celler i hvert par og rapporterer, hvad der er forskelligt, som en struktureret liste af forskelsposter og, på anmodning, som én læsbar linje pr. forskel. Ingen Excel-installation er involveret, og sammenligningen kører udelukkende på den indlæste objektmodel i Delphi eller C++Builder

Behovet dukker som regel op, første gang nogen spørger, hvad der er ændret. En finansarbejdsbog kommer tilbage fra gennemgang, en natlig eksport regenereres efter en kodeændring, eller to afdelinger sender versioner af samme skabelon. At åbne begge side om side virker for ét ark og fejler for tyve. At sammenligne filer byte for byte besvarer slet ingenting, fordi to gemninger af samme arbejdsbog adskiller sig på måder, ingen bekymrer sig om

Hvad tæller som en forskel?

Sammenligningen rapporterer otte typer, og sættet er bevidst lille: et ark tilføjet eller fjernet, en befolket celle tilføjet eller fjernet, en celle, hvis værdi ændrede sig, en celle, hvis formel ændrede sig, og et fletningsområde tilføjet eller fjernet. Alt udtrykkes mod den venstre arbejdsbog som baseline, så et tilføjet element kun findes til højre, og et fjernet element kun til venstre

Ark parres efter navn frem for efter position. At omarrangere regneark producerer derfor slet ingen forskelle, hvilket næsten altid er den adfærd, man ønsker: en bruger, der trækker en fane, er ikke en dataændring. Et ark, der kun findes på den ene side, rapporterer én post på arkniveau frem for at udfolde hver befolket celle inde i det, hvilket holder en rapport over to strukturelt forskellige arbejdsbøger læsbar i stedet for tusindvis af linjer lang

Værdi eller formel, og hvordan hver sammenlignes

Hver celle bidrager med en signatur, og reglen er enkel: en celle, der bærer en formel, sammenlignes ved sin formeltekst med et indledende lighedstegn, og en celle uden en sammenlignes ved sin værdi konverteret til tekst. Den skelnen betyder mere, end den ser ud til ved første øjekast. To celler kan indeholde det samme viste tal, mens den ene er en literal og den anden en formel, og at behandle dem som ens ville skjule netop den redigering, der er mest værd at fange i en gennemgået arbejdsbog

Det betyder også, at en formel, hvis tekst er uændret, rapporterer ingen forskel, selv hvis dens cachede resultat er anderledes, hvilket er den korrekte adfærd for at sammenligne forfattet indhold, og den forkerte adfærd, hvis man forsøger at opdage genberegningsdrift. Til det andet spørgsmål skal man genberegne begge arbejdsbøger, før man sammenligner, så de værdier, man sammenligner, er dem, formlerne rent faktisk producerer i dag

Kørsel af en sammenligning

Compare tager de to indlæste arbejdsbøger og returnerer antallet af fundne forskelle. Forskelslisten er derefter tilgængelig efter indeks eller kan dumpes ind i enhver 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);              // én læsbar linje pr. forskel
      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;

En linje produceret af Report lyder som value changed: Data!A2: 10 -> 99, hvilket er nok til en anmelder og nok til en commit-besked. Det er den menneskevendte flade. Den programmatiske flade er selve forskelsposten, og det er den, man skal bruge, når sammenligningen fodrer en beslutning frem for et dokument

At styre logik ud fra de strukturerede forskelle

Hver forskel afslører sin type, arknavnet, en-baseret række og kolonne for poster på celleniveau, en A1-reference for poster på fletningsniveau samt venstre og højre tekst. Poster på arkniveau og fletningsniveau rapporterer række og kolonne som nul, hvilket er, hvordan man skelner dem uden at inspicere typen:

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;

  // En gennemgangspolitik, der kun blokerer ved formelredigeringer
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

To egenskaber ved outputtet er værd at kende, før man skriver assertions mod det. Rækkefølgen af poster på celleniveau følger cellelagrets interne gennemløbsrækkefølge, så tests bør skrives rækkefølgeuafhængigt. Og formelsignaturen bærer sit eget indledende lighedstegn, hvilket betyder, at en beskrivelsesstreng bygget ved sammenkædning kan vise et dobbelt ==; tjek feltværdierne frem for at parse den beskrivende linje, når resultatet styrer logik

Hvor arbejdsbog-diffing betaler sig

Tre anvendelser retfærdiggør funktionen på egen hånd. Regressionstest af en rapportgenerator: behold en kendt-god arbejdsbog, regenerér, sammenlign, og fejl builden ved enhver uventet forskel. Ændringsgennemgang: giv en anmelder den læsbare rapport i stedet for to filer. Og migrationsverifikation: efter konvertering af en batch af legacy-arbejdsbøger, sammenlign hvert resultat mod dets kilde for at bevise, at intet gik tabt

Det tredje tilfælde parrer sig naturligt med opgørelses- og revisionsgennemløbene beskrevet i arbejdsbog-revision og konverteringsværkstedet, hvor at tælle, hvad en arbejdsbog indeholder, sker før konvertering, og sammenligning sker efter. Hvis dine forskelle klynger sig omkring indsatte rækker, forklarer reglerne for referenceomskrivning i formelreferencejustering ved indsættelse og sletning, hvorfor formler, der ser uændrede ud, rapporteres som ændrede

Grænserne, sagt ligeud

Sammenligningen dækker værdier, formler, fletninger og arktilstedeværelse. Den sammenligner ikke talformater, skrifttyper, udfyldninger, betingede formateringsregler, datavalideringer, diagrammer, billeder eller definerede navne. En celle, hvis værdi er identisk, men hvis format ændrede sig fra Generelt til Valuta, rapporterer ingen forskel, hvilket er korrekt for en datasammenligning og utilstrækkeligt for en formateringsgennemgang

Datoværdi-celler fortjener én specifik advarsel: de sammenlignes som deres tekstkonvertering, så en arbejdsbog gemt på 1904-datosystemet og en på 1900-systemet kan sammenlignes som ens eller forskellige på måder, der overrasker, hvis de underliggende serienumre er forskellige. Datosystemreglerne er beskrevet i seriedatoer og 1904-systemet. Når formatering eller objektniveau-troskab er en del af spørgsmålet, kombinér diffen med et revisionsgennemløb, der tæller disse funktioner på hver side

Arbejdsbog-sammenligning, revision og konvertering kører alle på den samme motor til Delphi og C++Builder; den komplette funktionsliste findes på HotXLS Delphi regnearkskomponentsiden