SpringBoot如何实现word文档转pdf

目录
  • 一.背景
  • 二.方案选择
    • 1.Spire.Doc for Java方案
    • 2.docx4j方案
    • 3.jodconverter+LibreOffice 方案
    • 4.其他
  • 三.实操
    • 1.docx4j
    • 2.poi-tl+jodconverter+LibreOffice 方案
  • 四.结论
    • 1.docx4j方案
    • 2.jodconverter+LibreOffice 方案

一.背景

项目中有个需求大体意思是,上传一个word模板,根据word模板合成word文件,再将word文件转为pdf。

二.方案选择

1.Spire.Doc for Java方案

Spire.Doc for Java这个是商用收费的,不过API文档丰富且集成简单,免费版仅支持3页转换。类似的还有ITEXT,这个商用也是受限制的。

2.docx4j方案

开源可商用,仅支持docx格式的word。

3.jodconverter+LibreOffice 方案

开源可商用,调用本地office服务,进行pdf转换,类似的还有jodconverter+openOffice。

4.其他

至于其他的由于不支持跨平台不做考虑。

三.实操

1.docx4j

首先尝试了docx4j,因为docx4j本身支持模板替换的操作,可一次性做替换及文档类型转换,而且仅支持docx类型,对于本次需求问题不大。

1.依赖仅需要一个即可

<dependency>
    <groupId>org.docx4j</groupId>
    <artifactId>docx4j-export-fo</artifactId>
    <version>6.1.0</version>
</dependency>

2.主要代码

@Slf4j
public class PdfUtil {
    public static <T> void exportByLocalPath(HttpServletResponse response, String fileName, String path, Map<String,String> params){
        try (InputStream in = PdfUtil.class.getClassLoader().getResourceAsStream(path)) {
            convertDocxToPdf(in, response,fileName,params);
        } catch (Exception e) {
            log.error("docx文档转换为PDF失败", e.getMessage());
        }
    }
    /**
     * docx文档转换为PDF
     * @param in
     * @param response
     * @return
     */
    public static void convertDocxToPdf(InputStream in, HttpServletResponse response, String fileName, Map<String,String> params) throws Exception {
        response.setContentType("application/pdf");
         String fullFileName = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1);
        response.setHeader("Content-disposition", "attachment;filename=" + fullFileName + ".pdf");
        WordprocessingMLPackage wmlPackage = WordprocessingMLPackage.load(in);
        if (params!=null&&!params.isEmpty()) {
            MainDocumentPart documentPart = wmlPackage.getMainDocumentPart();
            cleanDocumentPart(documentPart);
            documentPart.variableReplace(params);
        }
        setFontMapper(wmlPackage);
        Docx4J.toPDF(wmlPackage,response.getOutputStream());
    }
    /**
     * 清除文档空白占位符
     * @param documentPart
     * @return {@link boolean}
     */
    public static boolean cleanDocumentPart(MainDocumentPart documentPart) throws Exception {
        if (documentPart == null) {
            return false;
        }
        Document document = documentPart.getContents();
        String wmlTemplate =
                XmlUtils.marshaltoString(document, true, false, Context.jc);
        document = (Document) XmlUtils.unwrap(DocxVariableClearUtil.doCleanDocumentPart(wmlTemplate, Context.jc));
        documentPart.setContents(document);
        return true;
    }
    /**
     * 设置字体样式
     * @param mlPackage
     */
    private static void setFontMapper(WordprocessingMLPackage mlPackage) throws Exception {
        Mapper fontMapper = new IdentityPlusMapper();
        fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
        fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
        fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
        fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
        fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
        fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
        fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
        fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
        fontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
        fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
        fontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
        fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
        fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
        fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
        mlPackage.setFontMapper(fontMapper);
    }
}

清除工具类,用于处理占位符替换不生效的问题,这里参考文章

