java Apache Poi 操作word ,生成报告之简化版本 采用 poi-tl 组件进行word 文档的操作

前言


        在工作中有遇到需要在一个固定的word 模板中填充相关的数据,最终形成一份完成word 文档形式的报告。简单一些的可能只是对模板进行文字和数值的填充,稍微复杂一些的是模板中除了文本数值之外,还混合的有表格(可以循环遍历填充)、不规则表格无序的,只能按照格子进行填充)、图片、条形图柱状图等。

        本次主要介绍的插件是一个开源的操作word文档的组件,名为 POI-tl ,是一款基于Apache poi 打造的,官网地址为:https://deepoove.com/poi-tl/ 。


简述Apache Poi


        在以往用Apache poi 进行操作word 文档时,我们通常这样操作:

  1. 根据模板存放的路径,获取到文件的输入流,然后创建XWPFDocument 对象。
  2. 获取word 文档中的所有段落,getParagraphs()。
  3. 循环遍历所有段落,获取到每个段落中的每一句话。getRuns()。
  4. 替换runs 中的文本text

以下代码为简单实例, poi 操作word 相对比较繁琐,word模板中的占位符需要自己进行定义,我习惯使用${}的形式,也有人直接使用{}。

String path = "";
File file = new File(path);
FileOutputStream os = null;
FileInputStream is = new FileInputStream(file);
XWPFDocument doc = new XWPFDocument(is);
// 替换文本
List<XWPFParagraph> paragraphs = doc.getParagraphs();
if (paragraphs != null && paragraphs.size() > 0){
    for(XWPFParagraph paragraph:paragraphs){
        List<XWPFRun> runs=paragraph.getRuns();
        for(int i=0;i<runs.size();i++){
            String text=runs.get(i).getText(0);
            text=text.replace("${name}","创作不易,给个鼓励");
            runs.get(i).setText(text,0);
        }
    }
}
//替换表格内容
List<XWPFTable> tables = doc.getTables();
    for (int i = 0; i < tables.size(); i++){
        XWPFTable xwpfTable=tables.get(i);
        XWPFTableRow defRow=xwpfTable.getRow(3);
        setTextColorAndFont(row,"张三"),0);
        setTextColorAndFont(row,"北京",1);
        setTextColorAndFont(row,"程序员",2);
        setTextColorAndFont(row,"25",3);
        setTextColorAndFont(row,"138336287882"),4);
    }


/**
*row 行对象
*tbText 单元格要填充的内容
*cellIndex 单元格的下标
*/

public void setTextColorAndFont(XWPFTableRow row,String tbText, int cellIndex) {

        tbText = checkValueIsNull(tbText);
        XWPFTableCell cell = row.getCell(cellIndex);
        List<XWPFParagraph> paragraphs = cell.getParagraphs();
        XWPFParagraph xwpfParagraph = paragraphs.get(0);
        XWPFRun xpfr = xwpfParagraph.createRun();
        xpfr.setFontFamily("宋体");
        xpfr.setFontSize(12);
        xpfr.setText(tbText);
    }

Poi-tl 上手实操

安装使用步骤可以详见API文档,这里不做过多的赘述。https://deepoove.com/poi-tl/#_%E5%89%8D%E6%8F%90

