HotXLS порівнює дві книги через TXLSXWorkbookCompare, який зіставляє аркуші за іменем, обходить заповнені клітинки кожної пари й повідомляє про різниці як структурований список записів різниць та, за запитом, як один читабельний рядок на кожну різницю. Жодна інсталяція Excel не задіяна, і порівняння цілком виконується на завантаженій об'єктній моделі в Delphi чи C++Builder
Потреба зазвичай з'являється, коли хтось уперше запитує, що змінилося. Фінансова книга повертається з рецензування, нічний експорт перегенерується після зміни коду, або два відділи надсилають версії того самого шаблону. Відкриття обох поруч працює для одного аркуша й провалюється для двадцяти. Побайтове порівняння файлів не відповідає взагалі нічого, оскільки два збереження однієї книги відрізняються способами, які нікого не цікавлять
Що вважається різницею?
Порівняння повідомляє вісім видів, і цей набір навмисно невеликий: аркуш додано чи видалено, заповнену клітинку додано чи видалено, клітинку, значення якої змінилося, клітинку, формула якої змінилася, та об'єднаний діапазон додано чи видалено. Усе виражається відносно лівої книги як базової лінії, тож доданий елемент існує лише праворуч, а видалений — лише ліворуч
Аркуші зіставляються за іменем, а не за позицією. Тому зміна порядку аркушів взагалі не дає жодних різниць, і це майже завжди та поведінка, якої ви хочете: користувач, що перетягує вкладку, — це не зміна даних. Аркуш, присутній лише на одній стороні, повідомляється як один запис рівня аркуша, а не розгортається на кожну заповнену клітинку всередині нього, що зберігає звіт про дві структурно різні книги читабельним, а не на тисячі рядків завдовжки
Значення чи формула, і як кожне з них порівнюється
Кожна клітинка вносить свій підпис, і правило просте: клітинка з формулою порівнюється за текстом формули з початковим знаком рівності, а клітинка без формули порівнюється за своїм значенням, перетвореним на текст. Ця відмінність важливіша, ніж здається на перший погляд. Дві клітинки можуть містити те саме відображуване число, тоді як одна — літерал, а інша — формула, і трактування їх як однакових приховало б саме ту правку, яку найбільше варто виловити в перевіреній книзі
Це також означає, що формула, чий текст не змінився, не повідомляє різниці, навіть якщо її кешований результат відрізняється, — і це правильна поведінка для порівняння авторського вмісту, і неправильна поведінка, якщо ви намагаєтеся виявити дрейф перерахунку. Для цього другого питання перерахуйте обидві книги перед порівнянням, щоб значення, які ви порівнюєте, були тими, що формули справді видають сьогодні
Виконання порівняння
Compare приймає дві завантажені книги й повертає кількість знайдених різниць. Список різниць після цього доступний за індексом або може бути вивантажений у будь-який 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); // один читабельний рядок на кожну різницю
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;
Рядок, створений Report, читається як value changed: Data!A2: 10 -> 99, чого достатньо для рецензента й достатньо для повідомлення коміту. Це поверхня, орієнтована на людину. Програмна поверхня — це сам запис різниці, і саме його слід використовувати, коли порівняння живить рішення, а не документ
Керування логікою за структурованими різницями
Кожна різниця виявляє свій вид, назву аркуша, рядок і стовпець з відліком від одиниці для записів рівня клітинки, посилання A1 для записів рівня об'єднання, а також лівий і правий текст. Записи рівня аркуша й рівня об'єднання повідомляють рядок і стовпець як нуль — так їх можна відрізнити без огляду виду:
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;
// Політика рецензування, що блокує лише на правках формул
if FormulaEdits > 0 then
raise Exception.CreateFmt(
'%d formula change(s) need sign-off', [FormulaEdits]);
end;
Два властивості виводу варто знати перед написанням перевірок проти нього. Порядок записів рівня клітинки слідує внутрішньому порядку обходу сховища клітинок, тож тести слід писати незалежно від порядку. А підпис формули несе власний початковий знак рівності, тож рядок опису, побудований конкатенацією, може показати подвоєне ==; перевіряйте значення полів, а не розбирайте описовий рядок, коли результат керує логікою
Де порівняння книг окупається
Три застосування самі по собі виправдовують цю можливість. Регресійне тестування генератора звітів: тримайте перевірено правильну книгу, перегенеруйте, порівняйте й провалюйте збірку на будь-якій неочікуваній різниці. Рецензування змін: дайте рецензенту читабельний звіт замість двох файлів. І перевірка міграції: після конвертації пакету застарілих книг порівняйте кожен результат з його джерелом, щоб довести, що нічого не втрачено
Цей третій випадок природно поєднується з проходами інвентаризації та аудиту, описаними у статті верстак аудиту та конвертації книг, де підрахунок вмісту книги відбувається до конвертації, а порівняння — після. Якщо ваші різниці скупчуються навколо вставлених рядків, правила переписування посилань у статті коригування посилань формул при вставці й видаленні пояснюють, чому формули, що виглядають незмінними, повідомляються як змінені
Обмеження, викладені прямо
Порівняння охоплює значення, формули, об'єднання та наявність аркушів. Воно не порівнює числові формати, шрифти, заливки, правила умовного форматування, перевірку даних, діаграми, зображення чи іменовані діапазони. Клітинка, значення якої ідентичне, але формат якої змінився з General на Currency, не повідомляє різниці, що правильно для порівняння даних і недостатньо для рецензування форматування
Клітинки зі значеннями дат заслуговують на одне специфічне застереження: вони порівнюються за своїм текстовим перетворенням, тож книга, збережена в системі дат 1904, і книга в системі 1900 можуть порівнюватися як однакові чи різні способами, що вас здивують, якщо базові порядкові номери відрізняються. Правила системи дат описано у статті порядкові номери дат і система 1904. Коли частиною питання є точність форматування чи об'єктного рівня, поєднайте порівняння з проходом аудиту, що підраховує ці можливості на кожній стороні
Порівняння книг, аудит і конвертація — усе це працює на тому самому рушії для Delphi та C++Builder; повний перелік можливостей на сторінці компонента електронних таблиць HotXLS для Delphi