动态生成word文件并转成pdf格式(支持linux+windows)

前言

最近由于项目要求需要导出pdf文件,模板是word版本
项目有两期需求第二期是关于动态合并word中的单元格,这里合并在一起写给各位
我也是踩了各种坑之后才完整写出这篇文章

一、导出word文件

环境准备

word替参需要 freemarker-2.3.30.jar
word转pdf需要 docx4j-6.1.2.jar
建议使用maven防止缺失相关依赖包

示例

首先找一个word模板
我这边主要是处理表格示例如下

单个参数

如果是单个参数不在循环中直接替换成${参数名字}即可

动态行参数

在这里插入图片描述
在需要填参的地方写入自己定义的名字方便在xml文件中查找,另存为word xml文档,此处有一个坑,如果用上方的xml 文件,在有些2003文件格式的会出现不能转换问题
在这里插入图片描述
打开XML文件(此处我使用的notepad++然后使用XML tools进行格式化)
找到你需要填写参数的那一行
以上面图片为例第一个填写参数为“日期”,找到对应的字,然后向上查找到
<w:tr>标签这个代表的是一行
此处我需要使用list进行循环出多行所以在<w:tr>标签前面加入
<#list refuelList as refuel>
在这里插入图片描述
并找到对应的tr结束标签加入对应的</#list>
在这里插入图片描述
在中间的相关参数替换成如下这种参数
在这里插入图片描述
参数解析${}表示占位符是必须的 refuel.refuelDateStr中refuel来自<#list refuelList as refuel>中as后的值,refuelDateStr代表list中的具体参数名字

动态行合并参数

首先找到需要合并的列位置
此处根据上方动态行参数图片找到对应日期所在位置然后向上找到<w:tc>位置在如图位置添加判断,此处用的比较简单的方式判断
合并需要的属性值:<w:vMerge w:val=‘restart’/>和<w:vMerge/>
在这里插入图片描述

图片参数:

首先在word模板中添加一张图片,注:如需插入多张图片切记不要上传同一张图片,不然上传完后只能在xml中找到一段base64字符串,word相同图片会进行引用
base64字符串很容易就找到此处不截图了,找到base64字符串后替换成 ${image1},整段base64字符串替换的。
注:此处为了解决如果无图片也会进行图片大小占位问题需要找到<wp:extent cx="…" cy="…"/> 标签
此处的cx代表图片宽度,cy代表图片高度他们都是 *9525的倍数
使用图片作为参数是可能会缺失依赖包xmlgraphics-commons-2.1.jar,可以去maven仓自行下载
此处是我处理这个图片的部分代码

