Docx4J word模板实战

Docx4J

背景:最近要生成word文档,刚开始考虑了POI,但是POI插入图片有bug,而且比较难用,Docx4J比POI容易一些而且功能更强大,遂打算将Docx4J用到项目中去。更多信息见这里 Build Status

使用准备

  1. 在gradle中引入依赖

            "org.docx4j:docx4j:3.3.1",
            "org.docx4j:docx4j-export-fo:3.3.1",

  1. 一个能解压的软件 如:7zip

  2. 能格式化XML文件的文本编辑器 如:Notepad++(需要装一个插件:XML Tools) 将xml的默认打开方式提前设置为用此软件打开

第一部分 简单文本替换

1. 将需要生成的word文件保存为docx格式

2. 在需要替换字符的地方写上${parameter} (注意:这里要根据你的需要设定参数,parameter只是示例)

3. 使用7zip打开docx文件,即打开压缩包(注意:不是解压,解压了就压缩不回去了)

4. 进入word文件夹,打开document.xml

5. 点击Notepad++菜单栏中的插件:XMl Tools,然后点击 pretty XML With Line Brekes格式化XMl文件,或者选择你喜欢的方式

6. 第二步写的${parameter}在xml中可能为下面的样子

<w:r>
    <w:rPr>
        <w:rFonts w:hint="eastAsia"/>
        <w:b/>
        <w:szCs w:val="21"/>
    </w:rPr>
    <w:t>${</w:t>
</w:r>
<w:r>
    <w:rPr>
        <w:rFonts w:hint="eastAsia"/>
        <w:b/>
        <w:szCs w:val="21"/>
    </w:rPr>
    <w:t>parameter</w:t>
</w:r>
<w:r>
    <w:rPr>
        <w:rFonts w:hint="eastAsia"/>
        <w:b/>
        <w:szCs w:val="21"/>
    </w:rPr>
    <w:t>}</w:t>
</w:r>

7. 将他们”撮合”到一个中去,如下:

<w:r>
    <w:rPr>
        <w:rFonts w:hint="eastAsia"/>
        <w:b/>
        <w:szCs w:val="21"/>
    </w:rPr>
    <w:t>${parameter}</w:t>
</w:r>

8. 然后 保存文件,切到7zip,这时7zip会提醒你更新压缩包的的文件,点击确定就行了

9. 然后就可以替换参数生成简单的word文档了!

    /**
     * 创建Docx的主方法
     *
     * @param templatePath        模板docx路径
     * @param parameters          参数和值
     * @param paragraphParameters 段落参数
     * @param imageParameters     书签和图片
     * @return
     */
    private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
                                                                                     HashMap<String, String> parameters,
                                                                                     HashMap<String, String> paragraphParameters,
                                                                                     HashMap<String, String> imageParameters)
            throws Exception {
        @Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();

        //第一步 替换字符参数
        if (parameters != null) {
            replaceParameters(documentPart, parameters);
        }

        ...............
        return wordMLPackage;
    }
    /**
     * 替换模板中的参数
     *
     * @param documentPart
     * @param parameters
     * @throws JAXBException
     * @throws Docx4JException
     */
    private static void replaceParameters(MainDocumentPart documentPart,
                                          HashMap<String, String> parameters)
            throws JAXBException, Docx4JException {
        documentPart.variableReplace(parameters);
    }

第二部分 段落替换(换行)

10. 替换段落:将需要替换为段落的地方按照 第一部分 处理

11. 替换段落的工具方法

    /**
     * 针对一个特定的类来搜索指定元素以及它所有的孩子,例如,获取文档中所有的表格、表格中所有的行以及其它类似的操作
     *
     * @param obj
     * @param toSearch
     * @return
     */
    private static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
        List<Object> result = new ArrayList<>();
        if (obj instanceof JAXBElement) obj = ((JAXBElement<?>) obj).getValue();

        if (obj.getClass().equals(toSearch))
            result.add(obj);
        else if (obj instanceof ContentAccessor) {
            List<?> children = ((ContentAccessor) obj).getContent();
            for (Object child : children) {
                result.addAll(getAllElementFromObject(child, toSearch));
            }
        }
        return result;
    }
    /**
     * @param content
     * @Description: 在一个段落后追加新的段落内容
     */
    public static void appendParaRContent(P p, String content) {
        List<?> texts = getAllElementFromObject(p, Text.class);
        if (texts.size() > 0) {
            Text textToReplace = (Text) texts.get(0);
            textToReplace.setValue("");
        }
        if (content != null) {
            R run = new R();
            p.getContent().add(run);
            String[] contentArr = content.split("\n");
            Text text = new Text();
            text.setSpace("preserve");
            text.setValue("    " + contentArr[0]);
            run.getContent().add(text);

            for (int i = 1, len = contentArr.length; i < len; i++) {
                Br br = new Br();
                run.getContent().add(br);// 换行
                text = new Text();
                text.setSpace("preserve");
                text.setValue("    " + contentArr[i]);
                run.getContent().add(text);
            }
        }
    }
    /**
     * 从Content中获得内容(返回Content的文本)
     *
     * @param content
     * @return
     */
    public static String getStringFromContent(List<Object> content) {
        String temp = "";
        for (Object o : content) {
            if (o.getClass() == R.class) {
                final Object text = ((JAXBElement) ((R) o).getContent().get(0)).getValue();
                if (text.getClass() == Text.class) {
                    temp += ((Text) text).getValue();
                }
            }
        }
        return temp;
    }

