Article technique

Critères DOPER AutoFilter BIFF8 en Delphi avec HotXLS

HotXLS sauvegarde chaque critère AutoFilter BIFF8 dans un enregistrement AUTOFILTER qui transporte deux structures DOPER de 10 octets, et le type DOPER décide comment Excel compare. Depuis la v2.384.45, TXLSWorksheet.ApplyAutoFilter écrit une comparaison telle que '>=100' comme un DOPER nombre IEEE, si bien qu'Excel compare les cellules numériques au lieu de comparer du texte. Le rapport de bogue qui a déclenché le changement était court et rageant : un export nocturne appliquait un filtre sur une colonne de montants, le fichier s'ouvrait sans se plaindre, la flèche du menu déroulant montrait le critère, et le filtre ne trouvait zéro ligne. Rien n'était corrompu. Les octets formaient du BIFF8 valide, simplement du valide de la mauvaise sorte, et c'est cette classe de défaillance que cet article parcourt, avec deux erreurs plus anciennes au niveau de l'octet corrigées en v2.384.18

Que stocke réellement un AutoFilter BIFF8 ?

Un AutoFilter BIFF8 est un ensemble de trois types d'enregistrements, pas un seul, et seul l'enregistrement propre à chaque champ porte des critères. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) note combien de colonnes couvre la plage du filtre. FILTERMODE ($009B) est un marqueur sans corps que HotXLS n'émet que lorsqu'au moins un champ a un critère actif. Ensuite chaque champ actif reçoit son propre enregistrement AUTOFILTER ($009E, §2.4.6) : un index de champ en base 0, un mot grbit dont les deux bits de poids faible sont wJoin, deux DOPER d'exactement 10 octets chacun, et une queue optionnelle qui porte les caractères de tout DOPER chaîne. L'index de champ est en base 0 sur disque alors que ApplyAutoFilter numérote les champs à partir de 1, ce qui compte la première fois que vous partez à la chasse à un enregistrement dans un dump hexadécimal. Le premier octet de chaque DOPER, vt, dit quel genre d'opérande suit :

  • $04 est un double IEEE 754 stocké dans les 8 octets restants, c'est ainsi qu'Excel stocke une comparaison numérique
  • $06 est une chaîne dont la longueur tient dans un unique octet cch, les caractères eux-mêmes étant poussés dans la queue de l'enregistrement
  • $08 est une valeur Bes, un booléen ou un code d'erreur empaqueté dans deux octets
  • $0C et $0E ne transportent aucun opérande et signifient tout-vide et tout-non-vide

Le second octet, grbitSgn, porte la comparaison : 1 à 6 correspondent à <, =, <=, >, <> et >=. HotXLS garde les deux octets visibles après coup via AutoFilterColumns, dont les éléments exposent Criteria1 et Criteria2 comme objets TXLSAutofilterDOPER avec DataType, grbitSgn et Value, si bien que vous pouvez poser des assertions sur ce qui sera écrit au lieu de deviner

Anatomie de l'enregistrement AUTOFILTER de HotXLS montrant l'index de champ en base 0, le mot grbit dont les deux bits de poids faible portent wJoin, et deux structures DOPER de 10 octets dont l'octet vt sélectionne un opérande nombre IEEE, chaîne, booléen Bes, vide ou non-vide tandis que grbitSgn encode l'opérateur de comparaison qu'Excel applique
Chaque enregistrement AUTOFILTER transporte deux DOPER de 10 octets, et l'octet vt décide si Excel compare un critère comme nombre, texte, booléen ou test de vide — relisez les deux via AutoFilterColumns avant de sauvegarder

Pourquoi un filtre '>=100' ne trouvait-il aucune ligne dans Excel ?

