TestXSSFOptimiser.java
/* ====================================================================
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==================================================================== */
package org.apache.poi.xssf.usermodel;
import static org.junit.jupiter.api.Assertions.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.usermodel.CellStyle;
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.DataFormat;
import org.apache.poi.ss.usermodel.Font;
import org.apache.poi.ss.usermodel.HorizontalAlignment;
import org.apache.poi.ss.usermodel.Row;
import org.junit.jupiter.api.Test;
/**
* Tests for {@link XSSFOptimiser}.
*/
class TestXSSFOptimiser {
/**
* Test that optimiseCellStyles remaps cells with duplicate styles to canonical style.
*/
@Test
void testOptimiseCellStylesRemapsDuplicates() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Create 10 cells, each with a "new" style that is actually identical
for (int i = 0; i < 10; i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);
cell.setCellValue("Value " + i);
// Create a "new" style for each cell (causes style explosion)
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("Calibri");
font.setFontHeightInPoints((short) 11);
font.setBold(false);
style.setFont(font);
cell.setCellStyle(style);
}
int stylesBeforeOptimize = workbook.getNumCellStyles();
assertTrue(stylesBeforeOptimize > 5,
"Should have created multiple styles before optimization, got: " + stylesBeforeOptimize);
// Optimize - returns number of cells remapped
int cellsRemapped = XSSFOptimiser.optimiseCellStyles(workbook);
assertTrue(cellsRemapped > 0,
"Should have remapped cells to canonical styles, got: " + cellsRemapped);
// Verify all cells now reference the same canonical style index
int canonicalStyleIdx = sheet.getRow(0).getCell(0).getCellStyle().getIndex();
for (int i = 1; i < 10; i++) {
Cell cell = sheet.getRow(i).getCell(0);
assertEquals(canonicalStyleIdx, cell.getCellStyle().getIndex(),
"Cell " + i + " should use canonical style index");
}
// Verify cells still have correct values
for (int i = 0; i < 10; i++) {
Cell cell = sheet.getRow(i).getCell(0);
assertEquals("Value " + i, cell.getStringCellValue());
}
}
}
/**
* Test that optimiseCellStyles handles unused styles gracefully.
*/
@Test
void testOptimiseCellStylesHandlesUnusedStyles() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Create a cell with one style
Row row = sheet.createRow(0);
Cell cell = row.createCell(0);
cell.setCellValue("Used style");
CellStyle usedStyle = workbook.createCellStyle();
usedStyle.setAlignment(HorizontalAlignment.CENTER);
cell.setCellStyle(usedStyle);
// Create several unused styles
for (int i = 0; i < 5; i++) {
CellStyle unusedStyle = workbook.createCellStyle();
unusedStyle.setAlignment(HorizontalAlignment.LEFT);
// Never apply this style to any cell
}
int stylesBeforeOptimize = workbook.getNumCellStyles();
assertTrue(stylesBeforeOptimize >= 6,
"Should have at least 6 styles (1 default + 1 used + 5 unused), got: " + stylesBeforeOptimize);
// Optimize - should not throw, returns 0 (no duplicates to remap)
int cellsRemapped = XSSFOptimiser.optimiseCellStyles(workbook);
assertEquals(0, cellsRemapped, "No duplicate styles to remap");
// Verify the used cell still has correct alignment
CellStyle cellStyle = cell.getCellStyle();
assertEquals(HorizontalAlignment.CENTER, cellStyle.getAlignment(),
"Cell should still have CENTER alignment after optimization");
}
}
/**
* Test that optimiseCellStyles preserves different styles.
*/
@Test
void testOptimiseCellStylesPreservesDifferentStyles() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Cell with bold style
Row row1 = sheet.createRow(0);
Cell cell1 = row1.createCell(0);
cell1.setCellValue("Bold");
CellStyle boldStyle = workbook.createCellStyle();
Font boldFont = workbook.createFont();
boldFont.setBold(true);
boldStyle.setFont(boldFont);
cell1.setCellStyle(boldStyle);
// Cell with italic style
Row row2 = sheet.createRow(1);
Cell cell2 = row2.createCell(0);
cell2.setCellValue("Italic");
CellStyle italicStyle = workbook.createCellStyle();
Font italicFont = workbook.createFont();
italicFont.setItalic(true);
italicStyle.setFont(italicFont);
cell2.setCellStyle(italicStyle);
// Cell with number format
Row row3 = sheet.createRow(2);
Cell cell3 = row3.createCell(0);
cell3.setCellValue(1234.56);
CellStyle numStyle = workbook.createCellStyle();
DataFormat format = workbook.createDataFormat();
numStyle.setDataFormat(format.getFormat("#,##0.00"));
cell3.setCellStyle(numStyle);
// Optimize
XSSFOptimiser.optimiseCellStyles(workbook);
// Verify each cell retains its distinct formatting
Font font1 = workbook.getFontAt(cell1.getCellStyle().getFontIndex());
assertTrue(font1.getBold(), "Cell 1 should still be bold");
Font font2 = workbook.getFontAt(cell2.getCellStyle().getFontIndex());
assertTrue(font2.getItalic(), "Cell 2 should still be italic");
String numFormat = cell3.getCellStyle().getDataFormatString();
assertTrue(numFormat.contains("#,##0"),
"Cell 3 should still have number format, got: " + numFormat);
}
}
/**
* Test that optimiseFonts remaps style font references to canonical fonts.
*/
@Test
void testOptimiseFontsRemapsDuplicates() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Create cells with duplicate fonts
for (int i = 0; i < 10; i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);
cell.setCellValue("Value " + i);
// Create a "new" font for each cell (identical fonts)
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("Arial");
font.setFontHeightInPoints((short) 12);
font.setBold(true);
style.setFont(font);
cell.setCellStyle(style);
}
int fontsBeforeOptimize = workbook.getNumberOfFonts();
assertTrue(fontsBeforeOptimize > 5,
"Should have created multiple fonts before optimization, got: " + fontsBeforeOptimize);
// Optimize fonts - returns number of duplicates found
int duplicatesFound = XSSFOptimiser.optimiseFonts(workbook);
assertTrue(duplicatesFound > 0,
"Should have found duplicate fonts, got: " + duplicatesFound);
// Verify cells still have bold Arial font (formatting preserved)
for (int i = 0; i < 10; i++) {
Cell cell = sheet.getRow(i).getCell(0);
Font font = workbook.getFontAt(cell.getCellStyle().getFontIndex());
assertEquals("Arial", font.getFontName(), "Font name should be preserved");
assertTrue(font.getBold(), "Font should still be bold");
}
}
}
/**
* Test that workbook is saveable after optimization.
*/
@Test
void testWorkbookSaveableAfterOptimization() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Create style explosion
for (int i = 0; i < 20; i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);
cell.setCellValue("Value " + i);
CellStyle style = workbook.createCellStyle();
Font font = workbook.createFont();
font.setFontName("Calibri");
style.setFont(font);
cell.setCellStyle(style);
}
// Optimize
XSSFOptimiser.optimiseCellStyles(workbook);
XSSFOptimiser.optimiseFonts(workbook);
// Save to byte array (should not throw XmlValueDisconnectedException)
ByteArrayOutputStream baos = new ByteArrayOutputStream();
workbook.write(baos);
byte[] savedBytes = baos.toByteArray();
assertTrue(savedBytes.length > 0, "Saved workbook should have content");
// Verify we can read it back
try (XSSFWorkbook reloaded = new XSSFWorkbook(new ByteArrayInputStream(savedBytes))) {
XSSFSheet reloadedSheet = reloaded.getSheetAt(0);
assertEquals("Value 0", reloadedSheet.getRow(0).getCell(0).getStringCellValue());
assertEquals("Value 19", reloadedSheet.getRow(19).getCell(0).getStringCellValue());
}
}
}
/**
* Test that optimization preserves formulas.
*/
@Test
void testOptimizationPreservesFormulas() throws Exception {
try (XSSFWorkbook workbook = new XSSFWorkbook()) {
XSSFSheet sheet = workbook.createSheet("Test");
// Create cells with values
for (int i = 0; i < 5; i++) {
Row row = sheet.createRow(i);
Cell cell = row.createCell(0);
cell.setCellValue(i + 1);
CellStyle style = workbook.createCellStyle();
style.setAlignment(HorizontalAlignment.RIGHT);
cell.setCellStyle(style);
}
// Create formula cell
Row sumRow = sheet.createRow(5);
Cell sumCell = sumRow.createCell(0);
sumCell.setCellFormula("SUM(A1:A5)");
CellStyle sumStyle = workbook.createCellStyle();
Font boldFont = workbook.createFont();
boldFont.setBold(true);
sumStyle.setFont(boldFont);
sumCell.setCellStyle(sumStyle);
// Optimize
XSSFOptimiser.optimiseCellStyles(workbook);
// Verify formula is preserved
Cell formulaCell = sheet.getRow(5).getCell(0);
assertEquals(CellType.FORMULA, formulaCell.getCellType());
assertEquals("SUM(A1:A5)", formulaCell.getCellFormula());
// Verify bold style is preserved on formula cell
Font font = workbook.getFontAt(formulaCell.getCellStyle().getFontIndex());
assertTrue(font.getBold(), "Formula cell should still be bold");
}
}
}