Skip to content

Commit 520da9b

Browse files
committed
feat: Merge the data of the same content in the same cells of the specified column
(cherry picked from commit 8cceadf)
1 parent 58a5509 commit 520da9b

2 files changed

Lines changed: 247 additions & 2 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package cn.idev.excel.write.merge;
2+
3+
import cn.idev.excel.write.handler.RowWriteHandler;
4+
import cn.idev.excel.write.handler.context.RowWriteHandlerContext;
5+
import lombok.AllArgsConstructor;
6+
import lombok.Data;
7+
import org.apache.poi.ss.usermodel.Row;
8+
import org.apache.poi.ss.util.CellRangeAddress;
9+
10+
import java.util.ArrayDeque;
11+
import java.util.Deque;
12+
13+
/**
14+
* @Description Specifies that the column merges adjacent cells with the same content
15+
* @Date 2025/3/8
16+
*/
17+
public class DynamicMergeStrategy implements RowWriteHandler {
18+
19+
/**
20+
* You want to merge columns that are adjacent to the same cell data
21+
*/
22+
private final int columnIndex;
23+
/**
24+
* Extend column
25+
*/
26+
private final int columnExtend;
27+
/**
28+
* size of collection date
29+
*/
30+
private final int dataSize;
31+
private final Deque<MergeRow> rowStack = new ArrayDeque<>();
32+
33+
public DynamicMergeStrategy(int columnIndex, int dataSize) {
34+
this(columnIndex, 1, dataSize);
35+
}
36+
37+
public DynamicMergeStrategy(int columnIndex, int columnExtend, int dataSize) {
38+
if (columnExtend < 1) {
39+
throw new IllegalArgumentException("ColumnExtend must be greater than 1");
40+
}
41+
if (columnIndex < 0) {
42+
throw new IllegalArgumentException("ColumnIndex must be greater than 0");
43+
}
44+
if (dataSize <= 0) {
45+
throw new IllegalArgumentException("dataSize must be greater than 0");
46+
}
47+
this.columnIndex = columnIndex;
48+
this.columnExtend = columnExtend;
49+
this.dataSize = dataSize;
50+
51+
}
52+
53+
@Override
54+
public void afterRowDispose(RowWriteHandlerContext context) {
55+
if (context.getHead() || context.getRelativeRowIndex() == null) {
56+
return;
57+
}
58+
Row row = context.getRow();
59+
rowStack.push(new MergeRow(row, context.getRelativeRowIndex()));
60+
if (context.getRelativeRowIndex() == (dataSize - 1)) {
61+
while (!rowStack.isEmpty()) {
62+
MergeRow lastRow = rowStack.pop();
63+
while (!rowStack.isEmpty()) {
64+
MergeRow prevRow = rowStack.pop();
65+
66+
if (!prevRow.getRow().getCell(columnIndex).getStringCellValue().equals(lastRow.getRow().getCell(columnIndex).getStringCellValue())) {
67+
if (lastRow.getRow().getRowNum() != (prevRow.getRow().getRowNum() + 1)) {
68+
CellRangeAddress cellRangeAddress = new CellRangeAddress(prevRow.getRow().getRowNum() + 1,
69+
lastRow.getRow().getRowNum(), columnIndex, columnIndex + columnExtend - 1);
70+
context.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(cellRangeAddress);
71+
}
72+
rowStack.push(prevRow);
73+
break;
74+
} else {
75+
if (prevRow.getRelativeRowIndex().equals(0)) {
76+
CellRangeAddress cellRangeAddress = new CellRangeAddress(prevRow.getRow().getRowNum(),
77+
lastRow.getRow().getRowNum(), columnIndex, columnIndex + columnExtend - 1);
78+
context.getWriteSheetHolder().getSheet().addMergedRegionUnsafe(cellRangeAddress);
79+
}
80+
}
81+
}
82+
83+
}
84+
}
85+
86+
}
87+
88+
@Data
89+
@AllArgsConstructor
90+
public static class MergeRow {
91+
private Row row;
92+
private Integer relativeRowIndex;
93+
}
94+
}

