Techninis straipsnis

BIFF8 AutoFilter DOPER kriterijai Delphi su HotXLS

HotXLS kiekvieną BIFF8 AutoFilter kriterijų įrašo kaip AUTOFILTER įrašą, nešantį dvi 10 baitų DOPER struktūras, ir būtent DOPER tipas nulemia, kaip lygins Excel. Nuo v2.384.45 TXLSWorksheet.ApplyAutoFilter tokį palyginimą kaip '>=100' užrašo IEEE skaičiaus DOPER pavidalu, tad Excel atitinka skaitinius langelius, o ne lygina tekstą. Pakeitimą lėmęs pranešimas apie klaidą buvo trumpas ir be galo erzinantis: naktinis eksportas pritaikė filtrą sumų stulpeliui, failas atsivėrė be jokio pasipriešinimo, išskleidžiamajame sąraše matėsi kriterijus, o filtras neatitiko nė vienos eilutės. Nieko nesugriauta. Baitai buvo validūs BIFF8, tiesiog ne tos rūšies validūs, ir būtent tokios rūšies nesėkmę šis straipsnis apžvelgia kartu su dviem senesnėmis baitų lygio klaidomis, sutvarkytomis v2.384.18

Ką iš tikrųjų saugo BIFF8 AutoFilter?

BIFF8 AutoFilter yra trijų įrašų tipų rinkinys, o ne vienas, ir tik laukui skirtas įrašas laiko kriterijus. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) užfiksuoja, kiek stulpelių dengia filtro diapazonas. FILTERMODE ($009B) — žymeklis be kūno, kurį HotXLS išduoda tik tada, kai bent vienas laukas turi aktyvų kriterijų. Tada kiekvienas aktyvus laukas gauna savąjį AUTOFILTER įrašą ($009E, §2.4.6): lauko indeksą nuo nulio, grbit žodį, kurio žemi du bitai yra wJoin, du DOPER po lygiai 10 baitų ir pasirinktinę uodegą, laikančią bet kokio string DOPER simbolius. Lauko indeksas diske skaičiuojamas nuo nulio, nors ApplyAutoFilter laukus numeruoja nuo 1 — tai suvoksite pirmąjį kartą medžiodami įrašą hex atvaizde. Kiekvieno DOPER pirmasis baitas, vt, sako, koks operandas seka paskui:

  • $04 yra IEEE 754 double, saugomas likusiuose 8 baituose, — taip Excel saugo skaitinį palyginimą
  • $06 yra string, kurio ilgis telpa viename cch baite, o patys simboliai nustumti į įrašo uodegą
  • $08 yra Bes reikšmė — Boolean arba klaidos kodas, supakuotas į du baitus
  • $0C ir $0E operando neturi ir reiškia atitikti visus tuščius bei atitikti visus ne tuščius

Antrasis baitas, grbitSgn, laiko palyginimą: 1 iki 6 atitinka <, =, <=, >, <> ir >=. HotXLS abu baitus po fakto leidžia peržiūrėti per AutoFilterColumns, kurio elementai atveria Criteria1 ir Criteria2 kaip TXLSAutofilterDOPER objektus su DataType, grbitSgn ir Value, tad galite tikrinti tai, kas bus įrašyta, vietoj to, kad spėliotumėte

HotXLS AUTOFILTER įrašo anatomija: lauko indeksas nuo nulio, grbit žodis, kurio žemi du bitai laiko wJoin, ir dvi 10 baitų DOPER struktūros, kurių vt baitas parinktinę IEEE skaičiaus, string, Bes Boolean, tuščio ar ne tuščio operandą, o grbitSgn užkoduoja palyginimo operatorių, kurį taiko Excel
Kiekvienas AUTOFILTER įrašas neša du 10 baitų DOPER, o vt baitas nusprendžia, ar Excel kriterijų lygina kaip skaičių, tekstą, Boolean ar tuščios vietos testą — prieš įrašydami abu perskaitykite per AutoFilterColumns

Kodėl '>=100' filtras Excel programoje neatitiko nė vienos eilutės?