Le filtre ne trouvait rien parce que l'opérande était stocké en texte, et Excel compare un DOPER chaîne contre la cellule en tant que texte. Avant la v2.384.45, CreateFilterDoper dans lxFilter.pas retirait correctement le préfixe >= et fixait le signe à 6, puis construisait toujours un DOPER vtString portant les caractères 100. Une cellule numérique valant 250 ne satisfait jamais une comparaison texte contre "100", donc chaque ligne tombait. Aucune exception, aucun diagnostic, aucune invite de réparation de la part d'Excel. La règle depuis la v2.384.45 est étroite à dessein : si le critère commence par un opérateur de comparaison et que le reste s'analyse comme un nombre selon les règles de culture invariante, HotXLS écrit un DOPER vtIEEENumber avec le même signe. Une valeur nue sans opérateur garde la forme chaîne, parce que c'est ainsi qu'Excel lui-même stocke un élément choisi dans la liste déroulante

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;

  // Champ 2 = seconde colonne de A1:B100 (base 1 côté API)
  Sh.ApplyAutoFilter('A1:B100', 2, '>=100');

  Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
  // v2.384.45+ : DataType = 4 (nombre IEEE), grbitSgn = 6 (>=)
  // Avant la correction : DataType = 6 (chaîne), qui ne trouvait rien
  Assert(Doper.DataType = 4);

  Wb.SaveAs('orders.xls');
end;
HotXLS écrit le même critère AutoFilter >=100 comme un DOPER vtString qui ne trouve aucune cellule numérique ou comme un DOPER vtIEEENumber avec grbitSgn 6 qu'Excel évalue numériquement contre le montant 250, la défaillance silencieuse à zéro ligne que CreateFilterDoper a corrigée en v2.384.45
Les octets étaient du BIFF8 valide dans les deux cas — seul l'octet de type d'opérande changeait, voilà pourquoi Excel ouvrait le fichier, affichait le critère dans le menu déroulant et ne trouvait quand même zéro ligne

L'analyse est là où vivent les arêtes restantes. L'opérande passe par TryStrToFloat avec un point comme séparateur décimal, donc '>=1.5' devient un nombre tandis que '>=1,5' reste un DOPER chaîne et ne trouve encore rien en silence, quelle que soit la locale Windows. Les dates sont le même piège sous un autre costume : '>=2026-01-01' n'est pas un nombre, donc il est écrit en texte, alors qu'Excel conserve les cellules de dates comme numéros de série. Pour une égalité sur un nombre, '=100' comme un Variant numérique tel que 100 produisent un DOPER IEEE avec le signe 2, tandis que la chaîne nue '100' produit une correspondance texte. Construisez les opérandes numériques dans le code plutôt que de les formater pour des humains :

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

  // Seuil avec fraction : formatez toujours avec un point
  Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));

  // Dates : comparez contre le numéro de série qu'Excel stocke dans la cellule.
  // Un TDateTime Delphi égale le numéro de série du système 1900 après mars 1900
  Since := EncodeDate(2026, 1, 1);
  Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
    xlAnd, Unassigned);
end;

Comment AND et OR joignent-ils deux conditions ?

Les bits wJoin du grbit AUTOFILTER valent 0 pour AND et 1 pour OR, et HotXLS avait ces deux constantes inversées jusqu'à la v2.384.18. Un filtre de type entre-deux, comme au moins 100 et en dessous de 500, était sauvegardé comme au moins 100 ou en dessous de 500, ce qui en pratique trouve chaque nombre et ressemble à un filtre qui ne s'applique tout simplement pas. Les constantes publiques d'opérateur ajoutent un second danger de portage. Dans HotXLS, xlAnd vaut 0 et xlOr vaut 1, alors que l'automatisation Excel les numérote 1 et 2. XlAutoFilterOperator est un simple Byte, donc du code traduit depuis une macro VBA avec des nombres littéraux compile proprement, et un 1 littéral qui signifiait AND en COM signifie désormais OR. Utilisez les constantes nommées et le problème ne peut pas se produire :

// Montant entre 100 (inclus) et 500 (exclu)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');

with Sh.AutoFilterColumns.Find(3) do
begin
  Assert(Operator = xlAnd);            // wJoin = 0 sur disque
  Assert(Criteria2.grbitSgn = 1);      // 1 = inférieur à