12. 最后就是替换段落的方法了

/**
     * 根据字符串参数替换段落
     *
     * @param documentPart
     * @param paragraphParameters
     */
    private static void replaceParagraph(MainDocumentPart documentPart,
                                         HashMap<String, String> paragraphParameters)
                                         throws JAXBException, Docx4JException {
        //获得所有的段落内容
        final List<Object> allElementFromObject = getAllElementFromObject(documentPart, P.class);

        for (Object paragraph : allElementFromObject) {
            final P para = (P) paragraph;
            if (!isNullOrEmpty(para.getContent())) {
                final List<Object> content = para.getContent();
                final String stringFromContent = getStringFromContent(content);
                final String s = paragraphParameters.get(stringFromContent);
                if (s != null) {
                    appendParaRContent(para, s);
                }
            }
        }
    }

13. 调用

    /**
     * 创建Docx的主方法
     *
     * @param templatePath        模板docx路径
     * @param parameters          参数和值
     * @param paragraphParameters 段落参数
     * @param imageParameters     书签和图片
     * @return
     */
    private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
                                                                                     HashMap<String, String> parameters,
                                                                                     HashMap<String, String> paragraphParameters,
                                                                                     HashMap<String, String> imageParameters)
            throws Exception {
        @Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();

        ......

        //第二步 替换段落
        if (paragraphParameters != null) {
            replaceParagraph(documentPart, paragraphParameters);
        }

        ......

        return wordMLPackage;
    }

第三部分 替换书签为图片

13. 准备:在需要插入图片的地方增加书签

14. 替换书签为图片的方法

    /**
     * 替换书签为图片
     *
     * @param wordMLPackage
     * @param documentPart
     * @param imageParameters
     * @throws Exception
     */
    private static void replaceBookMarkWithImage(WordprocessingMLPackage wordMLPackage,
                                                 MainDocumentPart documentPart,
                                                 Map<String, String> imageParameters)
            throws Exception {
        Document wmlDoc = documentPart.getContents();
        Body body = wmlDoc.getBody();
        // 提取正文中所有段落
        List<Object> paragraphs = body.getContent();
        // 提取书签并创建书签的游标
        RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
        new TraversalUtil(paragraphs, rt);

        // 遍历书签
        for (CTBookmark bm : rt.getStarts()) {
            String bookmarkName = bm.getName();
            String imagePath = imageParameters.get(bookmarkName);
            if (imagePath != null) {
                File imageFile = new File(imagePath);
                InputStream imageStream = new FileInputStream(imageFile);
                // 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
                byte[] bytes = IOUtils.toByteArray(imageStream);
                // 创建一个行内图片
                BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
                // 最后一个是限制图片的宽度,缩放的依据
                Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 800);
                // 获取该书签的父级段落
                P p = (P) (bm.getParent());
                ObjectFactory factory = new ObjectFactory();
                // 新建一个Run
                R run = factory.createR();
                // drawing 画布
                Drawing drawing = factory.createDrawing();
                drawing.getAnchorOrInline()
                        .add(inline);
                run.getContent()
                        .add(drawing);
                p.getContent()
                        .add(run);
            }
        }
    }

15.调用

    /**
     * 创建Docx的主方法
     *
     * @param templatePath        模板docx路径
     * @param parameters          参数和值
     * @param paragraphParameters 段落参数
     * @param imageParameters     书签和图片
     * @return
     */
    private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
                                                                                     HashMap<String, String> parameters,
                                                                                     HashMap<String, String> paragraphParameters,
                                                                                     HashMap<String, String> imageParameters)
            throws Exception {
        @Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();

        ......

        //第三步 插入图片
        if (imageParameters != null) {
            replaceBookMarkWithImage(wordMLPackage, documentPart, imageParameters);
        }
        return wordMLPackage;
    }

第四部分 附上几个工具方法

