Műszaki cikk

HotXLS: BIFF8 AutoFilter DOPER feltételek Delphiben

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:

  • $04 IEEE 754 double, amely a maradék 8 bájtban ül; így tárol az Excel egy numerikus összehasonlítást
  • $06 szöveg, amelynek hossza egyetlen cch bájtban lakik, a karakterek maguk pedig a rekord farkába kerülnek
  • $08 Bes érték, azaz két bájtba csomagolt logikai érték vagy hibakód
  • $0C és $0E operandus 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

HotXLS AUTOFILTER rekord anatómia: a nullaalapú mezőindex, a grbit szó, amelynek alsó két bitje a wJoin-t hordozza, valamint két 10 bájtos DOPER struktúra, amelyeknek vt bájtja IEEE szám, szöveg, Bes logikai, üres vagy nem üres operandust választ, a grbitSgn pedig kódolja azt az összehasonlító operátort, amelyet az Excel alkalmaz
Minden AUTOFILTER rekord két 10 bájtos DOPER-t hordoz, és a vt bájt dönti el, hogy az Excel szám, szöveg, logikai érték vagy üresség-vizsgálatként értékeli-e a feltételt — mentés előtt olvassa vissza mindkettőt az AutoFilterColumns-on keresztül

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;
A HotXLS ugyanazt a >=100 AutoFilter feltételt vtString DOPER-ként írja, amely egyetlen numerikus cellát sem talál el, vagy vtIEEENumber DOPER-ként grbitSgn 6-tal, amelyet az Excel numerikusan értékel a 250-es összegre, ez a CreateFilterDoper által v2.384.45-ben javított néma nullasoros hiba
Mindkét alkalommal érvényes BIFF8 bájtok születtek, csak az operandustípus bájtja változott, ezért nyitotta meg az Excel a fájlt, mutatta a feltételt a legördülőben, és talált el mégis nulla sort

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;
HotXLS wJoin bitelrendezés AutoFilter feltételekhez, ahol a 0 két DOPER-t AND-del fűz össze, az 1 pedig OR-ral, egy szálegyenes, amely megmutatja, hogyan szélesítette a v2.384.18 előtti felcserélt konstans a between szűrőt mindent egyező OR-rá, valamint az xlAnd és xlOr számozási ütközése az Excel automatizálásával
A felcserélt wJoin konstansok a between szűrőt minden számra illeszkedővé tették, a fordított VBA pedig továbbra is lefordul, mert az XlAutoFilterOperator sima Byte — a COM automatizálás alatt AND-t jelentő literál 1 itt OR-t jelent

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