Tehnični članak

DOPER kriteriji BIFF8 AutoFilter v Delphiju s HotXLS

HotXLS shrani vsak kriterij BIFF8 AutoFilter kot zapis AUTOFILTER, ki nosi dve strukturi DOPER po 10 bajtov, vrsta DOPER pa odloča, kako Excel primerja. Od v2.384.45 zapiše TXLSWorksheet.ApplyAutoFilter primerjavo, kot je '>=100', kot številčni DOPER IEEE, zato Excel primerja številčne celice namesto besedila. Poročilo o napaki, ki je sprožilo spremembo, je bilo kratko in ponižujoče: nočni izvoz je uporabil filter na stolpcu z zneski, datoteka se je odprla brez pritožbe, puščica spustnega seznama je pokazala kriterij, filter pa ni ujel nobene vrstice. Nič ni bilo pokvarjeno. Bajti so bili veljavni BIFF8, le napačne vrste veljavni, in to je razred napak, ki mu ta članek sledi, skupaj z dvema starejšima napakama na ravni bajtov, popravljenima v v2.384.18

Kaj zapis BIFF8 AutoFilter dejansko hrani?

BIFF8 AutoFilter je nabor treh vrst zapisov, ne ena, in le zapis na polje nosi kriterije. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) zabeleži, koliko stolpcev pokriva območje filtra. FILTERMODE ($009B) je marker brez telesa, ki ga HotXLS izda šele, ko ima vsaj eno polje dejaven kriterij. Nato vsako dejavno polje dobi svoj zapis AUTOFILTER ($009E, §2.4.6): ničelno osnovan indeks polja, besedo grbit, katere spodnja dva bita sta wJoin, dva DOPERja po točno 10 bajtov in neobvezen rep, ki hrani znake morebitnega niznega DOPERja. Indeks polja je na disku ničelno osnovan, čeprav ApplyAutoFilter šteje polja od 1, kar postane pomembno, ko prvič iščete zapis v šestnajstiškem izpisu. Prvi bajt vsakega DOPERja, vt, pove, kakšen operand sledi:

  • $04 je double po IEEE 754, shranjen v preostalih 8 bajtih, tako Excel shrani številčno primerjavo
  • $06 je niz, katerega dolžina živi v enem samem bajtu cch, znaki sami pa so potisnjeni v rep zapisa
  • $08 je vrednost Bes, logična vrednost ali koda napake, zapakirana v dva bajta
  • $0C in $0E ne nosita operanda in pomenita »ujemi vse prazne« oziroma »ujemi vse neprazne«

Drugi bajt, grbitSgn, nosi primerjavo: 1 do 6 se preslika na <, =, <=, >, <> in >=. HotXLS oba bajta pusti vidna tudi pozneje prek AutoFilterColumns, katere elementi izpostavijo Criteria1 in Criteria2 kot objekte TXLSAutofilterDOPER s DataType, grbitSgn in Value, tako da lahko preverjate tisto, kar bo zapisano, namesto da bi ugibali

Anatomija zapisa AUTOFILTER v HotXLS: ničelno osnovan indeks polja, beseda grbit, katere spodnja dva bita nosita wJoin, ter dve strukturi DOPER po 10 bajtov, kjer bajt vt izbere operand številke IEEE, niza, logične vrednosti Bes, praznega ali nepraznega, grbitSgn pa kodira primerjalni operator, ki ga uporabi Excel
Vsak zapis AUTOFILTER nosi dva DOPERja po 10 bajtov, bajt vt pa odloča, ali Excel kriterij primerja kot številko, besedilo, logično vrednost ali preizkus praznih — oba preberite nazaj prek AutoFilterColumns, preden shranite

Zakaj filter '>=100' v Excelu ni ujel nobene vrstice?

