HotXLS שומרת כל קריטריון AutoFilter של BIFF8 כרשומת AUTOFILTER שנושאת שני מבני DOPER בני 10 בייטים, וסוג ה-DOPER הוא שקובע איך Excel משווה. החל מ-v2.384.45, TXLSWorksheet.ApplyAutoFilter כותב השוואה כמו '>=100' כ-DOPER של מספר IEEE, כך ש-Excel מוצא תאים מספריים במקום להשוות טקסט. דיווח הבאג שהניע את השינוי היה קצר ומתסכל: ייצוא לילי הפעיל מסנן על עמודת סכומים, הקובץ נפתח בלי תלונה, חץ ה-dropdown הציג את הקריטריון, והמסנן לא מצא אף שורה. שום דבר לא היה פגום. הבייטים היו 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, שני DOPERs של 10 בייטים מדויקים כל אחד, וזנב אופציונלי שמחזיק את התווים של כל DOPER מחרוזתי. אינדקס השדה מבוסס-אפס על הדיסק למרות ש-ApplyAutoFilter ממספרת שדות מ-1, וזה משנה בפעם הראשונה שאתה יוצא לצוד רשומה ב-hex dump. הבייט הראשון של כל DOPER, vt, אומר איזה סוג אופרנד בא אחריו:
-
$04הוא מספר double של IEEE 754 הנשמר ב-8 הבייטים הנותרים, וזו הדרך שבה Excel מאחסן השוואה מספרית -
$06הוא מחרוזת שאורכה נתון בבייטcchבודד, והתווים עצמם נדחפים לזנב הרשומה -
$08הוא ערך Bes, בוליאן או קוד שגיאה ארוזים בשני בייטים -
$0Cו-$0Eלא נושאים אופרנד ומשמעותם התאמת כל הריקים והתאמת כל הלא-ריקים
הבייט השני, grbitSgn, מחזיק את ההשוואה: 1 עד 6 ממופים ל-<, =, <=, >, <> ו->=. HotXLS משאירה את שני הבייטים נגישים גם אחרי העובדה דרך AutoFilterColumns, שהפריטים שלה חושפים את Criteria1 ו-Criteria2 כאובייקטי TXLSAutofilterDOPER עם DataType, grbitSgn ו-Value, כך שאפשר להריץ assert על מה שייכתב במקום לנחש
למה מסנן '>=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 עצמו מאחסן פריט שנבחר מרשימת ה-dropdown
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;
הפרסור הוא המקום שבו מסתתרות שאר הפינות החדות. האופרנד עובר דרך TryStrToFloat עם נקודה כמפריד עשרוני, כך ש-'>=1.5' הופך למספר ו-'>=1,5' נשאר DOPER מחרוזתי ושוב לא מוצא כלום בשקט, מה שלא יהיה ה-locale של 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 של דלפי שווה למספר הסידורי של מערכת 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 automation ממספר אותם 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;
בוליאנים, ריקים ותקרת 255 התווים
קריטריון בוליאני נשמר כערך Bes ([MS-XLS] §2.5.10), ו-Bes שם את בייט הערך bBoolErr ראשון ואת הדגל fError שני. HotXLS כתבה אותם בסדר הפוך לפני v2.384.18, כך שמסנן עבור TRUE שם 1 אל דגל השגיאה ו-Excel קרא את הקריטריון כקוד שגיאה. הכותב והקורא הוחלפו יחד, ולכן HotXLS עשתה round trip לקבצים שלה עצמה בלי תלונה בזמן ש-Excel חלק, תזכורת לכך ש-round trip עקבי עם עצמו לא מוכיח דבר על התאמה למפרט. ריקים לא צריכים אופרנד בכלל: העברת '=' לבדה מניבה DOPER של התאמת-כל-הריקים ($0C) ו-'<>' לבדו DOPER של התאמת-כל-הלא-ריקים ($0E)
קריטריונים מחרוזתיים פוגעים בתקרה קשה בפריסת ה-DOPER. שדה האורך cch הוא בייט בודד, כך שאופרנד מחרוזתי לא יכול לעבור 255 תווים, ו-CreateFilterDoper קוטע טקסט ארוך יותר אחרי קילוף האופרטור במקום לתת לבייט האורך להתגלגל ולנתק את הסנכרון עם זנב הרשומה. הקטיעה שקטה, ומסנן על עמודת תיאור ארוכה עשוי להתאים אחרת מהטקסט המלא שהעברת. ב-BIFF8 הזנב מאחסן כל מחרוזת כדגל של בייט אחד ואחריו יחידות קוד UTF-16, וגודל הרשומה המוכרז חייב לספור את הבייטים האלה במדויק — אותה משמעת ניהול ספרים שמכוסה באיך הצהרות אורך רשומות BIFF סוטות בכותב XLS לדלפי
למה קריאת 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 הקלאסי, כך ש-pipeline שצריך את השורות התואמות על השרת חייב לחשב אותן בעצמו שם, בזמן שחזית ה-XLSX מציעה הערכה ברמת שורה כפי שמוצג באימות נתונים, AutoFilter וטבלאות ב-HotXLS לדלפי. ברגע ש-Excel כן מסתיר שורות, כל סיכום מתחת לטווח תלוי באיך SUBTOTAL ו-AGGREGATE מתייחסים לשורות נסתרות ומסוננות, שהוא המקום הבא שבו מסנן מספרי שמוצא כלום בשקט מתגלה כמספר שגוי
HotXLS קוראת וכותבת חוברות XLS של BIFF8 ו-XLSX נייטיב מתוך Delphi ו-C++Builder, כולל קריטריוני AutoFilter עם DOPERs מספריים, בוליאניים ו-AND/OR ש-Excel מעריך כמתוכנן. ראו את רכיב הגיליונות של HotXLS לדלפי לרשימת יכולות, מהדורות והורדת גרסת ניסיון