public class DocxVariableClearUtil {
    /**
     * 去任意XML标签
     */
    private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>");
    private DocxVariableClearUtil() {
    }
    /**
     * start符号
     */
    private static final char PREFIX = '$';
    /**
     * 中包含
     */
    private static final char LEFT_BRACE = '{';
    /**
     * 结尾
     */
    private static final char RIGHT_BRACE = '}';
    /**
     * 未开始
     */
    private static final int NONE_START = -1;
    /**
     * 未开始
     */
    private static final int NONE_START_INDEX = -1;
    /**
     * 开始
     */
    private static final int PREFIX_STATUS = 1;
    /**
     * 左括号
     */
    private static final int LEFT_BRACE_STATUS = 2;
    /**
     * 右括号
     */
    private static final int RIGHT_BRACE_STATUS = 3;
    /**
     * doCleanDocumentPart
     *
     * @param wmlTemplate
     * @param jc
     * @return
     * @throws JAXBException
     */
    public static Object doCleanDocumentPart(String wmlTemplate, JAXBContext jc) throws JAXBException {
        // 进入变量块位置
        int curStatus = NONE_START;
        // 开始位置
        int keyStartIndex = NONE_START_INDEX;
        // 当前位置
        int curIndex = 0;
        char[] textCharacters = wmlTemplate.toCharArray();
        StringBuilder documentBuilder = new StringBuilder(textCharacters.length);
        documentBuilder.append(textCharacters);
        // 新文档
        StringBuilder newDocumentBuilder = new StringBuilder(textCharacters.length);
        // 最后一次写位置
        int lastWriteIndex = 0;
        for (char c : textCharacters) {
            switch (c) {
                case PREFIX:
                    // 不管其何状态直接修改指针,这也意味着变量名称里面不能有PREFIX
                    keyStartIndex = curIndex;
                    curStatus = PREFIX_STATUS;
                    break;
                case LEFT_BRACE:
                    if (curStatus == PREFIX_STATUS) {
                        curStatus = LEFT_BRACE_STATUS;
                    }
                    break;
                case RIGHT_BRACE:
                    if (curStatus == LEFT_BRACE_STATUS) {
                        // 接上之前的字符
                        newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex, keyStartIndex));
                        // 结束位置
                        int keyEndIndex = curIndex + 1;
                        // 替换
                        String rawKey = documentBuilder.substring(keyStartIndex, keyEndIndex);
                        // 干掉多余标签
                        String mappingKey = XML_PATTERN.matcher(rawKey).replaceAll("");
                        if (!mappingKey.equals(rawKey)) {
                            char[] rawKeyChars = rawKey.toCharArray();
                            // 保留原格式
                            StringBuilder rawStringBuilder = new StringBuilder(rawKey.length());
                            // 去掉变量引用字符
                            for (char rawChar : rawKeyChars) {
                                if (rawChar == PREFIX || rawChar == LEFT_BRACE || rawChar == RIGHT_BRACE) {
                                    continue;
                                }
                                rawStringBuilder.append(rawChar);
                            }
                            // 要求变量连在一起
                            String variable = mappingKey.substring(2, mappingKey.length() - 1);
                            int variableStart = rawStringBuilder.indexOf(variable);
                            if (variableStart > 0) {
                                rawStringBuilder = rawStringBuilder.replace(variableStart, variableStart + variable.length(), mappingKey);
                            }
                            newDocumentBuilder.append(rawStringBuilder.toString());
                        } else {
                            newDocumentBuilder.append(mappingKey);
                        }
                        lastWriteIndex = keyEndIndex;
                        curStatus = NONE_START;
                        keyStartIndex = NONE_START_INDEX;
                    }
                default:
                    break;
            }
            curIndex++;
        }
        // 余部
        if (lastWriteIndex < documentBuilder.length()) {
            newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex));
        }
        return XmlUtils.unmarshalString(newDocumentBuilder.toString(), jc);
    }
}

2.poi-tl+jodconverter+LibreOffice 方案

poi-tl这个是专门用来进行word模板合成的开源库,文档很详细。

LibreOffice 下载最新的稳定版本即可。

1.maven依赖

		<!-- word合成 -->
		<!-- 这里注意版本,1.5版本依赖的poi 3.x的版本 -->
		<dependency>
			<groupId>com.deepoove</groupId>
			<artifactId>poi-tl</artifactId>
			<version>1.5.1</version>
		</dependency>
		<!-- jodconverter  word转pdf -->
		<!-- jodconverter-core这个依赖,理论上不用加的,jodconverter-local已经依赖了,但测试的时候不添加依赖找不到 -->
		<dependency>
			<groupId>org.jodconverter</groupId>
			<artifactId>jodconverter-core</artifactId>
			<version>4.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.jodconverter</groupId>
			<artifactId>jodconverter-local</artifactId>
			<version>4.2.0</version>
		</dependency>
		<dependency>
			<groupId>org.jodconverter</groupId>
			<artifactId>jodconverter-spring-boot-starter</artifactId>
			<version>4.2.0</version>
		</dependency>
		<!--  工具类,非必须 -->
		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.4.3</version>
		</dependency>

2.主要代码

JodConverterConfig配置类

@Configuration
public class JodConverterConfig {
    @Autowired
    private OfficeManager officeManager;
    @Bean
    public DocumentConverter documentConverter() {
        return LocalConverter.builder()
                .officeManager(officeManager)
                .build();
    }
}

yml配置文件

