HotXLS crea slicers de tabla de Excel desde Delphi y C++Builder con una sola llamada — Worksheet.Slicers.Add(Table, ColumnName) — y escribe las ocho ubicaciones del paquete que un slicer necesita: las partes de slicer y slicerCache, los dos overrides de content type, las relaciones de hoja y de workbook con sus bloques extLst correspondientes, y el ancla de dibujo
Un slicer parece un control que sueltas en una hoja. En el archivo no es nada de eso. Es un registro distribuido esparcido por dos partes nuevas, dos grafos de relaciones, tres bloques de extensión y un ancla de dibujo, y todo el conjunto se valida como una unidad cuando Excel abre el paquete. Que falte una pieza y no tienes un slicer degradado; tienes el diálogo de reparación, seguido de un workbook con el slicer eliminado en silencio. Ese modo de fallo es lo que vuelve molesto emitir slicers a mano, y es por qué la ingeniería interesante aquí es contabilidad en lugar de renderizado
¿Por qué un solo slicer toca ocho lugares del paquete?
Porque un slicer son tres conceptos separados que OPC obliga a vivir en tres casas separadas: el estado del filtro (a nivel de workbook), la vista (a nivel de hoja) y la forma (a nivel de dibujo). HotXLS escribe exactamente ocho puntos de aterrizaje por slicer de tabla, y cada uno de ellos soporta carga
xl/slicers/slicerN.xml— una parte por hoja, que contiene cada vista<slicer>de esa hoja en el namespacehttp://schemas.microsoft.com/office/spreadsheetml/2009/9/mainxl/slicerCaches/slicerCacheN.xml— a nivel de workbook, uno por columna de tabla con slicer, que lleva el binding real- un
<Override>en[Content_Types].xmlpara cada una de esas dos partes, usandoapplication/vnd.ms-excel.slicer+xmlyapplication/vnd.ms-excel.slicerCache+xml(ECMA-376 Parte 2 exige un content type para cada parte, y Excel rechaza el paquete sin ellos) - una relación de hoja del tipo
.../office/2007/relationships/slicer, más una extensión<x14:slicerList>dentro delextLstde la hoja que apunta a ese rid - una relación de workbook del tipo
.../office/2007/relationships/slicerCache, más una extensión<x15:slicerCaches>dentro delextLstdel workbook que lista los mismos rids - un ancla
mc:AlternateContenten el dibujo de la hoja que le da al slicer su posición y su tamaño - un bloque
<x15:tableSlicerCache>dentro de la parte de caché que lo ata a un id de tabla y un id de columna de tabla
Los bloques de extensión usan el mecanismo estándar extLst de ECMA-376 Parte 1 §18.2.10: cada <ext> lleva un atributo uri que identifica la feature, y los lectores que no reconocen el GUID se saltan el bloque. HotXLS distingue los slicers de tabla de los slicers de pivote solo por ese GUID — {3A4CF648-6AED-40f4-86FF-DC5316D8AED3} para el slicerList de tabla, {A8765BA9-456A-4dab-B4F3-ACF838C121DE} para el de pivote, y en correspondencia {46BE6895-7355-4a93-B00E-2C351335B9C9} y {BBE1A952-AA13-448e-AADC-164F8A28A991} para las dos extensiones de slicerCaches del workbook. El binding de tabla en sí vive bajo {2F2917AC-EB37-4324-AD4E-5DD8C200BD13}. Esas constantes se exponen en la unit como XlsxExtUriSlicerListX15, XlsxExtUriWbSlicerCachesX15 y XlsxExtUriTableSlicerCache, así que nada en el camino de escritura lleva un magic string
Qué partes pueden repetirse verbatim y cuáles deben reconstruirse
HotXLS responde con una sola regla: una parte puede repetir sus bytes originales solo si nada de lo que referencia puede renumerarse durante el guardado. Las cachés de slicers de tabla fallan esa prueba, así que HotXLS las reconstruye desde el modelo en cada guardado, sin condiciones — incluso cuando el usuario nunca tocó el slicer. La razón es tableId: el elemento <x15:tableSlicerCache> identifica su tabla por el atributo id de <table>, y HotXLS renumera los ids de tabla a nivel de todo el workbook cuando escribe. Una implementación temprana guardaba la parte de caché como bytes crudos al abrir y los devolvía al guardar, que es la estrategia correcta para la mayoría de las partes opacas. Producía archivos que se abrían limpio en el caso común y disparaban una reparación en el interesante: agrega una tabla en una hoja anterior, guarda, y la caché ahora apunta a un id de tabla que pertenece a otra cosa, o a nada. El bug es invisible en una regresión simple de abrir y guardar porque los ids casualmente no se mueven
Las cachés con forma de pivote toman la ruta opuesta. Su payload es <pivotTables> más un bloque <x14:data> que describe los items cacheados, el estado de orden y los índices de item — una estructura sin renumeración del lado de HotXLS, y cara de modelar con fidelidad. Esas se mantienen como bytes crudos y se repiten exactas, con TXLSXSlicerCache.IsPivot e IsTableCache distinguiendo las dos. Las vistas de slicer siguen una tercera política, con dirty tracking: un elemento <slicer> sin tocar y su ancla de dibujo se repiten verbatim, y en el momento en que cualquier vista de la hoja se modifica, toda la parte de la hoja se regenera desde el modelo. Esa es la forma general del criterio — replay verbatim donde las referencias son estables, rebuild desde el modelo donde el escritor reasigna identidad, dirty tracking donde ambas son aceptables — y aplica mucho más allá de los slicers, como trabajan en más profundidad las notas de round-trip sin pérdida sobre themes, bloques extLst y calcChain
Crear un slicer de tabla desde Delphi
TXLSXSlicers.Add es toda la API de creación. Dado un TXLSXTable y un nombre de columna materializa los ids de columna de la tabla, encuentra o crea el TXLSXSlicerCache de respaldo, reserva el defined name correspondiente, y ancla una vista a la derecha de la tabla. La sobrecarga de dos argumentos elige la ubicación; la de seis toma una caja inclusiva explícita basada en 1
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Slicer: TXLSXSlicer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('sales.xlsx');
Sheet := Book.Sheets[0];
// Ubicación por defecto: dos columnas despejadas de la tabla,
// 13 filas de alto con la altura de fila por defecto de 19pt
Slicer := Sheet.Slicers.Add(Sheet.Tables[0], 'Region');
Slicer.Caption := 'Sales region';
Slicer.StyleName := 'SlicerStyleLight1';
Slicer.ColumnCount := 2;
Book.SaveAs('sales-sliced.xlsx');
finally
Book.Free;
end;
end;
Dos reglas de nombres valen la pena porque son convenciones de Excel y no inventos de HotXLS. La caché se llama Slicer_<columna> con los caracteres que no son identificadores doblados a guiones bajos, y las colisiones agregan un sufijo numérico — Slicer_Region, después Slicer_Region1, y así. Y cada nombre de caché también se reserva como defined name del workbook cuya fórmula es #N/A, que es lo que hace TXLSXSlicers.ReserveCacheDefinedName. Excel crea ese nombre por sí mismo, y eso importa: los nombres de caché y los defined names comparten un namespace, así que un workbook que ya tenga un nombre llamado Slicer_Region colisionaría en el próximo guardado. Si construyes tablas y validación juntas, las interacciones de defined names en el artículo sobre validación de datos, AutoFilter y tablas cubren los casos vecinos. El compartir sigue la misma regla que usa Excel. TXLSXSlicerCaches.FindForTableColumn(ATableId, ATableColumnId) se consulta antes de crear una caché nueva, así que dos vistas de slicer sobre la misma columna de tabla — una en la hoja de dashboard, otra en la hoja de detalle — comparten una sola parte de caché y se mantienen sincronizadas en el filtro. Borrar una vista corre la comprobación inversa por cada hoja del workbook y tira la caché más su nombre #N/A reservado solo cuando la última vista que la referencia desaparece
¿Cómo se mantienen estables los ids de relación en ambos extremos?
Se mantienen estables porque HotXLS calcula el rid del slicer reproduciendo el orden exacto en que emite cada otra relación, y agrega las relaciones nuevas al final. Nada de lo que ya está en el archivo se mueve. Suena a over-engineering hasta que has depurado la alternativa. Los ids de relación en un .rels de hoja se referencian desde dentro de la parte de la hoja — por hyperlinks, por el dibujo, por los comments, por las partes de tabla — y ECMA-376 Parte 2 los resuelve posicionalmente dentro del grafo de relaciones, no por ninguna identidad estable. Si el escritor inserta la relación del slicer en el medio, cada referencia posterior se desplaza en uno, y la hoja empieza en silencio a apuntar a la parte equivocada. Por eso XlsxWorksheetSlicerRelNumber recorre la cadena determinística completa — hyperlinks externos, luego el VML de comments, comments, imágenes de header/footer, threaded comments, el dibujo, el fondo de la hoja, la configuración de impresora, tablas, tablas de pivote, query tables, celdas únicas de tabla — y solo entonces reclama el número siguiente para el slicer. XlsxWorkbookSlicerCacheFirstRid hace lo mismo del lado del workbook: hojas, sharedStrings, styles, VBA, enlaces externos, theme, connections, cachés de pivote, person, calcChain, metadatos de hoja, XML maps, custom XML, y después la corrida de slicerCache. El problema general está trabajado en la pieza sobre resolución de relaciones OPC. La extensión del lado del workbook se construye entonces desde esos números con XlsxBuildWorkbookSlicerCachesExtXml, y si el archivo de origen ya tenía bloques de extensión slicerCaches, primero se sacan del extLst crudo, para que el modelo los reemita contra los rids actuales en lugar de dejar una copia vieja atrás
El ancla AlternateContent y su fallback heredado
El ancla de dibujo es el único lugar donde el markup del slicer se pone genuinamente raro, y la rareza es deliberada. HotXLS emite la forma dentro de un elemento mc:AlternateContent según ECMA-376 Parte 3, con una rama Choice que toman Excel 2013 y posteriores y una rama Fallback para todo lo más viejo
// Lo que produce XlsxSlicerAnchorPayloadXml, estructuralmente:
//
// <mc:AlternateContent>
// <mc:Choice xmlns:sle15=".../drawing/2012/slicer" Requires="sle15">
// <xdr:graphicFrame>
// <a:graphicData uri=".../drawing/2010/slicer">
// <sle:slicer xmlns:sle=".../drawing/2010/slicer" name="Region"/>
// </a:graphicData>
// </xdr:graphicFrame>
// </mc:Choice>
// <mc:Fallback><xdr:sp>...notice shape...</xdr:sp></mc:Fallback>
// </mc:AlternateContent>
Mira los dos namespaces. El token Requires="sle15" resuelve al namespace de slicers de 2012, que es la compuerta de versión — un lector que no entienda los slicers de 2012 debe saltarse la rama Choice completa. Pero el graphicData/@uri y el elemento sle:slicer adentro usan el namespace 2010, porque ese es el identificador del lado del dibujo que el graphic frame siempre usó. Invertirlos produce un archivo que Excel abre sin quejarse y después renderiza como un marco vacío, que es un síntoma memorablemente inútil. La rama Fallback guarda un rectángulo liso con un cuerpo de texto que explica que los slicers de tabla no se soportan en esta versión — eso es lo que un lector de la era 2007 dibuja en lugar del control, y es por qué un Excel viejo puede hacer round trip del workbook sin destruir nada. TXLSXSlicer expone la geometría como una caja inclusiva basada en 1 a través de SetBounds(ARow1, ACol1, ARow2, ACol2) y las propiedades individuales FromRow / ToCol, más AnchorType para el comportamiento habitual de mover y dimensionar con las celdas. HasAnchor es False para un caso específico: un slicer anidado dentro de una forma de grupo. Esos se repiten con su ancla de grupo verbatim y HotXLS deliberadamente no sintetiza un segundo ancla para ellos, porque hacerlo emitiría la forma dos veces
Editar un slicer existente
var
Slicer: TXLSXSlicer;
Cache: TXLSXSlicerCache;
begin
Slicer := Sheet.Slicers.FindByName('Region');
if Slicer <> nil then
begin
Slicer.SetBounds(2, 8, 16, 11); // basado en 1, inclusivo
Slicer.ShowCaption := 1; // -1 = atributo omitido
Slicer.LockedPosition := 1;
Slicer.RowHeightEmu := 19 * XlsxEmuPerPoint; // 241300 EMU
Cache := Slicer.Cache;
if Cache <> nil then
begin
Cache.SortOrder := 'descending';
Cache.CrossFilter := 'showItemsWithNoData';
end;
end;
// Quitar la última vista sobre una columna elimina su caché
// y el defined name #N/A reservado junto con ella
Sheet.Slicers.Delete(Sheet.Slicers.IndexByName('Region'));
Las propiedades enteras de tres estados merecen una nota, porque parecen un accidente de modelado y no lo son. ShowCaption, LockedPosition, ColumnCount, StartItem y CustomListSort todas usan -1 para significar que el atributo estaba ausente en el archivo de origen y debe seguir ausente al escribir, con 0 y 1 como valores explícitos. Excel trata el ausente y el explícitamente por defecto de forma distinta en algunos lugares, y preservar esa distinción es lo que mantiene limpio un diff de bytes de un guardado sin tocar. RowHeight y ColumnWidth se mantienen como strings crudos por una razón relacionada: Excel escribe decimales en esos atributos EMU, y reparsearlos por un entero perdería precisión en el round-trip. RowHeightEmu es el accessor entero de conveniencia sobre el mismo almacenamiento, con XlsxSlicerDefaultRowHeight en 241300 EMU — 19 puntos a la proporción XlsxEmuPerPoint de 12700
Dónde termina esto
Dos límites honestos. Primero, HotXLS modela el slicer de tabla por completo pero no modela la creación de slicers de pivote — puedes abrir un workbook con slicers de pivote, leer su identidad, mantenerlos intactos a través de un guardado y borrarlos, pero el lado de la caché se repite como bytes y no hay API para construir uno desde cero. Segundo, un slicer no filtra nada por sí mismo. Es una UI sobre una columna de tabla, y el estado de filas visibles que maneja es del lado de Excel; HotXLS escribe el control y el binding, no un resultado de filtro calculado. Si necesitas filas realmente filtradas en el archivo guardado, configura el AutoFilter de la tabla también. Cualquier atributo que HotXLS no modele en <slicer> o <x15:tableSlicerCache> se arrastra sin cambios, así que un slicer que vino de un build más nuevo de Excel igual se re-serializa con sus atributos desconocidos intactos incluso después de moverlo. Esa es la propiedad que vuelve seguro tomar el camino de dirty rebuild: reconstruir desde el modelo solo es aceptable si el modelo es un superconjunto de lo que parseó. El mecanismo de extensión con tags GUID de ECMA-376 Parte 1 §18.2.10, y las definiciones de partes de slicer en [MS-XLSX], son lo que vuelve expresable esa garantía en primer lugar
Los slicers de tabla viajan en el HotXLS Delphi Component, que lee y escribe workbooks XLSX, XLSM y BIFF legado desde Delphi 5 hasta el release actual, más C++Builder y Free Pascal, sin instalación de Excel y sin OLE. La referencia completa de la API y un build de prueba están en la página del producto HotXLS Delphi Spreadsheet Component