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 a la pregunta que Excel normalmente se guarda para sí mismo: para esta celda exacta, qué reglas de formato condicional se disparan, 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 rejilla que pintas tú mismo
Este es un problema distinto al de crear reglas. Dos notas anteriores cubren el lado de la 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 ocurre al rango de una regla cuando se insertan o eliminan filas y columnas. Ambos son estructurales. Este artículo 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, un indicador stopIfTrue, uno o dos hijos formula, y para las familias visuales un conjunto de umbrales cfvo. Cada uno de ellos describe fielmente lo que configuró el usuario, y ninguno de ellos 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, ¿cuántas celdas resalta? Dos coma siete no es un número. Redondear, truncar hacia abajo 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 se ocupó primero de la mitad barata. TCondFormatRule.Evaluate en lxCondFormat.pas, añadido en la 2.199.0, responde si una regla se dispara para una celda sin saber nada del resto del rango. Gestiona los ocho operadores de comparación BIFF detrás de cellIs (entre, no entre, igual, no igual, mayor, menor, mayor o igual, menor o igual), reglas expression de forma libre evaluadas en la celda para que las referencias relativas se reajusten correctamente, los cuatro predicados de texto, y los predicados de vacíos y errores. Los umbrales provienen 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 lugar 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 sola celda —cada una de ellas necesita una estadística sobre todo el dominio—. Las cuatro familias visuales, dataBar, colorScale2, colorScale3 e iconSet, devuelven False por un motivo distinto: nunca producen ningún booleano en absoluto, producen una carga 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 explorar la hoja?
Calculando cada cantidad compartida una sola 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 una exploración completa del rango
Cuatro cosas ocurren 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 una pasada de estadísticas. Esa pasada recorre en un único paso la media, la desviación poblacional, el mínimo y el máximo sobre las celdas pobladas, y solo conserva un array numérico ordenado cuando una regla Top/Bottom o de percentil realmente necesita estadísticos de orden. Las claves de duplicados y únicos se construyen de forma segura para Unicode y se ordenan por lotes una sola vez en lugar de por búsqueda. Después el eje de filas se corta en bandas en cada límite de área, así que EvaluateCell busca una banda mediante búsqueda binaria y solo visita las reglas cuyos rangos puedan posiblemente 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 distinto 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 mediante 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 se estratifican entonces por priority, y una coincidencia en una regla cuyo StopIfTrue está activado interrumpe el bucle, exactamente como Excel corta el circuito
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 de Top 10 por ciento?
Trunca hacia abajo, con un mínimo de uno, e incluye los empates en el punto de corte. Eso no está escrito en ningún lugar de la ISO 29500-1 —se fijó sondeando Excel 16 con libros construidos a mano y observando qué celdas resaltaba la aplicación—. HotXLS implementa exactamente eso: el recuento de rango es Floor(Count * Min(Rank, 100) / 100), elevado a 1 cuando cae en cero, limitado al recuento poblado, y el valor de corte se compara entonces con >= así que toda celda igual al límite se resalta incluso cuando eso supera el recuento solicitado. Veintisiete valores y una regla del 10 por ciento resaltan dos celdas, más cualquier otra celda empatada con la segunda
Las reglas de por-encima-de-la-media escondían una segunda ambigüedad: aboveAverage con stdDev="1" selecciona celdas una desviación típica por encima de la media, pero la desviación muestral y la poblacional difieren por la corrección de Bessel y discrepan de forma visible 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 el indicador equalAverage haciendo inclusiva la comparación estricta solo cuando no hay ninguna banda de desviación en juego. Las reglas de duplicados y únicos activaron en cambio la identidad de clave. Si una celda contiene el número 100 y otra contiene el texto "100", Excel las trata como la misma clave de duplicado, así que HotXLS normaliza el texto numérico al espacio de claves numéricas en lugar de comparar cadenas en bruto. Las celdas vacías son el caso espejo: una celda verdaderamente vacía participa en el recuento del rango pero no se estiliza a sí misma, así que las celdas vacías de una columna no se iluminan todas como duplicados 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 lugar de booleanos, y su comportamiento en los límites 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, y luego interpola por canal con truncamiento en lugar de redondeo —un valor por debajo del mínimo obtiene el color mínimo en lugar de uno extrapolado, una escala de tres puntos elige su par comparando contra el punto medio, y una escala degenerada cuyos dos extremos llevan el mismo umbral colapsa al color superior en lugar 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 > en consecuencia, recorriendo hacia arriba para que el umbral satisfecho más alto gane el índice del icono. Un conjunto invertido voltea el índice resuelto en lugar de los umbrales, las anulaciones por icono pueden extraer un glifo de una familia distinta, y cualquier umbral no válido aborta la regla en lugar de producir un icono incorrecto de aspecto plausible
Alimentar una rejilla, una exportación HTML y un PDF desde un único resultado
Como EvaluateCell devuelve un TXLSXCfCellResult completamente resuelto —color de relleno y fuente diferencial con el matiz de 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 las reglas. HotXLS usa esa misma ruta para la exportación a HTML, la exportación a PDF y el visor interactivo, que es la única forma práctica de evitar que tres renderizadores se desincronicen. La versión 2.210.0 lo conectó a TXLSWorkbookViewer, que almacena en caché un evaluador preparado por cada hoja activa y lo reutiliza a través del desplazamiento, la selección y el repintado, liberándolo cuando cambia el libro o la hoja —reconstruir la instantánea en cada Paint anularía todo el diseño en tiempo de construcción—. Esa caché es también el motivo por el que existe TXLSWorkbookViewer.RefreshConditionalFormats: la instantánea es inmutable, así que si mutas el libro adjunto in situ, las estadísticas agregadas y los umbrales resueltos quedan obsoletos hasta que la llamas
// 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 señalar con claridad tres límites. El clásico TCondFormatRule.Evaluate de una sola celda y el TXLSXConditionalFormatEvaluator a nivel de hoja son superficies distintas con capacidades distintas, y el de una sola celda declina deliberadamente las familias agregadas y visuales en lugar de aproximarlas —si necesitas Top/Bottom o una escala de color, construye el evaluador—. Los periodos de fecha relativos dependen del reloj de la máquina en el momento de la evaluación, así que una regla timePeriod se renderiza de forma distinta en un PDF generado hoy y otro generado la semana que viene, lo cual es un comportamiento correcto y sigue siendo un ticket de soporte esperando a ocurrir si se espera que tu archivo sea estable byte a byte. El tercero es gramatical más que técnico: la gramática de fórmulas de formato condicional prohíbe las referencias de tabla estructuradas, así que una regla no puede dirigirse a una columna de tabla por nombre como puede hacerlo una fórmula de hoja de cálculo, y eso es una restricción del formato y no de la implementación
Si estás construyendo una salida de informe, un pipeline de exportación o una rejilla personalizada que tenga que coincidir con Excel celda a celda, el mismo resultado resuelto también impulsa la rejilla VCL de hoja de cálculo personalizada descrita en otro lugar de este blog. La documentación completa de la API, el modelo de reglas y las descargas de prueba del componente de hoja de cálculo para Delphi HotXLS están disponibles en la página del producto