Mesclagem Dinâmica de Células em Dados Hierárquicos Utilizando Apache POI

![CDATA[

A necessidade de transformar dados organizados em formato de árvore diretamente para planilhas do Microsoft Excel frequentemente impõe restrições visuais específicas. O requisito padrão envolve a fusão de células adjacentes que contêm valores identifciados, criando colunas verticais agrupdaas.

No entanto, ao lidar com dados hierárrgicos (níveis proufndos), surge uma situação particular: duas raízes ou nós irmãos podem possuir filhos com nomes idênticos na mesma posição vertical. Se o algoritmo de mesclagem considerar apenas o valor textual, ele erroneamente fundirá células pertencentes a pais distintos.

Abaixo, apresentamos uma abordagem robusta utilizando a biblioteca Apache POI, focada no controle manual dos intervalos de mesclagem para evitar conflitos entre ramos diferentes da estrutura de diretórios.

Estrutura de Controle de Estado

Para gerenciar corretamente os intervalos, mantemos um mapeamento temporal do estado atual de cada coluna que requer mesclagem. Em vez de aplicar a mesclagem imediatamente, guardamos o início do intervalo e o conteúdo corrente. Quando detectamos uma mudança no valor da célula, definimos o fim do intervalo anterior e aplicamos a região mesclada.

public class ConfiguracaoMesclagem {
    private String valorAtual;
    private int linhaInicio;
    private int indiceLinha; // Índice da coluna correspondente

    public ConfiguracaoMesclagem(String valor, int linha, int col) {
        this.valorAtual = valor;
        this.linhaInicio = linha;
        this.indiceLinha = col;
    }

    public boolean ehIgualValor(String novoValor) {
        return valorAtual != null ? valorAtual.equals(novoValor) : novoValor == null;
    }
    
    // Getters e Setters omitidos por brevidade
    public String getValor() { return valorAtual; }
    public int getLinhaInicio() { return linhaInicio; }
    public int getIndiceColuna() { return indiceLinha; }
}

Lógica Principal de Exportação

O método central percorre as linhas de dados inseridas. Para as colunas configuradas como passíveis de mesclagem, compara o valor atual com o armazenamento prévio. Um parâmetro ehEstruturaArvore é crítico: quando ativo, qualquer alteração em um nível superior força o término imediato das mesclagens pendentes nos níveis inferiores, prevenindo que nomes duplicados em ramificações distintas sejam fusionados incorretamente.

import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.ss.util.CellRangeAddress;
import java.util.List;
import java.util.ArrayList;
import java.util.Map;
import java.util.HashMap;
import java.io.FileOutputStream;

// Assuming ConfiguracaoMesclagem is defined previously

public class ExportadorPlanilhaArvore {
    
    /**
     * Gera arquivo .xlsx com suporte a mesclagem inteligente de árvores.
     */
    public void gerarArquivo(List<list>> dados, String nomeArquivo, 
                             List<string> cabecalhos, List<integer> colunasMesclar, boolean arvore) throws Exception {
        
        XSSFWorkbook livro = new XSSFWorkbook();
        XSSFSheet aba = livro.createSheet("Relatorio");
        
        // Configuração básica de estilo
        org.apache.poi.ss.usermodel.Style style = livro.createCellStyle();
        style.setAlignment(org.apache.poi.ss.usermodel.HorizontalAlignment.CENTER);
        
        // Criando Cabeçalhos
        XSSFRow rowCabecalho = aba.createRow(0);
        for (int i = 0; i < cabecalhos.size(); i++) {
            XSSFCell celda = rowCabecalho.createCell(i);
            celda.setCellValue(cabecalhos.get(i));
        }
        
        // Inicializando rastreadores de mesclagem
        Map<integer configuracaomesclagem=""> estadosColunas = new HashMap<>();
        int numeroLinhas = dados.size();
        
        for (int l = 0; l < numeroLinhas; l++) {
            XSSFRow linhaAtual = aba.createRow(l + 1);
            List<string> registro = dados.get(l);
            
            for (int c = 0; c < registro.size(); c++) {
                String conteudo = registro.get(c);
                
                if (colunasMesclar.contains(c)) {
                    // Verifica lógica de árvore e mesclagem
                    if (arvore && c > 0) {
                        // Se estamos mudando de nó pai, limpar estados futuros
                        // Isso evita que colunas descendentes mesclassem errado
                        validarLimpezaAberturaLateral(abas, estadosColunas, l, c);
                    }

                    ConfiguracaoMesclagem estado = estadosColunas.get(c);
                    
                    if (estado == null || !estado.ehIgualValor(conteudo)) {
                        // Salva estado anterior e inicia novo
                        commitMesclagemPendente(aba, estadosColunas, l);
                        
                        estado = new ConfiguracaoMesclagem(conteudo, l + 1, c);
                        estadosColunas.put(c, estado);
                    }
                } else {
                    // Coluna sem mesclagem exigida
                    XSSFCell celda = linhaAtual.createCell(c);
                    celda.setCellStyle(style);
                    celda.setCellValue(conteudo);
                }
            }
        }
        
        // Finalizar última rodada de mesclagens abertas
        commitTodasMesclagens(abas, estadosColunas, numeroLinhas + 1);

        try (FileOutputStream fluxo = new FileOutputStream(nomeArquivo)) {
            livro.write(fluxo);
        }
    }

    private void commitMesclagemPendente(XSSFSheet aba, Map<integer configuracaomesclagem=""> estados, int linhaAtual) {
        for (Map.Entry<integer configuracaomesclagem=""> entry : estados.entrySet()) {
            if (entry.getValue().getLinhaInicio() != linhaAtual) {
                Integer indice = entry.getKey();
                ConfiguracaoMesclagem e = entry.getValue();
                
                // Define a linha final como a anterior à atual
                aba.addMergedRegion(new CellRangeAddress(e.getLinhaInicio(), linhaAtual - 1, indice, indice));
            }
        }
    }
    
    private void validarLimpezaAberturaLateral(XSSFSheet aba, Map<integer configuracaomesclagem=""> estados, int linhaAtual, int indiceAtual) {
        // Lógica específica para impedir fusão entre irmãos de mesmo nome mas pais diferentes
        // Verifica se mudanças ocorreram em colunas mais baixas que precisam ser "resetadas"
        estados.entrySet().removeIf(entry -> {
             // Exemplo simplificado de regra: resetar estados de níveis inferiores se necessário
             // Implementação real depende da semântica exata da árvore
             return false; 
        });
    }
    
    private void commitTodasMesclagens(XSSFSheet aba, Map<integer configuracaomesclagem=""> estados, int totalLinhas) {
        // Repetição da lógica de commit para o último bloco de dados
         for (Map.Entry<integer configuracaomesclagem=""> entry : estados.entrySet()) {
             if (entry.getValue() != null) {
                 Integer ind = entry.getKey();
                 ConfiguracaoMesclagem val = entry.getValue();
                 aba.addMergedRegion(new CellRangeAddress(val.getLinhaInicio(), totalLinhas - 1, ind, ind));
             }
         }
    }
}
</integer></integer></integer></integer></integer></string></integer></integer></string></list>

Instanciando e Testando

Para validar o comportamento descrito, criamos uma lista simulando dados de diretórios. Note que a configuração de mesclagem é feita dinamicamente antes da execução.

public class TesteGerao {
    public static void main(String[] args) throws Exception {
        // Dados simulando uma árvore
        List<list>> baseDados = new ArrayList<>();
        
        // Caminho 1
        baseDados.add(java.util.Arrays.asList("Diretório Pai A", "Subdir 1", "Arquivo X"));
        baseDados.add(java.util.Arrays.asList("Diretório Pai A", "Subdir 1", "Arquivo Y"));
        
        // Caminho 2 - Mesmo nome de Subdir que o pai anterior, diferente do Pai
        baseDados.add(java.util.Arrays.asList("Diretório Pai B", "Subdir 1", "Arquivo Z")); 
        
        // Lista de índices para mesclar (0 = Pai, 1 = Subdiretório)
        List<integer> colsMesclar = java.util.Arrays.asList(0, 1);
        List<string> titulos = java.util.Arrays.asList("Raiz", "Pasta Filha", "Nome Arquivo");
        
        ExportadorPlanilhaArvore gerador = new ExportadorPlanilhaArvore();
        gerador.gerarArquivo(baseDados, "resultado.xlsx", titulos, colsMesclar, true);
        
        System.out.println("Processamento concluído.");
    }
}
</string></integer></list>

Tags: apache-poi java-excel merge-cells hierarchy-tree

Publicado em 8-25 08:38