Technisch artikel

Twee Excel-werkmappen diffen in Delphi met HotXLS

HotXLS vergelijkt twee werkmappen via TXLSXWorkbookCompare, dat werkbladen op naam koppelt, de gevulde cellen van elk paar doorloopt, en rapporteert wat verschilt als een gestructureerde lijst van verschilrecords en, op verzoek, als één leesbare regel per verschil. Er is geen Excel-installatie bij betrokken, en de vergelijking draait volledig op het geladen objectmodel in Delphi of C++Builder

De behoefte ontstaat meestal de eerste keer dat iemand vraagt wat er is veranderd. Een financiële werkmap komt terug van een beoordeling, een nachtelijke export wordt na een codewijziging opnieuw gegenereerd, of twee afdelingen sturen versies van hetzelfde sjabloon. Beide naast elkaar openen werkt voor één werkblad en faalt bij twintig. Bestanden byte voor byte vergelijken beantwoordt helemaal niets, omdat twee opslagacties van dezelfde werkmap verschillen op manieren die niemand iets kunnen schelen

Wat telt als een verschil?

De vergelijking rapporteert acht soorten, en de set is bewust klein: een werkblad toegevoegd of verwijderd, een gevulde cel toegevoegd of verwijderd, een cel waarvan de waarde is veranderd, een cel waarvan de formule is veranderd, en een samengevoegd bereik toegevoegd of verwijderd. Alles wordt uitgedrukt ten opzichte van de linkerwerkmap als basislijn, dus een toegevoegd item bestaat alleen rechts en een verwijderd item alleen links

Werkbladen worden op naam gekoppeld, niet op positie. Het herordenen van werkbladen levert daarom helemaal geen verschillen op, wat bijna altijd het gewenste gedrag is: een gebruiker die een tabblad versleept, is geen datawijziging. Een werkblad dat slechts aan één kant aanwezig is, rapporteert één item op werkbladniveau in plaats van elke gevulde cel erbinnen uit te breiden, wat een rapport van twee structureel verschillende werkmappen leesbaar houdt in plaats van duizenden regels lang

Waarde of formule, en hoe elk wordt vergeleken

Elke cel draagt een handtekening bij, en de regel is eenvoudig: een cel met een formule wordt vergeleken op zijn formuletekst met een voorafgaand isgelijkteken, en een cel zonder formule wordt vergeleken op zijn waarde omgezet naar tekst. Dat onderscheid doet er meer toe dan het op het eerste gezicht lijkt. Twee cellen kunnen hetzelfde weergegeven getal bevatten terwijl de ene een letterlijke waarde is en de andere een formule, en ze als gelijk behandelen zou precies de bewerking verbergen die het meest de moeite waard is om op te vangen in een beoordeelde werkmap

Het betekent ook dat een formule waarvan de tekst ongewijzigd is, geen verschil rapporteert, zelfs als het gecachte resultaat verschilt, wat het juiste gedrag is voor het vergelijken van opgestelde inhoud, en het verkeerde gedrag als u probeert herberekeningsdrift te detecteren. Voor die tweede vraag herberekent u beide werkmappen vóór het vergelijken, zodat de waarden die u vergelijkt de waarden zijn die de formules vandaag daadwerkelijk produceren

Een vergelijking uitvoeren

Compare neemt de twee geladen werkmappen en retourneert het aantal gevonden verschillen. De verschillenlijst is vervolgens beschikbaar op index, of kan naar elke TStrings worden weggeschreven:

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 leesbare regel per verschil
      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;

Een regel geproduceerd door Report luidt bijvoorbeeld value changed: Data!A2: 10 -> 99, wat genoeg is voor een reviewer en genoeg voor een commitbericht. Dat is het mensgerichte oppervlak. Het programmatische oppervlak is het verschilrecord zelf, en dat is het oppervlak om te gebruiken wanneer de vergelijking een beslissing voedt in plaats van een document

Logica aansturen vanuit de gestructureerde verschillen

Elk verschil geeft zijn soort weer, de werkbladnaam, bij-één-beginnende rij en kolom voor items op celniveau, een A1-verwijzing voor items op samenvoegingsniveau, en de linker- en rechtertekst. Items op werkblad- en samenvoegingsniveau rapporteren rij en kolom als nul, en zo onderscheidt u ze zonder de soort te inspecteren:

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;

  // Een beoordelingsbeleid dat alleen blokkeert op formulewijzigingen
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

Twee eigenschappen van de uitvoer zijn het waard om te kennen voordat u er assertions tegen schrijft. De volgorde van items op celniveau volgt de interne doorloopvolgorde van de celopslag, dus tests moeten volgorde-onafhankelijk worden geschreven. En de formulehandtekening draagt zijn eigen voorafgaande isgelijkteken, wat betekent dat een via concatenatie opgebouwde beschrijvingsstring een verdubbelde == kan tonen; controleer de veldwaarden in plaats van de beschrijvende regel te parsen wanneer het resultaat logica aanstuurt

Waar workbook-diffing zich terugbetaalt

Drie toepassingen rechtvaardigen de functie op zichzelf. Regressietests van een rapportgenerator: bewaar een bekend-goede werkmap, genereer opnieuw, vergelijk, en laat de build falen bij elk onverwacht verschil. Wijzigingsbeoordeling: geef een reviewer het leesbare rapport in plaats van twee bestanden. En migratieverificatie: vergelijk na het converteren van een batch legacy-werkmappen elk resultaat met de bron om te bewijzen dat er niets verloren is gegaan

Dat derde geval sluit natuurlijk aan bij de inventarisatie- en auditpassages beschreven in het audit- en conversiewerkbank voor werkmappen, waar het tellen van wat een werkmap bevat vóór de conversie gebeurt en vergelijken erna. Als uw verschillen zich groeperen rond ingevoegde rijen, verklaren de regels voor het herschrijven van verwijzingen in aanpassing van formuleverwijzingen bij invoegen en verwijderen waarom formules die er ongewijzigd uitzien, als gewijzigd worden gerapporteerd

De grenzen, ronduit gesteld

De vergelijking omvat waarden, formules, samenvoegingen en de aanwezigheid van werkbladen. Ze vergelijkt geen getalnotaties, lettertypen, vullingen, regels voor voorwaardelijke opmaak, gegevensvalidaties, grafieken, afbeeldingen of gedefinieerde namen. Een cel waarvan de waarde identiek is maar waarvan de notatie is veranderd van Standaard naar Valuta, rapporteert geen verschil, wat correct is voor een datavergelijking en onvoldoende voor een opmaakbeoordeling

Cellen met datumwaarden verdienen één specifieke waarschuwing: ze worden vergeleken als hun tekstconversie, dus een werkmap opgeslagen volgens het 1904-datumsysteem en een volgens het 1900-systeem kan gelijk of ongelijk uitvallen op manieren die u verrassen als de onderliggende seriële getallen verschillen. De regels voor het datumsysteem worden behandeld in seriële datumgetallen en het 1904-systeem. Wanneer opmaak- of objectgetrouwheid deel uitmaakt van de vraag, combineer de diff dan met een auditpassage die deze functies aan elke kant telt

Werkmapvergelijking, auditing en conversie draaien allemaal op dezelfde engine voor Delphi en C++Builder; de volledige functielijst staat op de HotXLS Delphi-spreadsheetcomponentpagina