Tehnički članak

BIFF8 AutoFilter DOPER kriterijumi u Delphi-ju uz HotXLS

HotXLS svaki BIFF8 AutoFilter kriterijum upisuje kao AUTOFILTER zapis koji nose dve 10-bajtne DOPER strukture, i upravo tip DOPER-a odlučuje kako će Excel porediti. Od v2.384.45, TXLSWorksheet.ApplyAutoFilter upisuje poređenje poput '>=100' kao IEEE number DOPER, pa Excel poklapa numeričke ćelije umesto da poredi tekst. Izveštaj o grešci koji je pokrenuo promenu bio je kratak i frustrirajući: noćni izvoz primenio je filter na kolonu sa iznosima, fajl se otvorio bez ijedne zamere, strelica padajuće liste pokazivala je kriterijum, a filter poklopio nula redova. Ništa nije bilo oštećeno. Bajtovi su činili potpuno validan BIFF8, samo pogrešne vrste validnog, i baš tu klasu kvarova ovaj članak razrađuje, zajedno sa dve starije greške na nivou bajtova ispravljene u v2.384.18

Šta BIFF8 AutoFilter zapravo čuva?

BIFF8 AutoFilter je skup od tri tipa zapisa, a ne jedan, i kriterijume nosi samo zapis po polju. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) beleži koliko kolona obuhvata opseg filtera. FILTERMODE ($009B) je marker bez tela koji HotXLS emituje tek kada bar jedno polje ima aktivan kriterijum. Zatim svako aktivno polje dobija svoj AUTOFILTER zapis ($009E, §2.4.6): indeks polja (nula-baziran), grbit reč čija donja dva bita nose wJoin, dva DOPER-a od tačno 10 bajtova svaki i opcion rep koji čuva znakove string DOPER-a kada ga ima. Indeks polja je na disku nula-baziran iako ApplyAutoFilter broji polja od 1, što postaje važno prvog trenutka kad krenete u lov na zapis u heksadecimalnom ispisu. Prvi bajt svakog DOPER-a, vt, govori kakav operand sledi:

  • $04 je IEEE 754 double smešten u preostalih 8 bajtova, i tako Excel čuva numeričko poređenje
  • $06 je string čija dužina stoji u jednom cch bajtu, a sami znakovi odlaze u rep zapisa
  • $08 je Bes vrednost, logička vrednost ili kod greške zapakovani u dva bajta
  • $0C i $0E ne nose operand i znače poklapanje sa svim praznim, odnosno sa svim nepraznim

Drugi bajt, grbitSgn, nosi poređenje: vrednosti od 1 do 6 preslikavaju se na <, =, <=, >, <> i >=. HotXLS oba bajta drži vidljivima i posle činjenice kroz AutoFilterColumns, čije stavke izlažu Criteria1 i Criteria2 kao TXLSAutofilterDOPER objekte sa DataType, grbitSgn i Value, pa možete da asertujete ono što će biti upisano umesto da nagađate

Anatomija AUTOFILTER zapisa u HotXLS-u: indeks polja nula-baziran, grbit reč čija donja dva bita nose wJoin, i dve 10-bajtne DOPER strukture čiji vt bajt bira IEEE broj, string, Bes logičku vrednost, prazan ili neprazan operand, dok grbitSgn kodira operator poređenja koji Excel primenjuje
Svaki AUTOFILTER zapis nosi dva 10-bajtna DOPER-a, i vt bajt odlučuje da li Excel poredi kriterijum kao broj, tekst, logičku vrednost ili test na prazno — pročitajte oba nazad kroz AutoFilterColumns pre nego što sačuvate

Zašto je filter '>=100' poklopio nula redova u Excelu?