简单的一些操作直接看在线文档就可以搞定, 我在这里主要说一下复杂一些的表格生成,以及在开发过程遇到的坑。

  1. poi-tl 中, word 模板中的占位符需要设置为{{name}} 这种形式。
  2. 如果word 模板中内容很多,那么在一个类里面要写的代码就会很多,也没有办法进行协同编码,因为都在一个类中写代码的话,每次合并代码就会很麻烦,所以我们采用讲word模板分段来实现的方式。
  3. AutoAwareZgybHandlerFactory.getTemplateHandler(param.getClass()); 通过获取TemplateHandler 的这种方式, 需要自己实现一个Handler,实现 WordContextChain<ReqeustParam>接口,泛型中的类需要和下面代码段中的请求入参实体类一致。
  4. public void generatorWord(Requestparam param){
            List<WordContextChain> contextChains = AutoAwareZgybHandlerFactory.getTemplateHandler(param.getClass());
    
            Map<String, Object> context = new HashMap<>();
            for (WordContextChain contextChain : contextChains) {
                contextChain.process(param, context); // 执行process 的时候,代码会进入自己定义的handler 中,如果有多个handerl, 执行的顺序是按照Order中的序号执行
            }
            generateZgybReport(context);
    }
    
    
    public static generateZgybReport(Map<String, Object> context) {
    		ExpResult er = new ExpResult();
    
    		String templateFilePath = "/download/expfile/ZG5C_ZGYB_TEMPLATE.docx";
    		String tempFileName = FileIeUtil.getRandomName()+".docx";
    		try (InputStream inputStream = this.getClass().getResourceAsStream(templateFilePath)) {
    			byte[] bytes = replaceText(inputStream, context);
    			String tempFilePath = FileIeUtil.getRandomName();
    			String filePath = FileIeUtil.getExportFileDirePath(tempFilePath, tempFileName);
    			filePath = filePath.replaceAll("\\\\", "/");
    			FileUtils.writeByteArrayToFile(new File(filePath), bytes);
    
    			er = readTempFileRenderTable(filePath, tempFilePath);
    		} catch (IOException e) {
    			log.error("生成税收征管质量月报失败!", e);
    		}
    		return er;
    	}
    
    public static byte[] replaceText(InputStream wordTemplateInputStream, Map<String, Object> model) {
    		LoopRowTableRenderPolicy policy = new LoopRowTableRenderPolicy();
    		TableColorPolicy colorPolicy = new TableColorPolicy();
    		Configure configure = Configure.builder().useSpringEL(false).bind("tableList1",policy)// 此处,list 对应的key 是需要做绑定操作的,详细的可以看官方文档。
    				.bind("tableList8",policy).bind("tableList2",colorPolicy)
    				.bind("tableList10",policy).build();
    		try (XWPFTemplate template = XWPFTemplate.compile(wordTemplateInputStream, configure)) {
    			template.render(model);
    			ByteArrayOutputStream out = new ByteArrayOutputStream();
    			template.write(out);
    			byte[] bytes = out.toByteArray();
    			out.close();
    			return bytes;
    		} catch (IOException e) {
    			throw new Zg5cRunTimeException("模板word生成失败", e);
    		}
    	}

 

复杂word 表格操作

假如遇到如下情况,要根据年龄, 对年龄超过25的单元格设置填充色,或者其他格式操作等。

序号

身份信息

姓名

年龄

地址

身份证

学历

1

张三

17

Xx

Xx

Xx

2

李四

19

Xx

Xx

Xx

3

王五

28

Xx

Xx

Xx

4

赵六

21

Xx

Xx

Xx

5

点赞转发

22

Xx

Xx

Xx

6

感激不尽

34

Xx

Xx

Xx

此时,我们需要借助  DynamicTableRenderPolicy 类, 此时,我们需要自己实现一个处理类,继承DynamicTableRenderPolicy,重写render 方法,对表格进行颜色的或者其他形式的渲染

/**
 * 渲染表格颜色
 */
@Slf4j
public class TableColorPolicy extends DynamicTableRenderPolicy {
    @Override
    public void render(XWPFTable xwpfTable, Object data) throws Exception {
        if (data == null) {
            return;
        }
        List<Map<String, String>> list  = (List<Map<String, String>>) data;
        xwpfTable.removeRow(3);
        String sxbm = "";
        int i = 0;
        for (Map<String, String> map : list) {
            XWPFTableRow xwpfTableRow = xwpfTable.insertNewTableRow(3 + i); //从哪行开始插入新的数据(除去表头后的第一行)

            RowRenderData renderData = null;
            TextRenderData textRenderData = new TextRenderData();
            Style style = new Style();
            style.setCharacterSpacing(0);
            style.setVertAlign("CENTER");

            textRenderData.setStyle(style);
           
                for (int j = 0; j < 9; j++) xwpfTableRow.createCell();
                renderData = Rows.of().rowAtleastHeight(0.16).textFontFamily("宋体").textFontSize(9).create()
                        .addCell(getHearderCellRenderData(map.get("SWJG_DM").getSwjgMc()))
                        .addCell(getCellRenderData(map.get("num"),"1"))
                        .addCell(getCellRenderData(map.get("name"),"1"))
                        .addCell(getCellRenderData(map.get("age"),)"-1")
                        .addCell(getCellRenderData(map.get("address"),"1"))
                        .addCell(getCellRenderData(map.get("sfzhm"),"1"))
                        .addCell(getCellRenderData(map.get("edu"),"1"));
            
            TableRenderPolicy.Helper.renderRow(xwpfTable.getRow(3+i),renderData);
            i++;
        }
        log.info("动态表格渲染,表格:"+sxbm+"--渲染完成----------------------------------");
    }

