A HotXLS cria slicers de tabela do Excel de Delphi e C++Builder com uma chamada — Worksheet.Slicers.Add(Table, ColumnName) — e escreve as oito localizações de pacote que um slicer precisa: as parts de slicer e slicerCache, os dois overrides de content type, os relacionamentos de worksheet e workbook com os blocos extLst correspondentes, e a âncora de drawing
Um slicer parece um controle que você solta numa planilha. No arquivo não é nada disso. É um registro distribuído espalhado por duas parts novas, dois grafos de relacionamento, três blocos de extensão e uma âncora de drawing, e o conjunto inteiro é validado como unidade quando o Excel abre o pacote. Erre uma peça e você não ganha um slicer degradado; ganha o diálogo de reparo, seguido de uma pasta de trabalho com o slicer removido silenciosamente. Esse modo de falha é o que torna slicers irritantes de emitir na mão, e é por que a engenharia interessante aqui é contabilidade em vez de renderização
Por que um slicer toca oito lugares no pacote?
Porque um slicer são três conceitos separados que o OPC força a três casas separadas: o estado do filtro (escopo da pasta de trabalho), a view (escopo da worksheet) e a shape (escopo do drawing). A HotXLS escreve exatamente oito pontos de aterrissagem por slicer de tabela, e cada um deles é load-bearing
xl/slicers/slicerN.xml— uma part por worksheet, segurando toda view<slicer>daquela planilha no namespacehttp://schemas.microsoft.com/office/spreadsheetml/2009/9/mainxl/slicerCaches/slicerCacheN.xml— nível de pasta de trabalho, uma por coluna de tabela fatiada, carregando o binding de verdade- um
<Override>em[Content_Types].xmlpara cada uma dessas duas parts, usandoapplication/vnd.ms-excel.slicer+xmleapplication/vnd.ms-excel.slicerCache+xml(a ECMA-376 Parte 2 exige um content type para toda part, e o Excel recusa o pacote sem eles) - um relacionamento de worksheet do tipo
.../office/2007/relationships/slicer, mais uma extensão<x14:slicerList>dentro doextLstda worksheet apontando para esse rid - um relacionamento de workbook do tipo
.../office/2007/relationships/slicerCache, mais uma extensão<x15:slicerCaches>dentro doextLstdo workbook listando os mesmos rids - uma âncora
mc:AlternateContentno drawing da planilha que dá ao slicer posição e tamanho - um bloco
<x15:tableSlicerCache>dentro da part de cache que o liga a um id de tabela e a um id de coluna de tabela
Os blocos de extensão usam o mecanismo padrão extLst da ECMA-376 Parte 1 §18.2.10: cada <ext> carrega um atributo uri que identifica a feature, e leitores que não reconhecem o GUID pulam o bloco. A HotXLS discrimina slicers de tabela de slicers de pivô por esse GUID sozinho — {3A4CF648-6AED-40f4-86FF-DC5316D8AED3} para o slicerList de tabela, {A8765BA9-456A-4dab-B4F3-ACF838C121DE} para o de pivô, e correspondentemente {46BE6895-7355-4a93-B00E-2C351335B9C9} e {BBE1A952-AA13-448e-AADC-164F8A28A991} para as duas extensões slicerCaches do workbook. O binding de tabela em si vive sob {2F2917AC-EB37-4324-AD4E-5DD8C200BD13}. Essas constantes são expostas na unit como XlsxExtUriSlicerListX15, XlsxExtUriWbSlicerCachesX15 e XlsxExtUriTableSlicerCache, então nada no caminho de escrita carrega uma string mágica
Quais parts podem repetir o replay verbatim, e quais precisam ser reconstruídas?
A HotXLS responde com uma regra única: uma part pode reproduzir os bytes originais só se nada que ela referencia puder ser renumerado durante o save. Caches de slicer de tabela falham nesse teste, então a HotXLS os reconstrói a partir do modelo em todo save, incondicionalmente — mesmo quando o usuário nunca tocou no slicer. A razão é tableId: o elemento <x15:tableSlicerCache> identifica a tabela pelo atributo id de <table>, e a HotXLS renumera ids de tabela globalmente no workbook quando escreve. Uma implementação inicial guardava a part de cache como bytes crus no open e os empurrava de volta no save, que é a estratégia certa para a maioria das parts opacas. Ela produzia arquivos que abriam limpos no caso comum e disparavam um reparo no caso interessante: adicione uma tabela numa planilha anterior, salve, e o cache agora aponta para um id de tabela que pertence a outra coisa, ou a nada. O bug é invisível num teste de regressão simples de abrir-e-salvar porque os ids por acaso não se movem
Caches em forma de pivô tomam a rota oposta. O payload deles é <pivotTables> mais um bloco <x14:data> descrevendo itens cacheados, estado de sort e índices de item — uma estrutura sem renomeação do lado da HotXLS, e cara de modelar fielmente. Esses são mantidos como bytes crus e reproduzidos exatamente, com TXLSXSlicerCache.IsPivot e IsTableCache distinguindo os dois. As views de slicer seguem uma terceira política, de dirty-tracking: um elemento <slicer> intocado e a âncora de drawing dele se reproduzem verbatim, e no momento em que qualquer view da planilha é modificada, a part inteira da worksheet é regenerada a partir do modelo. Essa é a forma geral do julgamento — replay verbatim onde referências são estáveis, rebuild do modelo onde o writer reassina identidade, dirty tracking onde os dois são aceitáveis — e ela vale muito além de slicers, como as notas de round-trip sem perdas sobre themes, blocos extLst e calcChain detalham com mais profundidade
Criando um slicer de tabela em Delphi
TXLSXSlicers.Add é a API inteira de criação. Dado um TXLSXTable e um nome de coluna, ela materializa os ids de coluna da tabela, encontra ou cria o TXLSXSlicerCache de apoio, reserva o nome definido correspondente, e ancora uma view à direita da tabela. O overload de dois argumentos escolhe o posicionamento; o de seis argumentos recebe uma caixa inclusiva explícita baseada em 1
var
Book: TXLSXWorkbook;
Sheet: TXLSXWorksheet;
Slicer: TXLSXSlicer;
begin
Book := TXLSXWorkbook.Create;
try
Book.Open('sales.xlsx');
Sheet := Book.Sheets[0];
// Posicionamento padrão: duas colunas de folga da tabela,
// 13 linhas de altura na altura de linha padrão 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;
Duas regras de nomenclatura valem conhecer porque são convenções do Excel, não invenções da HotXLS. O cache é nomeado Slicer_<column> com caracteres não identificadores dobrados em underscores, e colisões ganham um sufixo numérico — Slicer_Region, depois Slicer_Region1, e assim por diante. E todo nome de cache também é reservado como nome definido do workbook cuja fórmula é #N/A, que é o que TXLSXSlicers.ReserveCacheDefinedName faz. O Excel cria esse nome sozinho, e isso importa: nomes de cache e nomes definidos compartilham um namespace, então uma pasta de trabalho que já tenha um nome chamado Slicer_Region colidiria de outro jeito no próximo save. Se você constrói tabelas e validação juntas, as interações de nomes definidos no artigo sobre validação de dados, AutoFilter e tabelas cobrem os casos vizinhos. Compartilhamento segue a mesma regra que o Excel usa. TXLSXSlicerCaches.FindForTableColumn(ATableId, ATableColumnId) é consultado antes de um cache novo ser criado, então duas views de slicer sobre a mesma coluna de tabela — uma na planilha de dashboard, outra na planilha de detalhe — compartilham uma única part de cache e ficam sincronizadas de filtro. Deletar uma view roda a checagem inversa em toda planilha do workbook e derruba o cache mais o nome #N/A reservado dele só quando a última view que o referencia sumiu
Como ids de relacionamento ficam estáveis nos dois lados?
Ficam estáveis porque a HotXLS calcula o rid do slicer reproduzindo a ordem exata em que emite todo outro relacionamento, e adiciona os relacionamentos novos na cauda. Nada que já está no arquivo se move. Isso parece over-engineering até você debugar a alternativa. Ids de relacionamento num .rels de worksheet são referenciados de dentro da part da worksheet — por hyperlinks, pelo drawing, por comentários, por parts de tabela — e a ECMA-376 Parte 2 os resolve posicionalmente dentro do grafo de relacionamento, não por identidade estável. Se o writer inserisse o relacionamento do slicer no meio, toda referência depois dele desloca um, e a planilha silenciosamente começa a apontar para a part errada. Então XlsxWorksheetSlicerRelNumber percorre a cadeia determinística inteira — hyperlinks externos, depois o VML de comentários, comentários, imagens de header/footer, threaded comments, o drawing, o background da planilha, configurações de impressão, tabelas, tabelas dinâmicas, query tables, células únicas de tabela — e só então reivindica o próximo número para o slicer. XlsxWorkbookSlicerCacheFirstRid faz o mesmo no lado do workbook: sheets, sharedStrings, styles, VBA, external links, theme, connections, pivot caches, person, calcChain, sheet metadata, XML maps, custom XML, e então a sequência de slicerCache. O problema geral é trabalhado no texto sobre resolução de relacionamentos OPC. A extensão do lado do workbook é então construída a partir desses números por XlsxBuildWorkbookSlicerCachesExtXml, e se o arquivo de origem já tinha blocos de extensão slicerCaches, eles são removidos do extLst cru primeiro, para que o modelo os re-emita contra os rids atuais em vez de deixar uma cópia velha para trás
A âncora AlternateContent e o fallback legado
A âncora de drawing é o único lugar onde o markup do slicer fica genuinamente estranho, e a estranheza é deliberada. A HotXLS emite a shape dentro de um elemento mc:AlternateContent pela ECMA-376 Parte 3, com um ramo Choice que o Excel 2013 em diante toma e um ramo Fallback para tudo mais antigo
// O que XlsxSlicerAnchorPayloadXml produz, estruturalmente:
//
// <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>
Olhe para os dois namespaces. O token Requires="sle15" resolve para o namespace de slicer de 2012, que é o portão de versão — um leitor que não entende slicers de 2012 deve pular o ramo Choice inteiro. Mas o graphicData/@uri e o elemento sle:slicer dentro dele usam o namespace 2010, porque esse é o identificador do lado do drawing que o graphic frame sempre usou. Inverter esses dois produz um arquivo que o Excel abre sem reclamar e depois renderiza como um frame vazio, que é um sintoma memoravelmente inútil. O ramo Fallback guarda um retângulo simples com um corpo de texto explicando que slicers de tabela não são suportados nesta versão — é isso que um leitor da era 2007 desenha no lugar do controle, e é por que um Excel antigo consegue fazer round-trip da pasta de trabalho sem destruir nada. TXLSXSlicer expõe a geometria como uma caixa inclusiva baseada em 1 por SetBounds(ARow1, ACol1, ARow2, ACol2) e as propriedades individuais FromRow / ToCol, mais AnchorType para o comportamento usual de mover-e-redimensionar-com-células. HasAnchor é False num caso específico: um slicer aninhado dentro de uma group shape. Esses se reproduzem com a âncora do grupo deles verbatim, e a HotXLS deliberadamente não sintetiza uma segunda âncora para eles, porque fazer isso emitiria a shape duas vezes
Editando um slicer existente
var
Slicer: TXLSXSlicer;
Cache: TXLSXSlicerCache;
begin
Slicer := Sheet.Slicers.FindByName('Region');
if Slicer <> nil then
begin
Slicer.SetBounds(2, 8, 16, 11); // baseado em 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;
// Remover a última view sobre uma coluna derruba o cache
// e o nome definido #N/A reservado junto
Sheet.Slicers.Delete(Sheet.Slicers.IndexByName('Region'));
As propriedades inteiras triestado merecem uma nota, porque parecem um acidente de modelagem e não são. ShowCaption, LockedPosition, ColumnCount, StartItem e CustomListSort todas usam -1 para significar que o atributo estava ausente no arquivo de origem e deve continuar ausente na escrita, com 0 e 1 como valores explícitos. O Excel trata ausente e explicitamente-default de forma diferente em alguns lugares, e preservar essa distinção é o que mantém um diff de bytes de um save intocado limpo. RowHeight e ColumnWidth são mantidos como strings cruas por uma razão relacionada: o Excel escreve decimais nesses atributos EMU, e reanalisá-los por um inteiro perderia precisão no round-trip. RowHeightEmu é o accessor de conveniência inteiro sobre o mesmo storage, com XlsxSlicerDefaultRowHeight em 241300 EMU — 19 pontos na razão XlsxEmuPerPoint de 12700
Onde isso para
Dois limites honestos. Primeiro, a HotXLS modela o slicer de tabela completamente mas não modela criação de slicer de pivô — você consegue abrir uma pasta de trabalho com slicers de pivô, ler a identidade deles, mantê-los intactos num save e deletá-los, mas o lado do cache se reproduz como bytes e não há API para construir um do zero. Segundo, um slicer não filtra nada sozinho. É uma UI sobre uma coluna de tabela, e o estado de linhas visíveis que ele dirige é do lado do Excel; a HotXLS escreve o controle e o binding, não um resultado de filtro computado. Se você precisa de linhas realmente filtradas no arquivo salvo, configure também o AutoFilter da tabela. Qualquer atributo que a HotXLS não modela em <slicer> ou <x15:tableSlicerCache> é carregado inalterado, então um slicer vindo de um build mais novo do Excel ainda re-serializa com os atributos desconhecidos intactos mesmo depois de você movê-lo. Essa é a propriedade que torna seguro o caminho de dirty-rebuild: reconstruir a partir do modelo só é aceitável se o modelo é um superset do que analisou. O mecanismo de extensão marcado por GUID da ECMA-376 Parte 1 §18.2.10, e as definições de part de slicer em [MS-XLSX], são o que tornam essa garantia expressável em primeiro lugar
Slicers de tabela vêm no HotXLS Delphi Component, que lê e escreve XLSX, XLSM e workbooks BIFF legados de Delphi 5 até o release atual, mais C++Builder e Free Pascal, sem instalação do Excel e sem OLE. Referência completa da API e um build de avaliação estão na página do produto HotXLS Delphi Spreadsheet Component