Articol tehnic

Criterii AutoFilter DOPER BIFF8 în Delphi cu HotXLS

HotXLS salvează fiecare criteriu AutoFilter BIFF8 ca o înregistrare AUTOFILTER care transportă două structuri DOPER de câte 10 octeți, iar tipul DOPER decide cum compară Excel. Din v2.384.45, TXLSWorksheet.ApplyAutoFilter scrie o comparație precum '>=100' ca un DOPER numeric IEEE, astfel încât Excel potrivește celule numerice în loc să compare text. Raportul de bug care a declanșat schimbarea era scurt și exasperant: un export nocturn aplica un filtru pe o coloană de sume, fișierul se deschidea fără nicio plângere, săgeata dropdown arăta criteriul, iar filtrul nu potrivea niciun rând. Nimic nu era corupt. Octeții erau BIFF8 valid, doar valid de soiul greșit, și exact această clasă de defecțiuni este cea pe care o parcurge articolul, împreună cu două greșeli mai vechi la nivel de octeți rezolvate în v2.384.18

Ce stochează de fapt un AutoFilter BIFF8?

Un AutoFilter BIFF8 este un set de trei tipuri de înregistrări, nu unul singur, și doar înregistrarea per câmp păstrează criteriile. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) înregistrează câte coloane acoperă intervalul filtrului. FILTERMODE ($009B) este un marker fără corp pe care HotXLS îl emite doar când cel puțin un câmp are un criteriu activ. Apoi fiecare câmp activ primește propria înregistrare AUTOFILTER ($009E, §2.4.6): un index de câmp cu bază zero, un cuvânt grbit ale cărui doi biți de jos sunt wJoin, două DOPER-uri de exact 10 octeți fiecare și o coadă opțională care păstrează caracterele oricărui DOPER de tip șir. Indexul de câmp are bază zero pe disc deși ApplyAutoFilter numără câmpurile de la 1, lucru care contează prima dată când vânați o înregistrare într-un hex dump. Primul octet al fiecărui DOPER, vt, spune ce fel de operand urmează:

  • $04 este un double IEEE 754 stocat în restul de 8 octeți, adică modul în care Excel stochează o comparație numerică
  • $06 este un șir a cărui lungime stă într-un singur octet cch, caracterele în sine fiind împinse în coada înregistrării
  • $08 este o valoare Bes, un Boolean sau cod de eroare împachetat în doi octeți
  • $0C și $0E nu transportă niciun operand și înseamnă potrivirea tuturor celulelor goale, respectiv a tuturor celulelor negoale

Al doilea octet, grbitSgn, poartă comparația: 1 până la 6 se mapează la <, =, <=, >, <> și >=. HotXLS ține ambii octeți vizibili după aceea prin AutoFilterColumns, ale cărui elemente expun Criteria1 și Criteria2 ca obiecte TXLSAutofilterDOPER cu DataType, grbitSgn și Value, astfel încât puteți verifica cu assert ce va fi scris în loc să ghiciți

Anatomia înregistrării AUTOFILTER din HotXLS cu indexul de câmp cu bază zero, cuvântul grbit ale cărui doi biți de jos poartă wJoin și două structuri DOPER de 10 octeți al căror octet vt selectează un operand numeric IEEE, șir, Boolean Bes, gol sau negol, în timp ce grbitSgn codifică operatorul de comparație pe care Excel îl aplică
Fiecare înregistrare AUTOFILTER transportă două DOPER-uri de 10 octeți, iar octetul vt decide dacă Excel compară un criteriu ca număr, text, Boolean sau test de celule goale — citiți-le pe ambele înapoi prin AutoFilterColumns înainte de salvare

De ce un filtru '>=100' nu potrivea niciun rând în Excel?

