POI生成word文档,包括标题,段落,表格,统计图(非图片格式)

    Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程式对Microsoft Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写,意为“简洁版的模糊实现”。

POI生成excel是比较常用的技术之一,但是用来生成word相对来说比较少,今天演示一下利用POI生成word文档的整个流程,,当然方法有很多种,这是我感觉比较方便的一种。
需要实现的功能

  • 在文档中动态生成章节标题、文本段落、表格标题、表格、统计图等等。
  • 给每个章节编号
  • 删除没有替换的占位符

总体思路
这种方法的本质其实是动态替换,动态替换的意思是在模板中写入很多某种特定格式的占位符(关键词)以及图的样例,如果前端需要生成这个章节的内容,那么,把这个关键词传到后端,再在模板中寻找关键词,有则替换成具体内容,无则不替换。最后把没有替换掉的占位符删除,从而达到动态替换的效果。

具体实现过程如下

  1. 创建word模板.文档中包含若干个章节,章节中包含章节标题、文本段落、表格标题、表格、统计图的占位符。占位符的内容依据自己的实际情况而定。
  2. 加载模板,判断该章节是否存在,如果存在就替换具体数据。本示例数据都是模拟的,实际情况应该从数据库中查出。
  3. 再次遍历替换后的word文档,把没有替换掉的占位符删除。

示例

1. 创建模板

如下图所示:
在这里插入图片描述

2.替换数据

2.1 测试类

import org.apache.poi.xwpf.usermodel.*;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.util.ResourceUtils;

import java.io.*;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Pattern;

@RunWith(SpringRunner.class)
@SpringBootTest
public class PoiChartsTest {
   
    @Autowired
    private  PoiPropsConfig config;

    @Autowired
    private PoiUtils poiUtils;