jodconverter:
  local:
    enabled: true
    office-home: "C:\\Program Files\\LibreOffice"

PdfService合成导出代码

@Slf4j
@Component
public class PdfService {
    @Autowired
    private DocumentConverter documentConverter;
    public  void docxToPDF(InputStream inputStream,HttpServletResponse response,String fileName) {
        response.setContentType("application/pdf");
        try {
            String fullFileName = new String(fileName.getBytes(), StandardCharsets.ISO_8859_1);
            response.setHeader("Content-disposition","attachment;filename=\\"+fullFileName+".pdf\\");
            documentConverter
                    .convert(inputStream)
                    .as(DefaultDocumentFormatRegistry.DOCX)
                    .to(response.getOutputStream())
                    .as(DefaultDocumentFormatRegistry.PDF)
                    .execute();
        } catch (OfficeException |IOException e) {
           log.error("word转pdf失败:{}",e.getMessage());
        }
    }
    public void exportByLocalPath(HttpServletResponse response, String fileName, String path, Object params) throws Exception {
        BufferedOutputStream outputStream = null;
        BufferedInputStream wordInputStream = null;
        try (InputStream in = PdfService.class.getClassLoader().getResourceAsStream(path)) {
            // 生成临时文件
            String outPutWordPath = System.getProperty("java.io.tmpdir").replaceAll(File.separator + "$", "") + fileName+".docx";
            File tempFile = FileUtil.touch(outPutWordPath);
            outputStream = FileUtil.getOutputStream(tempFile);
            // word模板合成写到临时文件
            WordUtil.replaceWord(outputStream, in, params);
            // word 转pdf
            wordInputStream = FileUtil.getInputStream(tempFile);
            docxToPDF(wordInputStream, response,fileName);
            // 移除临时文件
            FileUtil.del(tempFile);
        } catch (Exception e) {
            log.error("docx文档转换为PDF失败", e.getMessage());
        } finally {
            IoUtil.close(outputStream);
            IoUtil.close(wordInputStream);
        }
    }

四.结论

1.docx4j方案

  • 依赖少
  • 同时支持word合成及格式转换
  • 转化效率较差
  • 对于含样式及图片转换不友好,容易排版混乱

2.jodconverter+LibreOffice 方案

  • 操作稳定
  • 转换效率快
  • 集成依赖设置较多
  • 依赖本地服务
  • LibreOffice打开word可能排版样式错乱
  • 最后考虑项目需求,最终选择了jodconverter+LibreOffice方案。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • Java 实现word模板转为pdf

    1. pom相关依赖 工具poi-tl (操作word文档模板) + jacob (将操作后的word模板转为pdf) <!-- poi-tl的pom依赖 --> <dependency> <groupId>com.deepoove</groupId> <artifactId>poi-tl</artifactId> <version>1.9.1</version> </dependency> <

  • spring boot实现自动输出word文档功能的实例代码

    spring boot实现自动输出word文档功能 本文用到Apache POI组件 组件依赖在pom.xml文件中添加 <dependency> <groupId>org.apache.poi</groupId> <artifactId>poi</artifactId> <version>4.1.0</version> </dependency> <dependency> <groupId&

  • springboot各种格式转pdf的实例代码

    添加依赖 <!--转pdf--> <dependency> <groupId>com.documents4j</groupId> <artifactId>documents4j-local</artifactId> <version>1.0.3</version> </dependency> <dependency> <groupId>com.documents4j</

  • SpringBoot如何实现word文档转pdf

    目录 一.背景 二.方案选择 1.Spire.Doc for Java方案 2.docx4j方案 3.jodconverter+LibreOffice 方案 4.其他 三.实操 1.docx4j 2.poi-tl+jodconverter+LibreOffice 方案 四.结论 1.docx4j方案 2.jodconverter+LibreOffice 方案 一.背景 项目中有个需求大体意思是,上传一个word模板,根据word模板合成word文件,再将word文件转为pdf. 二.方案选择 1

  • java实现word文档转pdf并添加水印的方法详解

    本文实例讲述了java实现word文档转pdf并添加水印的方法.分享给大家供大家参考,具体如下: 前段时间,项目需要自动生成word文档,用WordFreeMarker生成word文档后,又要求生成的文档能在浏览器浏览,思来想去,把word文档转成pdf就好了,于是乎研究了一下. 将word文档转化为PDF是项目中常见的需求之一,目前主流的方法可以分为两大类,一类是利用各种Office应用进行转换,譬如Microsoft Office.WPS以及LiberOffice,另一种是利用各种语言提供的

  • 1秒钟实现Springboot 替换/写入 word文档里面的文字、图片功能