fastexcel-test/src/test/java/cn/idev/excel/test/demo/write/WriteTest.java

Lines changed: 153 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import java.util.HashSet;
99
import java.util.List;
1010
import java.util.Set;
11+
import java.util.Map;
12+
import java.util.HashMap;
1113

1214
import cn.idev.excel.EasyExcel;
1315
import cn.idev.excel.ExcelWriter;
@@ -18,6 +20,7 @@
1820
import cn.idev.excel.util.ListUtils;
1921
import cn.idev.excel.write.handler.CellWriteHandler;
2022
import cn.idev.excel.write.handler.context.CellWriteHandlerContext;
23+
import cn.idev.excel.write.merge.DynamicMergeStrategy;
2124
import cn.idev.excel.write.merge.LoopMergeStrategy;
2225
import cn.idev.excel.write.style.HorizontalCellStyleStrategy;
2326
import cn.idev.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
@@ -457,7 +460,7 @@ public void handlerStyleWrite() {
457460
// 背景设置为红色
458461
headWriteCellStyle.setFillForegroundColor(IndexedColors.RED.getIndex());
459462
WriteFont headWriteFont = new WriteFont();
460-
headWriteFont.setFontHeightInPoints((short)20);
463+
headWriteFont.setFontHeightInPoints((short) 20);
461464
headWriteCellStyle.setWriteFont(headWriteFont);
462465
// 内容的策略
463466
WriteCellStyle contentWriteCellStyle = new WriteCellStyle();
@@ -467,7 +470,7 @@ public void handlerStyleWrite() {
467470
contentWriteCellStyle.setFillForegroundColor(IndexedColors.GREEN.getIndex());
468471
WriteFont contentWriteFont = new WriteFont();
469472
// 字体大小
470-
contentWriteFont.setFontHeightInPoints((short)20);
473+
contentWriteFont.setFontHeightInPoints((short) 20);
471474
contentWriteCellStyle.setWriteFont(contentWriteFont);
472475
// 这个策略是 头是头的样式 内容是内容的样式 其他的策略可以自己实现
473476
HorizontalCellStyleStrategy horizontalCellStyleStrategy =
@@ -613,6 +616,154 @@ public void dynamicHeadWrite() {
613616
.doWrite(data());
614617
}
615618

619+
/**
620+
* 动态头,实时生成头写入
621+
* 指定列值相同时合并
622+
*/
623+
@Test
624+
public void customHeadReadAndDynamicMergeStrategy() {
625+
List<Map<Integer, String>> maps = yearData();
626+
EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx")
627+
.head(yearHead())
628+
.sheet("模板")
629+
.registerWriteHandler(new DynamicMergeStrategy(0, maps.size()))
630+
.registerWriteHandler(new DynamicMergeStrategy(2, maps.size()))
631+
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
632+
.doWrite(maps);
633+
634+
List<Map<Integer, String>> maps1 = yearData1();
635+
EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx")
636+
.head(yearHead())
637+
.sheet("模板")
638+
.registerWriteHandler(new DynamicMergeStrategy(0, maps1.size()))
639+
.registerWriteHandler(new DynamicMergeStrategy(2, maps1.size()))
640+
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
641+
.doWrite(maps1);
642+
List<Map<Integer, String>> maps2 = yearData2();
643+
EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx")
644+
.head(yearHead())
645+
.sheet("模板")
646+
.registerWriteHandler(new DynamicMergeStrategy(0, maps2.size()))
647+
.registerWriteHandler(new DynamicMergeStrategy(2, maps2.size()))
648+
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
649+
.doWrite(maps2);
650+
List<Map<Integer, String>> maps3 = yearData3();
651+
EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx")
652+
.head(yearHead())
653+
.sheet("模板")
654+
.registerWriteHandler(new DynamicMergeStrategy(0, maps3.size()))
655+
.registerWriteHandler(new DynamicMergeStrategy(2, maps3.size()))
656+
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
657+
.doWrite(maps3);
658+
659+
List<Map<Integer, String>> maps4 = yearData4();
660+
EasyExcel.write(TestFileUtil.getPath() + "customHeadRead" + System.currentTimeMillis() + ".xlsx")
661+
.head(yearHead())
662+
.sheet("模板")
663+
.registerWriteHandler(new DynamicMergeStrategy(0, maps4.size()))
664+
.registerWriteHandler(new DynamicMergeStrategy(2, maps4.size()))
665+
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
666+
.doWrite(maps4);
667+
}
668+
669+
public List<List<String>> yearHead() {
670+
List<List<String>> head = new ArrayList<>();
671+
for (int i = 0; i < 12; i++) {
672+
List<String> h = new ArrayList<>();
673+
if (i < 3) {
674+
h.add("第一季度");
675+
}
676+
if (i >= 3 && i < 6) {
677+
h.add("第二季度");
678+
}
679+
if (i >= 6 && i < 9) {
680+
h.add("第三季度");
681+
}
682+
if (i >= 9) {
683+
h.add("第四季度");
684+
}
685+
h.add("第" + (i + 1) + "月");
686+
head.add(h);
687+
}
688+
return head;
689+
}
690+
691+
public List<Map<Integer, String>> yearData() {
692+
List<Map<Integer, String>> data = new ArrayList<>();
693+
for (int i = 0; i < 100; i++) {
694+
Map<Integer, String> map = new HashMap<>();
695+
for (int j = 0; j < 12; j++) {
696+
if (i < 20) {
697+
map.put(j, "第" + (j + 1) + "月" + "前20条数据");
698+
} else {
699+
map.put(j, "第" + (j + 1) + "月");
700+
}
701+
702+
}
703+
data.add(map);
704+
}
705+
return data;
706+
}
707+
708+
public List<Map<Integer, String>> yearData1() {
709+
List<Map<Integer, String>> data = new ArrayList<>();
710+
for (int i = 0; i < 3; i++) {
711+
Map<Integer, String> map = new HashMap<>();
712+
for (int j = 0; j < 12; j++) {
713+
if (i > 0) {
714+
map.put(j, (j + 1) + "");
715+
} else {
716+
map.put(j, i + "");
717+
}
718+
719+
}
720+
data.add(map);
721+
}
722+
return data;
723+
}
724+
725+
public List<Map<Integer, String>> yearData4() {
726+
List<Map<Integer, String>> data = new ArrayList<>();
727+
for (int i = 0; i < 3; i++) {
728+
Map<Integer, String> map = new HashMap<>();
729+
for (int j = 0; j < 12; j++) {
730+
if (i < 2) {
731+
map.put(j, (j + 1) + "");
732+
} else {
733+
map.put(j, i + "");
734+
}
735+
736+
}
737+
data.add(map);
738+
}
739+
return data;
740+
}
741+
742+
public List<Map<Integer, String>> yearData3() {
743+
List<Map<Integer, String>> data = new ArrayList<>();
744+
for (int i = 0; i < 3; i++) {
745+
Map<Integer, String> map = new HashMap<>();
746+
for (int j = 0; j < 12; j++) {
747+
map.put(j, j + "");
748+
749+
}
750+
data.add(map);
751+
}
752+
return data;
753+
}
754+
755+
public List<Map<Integer, String>> yearData2() {
756+
List<Map<Integer, String>> data = new ArrayList<>();
757+
for (int i = 0; i < 3; i++) {
758+
Map<Integer, String> map = new HashMap<>();
759+
for (int j = 0; j < 12; j++) {
760+
map.put(j, i + "");
761+
}
762+
data.add(map);
763+
}
764+
return data;
765+
}
766+
616767
/**
617768
* 自动列宽(不太精确)
618769
* <p>

0 commit comments

Comments
 (0)