【Springboot】-创建Word

生成word

使用freemarker生成word

准备模板

1、创建模板文件

首先先建立一个word文件,输入模板内容freemaker的内容,下面是本次演示的word文件。
img

然后将word文件另存为 .xml 文件,然后再把文件后缀改成.ftl 。将项目的resource目录下建立一个templates目录(非必须步骤)将 模板文件放到templates目录下
img

img
打开模板文件按 Ctrl + Shift + L 将模板内容格式化。

2、处理模板
2.1 处理普通文本

处理文本比较简单,将需要替换文本中直接用占位符 ${} 替换即可。

这里发现一个问题因为之前在word格式时我就已经替换了变量,但是在ftl变量却被 拆分成多段了(见图1)。但是这样是freemaker是无法成功替换变量的,所以需要手动处理成到一个段里(如图2),关于这点实在太无语了,因为没有找到比较好的处理办法,只能手工处理,在实际的开发工作中曾经花了几个小时来做这件事情。
图1:
img

图2
img

2.2 处理表格

如果模板里需要用变量填充表格,建议模板里的表格像word文件一样建一个两行的表格。在模板中<<w:tbl>> 表示一个表格 、<w: tr> 表示一行、<w: tc> 表示一列。因为FreeMarker 是利用列表一行一行循环填充的,所以我们可以根据关键字找到<<w:tbl>>标签,因为第一个 <w: tr>是表头注意不要改到了,找到第二个<w: tr>在前后分别加上如下语句即可,后面的表格里后面的行<w: tr>需要删掉,建议模板里的表格像word文件一样建一个两行的表格即可这样就不用删了:

<#list itemList as item>
</#list>

替换后的模板如下:
img

2.3 处理图片

如果模板里需要用变量填充图片,建议先在word文件里插入一张图片,这样在模板文件里找到<pkg:binaryData>标签直接里面把里面的图片base64字符替换成变量即可,word里可以通过植入base64字符来展示图片:

替换前:
img

替换后:

<pkg:binaryData>${image1}</pkg:binaryData>

到此模板已经调整完成,接下来就可以开始写代码了。

项目代码

1、引入依赖

在项目的pom文件里引入如下依赖

	<dependency>
          <groupId>org.freemarker</groupId>
          <artifactId>freemarker</artifactId>
          <version>2.3.31</version>
      </dependency>
2、生成代码

将图片转成Base64字符串的公共方法:

public static String getImageBase64Str(String imgFile) {
        try( InputStream in = new FileInputStream(imgFile)) {
            byte[] data = new byte[in.available()];
            in.read(data);
            BASE64Encoder encoder = new BASE64Encoder();
            return encoder.encode(data);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

根据模板文件生成word,主要生成的word的文件后缀必须是doc不能是docx,不然生成的文件无法打开。

public static void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile){
        String path = templatePath.substring(0,templatePath.lastIndexOf("/"));
        String templateName = templatePath.substring(templatePath.lastIndexOf("/") + 1);
        try (FileOutputStream out = new FileOutputStream(targetFile);
             Writer writer = new BufferedWriter(new OutputStreamWriter(out, "utf-8"))){
            Configuration configuration = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            configuration.setDefaultEncoding("utf-8");
            configuration.setClassForTemplateLoading(FreeMakerTest.class, path);
            //除了ClassForTemplateLoading外,另一种模板加载方式DirectoryForTemplateLoading,也可用
            //ClassPathResource resource = new ClassPathResource(path);
            //configuration.setDirectoryForTemplateLoading(resource.getFile());
            //加载模板
            Template template = configuration.getTemplate(templateName);
            //渲染模板
            template.process(dataMap, writer);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (TemplateException e) {
            throw new RuntimeException(e);
        }
    }

验证生成word

新建的列表数据实体类:

public class Arrears{
    private String name;
    private Integer num;

    private String endDay;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getNum() {
        return num;
    }

    public void setNum(Integer num) {
        this.num = num;
    }

    public String getEndDay() {
        return endDay;
    }

    public void setEndDay(String endDay) {
        this.endDay = endDay;
    }
}

准备模板填充数据

private static Map<String, Object> prepareParam(){
        LocalDate currentDate = LocalDate.now();
        LocalDate endDate = currentDate.plusYears(1L);
        List<Arrears> arrearsList = new ArrayList<>();
        arrearsList.add(new Arrears(){{setName("一顿老魏");setNum(1);setEndDay("三月内");}});
        arrearsList.add(new Arrears(){{setName("贵州大黄牛");setNum(1);setEndDay("一年内");}});
        arrearsList.add(new Arrears(){{setName("v我50");setNum(1);setEndDay("一月内");}});

        //填充所需要的数据
        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("debtor", "陈有楚");
        dataMap.put("nowYear", String.valueOf(currentDate.getYear()));
        dataMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
        dataMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
        dataMap.put("arrears", "一顿老魏、贵州大黄牛、v我50");
        dataMap.put("endYear", String.valueOf(endDate.getYear()));
        dataMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
        dataMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
        dataMap.put("arrearsList", arrearsList);
        dataMap.put("image1", getImageBase64Str("D:\\picture\\其他\\24-05-23-142418.png"));
        dataMap.put("creditor", "知北游");
        return dataMap;
    }

测试代码:

public static void main(String[] args) throws IOException {
        //准备参数
        Map<String, Object> dataMap = prepareParam();
        crateWord(dataMap,"/templates/qiantiao.ftl","D:\\test\\qiantiao.doc");
    }

测试结果:
img

使用poi生成word

准备模板

1、创建模板文件

创建一个word文件,输入如下图所示的内容:
img

代码实践

1、引入依赖
     <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi</artifactId>
          <version>5.2.0</version>
      </dependency>
      <dependency>
          <groupId>org.apache.poi</groupId>
          <artifactId>poi-ooxml</artifactId>
          <version>5.2.0</version>
      </dependency>
2、自定义XWPFDocument
import org.apache.poi.openxml4j.opc.OPCPackage;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlToken;
import org.openxmlformats.schemas.drawingml.x2006.main.CTNonVisualDrawingProps;
import org.openxmlformats.schemas.drawingml.x2006.main.CTPositiveSize2D;
import org.openxmlformats.schemas.drawingml.x2006.wordprocessingDrawing.CTInline;

import java.io.IOException;
import java.io.InputStream;

public class CustomXWPFDocument extends XWPFDocument {
    public CustomXWPFDocument(InputStream in) throws IOException {
        super(in);
    }

    public CustomXWPFDocument() {
        super();
    }

    public CustomXWPFDocument(OPCPackage pkg) throws IOException {
        super(pkg);
    }

    /**
     * @param id
     * @param width 宽
     * @param height 高
     * @param paragraph 段落
     */
    public void createPicture(int id, int width, int height,
                              XWPFParagraph paragraph) {
        final int EMU = 9525;
        width *= EMU;
        height *= EMU;
        String blipId = super.getRelationId(super.getAllPictures().get(id));
        CTInline inline = paragraph.createRun().getCTR().addNewDrawing()
                .addNewInline();
        String picXml = ""
                + "<a:graphic xmlns:a=\"http://schemas.openxmlformats.org/drawingml/2006/main\">"
                + "   <a:graphicData uri=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
                + "      <pic:pic xmlns:pic=\"http://schemas.openxmlformats.org/drawingml/2006/picture\">"
                + "         <pic:nvPicPr>" + "            <pic:cNvPr id=\""
                + id
                + "\" name=\"Generated\"/>"
                + "            <pic:cNvPicPr/>"
                + "         </pic:nvPicPr>"
                + "         <pic:blipFill>"
                + "            <a:blip r:embed=\""
                + blipId
                + "\" xmlns:r=\"http://schemas.openxmlformats.org/officeDocument/2006/relationships\"/>"
                + "            <a:stretch>"
                + "               <a:fillRect/>"
                + "            </a:stretch>"
                + "         </pic:blipFill>"
                + "         <pic:spPr>"
                + "            <a:xfrm>"
                + "               <a:off x=\"0\" y=\"0\"/>"
                + "               <a:ext cx=\""
                + width
                + "\" cy=\""
                + height
                + "\"/>"
                + "            </a:xfrm>"
                + "            <a:prstGeom prst=\"rect\">"
                + "               <a:avLst/>"
                + "            </a:prstGeom>"
                + "         </pic:spPr>"
                + "      </pic:pic>"
                + "   </a:graphicData>" + "</a:graphic>";

        inline.addNewGraphic().addNewGraphicData();
        XmlToken xmlToken = null;
        try {
            xmlToken = XmlToken.Factory.parse(picXml);
        } catch (XmlException xe) {
            xe.printStackTrace();
        }
        inline.set(xmlToken);

        inline.setDistT(0);
        inline.setDistB(0);
        inline.setDistL(0);
        inline.setDistR(0);

        CTPositiveSize2D extent = inline.addNewExtent();
        extent.setCx(width);
        extent.setCy(height);

        CTNonVisualDrawingProps docPr = inline.addNewDocPr();
        docPr.setId(id);
        docPr.setName("图片名称");
        docPr.setDescr("描述信息");
    }
}

3、公用的方法和变量
	private final String REGEX = "\\$\\{(.+?)\\}";

    private CustomXWPFDocument document;

    public PoICreateWordFactory(String templatePath) throws IOException {
        loadTemplate(templatePath);
    }

    /**
     * 加载模板
     *
     * @param templatePath 模板路径
     * @return 包含返回true, 不包含返回false
     */
    private void loadTemplate(String templatePath) throws IOException {
        try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
            //转成word
            this.document = new CustomXWPFDocument(in);
        }
    }

    /**
     * 生成word
     *
     * @param targetFile word生成路径
     * @return 包含返回true, 不包含返回false
     */
    public void createWordFile(String targetFile) throws IOException {
        try (FileOutputStream out = new FileOutputStream(targetFile)){
            document.write(out);
        }
    }

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

4、工具类引用的包名
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

5、段落文本替换
	/**
     * 替换段落文本
     *
     * @param textMap(数据源)
     */
    public void replaceText(Map<String, Object> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    Matcher matcher = pattern.matcher(run.toString());
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(textMap.containsKey(key)){
                            run.setText(String.valueOf(textMap.get(key)), 0);
                        }
                    }
                }
            }
        }
    }