Filter nije poklopio ništa jer je operand bio upisan kao tekst, a Excel string DOPER upoređuje sa ćelijom kao sa tekstom. Pre v2.384.45, CreateFilterDoper u lxFilter.pas bio je ispravno skidao >= prefiks i postavljao znak na 6, pa uvek gradio vtString DOPER koji nosi znakove 100. Ćelija sa brojem 250 nikada ne prolazi tekstualno poređenje sa "100", pa je svaki red otpadao. Bez izuzetka, bez dijagnostike, bez Excelovog predloga popravke. Pravilo od v2.384.45 namerno je uzano: ako kriterijum počinje operatorom poređenja i ostatak se po invariant-culture pravilima parsira kao broj, HotXLS upisuje vtIEEENumber DOPER sa istim znakom. Gola vrednost bez operatora ostaje u string obliku, jer baš tako Excel sam čuva stavku izabranu iz padajuće liste

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;

  // Polje 2 = druga kolona od A1:B100 (na API strani od 1)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (IEEE broj), grbitSgn = 6 (>=)
  // Pre popravke: DataType = 6 (string), što nije poklopilo ništa
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS isti >=100 AutoFilter kriterijum upisuje kao vtString DOPER koji ne poklapa nijednu numeričku ćeliju ili kao vtIEEENumber DOPER sa grbitSgn 6 koji Excel numerički vrednuje prema iznosu 250, što je tihi kvar sa nula poklopljenih redova koji je CreateFilterDoper ispravio u v2.384.45
Bajtovi su oba puta činili validan BIFF8 — menjao se samo bajt tipa operanda, pa je Excel otvorio fajl, pokazao kriterijum u padajućoj listi i dalje poklopio nula redova

Oštre ivice žive u parsiranju. Operand prolazi kroz TryStrToFloat sa tačkom kao decimalnim separatorom, pa '>=1.5' postaje broj, dok '>=1,5' ostaje string DOPER i ponovo tiho ne poklapa ništa, šta god Windows lokal tvrdio. Datumi su ista zamka u drugom kostimu: '>=2026-01-01' nije broj, pa se upisuje kao tekst, a Excel ćelije sa datumima drži kao serijske brojeve. Za jednakost na broju, i '=100' i numerički Variant poput 100 daju IEEE DOPER sa znakom 2, dok goli string '100' daje tekstualno poklapanje. Brojevne operande gradite u kodu, umesto da ih formatirate za ljudsko oko:

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

  // Prag sa razlomkom: uvek formatirajte sa tačkom
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Datumi: poredite sa serijskim brojem koji Excel čuva u ćeliji.
  // Delphi TDateTime odgovara serijskom broju 1900 sistema za datume posle marta 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Kako AND i OR spajaju dva uslova?

wJoin bitovi AUTOFILTER grbit-a su 0 za AND i 1 za OR, i HotXLS je imao te dve konstante zamenjene sve do v2.384.18. Filter tipa between, poput najmanje 100 i ispod 500, čuvan je kao najmanje 100 ili ispod 500, što u praksi poklapa svaki broj i deluje kao da filter jednostavno nije primenjen. Javne operatorske konstante donose i drugu opasnost pri portiranju. U HotXLS-u je xlAnd 0 a xlOr 1, dok ih Excel automation numeriše kao 1 i 2. XlAutoFilterOperator je običan Byte, pa će kod preveden iz VBA makroa sa literalnim brojevima proći kroz kompajler bez ijedne primedbe, i literal 1 koji je u COM značio AND ovde znači OR. Koristite imenovane konstante i problem ne može ni da nastane:

// Iznos između 100 (uključivo) i 500 (isključivo)
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 = manje od
end;
HotXLS raspored wJoin bitova za AutoFilter kriterijume gde 0 spaja dva DOPER-a sa AND a 1 sa OR, brojevna prava koja pokazuje kako su pre-v2.384.18 zamenjene konstante raširile between filter u OR koji poklapa sve, i sudar numeracije xlAnd i xlOr sa Excel automation-om
Zamenjene wJoin konstante pretvorile su between filter u onaj koji poklapa svaki broj, a prevedeni VBA se i dalje kompajlira jer je XlAutoFilterOperator običan Byte — literal 1 koji je pod COM automation značio AND ovde znači OR