Filtrul nu potrivea nimic pentru că operandul era stocat ca text, iar Excel compară un DOPER de tip șir cu celula ca text. Înainte de v2.384.45, CreateFilterDoper din lxFilter.pas elimina corect prefixul >= și seta semnul la 6, apoi construiea mereu un DOPER vtString care ținea caracterele 100. O celulă numerică cu 250 nu satisface niciodată o comparație text cu "100", deci fiecare rând cădea. Nicio excepție, niciun diagnostic, niciun prompt de reparare din partea Excel. Regula de la v2.384.45 este îngustă din intenție: dacă criteriul începe cu un operator de comparație și restul se parsează ca număr după regulile de cultură invariantă, HotXLS scrie un DOPER vtIEEENumber cu același semn. O valoare simplă, fără operator, rămâne în forma de șir, pentru că exact așa stochează Excel însuși un element ales din lista dropdown

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;

  // Câmpul 2 = a doua coloană din A1:B100 (bazat pe 1 pe partea de API)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+: DataType = 4 (număr IEEE), grbitSgn = 6 (>=)
  // Înainte de reparare: DataType = 6 (șir), care nu potrivea nimic
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS scrie același criteriu AutoFilter >=100 ca un DOPER vtString care nu potrivește nicio celulă numerică sau ca un DOPER vtIEEENumber cu grbitSgn 6 pe care Excel îl evaluează numeric față de suma 250, adică defecțiunea silențioasă de zero rânduri pe care CreateFilterDoper a rezolvat-o în v2.384.45
Octeții erau BIFF8 valid de ambele ori — s-a schimbat doar octetul de tip al operandului, motiv pentru care Excel deschidea fișierul, arăta criteriul în dropdown și tot potrivea zero rânduri

Parsarea este locul unde mai rămân muchiile ascuțite. Operandul trece prin TryStrToFloat cu punct ca separator zecimal, deci '>=1.5' devine număr, iar '>=1,5' rămâne DOPER de tip șir și iarăși nu potrivește nimic în tăcere, orice ar spune locația Windows. Datele calendaristice sunt aceeași capcană într-un alt costum: '>=2026-01-01' nu este număr, deci se scrie ca text, în timp ce Excel ține celulele de date ca numere seriale. Pentru egalitate pe un număr, atât '=100', cât și un Variant numeric precum 100 produc un DOPER IEEE cu semnul 2, în timp ce șirul simplu '100' produce o potrivire text. Construiți operanzii numerici în cod, nu-i formatați pentru oameni:

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

  // Prag cu fracție: formatați mereu cu punct
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Date calendaristice: comparați cu numărul serial pe care Excel îl stochează în celulă.
  // Un TDateTime din Delphi este egal cu serialul din sistemul 1900 pentru date după martie 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Cum îmbină AND și OR două condiții?

Biții wJoin ai grbit-ului AUTOFILTER sunt 0 pentru AND și 1 pentru OR, iar HotXLS avea acele două constante inversate până la v2.384.18. Un filtru de tip between precum cel puțin 100 și sub 500 era salvat ca cel puțin 100 sau sub 500, ceea ce în practică potrivește fiecare număr și arată ca și cum filtrul pur și simplu nu s-a aplicat. Constantele publice de operator adaugă un al doilea pericol de portare. În HotXLS, xlAnd este 0 și xlOr este 1, pe când automatizarea Excel le numerotează 1 și 2. XlAutoFilterOperator este un simplu Byte, deci un cod tradus dintr-o macrocomandă VBA cu numere literale se compilează curat, iar un literal 1 care însemna AND în COM acum înseamnă OR. Folosiți constantele cu nume și problema nu poate apărea:

// Sumă între 100 (inclusiv) și 500 (exclusiv)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 pe disc
  Assert(Criteria2.grbitSgn = 1);      // 1 = mai mic decât
end;
Aranjamentul biților wJoin din HotXLS pentru criterii AutoFilter unde 0 îmbină două DOPER-uri cu AND și 1 cu OR, o dreaptă a numerelor care arată cum au lătit constantele inversate de dinainte de v2.384.18 un filtru between într-un OR care potrivește totul și ciocnirea de numerotare xlAnd xlOr cu automatizarea Excel
Constantele wJoin inversate au transformat un filtru between într-unul care potrivește fiecare număr, iar VBA-ul tradus tot se compilează pentru că XlAutoFilterOperator este un simplu Byte — un literal 1 care însemna AND sub automatizarea COM înseamnă aici OR

