Teknisk artikel

BIFF8 AutoFilter DOPER-villkor i Delphi med HotXLS

HotXLS sparar varje BIFF8-AutoFilter-villkor som en AUTOFILTER-post som bär två DOPER-strukturer på 10 byte, och DOPER-typen avgör hur Excel jämför. Från v2.384.45 skriver TXLSWorksheet.ApplyAutoFilter en jämförelse som '>=100' som en IEEE-tals-DOPER, så att Excel matchar numeriska celler i stället för att jämföra text. Felrapporten som utlöste ändringen var kort och irriterande: ett nattligt exportjobb lade ett filter på en beloppskolumn, filen öppnades utan klagan, listrutepilen visade villkoret, och filtret matchade noll rader. Ingenting var korrupt. Bytena var giltig BIFF8, bara fel sorts giltig, och det är den typen av fel den här artikeln går igenom, tillsammans med två äldre misstag på bytenivå som fixades i v2.384.18

Vad lagrar ett BIFF8-AutoFilter egentligen?

Ett BIFF8-AutoFilter består av tre posttyper, inte en, och bara posten per fält bär villkor. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) registrerar hur många kolumner filterintervallet omfattar. FILTERMODE ($009B) är en kroppslös markör som HotXLS emitterar bara när åtminstone ett fält har ett aktivt villkor. Sedan får varje aktivt fält en egen AUTOFILTER-post ($009E, §2.4.6): ett nollbaserat fältindex, ett grbit-ord vars två lägstaställda bitar är wJoin, två DOPER:er på exakt 10 byte vardera, samt en valfri svans som rymmer tecknen i en eventuell sträng-DOPER. Fältindexet är nollbaserat på disk även om ApplyAutoFilter numrerar fälten från 1, vilket spelar roll första gången du jagar en post i en hexdump. Det första bytet i varje DOPER, vt, säger vilken sorts operand som följer:

  • $04 är en IEEE 754-double i de återstående 8 bytena, vilket är hur Excel lagrar en numerisk jämförelse
  • $06 är en sträng vars längd ryms i en enda cch-byte, medan tecknen själva hamnar i postens svans
  • $08 är ett Bes-värde, ett booleskt värde eller felkod packat i två byte
  • $0C och $0E bär ingen operand och betyder matcha alla tomma respektive alla icke-tomma

Andra byten, grbitSgn, bär jämförelsen: 1 till 6 motsvarar <, =, <=, >, <> och >=. HotXLS håller båda bytena läsbara i efterhand genom AutoFilterColumns, vars poster exponerar Criteria1 och Criteria2 som TXLSAutofilterDOPER-objekt med DataType, grbitSgn och Value, så att du kan hävda mot det som skrivs i stället för att gissa

Anatomin i HotXLS AUTOFILTER-post med det nollbaserade fältindexet, grbit-ordet vars två lägsta bitar bär wJoin, och två DOPER-strukturer på 10 byte vars vt-byte väljer en IEEE-tals-, sträng-, Bes-boolesk, tom eller icke-tom operand medan grbitSgn kodar den jämförelseoperator Excel tillämpar
Varje AUTOFILTER-post bär två DOPER:er på 10 byte, och vt-byten avgör om Excel jämför ett villkor som tal, text, booleskt värde eller tomttest — läs tillbaka båda genom AutoFilterColumns innan du sparar

Varför matchade filtret '>=100' inga rader i Excel?

Filtret matchade ingenting eftersom operanden lagrats som text, och Excel jämför en sträng-DOPER mot cellen som text. Före v2.384.45 ströks prefixet >= korrekt i CreateFilterDoper i lxFilter.pas och jämförelsetecknet sattes till 6, men därefter byggdes alltid en vtString-DOPER med tecknen 100. En numerisk cell med 250 klarar aldrig en textjämförelse mot "100", så varje rad föll bort. Inget undantag, ingen diagnostik, inget reparationsförslag från Excel. Regeln sedan v2.384.45 är medvetet snäv: börjar villkoret med en jämförelseoperator och går resten att tolka som ett tal oavsett regionala inställningar, skriver HotXLS en vtIEEENumber-DOPER med samma jämförelsetecken. Ett bart värde utan operator behåller strängformen, för det är så Excel själv lagrar ett val från listrutan

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;

  // Fält 2 = andra kolumnen i A1:B100 (1-baserat på API-sidan)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (IEEE-tal), grbitSgn = 6 (>=)
  // Före fixen: DataType = 6 (sträng), vilket matchade ingenting
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS skriver samma >=100 AutoFilter-villkor antingen som en vtString-DOPER som inte matchar några numeriska celler eller som en vtIEEENumber-DOPER med grbitSgn 6 som Excel utvärderar numeriskt mot beloppet 250, vilket är det tysta noll-raders-felet CreateFilterDoper fixade i v2.384.45
Bytena var giltig BIFF8 båda gångerna — bara operandtypsbytet ändrades, vilket är därför Excel öppnade filen, visade villkoret i listrutan och ändå matchade noll rader

