Műszaki cikk

Két Excel-munkafüzet összehasonlítása Delphiben a HotXLS-szel

A HotXLS a TXLSXWorkbookCompare-en keresztül hasonlít össze két munkafüzetet, amely név szerint párosítja a munkalapokat, végigjárja minden pár feltöltött celláit, és eltérésrekordok strukturált listájaként, kérésre pedig eltérésenként egy olvasható sorként jelenti, mi tér el. Nincs szükség Excel-telepítésre, az összehasonlítás pedig teljes egészében a betöltött objektummodellen fut Delphiben vagy C++Builderben

Az igény általában akkor jelentkezik először, amikor valaki megkérdezi, mi változott. Egy pénzügyi munkafüzet visszaérkezik ellenőrzésről, egy éjszakai export egy kódváltoztatás után újragenerálódik, vagy két osztály ugyanannak a sablonnak eltérő verzióit küldi. A két fájl egymás mellett történő megnyitása egy munkalapnál működik, húsznál viszont csődöt mond. A fájlok bájtonkénti összehasonlítása semmit nem válaszol meg, mert ugyanannak a munkafüzetnek két mentése olyan módokon tér el, amelyekkel senki sem törődik

Mi számít eltérésnek?

Az összehasonlítás nyolc fajtát jelent, és a készlet szándékosan kicsi: hozzáadott vagy eltávolított munkalap, hozzáadott vagy eltávolított feltöltött cella, megváltozott értékű cella, megváltozott képletű cella, valamint hozzáadott vagy eltávolított egyesített tartomány. Minden a bal oldali munkafüzethez van kifejezve alapvonalként, így egy hozzáadott elem csak a jobb oldalon létezik, egy eltávolított elem pedig csak a bal oldalon

A munkalapok név szerint, nem pozíció szerint párosodnak. A munkalapok átrendezése ezért egyáltalán nem eredményez eltérést, ami szinte mindig a kívánt viselkedés: egy fülét húzgáló felhasználó nem adatváltozás. Egy csak az egyik oldalon jelen lévő munkalap egyetlen munkalapszintű bejegyzést jelent, ahelyett hogy kibontaná a benne lévő minden feltöltött cellát, ami olvashatóvá teszi két strukturálisan eltérő munkafüzet jelentését, ahelyett hogy több ezer sor hosszú lenne

Érték vagy képlet, és hogyan hasonlítja össze mindkettőt

Minden cella egy aláírással járul hozzá, a szabály pedig egyszerű: egy képletet hordozó cella a képletszövegét hasonlítja össze egy elöl álló egyenlőségjellel, egy anélküli cella pedig a szöveggé alakított értékét. Ez a megkülönböztetés jobban számít, mint elsőre tűnik. Két cella tarthatja ugyanazt a megjelenített számot, miközben az egyik literál, a másik pedig képlet, és egyenlőként kezelésük pontosan azt a szerkesztést rejtené el, amelyet a leginkább érdemes elkapni egy ellenőrzött munkafüzetben

Ez azt is jelenti, hogy egy változatlan szövegű képlet nem jelent eltérést akkor sem, ha a gyorsítótárazott eredménye eltér, ami helyes viselkedés a szerkesztett tartalom összehasonlításához, és helytelen, ha újraszámolási eltérést próbálsz felderíteni. Ehhez a második kérdéshez számold újra mindkét munkafüzetet összehasonlítás előtt, így azok az értékek kerülnek összehasonlításra, amelyeket a képletek ma valóban előállítanak

Egy összehasonlítás futtatása

A Compare a két betöltött munkafüzetet várja, és a talált eltérések számával tér vissza. Az eltéréslista ezután index szerint elérhető, vagy bármilyen TStrings-ba kiüríthető:

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);              // eltérésenként egy olvasható sor
      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;

A Report által előállított sor így néz ki: value changed: Data!A2: 10 -> 99, ami elég egy ellenőrnek, és elég egy commit üzenethez. Ez az emberek felé mutatott felület. A programozott felület maga az eltérésrekord, és ezt kell használni, amikor az összehasonlítás egy döntést táplál, nem egy dokumentumot