6、图片替换
	/**
     * 替换图片
     *
     * @param imageMap(数据源)
     */
    public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    String runText = run.toString();
                    Matcher matcher = pattern.matcher(runText);
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(imageMap.containsKey(key)){
                            //清空原有内容
                            run.setText("", 0);
                            //设置图片
                            document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
                            //创建一个word图片,并插入到文档中-->像素可改
                            document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
                            break;
                        }
                    }
                }
            }
        }
    }

7、表格替换
	/**
     * 替换表格内容
     *
     * @param index(表格索引:第几个表格)
     * @param tableList(数据源)
     * @Return void
     * @Exception
     */
    public void replaceTable(int index, List<List<String>> tableList) {
        XWPFTable table = document.getTables().get(index);
        //创建行,根据需要插入的数据添加新行,不处理表头
        for (int i = 1; i <= tableList.size(); i++) {
            table.createRow();
        }
        //遍历表格插入数据
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 1; i < tableList.size()+1; i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            List<String> rowData = tableList.get(i - 1);
            for (int j = 0; j < rowData.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                String text = rowData.get(j);
                cell.setText(text);
                //表格样式一致-->没有此段表格会默认左对齐
                //有此段会使表格格式一致
                CTTc cttc = cell.getCTTc();
                CTTcPr ctPr = cttc.addNewTcPr();
                ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
            }
        }
    }

