Poi实现根据word模板导出-文本段落篇

最近在做word模板导出的需求,本来意为是很简单,做起来才发现细节上有很多东西处理起来还是比较麻烦的(客户要求太多!!!)

因此我把涉及到基于word模板导出的这部分整理了一下,大家直接取经,内容有点多分了三部分写:文本段落、图表、表格。这三部分应该涵盖大部分应用场景了。

(需要完整代码的直接看最后位置!!!)

前言:

poi操作word原理同excel一样,先获取word对象,通过对象操作文件内容。poi实际上是将word解析成xml,然后在读取里面的内容。

逻辑:

1、获取word对象:

// 获取docx解析对象
//获取word模板
InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");
XWPFDocument document = new XWPFDocument(is);

2、修改word模板,将需要封装的数据通过占位符代替

Poi实现根据word模板导出-文本段落篇_第1张图片

比如这里的${xxxx}就是占位符,我们通过代码可以将这部分替换成我们需要的内容。注意格式是自己定义的,并不是固定这种,自己选择什么格式,就按照对应格式处理,下面给指出。

3、封装我们的业务数据,根据你的具体业务填充数据,Map中的key是我们占位符中的值,value就是要展示在页面的值

// 替换word模板中占位符数据,根据业务自行封装
Map params = new HashMap<>();
params.put("area1", "山东省");
params.put("area2", "河南省");
params.put("area3", "北京市");
params.put("area4", "天津市");
params.put("area5", "陕西省");
params.put("areaRate1", "42.15");
params.put("areaRate2", "22.35");
params.put("areaRate3", "42.35");
params.put("areaRate4", "23.11");
params.put("areaRate5", "15.34");

4、操作document文件对象,先获取段落,在获取段落中每段内容,根据我们的占位符格式判断是否存在,存在则更新我们占位符位置的内容。

private void changeText(XWPFDocument document, Map params) {
    //获取段落集合
    List paragraphs = document.getParagraphs();

    for (XWPFParagraph paragraph : paragraphs) {
        //判断此段落时候需要进行替换
        String text = paragraph.getText();
        if(checkText(text)){
            List runs = paragraph.getRuns();
            for (XWPFRun run : runs) {
                //替换模板原来位置
                String value = changeValue(run.toString(), params);
                if (Objects.nonNull(value)) {
                    run.setText(value, 0);
                }
            }
        }
    }
}

/**
 * 匹配传入信息集合与模板
 * @param value 模板需要替换的区域
 * @param textMap 传入信息集合
 * @return 模板需要替换区域信息集合对应值
 */
public static String changeValue(String value, Map textMap){
    Set> textSets = textMap.entrySet();
    for (Map.Entry textSet : textSets) {
        //匹配模板与替换值 格式${key}
        String key = "${"+textSet.getKey()+"}";
        if(value.indexOf(key)!= -1){
            value = value.replace(key, textSet.getValue());
        }
    }
    //模板未匹配到区域替换为空
    if(checkText(value)){
        value = "";
    }
    return value;
}


/**
 * 判断文本中时候包含$
 * @param text 文本
 * @return 包含返回true,不包含返回false
 */
public static boolean checkText(String text){
    boolean check  =  false;
    if(text.indexOf("$")!= -1){
        check = true;
    }
    return check;
}

解释:String key = "${"+textSet.getKey()+"}";这段代码就是我们自定义的占位符格式,如果你是其他格式在这里做相应修改

ok,完成!!!

但是这时大部分情况可能会遇到下面问题:

Poi实现根据word模板导出-文本段落篇_第2张图片

可以看到有的成功、有的失败。之前说过poi本质就是把word解析成xml,那我们直接把word导出为xml看一下,用office可以快速把word导出为xml格式,这里我们把我们的模板导出为xml看下同样的占位符有啥不同???

Poi实现根据word模板导出-文本段落篇_第3张图片

