HotXLS guarda cada criterio de AutoFilter BIFF8 como un registro AUTOFILTER que lleva dos estructuras DOPER de 10 bytes, y el tipo de DOPER decide cómo compara Excel. Desde la v2.384.45, TXLSWorksheet.ApplyAutoFilter escribe una comparación como '>=100' como un DOPER de número IEEE, así que Excel matchea celdas numéricas en lugar de comparar texto. El reporte de bug que motivó el cambio era corto y exasperante: un export nocturno aplicaba un filtro sobre una columna de montos, el archivo abría sin quejarse, la flecha del dropdown mostraba el criterio, y el filtro matcheaba cero filas. Nada estaba corrupto. Los bytes eran BIFF8 válido, solo del tipo equivocado de válido, y esa es la clase de falla que este artículo recorre, junto con dos errores antiguos a nivel de bytes corregidos en la v2.384.18
¿Qué guarda realmente un AutoFilter de BIFF8?
Un AutoFilter de BIFF8 es un conjunto de tres tipos de registro, no uno solo, y solo el registro por campo guarda criterios. AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) anota cuántas columnas cubre el rango del filtro. FILTERMODE ($009B) es un marcador sin cuerpo que HotXLS emite solo cuando al menos un campo tiene un criterio activo. Después cada campo activo recibe su propio registro AUTOFILTER ($009E, §2.4.6): un índice de campo base cero, una palabra grbit cuyos dos bits bajos son wJoin, dos DOPERs de exactamente 10 bytes cada uno, y una cola opcional que guarda los caracteres de cualquier DOPER de string. El índice de campo es base cero en disco aunque ApplyAutoFilter numera los campos desde 1, algo que importa la primera vez que sale usted a cazar un registro en un hex dump. El primer byte de cada DOPER, vt, dice qué clase de operando viene después:
$04es un double IEEE 754 guardado en los 8 bytes restantes, que es como Excel guarda una comparación numérica$06es un string cuya longitud vive en un solo bytecch, con los caracteres mismos empujados a la cola del registro$08es un valor Bes, un Boolean o código de error empaquetado en dos bytes$0Cy$0Eno llevan operando y significan match con todas las celdas en blanco y con todas las que no lo están
El segundo byte, grbitSgn, guarda la comparación: 1 a 6 se mapean a <, =, <=, >, <> y >=. HotXLS mantiene ambos bytes visibles después del hecho vía AutoFilterColumns, cuyos ítems exponen Criteria1 y Criteria2 como objetos TXLSAutofilterDOPER con DataType, grbitSgn y Value, así que puede hacer assert sobre lo que se va a escribir en lugar de adivinar
¿Por qué un filtro '>=100' no matcheó ninguna fila en Excel?
El filtro no matcheó nada porque el operando quedó guardado como texto, y Excel compara un DOPER de string contra la celda como texto. Antes de la v2.384.45, CreateFilterDoper en lxFilter.pas quitaba bien el prefijo >= y fijaba el signo en 6, pero siempre construía un DOPER vtString con los caracteres 100. Una celda numérica con 250 jamás satisface una comparación de texto contra "100", así que todas las filas caían. Sin excepción, sin diagnóstico, sin aviso de reparación de parte de Excel. La regla desde la v2.384.45 es angosta a propósito: si el criterio arranca con un operador de comparación y el resto parsea como número bajo reglas invariantes de cultura, HotXLS escribe un DOPER vtIEEENumber con el mismo signo. Un valor sin operador conserva la forma de string, porque así es como el propio Excel guarda un ítem elegido de la lista del 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;
// Campo 2 = segunda columna de A1:B100 (base 1 del lado del API)
Sh.ApplyAutoFilter('A1:B100', 2, '>=100');
Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
// v2.384.45+: DataType = 4 (número IEEE), grbitSgn = 6 (>=)
// Antes del fix: DataType = 6 (string), que no matcheaba nada
Assert(Doper.DataType = 4);
Wb.SaveAs('orders.xls');
end;
El parseo es donde viven las aristas filosas restantes. El operando pasa por TryStrToFloat con punto como separador decimal, así que '>=1.5' se vuelve número y '>=1,5' se queda como DOPER de string y otra vez no matchea nada en silencio, diga lo que diga la configuración regional de Windows. Las fechas son la misma trampa con otro disfraz: '>=2026-01-01' no es un número, así que se escribe como texto, mientras Excel guarda las celdas de fecha como números seriales. Para igualdad sobre un número, tanto '=100' como un Variant numérico tal como 100 producen un DOPER IEEE con signo 2, mientras que el string pelado '100' produce un match de texto. Arme los operandos numéricos en código en lugar de formatearlos para humanos:
var
Fmt: TFormatSettings;
Since: TDateTime;
begin
Fmt := TFormatSettings.Create;
Fmt.DecimalSeparator := '.';
// Umbral con fracción: formatee siempre con punto
Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));
// Fechas: compare contra el número serial que Excel guarda en la celda.
// Un TDateTime de Delphi equivale al serial del sistema 1900 para fechas posteriores a marzo de 1900
Since := EncodeDate(2026, 1, 1);
Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
xlAnd, Unassigned);
end;
¿Cómo unen AND y OR dos condiciones?
Los bits wJoin del grbit de AUTOFILTER son 0 para AND y 1 para OR, y HotXLS tenía esas dos constantes invertidas hasta la v2.384.18. Un filtro estilo between como al menos 100 y menor que 500 se guardaba como al menos 100 o menor que 500, lo que en la práctica matchea todos los números y aparenta que el filtro simplemente no se aplicó. Las constantes públicas de operador suman un segundo peligro de porteo. En HotXLS, xlAnd es 0 y xlOr es 1, mientras que la automatización de Excel los numera 1 y 2. XlAutoFilterOperator es un Byte común, así que código traducido de una macro VBA con números literales compila sin reclamos, y un literal 1 que significaba AND en COM ahora significa OR. Use las constantes nombradas y el problema no puede aparecer:
// Monto entre 100 (inclusive) y 500 (exclusive)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');
with Sh.AutoFilterColumns.Find(3) do
begin
Assert(Operator = xlAnd); // wJoin = 0 en disco
Assert(Criteria2.grbitSgn = 1); // 1 = menor que
end;
Booleans, celdas en blanco y el techo de 255 caracteres
Un criterio Boolean se guarda como un valor Bes ([MS-XLS] §2.5.10), y Bes pone primero el byte de valor bBoolErr y después la bandera fError. HotXLS los escribía en el orden contrario antes de la v2.384.18, de modo que un filtro por TRUE ponía 1 en la bandera de error y Excel leía el criterio como un código de error. Escritor y lector estaban intercambiados en espejo, que es por qué HotXLS hacía round-trip de sus propios archivos sin quejarse mientras Excel discrepaba, un recordatorio de que un round-trip autoconsistente no prueba nada sobre conformidad con la especificación. Las celdas en blanco no necesitan operando para nada: pasar '=' solo produce un DOPER de match con todos los vacíos ($0C) y '<>' solo un DOPER de match con los no vacíos ($0E)
Los criterios de string chocan con un límite duro en el layout del DOPER. El campo de longitud cch es de un solo byte, así que un operando de string no puede superar los 255 caracteres, y CreateFilterDoper trunca el texto más largo después de quitar el operador en lugar de dejar que el byte de longitud dé la vuelta y desincronice la cola del registro. El truncado es silencioso, y un filtro sobre una columna de descripciones largas puede matchear distinto del texto completo que usted pasó. En BIFF8 la cola guarda cada string como una bandera de un byte seguida de unidades de código UTF-16, y el tamaño declarado del registro debe contar esos bytes con exactitud, la misma disciplina de contabilidad que cubre cómo se desvían las declaraciones de longitud de registro BIFF en un escritor XLS de Delphi
¿Por qué una segunda llamada a ApplyAutoFilter borra la primera?
Cada llamada a ApplyAutoFilter redefine todo el rango del filtro, así que solo sobrevive el criterio de la última llamada. Internamente llama a SetAutoFilter, que limpia cada campo antes de reconstruir el rango, y eso es correcto para una columna y sorprendente para dos. Para filtrar varias columnas, llame a ApplyAutoFilter una vez para establecer el rango y el primer criterio, y después sume los demás vía AutoFilterColumns.SetFieldCriteria, que deja el rango y los otros campos tranquilos. Ambos caminos ignoran un número de campo fuera del rango sin lanzar, así que verifique leyendo de vuelta, idealmente tras reabrir el archivo guardado:
Sh.ApplyAutoFilter('A1:D500', 1, 'North'); // rango + campo 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);
Tenga presente que el registro AUTOFILTER es una definición almacenada: HotXLS escribe los criterios y no los evalúa en la hoja XLS clásica, así que un pipeline que necesita las filas que matchean del lado del servidor tiene que computarlas por su cuenta allá, mientras que la fachada XLSX ofrece evaluación a nivel de fila como se muestra en validación de datos, AutoFilter y tablas de HotXLS en Delphi. Una vez que Excel sí oculta filas, cualquier total debajo del rango depende de cómo tratan SUBTOTAL y AGGREGATE las filas ocultas y filtradas, que es el siguiente lugar donde un filtro numérico que matchea nada en silencio aparece como un número equivocado
HotXLS lee y escribe libros BIFF8 XLS y XLSX en forma nativa desde Delphi y C++Builder, incluidos criterios de AutoFilter con DOPERs numéricos, Booleanos y AND/OR que Excel evalúa como corresponde. Vea el componente de hojas de cálculo HotXLS para Delphi para funcionalidades, ediciones y una descarga de prueba