java docx4j动态生成表格,保存为word,并通过Libreoffice转PDF

缘起

最近客户要求把业务字段生成一个pdf,包含大量的表格,于是探究了两天版,终于找出一个比较完美的解决方案。本次采用的是docx4j,Libreoffice两个套件,docx4j本身有转PDF的功能,但是转换完成的PDF乱码,我没有找到解决乱码的方法(比较菜,勿喷),然后选择了Libreoffice的方式,期间也是解决了很多bug,然后最终成功

安装 Libreoffice

之前在网上看到也有使用openoffice的,我之前也试过,转成pdf格式全变了,尤其是表格,后来发现的兼容性更好的Libreoffice,下面开始安装。

  1. 官网地址:https://www.libreoffice.org/
  2. 点击download,然后选择系统,我的是win10 64位的,所以选择Windows x86_64
  3. 点下载后开始下载,如果感觉下载比较慢,在chrome(我使用的是谷歌浏览器)的下载内容里面,复制下载链接,拷贝到迅雷里面,我就是用的这种方法,下载速度还不错
  4. 下载下来是msi文件,开始安装,我自定义了自己的路径:D:\libreOffice6\program\soffice -headless --accept="socket,host=127.0.0.1,port=8100;urp;" -nologo -nofirststartwizard,host如果是127.0.0.1的话是只能本机访问,要是想让所有机器都能访问,就改为0.0.0.0,port是端口号。记住一定要加 -handless这个参数,不然在windows下调试一定会没法退出程序,这个参数的意思就是无界面运行。

coding

新建一个maven工程,pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>net.blf2</groupId>
    <artifactId>test-docx4j</artifactId>
    <version>0.1</version>
    <name>Testdocx4j</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <skipTests>true</skipTests>
    </properties>

    <dependencies>

        <!-- https://mvnrepository.com/artifact/org.docx4j/docx4j -->
        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j</artifactId>
            <version>3.3.7</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.docx4j/docx4j-export-fo -->
        <dependency>
            <groupId>org.docx4j</groupId>
            <artifactId>docx4j-export-fo</artifactId>
            <version>3.3.1</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.openoffice/bootstrap-connector -->
        <dependency>
            <groupId>org.openoffice</groupId>
            <artifactId>bootstrap-connector</artifactId>
            <version>0.1.1</version>
        </dependency>

    </dependencies>
</project>

代码:

package main.java.net.blf2;

import com.sun.star.beans.PropertyValue;
import com.sun.star.frame.XComponentLoader;
import com.sun.star.frame.XDesktop;
import com.sun.star.frame.XStorable;
import com.sun.star.lang.XComponent;
import com.sun.star.lang.XMultiComponentFactory;
import com.sun.star.uno.UnoRuntime;
import com.sun.star.uno.XComponentContext;
import ooo.connector.BootstrapSocketConnector;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.math.BigInteger;

public class Docx4jUtil {

    private static final Logger logger = LoggerFactory.getLogger(Docx4jUtil.class);
    public static WordprocessingMLPackage wordPackage = null;
    public static MainDocumentPart mainDocumentPart = null;
    public static ObjectFactory objectFactory = null;