Tolkningen är där de återstående vassa kanterna bor. Operanden går genom TryStrToFloat med punkt som decimaltecken, så '>=1.5' blir ett tal medan '>=1,5' förblir en sträng-DOPER och tyst matchar ingenting igen, oavsett vad Windows-regionen säger. Datum är samma fälla i en annan kostym: '>=2026-01-01' är inget tal och skrivs därför som text, medan Excel lagrar datumceller som serienummer. För likhet på ett tal ger både '=100' och en numerisk Variant som 100 en IEEE-DOPER med jämförelsetecknet 2, medan den bara strängen '100' ger en textmatchning. Bygg numeriska operander i kod i stället för att formatera dem för människor:

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

  // Tröskel med decimal: formatera alltid med punkt
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Datum: jämför mot serienumret Excel lagrar i cellen.
  // En Delphi TDateTime motsvarar 1900-systemets serial för datum efter mars 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Hur fogar AND och OR samman två villkor?

wJoin-bitarna i AUTOFILTER-grbit är 0 för AND och 1 för OR, och HotXLS hade de två konstanterna ombytta fram till v2.384.18. Ett mellan-filter som minst 100 och under 500 sparades som minst 100 eller under 500, vilket i praktiken matchar vartenda tal och ser ut som om filtret inte applicerats. De publika operatorkonstanterna lägger till en andra portningsfara. I HotXLS är xlAnd 0 och xlOr 1, medan Excel-automatisering numrerar dem 1 och 2. XlAutoFilterOperator är en ren Byte, så kod översatt från ett VBA-makro med literala tal kompileras rent, och en literal 1 som betydde AND i COM betyder nu OR. Använd de namngivna konstanterna så kan problemet inte uppstå:

// Belopp mellan 100 (inklusive) och 500 (exklusivt)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 på disk
  Assert(Criteria2.grbitSgn = 1);      // 1 = mindre än
end;
Bitlayouten för HotXLS wJoin i AutoFilter-villkor där 0 fogar två DOPER:er med AND och 1 med OR, en tallinje som visar hur de ombytta konstanterna före v2.384.18 vidgade ett mellan-filter till ett matchar-allt-OR, och nummerkrocken mellan xlAnd xlOr och Excel-automatisering
Ombytta wJoin-konstanter gjorde ett mellan-filter till ett som matchar vartenda tal, och översatt VBA kompileras fortfarande eftersom XlAutoFilterOperator är en ren Byte — en literal 1 som betydde AND under COM-automatisering betyder OR här

Booleska värden, tomma celler och 255-teckensgränsen

Ett booleskt villkor lagras som ett Bes-värde ([MS-XLS] §2.5.10), och Bes lägger värdebyten bBoolErr först och fError-flaggan sedan. HotXLS skrev dem i omvänd ordning före v2.384.18, så ett filter för TRUE lade 1 i felflaggan och Excel läste villkoret som en felkod. Skrivare och läsare var ombytta tillsammans, vilket är därför HotXLS egna filer alltid klarade rundturen utan klagan medan Excel invände — en påminnelse om att en självkonsistent rundtur bevisar ingenting om spec-efterlevnad. Tomma celler behöver ingen operand alls: att skicka '=' ensam ger en matcha-alla-tomma-DOPER ($0C) och '<>' ensam en matcha-alla-icke-tomma-DOPER ($0E)

Strängvillkor stöter på en hård gräns i DOPER-layouten. Längdfältet cch är en enda byte, så en strängoperand kan inte överstiga 255 tecken, och CreateFilterDoper trunkerar längre text efter att operatorn strukits i stället för att låta längdbyten slå runt och desynkronisera postens svans. Trunkeringen är tyst, och ett filter på en lång beskrivningskolumn kan matcha annorlunda än den fullständiga text du skickade. I BIFF8 lagrar svansen varje sträng som en enbyteflagga följt av UTF-16-kodenheter, och den deklarerade poststorleken måste räkna de bytena exakt — samma bokföringsdisciplin som tas upp i hur BIFF-postlängdsdeklarationer driver i en Delphi XLS-skrivare

Varför raderar ett andra ApplyAutoFilter-anrop det första?

Varje ApplyAutoFilter-anrop definierar om hela filterintervallet, så bara villkoret från sista anropet överlever. Internt anropar det SetAutoFilter, som rensar varje fält innan intervallet byggs upp igen, vilket är korrekt för en kolumn och överraskande för två. För att filtrera flera kolumner, anropa ApplyAutoFilter en gång för att etablera intervallet och första villkoret, och lägg sedan till de övriga genom AutoFilterColumns.SetFieldCriteria, som lämnar intervallet och de andra fälten i fred. Båda vägarna ignorerar ett fältnummer utanför intervallet utan att kasta undantag, så verifiera genom att läsa tillbaka, helst efter att den sparade filen öppnats igen:

Sh.ApplyAutoFilter('A1:D500', 1, 'North');                     // intervall + fält 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);

Kom ihåg att AUTOFILTER-posten är en lagrad definition: HotXLS skriver villkoren och utvärderar dem inte på det klassiska XLS-kalkylbladet, så en pipeline som behöver de matchande raderna på servern får beräkna dem själv där, medan XLSX-fasaden erbjuder utvärdering på radnivå som visas i HotXLS datavalidering, AutoFilter och tabeller i Delphi. När Excel väl döljer rader beror summeringar under intervallet på hur SUBTOTAL och AGGREGATE behandlar dolda och filtrerade rader, vilket är nästa plats där ett numeriskt filter som tyst matchar ingenting visar sig som en felaktig siffra

HotXLS läser och skriver BIFF8 XLS- och XLSX-arbetsböcker nativt från Delphi och C++Builder, inklusive AutoFilter-villkor med numeriska, booleska och AND/OR-DOPER:er som Excel utvärderar som avsett. Se HotXLS Delphi-kalkylbladskomponenten för funktioner, utgåvor och en testnedladdning