Al automatizar la manipulación de documentos Excel en Java utilizando la biblioteca Apache POI, surge con frecuencia el reto de copiar hojas de cálculo manteniendo la integridad de los datos y el formato. Un problema común es que, al trnasferir filas y celdas individualmente, las regiones combinadas (merged regions) no se replican de forma automática, perdiendo la estructura visual del documento original.
Requisitos previos
Para implementar esta solución, es necesario incluir las dependencias de Apache POI en el proyecto (específicamente poi-ooxml para archivos .xlsx).
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import java.io.FileInputStream;
import java.io.FileOutputStream;
Lógica de duplicación de libros
El proceso requiere iterar sobre la estructura del libro de origen, crear los elementos correspondientes en el destino y, finalmente, mapear las áreas combinadas.
public void duplicarLibroExcel(String rutaOrigen, String rutaDestino) throws Exception {
try (Workbook libroFuente = WorkbookFactory.create(new FileInputStream(rutaOrigen));
Workbook libroNuevo = new XSSFWorkbook()) {
for (int i = 0; i < libroFuente.getNumberOfSheets(); i++) {
Sheet hojaOriginal = libroFuente.getSheetAt(i);
Sheet hojaNueva = libroNuevo.createSheet(hojaOriginal.getSheetName());
// 1. Clonar filas y celdas
clonarEstructuraDatos(hojaOriginal, hojaNueva);
// 2. Transferir regiones combinadas
clonarCeldasCombinadas(hojaOriginal, hojaNueva);
}
try (FileOutputStream outputStream = new FileOutputStream(rutaDestino)) {
libroNuevo.write(outputStream);
}
}
}
Implementación de la copia de celdas
Es fundamental gestionar los diferentes tipos de datos (numéricos, cadenas, booleanos) para evitar errores de formato en el archivo resultante.
private void clonarEstructuraDatos(Sheet origen, Sheet destino) {
for (int numFila = 0; numFila <= origen.getLastRowNum(); numFila++) {
Row filaOrigen = origen.getRow(numFila);
Row filaNueva = destino.createRow(numFila);
if (filaOrigen != null) {
for (int numCol = 0; numCol < filaOrigen.getLastCellNum(); numCol++) {
Cell celdaOrigen = filaOrigen.getCell(numCol);
Cell celdaNueva = filaNueva.createCell(numCol);
if (celdaOrigen != null) {
transferirValor(celdaOrigen, celdaNueva);
}
}
}
}
}
private void transferirValor(Cell fuente, Cell destino) {
switch (fuente.getCellType()) {
public static final CellType STRING = CellType.STRING;
case STRING:
destino.setCellValue(fuente.getStringCellValue());
break;
case NUMERIC:
if (DateUtil.isCellDateFormatted(fuente)) {
destino.setCellValue(fuente.getDateCellValue());
} else {
destino.setCellValue(fuente.getNumericCellValue());
}
break;
case BOOLEAN:
destino.setCellValue(fuente.getBooleanCellValue());
break;
case FORMULA:
destino.setCellFormula(fuente.getCellFormula());
break;
default:
break;
}
}
Resolución del problema de celdas combinadas
Para solucionar la pérdida de las celdas combinadas, se deben extraer los objetos CellRangeAddress de la hoja original y aplicarlos de nuevo en la hoja de destino.
private void clonarCeldasCombinadas(Sheet origen, Sheet destino) {
int cantidadRegiones = origen.getNumMergedRegions();
for (int i = 0; i < cantidadRegiones; i++) {
CellRangeAddress rangoOriginal = origen.getMergedRegion(i);
// Se crea una nueva instancia del rango para la hoja de destino
CellRangeAddress nuevoRango = new CellRangeAddress(
rangoOriginal.getFirstRow(),
rangoOriginal.getLastRow(),
rangoOriginal.getFirstColumn(),
rangoOriginal.getLastColumn()
);
destino.addMergedRegion(nuevoRango);
}
}
Este enfoque garantiza que tanto el contenido como la estructura de diseño de las tablas se mantengan consistentes durante la exportación. Es importante cerrar los flujos de archivos (Streams) adecuadamente, preferiblemente utilizando la sentencia try-with-resources introducida en Java 7 para prevenir fugas de memoria en el servidor.