مقال تقني

معايير DOPER لـ AutoFilter في BIFF8 مع HotXLS لـ Delphi

يحفظ HotXLS كل معيار AutoFilter في BIFF8 في سجل AUTOFILTER يحمل بنيتي DOPER بحجم 10 بايتات، ونوع DOPER هو من يقرر كيف تجري Excel المقارنة. منذ v2.384.45 يكتب TXLSWorksheet.ApplyAutoFilter مقارنة مثل '>=100' بوصفها DOPER لعدد IEEE، فيطابق Excel الخلايا الرقمية بدل أن يقارن نصًا. تقرير العيب الذي دفع إلى هذا التغيير كان قصيرًا ومليئًا بالاستفزاز: تصدير ليلي يطبق مرشحًا على عمود مبالغ، والملف يفتح دون أي شكوى، وسهم القائمة المنسدلة يُظهر المعيار، ثم يطابق المرشح صفر صف. لا شيء كان تالفًا؛ البايتات كانت BIFF8 صحيحة، من النوع الخطأ من الصحيح تحديدًا، وذلك هو صنف الإخفاق الذي تتجول فيه هذه المقالة، إلى جانب خطأين أقدم على مستوى البايتات صُححا في v2.384.18

ماذا يخزن مرشح AutoFilter في BIFF8 فعليًا؟

مرشح AutoFilter في BIFF8 ليس سجلًا واحدًا بل مجموعة من ثلاثة أنواع سجلات، وسجل الحقل ذاته فقط هو الذي يحمل المعايير. يسجل AUTOFILTERINFO ($009D، [MS-XLS] §2.4.8) عدد الأعمدة التي يغطيها مدى المرشح، وFILTERMODE ($009B) علامة بلا جسم لا يصدرها HotXLS إلا حين يحمل حقل واحد على الأقل معيارًا نشطًا. ثم ينال كل حقل نشط سجل AUTOFILTER خاصًا به ($009E، §2.4.6): فهرس حقل يبدأ من الصفر، وكلمة grbit التي تشغل بتّاها الأدنان قيمة wJoin، وبنيتي DOPER بحجم 10 بايتات بالضبط لكل منهما، وذيل اختياري يحمل محارف أي DOPER نصي. فهرس الحقل يبدأ من الصفر على القرص بينما ترقّم ApplyAutoFilter الحقول اعتبارًا من 1، وهذا يصير مهمًا أول مرة تتعقب فيه سجلًا داخل تفريغ سداسي. أما البايت الأول من كل DOPER، أي vt، فيحدد نوع المعامل الذي يليه:

  • $04 عدد IEEE 754 مضاعف الدقة مخزّن في البايتات الثمانية المتبقية، وهو شكل Excel في تخزين المقارنة الرقمية
  • $06 نصّ يقيده بايت واحد هو cch، وتُدفع المحارف نفسها إلى ذيل السجل
  • $08 قيمة Bes، أي قيمة منطقية أو كود خطأ مضغوط في بايتين
  • $0C و$0E بلا معامل، وتعنيان مطابقة كل الفراغات ومطابقة كل غير الفراغات

البايت الثاني، grbitSgn، يحمل المقارنة: القيم من 1 إلى 6 تقابل < و= و<= و> و<> و>=. يُبقي HotXLS البايتين مرئيين بعد الحدث عبر AutoFilterColumns، التي تكشف عناصرها Criteria1 وCriteria2 ككائنات TXLSAutofilterDOPER تحمل DataType وgrbitSgn وValue، فيمكنك التأكد برمجيًا مما سيُكتب فعلًا بدل التخمين

تشريح سجل AUTOFILTER في HotXLS موضحًا فهرس الحقل الذي يبدأ من الصفر، وكلمة grbit التي تشغل بتّاها الأدنان wJoin، وبنيتي DOPER بحجم 10 بايتات يختار بايت vt فيهما معاملًا من عدد IEEE أو نصًا أو قيمة Bes منطقية أو اختبار فراغ أو غير فراغ، بينما يرمّز grbitSgn عامل المقارنة الذي يطبقه Excel
كل سجل AUTOFILTER يحمل بنيتي DOPER بحجم 10 بايتات، وبايت vt يقرر هل يقارن Excel المعيار بوصفه رقمًا أو نصًا أو قيمة منطقية أو اختبار فراغ — اقرأ البنيتين عبر AutoFilterColumns قبل أن تحفظ

لماذا لم يطابق مرشح '>=100' أي صف في Excel؟