8、完整的工具类代码
import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
import org.apache.poi.xwpf.usermodel.*;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STJc;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.STVerticalJc;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class PoICreateWordFactory {
    

    /**
     * 生成ord
     *
     */
    public  void crateWord(Map<String, Object> dataMap, String templatePath, String targetFile) throws IOException, InvalidFormatException {
        //加载模板文件
        loadTemplate(templatePath);

        //将dataMap拆分成 textMap、imageMap、tableMap TODO 烂尾中

        //段落替换变量
        Map<String, Object> textMap = new HashMap<>();
        //替换模板数据
        replaceText(textMap);

        //图片替换变量
        Map<String, byte[]> imageMap = new HashMap<>();
        replaceImage(imageMap);

        //写入表格
        List<List<String>> arrearsList = new ArrayList<>();
        replaceTable(0, arrearsList);

        //生成新的word
        createWordFile(targetFile);
    }

    private final String REGEX = "\\$\\{(.+?)\\}";

    private CustomXWPFDocument document;

    public PoICreateWordFactory() {}

    public PoICreateWordFactory(String templatePath) throws IOException {
        loadTemplate(templatePath);
    }

    /**
     * 加载模板
     *
     * @param templatePath 模板路径
     * @return 包含返回true, 不包含返回false
     */
        public void loadTemplate(String templatePath) throws IOException {
        try (InputStream in = Files.newInputStream(Paths.get(templatePath))) {
            //转成word
            this.document = new CustomXWPFDocument(in);
        }
    }

    /**
     * 生成word
     *
     * @param targetFile word生成路径
     * @return 包含返回true, 不包含返回false
     */
    public void createWordFile(String targetFile) throws IOException {
        try (FileOutputStream out = new FileOutputStream(targetFile)){
            document.write(out);
        }
    }

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


    /**
     * 替换段落文本
     *
     * @param textMap(数据源)
     */
    public void replaceText(Map<String, Object> textMap) {
        //获取段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    Matcher matcher = pattern.matcher(run.toString());
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(textMap.containsKey(key)){
                            run.setText(String.valueOf(textMap.get(key)), 0);
                        }
                    }
                }
            }
        }
    }

    /**
     * 替换图片
     *
     * @param imageMap(数据源)
     */
    public void replaceImage(Map<String, byte[]> imageMap) throws org.apache.poi.openxml4j.exceptions.InvalidFormatException {
        //段落集合
        List<XWPFParagraph> paragraphs = document.getParagraphs();
        Set<Map.Entry<String, byte[]>> imageSets = imageMap.entrySet();
        for (XWPFParagraph paragraph : paragraphs) {
            //获取到段落中的所有文本内容
            String text = paragraph.getText();
            //判断此段落中是否有需要进行替换的文本
            if (checkText(text)) {
                List<XWPFRun> runs = paragraph.getRuns();
                for (XWPFRun run : runs) {
                    //替换模板原来位置
                    Pattern pattern = Pattern.compile(REGEX);
                    String runText = run.toString();
                    Matcher matcher = pattern.matcher(runText);
                    if (matcher.find()) {
                        String key = matcher.group(1);
                        if(imageMap.containsKey(key)){
                            //清空原有内容
                            run.setText("", 0);
                            //设置图片
                            document.addPictureData(imageMap.get(key), XWPFDocument.PICTURE_TYPE_PNG);
                            //创建一个word图片,并插入到文档中-->像素可改
                            document.createPicture(document.getAllPictures().size() - 1, 240, 240,paragraph);
                            break;
                        }
                    }
                }
            }
        }
    }

    /**
     * 替换表格内容
     *
     * @param index(表格索引:第几个表格)
     * @param tableList(数据源)
     * @Return void
     * @Exception
     */
    public void replaceTable(int index, List<List<String>> tableList) {
        XWPFTable table = document.getTables().get(index);
        //创建行,根据需要插入的数据添加新行,不处理表头
        for (int i = 1; i <= tableList.size(); i++) {
            table.createRow();
        }
        //遍历表格插入数据
        List<XWPFTableRow> rows = table.getRows();
        for (int i = 1; i < tableList.size()+1; i++) {
            XWPFTableRow newRow = table.getRow(i);
            List<XWPFTableCell> cells = newRow.getTableCells();
            List<String> rowData = tableList.get(i - 1);
            for (int j = 0; j < rowData.size(); j++) {
                XWPFTableCell cell = cells.get(j);
                String text = rowData.get(j);
                cell.setText(text);
                //表格样式一致-->没有此段表格会默认左对齐
                //有此段会使表格格式一致
                CTTc cttc = cell.getCTTc();
                CTTcPr ctPr = cttc.addNewTcPr();
                ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
                cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
            }
        }
    }
}