Filtras neatitiko nieko, nes operandas buvo saugomas kaip tekstas, o Excel string DOPER su langeliu lygina kaip tekstą. Iki v2.384.45 CreateFilterDoper iš lxFilter.pas teisingai nupjaudavo >= priešdėlį ir nustatydavo ženklą 6, tada visada surėsdavo vtString DOPER su simboliais 100. Skaičių laikantis langelis su 250 niekada neatitinka tekstinio palyginimo su "100", tad iškrisdavo visos eilutės. Jokios išimtis, jokios diagnostikos, jokio Excel remonto raginimo. Nuo v2.384.45 taisyklė sąmoningai siaura: jei kriterijus prasideda palyginimo operatoriumi ir likutis pagal invariantinės kultūros taisykles išsiskaito į skaičių, HotXLS rašo vtIEEENumber DOPER su tuo pačiu ženklu. Nuoga reikšmė be operatoriaus lieka string pavidalu, nes taip pats Excel saugo elementą, parinktą iš išskleidžiamojo sąrašo

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;

  // Laukas 2 = antras A1:B100 stulpelis (API pusėje numeruojama nuo 1)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (IEEE skaičius), grbitSgn = 6 (>=)
  // Iki pataisymo: DataType = 6 (string), kuris neatitiko nieko
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS tą patį >=100 AutoFilter kriterijų rašo kaip vtString DOPER, neatitantį nė vieno skaitinio langelio, arba kaip vtIEEENumber DOPER su grbitSgn 6, kurį Excel skaitine prasme įvertina prieš sumą 250 — tai tylioji nulio eilučių nesėkmė, kurią CreateFilterDoper sutvarkė v2.384.45
Baitai abu kartus buvo validūs BIFF8 — pasikeitė tik operando tipo baitas, todėl Excel atvėrė failą, parodė kriterijų išskleidžiamajame sąraše ir vis tiek atitiko nulį eilučių

Išskaidyme ir slepiasi likę aštrūs kraštai. Operandas eina per TryStrToFloat su tašku kaip dešimtainiu skirtuku, tad '>=1.5' virsta skaičiumi, o '>=1,5' lieka string DOPER ir vėl tyliai neatitinka nieko, ką besakytų Windows lokalė. Datos — tas pats spąstai kitu apsireiškimu: '>=2026-01-01' nėra skaičius, tad užrašomas kaip tekstas, nors Excel datų langelius laiko serijiniais numeriais. Lygybei su skaičiumi ir '=100', ir skaitinis Variant, pavyzdžiui 100, duoda IEEE DOPER su ženklu 2, o nuoga eilutė '100' — tekstinę atitiktį. Skaitinius operandus formuokite kode, o ne formatuokite žmonėms skaityt:

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

  // Slenkstis su trupmena: visada formatuokite su tašku
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Datos: lyginkite su serijiniu numeriu, kurį Excel saugo langelyje.
  // Delphi TDateTime datoms po 1900 metų kovo lygus 1900 sistemos serijai
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Kaip AND ir OR sujungia dvi sąlygas?

AUTOFILTER grbit wJoin bitai yra 0 AND ir 1 OR, o HotXLS šias dvi konstantas laikė apsukęs iki v2.384.18. Tarpinis filtras, pavyzdžiui ne mažiau kaip 100 ir mažiau kaip 500, būdavo išsaugomas kaip ne mažiau kaip 100 arba mažiau kaip 500, kas praktikoje atitinka kiekvieną skaičių ir atrodo taip, lyg filtras iš viso nebūtų pritaikytas. Viešosios operatorių konstantos prideda antrą perkeliamo kodo pavojų. HotXLS xlAnd yra 0, o xlOr — 1, kai tuo tarpu Excel automation jas numeruoja 1 ir 2. XlAutoFilterOperator yra paprastas Byte, tad kodas, išverstas iš VBA makroso su literaliais skaičiais, ramiai sukompiliuojasi, ir literalus 1, kuris COM reiškė AND, čia reiškia OR. Naudokite vardines konstantas, ir problema negali atsirasti:

// Suma tarp 100 (įskaitant) ir 500 (neįskaitant)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 diske
  Assert(Criteria2.grbitSgn = 1);      // 1 = mažiau nei
end;
HotXLS wJoin bitų išdėstymas AutoFilter kriterijams, kur 0 sujungia du DOPER su AND, o 1 su OR, skaičių ašis, rodanti, kaip iki v2.384.18 apsukti konstantos išplėtė tarpinį filtrą į viską atitinkantį OR, ir xlAnd bei xlOr numeravos konfliktas su Excel automation
Apsukti wJoin konstantos tarpinį filtrą pavertė viską atitinkančiu, o išverstas VBA vis tiek kompiliuojasi, nes XlAutoFilterOperator yra paprastas Byte — literalus 1, kuris COM automation reiškė AND, čia reiškia OR

