easyexcel content append and cell merge

    The requirement here is that if the table does not exist, create a new table and fill in the data. If the table exists, add content and support cell merging.

    Content appending needs to be inserted in two ways. The first is that there is no table, and the header needs to be generated and the content is inserted. The second is that there is already a table, and this table is used as a template. There is no need to set the header and the content is added directly. Here a new form needs to be generated and, if necessary, the old form should be deleted at the end.

    Regarding cell merging, there are such examples on the Internet. To implement it using easyexcel, we need to define an interface implementation of CellWriteHandler. In the implementation class, override the afterCellDispose() method. In this case, it is the CustomCellWriteHandler class.

    In order to verify our results here, we use springboot to construct two different requests, one is to create excel, and the other is to append excel.

    The main code is as follows:

    Dependency file: 

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>
<dependency>
	<groupId>com.alibaba</groupId>
	<artifactId>easyexcel-core</artifactId>
	<version>3.2.1</version>
</dependency>

     Controller class:

import com.example.service.ExcelService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/excel")
public class ExcelController {

    @Autowired
    private ExcelService excelService;

    @GetMapping("/create")
    public ResponseEntity create() {
        return ResponseEntity.ok(excelService.create());
    }

    @GetMapping("/append")
    public ResponseEntity append() {
        return ResponseEntity.ok(excelService.append());
    }
}

    Service class:

package com.example.service;

import com.alibaba.excel.EasyExcel;
import com.example.config.CustomCellWriteHandler;
import com.example.dao.ExcelDao;
import com.example.model.ExcelData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.File;

@Service
public class ExcelService {

    @Autowired
    private ExcelDao excelDao;

    private String fileName = "e:\\test\\student.xlsx";

    private String tempName = "e:\\test\\temp.xlsx";

    private int mergeRowIndex = 0;

    private int[] mergeColumnIndexes = {0, 1, 2};

    public boolean create() {
        File out = new File(fileName);
        EasyExcel.write(out, ExcelData.class)
                .sheet("student")
                .registerWriteHandler(new CustomCellWriteHandler(mergeRowIndex,mergeColumnIndexes))
                .doWrite(excelDao.createData());
        return true;
    }

    public boolean append() {
        File out = new File(fileName);
        File temp = new File(tempName);
        if (out.exists()) {
            // 采用模板方式写入
            EasyExcel.write(out).needHead(false)
                    .withTemplate(out)
                    .file(temp)
                    .sheet("student")
                    .registerWriteHandler(new CustomCellWriteHandler(mergeRowIndex,mergeColumnIndexes))
                    .doWrite(excelDao.appendData());
            // out.delete();
            // temp.renameTo(out);
        }
        return true;
    }


}

    Data interface mock class:

package com.example.dao;

import com.example.model.ExcelData;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
public class ExcelDao {
    public List<ExcelData> createData() {
        List<ExcelData> list = new ArrayList<>();
        list.add(new ExcelData("罗芳",18, "female","语文",100));
        list.add(new ExcelData("罗芳",18, "female","数学",99));
        list.add(new ExcelData("王芳",17, "female","语文",80));
        list.add(new ExcelData("王芳",17, "female","数学",60));
        return list;
    }

    public List<ExcelData> appendData() {
        List<ExcelData> list = new ArrayList<>();
        list.add(new ExcelData("张雪琴",18, "female","语文",88));
        list.add(new ExcelData("张雪琴",18, "female","数学",75));
        list.add(new ExcelData("王勇",18, "male","语文",80));
        list.add(new ExcelData("王勇",18, "male","数学",92));
        return list;
    }
}

     Form entity class:

package com.example.model;

import com.alibaba.excel.annotation.ExcelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class ExcelData {
    @ExcelProperty("学生姓名")
    private String name;

    @ExcelProperty("年龄")
    private int age;

    @ExcelProperty("性别")
    private String gender;

    @ExcelProperty({"课程", "课程名称"})
    private String courseName;

    @ExcelProperty({"课程", "分数"})
    private double score;
}

    Table cell merge strategy class:

package com.example.config;

import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.handler.CellWriteHandler;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
import org.apache.poi.ss.usermodel.Cell;
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;

import java.util.List;


public class CustomCellWriteHandler implements CellWriteHandler {

    private int mergeRowIndex;
    private int[] mergeColumnIndexes;

    public CustomCellWriteHandler(int mergeRowIndex, int[] mergeColumnIndexes) {
        this.mergeRowIndex = mergeRowIndex;
        this.mergeColumnIndexes = mergeColumnIndexes;
    }

    @Override
    public void afterCellDispose(WriteSheetHolder writeSheetHolder, WriteTableHolder writeTableHolder, List<WriteCellData<?>> cellDataList, Cell cell, Head head, Integer relativeRowIndex, Boolean isHead) {
        int curRowIndex = cell.getRowIndex();
        int curColIndex = cell.getColumnIndex();
        if (curRowIndex > mergeRowIndex) {
            for (int i = 0; i < mergeColumnIndexes.length; i++) {
                if (curColIndex == mergeColumnIndexes[i]) {
                    mergeWithPrevRow(writeSheetHolder, cell, curRowIndex, curColIndex);
                    break;
                }
            }
        }
    }