Filter ni ujel nič, ker je bil operand shranjen kot besedilo, Excel pa primerja nizni DOPER s celico kot besedilo. Pred v2.384.45 je CreateFilterDoper v lxFilter.pas pravilno olupil predpono >= in nastavil znak na 6, nato pa vedno zgradil DOPER vtString z znaki 100. Številčna celica z 250 nikoli ne zadovolji besedilne primerjave s "100", zato je izpadla vsaka vrstica. Brez izjeme, brez diagnostike, brez poziva k popravilu od Excela. Pravilo od v2.384.45 je namerno ozko: če se kriterij začne s primerjalnim operatorjem in preostanek po pravilih invariantne kulture razčleni kot številko, HotXLS zapiše DOPER vtIEEENumber z istim znakom. Gola vrednost brez operatorja ostane v nizni obliki, ker tako Excel sam shrani element, izbran s spustnega seznama

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 = drugi stolpec A1:B100 (na strani API z osnovo 1)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (številka IEEE), grbitSgn = 6 (>=)
  // Pred popravkom: DataType = 6 (niz), ki ni ujel nič
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS zapiše isti kriterij AutoFilter >=100 kot DOPER vtString, ki ne ujame nobene številčne celice, ali kot DOPER vtIEEENumber z grbitSgn 6, ki ga Excel številčno ovrednoti proti znesku 250 — tiha napaka z nič vrsticami, ki jo je CreateFilterDoper odpravil v v2.384.45
Bajti so bili obakrat veljavni BIFF8 — spremenil se je le bajt vrste operanda, zato je Excel odprl datoteko, pokazal kriterij v spustnem seznamu in še vedno ni ujel nobene vrstice

Razčlenjevanje je mesto, kjer ostajajo preostali ostri robovi. Operand gre skozi TryStrToFloat s piko kot decimalnim ločilom, zato '>=1.5' postane številka, '>=1,5' pa ostane nizni DOPER in spet tiho ne ujame nič, kar koli že pravi locale Windows. Datumi so ista past v drugi obleki: '>=2026-01-01' ni številka, zato se zapiše kot besedilo, Excel pa hrani celice z datumi kot serijske številke. Za enakost na številki tako '=100' kot številčni Variant, kot je 100, proizvedeta DOPER IEEE z znakom 2, goli niz '100' pa besedilno ujemanje. Številčne operande sestavite v kodi, namesto da bi jih oblikovali za ljudi:

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

  // Prag z decimalko: vedno oblikujte s piko
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Datumi: primerjajte s serijsko številko, ki jo Excel shrani v celico.
  // Delphi TDateTime je enak serijski številki sistema 1900 za datume po marcu 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Kako AND in OR združita dva pogoja?

Bitova wJoin grbita zapisa AUTOFILTER sta 0 za AND in 1 za OR, HotXLS pa je ti dve konstanti imel obrnjena do v2.384.18. Filter tipa med vrednostima, na primer vsaj 100 in pod 500, se je shranil kot vsaj 100 ali pod 500, kar v praksi ujame vsako številko in zgleda, kot da filter sploh ni bil uporabljen. Javne operatorske konstante dodajajo drugo nevarnost pri prenašanju kode. V HotXLS je xlAnd 0 in xlOr 1, avtomatizacija Excel pa ju šteje 1 in 2. XlAutoFilterOperator je običajen Byte, zato koda, prevedena iz makra VBA s številskimi literali, čisto lepo prevede, literal 1, ki je v COM pomenil AND, pa tu pomeni OR. Uporabite poimenovane konstante in težava ne more nastati:

// Znesek med 100 (vključno) in 500 (izključno)
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 = manj kot
end;
Razpored bitov wJoin v HotXLS za kriterije AutoFilter, kjer 0 združi dva DOPERja z AND in 1 z OR, številska premica, ki pokazuje, kako so obrnjene konstante pred v2.384.18 razširile filter med vrednostima v OR, ki ujame vse, ter spor številčenja xlOr in xlAnd z avtomatizacijo Excel
Obrnjene konstante wJoin so iz filtra med vrednostima naredile enega, ki ujame vsako številko, prevedeni VBA pa se še vedno lepo prevede, ker je XlAutoFilterOperator običajen Byte — literal 1, ki je pod avtomatizacijo COM pomenil AND, tu pomeni OR

