docx4j

参考:https://blog.csdn.net/zhyh1986/article/details/8766131

 

 

我在使用过程中“翻译”了几篇外国人写的博客,这些博客质量很高(当然,不是说翻译的质量高),它们可以指引你使用docx4j,目录如下:

使用Docx4j创建word文档   https://blog.csdn.net/zhyh1986/article/details/8727523
向Docx4j生成的word文档添加图片和布局--第一部分 https://blog.csdn.net/zhyh1986/article/details/8731017
向Docx4j生成的word文档中添加布局--第二部分   https://blog.csdn.net/zhyh1986/article/details/8733389
使用docx4j编程式地创建复杂的Word(.docx)文档  https://blog.csdn.net/zhyh1986/article/details/8766628

docx4j介绍

官方网站:http://www.docx4java.org/trac/docx4j

下载地址:http://www.docx4java.org/downloads.html

入门指南:Getting Started guide(PDF)(HTML)

得益于天朝伟大的GFW,docx4j的官方站点有时可能需要挂代理才能访问。

官方介绍:

docx4j is a Java library for creating and manipulating Microsoft Open XML (Word docx, Powerpoint pptx, and Excel xlsx) files.
It is similar to Microsoft's OpenXML SDK, but for Java.
docx4j uses JAXB to create the in-memory object representation.
It is available under the Apache License (v2).
docx4j was created by Plutext Pty Ltd in 2008 - using OpenXML4J for the OPC piece. Plutext still drives the project, but since then docx4j has benefited from contributions from many individuals. The contributors are listed in docx4j's pom.xml.

docx4j能做什么

打开已存在docx(从文件系统、SMB/CIFS、使用VFS的WebDAV),pptx,xlsx
创建新的docx、pptx、xlsx
编程式地操作上面打开的文档(很显示)
docx4j特殊的功能支持:
模版替换;CustomXML绑定
生产/消费Word2007的xmlPackage(pkg)格式
作为docx保存docx到文件系统(zipped)或者保存到JCR(unzipped)
应用转换,包括常见过滤器
作为HTML或者PDF导出
比较文档、段落或者sdt(内容控件)之间的差异
字体支持(字体替换及使用任何文档中嵌入的字体)

具体的使用技巧请看前面提到的几篇博客以及docx4j的入门指南,这里仅列出几个自己了解而前面博客没有提到的使用技巧:合并docx文档和转换PDF。


合并多个docx文档

现在所做的项目中,需要合并多个docx文档,这让我纠结了很长一段时间;其实在docx4j的基础上,作者还提供了合并多个docx文档的lib,但那是需要商业授权的,所以没法使用,但后来在docx4j的forum中看到了其他人提供的解决方案,详情如下:

 

public InputStream mergeDocx(final List<InputStream> streams)
            throws Docx4JException, IOException {
 
    WordprocessingMLPackage target = null;
    final File generated = File.createTempFile("generated", ".docx");
 
    int chunkId = 0;
    Iterator<InputStream> it = streams.iterator();
    while (it.hasNext()) {
        InputStream is = it.next();
        if (is != null) {
            if (target == null) {
                // Copy first (master) document
                OutputStream os = new FileOutputStream(generated);
                os.write(IOUtils.toByteArray(is));
                os.close();
 
                target = WordprocessingMLPackage.load(generated);
            } else {
                // Attach the others (Alternative input parts)
                insertDocx(target.getMainDocumentPart(),
                        IOUtils.toByteArray(is), chunkId++);
            }
        }
    }
 
    if (target != null) {
        target.save(generated);
        return new FileInputStream(generated);
    } else {
        return null;
    }
}
 
// 插入文档
private void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) {
    try {
        AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart(
                new PartName("/part" + chunkId + ".docx"));
        afiPart.setContentType(new ContentType(CONTENT_TYPE));
        afiPart.setBinaryData(bytes);
        Relationship altChunkRel = main.addTargetPart(afiPart);
 
        CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk();
        chunk.setId(altChunkRel.getId());
 
        main.addObject(chunk);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

 

 

docx文档转换为PDF

在做docx转换PDF时让我为难了好长一阵子,因为中文导致乱码,官方示例中是有这一部分内容的,但由于注释太少,所以一直没有注意到,后来才发现示例的作者将字体相关的两句代码注释掉了:

//    Set up font mapper
//    Mapper fontMapper = new BestMatchingMapper();
//    wordMLPackage.setFontMapper(fontMapper);

 

在将这段代码加上之后,中文乱码没有了,但是好像除了“宋体”以外的其它字体还会乱码,比如:华文行楷、隶书之类的,要解决这些问题,需要多做点工作:

Mapper fontMapper = new IdentityPlusMapper();
fontMapper.getFontMappings().put("华文行楷", PhysicalFonts.getPhysicalFonts().get("STXingkai"));
// 其它中文字体
mlPackage.setFontMapper(fontMapper);
 
// 然后再创建转换器
PdfConversion conversion = new Conversion(mlPackage);

 

完整方法代码:

/**
 * docx文档转换为PDF
 * @param docx docx文档
 * @param pdfPath PDF文档存储路径
 * @throws Exception 可能为Docx4JException, FileNotFoundException, IOException等
 */
public void convertDocxToPDF(File docx, String pdfPath) throws Exception {
    OutputStream os = null;
    try {
        WordprocessingMLPackage mlPackage = WordprocessingMLPackage.load(docx);
//            Mapper fontMapper = new BestMatchingMapper();
        Mapper fontMapper = new IdentityPlusMapper();
        fontMapper.getFontMappings().put("华文行楷", PhysicalFonts.getPhysicalFonts().get("STXingkai"));
        fontMapper.getFontMappings().put("华文仿宋", PhysicalFonts.getPhysicalFonts().get("STFangsong"));
        fontMapper.getFontMappings().put("隶书", PhysicalFonts.getPhysicalFonts().get("LiSu"));
        mlPackage.setFontMapper(fontMapper);
 
        PdfConversion conversion = new org.docx4j.convert.out.pdf.viaXSLFO.Conversion(mlPackage);
        os = new FileOutputStream(pdfPath);
        
        conversion.output(os, new PdfSettings());
    } finally {
        IOUtils.closeQuietly(os);
    }
}

 


另外,docx4j还支持通过iTtext将docx文档转换为PDF,不过好像只能支持iText2.X版本,新版本不能用!
其实这样转换好像还不够完美,比如页眉页脚、目录什么的,都会出乱子;由于项目中word文档较为复杂,最终没有采用docx4j做PDF转换,换成了jacob......

docx4j整合word文档

   需求:将已经存在的多个文档,首先按照业务需求提取文档中特定文本或表格中数据,其次将数据按照整合文档格式及位置要求整合到一个word中  

   思路:将上传整合的文档放到文件服务器的过程中读取文件中固定提取部位的文本,当作一个字段的值存入数据库中。在创建模板word文件,模板word文档中需要设置替换字符的标识(如{{}})。上述条件设置好后,读取整合的文档和模板,从整合文档中读取文本,过滤出对应替换的字符标识,再从数据库中查询需要替换的值,进行适配替换。

package com.dataEvaluation.dataEvaluationTool;


import com.dataEvaluation.dataEvaluationTool.domain.DataEvaluationProject;
import com.dataEvaluation.dataEvaluationTool.mapper.DataEvaluationProjectMapper;
import com.ruoyi.common.core.utils.StringUtils;
import com.ruoyi.common.core.utils.bean.EnhancedObjectToMap;
import org.docx4j.Docx4J;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Text;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

import javax.annotation.Resource;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import java.io.File;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@SpringBootTest
public class DataEvaluationToolApplicationTests {
    @Resource
    private DataEvaluationProjectMapper dataEvaluationProjectMapper;

    // 匹配 {{key}} 格式占位符的正则表达式
    private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("\\{\\{([^}]+)\\}\\}");


    /**
     * 整合文档
     * @throws Docx4JException
     */
    @Test
    void contextLoads() throws Exception {
        //1.查询数据库封装参数
        DataEvaluationProject dataEvaluationProject = new DataEvaluationProject();
        if (Objects.isNull(dataEvaluationProject.getProjectId())) {
            dataEvaluationProject.setProjectId(1L);
        }
        DataEvaluationProject item = dataEvaluationProjectMapper.selectDataEvaluationProjectByProjectId(dataEvaluationProject.getProjectId());
        Map<String, String> map = EnhancedObjectToMap.convertToMapString(item);
        String templatePath = "D:\\\\project\\\\pcProject\\\\dataevaluationtool\\\\RuoYi-Cloud-master\\\\RuoYi-Cloud-master\\\\ruoyi-modules\\\\dataEvaluation-tool\\\\src\\\\main\\\\resources\\\\template/测试模板-网络数据安全风险评估报告.docx";
        String outputPath =  "D:\\project\\pcProject\\dataevaluationtool\\RuoYi-Cloud-master\\RuoYi-Cloud-master\\ruoyi-modules\\dataEvaluation-tool\\target\\报告" + System.currentTimeMillis() + ".docx";
        // 2. 加载Word文档
        System.out.println("正在加载文档: " + templatePath);
        File templateFile = new File(templatePath);
        WordprocessingMLPackage wordMLPackage;
        try {
            wordMLPackage = Docx4J.load(templateFile);
        } catch (Exception loadException) {
            // 如果直接加载失败,尝试使用宽松模式
            System.out.println("使用默认加载方式失败,尝试宽松模式...");
            wordMLPackage = WordprocessingMLPackage.load(templateFile);
        }
        // 关闭严格验证
        MainDocumentPart mainDocumentPart = wordMLPackage.getMainDocumentPart();

        //3.匹配替换
        Set<String> uniquePlaceholders = new HashSet<>(); // 使用Set确保占位符唯一

        long start = System.currentTimeMillis();
        System.out.println("starTime " + start);
        if (mainDocumentPart != null) {
            String textNodesXPath = "//w:t";
            List<?> textNodes = mainDocumentPart.getJAXBNodesViaXPath(textNodesXPath, true);
            System.out.println("找到 " + textNodes.size() + " 个文本节点");
            for (Object obj : textNodes) {
                if (obj instanceof JAXBElement) {
                    JAXBElement<?> jaxbElement = (JAXBElement<?>) obj;
                    if (jaxbElement.getValue() instanceof Text) {
                        Text text = (Text) jaxbElement.getValue();
                        String textString = text.getValue();
                        if (StringUtils.isNotEmpty(textString)) {
                            Matcher matcher = PLACEHOLDER_PATTERN.matcher(textString);
                            while (matcher.find()) {
                                uniquePlaceholders.add(matcher.group(0)); // group(0) 是完整的匹配项,如 {{name}}
                                System.out.println("匹配的文本内容: " + textString);
                                // 3. 执行替换
                                String value = map.get(matcher.group(1));
                                if (value != null) {
                                    text.setValue(value);
                                    text.setSpace("preserve");
                                    System.out.println("已将 '" + textString + "' 替换为 '" + value + "'");
                                }
                            }
                        }
                    }
                }
            }
        }
        System.out.println("endTime " + (System.currentTimeMillis() - start));

        // 4. 保存修改后的文档
        System.out.println("正在保存文档到: " + outputPath);
        Docx4J.save(wordMLPackage, new File(outputPath));
        System.out.println("文档保存成功!");

    }


}

 

posted @ 2025-12-30 13:58  ConfidentLiu  阅读(1)  评论(0)    收藏  举报