diff --git a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/executor/ExcelWriteAddExecutor.java b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/executor/ExcelWriteAddExecutor.java index 36cace7f5..1d9951094 100644 --- a/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/executor/ExcelWriteAddExecutor.java +++ b/fesod-sheet/src/main/java/org/apache/fesod/sheet/write/executor/ExcelWriteAddExecutor.java @@ -48,6 +48,7 @@ import org.apache.fesod.sheet.write.metadata.CollectionRowData; import org.apache.fesod.sheet.write.metadata.MapRowData; import org.apache.fesod.sheet.write.metadata.RowData; +import org.apache.fesod.sheet.write.metadata.holder.AbstractWriteHolder; import org.apache.fesod.sheet.write.metadata.holder.WriteHolder; import org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder; import org.apache.poi.ss.usermodel.Cell; @@ -69,10 +70,13 @@ public void add(Collection data) { data = new ArrayList<>(); } WriteSheetHolder writeSheetHolder = writeContext.writeSheetHolder(); + WriteHolder currentWriteHolder = writeContext.currentWriteHolder(); int newRowIndex = writeSheetHolder.getNewRowIndexAndStartDoWrite(); - if (writeSheetHolder.isNew() - && !writeSheetHolder.getExcelWriteHeadProperty().hasHead()) { - newRowIndex += writeContext.currentWriteHolder().relativeHeadRowIndex(); + if (currentWriteHolder.isNew()) { + AbstractWriteHolder writeHolder = (AbstractWriteHolder) currentWriteHolder; + if (!writeHolder.getExcelWriteHeadProperty().hasHead()) { + newRowIndex += currentWriteHolder.relativeHeadRowIndex(); + } } int relativeRowIndex = 0; for (Object oneRowData : data) { diff --git a/fesod-sheet/src/test/java/org/apache/fesod/sheet/writesheet/WriteTableTest.java b/fesod-sheet/src/test/java/org/apache/fesod/sheet/writesheet/WriteTableTest.java new file mode 100644 index 000000000..5a47deeb4 --- /dev/null +++ b/fesod-sheet/src/test/java/org/apache/fesod/sheet/writesheet/WriteTableTest.java @@ -0,0 +1,247 @@ +package org.apache.fesod.sheet.writesheet; + +import java.io.File; +import java.lang.reflect.Field; +import java.util.*; +import org.apache.fesod.sheet.ExcelWriter; +import org.apache.fesod.sheet.FesodSheet; +import org.apache.fesod.sheet.annotation.ExcelProperty; +import org.apache.fesod.sheet.support.ExcelTypeEnum; +import org.apache.fesod.sheet.util.TestFileUtil; +import org.apache.fesod.sheet.write.builder.ExcelWriterBuilder; +import org.apache.fesod.sheet.write.builder.ExcelWriterSheetBuilder; +import org.apache.fesod.sheet.write.metadata.WriteSheet; +import org.apache.fesod.sheet.write.metadata.WriteTable; +import org.apache.poi.ss.usermodel.*; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; + +public class WriteTableTest { + + private static final String HEADER_STRING; + + static { + try { + Field field = WriteSheetData.class.getDeclaredField("string"); + ExcelProperty annotation = field.getAnnotation(ExcelProperty.class); + HEADER_STRING = annotation.value()[0]; + } catch (Exception e) { + throw new RuntimeException("Failed to resolve @ExcelProperty header from WriteSheetData.string", e); + } + } + + @ParameterizedTest + @EnumSource( + value = ExcelTypeEnum.class, + names = {"XLS", "XLSX"}) + public void testWriteTableWithTitle(ExcelTypeEnum excelType) throws Exception { + Random random = new Random(); + int offsetA = random.nextInt(10); + int offsetB = random.nextInt(10); + int sizeA = random.nextInt(10) + 1; + int sizeB = random.nextInt(10) + 1; + + File testFile = TestFileUtil.createNewFile("writesheet/write-table-title" + excelType.getValue()); + + // ── write ──────────────────────────────────────────────────────────── + + ExcelWriterBuilder write = FesodSheet.write(testFile); + ExcelWriterSheetBuilder sheetBuilder = write.sheet(0, "0"); + WriteSheet sheet = sheetBuilder.build(); + + WriteTable tableA = sheetBuilder + .table(0) + .head(WriteSheetData.class) + .relativeHeadRowIndex(offsetA) + .build(); + WriteTable tableB = sheetBuilder + .table(1) + .relativeHeadRowIndex(offsetB) + .head(WriteSheetData.class) + .build(); + + try (ExcelWriter writer = write.build()) { + writer.write(getList(sizeA, "A-"), sheet, tableA); + writer.write(getList(sizeB, "B-"), sheet, tableB); + writer.finish(); + } + + Assertions.assertTrue(testFile.exists(), "Written file should exist"); + Assertions.assertTrue(testFile.length() > 0, "Written file should not be empty"); + + // ── read & assert ──────────────────────────────────────────────────── + + try (Workbook workbook = WorkbookFactory.create(testFile)) { + Sheet excelSheet = workbook.getSheetAt(0); + Assertions.assertNotNull(excelSheet, "Sheet '0' should exist"); + + Map expected = buildExpectedWithTitle(offsetA, offsetB, sizeA, sizeB); + int totalRows = expected.size(); + Assertions.assertEquals( + totalRows, + excelSheet.getPhysicalNumberOfRows(), + "Sheet should have exactly " + totalRows + " rows " + + "(offsets: A=" + offsetA + " B=" + offsetB + + ", sizes: A=" + sizeA + " B=" + sizeB + ")"); + + for (Map.Entry entry : expected.entrySet()) { + int rowIdx = entry.getKey(); + String expectedValue = entry.getValue(); + Row row = excelSheet.getRow(rowIdx); + Assertions.assertNotNull( + row, + "Expected row " + rowIdx + " with value '" + expectedValue + + "' (A:" + offsetA + "/" + sizeA + + " B:" + offsetB + "/" + sizeB + ")"); + Cell cell = row.getCell(0); + Assertions.assertNotNull(cell, "Expected cell at row " + rowIdx); + Assertions.assertEquals( + expectedValue, + cell.getStringCellValue(), + "Row " + rowIdx + " expected '" + expectedValue + "' " + + "(A:" + offsetA + "/" + sizeA + + " B:" + offsetB + "/" + sizeB + ")"); + } + } + } + + @ParameterizedTest + @EnumSource( + value = ExcelTypeEnum.class, + names = {"XLS", "XLSX"}) + public void testWriteTableWithoutTitle(ExcelTypeEnum excelType) throws Exception { + Random random = new Random(); + int offsetC = random.nextInt(10); + int offsetA = random.nextInt(10); + int offsetB = random.nextInt(10); + int sizeC = random.nextInt(10) + 1; + int sizeA = random.nextInt(10) + 1; + int sizeB = random.nextInt(10) + 1; + + File testFile = TestFileUtil.createNewFile("writesheet/write-table-notitle" + excelType.getValue()); + + // ── write ──────────────────────────────────────────────────────────── + + ExcelWriterBuilder write = FesodSheet.write(testFile); + ExcelWriterSheetBuilder sheetBuilder = write.sheet(0, "0"); + WriteSheet sheet = sheetBuilder.build(); + + WriteSheet collect = sheetBuilder.relativeHeadRowIndex(offsetC).build(); + WriteTable tableA = sheetBuilder.table(0).relativeHeadRowIndex(offsetA).build(); + WriteTable tableB = sheetBuilder.table(1).relativeHeadRowIndex(offsetB).build(); + + try (ExcelWriter writer = write.build()) { + writer.write(getList(sizeC, "C-"), collect); + writer.write(getList(sizeA, "A-"), sheet, tableA); + writer.write(getList(sizeB, "B-"), sheet, tableB); + writer.finish(); + } + + Assertions.assertTrue(testFile.exists(), "Written file should exist"); + Assertions.assertTrue(testFile.length() > 0, "Written file should not be empty"); + + // ── read & assert ──────────────────────────────────────────────────── + + try (Workbook workbook = WorkbookFactory.create(testFile)) { + Sheet excelSheet = workbook.getSheetAt(0); + Assertions.assertNotNull(excelSheet, "Sheet '0' should exist"); + + Map expected = buildExpectedWithoutTitle(offsetC, offsetA, offsetB, sizeC, sizeA, sizeB); + int totalRows = expected.size(); + Assertions.assertEquals( + totalRows, + excelSheet.getPhysicalNumberOfRows(), + "Sheet should have exactly " + totalRows + " rows " + + "(C:" + offsetC + "/" + sizeC + + " A:" + offsetA + "/" + sizeA + + " B:" + offsetB + "/" + sizeB + ")"); + + for (Map.Entry entry : expected.entrySet()) { + int rowIdx = entry.getKey(); + String expectedValue = entry.getValue(); + Row row = excelSheet.getRow(rowIdx); + Assertions.assertNotNull( + row, + "Expected row " + rowIdx + " with value '" + expectedValue + + "' (C:" + offsetC + "/" + sizeC + + " A:" + offsetA + "/" + sizeA + + " B:" + offsetB + "/" + sizeB + ")"); + Cell cell = row.getCell(0); + Assertions.assertNotNull(cell, "Expected cell at row " + rowIdx); + Assertions.assertEquals( + expectedValue, + cell.getStringCellValue(), + "Row " + rowIdx + " expected '" + expectedValue + "' " + + "(C:" + offsetC + "/" + sizeC + + " A:" + offsetA + "/" + sizeA + + " B:" + offsetB + "/" + sizeB + ")"); + } + } + } + + // ── expected-row builders ──────────────────────────────────────────────── + + /** + * Build expected row map for with-title tables. + * Tables are sequential: B starts after A's last row + offsetB empty rows. + */ + private static Map buildExpectedWithTitle(int offsetA, int offsetB, int sizeA, int sizeB) { + Map expected = new LinkedHashMap<>(); + + // Table A: head at offsetA, data at offsetA+1 ... offsetA+sizeA + expected.put(offsetA, HEADER_STRING); + for (int i = 0; i < sizeA; i++) { + expected.put(offsetA + 1 + i, "A-" + i); + } + + // Table B: offsetB empty rows after A's last row, then head + data + int bHeadRow = offsetA + sizeA + offsetB + 1; + expected.put(bHeadRow, HEADER_STRING); + for (int i = 0; i < sizeB; i++) { + expected.put(bHeadRow + 1 + i, "B-" + i); + } + + return expected; + } + + /** + * Build expected row map for headerless writes. + * Tables are sequential: each starts after the previous one's last row + offset. + */ + private static Map buildExpectedWithoutTitle( + int offsetC, int offsetA, int offsetB, int sizeC, int sizeA, int sizeB) { + Map expected = new LinkedHashMap<>(); + + // collect: data at offsetC ... offsetC+sizeC-1 + for (int i = 0; i < sizeC; i++) { + expected.put(offsetC + i, "C-" + i); + } + + // tableA: starts after collect's last row + offsetA empty rows + int aStartRow = offsetC + sizeC + offsetA; + for (int i = 0; i < sizeA; i++) { + expected.put(aStartRow + i, "A-" + i); + } + + // tableB: starts after A's last row + offsetB empty rows + int bStartRow = aStartRow + sizeA + offsetB; + for (int i = 0; i < sizeB; i++) { + expected.put(bStartRow + i, "B-" + i); + } + + return expected; + } + + // ── helper ─────────────────────────────────────────────────────────────── + + private static List getList(int size, String prefix) { + List dataList = new ArrayList<>(); + for (int j = 0; j < size; j++) { + WriteSheetData data = new WriteSheetData(); + data.setString(prefix + j); + dataList.add(data); + } + return dataList; + } +}