Skip to content

Conversation

@milixiang
Copy link

@milixiang milixiang commented Jul 31, 2025

Purpose of the pull request

模板导出时不支持类似下图的需求,故做此次修改
from:
image
to
image

What's changed?

  1. 添加注解 cn.idev.excel.annotation.fill.DynamicColumn 标注该注解的属性会被新逻辑处理 该属性需要为Map类型
  2. 添加dynamicColumnKeys作为动态列的key 动态列会按照list的顺序排列
  3. 调整了dofill方法的fill逻辑
  4. 动态列填充时添加shiftcell操作,解决非末列动态列会覆盖后续列的问题
  5. 添加了个$变量规则
image 如图部分如果使用xxx.$格式进行fill,则会直接遍历集合元素并填充,适合表头横向填充时免去构造对象/map的操作。

Copy link
Member

@psxjoy psxjoy left a comment

Choose a reason for hiding this comment

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

A very useful extension. If possible, we hope to merge and release it in the next version :)
很有用的扩展。如果可以的话,我们希望下个版本尝试合并并发布它:)

@psxjoy psxjoy added the PR: developing This feature will be added in future releases label Aug 7, 2025
@yuzhi-jiang
Copy link

Is it possible to use @ExcelProperty and @DynamicColumn together?

@milixiang
Copy link
Author

DynamicColumn

之前没考虑过这个问题,但看代码应该是支持的,原本的convert方法都会被正常调用。

@yuzhi-jiang
Copy link

yuzhi-jiang commented Sep 28, 2025

DynamicColumn

之前没考虑过这个问题,但看代码应该是支持的,原本的convert方法都会被正常调用。

代码还未合并,是哪一部分在生效呢,我要在当前version可以实现吗,通过SheetWriteHandler等方式啥的

@milixiang
Copy link
Author

DynamicColumn

DynamicColumn

之前没考虑过这个问题,但看代码应该是支持的,原本的convert方法都会被正常调用。

代码还未合并,是哪一部分在生效呢,我要在当前version可以实现吗,通过SheetWriteHandler等方式啥的

不行,跟handler实现逻辑不一样。不确定owner什么时候会合并这个mergeRequest。想用的话可以fork我的分支
自己打包引用 https://github.com/milixiang/fastexcel

@yuzhi-jiang
Copy link

DynamicColumn

DynamicColumn

之前没考虑过这个问题,但看代码应该是支持的,原本的convert方法都会被正常调用。

代码还未合并,是哪一部分在生效呢,我要在当前version可以实现吗,通过SheetWriteHandler等方式啥的

不行,跟handler实现逻辑不一样。不确定owner什么时候会合并这个mergeRequest。想用的话可以fork我的分支 自己打包引用 https://github.com/milixiang/fastexcel

好的

@psxjoy
Copy link
Member

psxjoy commented Sep 28, 2025

hi, @milixiang, sorry for not responding to this PR. There are still some conflicts, can you fix that?
I think we can discuss merging this pr into the main branch.

抱歉没有及时反馈。现在有些文件有冲突,你能处理一下吗?
我觉得现在我们可以讨论合并这个PR了。

如果我没有及时回复,随时pin我就好。

@yuzhi-jiang
Copy link

yuzhi-jiang commented Sep 28, 2025

DynamicColumn

DynamicColumn

之前没考虑过这个问题,但看代码应该是支持的,原本的convert方法都会被正常调用。

代码还未合并,是哪一部分在生效呢,我要在当前version可以实现吗,通过SheetWriteHandler等方式啥的

不行,跟handler实现逻辑不一样。不确定owner什么时候会合并这个mergeRequest。想用的话可以fork我的分支 自己打包引用 https://github.com/milixiang/fastexcel

好的

我clone你的项目后使用了FillTempTest的dynamicFill的数据,修改了导出方式,测试 @ExcelProperty和@DynamicColumn,似乎没有生效

