يقارن 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، وهو كافٍ لمراجع بشري وكافٍ لرسالة commit. وهذا هو السطح المواجه للبشر. أما السطح البرمجي فهو سجل الفرق نفسه، وهو ما يجب استخدامه حين تغذي المقارنة قرارًا لا مستندًا
قيادة المنطق من الفروق البنيوية
يكشف كل فرق عن نوعه، واسم الورقة، والصف والعمود بترقيم يبدأ من واحد للمُدخَلات على مستوى الخلية، ومرجع 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