O HotXLS grava cada critério de AutoFilter BIFF8 como um registo AUTOFILTER que transporta duas estruturas DOPER de 10 bytes, e o tipo de DOPER decide como o Excel compara. Desde a v2.384.45, o TXLSWorksheet.ApplyAutoFilter escreve uma comparação como '>=100' como um DOPER número IEEE, pelo que o Excel encontra células numéricas em vez de comparar texto. O relatório de bug que motivou a alteração era curto e desesperante: uma exportação noturna aplicava um filtro a uma coluna de valores, o ficheiro abria sem queixas, a seta do menu pendente mostrava o critério, e o filtro não apanhava linha nenhuma. Nada estava corrompido. Os bytes eram BIFF8 válido, apenas do tipo errado de válido, e é essa classe de falha que este artigo percorre, juntamente com dois erros mais antigos ao nível do byte corrigidos na v2.384.18
O que é que um AutoFilter BIFF8 realmente guarda?
Um AutoFilter BIFF8 é um conjunto de três tipos de registo, não um só, e só o registo por campo guarda critérios. O AUTOFILTERINFO ($009D, [MS-XLS] §2.4.8) regista quantas colunas o intervalo do filtro cobre. O FILTERMODE ($009B) é um marcador sem corpo que o HotXLS só emite quando pelo menos um campo tem um critério ativo. Depois cada campo ativo recebe o seu próprio registo AUTOFILTER ($009E, §2.4.6): um índice de campo de base zero, uma palavra grbit cujos dois bits baixos são o wJoin, dois DOPER de exatamente 10 bytes cada, e uma cauda opcional que guarda os caracteres de qualquer DOPER string. O índice de campo é de base zero no disco embora o ApplyAutoFilter numere os campos a partir de 1, o que importa na primeira vez que vai caçar um registo num hex dump. O primeiro byte de cada DOPER, o vt, diz que tipo de operando se segue:
$04é um double IEEE 754 guardado nos restantes 8 bytes, que é como o Excel guarda uma comparação numérica$06é uma string cujo comprimento vive num único bytecch, com os caracteres propriamente ditos empurrados para a cauda do registo$08é um valor Bes, um Boolean ou código de erro comprimido em dois bytes$0Ce$0Enão transportam operando e significam corresponder a todas as células vazias e a todas as não vazias
O segundo byte, o grbitSgn, guarda a comparação: 1 a 6 mapeiam para <, =, <=, >, <> e >=. O HotXLS mantém ambos os bytes visíveis depois do facto através do AutoFilterColumns, cujos itens expõem Criteria1 e Criteria2 como objetos TXLSAutofilterDOPER com DataType, grbitSgn e Value, por isso pode fazer assert sobre o que vai ser escrito em vez de adivinhar
Porque é que um filtro '>=100' não encontrava linhas no Excel?
O filtro não encontrava nada porque o operando era guardado como texto, e o Excel compara um DOPER string contra a célula como texto. Antes da v2.384.45, o CreateFilterDoper no lxFilter.pas retirava corretamente o prefixo >= e fixava o sinal em 6, mas depois construía sempre um DOPER vtString com os caracteres 100. Uma célula numérica com 250 nunca satisfaz uma comparação de texto contra "100", por isso todas as linhas caíam fora. Nenhuma exceção, nenhum diagnóstico, nenhum pedido de reparação por parte do Excel. A regra desde a v2.384.45 é limitada de propósito: se o critério começa por um operador de comparação e o restante analisa como número sob as regras de cultura invariante, o HotXLS escreve um DOPER vtIEEENumber com o mesmo sinal. Um valor nu sem operador mantém a forma string, porque é assim que o próprio Excel guarda um item escolhido da lista do menu pendente
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 coluna de A1:B100 (1-based do lado da API)
Sh.ApplyAutoFilter('A1:B100', 2, '>=100');
Doper := Sh.AutoFilterColumns.Find(2).Criteria1;
// v2.384.45+: DataType = 4 (número IEEE), grbitSgn = 6 (>=)
// Antes da correção: DataType = 6 (string), que não encontrava nada
Assert(Doper.DataType = 4);
Wb.SaveAs('orders.xls');
end;
A análise é onde vivem os restantes riscos. O operando passa pelo TryStrToFloat com ponto como separador decimal, por isso '>=1.5' vira número e '>=1,5' permanece um DOPER string e volta a não encontrar nada em silêncio, diga o que disser a configuração regional do Windows. As datas são a mesma armadilha com outro traje: '>=2026-01-01' não é um número, por isso é escrito como texto, enquanto o Excel guarda as células de data como números de série. Para igualdade sobre um número, tanto '=100' como um Variant numérico como 100 produzem um DOPER IEEE com sinal 2, enquanto a string nua '100' produz uma correspondência de texto. Construa operandos numéricos em código em vez de os formatar para humanos:
var
Fmt: TFormatSettings;
Since: TDateTime;
begin
Fmt := TFormatSettings.Create;
Fmt.DecimalSeparator := '.';
// Limiar com fração: formatar sempre com ponto
Sh.ApplyAutoFilter('A1:D500', 3, '>' + FloatToStr(1499.5, Fmt));
// Datas: comparar contra o número de série que o Excel guarda na célula.
// Um TDateTime Delphi equivale ao serial do sistema 1900 para datas depois de Março de 1900
Since := EncodeDate(2026, 1, 1);
Sh.AutoFilterColumns.SetFieldCriteria(4, '>=' + IntToStr(Trunc(Since)),
xlAnd, Unassigned);
end;
Como é que AND e OR juntam duas condições?
Os bits wJoin do grbit do AUTOFILTER são 0 para AND e 1 para OR, e o HotXLS tinha essas duas constantes trocadas até à v2.384.18. Um filtro do tipo entre, como pelo menos 100 e abaixo de 500, era gravado como pelo menos 100 ou abaixo de 500, o que na prática encontra todos os números e parece que o filtro simplesmente não se aplicou. As constantes públicas de operador acrescentam um segundo perigo de porting. No HotXLS, xlAnd é 0 e xlOr é 1, ao passo que a automação do Excel os numera 1 e 2. O XlAutoFilterOperator é um Byte simples, por isso código traduzido de uma macro VBA com números literais compila sem aviso, e um literal 1 que significava AND em COM agora significa OR. Use as constantes nomeadas e o problema não pode surgir:
// Valor entre 100 (inclusive) e 500 (exclusive)
Sh.ApplyAutoFilter('A1:D500', 3, '>=100', xlAnd, '<500');
with Sh.AutoFilterColumns.Find(3) do
begin
Assert(Operator = xlAnd); // wJoin = 0 no disco
Assert(Criteria2.grbitSgn = 1); // 1 = menor que
end;
Booleans, células vazias e o teto de 255 caracteres
Um critério Boolean é guardado como um valor Bes ([MS-XLS] §2.5.10), e o Bes põe o byte de valor bBoolErr primeiro e a flag fError segundo. O HotXLS escrevia-os pela ordem oposta antes da v2.384.18, por isso um filtro para TRUE punha 1 na flag de erro e o Excel lia o critério como um código de erro. Escritor e leitor estavam trocados em conjunto, razão pela qual o HotXLS fazia round-trip dos seus próprios ficheiros sem queixas enquanto o Excel discordava, um lembrete de que um round-trip autoconsistente nada prova sobre a conformidade com a spec. Células vazias não precisam de operando nenhum: passar '=' sozinho produz um DOPER de encontrar todas as vazias ($0C) e '<>' sozinho um DOPER de encontrar todas as não vazias ($0E)
Critérios string esbarram num limite duro na disposição do DOPER. O campo de comprimento cch é um único byte, por isso um operando string não pode exceder 255 caracteres, e o CreateFilterDoper trunca texto mais comprido depois de retirar o operador em vez de deixar o byte de comprimento dar a volta e dessincronizar a cauda do registo. O truncamento é silencioso, e um filtro numa coluna de descrições compridas pode encontrar de forma diferente do texto completo que passou. No BIFF8 a cauda guarda cada string como uma flag de um byte seguida de unidades de código UTF-16, e o tamanho declarado do registo tem de contar esses bytes exatamente, a mesma disciplina de contabilidade coberta em como as declarações de comprimento de registo BIFF derivam num escritor XLS em Delphi
Porque é que uma segunda chamada ao ApplyAutoFilter apaga a primeira?
Cada chamada ao ApplyAutoFilter redefine o intervalo de filtro inteiro, por isso só o critério da última chamada sobrevive. Internamente chama o SetAutoFilter, que limpa todos os campos antes de reconstruir o intervalo, o que é correto para uma coluna e surpreendente para duas. Para filtrar várias colunas, chame o ApplyAutoFilter uma vez para estabelecer o intervalo e o primeiro critério, e depois acrescente os outros através do AutoFilterColumns.SetFieldCriteria, que deixa o intervalo e os outros campos em paz. Ambos os caminhos ignoram um número de campo fora do intervalo sem levantar exceção, por isso verifique lendo de volta, idealmente depois de reabrir o ficheiro gravado:
Sh.ApplyAutoFilter('A1:D500', 1, 'North'); // intervalo + 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);
Tenha em mente que o registo AUTOFILTER é uma definição armazenada: o HotXLS escreve os critérios e não os avalia na folha de cálculo XLS clássica, por isso um pipeline que precise das linhas correspondentes no servidor as tem de calcular lá próprio, enquanto a fachada XLSX oferece avaliação ao nível da linha como mostrado em validação de dados, AutoFilter e tabelas no HotXLS em Delphi. Quando o Excel efetivamente esconde linhas, quaisquer totais por baixo do intervalo dependem de como o SUBTOTAL e o AGGREGATE tratam linhas ocultas e filtradas, que é o próximo sítio onde um filtro numérico que silenciosamente não encontra nada aparece como um número errado
O HotXLS lê e escreve livros XLS BIFF8 e XLSX nativamente a partir de Delphi e C++Builder, incluindo critérios de AutoFilter com DOPER numéricos, Boolean e AND/OR que o Excel avalia como pretendido. Veja a página do componente de folhas de cálculo HotXLS para Delphi para funcionalidades, edições e download de avaliação