Odborný článok

Porovnanie dvoch zošitov Excel v Delphi pomocou HotXLS

HotXLS porovnáva dva zošity prostredníctvom TXLSXWorkbookCompare, ktorý spáruje hárky podľa názvu, prejde obsadené bunky každej dvojice a nahlási, čo sa líši, ako štruktúrovaný zoznam záznamov rozdielov a na požiadanie aj ako jeden čitateľný riadok na rozdiel. Nie je potrebná žiadna inštalácia Excelu a porovnanie beží úplne nad načítaným objektovým modelom v Delphi alebo C++Builder

Táto potreba sa zvyčajne objaví, keď sa niekto prvýkrát opýta, čo sa zmenilo. Finančný zošit sa vráti z revízie, nočný export sa po zmene kódu znovu vygeneruje, alebo dve oddelenia pošlú verzie tej istej šablóny. Otvorenie oboch vedľa seba funguje pri jednom hárku a zlyháva pri dvadsiatich. Porovnávanie súborov bajt po bajte neodpovie vôbec na nič, pretože dve uloženia toho istého zošita sa líšia spôsobmi, o ktoré sa nikto nezaujíma

Čo sa počíta ako rozdiel?

Porovnanie hlási osem druhov rozdielov a táto množina je zámerne malá: pridaný alebo odstránený hárok, pridaná alebo odstránená obsadená bunka, bunka, ktorej hodnota sa zmenila, bunka, ktorej vzorec sa zmenil, a pridaný alebo odstránený zlúčený rozsah. Všetko sa vyjadruje voči ľavému zošitu ako základnej línii, takže pridaná položka existuje iba na pravej strane a odstránená iba na ľavej

Hárky sa spárujú podľa názvu, nie podľa pozície. Zmena poradia hárkov preto neprodukuje žiadne rozdiely, čo je takmer vždy želané správanie: presunutie záložky používateľom nie je zmena dát. Hárok prítomný iba na jednej strane nahlási jeden záznam na úrovni hárka namiesto toho, aby rozvinul každú obsadenú bunku vo vnútri, čím zostáva report dvoch štruktúrne odlišných zošitov čitateľný namiesto toho, aby mal tisíce riadkov

Hodnota alebo vzorec, a ako sa každý porovnáva

Každá bunka prispieva podpisom a pravidlo je jednoduché: bunka nesúca vzorec sa porovnáva podľa textu svojho vzorca s úvodným znamienkom rovnosti, a bunka bez vzorca sa porovnáva podľa svojej hodnoty prevedenej na text. Tento rozdiel je dôležitejší, než sa na prvý pohľad zdá. Dve bunky môžu zobrazovať to isté číslo, pričom jedna je literál a druhá vzorec, a považovať ich za rovnaké by ukrylo presne tú úpravu, ktorú sa v recenzovanom zošite najviac oplatí zachytiť

Znamená to tiež, že vzorec, ktorého text sa nezmenil, nehlási žiadny rozdiel, aj keď sa líši jeho vyrovnávaný výsledok, čo je správne správanie pri porovnávaní autorského obsahu a nesprávne správanie, ak sa snažíte odhaliť odchýlku pri prepočte. Pri druhej otázke oba zošity pred porovnaním prepočítajte, aby hodnoty, ktoré porovnávate, boli tie, ktoré vzorce dnes skutočne produkujú

Spustenie porovnania

Compare prijíma dva načítané zošity a vracia počet nájdených rozdielov. Zoznam rozdielov je potom dostupný podľa indexu, alebo sa dá vypísať do ľubovoľného 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);              // jeden čitateľný riadok na rozdiel
      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;

Riadok vytvorený funkciou Report znie ako value changed: Data!A2: 10 -> 99, čo stačí pre recenzenta aj pre commit message. Toto je rozhranie orientované na človeka. Programové rozhranie je samotný záznam rozdielu a práve ten treba použiť, keď porovnanie zásobuje rozhodnutie, nie dokument

Riadenie logiky zo štruktúrovaných rozdielov

Každý rozdiel odhaľuje svoj druh, názov hárka, riadok a stĺpec indexovaný od jedna pri záznamoch na úrovni buniek, referenciu A1 pri záznamoch na úrovni zlúčenia a ľavý a pravý text. Záznamy na úrovni hárka a zlúčenia hlásia riadok aj stĺpec ako nulu, čím ich viete rozlíšiť bez skúmania druhu:

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;

  // Politika revízie, ktorá blokuje iba pri úpravách vzorcov
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

Pred písaním asertov voči výstupu stojí za to poznať dve jeho vlastnosti. Poradie záznamov na úrovni buniek sleduje interné poradie prechodu úložiska buniek, takže testy by mali byť napísané nezávisle od poradia. A podpis vzorca nesie vlastné úvodné znamienko rovnosti, čo znamená, že popisný reťazec zostavený zreťazením môže zobraziť zdvojené ==; keď výsledok riadi logiku, kontrolujte hodnoty polí namiesto parsovania popisného riadka

Kde sa porovnávanie zošitov oplatí

Túto funkciu samostatne odôvodňujú tri prípady použitia. Regresné testovanie generátora zostáv: uchovávajte overený zošit, znovu vygenerujte, porovnajte a pri akomkoľvek neočakávanom rozdiele nechajte zlyhať zostavenie. Revízia zmien: recenzentovi odovzdajte čitateľný report namiesto dvoch súborov. A overenie migrácie: po konverzii dávky starších zošitov porovnajte každý výsledok s jeho zdrojom, aby ste dokázali, že sa nič nestratilo

Tento tretí prípad sa prirodzene spája s priechodmi inventarizácie a auditu opísanými v časti nástroj na audit a konverziu zošitov, kde sa počítanie obsahu zošita deje pred konverziou a porovnanie po nej. Ak sa vaše rozdiely zhlukujú okolo vložených riadkov, pravidlá prepisovania referencií v časti úprava referencií vzorcov pri vkladaní a mazaní vysvetľujú, prečo sa vzorce, ktoré vyzerajú nezmenené, hlásia ako zmenené

Limity, povedané na rovinu

Porovnanie pokrýva hodnoty, vzorce, zlúčenia a prítomnosť hárkov. Neporovnáva formáty čísel, písma, výplne, pravidlá podmieneného formátovania, overenia dát, grafy, obrázky ani definované názvy. Bunka, ktorej hodnota je identická, no formát sa zmenil zo Všeobecný na Mena, nehlási žiadny rozdiel, čo je správne pre porovnanie dát a nedostatočné pre revíziu formátovania

Bunky s dátumovou hodnotou si zaslúžia jedno konkrétne upozornenie: porovnávajú sa podľa svojho prevodu na text, takže zošit uložený v dátumovom systéme 1904 a zošit v systéme 1900 sa môžu porovnávať ako rovnaké alebo odlišné spôsobmi, ktoré vás prekvapia, ak sa podkladové poradové čísla líšia. Pravidlá dátumového systému opisuje časť poradové čísla dátumov a systém 1904. Keď je súčasťou otázky aj vernosť formátovania alebo objektov, skombinujte diff s priechodom auditu, ktorý tieto funkcie počíta na oboch stranách

Porovnávanie zošitov, audit a konverzia bežia na tom istom jadre pre Delphi a C++Builder; kompletný zoznam funkcií nájdete na stránke HotXLS Delphi spreadsheet component