Booleeni, celule goale și plafonul de 255 de caractere

Un criteriu Boolean este stocat ca o valoare Bes ([MS-XLS] §2.5.10), iar Bes pune octetul de valoare bBoolErr primul și indicatorul fError al doilea. HotXLS îi scria în ordine inversă înainte de v2.384.18, deci un filtru pentru TRUE punea 1 în indicatorul de eroare, iar Excel citea criteriul ca cod de eroare. Scriitorul și cititorul erau inversate împreună, motiv pentru care HotXLS făcea round-trip pe propriile fișiere fără plângeri în timp ce Excel nu era de acord — un memento că un round-trip autoconsecvent nu dovedește nimic despre conformitatea cu specificația. Celulele goale nu au nevoie deloc de operand: trecerea lui '=' singur produce un DOPER de potrivire a tuturor celulelor goale ($0C), iar '<>' singur un DOPER de potrivire a tuturor celulelor negoale ($0E)

Criteriile de tip șir lovesc o limită dură în aranjamentul DOPER. Câmpul de lungime cch este un singur octet, deci un operand de tip șir nu poate depăși 255 de caractere, iar CreateFilterDoper trunchiază textul mai lung după eliminarea operatorului în loc să lase octetul de lungime să facă wrap și să desincronizeze coada înregistrării. Trunchierea este silențioasă, iar un filtru pe o coloană de descrieri lungi se poate potrivi diferit de textul complet pe care l-ați transmis. În BIFF8 coada stochează fiecare șir ca un indicator de un octet urmat de unități de cod UTF-16, iar dimensiunea declarată a înregistrării trebuie să numere exact acei octeți — aceeași disciplină de evidență acoperită în cum derivează declarațiile de lungime ale înregistrărilor BIFF într-un scriitor XLS în Delphi

De ce un al doilea apel ApplyAutoFilter șterge primul?

Fiecare apel ApplyAutoFilter redefineste întregul interval al filtrului, deci doar criteriul din ultimul apel supraviețuiește. În interior apelează SetAutoFilter, care golește fiecare câmp înainte să reconstruiască intervalul, ceea ce este corect pentru o coloană și surprinzător pentru două. Ca să filtrați mai multe coloane, apelați ApplyAutoFilter o dată ca să stabiliți intervalul și primul criteriu, apoi adăugați celelalte prin AutoFilterColumns.SetFieldCriteria, care lasă intervalul și celelalte câmpuri în pace. Ambele căi ignoră un număr de câmp din afara intervalului fără să ridice excepții, deci verificați citind înapoi, în mod ideal după redeschiderea fișierului salvat:

Sh.ApplyAutoFilter('A1:D500', 1, 'North');                     // interval + câmpul 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);

Țineți minte că înregistrarea AUTOFILTER este o definiție stocată: HotXLS scrie criteriile și nu le evaluează pe foaia de lucru XLS clasică, deci un pipeline care are nevoie de rândurile potrivite pe server trebuie să le calculeze el însuși acolo, în timp ce fațada XLSX oferă evaluare la nivel de rând, așa cum se arată în validarea datelor, AutoFilter și tabelele HotXLS în Delphi. Odată ce Excel ascunde rânduri, orice totaluri de sub interval depind de cum tratează SUBTOTAL și AGGREGATE rândurile ascunse și filtrate, locul următor unde un filtru numeric care nu potrivește nimic în tăcere apare ca un număr greșit

HotXLS citește și scrie registre de lucru BIFF8 XLS și XLSX nativ din Delphi și C++Builder, inclusiv criterii AutoFilter cu DOPER-uri numerice, Boolean și AND/OR pe care Excel le evaluează cum a fost intenționat. Consultați componenta de spreadsheet HotXLS pentru Delphi pentru funcții, ediții și o descărcare de încercare