/**
     * 创建Docx的主方法
     *
     * @param templatePath        模板docx路径
     * @param parameters          参数和值
     * @param paragraphParameters 段落参数
     * @param imageParameters     书签和图片
     * @return
     */
    private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
                                                                                     HashMap<String, String> parameters,
                                                                                     HashMap<String, String> paragraphParameters,
                                                                                     HashMap<String, String> imageParameters)
            throws Exception {
        @Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
        WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
        MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();

        //第一步 替换字符参数
        if (parameters != null) {
            replaceParameters(documentPart, parameters);
        }

        //第二步 替换段落
        if (paragraphParameters != null) {
            replaceParagraph(documentPart, paragraphParameters);
        }

        //第三步 插入图片
        if (imageParameters != null) {
            replaceBookMarkWithImage(wordMLPackage, documentPart, imageParameters);
        }
        return wordMLPackage;
    }

    /**
     * 创建Docx并保存
     *
     * @param templatePath    模板docx路径
     * @param parameters      参数和值
     * @param imageParameters 书签和图片
     * @param savePath        保存docx的路径
     * @return
     */
    public static void CreateDocxFromTemplate(String templatePath,
                                              HashMap<String, String> parameters,
                                              HashMap<String, String> paragraphParameters,
                                              HashMap<String, String> imageParameters,
                                              String savePath)
            throws Exception {
        WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);

        //保存
        saveDocx(wordMLPackage, savePath);
    }


    /**
     * 创建Docx并加密保存
     *
     * @param templatePath    模板docx路径
     * @param parameters      参数和值
     * @param imageParameters 书签和图片
     * @param savePath        保存docx的路径
     * @return
     */
    public static void CreateEncryptDocxFromTemplate(String templatePath,
                                                     HashMap<String, String> parameters,
                                                     HashMap<String, String> paragraphParameters,
                                                     HashMap<String, String> imageParameters,
                                                     String savePath,
                                                     String passWord)
            throws Exception {
        WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);

        //加密
        ProtectDocument protection = new ProtectDocument(wordMLPackage);
        protection.restrictEditing(STDocProtect.READ_ONLY, passWord);

        //保存
        saveDocx(wordMLPackage, savePath);
    }

    /**
     * 创建Docx并加密,返回InputStream
     *
     * @param templatePath    模板docx路径
     * @param parameters      参数和值
     * @param imageParameters 书签和图片
     * @return
     */
    public static InputStream CreateEncryptDocxStreamFromTemplate(String templatePath,
                                                                  HashMap<String, String> parameters,
                                                                  HashMap<String, String> paragraphParameters,
                                                                  HashMap<String, String> imageParameters,
                                                                  String passWord)
            throws Exception {
        WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);

        //加密
        ProtectDocument protection = new ProtectDocument(wordMLPackage);
        protection.restrictEditing(STDocProtect.READ_ONLY, passWord);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();

        wordMLPackage.save(baos);

        ByteArrayDataSource bads =
                new ByteArrayDataSource(baos.toByteArray(), "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        return bads.getInputStream();

    }

    /**
     * 根据模板创建docx文档并放到response的outputstream中
     *
     * @param templatePath
     * @param parameter
     * @param fileName
     * @param response
     */
    public static void CreateEncryptDocxToResponseFromTemplate(String templatePath,
                                                               HashMap<String, String> parameter,
                                                               HashMap<String, String> paragraphParameters,
                                                               HashMap<String, String> imageParameters,
                                                               String fileName,
                                                               HttpServletResponse response) throws Exception {
        final InputStream inputStream = CreateEncryptDocxStreamFromTemplate(templatePath, parameter, paragraphParameters, imageParameters, UUID.randomUUID().toString());

        response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
        response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "utf-8"));
        copy(inputStream, response.getOutputStream());
    }

    /**
     * 根据模板创建docx文档返回File
     *
     * @param templatePath
     * @param parameter
     * @param fileName
     */
    public static File CreateEncryptDocxFileFromTemplate(String templatePath,
                                                         HashMap<String, String> parameter,
                                                         HashMap<String, String> paragraphParameters,
                                                         HashMap<String, String> imageParameters,
                                                         String fileName) throws Exception {
        final InputStream inputStream = CreateEncryptDocxStreamFromTemplate(templatePath, parameter, paragraphParameters, imageParameters, UUID.randomUUID().toString());
        File file = new File(fileName);
        inputStreamToFile(inputStream, file);
        return file;
    }

    /**
     * 从Docx模板文件创建Docx然后转化为pdf
     *
     * @param templatePath    模板docx路径
     * @param parameters      参数和值
     * @param imageParameters 书签和图片
     * @param savePath        保存pdf的路径
     * @return
     */
    public static void CreatePDFFromDocxTemplate(String templatePath,
                                                 HashMap<String, String> parameters,
                                                 HashMap<String, String> paragraphParameters,
                                                 HashMap<String, String> imageParameters,
                                                 String savePath)
            throws Exception {
        WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);

        //转化成PDF
        convertDocxToPDF(wordMLPackage, savePath);

    }

    /**
     * 保存当前Docx文件
     */
    private static void saveDocx(WordprocessingMLPackage wordMLPackage,
                                 String savePath)
            throws FileNotFoundException, Docx4JException {
        Docx4J.save(wordMLPackage, new File(savePath), Docx4J.FLAG_SAVE_ZIP_FILE);
    }

Original link:https://izhangzhihao.github.io//2016/12/02/Docx4J-Word模板实战/

Search

    Table of Contents