HotXLS es un componente de hoja de cálculo nativo para Delphi y C++Builder, y desde la versión 2.209.0 puede responder la pregunta que Excel normalmente guarda para sí mismo: para esta celda exacta, qué reglas de formato condicional se activan, y a qué relleno, fuente, barra de datos o icono se resuelven. Esa respuesta es lo que necesitas en el momento en que tu salida es un informe HTML, un PDF, o una cuadrícula que pintas tú mismo
Este es un problema distinto de crear reglas. Dos notas anteriores cubren el lado de autoría: formato condicional y estilos de texto enriquecido trata sobre adjuntar reglas y formatos diferenciales a un rango, y partición de formatos condicionales anclados trata sobre lo que le pasa al rango de una regla cuando se insertan o eliminan filas y columnas. Ambos son estructurales. Este trata sobre semántica: dado un libro que ya lleva reglas, calcular el resaltado
Por qué el formato de archivo no te dice qué celdas se iluminan
La respuesta corta es que ECMA-376 e ISO 29500-1 definen almacenamiento, no evaluación. Un elemento conditionalFormatting (§18.3.1.18) lleva un sqref y una lista de hijos cfRule (§18.3.1.10), y cada regla lleva un type, un operator opcional, una priority, una bandera stopIfTrue, uno o dos hijos formula, y para las familias visuales un conjunto de umbrales cfvo. Cada uno de esos describe fielmente lo que el usuario configuró, y ninguno es un algoritmo. Para la mitad de los tipos de regla esa brecha no importa: cellIs con operator="greaterThan" significa mayor que, y containsText significa que la subcadena está presente. La brecha se abre en las familias agregadas. ¿Una regla top10 con rank="10" y percent="1" sobre 27 celdas numéricas pobladas resalta cuántas celdas? Dos coma siete no es un número. Redondear, truncar, o hacia arriba — la especificación guarda silencio, y elegir mal significa que tu PDF no coincide con el libro que el cliente tiene abierto al lado
Reglas de una sola celda y dónde se detiene TCondFormatRule.Evaluate
HotXLS tomó primero la mitad barata. TCondFormatRule.Evaluate en lxCondFormat.pas, agregado en 2.199.0, responde si una regla se activa para una celda sin saber nada sobre el resto del rango. Maneja los ocho operadores de comparación BIFF detrás de cellIs (between, notBetween, equal, notEqual, greater, less, greaterEqual, lessEqual), reglas expression de forma libre evaluadas en la celda para que las referencias relativas se rebaseen correctamente, los cuatro predicados de texto, y los predicados de blancos y errores. Los umbrales vienen de FFormula1 y FFormula2 resueltos a través de TXLSCalculator.GetRangeValue en la posición de la celda, y los límites invertidos se intercambian en vez de rechazarse
var
I: Integer;
Rule: TCondFormatRule;
Value: Variant;
begin
Value := Sheet.Cells[Row, Col].Value;
for I := 0 to CondFormat.RuleCount - 1 do
begin
Rule := CondFormat.Rule(I);
// Single-cell verdict only. Aggregate and visual kinds answer False.
if Rule.Evaluate(Calculator, SheetIndex, Row, Col, Value) then
ApplyHighlight(Row, Col, Rule.Style);
end;
end;
La parte honesta de ese método es lo que se niega a adivinar. top10, aboveAverage, belowAverage, duplicateValues y uniqueValues devuelven False, no porque sean difíciles sino porque son indecidibles desde una celda — cada una necesita una estadística sobre todo el dominio. Las cuatro familias visuales, dataBar, colorScale2, colorScale3 e iconSet, devuelven False por una razón diferente: nunca producen un booleano en absoluto, producen una carga útil de renderizado, y un tipo de retorno booleano es la forma equivocada para ellas
¿Cómo evita un evaluador a nivel de hoja volver a escanear la hoja?
Calculando cada cantidad compartida una vez, en la construcción, y nunca más. TXLSXConditionalFormatEvaluator en lxHandleX.pas es una instantánea inmutable para una hoja de cálculo, construida a través de TXLSXWorksheet.CreateConditionalFormatEvaluator, y todo su diseño es una defensa contra la implementación ingenua donde cada celda pintada dispara un escaneo completo del rango
Cuatro cosas suceden en el constructor. Cada sqref multi-área distinto se analiza exactamente una vez en un TXlsxCfRangeSnapshot, así que diez reglas que comparten un rango comparten un análisis y un pase de estadísticas. Ese pase transmite media, desviación poblacional, mínimo y máximo sobre las celdas pobladas en un solo recorrido, y solo conserva un arreglo numérico ordenado cuando una regla Top/Bottom o percentil realmente lo necesita para estadísticas de orden. Las claves de duplicados y únicos se construyen de forma segura para Unicode y se ordenan por lotes una vez en vez de por búsqueda. Luego el eje de filas se corta en bandas en cada límite de área, así que EvaluateCell busca binariamente una banda y solo visita reglas cuyos rangos posiblemente puedan alcanzar esa fila
El cuarto es el que más importa a escala. Una fórmula de regla relativa como =A1>AVERAGE($A$1:$A$100) significa algo diferente en cada celda del dominio, y la implementación obvia compila un árbol de sintaxis nuevo por celda. TXlsxCfRulePlan lo compila una vez y vuelve a evaluar el mismo árbol a través de desplazamientos de coordenadas reversibles, lo que preserva el comportamiento de anclaje de Excel sin una asignación de árbol de sintaxis por celda. Las reglas luego se apilan por priority, y una coincidencia en una regla cuya StopIfTrue está establecida rompe el bucle, exactamente como Excel evalúa con cortocircuito
var
Evaluator: TXLSXConditionalFormatEvaluator;
Res: TXLSXCfCellResult;
begin
Evaluator := Sheet.CreateConditionalFormatEvaluator;
try
if Evaluator.EvaluateCell(Row, Col, Res) then
begin
if Res.HasFillColor then
Canvas.Brush.Color := TColor(Res.FillColor);
if Res.HasIcon then
// IconIndex is zero-based inside Res.IconSetType
DrawIcon(Res.IconSetType, Res.IconIndex, Res.IconCount);
if Res.HasDataBar then
// DataBarAxis and DataBarEnd are normalised to 0..1
DrawBar(Res.DataBarAxis, Res.DataBarEnd, Res.DataBarColor);
if not Res.ShowCellValue then
Exit; // showValue="0" on the rule hides the number
end;
finally
Evaluator.Free;
end;
end;
¿Cómo redondea Excel realmente una regla del Top 10 por ciento?
Trunca hacia abajo, con un mínimo de uno, e incluye empates en el corte. Eso no está escrito en ningún lugar de ISO 29500-1 — se fijó probando Excel 16 con libros construidos a mano y leyendo qué celdas resaltaba la aplicación. HotXLS implementa exactamente eso: el conteo de rango es Floor(Count * Min(Rank, 100) / 100), elevado a 1 cuando cae en cero, limitado al conteo poblado, y el valor de corte luego se compara con >= así que cada celda igual al límite se resalta incluso cuando eso excede el conteo solicitado. Veintisiete valores y una regla del 10 por ciento resaltan dos celdas, más cualquier celda adicional empatada con la segunda
Las reglas de superior al promedio escondían una segunda ambigüedad: aboveAverage con stdDev="1" selecciona celdas una desviación estándar por encima de la media, pero la desviación de muestra y de población difieren por la corrección de Bessel y discrepan visiblemente en rangos pequeños, que es exactamente donde se usa el formato condicional. Excel 16 usa la desviación poblacional, y HotXLS la iguala, con la bandera equalAverage volviendo inclusiva la comparación estricta solo cuando ninguna banda de desviación está en juego. Las reglas de duplicados y únicos resultaron activar la identidad de clave en su lugar. Si una celda tiene el número 100 y otra tiene el texto "100", Excel las trata como la misma clave duplicada, así que HotXLS normaliza el texto numérico al espacio de clave numérica en vez de comparar cadenas crudas. Las celdas en blanco son el caso espejo: una celda vacía verdadera participa en el conteo del rango pero no se le da estilo a sí misma, así que las celdas vacías en una columna no se iluminan todas como duplicadas entre sí
Escalas de color y conjuntos de iconos: interpolación y reglas de límite
Las familias visuales se resuelven a números listos para renderizar en vez de booleanos, y su comportamiento de borde se fijó de la misma manera. Para una escala de color con umbrales numéricos explícitos, HotXLS limita la fracción de posición al intervalo cerrado de cero a uno, luego interpola por canal con truncamiento en vez de redondeo — un valor por debajo del mínimo obtiene el color mínimo en vez de uno extrapolado, una escala de tres paradas elige su par comparando contra la parada media, y una escala degenerada cuyos dos extremos llevan el mismo umbral colapsa al color superior en vez de dividir por cero. Los conjuntos de iconos necesitaron el tipo opuesto de cuidado, porque cada cfvo después del primero lleva su propia rigurosidad de comparación: HotXLS lee ThresholdEqualsInclude por umbral y aplica >= o > según corresponda, avanzando hacia arriba para que el umbral satisfecho más alto gane el índice del icono. Un conjunto invertido voltea el índice resuelto en vez de los umbrales, las sobrescrituras por icono pueden extraer un glifo de una familia diferente, y cualquier umbral inválido aborta la regla en vez de producir un icono incorrecto que parece plausible
Alimentar una cuadrícula, una exportación HTML y un PDF desde un resultado
Porque EvaluateCell devuelve un TXLSXCfCellResult completamente resuelto — color de relleno y fuente diferencial con el tinte del tema ya aplicado, negrita, cursiva, subrayado, id de formato numérico, extensiones de barra positiva y negativa direccionales, posición del eje, familia e índice de icono — cada consumidor lee el mismo registro y ninguno necesita entender los internos de la regla. HotXLS usa esa única ruta para exportación HTML, exportación PDF y el visor interactivo, que es la única forma práctica de evitar que tres renderizadores se desvíen entre sí. La versión 2.210.0 lo conectó a TXLSWorkbookViewer, que cachea un evaluador preparado por hoja activa y lo reutiliza a través de desplazamiento, selección y repintado, liberándolo cuando el libro o la hoja cambian — reconstruir la instantánea en cada Paint derrotaría todo el diseño en tiempo de construcción. Ese caché es también por qué existe TXLSWorkbookViewer.RefreshConditionalFormats: la instantánea es inmutable, así que si mutas el libro adjunto en su lugar, las estadísticas agregadas y los umbrales resueltos quedan obsoletos hasta que lo llames
// Editing behind a live viewer: the cached snapshot must be invalidated.
Sheet := Viewer.XlsxWorkbook.Sheets[1];
Sheet.Cells[5, 2].Value := 4200; // changes mean, min, max, ranking
Viewer.RefreshConditionalFormats; // drop evaluator, repaint
Lo que el evaluador no hará por ti
Vale la pena mencionar tres límites con claridad. El clásico TCondFormatRule.Evaluate de una sola celda y el TXLSXConditionalFormatEvaluator a nivel de hoja son superficies diferentes con capacidades diferentes, y el de una sola celda deliberadamente rechaza las familias agregadas y visuales en vez de aproximarlas — si necesitas Top/Bottom o una escala de color, construye el evaluador. Los períodos de fecha relativos dependen del reloj de la máquina en el momento de la evaluación, así que una regla timePeriod se renderiza diferente en un PDF generado hoy y uno generado la próxima semana, lo cual es un comportamiento correcto y aun así un ticket de soporte esperando a suceder si se espera que tu archivo sea estable a nivel de bytes. El tercero es gramatical en vez de técnico: la gramática de fórmula de formato condicional prohíbe referencias estructuradas a tablas, así que una regla no puede dirigirse a una columna de tabla por nombre como puede hacerlo una fórmula de hoja, y eso es una restricción del formato en vez de la implementación
Si estás construyendo salida de informes, un pipeline de exportación o una cuadrícula personalizada que tiene que coincidir con Excel celda por celda, el mismo resultado resuelto también impulsa la cuadrícula de hoja de cálculo VCL personalizada descrita en otro lugar de este blog. La documentación completa de la API, el modelo de reglas y las descargas de prueba para el componente de hoja de cálculo HotXLS para Delphi están disponibles en la página de producto