لم يطابق المرشح شيئًا لأن المعامل خُزّن نصًا، وExcel يقارن DOPER النصي بالخلية بوصفها نصًا. قبل v2.384.45 كان CreateFilterDoper في lxFilter.pas يجرد بادئة >= بشكل صحيح ويضبط الإشارة إلى 6، ثم يبني دائمًا DOPER من نوع vtString يحمل المحارف 100. خلية رقمية تحمل 250 لن تحقق مقارنة نصية مع "100" أبدًا، فتسقط كل الصفوف. لا استثناء، ولا تشخيص، ولا مطالبة إصلاح من Excel. القاعدة منذ v2.384.45 ضيقة عن قصد: إن بدأ المعيار بعامل مقارنة وكان الباقي يُفسَّر عددًا وفق قواعد invariant culture، كتب HotXLS DOPER من نوع vtIEEENumber بالإشارة نفسها. أما القيمة المجردة بلا عامل فتبقى بصيغة النص، لأن هذه هي الكيفية التي يخزن بها Excel نفسه عنصرًا انتُقي من القائمة المنسدلة

var
  Wb: IXLSWorkbook;
  Sh: TXLSWorksheet;
  Doper: TXLSAutofilterDOPER;
begin
  Wb := TXLSWorkbook.Create;
  Sh := Wb.Sheets.Add;
  Sh.Cells[1, 1].Value := 'Region';
  Sh.Cells[1, 2].Value := 'Amount';
  Sh.Cells[2, 1].Value := 'North';
  Sh.Cells[2, 2].Value := 250;

  // الحقل 2 = العمود الثاني من A1:B100 (يبدأ من 1 على جانب الـ API)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45 فما فوق: DataType = 4 (عدد IEEE) وgrbitSgn = 6 (>=)
  // قبل الإصلاح: DataType = 6 (نص)، وهو ما لم يطابق شيئًا
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
يكتب HotXLS معيار AutoFilter نفسه >=100 بوصفه DOPER من نوع vtString لا يطابق أي خلية رقمية، أو بوصفه DOPER من نوع vtIEEENumber مع grbitSgn يساوي 6 يقيّمه Excel رقميًا مقابل المبلغ 250، وهو الإخفاق الصامت ذو الصفوف الصفرية الذي صححه CreateFilterDoper في v2.384.45
البايتات كانت صالحة كـ BIFF8 في الحالتين — تغيّر بايت نوع المعامل وحده، ولهذا فتح Excel الملف وأظهر المعيار في القائمة المنسدلة ومع ذلك طابق صفر صف

التحليل هو حيث تبقى الحواف الحادة. يمر المعامل عبر TryStrToFloat والفاصلة العشرية نقطة، فـ '>=1.5' تصير عددًا بينما تبقى '>=1,5' بصيغة DOPER النصية وتفشل في المطابقة بصمت من جديد، أيًا كانت إعدادات الإعدادات المحلية في Windows. التواريخ الفخ نفسه بزي مختلف: '>=2026-01-01' ليست عددًا فتُكتب نصًا، بينما يحفظ Excel خلايا التواريخ أرقامًا تسلسلية. وللمساواة على عدد، تنتج '=100' وVariant رقمي مثل 100 على السواء DOPER من IEEE بإشارة 2، بينما ينتج النص المجرد '100' مطابقة نصية. ابنِ المعاملات الرقمية في الكود بدل تنسيقها للعين البشرية:

var
  Fmt: TFormatSettings;
  Since: TDateTime;
begin
  Fmt := TFormatSettings.Create;
  Fmt.DecimalSeparator := '.';

  // عتبة بكسر عشري: نسّق دائمًا بنقطة
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // التواريخ: قارن بالرقم التسلسلي الذي يخزنه Excel في الخلية.
  // يساوي TDateTime في Delphi الرقم التسلسلي بنظام 1900 للتواريخ بعد مارس 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

كيف تربط AND و OR شرطين معًا؟

بتّا wJoin في grbit الخاص بـ AUTOFILTER يساويان 0 لـ AND و1 لـ OR، وكان HotXLS قد عكس الثابتين حتى v2.384.18. مرشح بنمط between مثل «100 على الأقل وأقل من 500» كان يُحفظ «100 على الأقل أو أقل من 500»، وهو ما يطابق كل عدد عمليًا فيبدو كأن المرشح لم يُطبق أصلًا. ثوابت العوامل العامة تضيف خطر نقل ثانيًا: في HotXLS قيمة xlAnd هي 0 وقيمة xlOr هي 1، بينما ترقّمهما أتمتة Excel بالقيمتين 1 و2. نوع XlAutoFilterOperator مجرد Byte، فالكود المترجم من ماكرو VBA بأرقام حرفية يترجم بنجاح، والرقم الحرفي 1 الذي كان يعني AND تحت COM صار يعني OR هنا. استخدم الثوابت المسماة وياختفي الخطر:

// المبلغ بين 100 (شامل) و500 (غير شامل)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 على القرص
  Assert(Criteria2.grbitSgn = 1);      // 1 = أقل من