验证模板生成

1、测试代码
public static void main(String[] args) throws IOException, InvalidFormatException {
        String templatePath = "D:\\文章\\word生成\\poi\\qiantiao.docx";
        String targetFile = "D:\\test\\qiantiao.docx";
        //初始化,并加载模板文件
        PoICreateWordFactory poICreateWordFactory = new PoICreateWordFactory(templatePath);

        //段落替换变量
        LocalDate currentDate = LocalDate.now();
        LocalDate endDate = currentDate.plusYears(1L);
        Map<String, Object> textMap = new HashMap<>();
        textMap.put("debtor", "陈有楚");
        textMap.put("nowYear", String.valueOf(currentDate.getYear()));
        textMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
        textMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
        textMap.put("arrears", "一顿老魏、贵州大黄牛、v我50");
        textMap.put("endYear", String.valueOf(endDate.getYear()));
        textMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
        textMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
        textMap.put("creditor", "知北游");
        //替换模板数据
        poICreateWordFactory.replaceText(textMap);

        //图片替换变量
        FileInputStream imageInput = new FileInputStream("D:\\picture\\其他\\24-05-23-142418.png");
        byte[] bytes = new byte[imageInput.available()];
        imageInput.read(bytes);
        imageInput.close();
        Map<String, byte[]> imageMap = new HashMap<>();
        imageMap.put("image1", bytes);
        poICreateWordFactory.replaceImage(imageMap);

        //写入表格
        List<List<String>> arrearsList = new ArrayList<>();
        arrearsList.add(Arrays.asList("一顿老魏", "1", "三月内"));
        arrearsList.add(Arrays.asList("贵州大黄牛", "1", "一年内"));
        arrearsList.add(Arrays.asList("v我50", "1", "一月内"));
        //获取表格位置 0代表第一个表格,写死第一个,模板里也只有一个模板
        poICreateWordFactory.replaceTable(0, arrearsList);

        //生成新的word
        poICreateWordFactory.createWordFile(targetFile);

    }

2、生成效果

img

总结

其实从测试代码里就可以发现这其实只是一个半成品代码,文本替换、图片替换、表格替换甚至需要分别传递不同的数据map。本来是打算合并成一个dataMap,然后根据参数类来区分是文本、图片、表格的。然后拆分成多个数据map。但是在写这些代码时发现了也是基于poi开发的开源项目poi-tl。功能很全,我想实现的功能他都有,顿时我写的上面这些代码就失去了意义,然后就烂尾了。。。后面有时间介绍一下poi-tl这个开源项目使用方式吧,经过试验这个确实功能完善,非常推荐。

本文转自 https://www.cnblogs.com/fhey/p/17542195.html,如有侵权,请联系删除。

使用poi-tl生成word

前言

1、什么是poi-tl

poi-tl是一个基于Apache POI的Word模板引擎,也是一个免费开源的Java类库。同类型的FreeMarker或Velocity基于文本模板和数据生成新的html页面或配置文件。而poi tl是一个基于Word模板和数据生成新文档的Word模板引擎。

Word模板具有丰富的样式。Poi-tl将在生成的文档中完美地保留模板中的样式。也可以设置标记的样式。标记的样式将应用于替换的文本,因此您可以专注于模板设计。

poi-tl是一个“无逻辑”模板引擎。没有复杂的控制结构和变量分配,只有标签,有些标签可以用文本、图片、表格等代替,有些标签会隐藏某些文档内容,而另一些标签会循环一系列文档内容。

像变量赋值或条件语句这样的“强大”构造可以很容易地在模板系统中专门修改应用程序的外观。。。然而,以分离为代价,将模板本身变成应用程序逻辑的一部分。

poi-tl支持自定义函数(插件),函数可以在Word模板的任何地方执行,在文档的任何地方做任何事情都是poi-tl的目标。

2、官方信息
2.1 源码仓库

GitHub - Sayi/poi-tl: Generate awesome word(docx) with template

2.2 中文文档

Poi-tl Documentation (deepoove.com)

2.3 开源协议

Apache License 2.0

3、poi-tl的优势
3.1 poi-tl和其他模板引擎的对比

