Artículo técnico

Formato condicional y texto enriquecido de HotXLS en Delphi

Una regla de formato condicional en OOXML son dos cosas distintas bajo un mismo nombre. La condición (una comparación, una fórmula, una coincidencia de texto) decide qué celdas cumplen. La apariencia (un registro de formato diferencial, dxf en términos de ECMA-376) decide qué aspecto tienen esas celdas. El diálogo de Excel oculta la costura obligándole a rellenar ambas a la vez. HotXLS no. Cree una regla cellIs desde Delphi y omita el estilo, y la regla es válida, el rango es correcto, la fórmula se evalúa como verdadera exactamente en las celdas correctas, y nada cambia de color, porque la instrucción de la regla era «verdadero, no pintar nada». Esa brecha entre condición y consecuencia es lo primero que hay que hacer bien, y explica la mayoría de las reglas que parecen correctas en Administrar reglas pero no resaltan nada

HotXLS escribe formato condicional de forma nativa tanto en ficheros BIFF8 .xls como en OOXML .xlsx, y hace lo mismo con los fragmentos de texto enriquecido y con un modelo de estilos de celda agrupados en pools. Las tres funciones comparten más cableado del que sugiere la superficie plana de la API, y los puntos donde la salida se desvía de la intención suelen ser las juntas entre ellas

Una condición necesita una consecuencia: el estilo dxf

En la hoja XLSX, las reglas de comparación provienen de AddConditionalFormat, que toma un rango, un operador de TXLSXCfOperator y una fórmula o un literal, y devuelve el índice de la nueva regla dentro de la colección ConditionalFormats de la hoja. El objeto de regla en ese índice expone una propiedad Style, y ahí es donde vive el resaltado. Asígnele un relleno y las celdas que cumplen toman el relleno. Déjela sin tocar y habrá construido la regla invisible descrita arriba

Diagrama de una regla cellIs de HotXLS construida desde Delphi en dos mitades: AddConditionalFormat devuelve un índice de regla para la condición, ConditionalFormats[Idx].Style.SetFillBgColor aporta la consecuencia dxf, y una regla cuyo estilo nunca se fija valida sin problema mientras no pinta nada
La condición decide qué celdas cumplen y el estilo dxf decide qué aspecto tienen, así que omitir el estilo construye la regla invisible
var
  Book: TXLSXWorkbook;
  Sheet: TXLSXWorksheet;
  Idx: Integer;
begin
  Book := TXLSXWorkbook.Create;
  try
    Book.Open('kpi.xlsx');
    Sheet := Book.Sheets[0];

    // Varianza negativa: relleno rojo claro
    Idx := Sheet.AddConditionalFormat('D2:D200', xlsxCfOpLessThan, '0');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    // Los ID de pedido duplicados se marcan de la misma forma
    Idx := Sheet.AddCondFormatDuplicateValues('A2:A200');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFEB9C);

    // Regla de fórmula personalizada: resaltar filas donde el real no llega al 90 % del objetivo
    Idx := Sheet.AddCondFormatExpression('B2:B200', '$C2<$B2*0.9');
    Sheet.ConditionalFormats[Idx].Style.SetFillBgColor($FFFFC7CE);

    Book.SaveAs('kpi-flagged.xlsx');
  finally
    Book.Free;
  end;
end;

Los colores aquí son valores ARGB de 32 bits, así que $FFFFC7CE es el «rojo claro» de Excel que conoce del diálogo, con un byte alfa totalmente opaco delante del RGB. Todos los tipos de regla que se disparan por una condición por celda siguen la misma forma de crear y luego dar estilo. Los comparadores de texto (AddCondFormatContainsText, AddCondFormatBeginsWith, AddCondFormatEndsWith) devuelven un índice al que da estilo después, y lo mismo hacen AddCondFormatTop10, AddCondFormatAboveAverage y los detectores de celdas en blanco y de errores. Aprenda el patrón una vez y toda la familia de texto y comparación se comporta igual

Las barras de datos, escalas de color y conjuntos de iconos se pintan solos

Los tipos de regla visuales funcionan al revés. Llevan su apariencia dentro de la definición de la regla e ignoran por completo la propiedad Style. Asigne un relleno a una regla de barra de datos y no ocurre nada, lo que parece un fallo hasta que la taxonomía encaja: AddCondFormatDataBar toma el color de la barra como argumento directo, las escalas de color de dos y tres puntos toman sus colores extremos de la misma manera, y AddCondFormatIconSet selecciona uno de los 26 tipos de conjuntos de iconos, como icsTrafficLights3. Aquí no hay ningún registro de estilo aparte que olvidar, porque no hay ningún registro de estilo aparte en absoluto