    /**
     * 创建word文档
     */
    public static void createDocx() {
        try {
            wordPackage = WordprocessingMLPackage.createPackage();

            mainDocumentPart = wordPackage.getMainDocumentPart();
            objectFactory = Context.getWmlObjectFactory();
        } catch (InvalidFormatException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

        if (wordPackage == null || mainDocumentPart == null || objectFactory == null) {
            logger.error("创建word文档失败!!!");
            wordPackage = null;
            mainDocumentPart = null;
            objectFactory = null;
        }
    }


    /**
     * 横向合并单元格
     * @param mergeTable 需要合并的table
     * @param beginColumn 开始列(从0开始算起)
     * @param endColumn 结束列(从0开始算起)
     * @param row 需要合并的行(从0开始算起)
     * @param content 合并后内容
     */
    public static void mergeTdCell(Tbl mergeTable, int beginColumn, int endColumn, int row, String content) {

        Tr tr = (Tr) mergeTable.getContent().get(row);

        Tc beginTd = (Tc) tr.getContent().get(beginColumn);

        CTVerticalJc vjc = new CTVerticalJc();

        vjc.setVal(STVerticalJc.CENTER);

        beginTd.getTcPr().setVAlign(vjc);//上下垂直居中

        TcPrInner.HMerge beginMerge = new TcPrInner.HMerge();

        beginMerge.setVal("restart");

        beginTd.getTcPr().setHMerge(beginMerge);

        for (int i = beginColumn + 1; i <= endColumn; i++) {

            Tc endTd = (Tc) tr.getContent().get(i);

            TcPrInner.HMerge endMerge = new TcPrInner.HMerge();

            endMerge.setVal("continue");

            endTd.getTcPr().setHMerge(endMerge);
        }
        beginTd.getContent().clear();
        beginTd.getContent().add(mainDocumentPart.createParagraphOfText(content));
    }

    /**
     * 纵向合并单元格
     *
     * @param mergeTable 需要合并的表格
     * @param beginRow   开始行(从0开始算起)
     * @param endRow     结束行(从0算起)
     * @param column     列号(从0开始)
     */
    public static void mergeMdCell(Tbl mergeTable, int beginRow, int endRow, int column, String content) {

        CTVerticalJc vjc = new CTVerticalJc();

         vjc.setVal(STVerticalJc.CENTER);
		
         Tc beginTd = null;
         
		for(int i = beginRow;i <= endRow;i++) {
			 Tr tr = (Tr) mergeTable.getContent().get(i);

	          beginTd = (Tc) tr.getContent().get(column);

	          VMerge merge = new VMerge();

	          merge.setVal("restart");

	          beginTd.getTcPr().setVMerge(merge);

	          beginTd.getTcPr().setVAlign(vjc);//上下垂直居中
		}
		
		beginTd.getContent().clear();
		beginTd.getContent().add(mainDocumentPart.createParagraphOfText(content));
    }

    /**
     * 根据一个二维数据创建表格
     * @param content 二维数组 带内容
     * @return
     */
    public static Tbl createDataTable(String[][] content) {

        Tbl table = objectFactory.createTbl();

        PPr ppr = new PPr();

        Jc jc = new Jc();

        jc.setVal(JcEnumeration.CENTER);

        ppr.setJc(jc);//单元格文本居中使用

        for (int i = 0; i < content.length; i++) {
            Tr dataTr = objectFactory.createTr();
            for (int j = 0; j < content[i].length; j++) {
                Tc tc = objectFactory.createTc();

                tc.setTcPr(new TcPr());

                tc.getContent().add(mainDocumentPart.createParagraphOfText(content[i][j]));

                dataTr.getContent().add(tc);

            }
            table.getContent().add(dataTr);
        }
        setTcBorders(table);
        return table;
    }

    /**
     * 保存word文件
     * @param outPathFileName 输出目录+文件名 例如E:\\test\\test.docx
     */
    public static void save(String outPathFileName) {

        try {
            wordPackage.save(new File(outPathFileName));
        } catch (Docx4JException e) {
            e.printStackTrace();
        }

    }

    /**
     * 把初始化的实例置为空
     */
    public static void cleanResource() {
        wordPackage.clone();
        mainDocumentPart = null;
        objectFactory = null;
        logger.info("清理完成...");
    }

    /**
     * 设置单元格边框
     * @param table
     */
    private static void setTcBorders(Tbl table) {

        table.setTblPr(new TblPr());// 必须设置一个TblPr,否则最后会报空指针异常

        CTBorder border = new CTBorder();
        border.setColor("auto");
        border.setSz(new BigInteger("4"));
        border.setSpace(new BigInteger("0"));
        border.setVal(STBorder.SINGLE);

        TblBorders borders = new TblBorders();
        borders.setBottom(border);
        borders.setLeft(border);
        borders.setRight(border);
        borders.setTop(border);
        borders.setInsideH(border);
        borders.setInsideV(border);

        // 获取其内部的TblPr属性设置属性
        table.getTblPr().setTblBorders(borders);

    }

    /**
     * 把word转为pdf
     * @param workingDir 工作目录 就是word和pdf所在目录
     * @param docxFileName word文件名
     * @param pdfFileName pdf文件名
     * @return
     */
    public static void convertDocxToPDF2(String workingDir,String docxFileName,String pdfFileName) {

        try {
            // Initialise
            String oooExeFolder = "D:\\libreOffice6\\program";
            XComponentContext xContext = BootstrapSocketConnector.bootstrap(oooExeFolder);

            XMultiComponentFactory xMCF = xContext.getServiceManager();

            Object oDesktop = xMCF.createInstanceWithContext(
                    "com.sun.star.frame.Desktop", xContext);

            XDesktop xDesktop = (XDesktop) UnoRuntime.queryInterface(
                    XDesktop.class, oDesktop);
            // Load the Document
            String myTemplate = docxFileName;

            if (!new File(workingDir + myTemplate).canRead()) {
                throw new RuntimeException("Cannot load template:" + new File(workingDir + myTemplate));
            }

            XComponentLoader xCompLoader = (XComponentLoader) UnoRuntime
                    .queryInterface(com.sun.star.frame.XComponentLoader.class, xDesktop);

            String sUrl = "file:///" + workingDir + myTemplate;

            PropertyValue[] propertyValues = new PropertyValue[0];

            propertyValues = new PropertyValue[1];
            propertyValues[0] = new PropertyValue();
            propertyValues[0].Name = "Hidden";
            propertyValues[0].Value = new Boolean(true);

            XComponent xComp = xCompLoader.loadComponentFromURL(
                    sUrl, "_blank", 0, propertyValues);

            // save as a PDF
            XStorable xStorable = (XStorable) UnoRuntime
                    .queryInterface(XStorable.class, xComp);

            propertyValues = new PropertyValue[2];
            propertyValues[0] = new PropertyValue();
            propertyValues[0].Name = "Overwrite";
            propertyValues[0].Value = new Boolean(true);
            propertyValues[1] = new PropertyValue();
            propertyValues[1].Name = "FilterName";
            propertyValues[1].Value = "writer_pdf_Export";

            // Appending the favoured extension to the origin document name
            String myResult = workingDir + pdfFileName;
            xStorable.storeToURL("file:///" + myResult, propertyValues);

            System.out.println("Saved " + myResult);
            // shutdown
            xComp.dispose();
            xDesktop.terminate();
        }catch (Exception ex){
            ex.printStackTrace();
        }
    }


    public static void main(String[] args) {

        int row = 8;
        int column = 4;
        createDocx();
        String[][] content = new String[row][column];
        for (int i = 0; i < row; i++) {
            for (int j = 0; j < column; j++) {
                content[i][j] = "test" + i + "-" + j;
            }
        }
        Tbl table = createDataTable(content);
        mainDocumentPart.addObject(table);
        //mergeTdCell(table,1,3,3,"测试合并");
        mergeMdCell(table, 3, 5, 1, "测试纵向合并");
        String workDir = "E:/document/";
        String docxFileName = "test.docx";
        save(workDir + docxFileName);
        cleanResource();
        convertDocxToPDF2(workDir,docxFileName,docxFileName.replaceAll("docx","pdf"));

    }


}

上文中注释比较全面,比较好理解,搞了三天的成果。

遗憾

一直没能探索出Libreoffice部署在远程机器,使用ip调用的方法,如果谁知道怎么做,请留言告诉我。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值