Boolean, tušti langeliai ir 255 simbolių riba

Boolean kriterijus saugomas kaip Bes reikšmė ([MS-XLS] §2.5.10), o Bes reikšmės baitą bBoolErr deda pirmiau, o fError vėliavėlę — antra. HotXLS iki v2.384.18 juos rašydavo atvirkščia tvarka, tad TRUE filtras į klaidos vėliavėlę įrašydavo 1, ir Excel kriterijų skaitydavo kaip klaidos kodą. Rašytojas ir skaitytojas buvo apsukti kartu, todėl HotXLS savo failus abi kryptim pralenkdavo be nusiskundimų, o Excel nesutardavo — primenama, kad saviuderintas dvipusis praėjimas nieko neįrodo apie atitiktį specifikacijai. Tuštiems langeliams operandas apskritai nereikia: vienas '=' duoda visus tuščius atitinkantį DOPER ($0C), o vienas '<>' — visus ne tuščius atitinkantį DOPER ($0E)

String kriterijai DOPER išdėstyme užkliūva į kietą ribą. cch ilgio laukas yra vienas baitas, tad string operandas negali viršyti 255 simbolių, o CreateFilterDoper ilgesnį tekstą nukerpa nupjovęs operatorių, vietoj to, kad leistų ilgio baitui persisuoti ir išbalansuoti įrašo uodegą. Kerpa tyliai, ir filtras ilgo aprašymo stulpelyje gali atitikti kitaip nei visas jūsų perduotas tekstas. BIFF8 uodegoje kiekviena string saugoma kaip vieno baito vėliavėlė ir paskui einantys UTF-16 kodo vienetai, o deklaruotas įrašo dydis turi tuos baitus suskaičiuoti tiksliai — ta pati apskaitos drausmė, apie kurią rašo kaip BIFF įrašo ilgio deklaracijos nuklysta Delphi XLS rašytoje

Kodėl antras ApplyAutoFilter kvietimas ištrina pirmąjį?

Kiekvienas ApplyAutoFilter kvietimas perapibrėžia visą filtro diapazoną, tad išgyvena tik paskutinio kvietimo kriterijus. Viduje jis kviečia SetAutoFilter, kuris prieš perkurdamas diapazoną išvalo kiekvieną lauką, ir tai teisinga vienam stulpeliui bei netikėta dviem. Norėdami filtruoti kelis stulpelius, ApplyAutoFilter kvieskite kartą, kad užtvirtintumėte diapazoną ir pirmąjį kriterijų, o kitus pridėkite per AutoFilterColumns.SetFieldCriteria, kuris diapazono ir kitų laukų neliečia. Abu keliai lauko numerį už diapazono ribų ignoruoja nesukeldami išimties, tad tikrinkitės perskaitydami atgal, idealiu atveju atvėrę išsaugotą failą iš naujo:

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

Turėkite omenyje, kad AUTOFILTER įrašas yra saugomas apibrėžimas: HotXLS kriterijus užrašo, bet klasikiniame XLS darbalapyje jų nevertina, tad srautui, kuriam serverio pusėje reikia atitinkančių eilučių, ten jas tenka skaičiuoti pačiam, o XLSX fasadas siūlo eilutės lygio vertinimą, kaip rodo HotXLS duomenų tikrinimas, AutoFilter ir lentelės Delphi programoje. Kai Excel jau paslepia eilutes, bet kokie bendrieji po diapazonu priklauso nuo to, kaip SUBTOTAL ir AGGREGATE elgiasi su paslėptomis ir per filtra praėjusiomis eilutėmis — kita vieta, kur skaitinis filtras, tyliai neatitinkantis nieko, pasirodo kaip neteisingas skaičius

HotXLS BIFF8 XLS ir XLSX darbaknyges iš Delphi ir C++Builder skaito bei rašo natyviai, įskaitant AutoFilter kriterijus su skaitiniais, Boolean ir AND/OR DOPER, kuriuos Excel vertina taip, kaip numatyta. Ypatybes, leidimus ir bandomąją versiją rasite HotXLS Delphi skaičiuoklės komponento puslapyje