Logika vezérlése a strukturált eltérésekből

Minden eltérés kiteszi a fajtáját, a munkalap nevét, cellaszintű bejegyzéseknél az egytől induló sort és oszlopot, egyesítésszintű bejegyzéseknél egy A1-hivatkozást, valamint a bal és jobb oldali szöveget. A munkalapszintű és egyesítésszintű bejegyzések nullaként jelentik a sort és az oszlopot, így ezek alapján lehet őket megkülönböztetni a fajta vizsgálata nélkül:

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;

  // Egy felülvizsgálati szabály, amely csak a képletszerkesztéseknél blokkol
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

A kimenet két tulajdonságát érdemes ismerni, mielőtt állításokat írnál rá. A cellaszintű bejegyzések sorrendje a cellatároló belső bejárási sorrendjét követi, ezért a teszteket sorrendfüggetlenül kell megírni. A képlet-aláírás pedig saját elöl álló egyenlőségjelet hordoz, ami azt jelenti, hogy egy összefűzéssel felépített leíró string duplikált ==-t mutathat; ha az eredmény logikát vezérel, a mezőértékeket ellenőrizd, ne a leíró sort elemezd

Hol térül meg a munkafüzet-összehasonlítás?

Három felhasználás önmagában is indokolja a funkciót. Egy jelentéskészítő regressziós tesztelése: tarts meg egy ismerten jó munkafüzetet, generáld újra, hasonlítsd össze, és buktasd meg a buildet bármilyen váratlan eltérésnél. Változtatás-felülvizsgálat: adj át egy ellenőrnek olvasható jelentést két fájl helyett. És migrációellenőrzés: egy köteg örökölt munkafüzet konvertálása után hasonlítsd össze mindegyik eredményt a forrásával, hogy bizonyítsd, semmi nem veszett el

Ez a harmadik eset természetesen párosul a munkafüzet-audit és konverziós munkapadban leírt leltár- és auditfolyamatokkal, ahol a munkafüzet tartalmának megszámlálása a konverzió előtt, az összehasonlítás pedig utána történik. Ha az eltéréseid beszúrt sorok köré csoportosulnak, a képlethivatkozások módosítása beszúráskor és törléskor szereplő hivatkozás-átíró szabályok elmagyarázzák, miért jelentkeznek megváltozottként a változatlannak tűnő képletek

A korlátok, egyértelműen kimondva

Az összehasonlítás lefedi az értékeket, képleteket, egyesítéseket és a munkalapok jelenlétét. Nem hasonlítja össze a számformátumokat, betűtípusokat, kitöltéseket, feltételes formázási szabályokat, adatérvényesítéseket, diagramokat, képeket vagy elnevezett tartományokat. Egy azonos értékű, de Általánosról Pénznemre változtatott formátumú cella nem jelent eltérést, ami helyes egy adatösszehasonlításhoz, és elégtelen egy formázási felülvizsgálathoz

A dátumértékű cellák egy konkrét óvatosságot érdemelnek: szöveges átalakításukként hasonlítódnak össze, így egy 1904-es dátumrendszerben tárolt munkafüzet és egy 1900-as rendszerben tárolt munkafüzet meglepő módon egyezőnek vagy eltérőnek bizonyulhat, ha a mögöttes sorszámok eltérnek. A dátumrendszer-szabályokat a dátum-sorszámok és az 1904-es rendszer ismerteti. Ha a formázás vagy az objektumszintű hűség is a kérdés része, kombináld az összehasonlítást egy auditfolyamattal, amely mindkét oldalon megszámolja ezeket a funkciókat

A munkafüzet-összehasonlítás, az audit és a konverzió mind ugyanazon a motoron fut Delphihez és C++Builderhez; a teljes funkciólista a HotXLS Delphi táblázatkezelő-komponens oldalán található