Los parámetros que merecen reflexión en estas llamadas son los anclajes de valor, tipados como TXLSCfValueKind. El extremo de una barra o de una escala puede situarse en el mínimo o el máximo del rango, en un número literal, en un porcentaje o un percentil, o en el resultado de una fórmula. Los valores predeterminados, mínimo del rango y máximo del rango, se comportan bien con datos de demostración ordenados y luego le traicionan con datos reales con valores atípicos: un único valor desbocado estira la escala y aplasta todas las demás barras hasta un muñón. Cuando un panel está pensado para leerse a lo largo de varios periodos, ancle los extremos a números fijos o a percentiles, de modo que media barra en marzo signifique la misma cantidad que media barra en abril. Una barra autoescalada solo es comparable consigo misma

El escritor XLS cubre cuatro tipos de regla, y no más

El lado BIFF8 heredado no es un espejo reducido del lado XLSX; es un subconjunto deliberado. La fachada XLS puede crear exactamente cuatro formas de regla condicional, barras de datos, escalas de dos colores, escalas de tres colores y conjuntos de iconos, emitidas como registros CF12 en el flujo. No tiene API de creación para reglas cellIs, de expresión ni de texto. Las reglas de esos tipos que ya viven en un fichero que abre se leen, se conservan y se vuelven a escribir sin cambios, así que abrir y volver a guardar el .xls de un cliente nunca daña el formato que traía. Lo que no puede hacer es generar resaltado por umbral desde cero en un .xls. Las opciones ahí son simularlo con rellenos de celda ordinarios calculados en código, o hacer que el entregable sea un .xlsx, donde toda la familia de reglas está disponible

Esta es una restricción que hay que resolver antes de que exista la capa de datos, no después, porque cambia la decisión de formato de fichero para cualquier cosa con forma de panel. Un equipo que eligió .xls por compatibilidad y después especifica un informe de KPI con umbrales cellIs ha elegido dos cosas que no encajan, y el momento más barato para darse cuenta es la decisión del formato y no tres semanas después de empezar a construir

Apilamiento de reglas, prioridad y rangos solapados

Los paneles reales rara vez ejecutan una sola regla por rango. Una columna de varianza puede llevar una barra de datos para la magnitud, una regla cellIs para el umbral duro y una regla de expresión a nivel de fila por encima de ambas para las escaladas. Cada TXLSXConditionalFormat expone un valor Priority, y Excel resuelve las reglas en competencia en orden de prioridad. Cuando dos reglas quieren pintar la misma celda, el ganador lo decide un número que usted fija, no el orden en que un revisor se desplace por casualidad en el diálogo Administrar reglas

Trate la prioridad como un programa de dibujo trata el orden z. Asígnela a propósito allí donde dos reglas puedan alcanzar las mismas celdas, y deje huecos entre los valores para que una regla posterior encaje sin renumerar el resto. Donde las reglas no puedan colisionar, digamos una barra de datos confinada a la columna E y una regla de texto confinada a la columna G, el orden de creación basta y la prioridad no merece atención. Dedique esa atención a los límites de los rangos, porque los errores caros aquí casi nunca son inversiones de prioridad. Son rangos como B2:B200 en un informe que creció hasta 350 filas, donde la cola sin cubrir se renderiza como celdas planas que parecen exactamente datos sanos. Derive todos los rangos de regla del mismo valor de recuento de fila final que gobierna las series de gráficos y los rangos de validación en el resto del libro, y la cola deja de caerse

Hay un hábito de verificación que se paga solo. Tras la generación, abra el fichero en Excel, seleccione el rango con formato y recorra Administrar reglas una vez por cada cambio de plantilla. El formato condicional es una de las pocas áreas donde el único renderizador autoritativo es la aplicación que consume el fichero, así que una prueba unitaria sobre el XML demuestra que la regla se escribió, no que Excel la pinte como usted pretendía. Un minuto de inspección visual cierra esa brecha

Texto enriquecido: muchos formatos dentro de una celda

Una celda de texto enriquecido en el modelo XLSX contiene una lista de fragmentos, donde cada fragmento es un tramo de texto más sus propios atributos de fuente. La lista se construye aparte como un objeto TXLSXRichText, se le añaden fragmentos y después se adjunta el conjunto a una celda. La regla de propiedad es la parte que muerde. Asignar a Cell.RichText cede la propiedad de ese objeto a la celda, y la celda lo libera durante su propia destrucción. Libérelo usted también y tendrá una doble liberación, de las que permanecen silenciosas durante la ejecución que las causó y afloran como un cierre inesperado en algún lugar sin relación mucho más tarde

