From 4806a2f1c076d59f09ec235e4540d8636add77c5 Mon Sep 17 00:00:00 2001 From: Xujian66 <91264074+Xujian66@users.noreply.github.com> Date: Sat, 10 Oct 2026 15:38:55 +0800 Subject: [PATCH] Fix worksheet part identities after sorting --- .../java/org/dhatim/fastexcel/Workbook.java | 18 +++- .../java/org/dhatim/fastexcel/Worksheet.java | 9 +- .../dhatim/fastexcel/WorksheetSortTest.java | 83 +++++++++++++++++++ 3 files changed, 102 insertions(+), 8 deletions(-) create mode 100644 fastexcel-writer/src/test/java/org/dhatim/fastexcel/WorksheetSortTest.java diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java index d79f39d7..da02689e 100644 --- a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Workbook.java @@ -376,7 +376,7 @@ private void writeWorkbookFile() throws IOException { * (if there are any repeating rows or cols in the sheet at all) **/ w.append(""); for (Worksheet ws : worksheets) { - int worksheetIndex = getIndex(ws) - 1; + int worksheetIndex = getPosition(ws); List repeatingColsAndRows = Stream.of(ws.getRepeatingCols(), ws.getRepeatingRows()) .filter(Objects::nonNull) .collect(Collectors.toList()); @@ -523,15 +523,25 @@ int cacheDifferentialFormat(DifferentialFormat differentialFormat) { } /** - * Get unique index of a worksheet. + * Get stable unique part index of a worksheet, independent of its tab order. * * @param ws Worksheet. It must have been created previously by calling * {@link #newWorksheet(java.lang.String)} on this workbook. * @return Worksheet index. */ int getIndex(Worksheet ws) { + return ws.index; + } + + /** + * Get the current zero-based tab position of a worksheet. + * + * @param ws Worksheet belonging to this workbook. + * @return Worksheet position. + */ + int getPosition(Worksheet ws) { synchronized (worksheets) { - return worksheets.indexOf(ws) + 1; + return worksheets.indexOf(ws); } } @@ -563,7 +573,7 @@ public Worksheet newWorksheet(String name) { } ++number; } - Worksheet worksheet = new Worksheet(this, sheetName); + Worksheet worksheet = new Worksheet(this, sheetName, worksheets.size() + 1); worksheets.add(worksheet); return worksheet; } diff --git a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Worksheet.java b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Worksheet.java index bad3a6a2..1c0268a7 100644 --- a/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Worksheet.java +++ b/fastexcel-writer/src/main/java/org/dhatim/fastexcel/Worksheet.java @@ -58,6 +58,7 @@ public class Worksheet implements Closeable { private final Workbook workbook; private final String name; + final int index; /** * List of rows. A row is an array of cells. * Flushed rows are null. @@ -295,10 +296,12 @@ public class Worksheet implements Closeable { * * @param workbook Parent workbook. * @param name Worksheet name. + * @param index Stable worksheet part index. */ - Worksheet(Workbook workbook, String name) { + Worksheet(Workbook workbook, String name, int index) { this.workbook = Objects.requireNonNull(workbook); this.name = Objects.requireNonNull(name); + this.index = index; } /** @@ -498,9 +501,7 @@ public void showColumn(int column) { * Keep this sheet in active tab. */ public void keepInActiveTab() { - int sheetIndex = workbook.getIndex(this); - //tabs are indexed from 0, sheets are indexed from 1 - workbook.setActiveTab(sheetIndex - 1); + workbook.setActiveTab(workbook.getPosition(this)); } /** diff --git a/fastexcel-writer/src/test/java/org/dhatim/fastexcel/WorksheetSortTest.java b/fastexcel-writer/src/test/java/org/dhatim/fastexcel/WorksheetSortTest.java new file mode 100644 index 00000000..2aac0369 --- /dev/null +++ b/fastexcel-writer/src/test/java/org/dhatim/fastexcel/WorksheetSortTest.java @@ -0,0 +1,83 @@ +/* + * Copyright 2026 Dhatim. + * + * Licensed 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.dhatim.fastexcel; + +import org.apache.poi.xssf.usermodel.XSSFName; +import org.apache.poi.xssf.usermodel.XSSFSheet; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.util.Comparator; + +import static org.assertj.core.api.Assertions.assertThat; + +class WorksheetSortTest { + + @ParameterizedTest + @ValueSource(ints = {0, 1, 3}) + void sortPreservesWorksheetContentAndScopedMetadata(int finishedSheets) throws Exception { + String[] names = {"AAA", "BBB", "CCC"}; + String[] colors = {"FF0000", "00FF00", "0000FF"}; + ByteArrayOutputStream output = new ByteArrayOutputStream(); + try (Workbook workbook = new Workbook(output, "Test", "1.0")) { + Worksheet[] sheets = new Worksheet[names.length]; + for (int i = 0; i < names.length; i++) { + Worksheet sheet = workbook.newWorksheet(names[i]); + sheets[i] = sheet; + sheet.value(0, 0, (i + 1) * 111); + sheet.setTabColor(colors[i]); + sheet.comment(0, 0, "Comment for " + names[i]); + sheet.repeatRows(i); + sheet.repeatCols(i); + sheet.setAutoFilter(0, 0, 1, 0); + Range localRange = sheet.range(i, 0, i, 0); + localRange.setFolderScope(false); + sheet.addNamedRange(localRange, "localValue"); + if (i < finishedSheets) { + sheet.finish(); + } + } + workbook.sortWorksheets(Comparator.comparing(Worksheet::getName).reversed()); + sheets[0].keepInActiveTab(); + } + + try (XSSFWorkbook workbook = new XSSFWorkbook(new ByteArrayInputStream(output.toByteArray()))) { + assertThat(workbook.getNumberOfSheets()).isEqualTo(3); + assertThat(workbook.getSheetName(0)).isEqualTo("CCC"); + assertThat(workbook.getSheetName(1)).isEqualTo("BBB"); + assertThat(workbook.getSheetName(2)).isEqualTo("AAA"); + assertThat(workbook.getActiveSheetIndex()).isEqualTo(2); + assertThat(workbook.getNames("localValue")).hasSize(3); + for (int i = 0; i < names.length; i++) { + XSSFSheet sheet = workbook.getSheet(names[i]); + assertThat(sheet.getRow(0).getCell(0).getNumericCellValue()).isEqualTo((i + 1) * 111); + assertThat(sheet.getTabColor().getARGBHex()).endsWith(colors[i]); + assertThat(sheet.getRow(0).getCell(0).getCellComment().getString().getString()) + .isEqualTo("Comment for " + names[i]); + assertThat(sheet.getRepeatingRows().getFirstRow()).isEqualTo(i); + assertThat(sheet.getRepeatingColumns().getFirstColumn()).isEqualTo(i); + assertThat(sheet.getCTWorksheet().getAutoFilter().getRef()).isEqualTo("A1:A2"); + int sheetPosition = workbook.getSheetIndex(names[i]); + XSSFName localName = workbook.getNames("localValue").stream() + .filter(name -> name.getSheetIndex() == sheetPosition).findFirst().get(); + assertThat(localName.getRefersToFormula()).startsWith("'" + names[i] + "'!"); + } + } + } +}