    //预编译正则表达式,加快正则匹配速度
    private  Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);

    @Test
    public void test() throws Exception {
   
        XWPFDocument document = null;
        InputStream inputStream=null;
        try {
   
            File file = ResourceUtils.getFile("D://poi.docx");
            inputStream = new FileInputStream(file);
            document = new XWPFDocument(inputStream);
        } catch (Exception e) {
   
            e.printStackTrace();
        }

        Iterator<XWPFParagraph> itPara = document.getParagraphsIterator();

        //处理文字
        while (itPara.hasNext()) {
   
            XWPFParagraph paragraph = itPara.next();
            String paraText = paragraph.getText();
            //如果没有匹配到指定格式的关键词占位符(如${title}格式的)则不进行后续处理
            if (!pattern.matcher(paraText).find()) {
   
                continue;
            }
            //提取出文档模板占位符中的章节标题
            String keyInParaText = paraText.split("\\$\\{")[1].split("\\}")[0];
            //如果占位符是大标题
            if ("title".equalsIgnoreCase(keyInParaText)) {
   
                insertTitle(paragraph);
                continue;
            }
            //如果占位符代表文本总描述
            if ("totalDesc".equalsIgnoreCase(keyInParaText)) {
   
                insertText(paragraph);
                continue;
            }
            //如果占位符代表章节标题
            if (keyInParaText.contains("section") && keyInParaText.contains("Title")) {
   
            	//获取章节类名
                String name = keyInParaText.substring(0, 8);
                //获取章节类的路径
                String classPath = config.getSection().get(name);
                //通过类路径获取类对象
                BaseSection base = (BaseSection) Class.forName(classPath).newInstance();
                base.replaceSectionTitle(document, paragraph);
                continue;
            }
            //如果占位符代表章节文本描述
            if (keyInParaText.contains("body")) {
   
                String name = keyInParaText.substring(0, 8);
                BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
                base.replaceBody(paragraph);
                continue;
            }
            //如果占位符代表表名
            if (keyInParaText.contains("tableName")) {
   
                String name = keyInParaText.substring(0, 8);
                BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
                base.replaceTableName(paragraph);
                continue;
            }
            //如果占位符代表表
            if (keyInParaText.endsWith("table")) {
   
                String name = keyInParaText.substring(0, 8);
                BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
                base.insertTable(document, paragraph);
                continue;
            }
            //如果占位符代表统计图
            if (keyInParaText.endsWith("chart")) {
   
                String name = keyInParaText.substring(0, 8);
                paragraph.removeRun(0);
                BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
                base.replaceChart(document, keyInParaText);
                continue;
            }
            //如果占位符代表图名
            if (keyInParaText.contains("chartName")) {
   
                String name = keyInParaText.substring(0, 8);
                BaseSection base = (BaseSection) Class.forName(config.getSection().get(name)).newInstance();
                base.replaceChartName(paragraph);
                continue;
            }
        }

        //再遍历一次文档,把没有替换的占位符段落删除
        List<IBodyElement> elements = document.getBodyElements();
        int indexTable = 0;
        for (int k = 0; k < elements.size(); k++) {
   
            IBodyElement bodyElement = elements.get(k);
            //所有段落,如果有${}格式的段落便删除该段落
            if (bodyElement.getElementType().equals(BodyElementType.PARAGRAPH)) {
   
                XWPFParagraph p = (XWPFParagraph) bodyElement;
                String paraText = p.getText();
                boolean flag = false;
                if (pattern.matcher(paraText).find()) {
   
                    flag = document.removeBodyElement(k);
                    if (flag) {
   
                        k--;
                    }
                }
            }
            //如果是表格,那么给表格的前一个段落(即表名加上编号,如表1)
            if (bodyElement.getElementType().equals(BodyElementType.TABLE)) {
   
                indexTable++;
                XWPFParagraph tableTitleParagraph = (XWPFParagraph) elements.get(k - 1);
                StringBuilder tableTitleText = new StringBuilder(tableTitleParagraph.getParagraphText());
                tableTitleText.insert(0, "表" + indexTable + " ");
                poiUtils.setTableOrChartTitle(tableTitleParagraph, tableTitleText.toString());
            }
        }

        //给章节与小节添加序号
        poiUtils.init(document);

        //导出word文档
        FileOutputStream docxFos = new FileOutputStream("D://test1.docx");
        document.write(docxFos);
        docxFos.flush();
        docxFos.close();
        inputStream.close();
    }

    //插入大标题
    public void insertTitle(XWPFParagraph paragraph) {
   
        String title = "步步升超市报告";
        List<XWPFRun> runs = paragraph.getRuns();
        int runSize = runs.size();
        /**Paragrap中每删除一个run,其所有的run对象就会动态变化,即不能同时遍历和删除*/
        int haveRemoved = 0;
        for (int runIndex = 0; runIndex < runSize; runIndex++) {
   
            paragraph.removeRun(runIndex - haveRemoved);
            haveRemoved++;
        }
        /**3.插入新的Run即将新的文本插入段落*/
        XWPFRun createRun = paragraph.insertNewRun(0);
        createRun.setText(title);
        XWPFRun separtor = paragraph.insertNewRun(1);
        /**两段之间添加换行*/
        separtor.setText("\r");
        //设置字体大小
        createRun.setFontSize(22);
        //是否加粗
        createRun.setBold(true);
        //设置字体
        createRun.setFontFamily("宋体");
        //设置居中
        paragraph.setAlignment(ParagraphAlignment.CENTER);
    }

    //插入文本描述
    private void insertText(XWPFParagraph paragraph) {
   
        String text = "步步升超市作为零售业的典型代表,它已经在全国迅速发展起来。在2018年上半年取得了不菲的成绩," +
                "创造销售额230亿,本着方便于民,服务于民的宗旨,我们会继续努力。以下是详细信息报告:"
        poiUtils.setTextPro(paragraph, text);
    }
}

2.2 章节基类
因为要实现动态生成,所以要用到反射和多态。

import org.apache.poi.ooxml.POIXMLDocumentPart;
import org.apache.poi.xwpf.usermodel.*;
import org.apache.xmlbeans.XmlCursor;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTChart;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTTitle;
import org.openxmlformats.schemas.drawingml.x2006.chart.CTTx;
import org.openxmlformats.
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值