下面表格是官方文档中提供的与其他模板引擎的对比

方案 移植性 功能性 易用性
Poi-tl Java跨平台 Word模板引擎,基于Apache POI,提供更友好的API 低代码,准备文档模板和数据即可
Apache POI Java跨平台 Apache项目,封装了常见的文档操作,也可以操作底层XML结构 文档不全,这里有一个教程:Apache POI Word快速入门
Freemarker XML跨平台 仅支持文本,很大的局限性 不推荐,XML结构的代码几乎无法维护
OpenOffice 部署OpenOffice,移植性较差 - 需要了解OpenOffice的API
HTML浏览器导出 依赖浏览器的实现,移植性较差 HTML不能很好的兼容Word的格式,样式糟糕 -
Jacob、winlib Windows平台 - 复杂,完全不推荐使用
3.2 poi-tl Word模板引擎支持的功能
Word模板引擎功能 描述
文本 将标签渲染为文本
图片 将标签渲染为图片
表格 将标签渲染为表格
列表 将标签渲染为列表
图表 条形图(3D条形图)、柱形图(3D柱形图)、面积图(3D面积图)、折线图(3D折线图)、雷达图、饼图(3D饼图)、散点图等图表渲染
If Condition判断 根据条件隐藏或者显示某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Foreach Loop循环 根据集合循环某些文档内容(包括文本、段落、图片、表格、列表、图表等)
Loop表格行 循环复制渲染表格的某一行
Loop表格列 循环复制渲染表格的某一列
Loop有序列表 支持有序列表的循环,同时支持多级列表
Highlight代码高亮 word中代码块高亮展示,支持26种语言和上百种着色样式
Markdown 将Markdown渲染为word文档
Word批注 完整的批注功能,创建批注、修改批注等
Word附件 Word中插入附件
SDT内容控件 内容控件内标签支持
Textbox文本框 文本框内标签支持
图片替换 将原有图片替换成另一张图片
书签、锚点、超链接 支持设置书签,文档内锚点和超链接功能
Expression Language 完全支持SpringEL表达式,可以扩展更多的表达式:OGNL, MVEL…
样式 模板即样式,同时代码也可以设置样式
模板嵌套 模板包含子模板,子模板再包含子模板
合并 Word合并Merge,也可以在指定位置进行合并
用户自定义函数(插件) 插件化设计,在文档任何位置执行函数

基本的使用配置

1、引入依赖
1.1 Maven
<dependency>
  <groupId>com.deepoove</groupId>
  <artifactId>poi-tl</artifactId>
  <version>1.12.1</version>
</dependency>
1.2 Gradl
implementation 'com.deepoove:poi-tl:1.12.1'
2、配置
2.1 新建配置
ConfigureBuilder builder = Configure.builder();
2.2 标签前后缀替换

poi-tl所有的标签都是以{{开头,以}}结尾,这是为了致敬Google CTemplate。标签可以出现在任何位置,包括页眉,页脚,表格内部,文本框等,表格布局可以设计出很多优秀专业的文档,推荐使用表格布局。

当然如果你更偏爱freemarker ${} 的方式,也可以添加如下配置修改标签的前后缀配置:

builder.buildGramer("${", "}");
2.3 加载模板
XWPFTemplate template = XWPFTemplate.compile("template.docx", builder.buid());

poi-tl加载使用XWPFTemplate.compile方法来加载模板,支持模板以绝对路径(String),File、InputStream、XWPFDocument四种格式传入。

2.4 填充数据

poi-tl数据类似于哈希或者字典,可以是Map结构(key是标签名称):

Map<String, Object> data = new HashMap<>();
data.put("name", "Sayi");
data.put("start_time", "2019-08-04");
template.render(dataMap);
2.5 输出文件

poi-tl以流的方式进行输出:

template.write(OutputStream stream);

可以写到任意输出流中,比如文件流:

template.write(new FileOutputStream("output.docx"));

如网络流:

response.setContentType("application/octet-stream");
response.setHeader("Content-disposition","attachment;filename=\""+"out_template.docx"+"\"");

