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:
$04yra IEEE 754 double, saugomas likusiuose 8 baituose, — taip Excel saugo skaitinį palyginimą$06yra string, kurio ilgis telpa vienamecchbaite, o patys simboliai nustumti į įrašo uodegą$08yra Bes reikšmė — Boolean arba klaidos kodas, supakuotas į du baitus$0Cir$0Eoperando 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
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;
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;
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