public class DynamicFillData {
    @ExcelProperty("姓名")
    private String name;
    @ExcelProperty("编号")
    private double number;
    @DynamicColumn()
    private Map<String,String> qtyMap;

    @DynamicColumn()
    private Map<String,DynamicFillDataObj> priceMap;
}

    @Test
    public void dynamicFill() {
      String fileName = TestFileUtil.getPath() + "dynamicColumnFill" + System.currentTimeMillis() + ".xlsx";

        DynamicFillData fillData1 = new DynamicFillData();
        fillData1.setName("Zhang San");
        fillData1.setNumber(5.2);
        HashMap<String,String> qtyMap = new HashMap<>();
        qtyMap.put("2023-01-01", "100");
        qtyMap.put("2023-01-02", "200");
        qtyMap.put("2023-01-03", "300");
        fillData1.setQtyMap(qtyMap);
        HashMap<String,DynamicFillDataObj> priceMap = new HashMap<>();
        priceMap.put("2023-01-01", new DynamicFillDataObj("100个", 100));
        priceMap.put("2023-01-02", new DynamicFillDataObj("200个", 200));
        priceMap.put("2023-01-03", new DynamicFillDataObj("300个", 300));
        fillData1.setPriceMap(priceMap);


        DynamicFillData fillData2 = new DynamicFillData();
        fillData2.setName("Li Si");
        fillData2.setNumber(6.3);
        HashMap<String,String> qtyMap2 = new HashMap<>();
        qtyMap2.put("2023-01-01", "100");
        qtyMap2.put("2023-01-02", "200");
        qtyMap2.put("2023-01-03", "300");
        fillData2.setQtyMap(qtyMap2);
        HashMap<String,DynamicFillDataObj> priceMap2 = new HashMap<>();
        priceMap2.put("2023-01-01", new DynamicFillDataObj("100", 100));
        priceMap2.put("2023-01-02", new DynamicFillDataObj("200", 200));
        priceMap2.put("2023-01-03", new DynamicFillDataObj("300", 300));
        fillData2.setPriceMap(priceMap2);

        List<DynamicFillData> fillDataList = new ArrayList<>();
        fillDataList.add(fillData1);
        fillDataList.add(fillData2);

        FastExcel.write(fileName,DynamicFillData.class)
                .excelType(ExcelTypeEnum.XLS)
                .use1904windowing(Boolean.TRUE)
                .sheet()
                .doWrite(fillDataList);


错误信息如下:

cn.idev.excel.exception.ExcelWriteDataConvertException: DynamicColumn annotation must be used with FillConfig.dynamicColumnInfoMap

	at cn.idev.excel.write.executor.AbstractExcelWriteExecutor.converterAndSet(AbstractExcelWriteExecutor.java:130)
	at cn.idev.excel.write.executor.ExcelWriteAddExecutor.addJavaObjectToExcel(ExcelWriteAddExecutor.java:186)
	at cn.idev.excel.write.executor.ExcelWriteAddExecutor.addOneRowOfDataToExcel(ExcelWriteAddExecutor.java:78)
	at cn.idev.excel.write.executor.ExcelWriteAddExecutor.add(ExcelWriteAddExecutor.java:55)
	at cn.idev.excel.write.ExcelBuilderImpl.addContent(ExcelBuilderImpl.java:57)
	at cn.idev.excel.ExcelWriter.write(ExcelWriter.java:70)
	at cn.idev.excel.ExcelWriter.write(ExcelWriter.java:47)
	at cn.idev.excel.write.builder.ExcelWriterSheetBuilder.doWrite(ExcelWriterSheetBuilder.java:61)

milixiang added 5 commits September 28, 2025 17:43
- 新增 DynamicColumnInfo 类封装动态列信息
- 修改 FillConfig 类,使用 Map 存储多个动态列信息
- 更新动态列相关的处理逻辑和异常提示
- 优化单元测试用例
- 新增自引用变量标识符 "$"
- 在单元格填充时,如果变量为自引用标识符,则使用当前行数据进行替换
- 优化了变量替换逻辑,增加了对自引用变量的处理
@milixiang
Copy link
Author

milixiang commented Sep 28, 2025

@yuzhi-jiang 这个改动是给模板填充使用的 不支持write 而且如提示,使用的时候要配置好FillConfig.dynamicColumnInfoMap

@milixiang milixiang force-pushed the dynamicColumnInCollectionFill branch from a86aacf to 2fb9bc7 Compare September 28, 2025 10:15
@milixiang
Copy link
Author

@psxjoy 冲突已解决,请重新review。

@psxjoy psxjoy requested review from Copilot and removed request for jipengfei-jpf September 28, 2025 10:36
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for dynamic columns in Excel fill operations, allowing fields with the @DynamicColumn annotation to generate multiple columns based on runtime data. The implementation enables templates to dynamically expand horizontally based on Map-type data structures.

Key changes:

  • Introduces @DynamicColumn annotation to mark fields for dynamic column processing
  • Adds DynamicColumnInfo class to store configuration for dynamic column keys and group sizes
  • Implements cell shifting logic to prevent column overwriting when expanding dynamic columns
  • Adds $ variable rule for direct collection element traversal in fill operations

Reviewed Changes

Copilot reviewed 9 out of 11 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
FillConfig.java Adds dynamic column configuration storage and builder methods
DynamicColumnInfo.java New class to hold dynamic column metadata (keys and group size)
CellWriteHandlerContext.java Adds cloning support and dynamic column context storage
ExcelWriteFillExecutor.java Implements core dynamic column logic, cell shifting, and $ variable handling
AbstractExcelWriteExecutor.java Refactors cell conversion logic to handle dynamic columns
DynamicColumn.java New annotation to mark fields for dynamic column processing
Test files Adds comprehensive test cases for dynamic column functionality
Comments suppressed due to low confidence (1)

fesod/src/main/java/org/apache/fesod/excel/write/executor/ExcelWriteFillExecutor.java:131

  • [nitpick] Missing space after 'else'. Should be 'else {' for consistent code formatting.
        } else {

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 80 to 82
if (null == fieldName || !dynamicColumnInfoMap.containsKey(fieldName)) {
return dynamicColumnInfoMap.get(DEFAULT_DYNAMIC_INFO_KEY);
}else{
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Potential null pointer exception if dynamicColumnInfoMap is null. The method should check if dynamicColumnInfoMap is null before calling containsKey() or get() methods.

Suggested change
if (null == fieldName || !dynamicColumnInfoMap.containsKey(fieldName)) {
return dynamicColumnInfoMap.get(DEFAULT_DYNAMIC_INFO_KEY);
}else{
if (dynamicColumnInfoMap == null) {
return null;
}
if (fieldName == null || !dynamicColumnInfoMap.containsKey(fieldName)) {
return dynamicColumnInfoMap.get(DEFAULT_DYNAMIC_INFO_KEY);
} else {

Copilot uses AI. Check for mistakes.
cellWriteHandlerContext.getCellMap().put(lastRowIndex + "_" + lastColumnIndex, cellWriteHandlerContext);
DynamicColumnInfo dynamicColumnInfo = fillConfig.getDynamicColumnInfo(field.getName());
if (null == dynamicColumnInfo || CollectionUtils.isEmpty(dynamicColumnInfo.getKeys())) {
throw new ExcelGenerateException(String.format("Plase set dynamic column keys for %s in fillConfig", field.getName()));
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Spelling error: 'Plase' should be 'Please'.

Suggested change
throw new ExcelGenerateException(String.format("Plase set dynamic column keys for %s in fillConfig", field.getName()));
throw new ExcelGenerateException(String.format("Please set dynamic column keys for %s in fillConfig", field.getName()));

Copilot uses AI. Check for mistakes.
List<String> variableList = analysisCell.getVariableList();
for (String fieldName : dynamicFieldMap.keySet()) {
for (String variable : variableList) {
String variableFieldName = variable.split("\\.")[0];
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Potential IndexOutOfBoundsException if the variable doesn't contain a dot. Should check if the split result has at least one element before accessing index 0.

Copilot uses AI. Check for mistakes.
Comment on lines 282 to 283
} else if (variable.contains(COLLECTION_PREFIX)) {
variable = variable.split("\\.")[0];
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Same issue as above - potential IndexOutOfBoundsException if the variable doesn't contain a dot after the contains() check passes. Should verify the split result has elements.

Copilot uses AI. Check for mistakes.
Comment on lines 164 to 170
key = originalVariable.split("\\.")[1];
Object itemBean = o;
if (null == itemBean) {
o = null;
}else{
BeanMap beanMap = BeanMapUtils.create(itemBean);
o = beanMap.get(key);
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Potential IndexOutOfBoundsException if the originalVariable contains a dot but the split result doesn't have at least 2 elements. Should check array length before accessing index 1.

Suggested change
key = originalVariable.split("\\.")[1];
Object itemBean = o;
if (null == itemBean) {
o = null;
}else{
BeanMap beanMap = BeanMapUtils.create(itemBean);
o = beanMap.get(key);
String[] splitVar = originalVariable.split("\\.");
if (splitVar.length >= 2) {
key = splitVar[1];
Object itemBean = o;
if (null == itemBean) {
o = null;
}else{
BeanMap beanMap = BeanMapUtils.create(itemBean);
o = beanMap.get(key);
}
} else {
// Optionally handle the case where there is no second part
key = null;
o = null;

Copilot uses AI. Check for mistakes.
if (null != field && field.isAnnotationPresent(DynamicColumn.class)) {
Map<String, Object> dynamicColumnMap = (Map<String, Object>) originalValue;
FillConfig fillConfig = cellWriteHandlerContext.getFillConfig();
if(null == fillConfig || null == fillConfig.getDynamicColumnInfoMap()){
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

The method getDynamicColumnInfoMap() doesn't exist in the FillConfig class. It should be dynamicColumnInfoMap field access or a proper getter method.

Suggested change
if(null == fillConfig || null == fillConfig.getDynamicColumnInfoMap()){
if(null == fillConfig || null == fillConfig.dynamicColumnInfoMap){

Copilot uses AI. Check for mistakes.
excelWriter.fill(data(), fillConfig, writeSheet);

Map<String, Object> map = new HashMap<String, Object>();
Map<String, Object> map = new HashMap();
Copy link

Copilot AI Sep 28, 2025

Choose a reason for hiding this comment

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

Raw type usage. Should use new HashMap<>() or new HashMap<String, Object>() for type safety.

Suggested change
Map<String, Object> map = new HashMap();
Map<String, Object> map = new HashMap<String, Object>();

Copilot uses AI. Check for mistakes.
@yuzhi-jiang
Copy link

@yuzhi-jiang 这个改动是给模板填充使用的 不支持write 而且如提示,使用的时候要配置好FillConfig.dynamicColumnInfoMap

确实我看代码就觉得怪怪的,因为都涉及到fill,而我的需求是使用wirte,去写类中有map的,不知道能不能支持

@milixiang
Copy link
Author

@yuzhi-jiang write的需求我理解了,后边我会看看怎么实现,但是应该没那么快,你可以先看看fill能满足需求不。

@yuzhi-jiang
Copy link

@yuzhi-jiang write的需求我理解了,后边我会看看怎么实现,但是应该没那么快,你可以先看看fill能满足需求不。

好的,不过fill的方式确实满足不了需求,因为map中的key都是动态的,无法使用固定模板

@milixiang
Copy link
Author

@psxjoy 之前merge好像没成功 还需要我做什么吗?

Copy link
Member

@psxjoy psxjoy left a comment

Choose a reason for hiding this comment

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

Start coding review ,wait.....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

PR: developing This feature will be added in future releases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants