A HotXLS minden BIFF8 AutoFilter feltételt AUTOFILTER rekordként ment, amely két 10 bájtos DOPER struktúrát hordoz, és a DOPER típusa dönti el, hogyan hasonlít az Excel. A v2.384.45 óta a TXLSWorksheet.ApplyAutoFilter egy olyan összehasonlítást, mint a '>=100', IEEE szám DOPERként ír, így az Excel numerikus cellákat talál el szöveg-összehasonlítás helyett. A változást kiváltó hibajelentés rövid volt és idegőrlő: egy éjszakai export szűrt egy összegoszlopra, a fájl panasz nélkül megnyílt, a legördülő nyíl megmutatta a feltételt, és a szűrő nulla sort talált el. Semmi nem sérült. A bájtok érvényes BIFF8 voltak, csakhogy rossz fajta érvényesek, és pontosan erre a hibafajtára tér ki ez a cikk, a v2.384.18-ban javított két régebbi bájtszintű hibával együtt
Mit is tárol valójában egy BIFF8 AutoFilter?
A BIFF8 AutoFilter három rekordtípus halmaza, nem egy, és csak a mezőnkénti rekord hordoz feltételeket. Az AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) rögzíti, hány oszlopot fed le a szűrőtartomány. A FILTERMODE ($009B) törzs nélküli jelző, amelyet a HotXLS csak akkor bocsát ki, ha legalább egy mezőn él feltétel. Minden aktív mező pedig saját AUTOFILTER rekordot kap ($009E, §2.4.6): nullaalapú mezőindexet, egy grbit szót, amelynek alsó két bitje a wJoin, pontosan 10-10 bájtos két DOPER struktúrát, valamint egy opcionális farkat, amely az esetleges szöveg DOPER karaktereit hordozza. A mezőindex a lemezen nullaalapú, noha az ApplyAutoFilter 1-től számozza a mezőket, ami először akkor esik le, amikor hexa kiíratásban keresgéli a rekordot. Minden DOPER első bájtja, a vt, megmondja, milyen operandus következik:
$04IEEE 754 double, amely a maradék 8 bájtban ül; így tárol az Excel egy numerikus összehasonlítást$06szöveg, amelynek hossza egyetlencchbájtban lakik, a karakterek maguk pedig a rekord farkába kerülnek$08Bes érték, azaz két bájtba csomagolt logikai érték vagy hibakód$0Cés$0Eoperandus nélküli, és azt jelenti: illeszkedés minden üres cellára, illetve illeszkedés minden nem üres cellára
A második bájt, a grbitSgn, az összehasonlítást hordozza: az 1-től 6-ig terjedő értékek a <, =, <=, >, <> és >= operátorokra képeződnek le. A HotXLS mindkét bájtot utólag is láthatóan tartja az AutoFilterColumns gyűjteményben, amelynek elemei TXLSAutofilterDOPER objektumokként teszik elérhetővé a Criteria1-et és a Criteria2-t DataType, grbitSgn és Value mezőkkel, így nem kell találgatnia, hanem ellenőrizheti, mi kerül írásra
Miért nem talált el egyetlen sort sem az Excelben a „>=100” szűrő?
A szűrő azért nem talált el semmit, mert az operandus szövegként mentődött, az Excel pedig egy szöveg DOPER-t a cellával szemben szövegként hasonlít. A v2.384.45 előtt a lxFilter.pas-ban élő CreateFilterDoper helyesen levágta a >= előtagot, és hatra állította az előjelet, majd mindig vtString DOPER-t épített, amely a 100 karaktereket hordozta. Egy 250-et tároló számcella sosem teljesít szöveges összehasonlítást a "100" szöveggel szemben, ezért minden sor kiesett. Kivétel nélkül, diagnosztika nélkül, javítási felszólítás nélkül az Excel részéről. A v2.384.45 óta élő szabály szándékosan szűk: ha a feltétel összehasonlító operátorral kezdődik, és a maradék területfüggetlen szabályok szerint számként értelmezhető, a HotXLS ugyanazzal az előjellel vtIEEENumber DOPER-t ír. Az operátor nélküli csupasz érték megőrzi a szöveges formát, mert az Excel maga is így tárolja a legördülő listából kiválasztott elemet
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;
// Mező 2 = A1:B100 második oszlopa (az API oldalon 1-alapú)
Sh.ApplyAutoFilter('A1:B100', 2, '>=100');
Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
// v2.384.45+: DataType = 4 (IEEE szám), grbitSgn = 6 (>=)
// A javítás előtt: DataType = 6 (szöveg), amely semmit nem talált el
Assert(Doper.DataType = 4);
Wb.SaveAs('orders.xls');
end;
Az elemzésnél lapul a maradék éles él. Az operandus TryStrToFloat-on megy keresztül, ponttal tizedeselválasztóként, így a '>=1.5' számmá válik, a '>=1,5' viszont szöveg DOPER marad, és ismét csendben semmit sem talál el, bármilyen is a Windows területi beállítása. A dátumok ugyanez a csapda más jelmezben: a '>=2026-01-01' nem szám, tehát szövegként íródik, miközben az Excel a dátumcellákat sorozatszámokként tárolja. Szám egyenlőségvizsgálatánál a '=100' és a 100 példájú numerikus Variant egyaránt 2-es előjelű IEEE DOPER-t ad, míg a csupasz '100' szöveges egyezést produkál. A numerikus operandusokat kódban építse, ne embereknek formázva:
var
Fmt: TFormatSettings;
Since: TDateTime;
begin
Fmt := TFormatSettings.Create;
Fmt.DecimalSeparator := '.';
// Törtes küszöb: mindig ponttal formázzon
Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));
// Dátumok: az Excel által a cellában tárolt sorozatszámhoz hasonlítson.
// A Delphi TDateTime a 1900-as rendszer sorozatszámával egyezik 1900 márciusa utáni dátumokra
Since := EncodeDate(2026, 1, 1);
Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
xlAnd, Unassigned);
end;
Hogyan fűzi össze az AND és az OR két feltételt?
Az AUTOFILTER grbit wJoin bitjei AND esetén 0, OR esetén 1, a HotXLS pedig egészen v2.384.18-ig fel volt cserélve ez a két konstans. Egy between jellegű szűrő, például legalább 100 és 500 alatt, legalább 100 vagy 500 alattként mentődött, ami a gyakorlatban minden számra illeszkedik, és úgy fest, mintha a szűrő egyszerűen nem alkalmazódott volna. A nyilvános operátorkonstansok második portolási csapdát is rejtenek. A HotXLS-ben az xlAnd 0 és az xlOr 1, az Excel automatizálása viszont 1-gyel és 2-vel számozza őket. Az XlAutoFilterOperator sima Byte, így egy VBA makróból literál számokkal fordított kód szépen lefordul, és a COM-ban AND-t jelentő literál 1 itt OR-t jelent. Használja a névvel ellátott konstansokat, és a probléma fel sem merülhet:
// Összeg 100 (inkluzív) és 500 (exkluzív) között
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');
with Sh.AutoFilterColumns.Find(3) do
begin
Assert(Operator = xlAnd); // wJoin = 0 a lemezen
Assert(Criteria2.grbitSgn = 1); // 1 = kisebb mint
end;
Logikai értékek, üresek és a 255 karakteres plafon
A logikai feltétel Bes értékként tárolódik ([MS-XLS] §2.5.10), és a Bes az értékbájtot, a bBoolErr-t teszi elsőnek, a fError jelzőt másodiknak. A HotXLS v2.384.18 előtt fordított sorrendben írta őket, így egy TRUE-ra szűrő az 1-est a hibajelzőbe tette, az Excel pedig hibakódként olvasta a feltételt. Az író és az olvasó együtt volt felcserélve, ezért a HotXLS panasz nélkül körbevitte a saját fájljait, miközben az Excel ellentmondott — emlékeztetőül: az önmagával konzisztens round trip semmit nem bizonyít a szabványmegfelelésről. Az üres cellákhoz egyáltalán nem kell operandus: az önmagában átadott '=' minden üreset egyező DOPER-t ad ($0C), az önmagában átadott '<>' pedig minden nem üreset egyező DOPER-t ($0E)
A szöveges feltételek kemény határba ütköznek a DOPER elrendezésben. A cch hosszmező egyetlen bájt, tehát egy szöveges operandus nem lehet 255 karakternél hosszabb, a CreateFilterDoper pedig az operátor levágása után csonkítja a hosszabb szöveget, ahelyett hogy a hosszbájt átcsordulna, és szétszórná a rekord farkát. A csonkítás néma, és egy hosszú leírásoszlopra szűrő másképp egyezhet, mint a bekapott teljes szöveg. A BIFF8-ban a farok minden szöveget egybájtos jelzőként tárol, amelyet UTF-16 kódolási egységek követnek, és a deklarált rekordméretnek ezeket a bájtokat kell pontosan megszámolnia — ugyanaz a könyvelési fegyelem, amelyről a BIFF rekordhossz-deklarációk elszóródását egy Delphi XLS íróban taglaló cikk ír
Miért törli a második ApplyAutoFilter hívás az elsőt?
Minden ApplyAutoFilter hívás újradefiniálja a teljes szűrőtartományt, így csak az utolsó hívás feltétele él túl. Belül a SetAutoFilter-t hívja, amely a tartomány újraépítése előtt törli az összes mezőt — ez egy oszlopnál helyes, kettőnél meglepő. Több oszlop szűréséhez hívja az ApplyAutoFilter-t egyszer, hogy létrejöjjön a tartomány és az első feltétel, a többit pedig az AutoFilterColumns.SetFieldCriteria útján adja hozzá, amely békén hagyja a tartományt és a többi mezőt. Mindkét út a tartományon kívüli mezőszámot kivétel dobása nélkül hagyja figyelmen kívül, ezért visszaolvasással ellenőrizzen, ideálisan a mentett fájl újranyitása után:
Sh.ApplyAutoFilter('A1:D500', 1, 'North'); // tartomány + 1. mező
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);
Tartsa észben, hogy az AUTOFILTER rekord tárolt definíció: a HotXLS kiírja a feltételeket, de a klasszikus XLS munkalapon nem értékeli őket, így egy folyamnak, amelynek a szerveren kell az illeszkedő sor, ott magának kell kiszámolnia, míg az XLSX homlokzat sorszintű kiértékelést kínál, ahogy az a HotXLS adatérvényesítésről, AutoFilterről és táblákról Delphiben szóló cikkben látható. Ha az Excel már elrejti a sorokat, a tartomány alatti végösszegek azon múlnak, hogyan bánnak a SUBTOTAL és az AGGREGATE a rejtett és szűrt sorokkal — ez a következő hely, ahol egy csendben semmit sem egyező numerikus szűrő rossz számként bukkan fel
A HotXLS natívan olvas és ír BIFF8 XLS és XLSX munkafüzeteket Delphi és C++Builder alól, numerikus, logikai és AND/OR DOPER-eket hordozó AutoFilter feltételekkel együtt, amelyeket az Excel a szándéknak megfelelően értékel. A funkciókért, kiadásokért és próbaverzióért lásd a HotXLS Delphi táblázatkezelő komponens oldalát