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 endacch-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$0Coch$0Ebä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
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;
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;
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