Technický článek

Kritéria BIFF8 AutoFilter DOPER v Delphi s HotXLS

HotXLS ukládá každé kritérium AutoFilteru v BIFF8 jako záznam AUTOFILTER nesoucí dvě 10bajtové struktury DOPER a typ DOPERu rozhoduje o tom, jak bude Excel porovnávat. Od v2.384.45 zapisuje TXLSWorksheet.ApplyAutoFilter porovnání jako '>=100' jako IEEE number DOPER, takže Excel hledá shodu v číselných buňkách místo porovnávání textu. Bug report, který změnu odstartoval, byl krátký a šíleně frustrující: noční export nasadil filtr na sloupec s částkou, soubor se otevřel bez jediného slova, šipka dropdownu kritérium zobrazovala a filtr nesebral ani jeden řádek. Nic nebylo poškozené. Byty byly validní BIFF8, jen validní špatného druhu — a přesně tuhle třídu selhání článek rozebírá, spolu se dvěma staršími bajtovými chybami opravenými ve v2.384.18

Co vlastně AutoFilter v BIFF8 ukládá?

AutoFilter v BIFF8 není jeden záznam, ale sada tří typů záznamů a kritéria drží jen záznam patřící jednotlivému poli. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) zaznamenává, kolik sloupců filtrovaný rozsah pokrývá. FILTERMODE ($009B) je značka bez těla, kterou HotXLS vypustí jen tehdy, když má aspoň jedno pole aktivní kritérium. Pak dostává každé aktivní pole vlastní záznam AUTOFILTER ($009E, §2.4.6): index pole počítaný od nuly, slovo grbit, jehož spodní dva bity tvoří wJoin, dva DOPERy o přesně deseti bajtech a volitelný ocas nesoucí znaky případného string DOPERu. Index pole je na disku počítaný od nuly, i když ApplyAutoFilter čísluje pole od 1, což oceníte nejpozději, až budete poprvé hledat záznam v hex dumpu. První bajt každého DOPERu, vt, říká, jaký operand následuje:

  • $04 je IEEE 754 double uložený ve zbývajících 8 bajtech, tedy přesně tak, jak Excel ukládá číselné porovnání
  • $06 je string, jehož délka stojí v jediném bajtu cch, zatímco samotné znaky putují do ocasu záznamu
  • $08 je Bes hodnota, tedy Boolean nebo chybový kód zabalený do dvou bajtů
  • $0C a $0E operand nenesou a znamenají shodu se všemi prázdnými, respektive se všemi neprázdnými buňkami

Druhý bajt, grbitSgn, nese porovnání: hodnoty 1 až 6 se mapují na <, =, <=, >, <> a >=. HotXLS si oba bajty nechá i zpětně dohledatelné přes AutoFilterColumns, jejíž položky vystavují Criteria1 a Criteria2 jako objekty TXLSAutofilterDOPER s DataType, grbitSgn a Value, takže můžete assertovat nad tím, co se zapíše, místo abyste hádali

Anatomie záznamu AUTOFILTER v HotXLS: index pole počítaný od nuly, slovo grbit, jehož spodní dva bity drží wJoin, a dvě 10bajtové struktury DOPER, jejichž bajt vt vybírá operand IEEE number, string, Bes Boolean, prázdnou či neprázdnou buňku, zatímco grbitSgn kóduje srovnávací operátor, který Excel aplikuje
Každý záznam AUTOFILTER nese dva 10bajtové DOPERy a bajt vt rozhoduje, zda Excel porovná kritérium jako číslo, text, Boolean nebo test na prázdnou buňku — před uložením si oba přečtěte zpět přes AutoFilterColumns

Proč filtr '>=100' nesebral v Excelu ani jeden řádek?

Filtr nesebral nic, protože operand byl uložený jako text a Excel porovnává string DOPER s buňkou jako text. Před v2.384.45 odloupl CreateFilterDoper v lxFilter.pas prefix >= správně, nastavil sign na 6 a pak vždy postavil vtString DOPER držící znaky 100. Číselná buňka s hodnotou 250 textovému porovnání s "100" nikdy nevyhoví, takže vypadl každý řádek. Žádná výjimka, žádná diagnostika, žádná nabídka opravy od Excelu. Pravidlo od v2.384.45 je úzké záměrně: pokud kritérium začíná srovnávacím operátorem a zbytek se podle invariant-culture pravidel parsuje jako číslo, zapíše HotXLS vtIEEENumber DOPER se stejným sign. Holá hodnota bez operátora zůstává ve string podobě, protože tak sám Excel ukládá položku vybranou z dropdown seznamu

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;

  // Pole 2 = druhý sloupec A1:B100 (na straně API počítáno od 1)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (IEEE number), grbitSgn = 6 (>=)
  // Před opravou: DataType = 6 (string), což nesebralo žádný řádek
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS zapíše totéž kritérium AutoFilteru >=100 buď jako vtString DOPER, kterému neodpovídá žádná číselná buňka, nebo jako vtIEEENumber DOPER s grbitSgn 6, který Excel vyhodnotí číselně proti částce 250 — tiché selhání s nulou řádků, které CreateFilterDoper opravil ve v2.384.45
Byty byly obě kola validní BIFF8 — změnil se jen bajt typu operandu, a proto Excel otevřel soubor, zobrazil kritérium v dropdownu a pořád nesebral nula řádků