Logičke vrednosti, prazne ćelije i plafon od 255 znakova

Logički kriterijum se čuva kao Bes vrednost ([MS-XLS] §2.5.10), a Bes stavlja vrednosni bajt bBoolErr na prvo mesto i fError zastavicu na drugo. HotXLS ih je do v2.384.18 pisao obrnutim redosledom, pa je filter za TRUE ubacivao 1 u zastavicu greške i Excel je čitao kriterijum kao kod greške. Pisac i čitač bili su obrnuti zajedno, pa je HotXLS svoje fajlove propuštao kroz round trip bez ijedne primedbe dok se Excel nije slagao, podsetnik da round trip dosledan sam sebi ne dokazuje ništa o usaglašenosti sa specifikacijom. Prazne ćelije ne trebaju nikakav operand: samostalan '=' daje DOPER koji poklapa sve prazne ($0C), a samostalan '<>' DOPER koji poklapa sve neprazne ($0E)

String kriterijumi nailaze na čvrstu granicu DOPER rasporeda. Polje dužine cch je jedan bajt, pa string operand ne može preći 255 znakova, i CreateFilterDoper skraćuje duži tekst nakon skidanja operatora, umesto da pusti da se bajt dužine prelije i da rep zapisa izgubi sinhronizaciju. Skraćenje je tiho, i filter na koloni dugog opisa može poklapati drugačije nego pun tekst koji ste prosledili. U BIFF8 rep čuva svaki string kao jednobajtnu zastavicu pa UTF-16 code units, i deklarisana veličina zapisa mora te bajtove brojiti tačno — ista knjigovodstvena disciplina koju opisuje kako deklaracije dužine BIFF zapisa odstupaju u Delphi XLS piscu

Zašto drugi poziv ApplyAutoFilter-a briše prvi?

Svaki poziv ApplyAutoFilter redefiniše ceo opseg filtera, pa preživi samo kriterijum iz poslednjeg poziva. Unutra poziva SetAutoFilter, koji očisti svako polje pre ponovne izgradnje opsega, i to je ispravno za jednu kolonu a iznenađujuće za dve. Da filtrirate više kolona, pozovite ApplyAutoFilter jednom da uspostavite opseg i prvi kriterijum, a ostale dodajte kroz AutoFilterColumns.SetFieldCriteria, koji opseg i ostala polja ostavlja na miru. Obe putanje ignorišu broj polja van opsega bez podizanja izuzetka, pa proverite čitanjem nazad, idealno nakon ponovnog otvaranja sačuvanog fajla:

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

Imajte na umu da je AUTOFILTER zapis sačuvana definicija: HotXLS upisuje kriterijume i ne vrednuje ih na klasičnom XLS radnom listu, pa cevovod koji na serveru treba poklopljene redove mora da ih izračuna sam tamo, dok XLSX fasada nudi vrednovanje na nivou reda kao u tekstu HotXLS validacija podataka, AutoFilter i tabele u Delphi-ju. Kada Excel zaista sakrije redove, svi zbirovi ispod opsega zavise od kako SUBTOTAL i AGGREGATE tretiraju skrivene i filtrirane redove, i to je sledeće mesto gde se brojevni filter koji tiho ne poklapa ništa pokazuje kao pogrešan broj

HotXLS nativno čita i upisuje BIFF8 XLS i XLSX radne sveske iz Delphi-ja i C++Builder-a, uključujući AutoFilter kriterijume sa brojevnim, logičkim i AND/OR DOPER-ima koje Excel vrednuje onako kako je zamišljeno. Pogledajte HotXLS Delphi spreadsheet komponentu za funkcije, izdanja i probno preuzimanje