    目录 前言 正文 前言 图片加水印:Springboot 图片需要添加水印,怎么办? 1秒就实现 那么word文档替换文字.插入图片,当然也是1秒钟了(jar包引入,工具类代码复制粘贴,调试,完事). 事不宜迟,开始敲代码. 正文 本篇内容: 1.word文档 替换内容里面的文字 (模板占位符方式传参替换) 2.word文档 插入图片 (指定位置传参插入) ① pom.xml 引入依赖: <dependency> <groupId>org.apache.poi</groupI

  • Python实现自动化处理Word文档的方法详解

    目录 1. 批量生成Word文档 2. 将Word文档批量转换成PDF 3. 在Word文档中批量标记关键词 4. 在Word文档中批量替换关键词 使用Python实现Word文档的自动化处理,包括批量生成Word文档.在Word文档中批量进行查找和替换.将Word文档批量转换成PDF等. 1. 批量生成Word文档 安装openpyxl模块 pip install openpyxl 安装python-docx模块 pip install python-docx openpyxl模块可以读写扩展

  • ASP.NET实现将word文档转换成pdf的方法

    本文实例讲述了ASP.NET实现将word文档转换成pdf的方法,分享给大家供大家参考.具体实现步骤如下: 一.添加引用 复制代码 代码如下: using Microsoft.Office.Interop.Word; 二.转换方法   1.方法 复制代码 代码如下: /// <summary>     /// 把Word文件转换成pdf文件     /// </summary>     /// <param name="sourcePath">需要转

  • JSP生成WORD文档,EXCEL文档及PDF文档的方法

    本文实例讲述了JSP生成WORD文档,EXCEL文档及PDF文档的方法.分享给大家供大家参考,具体如下: 在web-oa系统中,公文管理好象不可或缺,有时需要从数据库中查询一些数据以某种格式输出来,并以word文档的形式展现,有时许多word文档保存到数据库中的某个表的Blob字段里,服务器再把保存在Blob字段中的图片文件展现给用户.通过网上查找发现很少有关于此类的文章,现在整理起来供大家参考. 1 在client端直接生成word文档 在jsp页面上生成word文档非常简单,只需把conte

  • python实现word 2007文档转换为pdf文件

    在开发过程中,会遇到在命令行下将DOC文档(或者是其他Office文档)转换为PDF的要求.比如在项目中如果手册是DOC格式的,在项目发布时希望将其转换为PDF格式,并且保留DOC中的书签,链接等.将该过程整合到构建过程中就要求命令行下进行转换. Michael Suodenjoki展示了使用Office的COM接口进行命令行下的转换.但其导出的PDF文档没有书签.在Office 2007 SP2中,微软加入了该功能,对应的接口是ExportAsFixedFormat.该方法不仅适用于Word,

  • Java SpringBoot集成文件之如何使用POI导出Word文档

    目录 前言 知识准备 什么是POI 实现案例 Pom依赖 导出Word 前言 通过Apache POI导出excel,而Apache POI包含是操作Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API.所以也是可以通过POI来导出word的.本文主要介绍通过SpringBoot集成POI工具实现Word的导出功能. 知识准备 需要理解Apache POI遵循的标准(Office Open XML(OOXML)标准和微软的OLE 2复合文档

  • Java实现PDF转为Word文档的示例代码

    目录 代码编译环境 将 PDF 转换为固定布局的 Doc/Docx 文档 完整代码 将 PDF 转换为流动形态的 Doc/Docx 文档 完整代码 效果图 众所周知,PDF文档除了具有较强稳定性和兼容性外, 还具有较强的安全性,在工作中可以有效避免别人无意中对文档内容进行修改.但与此同时,也妨碍了对文档的正常的修改.这时我们可以将PDF转为Word文档进行修改或再编辑.使用软件将 PDF 文档转换为 Word 文档十分简单,然而要在转换时保持布局甚至字体格式却并不容易.本文将分为以下两部分介绍如

  • asp.net下用Aspose.Words for .NET动态生成word文档中的数据表格的方法

    1.概述 最近项目中有一个这样的需求:导出word 文档,要求这个文档的格式不是固定的,用户可以随便的调整,导出内容中的数据表格列是动态的,例如要求导出姓名和性别,你就要导出这两列的数据,而且这个文档不是导出来之后再调整而是导出来后已经是调整过了的.看到这里,您也许马上想到用模板导出!而且.NET中自带有这个组件:Microsoft.Office.Interop.Word,暂且可以满足需求吧.但这个组件也是有局限性的,例如客户端必须装 office组件,而且编码复杂度高.最麻烦的需求是后面那个-

随机推荐