BASE64Encoder base64 = new BASE64Encoder();
InputStream in = null;
				try {
					try {
						// 输入流
						File imageFile = new File(filePathName);
						BufferedImage sourceImg = ImageIO.read(new FileInputStream(imageFile));
						//必需元素: <wp:extent cx="5274310" cy="3620117"/> 是图片的宽高= 像素值*9525;
						//这边先固定宽度,长度根据上传的图片进行处理,防止图片太大导致word格式不好看问题
						Integer width = 250;
						sourceImg.getWidth();
						Integer height = (Integer)(sourceImg.getHeight()*250)/sourceImg.getWidth();
						dataMap.put("imageSize1","<wp:extent cx=\""+width*9525+"\" cy=\""+height*9525+"\"/>");
						in = new FileInputStream(imageFile);
						byte[] bytes = new byte[in.available()];
						if(bytes.length>0){
							in.read(bytes);
							dataMap.put("image1",base64.encode(bytes));
						}
					} finally {
						// 完毕,关闭所有链接
						if (EmptyUtil.isNotEmpty(in)) {
							in.close();
						}
					}

动态单元格合并代码示例

import common.utils.DocUtils;
import common.utils.PdfUtils;
import module.vms.refuel.model.VmsRefuelModel;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class mergeCellsUtils {
    public static void main(String[] args) throws Exception{
        mergeCells();
    }

    public static void mergeCells() throws Exception{
        Map<String, Object> dataMap = new HashMap<String, Object>();
        List<VmsRefuelModel> lists = new ArrayList<VmsRefuelModel>();

        VmsRefuelModel refuelModel1 = new VmsRefuelModel();

        refuelModel1.setRefuelDateStr("2020-09-07");
        refuelModel1.setRefuelTime("09:15:10");
        refuelModel1.setRefuelPrice(5.7);
        refuelModel1.setRefuelLiter(20.0);
        refuelModel1.setRefuelAmount(114.0);
        refuelModel1.setGasStation("中石化加油站");
        lists.add(refuelModel1);

        VmsRefuelModel refuelModel2 = new VmsRefuelModel();
        refuelModel2.setRefuelDateStr("null");
        refuelModel2.setRefuelTime("null");
        refuelModel2.setRefuelPrice(5.7);
        refuelModel2.setRefuelLiter(21.0);
        refuelModel2.setRefuelAmount(115.0);
        refuelModel2.setGasStation("null");
        lists.add(refuelModel2);

        VmsRefuelModel refuelModel3 = new VmsRefuelModel();
        refuelModel3.setRefuelDateStr("null");
        refuelModel3.setRefuelTime("null");
        refuelModel3.setRefuelPrice(5.7);
        refuelModel3.setRefuelLiter(22.0);
        refuelModel3.setRefuelAmount(116.0);
        refuelModel3.setGasStation("null");
        lists.add(refuelModel3);

        VmsRefuelModel refuelModel4 = new VmsRefuelModel();
        refuelModel4.setRefuelDateStr("2020-09-08");
        refuelModel4.setRefuelTime("09:15:30");
        refuelModel4.setRefuelPrice(5.7);
        refuelModel4.setRefuelLiter(23.0);
        refuelModel4.setRefuelAmount(117.0);
        refuelModel4.setGasStation("中石化加油站1");
        lists.add(refuelModel4);

        VmsRefuelModel refuelModel5 = new VmsRefuelModel();
        refuelModel5.setRefuelDateStr("2020-09-09");
        refuelModel5.setRefuelTime("09:15:30");
        refuelModel5.setRefuelPrice(5.7);
        refuelModel5.setRefuelLiter(24.0);
        refuelModel5.setRefuelAmount(118.0);
        refuelModel5.setGasStation("中石化加油站2");
        lists.add(refuelModel5);

        VmsRefuelModel refuelModel6 = new VmsRefuelModel();
        refuelModel6.setRefuelDateStr("null");
        refuelModel6.setRefuelTime("null");
        refuelModel6.setRefuelPrice(5.7);
        refuelModel6.setRefuelLiter(25.0);
        refuelModel6.setRefuelAmount(119.0);
        refuelModel6.setGasStation("null");
        lists.add(refuelModel6);


        dataMap.put("refuelList",lists);
        //获取模板
        String fileBasePath = "D:/tempFile/vmsfile/template/vehicle/";
        String tempFileName = "tempFile.ftl";
        String outXmlPath = DocUtils.docParamHandle(dataMap,fileBasePath,tempFileName);
        //将doc文档转化为word类型文档
        String docPath = DocUtils.xmlCoverDoc(outXmlPath);
        //将word类型文件转化为pdf文档
        String pdfPath = PdfUtils.docCoverPdf(docPath);
        System.out.println(pdfPath);
    }
}

工具类代码

import freemarker.template.Configuration;
import freemarker.template.Template;
import org.docx4j.Docx4J;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Map;

/**
 * @author John
 * 2020年9月1日15:49:37
 */
public class DocUtils {

    private final static Logger log = LoggerFactory.getLogger(DocUtils.class);

    /**
     * 将xml格式的文件使用freemarker将参数替换
     * @param dataMap 文档参数替换
     * @param tempPath 模板路径
     * @param tempFileName 模板名称
     * @return 生成文件路径
     * @throws Exception 异常
     */
    public static String docParamHandle(Map<String, Object> dataMap,String tempPath,String tempFileName) throws Exception{
        Configuration configuration = new Configuration();
        configuration.setDefaultEncoding("utf-8");
        //有两种方式获取你的模板,模板在项目中时用第一个,模板在本地时用第二个。
        //注意:两种方式的路径都只需要写到模板的上一级目录
        log.info("获取模板地址为:"+tempPath);
        configuration.setDirectoryForTemplateLoading(new File(tempPath));
        Writer out = null;
        OutputStream outputStream = null;
        String outTempFileName = "tempFile.doc";
        //使用freemark处理文件参数并转化为xml格式doc文档
        try {
            Template t = configuration.getTemplate(tempFileName, "utf-8"); //文件名,获取模板
            File outFile = new File(tempPath+outTempFileName);
            out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile), "utf-8")); //输出路径
            t.process(dataMap, out);
        } finally {
            // 完毕,关闭所有链接
            if(out !=null)
                out.close();
            if (EmptyUtil.isNotEmpty(outputStream)) {
                outputStream.close();
            }
        }
        return tempPath+outTempFileName;
    }

	/**
     * 将xml格式的doc文件转为docx文件
     * @param xmlPath 文件路径
     * @return 返回生成后的文件路径
     */
    public static String xmlCoverDoc(String xmlPath){
        if(xmlPath.endsWith(".doc")){
            //DOC文档才转换
            String docxPath =  xmlPath.replaceAll(".doc",".docx");
            try(FileInputStream inputStream = new FileInputStream(xmlPath);){
                WordprocessingMLPackage wmlPackage = Docx4J.load(inputStream);
                //转换为DOCX
                try(FileOutputStream docx = new FileOutputStream(docxPath);){
                    Docx4J.save(wmlPackage, docx, Docx4J.FLAG_SAVE_ZIP_FILE);
                    xmlPath = docxPath;
                }
            } catch (Exception e) {
                e.printStackTrace();
                log.error(xmlPath+":不需要转换:"+e.getLocalizedMessage());
            }
        }
        log.info("WORD 路径:"+xmlPath);
        return xmlPath;
    }

    public static void main(String[] args) {
        xmlCoverDoc("D:/tempFile/vmsfile/export1.doc");
    }
}