Logične vrednosti, prazne celice in strop 255 znakov

Logični kriterij se shrani kot vrednost Bes ([MS-XLS] §2.5.10), Bes pa postavi vrednostni bajt bBoolErr na prvo mesto in zastavico fError na drugo. HotXLS ju je do v2.384.18 zapisoval v obratnem vrstem redu, zato je filter za TRUE vsul 1 v zastavico napake in Excel je kriterij prebral kot kodo napake. Pisatelj in bralec sta bila zamenjana skupaj, zato je round-trip lastnih datotek HotXLS potekal brez pritožb, medtem ko Excel ni soglašal — opomnik, da samoskladen round-trip ne dokaže nič o skladnosti s specifikacijo. Prazne celice sploh ne potrebujejo operanda: goli '=' proizvede DOPER »ujemi vse prazne« ($0C), goli '<>' pa DOPER »ujemi vse neprazne« ($0E)

Nizni kriteriji zadenejo trdo mejo v razporeditvi DOPER. Dolžinsko polje cch je en sam bajt, zato nizni operand ne sme presegati 255 znakov, CreateFilterDoper pa daljše besedilo po olupitvi operatorja skrajša, namesto da bi pustil dolžinskemu bajtu, da se zavije in razsinhronizira rep zapisa. Skrajšanje je tiho, filter na stolpcu z dolgimi opisi pa se lahko ujame drugače od polnega besedila, ki ste ga podali. V BIFF8 rep shrani vsak niz kot enobajtno zastavico, ki ji sledijo enote kode UTF-16, deklarirana velikost zapisa pa mora te bajte šteti natančno — ista knjigovodska disciplina, ki jo opisuje kako v pisatelju XLS za Delphi drsijo deklaracije dolžin zapisov BIFF

Zakaj drugi klic ApplyAutoFilter izbriše prvega?

Vsak klic ApplyAutoFilter na novo definira celotno območje filtra, zato preživi le kriterij zadnjega klica. Znotraj pokliče SetAutoFilter, ki počisti vsako polje, preden znova zgradi območje, kar je pravilno za en stolpec in presenetljivo za dva. Za filtriranje več stolpcev pokličite ApplyAutoFilter enkrat, da postavite območje in prvi kriterij, ostale pa dodajte prek AutoFilterColumns.SetFieldCriteria, ki območje in druga polja pusti pri miru. Poti ignorirata številko polja izven območja brez izjeme, zato preverjajte z branjem nazaj, najbolje po ponovnem odpiranju shranjene datoteke:

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

Upoštevajte, da je zapis AUTOFILTER shranjena definicija: HotXLS zapiše kriterije in jih na klasičnem delovnem listu XLS ne ovrednoti, zato mora cevovod, ki potrebuje ujemajoče vrstice na strežniku, tam izračunati sam, medtem ko fasada XLSX ponuja ovrednotenje na ravni vrstic, kot prikazuje preverjanje podatkov, AutoFilter in tabele v HotXLS za Delphi. Ko Excel vrstice res skrije, so vse vsote pod območjem odvisne od tega, kako SUBTOTAL in AGGREGATE obravnavata skrite in filtrirane vrstice, kar je naslednje mesto, kjer se številčni filter, ki tiho ne ujame nič, pokaže kot napačna številka

HotXLS iz Delphija in C++Builder izvorno bere in zapisuje delovne zvezke BIFF8 XLS in XLSX, vključno s kriteriji AutoFilter s številčnimi, logičnimi in AND/OR DOPERji, ki jih Excel ovrednoti, kot je mišljeno. Za funkcije, izdaje in preizkusni prenos glej komponento preglednic HotXLS za Delphi