// HttpServletResponse response
OutputStream out = response.getOutputStream();
BufferedOutputStream bos = new BufferedOutputStream(out);
template.write(bos);
bos.flush();
out.flush();
PoitlIOUtils.closeQuietlyMulti(template, bos, out);

各类模板标签替换和填充

1 文本
1.1 文本的标签如下
{{var}}
1.2 支持数据类型
  • String :文本
  • TextRenderData :有样式的文本
  • HyperlinkTextRenderData :超链接和锚点文本
  • Object :调用 toString() 方法转化为文本
1.3 文本数据填充方式如下

代码示例

put("name", "Sayi");
put("author", new TextRenderData("000000", "Sayi"));
put("link", new HyperlinkTextRenderData("website", "http://deepoove.com"));
put("anchor", new HyperlinkTextRenderData("anchortxt", "anchor:appendix1"));

除了new操作符,还提供了更加优雅的工厂 Texts 和链式调用的方式轻松构建文本模型。
链式代码示例

put("author", Texts.of("Sayi").color("000000").create());
put("link", Texts.of("website").link("http://deepoove.com").create());
put("anchor", Texts.of("anchortxt").anchor("appendix1").create());
2 图片
2.1 图片的标签如下:
图片标签以@开始:{{@var}}
2.2 支持数据类型
  • String :图片url或者本地路径,默认使用图片自身尺寸
  • `PictureRenderData
  • ByteArrayPictureRenderData
  • FilePictureRenderData
  • UrlPictureRenderData
2.3 图片数据填充方式如下
// 指定图片路径
put("image", "logo.png");
// svg图片
put("svg", "https://img.shields.io/badge/jdk-1.6%2B-orange.svg");

// 设置图片宽高
put("image1", Pictures.ofLocal("logo.png").size(120, 120).create());

// 图片流
put("streamImg", Pictures.ofStream(new FileInputStream("logo.jpeg"), PictureType.JPEG)
  .size(100, 120).create());

// 网络图片(注意网络耗时对系统可能的性能影响)
put("urlImg", Pictures.ofUrl("http://deepoove.com/images/icecream.png")
  .size(100, 100).create());

// java图片
put("buffered", Pictures.ofBufferedImage(bufferImage, PictureType.PNG)
  .size(100, 100).create());
3 表格
3.1 表格的标签如下:
表格标签以#开始:{{#var}}
3.2 支持数据类型
  • TableRenderData
3.3 表格数据填充方式如下

1. 基础表格示例

// 一个2行2列的表格
put("table0", Tables.of(new String[][] {
                new String[] { "00", "01" },
                new String[] { "10", "11" }
            }).border(BorderStyle.DEFAULT).create());

2. 表格样式示例

// 第0行居中且背景为蓝色的表格
RowRenderData row0 = Rows.of("姓名", "学历").textColor("FFFFFF")
      .bgColor("4472C4").center().create();
RowRenderData row1 = Rows.create("李四", "博士");
put("table1", Tables.create(row0, row1));

3. 表格合并示例

// 合并第1行所有单元格的表格
RowRenderData row0 = Rows.of("列0", "列1", "列2").center().bgColor("4472C4").create();
RowRenderData row1 = Rows.create("没有数据", null, null);
MergeCellRule rule = MergeCellRule.builder().map(Grid.of(1, 0), Grid.of(1, 2)).build();
put("table3", Tables.of(row0, row1).mergeRule(rule).create());

4、列表
4.1 列表的标签如下:
列表标签以*开始:{{*var}}
4.2 支持数据类型
  • List<String>
  • NumberingRenderData
4.3 列表数据填充方式如下
put("list", Numberings.create("Plug-in grammar",
                    "Supports word text, pictures, table...",
                    "Not just templates"));

验证

1、准备模板

首先我们建立一个word文件,在word文件里填充一下内容。
img

2、准备测试代码
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.config.ConfigureBuilder;
import com.deepoove.poi.data.*;
import dto.Qiankuan;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.util.*;

public class PoitlTest {

    public static void main(String[] args) throws IOException {
        ConfigureBuilder builder = Configure.builder();
        //获取模板的文件流
        FileInputStream fileInputStream = new FileInputStream("D:\\文章\\word生成\\poi-tl\\qiantiao.docx");

        HashMap<String, Object> dataMap = new HashMap<>();
        //添加文本
        LocalDate currentDate = LocalDate.now();
        LocalDate endDate = currentDate.plusYears(1L);
        dataMap.put("debtor", "陈有楚");
        dataMap.put("nowYear", String.valueOf(currentDate.getYear()));
        dataMap.put("nowMonth", String.valueOf(currentDate.getMonthValue()));
        dataMap.put("nowDay", String.valueOf(currentDate.getDayOfMonth()));
        //验证换行的情况
        dataMap.put("arrears", "\n一顿老魏,\n贵州大黄牛,\nv我50");
        dataMap.put("endYear", String.valueOf(endDate.getYear()));
        dataMap.put("endMonth", String.valueOf(endDate.getMonthValue()));
        dataMap.put("endDay", String.valueOf(endDate.getDayOfMonth()));
        //添加列表
        List<String> list = Arrays.asList("阿大", "阿二", "阿三");
        Numberings.NumberingBuilder numberingBuilder = Numberings.of(NumberingFormat.DECIMAL);
        for (String s : list) {
            numberingBuilder.addItem(s);
        }
        dataMap.put("witness", numberingBuilder.create());
        //添加图片,考虑到实际生产环境图片大都都从文件服务获取,所以这里用图片流做例子
        PictureRenderData pictureRenderData = Pictures.ofStream(Files.newInputStream(Paths.get("D:\\picture\\其他\\24-05-23-142418.png")), PictureType.JPEG)
                .size(300, 220).create();
        dataMap.put("image1", pictureRenderData);
        List<Qiankuan> qiankuanList = getQiankuanList();
        //添加表格
        //填充表头,表格的第一行
        RowRenderData row0 = Rows.of("拖欠物品", "拖欠次数", "偿还期限").center().bgColor("4472C4").create();
        Tables.TableBuilder tableBuilder = Tables.of(row0);
        //填充表格内容
        for (Qiankuan qiankuan : qiankuanList) {
            RowRenderData row = Rows.create(qiankuan.getName(), String.valueOf(qiankuan.getCount()), qiankuan.getQixian());
            tableBuilder.addRow(row);
        }
        //MergeCellRule rule = MergeCellRule.builder().map(MergeCellRule.Grid.of(1, 0), MergeCellRule.Grid.of(1, 2)).build();
        //tableBuilder.mergeRule(rule);
        dataMap.put("table1", tableBuilder.create());

        ChartMultiSeriesRenderData chart = Charts
                .ofMultiSeries("ChartTitle", new String[] { "中文", "English" })
                .addSeries("countries", new Double[] { 15.0, 6.0 })
                .addSeries("speakers", new Double[] { 223.0, 119.0 })
                .create();

        dataMap.put("barChart", chart);
        XWPFTemplate template = XWPFTemplate.compile(fileInputStream, builder.build())
                .render(dataMap);
        template.writeAndClose(Files.newOutputStream(Paths.get("D:\\test\\qiantiao-poitl.docx")));
        System.out.println("success");
    }

    static List<Qiankuan> getQiankuanList() {
        List<Qiankuan> list = new ArrayList<>();
        Qiankuan q1 = new Qiankuan();
        q1.setName("一顿老魏");
        q1.setCount(1);
        q1.setQixian("三月内");
        list.add(q1);

        Qiankuan q2 = new Qiankuan();
        q2.setName("一顿大黄牛");
        q2.setCount(1);
        q2.setQixian("半年内");
        list.add(q2);

        Qiankuan q3 = new Qiankuan();
        q3.setName("特一特");
        q3.setCount(3);
        q3.setQixian("一周内");
        list.add(q3);

        Qiankuan q4 = new Qiankuan();
        q4.setName("v我50");
        q4.setCount(5);
        q4.setQixian("一周内");
        list.add(q4);
        return list;
    }
    
}

3、生成效果

img

本文转自 https://www.cnblogs.com/fhey/p/17544518.html,如有侵权,请联系删除。

posted @   明小子@  阅读(1113)  评论(0编辑  收藏  举报
(评论功能已被禁用)
相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示