V parsování bydlí zbývající ostré hrany. Operand jde přes TryStrToFloat s tečkou jako desetinným oddělovačem, takže '>=1.5' se stane číslem, kdežto '>=1,5' zůstane string DOPER a znovu potichu nesebere nic, ať říká lokální nastavení Windows cokoli. Data jsou stejná pastka v jiném kostýmu: '>=2026-01-01' není číslo, takže se zapíše jako text, zatímco Excel drží datové buňky jako seriální čísla. Pro rovnost na čísle dají obě varianty, '=100' i číselný Variant jako 100, IEEE DOPER se sign 2, zatímco holý string '100' dá textovou shodu. Číselné operandy stavte v kódu, neformátujte je pro lidské oči:

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

  // Práh se zlomkem: vždy formátovat s tečkou
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Data: porovnávejte se seriálním číslem, které Excel v buňce drží.
  // Delphi TDateTime se pro data po březnu 1900 rovná sérii systému 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Jak AND a OR spojují dvě podmínky?

Bity wJoin v grbitu AUTOFILTERu jsou 0 pro AND a 1 pro OR a HotXLS měl tyhle dvě konstanty prohozené až do v2.384.18. Between filtr typu nejméně 100 a pod 500 se uložil jako nejméně 100 nebo pod 500, což v praxi odpovídá každému číslu a vypadá to, jako kdyby filtr prostě nenastoupil. Veřejné operátorové konstanty přidávají druhé portovací nebezpečí. V HotXLS je xlAnd 0 a xlOr 1, zatímco Excel automation je čísluje 1 a 2. XlAutoFilterOperator je holý Byte, takže kód přeložený z VBA makra s literálními čísly se zkompiluje bez mrknutí a literál 1, který v COM znamenal AND, tu teď znamená OR. Používejte pojmenované konstanty a problém nemůže nastat:

// Částka mezi 100 (včetně) a 500 (bez)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 na disku
  Assert(Criteria2.grbitSgn = 1);      // 1 = menší než
end;
Rozložení bitu wJoin pro kritéria AutoFilteru v HotXLS, kde 0 spojuje dva DOPERy pomocí AND a 1 pomocí OR, číselná osa ukazující, jak prohozené konstanty před v2.384.18 roztáhly between filtr na OR odpovídající všemu, a kolize číslování xlAnd a xlOr s Excel automation
Prohozené konstanty wJoin změnily between filtr na filtr odpovídající každému číslu a přeložené VBA se pořád zkompiluje, protože XlAutoFilterOperator je holý Byte — literál 1, který znamenal AND pod COM automation, tu znamená OR

Booleovské hodnoty, prázdné buňky a strop 255 znaků

Boolean kritérium se ukládá jako Bes hodnota ([MS-XLS] §2.5.10) a Bes dává bajt hodnoty bBoolErr napřed a příznak fError druhý. HotXLS je před v2.384.18 zapisoval v opačném pořadí, takže filtr na TRUE hodnotu dal 1 do chybového příznaku a Excel četl kritérium jako chybový kód. Writer i reader byly prohozené dohromady, a proto HotXLS round-tripoval vlastní soubory bez jediného protestu, zatímco Excel nesouhlasil — připomínka, že sebakonzistentní round trip o shodě se specifikací nic nedokazuje. Prázdné buňky nepotřebují žádný operand: samotné '=' dá DOPER match-all-blanks ($0C) a samotné '<>' DOPER match-all-non-blanks ($0E)

Stringová kritéria narážejí v rozložení DOPERu na tvrdý limit. Pole délky cch je jediný bajt, takže string operand nemůže přesáhnout 255 znaků a CreateFilterDoper delší text po odloupení operátora usekne, místo aby nechal bajt délky přetéct a rozsynchronizovat ocas záznamu. Useknutí je tiché a filtr na dlouhém sloupci popisů může matchovat jinak než celý text, který jste předali. V BIFF8 ukládá ocas každý string jako jednobajtový flag následovaný UTF-16 code units a deklarovaná velikost záznamu musí tyhle byty počítat na pinu — stejná účetní disciplína, jakou rozebírá Drift délky BIFF záznamu ve writeru XLS pro Delphi

Proč druhé volání ApplyAutoFilter smaže to první?

Každé volání ApplyAutoFilter předefinuje celý filtrovaný rozsah, takže přežije jen kritérium z posledního volání. Uvnitř volá SetAutoFilter, které před znovuvystavením rozsahu vymaže každé pole — pro jeden sloupec to je správně a u dvou to zaskočí. Pokud chcete filtrovat víc sloupců, zavolejte ApplyAutoFilter jednou pro nastavení rozsahu a prvního kritéria a další přidávejte přes AutoFilterColumns.SetFieldCriteria, které rozsah i ostatní pole nechá na pokoji. Obě cesty ignorují číslo pole mimo rozsah bez vyhození výjimky, takže si to ověřte čtením zpět, ideálně po znovuotevření uloženého souboru:

Sh.ApplyAutoFilter('A1:D500', 1, 'North');                     // rozsah + pole 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);

Mějte na paměti, že záznam AUTOFILTER je uložená definice: HotXLS kritéria zapíše a na klasickém XLS listu je nevyhodnocuje, takže pipeline, která potřebuje odpovídající řádky na serveru, si je musí spočítat tam sama, zatímco XLSX fasáda nabízí vyhodnocování na úrovni řádků, jak ukazuje Validace dat, AutoFilter a tabulky v listu v Delphi s HotXLS. Až Excel řádky skutečně skryje, závisí jakékoli součty pod rozsahem na tom, jak SUBTOTAL a AGGREGATE zacházejí se skrytými a filtrovanými řádky — a to je další místo, kde číselný filtr, který potichu nesebere nic, vyjde najevo jako špatné číslo

HotXLS čte a zapisuje sešity BIFF8 XLS a XLSX nativně z Delphi a C++Builderu, včetně kritérií AutoFilteru s číselnými, Boolean a AND/OR DOPERy, které Excel vyhodnotí tak, jak mají. Podívejte se na komponentu HotXLS pro Excel v Delphi — funkce, edice a trial ke stažení