POI通过模板导出excel(包含表头合并处理)

最近接触到比较多poi相关的需求,总结一下通过模板导出以及表头合并的一些复杂情况处理。
简单使用的话可以参考下我之前写的 POI实现导入导出excel

1、POI通过模板导出

其实通过模板导出的原理,无非就是去获取到指定的模板文件,然后再去转换成文件流,最后填充自己的数据再转出excel文件。

先看下模板:(模板包含了一些样式,需要手动创建比较麻烦,所以采用模板转文件流的方式)
在这里插入图片描述

看下代码:

    public static void exportMain(String templatePath){

        //获取模板
        File file = new File(templatePath);

        InputStream is = null;

        XSSFWorkbook wb = null;

        XSSFSheet sheet = null;

        InputStream exportInput = null;

        try {

            is = new FileInputStream(file);// 将excel文件转为输入流

            wb = new XSSFWorkbook(is);// 创建个workbook,

            // 获取第一个sheet
            sheet = wb.getSheetAt(0);

            //例子是第四行开始表头
            XSSFRow row3 = sheet.getRow(3);

            //获取总列数
            int cellNum = row3.getLastCellNum();

            //获取第四行的列
            for (int i=0;i<cellNum;i++){
                XSSFCell cell = row3.getCell(i);
                System.out.println("第4行第"+(i+1)+"列:"+cell.toString());
            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        exportMain("C:/Users/53065/Desktop/模板表.xlsx");
    }

打印结果:

第4行第1列:序号
第4行第2列:项目名称
第4行第3列:负责单位
第4行第4列:目前项目状态(完工、在建、前期)

第4行第5列:计划完工\开工时间
第4行第6列:建设内容
第4行第7列:进展情况
第4行第8列:2023年工作内容
第4行第9列:总投资
第4行第10列:截止2022年12月31日已完成投资
第4行第11列:
第4行第12列:
第4行第13列:
第4行第14列:2023年计划投资
第4行第15列:
第4行第16列:
第4行第17列:
第4行第18列:
第4行第19列:
第4行第20列:
第4行第21列:
第4行第22列:资金具体来源
第4行第23列:
第4行第24列:备注

从输出结果可以看出,模板的数据我们已经拿到,以上例子只是为了简单获取模板中的数据,通过模板再可以进行下一步的操作,这部分可以参考 POI实现导入导出excel

注意:XSSFSheet是获取xlsx文件格式,HSSFSheet是获取xls格式
请根据具体情况修改!这边只是比较简单的例子。

2、POI表头合并处理

在这里插入图片描述
我们从这部分表头可以发现,有合并多个单元格的表头。
在很多需求中,导出需要数据与表头对应,如果通过列的序列去指定这种方式,其实是很不稳妥的,在模板列顺序改变后就会出现问题,所以如果出现这种比较复杂的表头时,我们采用的做法是合并表头的拼接作为表头唯一标识,对应数据(例如 2023年计划投资-计划投资额-总额

那么需要怎么做呢?先来看个方法:(以上表头来看,我们需要处理的是第四行到第六行的表头

//获取单元格合并情况数
sheet.getNumMergedRegions();

//单元格合并情况
CellRangeAddress region = sheet.getMergedRegion(下标);
int firstRow = region.getFirstRow();//合并开始行
int firstColumn = region.getFirstColumn();//合并开始列
int lastColumn = region.getLastColumn();//合并结束列
int lastRow = region.getLastRow();//合并结束行

有了以上方法,我们的思路就是获取列合并的表头,只是行合并的表头过滤掉不处理,然后用map来存储,key为列的下标即可。

    public static void exportMain(String templatePath){

        //获取模板
        File file = new File(templatePath);

        InputStream is = null;

        XSSFWorkbook wb = null;

        XSSFSheet sheet = null;

        InputStream exportInput = null;

        try {

            is = new FileInputStream(file);// 将excel文件转为输入流

            wb = new XSSFWorkbook(is);// 创建个workbook,

            // 获取第一个sheet
            sheet = wb.getSheetAt(0);

            //存储表头合并部分
            Map<String,String> newBtMap = new HashMap<>();
            //获取合并部分
            for (int i=0;i<sheet.getNumMergedRegions();i++){
                CellRangeAddress region = sheet.getMergedRegion(i);
                int firstRow = region.getFirstRow();
                int firstColumn = region.getFirstColumn();
                int lastColumn = region.getLastColumn();
                int lastRow = region.getLastRow();
                //锁定表头且是列合并的,只是列合并的不管
                if (firstRow <3 || firstRow>5 || (lastColumn-firstColumn==0 && lastRow-firstRow>0)){
                    continue;
                }

                Row row = sheet.getRow(firstRow);
                Cell cell = row.getCell(firstColumn);
                String newBtName = cell.toString().trim();
                System.out.println("开始列:"+ firstColumn +",结束列:"+ lastColumn +",开始行:"+firstRow+",结束列:"+ lastRow+",值:"+newBtName);
                if (StringUtils.isEmpty(newBtName)){
                    continue;
                }
                for (int j=firstColumn;j<=lastColumn;j++){
                    String oldBtName = newBtMap.get(String.valueOf(j));
                    String newScBtName = "";
                    if (!StringUtils.isEmpty(oldBtName)){
                        newScBtName = oldBtName.trim() + "-";
                    }
                    newScBtName += newBtName ;
                    newBtMap.put(String.valueOf(j),newScBtName.replaceAll("\\s*|\r|\n|\t",""));
                }
            }

            System.out.println("合并表头:"+ JSONObject.toJSONString(newBtMap));

//            //例子是第四行开始表头
//            XSSFRow row3 = sheet.getRow(3);
//
//            //获取总列数
//            int cellNum = row3.getLastCellNum();
//
//            //获取第四行的列
//            for (int i=0;i<cellNum;i++){
//                XSSFCell cell = row3.getCell(i);
//                System.out.println("第4行第"+(i+1)+"列:"+cell.toString());
//            }

        }catch (Exception e){
            e.printStackTrace();
        }
    }

打印结果:

开始列:9,结束列:12,开始行:3,结束列:3,值:截止2022年12月31日已完成投资
开始列:13,结束列:20,开始行:3,结束列:3,值:2023年计划投资
开始列:21,结束列:22,开始行:3,结束列:3,值:资金具体来源
开始列:13,结束列:17,开始行:4,结束列:4,值:计划投资额
合并表头:{"11":"截止2022年12月31日已完成投资","22":"资金具体来源","12":"截止2022年12月31日已完成投资","13":"2023年计划投资-计划投资额","14":"2023年计划投资-计划投资额","15":"2023年计划投资-计划投资额","16":"2023年计划投资-计划投资额","17":"2023年计划投资-计划投资额","18":"2023年计划投资","19":"2023年计划投资","9":"截止2022年12月31日已完成投资","20":"2023年计划投资","10":"截止2022年12月31日已完成投资","21":"资金具体来源"}

这时会发现,这部分表头只包含了合并了列单元格的表头,那么还需要处理无单元格合并的情况。并将两种情况合并。

    public static void exportMain(String templatePath){

        //获取模板
        File file = new File(templatePath);

        InputStream is = null;

        XSSFWorkbook wb = null;

        XSSFSheet sheet = null;

        InputStream exportInput = null;

        try {

            is = new FileInputStream(file);// 将excel文件转为输入流

            wb = new XSSFWorkbook(is);// 创建个workbook,

            // 获取第一个sheet
            sheet = wb.getSheetAt(0);

            //获取表头
            Row btRow = sheet.getRow(3);
            //获取总列数
            int totalCellNum = btRow.getLastCellNum();

            //存储表头合并部分
            Map<String,String> newBtMap = new HashMap<>();
            //获取合并部分
            for (int i=0;i<sheet.getNumMergedRegions();i++){
                CellRangeAddress region = sheet.getMergedRegion(i);
                int firstRow = region.getFirstRow();
                int firstColumn = region.getFirstColumn();
                int lastColumn = region.getLastColumn();
                int lastRow = region.getLastRow();
                //锁定表头且是列合并的,只是列合并的不管
                if (firstRow <3 || firstRow>5 || (lastColumn-firstColumn==0 && lastRow-firstRow>0)){
                    continue;
                }

                Row row = sheet.getRow(firstRow);
                Cell cell = row.getCell(firstColumn);
                String newBtName = cell.toString().trim();
                System.out.println("开始列:"+ firstColumn +",结束列:"+ lastColumn +",开始行:"+firstRow+",结束列:"+ lastRow+",值:"+newBtName);
                if (StringUtils.isEmpty(newBtName)){
                    continue;
                }
                for (int j=firstColumn;j<=lastColumn;j++){
                    String oldBtName = newBtMap.get(String.valueOf(j));
                    String newScBtName = "";
                    if (!StringUtils.isEmpty(oldBtName)){
                        newScBtName = oldBtName.trim() + "-";
                    }
                    newScBtName += newBtName ;
                    newBtMap.put(String.valueOf(j),newScBtName.replaceAll("\\s*|\r|\n|\t",""));
                }
            }

            System.out.println("合并表头:"+ JSONObject.toJSONString(newBtMap));


            //取出无合并情况的表头(4-6行)
            Map<String,String> btMap = new HashMap<>();
            for (int j=3;j<6;j++){
                Row row = sheet.getRow(j);
                for (int i=0;i<totalCellNum;i++){
                    Cell cell = row.getCell(i);
                    if (StringUtils.isEmpty(cell.toString())){
                        continue;
                    }
                    String cellValue = cell.toString().trim().replaceAll("\\s*|\r|\n|\t","");
                    System.out.println("第"+(j+1)+"行第"+(i+1)+"列数据:"+ cellValue);
                    btMap.put(String.valueOf(i),cellValue);
                }
            }

            System.out.println("无合并情况表头:"+JSONObject.toJSONString(btMap));

            //合并所有表头
            //遍历拼接表头去拼接全部数据
            Set<String> newBtKeys = newBtMap.keySet();
            for (String key : newBtKeys){
                //最后一行表头
                String lastBtName = btMap.get(key);
                //除最后一行外的表头
                String cLastBtName = newBtMap.get(key);
                //最全的表头
                String allBtName = cLastBtName;
                if (!StringUtils.isEmpty(lastBtName) && !cLastBtName.equals(lastBtName)){
                    allBtName += "-"+lastBtName;
                }
                btMap.put(key,allBtName);
            }

            System.out.println("导出模板拼接表头:"+JSONObject.toJSONString(btMap));

        }catch (Exception e){
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        exportMain("C:/Users/53065/Desktop/模板表.xlsx");
    }

打印结果:

开始列:9,结束列:12,开始行:3,结束列:3,值:截止2022年12月31日已完成投资
开始列:13,结束列:20,开始行:3,结束列:3,值:2023年计划投资
开始列:21,结束列:22,开始行:3,结束列:3,值:资金具体来源
开始列:13,结束列:17,开始行:4,结束列:4,值:计划投资额
合并表头:{"11":"截止2022年12月31日已完成投资","22":"资金具体来源","12":"截止2022年12月31日已完成投资","13":"2023年计划投资-计划投资额","14":"2023年计划投资-计划投资额","15":"2023年计划投资-计划投资额","16":"2023年计划投资-计划投资额","17":"2023年计划投资-计划投资额","18":"2023年计划投资","19":"2023年计划投资","9":"截止2022年12月31日已完成投资","20":"2023年计划投资","10":"截止2022年12月31日已完成投资","21":"资金具体来源"}
第4行第1列数据:序号
第4行第2列数据:项目名称
第4行第3列数据:负责单位
第4行第4列数据:目前项目状态(完工、在建、前期)
第4行第5列数据:计划完工\开工时间
第4行第6列数据:建设内容
第4行第7列数据:进展情况
第4行第8列数据:2023年工作内容
第4行第9列数据:总投资
第4行第10列数据:截止2022年12月31日已完成投资
第4行第14列数据:2023年计划投资
第4行第22列数据:资金具体来源
第4行第24列数据:备注
第5行第10列数据:已投资总额
第5行第11列数据:1.财政安排
第5行第12列数据:2.融资
第5行第13列数据:3.自筹
第5行第14列数据:计划投资额
第5行第19列数据:1.财政安排
第5行第20列数据:2.自筹
第5行第21列数据:3.融资
第5行第22列数据:1.自筹
第5行第23列数据:2.融资
第6行第14列数据:总额
第6行第15列数据:一季度
第6行第16列数据:二季度
第6行第17列数据:三季度
第6行第18列数据:四季度
无合并情况表头:{"11":"2.融资","22":"2.融资","23":"备注","12":"3.自筹","13":"总额","14":"一季度","15":"二季度","16":"三季度","17":"四季度","18":"1.财政安排","19":"2.自筹","0":"序号","1":"项目名称","2":"负责单位","3":"目前项目状态(完工、在建、前期)","4":"计划完工\\开工时间","5":"建设内容","6":"进展情况","7":"2023年工作内容","8":"总投资","9":"已投资总额","20":"3.融资","21":"1.自筹","10":"1.财政安排"}
导出模板拼接表头:{"11":"截止2022年12月31日已完成投资-2.融资","22":"资金具体来源-2.融资","23":"备注","12":"截止2022年12月31日已完成投资-3.自筹","13":"2023年计划投资-计划投资额-总额","14":"2023年计划投资-计划投资额-一季度","15":"2023年计划投资-计划投资额-二季度","16":"2023年计划投资-计划投资额-三季度","17":"2023年计划投资-计划投资额-四季度","18":"2023年计划投资-1.财政安排","19":"2023年计划投资-2.自筹","0":"序号","1":"项目名称","2":"负责单位","3":"目前项目状态(完工、在建、前期)","4":"计划完工\\开工时间","5":"建设内容","6":"进展情况","7":"2023年工作内容","8":"总投资","9":"截止2022年12月31日已完成投资-已投资总额","20":"2023年计划投资-3.融资","21":"资金具体来源-1.自筹","10":"截止2022年12月31日已完成投资-1.财政安排"}

从最终结果可以看到处理成功啦

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谦风(Java)

一起学习,一起进步(✪ω✪)

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值