Artigo Técnico

Comparar duas pastas de trabalho do Excel no Delphi com o HotXLS

O HotXLS compara duas pastas de trabalho por meio de TXLSXWorkbookCompare, que pareia planilhas pelo nome, percorre as células preenchidas de cada par e relata o que difere como uma lista estruturada de registros de diferença e, quando solicitado, como uma linha legível por diferença. Nenhuma instalação do Excel é necessária, e a comparação roda inteiramente sobre o modelo de objeto carregado no Delphi ou no C++Builder

A necessidade costuma aparecer na primeira vez que alguém pergunta o que mudou. Uma pasta de trabalho financeira volta de uma revisão, uma exportação noturna é regenerada após uma mudança de código, ou dois departamentos enviam versões do mesmo template. Abrir as duas lado a lado funciona para uma planilha e falha para vinte. Comparar arquivos byte a byte não responde nada, porque dois salvamentos da mesma pasta de trabalho diferem de maneiras que ninguém se importa

O que conta como diferença?

A comparação relata oito tipos, e o conjunto é deliberadamente pequeno: uma planilha adicionada ou removida, uma célula preenchida adicionada ou removida, uma célula cujo valor mudou, uma célula cuja fórmula mudou, e um intervalo mesclado adicionado ou removido. Tudo é expresso em relação à pasta de trabalho da esquerda como referência, então um item adicionado existe apenas à direita e um item removido apenas à esquerda

As planilhas são pareadas pelo nome, não pela posição. Reordenar planilhas, portanto, não produz nenhuma diferença, o que é quase sempre o comportamento desejado: um usuário arrastando uma aba não é uma mudança de dados. Uma planilha presente apenas em um dos lados relata uma única entrada em nível de planilha, em vez de expandir cada célula preenchida dentro dela, o que mantém legível o relatório de duas pastas de trabalho estruturalmente diferentes, em vez de deixá-lo com milhares de linhas

Valor ou fórmula, e como cada um é comparado

Cada célula contribui com uma assinatura, e a regra é simples: uma célula que carrega uma fórmula é comparada pelo seu texto de fórmula com um sinal de igual à frente, e uma célula sem fórmula é comparada pelo seu valor convertido em texto. Essa distinção importa mais do que parece à primeira vista. Duas células podem exibir o mesmo número na tela enquanto uma é um literal e a outra é uma fórmula, e tratá-las como iguais esconderia exatamente a edição mais importante de capturar em uma pasta de trabalho revisada

Isso também significa que uma fórmula cujo texto não mudou não relata diferença, mesmo que seu resultado em cache seja diferente, o que é o comportamento correto para comparar conteúdo autoral, e o comportamento errado se você está tentando detectar desvio de recálculo. Para essa segunda pergunta, recalcule as duas pastas de trabalho antes de comparar, de modo que os valores comparados sejam os que as fórmulas realmente produzem hoje

Executando uma comparação

Compare recebe as duas pastas de trabalho carregadas e retorna o número de diferenças encontradas. A lista de diferenças fica então disponível por índice, ou pode ser despejada em qualquer 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);              // uma linha legível por diferença
      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;

Uma linha produzida por Report se lê como value changed: Data!A2: 10 -> 99, o que é suficiente para um revisor e suficiente para uma mensagem de commit. Essa é a superfície voltada para humanos. A superfície programática é o próprio registro de diferença, e é ela que se deve usar quando a comparação alimenta uma decisão em vez de um documento

Direcionando a lógica a partir das diferenças estruturadas

Cada diferença expõe seu tipo, o nome da planilha, linha e coluna baseadas em um para entradas em nível de célula, uma referência A1 para entradas em nível de mesclagem, e o texto da esquerda e da direita. Entradas em nível de planilha e em nível de mesclagem relatam linha e coluna como zero, o que é como você as distingue sem inspecionar o 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;

  // Uma política de revisão que só bloqueia em edições de fórmula
  if FormulaEdits > 0 then
    raise Exception.CreateFmt(
      '%d formula change(s) need sign-off', [FormulaEdits]);
end;

Duas propriedades da saída valem a pena conhecer antes de escrever asserções contra ela. A ordem das entradas em nível de célula segue a ordem de travessia interna do armazenamento de células, então os testes devem ser escritos de forma independente da ordem. E a assinatura de fórmula carrega seu próprio sinal de igual à frente, o que significa que uma string de descrição construída por concatenação pode exibir um == duplicado; verifique os valores dos campos em vez de analisar a linha descritiva quando o resultado direciona lógica

Onde a comparação de pastas de trabalho compensa

Três usos justificam o recurso por si só. Teste de regressão de um gerador de relatórios: mantenha uma pasta de trabalho conhecidamente correta, regenere, compare, e falhe o build em qualquer diferença inesperada. Revisão de mudanças: entregue ao revisor o relatório legível em vez de dois arquivos. E verificação de migração: depois de converter um lote de pastas de trabalho legadas, compare cada resultado com sua origem para provar que nada se perdeu

Esse terceiro caso combina naturalmente com as passagens de inventário e auditoria descritas em o workbench de auditoria e conversão de pastas de trabalho, onde contar o que uma pasta de trabalho contém acontece antes da conversão e a comparação acontece depois. Se suas diferenças se concentram em linhas inseridas, as regras de reescrita de referências em ajuste de referências de fórmula em inserção e exclusão explicam por que fórmulas que parecem inalteradas relatam como alteradas

Os limites, ditos com clareza

A comparação cobre valores, fórmulas, mesclagens e presença de planilhas. Ela não compara formatos numéricos, fontes, preenchimentos, regras de formatação condicional, validações de dados, gráficos, imagens ou nomes definidos. Uma célula cujo valor é idêntico, mas cujo formato mudou de Geral para Moeda, não relata diferença, o que é correto para uma comparação de dados e insuficiente para uma revisão de formatação

Células com valor de data merecem uma ressalva específica: elas são comparadas pela sua conversão em texto, então uma pasta de trabalho armazenada no sistema de datas 1904 e uma no sistema 1900 podem comparar como iguais ou diferentes de formas que surpreendem, se os números de série subjacentes diferirem. As regras do sistema de datas são abordadas em números de série de data e o sistema 1904. Quando a fidelidade de formatação ou de nível de objeto faz parte da pergunta, combine o diff com uma passagem de auditoria que conte esses recursos em cada lado

Comparação, auditoria e conversão de pastas de trabalho rodam sobre o mesmo mecanismo para Delphi e C++Builder; a lista completa de recursos está na página do componente de planilhas para Delphi HotXLS