Skip to content

Render and fill the template with automatic handling of cell merging functionality 渲染填充模板自动处理单元格合并功能 #397

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import cn.idev.excel.context.WriteContext;
import cn.idev.excel.enums.CellDataTypeEnum;
Expand Down Expand Up @@ -42,6 +43,7 @@
import org.apache.poi.ss.usermodel.CellType;
import org.apache.poi.ss.usermodel.Row;
import org.apache.poi.ss.usermodel.Sheet;
import org.apache.poi.ss.util.CellRangeAddress;

/**
* Fill the data into excel
Expand Down Expand Up @@ -69,6 +71,10 @@ public class ExcelWriteFillExecutor extends AbstractExcelWriteExecutor {
*/
private final Map<UniqueDataFlagKey, Map<AnalysisCell, CellStyle>> collectionFieldStyleCache
= MapUtils.newHashMap();
/**
* Record the merged region information of the initial row, to be used for setting the merged regions for newly added rows
*/
private final Map<AnalysisCell,List<CellRangeAddress>> originalMergeRegionMap = MapUtils.newHashMap();
/**
* Row height cache for collection
*/
Expand Down Expand Up @@ -125,11 +131,25 @@ public void fill(Object data, FillConfig fillConfig) {
while (iterator.hasNext()) {
doFill(analysisCellList, iterator.next(), fillConfig, getRelativeRowIndex());
}
// handle merge
if (!originalMergeRegionMap.isEmpty()) {
Sheet cachedSheet = writeContext.writeSheetHolder().getCachedSheet();
originalMergeRegionMap.forEach((analysisCell, regionList) -> {
for (int index = analysisCell.getRowIndex(); index < analysisCell.getRowIndex() + collectionData.size(); index++) {
for (CellRangeAddress cellAddresses : regionList) {
cachedSheet.addMergedRegionUnsafe(new CellRangeAddress(index, index, cellAddresses.getFirstColumn(), cellAddresses.getLastColumn()));
Comment on lines +139 to +140
Copy link
Preview

Copilot AI Jul 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ensure that the loop range based on 'collectionData.size()' is valid and that the new merged region does not exceed the sheet's boundaries, as using 'addMergedRegionUnsafe' without boundary checks may lead to runtime exceptions.

Suggested change
for (CellRangeAddress cellAddresses : regionList) {
cachedSheet.addMergedRegionUnsafe(new CellRangeAddress(index, index, cellAddresses.getFirstColumn(), cellAddresses.getLastColumn()));
if (index < 0 || index >= cachedSheet.getWorkbook().getSpreadsheetVersion().getMaxRows()) {
continue; // Skip if the row index is out of bounds
}
for (CellRangeAddress cellAddresses : regionList) {
int firstColumn = cellAddresses.getFirstColumn();
int lastColumn = cellAddresses.getLastColumn();
if (firstColumn < 0 || lastColumn >= cachedSheet.getWorkbook().getSpreadsheetVersion().getMaxColumns()) {
continue; // Skip if the column range is out of bounds
}
cachedSheet.addMergedRegionUnsafe(new CellRangeAddress(index, index, firstColumn, lastColumn));

Copilot uses AI. Check for mistakes.

}
}
});
}
} else {
doFill(readTemplateData(templateAnalysisCache), realData, fillConfig, null);
}
}

/**
* Leave space for the newly added data rows and shift the existing data down
*/
private void shiftRows(int size, List<AnalysisCell> analysisCellList) {
if (CollectionUtils.isEmpty(analysisCellList)) {
return;
Expand Down Expand Up @@ -367,9 +387,23 @@ private void createCell(AnalysisCell analysisCell, FillConfig fillConfig,
cellWriteHandlerContext.setCell(cell);

if (isOriginalCell) {
// Record the style of the original row so that the subsequent rows can inherit its style
Map<AnalysisCell, CellStyle> collectionFieldStyleMap = collectionFieldStyleCache.computeIfAbsent(
currentUniqueDataFlag, key -> MapUtils.newHashMap());
collectionFieldStyleMap.put(analysisCell, cell.getCellStyle());
// Find the column merges in the initial row
List<CellRangeAddress> mergedRegions = cachedSheet.getMergedRegions();
if (WriteDirectionEnum.VERTICAL.equals(fillConfig.getDirection()) && fillConfig.getForceNewRow()
&& fillConfig.getAutoStyle()) {
List<CellRangeAddress> oneRowRegionList = mergedRegions.stream()
// if the merge spans across rows, do not process it
.filter(region -> region.getFirstRow() == region.getLastRow() && region.getFirstRow() == row.getRowNum())
.filter(region -> region.getFirstColumn() <= cell.getColumnIndex() && region.getLastColumn() >= cell.getColumnIndex())
.collect(Collectors.toList());
if (!CollectionUtils.isEmpty(oneRowRegionList)) {
originalMergeRegionMap.put(analysisCell, oneRowRegionList);
}
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package cn.idev.excel.test.core.template;

import cn.idev.excel.FastExcel;
import cn.idev.excel.test.util.TestFileUtil;
import cn.idev.excel.write.metadata.fill.FillConfig;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;

import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
* @author wangmeng
*/
public class TemplateMergeTest {

private static File file01;

@BeforeAll
public static void init() {
file01 = TestFileUtil.createNewFile("template" + File.separator + "template_out01.xlsx");
}

@Test
public void testMerge() throws IOException {
write(file01);
}

public static void write(File file) {
FastExcel.write(file)
.withTemplate(TestFileUtil.readFile("template" + File.separator + "template01.xlsx"))
.sheet()
.doFill(data(), FillConfig.builder()
.forceNewRow(true)
.autoStyle(true)
.build());
}

private static List<Map<String, String>> data() {
List<Map<String, String>> list = new LinkedList<>();
for (int i = 0; i < 10; i++) {
Map<String, String> map = new HashMap<>();
map.put("name", "name- " + i);
map.put("number", String.valueOf(i));
map.put("age", String.valueOf(i));
map.put("orderNo", "order-" + i);
map.put("status", "1");
list.add(map);
}
return list;
}


}
Binary file not shown.