    /**
     * 设置表格的样式
     */
    private static CellRenderData getCellRenderData(String text,String color) {
        if (StringUtils.isEmpty(text)) {
            text = "";
        }
        String bgColor = Constants.COLOR_WHITE;
        if (StringUtils.isNotEmpty(color) && color.equals("1")) {
            bgColor = Constants.COLOR_BLUE;
        } else if (StringUtils.isNotEmpty(color) && color.equals("-1")) {
            bgColor = Constants.COLOR_RED;
        }
        return Cells.of().addParagraph(getParagraphRenderData(text)).bgColor(bgColor).create();
    }
    /**
     * 表头样式
     *
     * @param text
     * @return
     */
    private static CellRenderData getHearderCellRenderData(String text) {
        return Cells.of().addParagraph(getParagraphRenderData(text, true)).bgColor("DDEBF7").create();
    }

    private static ParagraphRenderData getParagraphRenderData(String text) {
        return getParagraphRenderData(text, false);
    }

    private static ParagraphRenderData getParagraphRenderData(String text, boolean title) {
        //设置表格字体大小
        Style style = Style.builder().buildFontSize(9).build();
        //设置表格行距
        ParagraphStyle build = ParagraphStyle
                .builder().withIndentLeftChars(0).withIndentRightChars(0).withAllowWordBreak(false).withSpacingAfter(0).withSpacingBefore(0).withSpacing(15).withSpacingRule(LineSpacingRule.EXACT).withIndentFirstLineChars(-2).build();
        if (title) {
            return Paragraphs.of(Texts.of(text).style(style).create()).paraStyle(build).center().create();
        }
        return Paragraphs.of(Texts.of(text).style(style).create()).paraStyle(build).right().create();
    }

}

坑坑坑坑: 在这个设置颜色的功能中,遇到了这么个情况,原本表格是有边框线的,但是通过这个功能插入数据之后,边框线死活设置不上,然后我就用原始的POI 重新读取了一次生成后的word 文档, 对指定的talbe 进行边框线的设置。

public static ExpResult readTempFileRenderTable(String filePath,String tempFilePath){
		String today = DateUtils.getYYYYMMDD(new Date());

		String wordFilename = "税收征管质量月报_"+today+".docx";
		ExpResult er = new ExpResult();
		File file = new File(filePath);
		XWPFDocument doc = null;
		FileOutputStream os = null;
		FileInputStream is = null;
		try {
			is = new FileInputStream(file);
			doc = new XWPFDocument(is);
			List<XWPFTable> tables = doc.getTables();
			int tIndex = 0;
			for (int i = 0; i < tables.size(); i++) {
				if (i == 6 || i==9 || i==13 || i==21 || i==26) {
					XWPFTable xwpfTable = tables.get(i);
					String bolderType = "single";
					CTTblBorders borders = xwpfTable.getCTTbl().getTblPr().addNewTblBorders();

					CTBorder hBorder = borders.addNewInsideH();
					hBorder.setVal(STBorder.Enum.forString(bolderType));
					hBorder.setSz(new BigInteger("1")); // 线条大小
					hBorder.setColor("000000"); // 设置颜色

					CTBorder vBorder = borders.addNewInsideV();
					vBorder.setVal(STBorder.Enum.forString(bolderType));
					vBorder.setSz(new BigInteger("1"));
					vBorder.setColor("000000");

					CTBorder lBorder = borders.addNewLeft();
					lBorder.setVal(STBorder.Enum.forString(bolderType));
					lBorder.setSz(new BigInteger("3"));
					lBorder.setColor("000000");

					CTBorder rBorder = borders.addNewRight();
					rBorder.setVal(STBorder.Enum.forString(bolderType));
					rBorder.setSz(new BigInteger("3"));
					rBorder.setColor("000000");

					CTBorder tBorder = borders.addNewTop();
					tBorder.setVal(STBorder.Enum.forString(bolderType));
					tBorder.setSz(new BigInteger("3"));
					tBorder.setColor("000000");

					CTBorder bBorder = borders.addNewBottom();
					bBorder.setVal(STBorder.Enum.forString(bolderType));
					bBorder.setSz(new BigInteger("3"));
					bBorder.setColor("000000");
				}
			}

			filePath = FileIeUtil.getExportFileDirePath(tempFilePath, wordFilename);
			filePath = filePath.replaceAll("\\\\", "/");
			os = new FileOutputStream(filePath);
			doc.write(os);


			List<ExpResult.FilePathBean> filePathBeans = new ArrayList<>();
			filePath = filePath.substring(FileIeUtil.getBASEPATH().length());
			filePathBeans.add(new ExpResult.FilePathBean(wordFilename, filePath));
			er.setFilePathList(filePathBeans);
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			try {
				os.close();
				doc.close();
				is.close();
				file.delete();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return er;
	}

以上便是近期对 POI-tl 使用的心得, 希望可以对你有所帮助。 

  • 14
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值