在idea里打开看一下,发现即使我们占位符都是通过复制粘贴在word里,他的xml格式还是会发生变化。

Poi实现根据word模板导出-文本段落篇_第4张图片

有两种解决方案:

1、最简单的办法把模板里的占位符粘贴到文本,在复制粘贴到word。

Poi实现根据word模板导出-文本段落篇_第5张图片

2、或者直接修改xml,在把xml导出为word。可以在idea中看xml文件,格式化一下,这种方式只要没有把xml格式改错,基本解析就不会再出问题了。

Poi实现根据word模板导出-文本段落篇_第6张图片

修改后再次执行程序:

Poi实现根据word模板导出-文本段落篇_第7张图片

成功!!!

完整代码:

package com.javacoding.controller;

import cn.hutool.core.util.RandomUtil;
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.openxml4j.util.ZipSecureFile;
import org.apache.poi.ss.util.CellRangeAddress;
import org.apache.poi.xddf.usermodel.chart.*;
import org.apache.poi.xssf.usermodel.XSSFCell;
import org.apache.poi.xssf.usermodel.XSSFRow;
import org.apache.poi.xssf.usermodel.XSSFSheet;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.poi.xwpf.usermodel.XWPFChart;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.openxmlformats.schemas.drawingml.x2006.chart.*;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.io.*;
import java.util.*;


@RestController
@RequestMapping("/word")
public class WordController {

    @RequestMapping("/export")
    public void exportWord() throws Exception {
        //获取word模板
        InputStream is = WordController.class.getResourceAsStream("/template/wordChartTemplate.docx");

        try {
            ZipSecureFile.setMinInflateRatio(-1.0d);
            // 获取docx解析对象
            XWPFDocument document = new XWPFDocument(is);
            // 解析替换文本段落对象
            // 替换word模板中占位符数据,根据业务自行封装
            Map params = new HashMap<>();
            params.put("area1", "山东省");
            params.put("area2", "河南省");
            params.put("area3", "北京市");
            params.put("area4", "天津市");
            params.put("area5", "陕西省");
            params.put("areaRate1", "42.15");
            params.put("areaRate2", "22.35");
            params.put("areaRate3", "42.35");
            params.put("areaRate4", "23.11");
            params.put("areaRate5", "15.34");
            changeText(document, params);
            // 输出新文件
            FileOutputStream fos = new FileOutputStream("D:\\test\\test.docx");
            document.write(fos);
            document.close();
            fos.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void changeText(XWPFDocument document, Map params) {
        //获取段落集合
        List paragraphs = document.getParagraphs();

        for (XWPFParagraph paragraph : paragraphs) {
            //判断此段落时候需要进行替换
            String text = paragraph.getText();
            if(checkText(text)){
                List runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    String value = changeValue(run.toString(), params);
                    if (Objects.nonNull(value)) {
                        run.setText(value, 0);
                    }
                }
            }
        }
    }

    /**
     * 判断文本中时候包含$
     * @param text 文本
     * @return 包含返回true,不包含返回false
     */
    public static boolean checkText(String text){
        boolean check  =  false;
        if(text.indexOf("$")!= -1){
            check = true;
        }
        return check;
    }

    /**
     * 匹配传入信息集合与模板
     * @param value 模板需要替换的区域
     * @param textMap 传入信息集合
     * @return 模板需要替换区域信息集合对应值
     */
    public static String changeValue(String value, Map textMap){
        Set> textSets = textMap.entrySet();
        for (Map.Entry textSet : textSets) {
            //匹配模板与替换值 格式${key}
            String key = "${"+textSet.getKey()+"}";
            if(value.indexOf(key)!= -1){
                value = value.replace(key, textSet.getValue());
            }
        }
        //模板未匹配到区域替换为空
        if(checkText(value)){
            value = "";
        }
        return value;
    }

}

你可能感兴趣的:(POI系列,java,word,开发语言)