import com.jacob.activeX.ActiveXComponent;
import com.jacob.com.Dispatch;
import com.lqting.framework.utils.SystemConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;


/**
 * @author John
 * 2020年9月1日15:49:37
 */
public class PdfUtils {

    private static final Logger logger = LoggerFactory.getLogger(PdfUtils.class);

    //window版本操作方式
    public static void docCoverPdfWindows(String wordFile,String pdfFile){//wordFile word 的路径  //pdfFile pdf 的路径
        ActiveXComponent app = null;
        logger.info("开始转换...");
        // 开始时间
        // long start = System.currentTimeMillis();
        try {
            // 打开word
            app = new ActiveXComponent("Word.Application");
            // 获得word中所有打开的文档
            Dispatch documents = app.getProperty("Documents").toDispatch();
            logger.info("打开文件: " + wordFile);
            // 打开文档
            Dispatch document = Dispatch.call(documents, "Open", wordFile, false, true).toDispatch();
            // 如果文件存在的话,不会覆盖,会直接报错,所以我们需要判断文件是否存在
            File target = new File(pdfFile);
            if (target.exists()) {
                target.delete();
            }
            logger.info("另存为: " + pdfFile);
            Dispatch.call(document, "SaveAs", pdfFile, 17);
            // 关闭文档
            Dispatch.call(document, "Close", false);
        }catch(Exception e) {
            logger.error("转换失败:{}",e);
            throw e;
        }finally {
            // 关闭office
            app.invoke("Quit", 0);
        }
    }


    public static String docCoverPdf(String docxPath) throws Exception {
        File file = new File(docxPath);
        String parentPath = file.getParent();
        String fileName = file.getName();
        String outFilePath = parentPath+fileName.substring(0,fileName.lastIndexOf(".doc"))+".pdf";
        try {
            String environment = SystemConfig.getProperty("system.run.environment");//获取当前所处系统环境
            String command = "";
            if (environment.contains("windows")) {
                docCoverPdfWindows(docxPath,outFilePath);
                return outFilePath;
            } else {
            	//此处的libreoffice7.0是在linux下生成的
                command = "libreoffice7.0 --headless --invisible --convert-to pdf "+docxPath+" --outdir "+parentPath;
            }
            String result = CommandExecute.executeCommand(command);
//	        logger.info("result==" + result);
            logger.info("生成pdf的result==" + result);
            if (result.equals("") || result.contains("writer_pdf_Export")) {
                return outFilePath;
            }
        } catch (Exception e) {
            throw e;
        }
        return "";
    }

}

此处有两个方法,第二个方法主要是为了解决doc文件在转成pdf文件时容易出现问题在pdf文件中会直接输出xml内容到pdf文件中

导出pdf所需的环境

windows环境

链接:https://pan.baidu.com/s/13RdIxmI0fXXT4HzcONaFXg.
提取码: 7ni7

windows环境直接用以上代码然后需要把配套的dll文件放在jdk的jre下bin中

liunx环境

liunx环境需要安装软件
LibreOffice_7.0.0_Linux_x86-64_rpm.tar.gz

1、配置linux下转换word文档环境
①:安装libreoffice
安装包可用 https://pan.baidu.com/s/1ADgKNWMDX-JUFKo9qOgemg.
提取码: 328b
(https://blog.csdn.net/LookingTomorrow/article/details/93632378).
步骤如下:
解压: tar -xvf LibreOffice_7.0.0_Linux_x86-64_rpm.tar.gz
安装:
cd LibreOffice_7.0.0_Linux_x86-64_rpm.tar.gz/RPMS
yum localinstall *.rpm
查看:
which libreoffice7.0 看到路径为 /usr/bin/libreoffice7.0
转换测试命令
libreoffice7.0 --headless --invisible --convert-to pdf doc文件目录 --outdir pdf放置的文件目录

安装可参考[https://blog.csdn.net/LookingTomorrow/article/details/93632378]

②导入字体
第一步:将windows下喜欢的字体文件copy到一个文件夹中,例如将XP里WINDOWS/FONTS中的字体文件在linux中命名为xpfonts
第二步:将copy到的字体文件夹copy到系统字体文件夹中并且修改权限
cp {存放xpfonts的路径}/xpfonts /usr/share/fonts/
第三步:建立字体缓存
cd /usr/share/fonts/xpfonts
mkfontscale
mkfontdir
fc-cache -fv

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值