Diagrama de fragmentos de texto enriquecido de HotXLS en Delphi: asignar un objeto TXLSXRichText a Cell.RichText traslada la propiedad a la celda de modo que un segundo Free corrompe el heap mucho después, y el color de un fragmento solo se respeta después de desactivar ColorIsAuto
La propiedad de la lista de fragmentos pasa a la celda al asignarla, y una asignación de color solo se mantiene cuando ColorIsAuto está desactivado
var
  Rich: TXLSXRichText;
  Run: TXLSXRichTextRun;
begin
  Rich := TXLSXRichText.Create;
  Rich.AddRunText('Status: ');
  Run := Rich.AddRunText('OVERDUE');
  Run.Bold := True;
  Run.Color := $FFC00000;
  Run.ColorIsAuto := False;
  Run := Rich.AddRunText(' (escalated to regional manager)');
  Run.Italic := True;
  Sheet.Cells[2, 7].RichText := Rich;   // la propiedad pasa a la celda: no hacer Free
end;

El ColorIsAuto := False explícito no es un adorno opcional. Un fragmento lleva una marca de color automático, y una asignación de color solo se respeta una vez desactivada esa marca. Fije Color y olvide ColorIsAuto y el fragmento sale en negrita pero obstinadamente negro, sin ningún error que señale la causa. Los fragmentos también admiten tachado, las variantes de subrayado y la alineación vertical para superíndice y subíndice, mientras que PlainText aplana toda la lista de vuelta a una única cadena cuando necesita exportar o comparar el contenido de texto

El texto enriquecido a nivel de celda es exclusivo de XLSX. La fachada XLS no tiene API pública para escribirlo, aunque los fragmentos están disponibles allí en comentarios y cuadros de texto mediante TextRuns, y las cadenas enriquecidas leídas de un .xls existente sobreviven intactas a un viaje de ida y vuelta. La conclusión es la misma que con el formato condicional: todo lo que mezcle formatos dentro de una celda pertenece al escritor XLSX

El pool de estilos y el error de uno que llega a producción

El estilo de celda normal en el modelo XLSX pasa por colecciones agrupadas en el libro. Fonts.Add, Fills.AddSolid y Borders.Add registran cada uno una definición y devuelven su índice en el pool. Esos índices empiezan en 0. Las propiedades del lado de la celda que los consumen, como FontIndex, reservan el 0 para «predeterminado», así que el valor que asigna a una celda es el índice del pool más uno:

Diagrama del error de uno en el pool de estilos XLSX de HotXLS: Fonts.Add devuelve un índice de pool que empieza en 0 mientras que el FontIndex de la celda empieza en 1 con el 0 reservado para el predeterminado, así que omitir el más uno renderiza en silencio todos los encabezados sin estilo
Los índices del pool empiezan en cero y los índices de celda reservan el cero para el predeterminado, así que el lado de la celda siempre suma uno
HeaderFont := Book.Fonts.Add('Calibri', 11, True, False);  // índice del pool, desde 0
for Col := 1 to 6 do
  Sheet.Cells[1, Col].FontIndex := HeaderFont + 1;          // índice de celda, desde 1

Omita el + 1 y todos los encabezados vuelven a la fuente predeterminada. No hay excepción ni aviso, solo un libro que parece que nadie le dio estilo. El error de segundo orden se esconde en el bucle: llamar a Fonts.Add una vez por fila. Las definiciones de fuente idénticas se deduplican, así que el fichero no se corrompe, pero el trabajo se desperdicia, y el pool de alineaciones en particular devuelve un objeto nuevo en cada llamada en lugar de plegar los duplicados. Construya el puñado de estilos una vez antes del bucle y reutilice sus índices. En informes de cien mil filas ese único cambio es una de las palancas tratadas en ajuste de rendimiento de libros grandes con HotXLS. Cuando solo necesita un aspecto semántico de serie, ambas fachadas exponen ApplyBuiltinStyle sobre rangos, que corresponde a los estilos integrados Bueno, Malo, Neutral y de énfasis de Excel sin que usted toque los pools en absoluto

El formato condicional, el texto enriquecido y los estilos agrupados son la última milla de un informe, aplicados después de que el modelo de datos y el diseño estén decididos, y esas etapas anteriores son el tema de generación de informes basada en plantillas con HotXLS. La referencia completa de reglas, fragmentos y estilos está en la página de producto del componente HotXLS para Delphi