    private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
        Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
        Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
        if (prevRow != null) {
            Cell prevCell = prevRow.getCell(curColIndex);
            Object prevData = prevCell.getCellType() == CellType.STRING ? prevCell.getStringCellValue() : prevCell.getNumericCellValue();
            boolean isDataSame = curData.equals(prevData);
            if (isDataSame) {
                Sheet sheet = writeSheetHolder.getSheet();
                List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
                boolean isMerged = false;
                for (int i = 0; i < mergeRegions.size(); i++) {
                    CellRangeAddress cellRangeAddress = mergeRegions.get(i);
                    if (cellRangeAddress.isInRange(curRowIndex - 1, curColIndex)) {
                        sheet.removeMergedRegion(i);
                        cellRangeAddress.setLastRow(curRowIndex);
                        sheet.addMergedRegionUnsafe(cellRangeAddress);
                        isMerged = true;
                    }
                }
                if (!isMerged) {
                    CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
                    sheet.addMergedRegion(cellRangeAddress);
                }
            }
        }
    }


}

    To run the example, visit http://localhost:8080/excel/create and http://localhost:8080/excel/append respectively.

    Finally, two files will be generated, namely e:\test\student.xlsx e:\test\temp.xlsx, the contents are as follows:

    The content is in line with expectations. The first one has only two student information, and the second table has four student information because it is appended. But for cell merging, we specified columns 0, 1, and 2, that is, name, age, and gender need to be merged. In fact, they are indeed merged, but it seems that the merge exceeds the range. What we want to merge is the age and gender of the same students, not all ages and genders with the same value.

    The solution is to add a judgment in the merge strategy. If the column values ​​​​of the merged rows (current attribute columns age, gender) are the same, and the first column value (the only attribute name) is also the same, then merge, instead of merging if the contents of the columns are the same. .

    The main logic of the changed merge strategy:

private void mergeWithPrevRow(WriteSheetHolder writeSheetHolder, Cell cell, int curRowIndex, int curColIndex) {
	Object curData = cell.getCellType() == CellType.STRING ? cell.getStringCellValue() : cell.getNumericCellValue();
	Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
	Row curRow = cell.getSheet().getRow(curRowIndex);
	if (prevRow == null) {
		prevRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
	}
	Cell prevFirstColCell = prevRow.getCell(0);
	Cell curFirstColCell = curRow.getCell(0);
	Object prevFirstColData = prevFirstColCell.getCellType() == CellType.STRING ? prevFirstColCell.getStringCellValue() : prevFirstColCell.getNumericCellValue();
	Object curFirstColData = curFirstColCell.getCellType() == CellType.STRING ? curFirstColCell.getStringCellValue() : curFirstColCell.getNumericCellValue();
	Cell prevCell = prevRow.getCell(curColIndex);
	Object prevData = prevCell.getCellType() == CellType.STRING ? prevCell.getStringCellValue() : prevCell.getNumericCellValue();
	boolean isDataSame = curData.equals(prevData) && curFirstColData.equals(prevFirstColData);
	if (isDataSame) {
		Sheet sheet = writeSheetHolder.getSheet();
		List<CellRangeAddress> mergeRegions = sheet.getMergedRegions();
		boolean isMerged = false;
		for (int i = 0; i < mergeRegions.size(); i++) {
			CellRangeAddress cellRangeAddress = mergeRegions.get(i);
			if (cellRangeAddress.isInRange(curRowIndex - 1, curColIndex)) {
				sheet.removeMergedRegion(i);
				cellRangeAddress.setLastRow(curRowIndex);
				sheet.addMergedRegionUnsafe(cellRangeAddress);
				isMerged = true;
			}
		}
		if (!isMerged) {
			CellRangeAddress cellRangeAddress = new CellRangeAddress(curRowIndex - 1, curRowIndex, curColIndex, curColIndex);
			sheet.addMergedRegion(cellRangeAddress);
		}
	}

}

    Generated form: 

    The problem of cells exceeding the merge range seems to be solved, perfectly. 

    In some codes, when obtaining a certain column of the previous row record, the code is as follows:

Cell prevCell = cell.getSheet().getRow(curRowIndex - 1).getCell(curColIndex);

    In practice, a null pointer exception may be reported here. In fact, the data is in the cache. We need to obtain it like this:

Row prevRow = cell.getSheet().getRow(curRowIndex - 1);
if (prevRow == null) {
	prevRow = writeSheetHolder.getCachedSheet().getRow(curRowIndex - 1);
}

    Alternatively, it is also possible to add a judgment if (prevRow != null) to the subsequent logic.

Guess you like

Origin blog.csdn.net/feinifi/article/details/130291090