end;
تخطيط بت wJoin في HotXLS لمعايير AutoFilter حيث يربط 0 بنيتي DOPER بعلاقة AND ويربطهما 1 بعلاقة OR، مع خط أعداد يوضح كيف وسّعت الثوابت المعكوسة قبل v2.384.18 مرشح between إلى OR يطابق كل شيء، وتعارض ترقيم xlAnd وxlOr مع أتمتة Excel
حوّلت ثوابت wJoin المعكوسة مرشح between إلى مرشح يطابق كل عدد، وكود VBA المترجم يظل يترجم بنجاح لأن XlAutoFilterOperator مجرد Byte — الرقم الحرفي 1 الذي كان يعني AND تحت أتمتة COM يعني OR هنا

القيم المنطقية والفراغات وسقف 255 محرفًا

يخزن المعيار المنطقي قيمة Bes ([MS-XLS] §2.5.10)، وBes يضع بايت القيمة bBoolErr أولًا وعلامة fError ثانيًا. كتبها HotXLS بالترتيب المعاكس قبل v2.384.18، فمرشح TRUE وضع 1 في علامة الخطأ وقرأ Excel المعيار بوصفه كود خطأ. كان الكاتب والقارئ معكوسين معًا، ولهذا دارت ملفات HotXLS ذهابًا وإيابًا دون شكوى بينما اعترض Excel، وهذا تذكير بأن دورة ذهاب وإياب متسقة ذاتيًا لا تثبت شيئًا عن المطابقة للمواصفة. أما الفراغات فلا تحتاج معاملًا أصلًا: تمرير '=' وحدها ينتج DOPER مطابقة كل الفراغات ($0C)، و'<>' وحدها ينتج DOPER مطابقة كل غير الفراغات ($0E)

تصطدم معايير النص بحد صارم في تخطيط DOPER. حقل الطول cch بايت واحد، فمعامل النص لا يمكن أن يتجاوز 255 محرفًا، ويقطع CreateFilterDoper النص الأطول بعد جرد العامل بدل أن يلتف بايت الطول ويفسد تزامن ذيل السجل. القطع صامت، وقد يطابق مرشح على عمود أوصاف طويلة شيئًا مختلفًا عن النص الكامل الذي مررته. وفي BIFF8 يخزن الذيل كل نص كعلم بايت واحد يليه وحدات كود UTF-16، ويجب أن يحسب حجم السجل المعلن تلك البايتات بالضبط، وهو نفس انضباط الحسابات الذي تغطيه مقالة انحراف تعريفات طول سجل BIFF في كاتب XLS بـ Delphi

لماذا يمحو الاستدعاء الثاني لـ ApplyAutoFilter الأول؟

كل استدعاء لـ ApplyAutoFilter يعيد تعريف مدى المرشح كله، فلا ينجو إلا معيار الاستدعاء الأخير. داخليًا يستدعي SetAutoFilter، الذي يمسح كل حقل قبل إعادة بناء المدى، وهذا صحيح لعمود واحد ومُربك لعمودين. لتصفية عدة أعمدة، استدعِ ApplyAutoFilter مرة واحدة لتأسيس المدى والمعيار الأول، ثم أضف البقية عبر AutoFilterColumns.SetFieldCriteria التي تترك المدى والحقول الأخرى وشأنها. كلا المسارين يتجاهل رقم حقل خارج المدى دون رفع استثناء، فتحقق بالقراءة الراجعة، ويستحسن ذلك بعد إعادة فتح الملف المحفوظ:

Sh.ApplyAutoFilter('A1:D500', 1, 'North');                     // المدى + الحقل 1
Sh.AutoFilterColumns.SetFieldCriteria(3, '>=100', xlAnd, Unassigned);
Sh.AutoFilterColumns.SetFieldCriteria(4, True, xlAnd, Unassigned);
Wb.SaveAs('orders.xls');

Wb := TXLSWorkbook.Create;
Wb.Open('orders.xls');
Assert(Wb.Sheets[1].AutoFilterColumns.Find(1).Active);
Assert(Wb.Sheets[1].AutoFilterColumns.Find(3).Criteria1.DataType = 4);

تذكر أن سجل AUTOFILTER تعريف مخزّن: يكتب HotXLS المعايير ولا يقيّمها على ورقة XLS الكلاسيكية، فأي مسار معالجة يحتاج الصفوف المطابقة على الخادم عليه أن يحسبها بنفسه هناك، بينما توفر واجهة XLSX تقييمًا على مستوى الصف كما في التحقق من البيانات وAutoFilter والجداول في HotXLS بـ Delphi. وبمجرد أن يخفي Excel الصفوف، تعتمد أي إجماليات أسفل المدى على كيفية تعامل SUBTOTAL وAGGREGATE مع الصفوف المخفية والمصفاة، وهي المحطة التالية التي يظهر فيها مرشح رقمي يطابق لا شيء بصمت على هيئة عدد خاطئ

يقرأ HotXLS ويكتب مصنفات BIFF8 XLS وXLSX أصلًا من Delphi وC++Builder، بما في ذلك معايير AutoFilter بعددية ومنطقية وبوصلات AND/OR يقيّمها Excel كما قصدت. راجع مكوّن HotXLS لجداول Delphi للميزات والإصدارات وتنزيل النسخة التجريبية