end;
Disposition des bits wJoin de HotXLS pour les critères AutoFilter où 0 joint deux DOPER avec AND et 1 avec OR, une droite numérique montrant comment les constantes inversées d'avant la v2.384.18 élargissaient un filtre entre-deux en un OR qui trouve tout, et le conflit de numérotation xlAnd xlOr avec l'automatisation Excel
Des constantes wJoin inversées transformaient un filtre entre-deux en un filtre qui trouve chaque nombre, et du VBA traduit compile encore parce que XlAutoFilterOperator est un simple Byte — un 1 littéral qui signifiait AND sous l'automatisation COM signifie OR ici

Booléens, vides et le plafond de 255 caractères

Un critère booléen est stocké comme une valeur Bes ([MS-XLS] §2.5.10), et Bes place l'octet de valeur bBoolErr en premier et le drapeau fError en second. HotXLS les écrivait dans l'ordre inverse avant la v2.384.18, si bien qu'un filtre pour TRUE mettait 1 dans le drapeau d'erreur et qu'Excel lisait le critère comme un code d'erreur. Écriture et lecture étaient inversées ensemble, voilà pourquoi HotXLS faisait l'aller-retour sur ses propres fichiers sans se plaindre tandis qu'Excel n'était pas d'accord — rappel qu'un aller-retour auto-cohérent ne prouve rien quant à la conformité à la spécification. Les vides n'ont besoin d'aucun opérande : passer '=' seul produit un DOPER tout-vide ($0C) et '<>' seul un DOPER tout-non-vide ($0E)

Les critères chaînes butent sur une limite dure dans la disposition DOPER. Le champ de longueur cch est un octet unique, donc un opérande chaîne ne peut pas dépasser 255 caractères, et CreateFilterDoper tronque un texte plus long après avoir retiré l'opérateur plutôt que de laisser l'octet de longueur boucler et désynchroniser la queue de l'enregistrement. La troncature est silencieuse, et un filtre sur une longue colonne de descriptions peut trouver différemment du texte complet que vous avez passé. En BIFF8, la queue stocke chaque chaîne comme un drapeau d'un octet suivi d'unités de code UTF-16, et la taille déclarée de l'enregistrement doit compter ces octets exactement, la même discipline de comptabilité que couvre comment les déclarations de longueur d'enregistrements BIFF dérivent dans un générateur XLS Delphi

Pourquoi un second appel à ApplyAutoFilter efface-t-il le premier ?

Chaque appel à ApplyAutoFilter redéfinit toute la plage du filtre, si bien que seul le critère du dernier appel survit. En interne il appelle SetAutoFilter, qui efface chaque champ avant de reconstruire la plage, ce qui est correct pour une colonne et surprenant pour deux. Pour filtrer plusieurs colonnes, appelez ApplyAutoFilter une fois pour établir la plage et le premier critère, puis ajoutez les autres via AutoFilterColumns.SetFieldCriteria, qui laisse la plage et les autres champs tranquilles. Les deux chemins ignorent un numéro de champ hors plage sans lever d'exception, donc vérifiez en relisant, idéalement après avoir rouvert le fichier sauvegardé :

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

Gardez en tête que l'enregistrement AUTOFILTER est une définition stockée : HotXLS écrit les critères et ne les évalue pas sur la feuille XLS classique, donc un pipeline qui a besoin des lignes correspondantes côté serveur doit les calculer lui-même là-bas, tandis que la façade XLSX offre une évaluation au niveau des lignes comme le montre validation de données, AutoFilter et tables HotXLS en Delphi. Une fois qu'Excel cache effectivement des lignes, tous les totaux sous la plage dépendent de la façon dont SUBTOTAL et AGGREGATE traitent les lignes cachées et filtrées, qui est l'endroit suivant où un filtre numérique qui ne trouve rien en silence se manifeste comme un chiffre faux

HotXLS lit et écrit nativement des classeurs BIFF8 XLS et XLSX depuis Delphi et C++Builder, y compris des critères AutoFilter avec des DOPER numériques, booléens et AND/OR qu'Excel évalue comme prévu. Consultez le composant tableur HotXLS pour Delphi pour les fonctionnalités, les éditions et un téléchargement d'essai