最近客户要求把业务字段生成一个pdf,包含大量的表格,于是探究了两天版,终于找出一个比较完美的解决方案。本次采用的是docx4j,Libreoffice两个套件,docx4j本身有转PDF的功能,但是转换完成的PDF乱码,我没有找到解决乱码的方法(比较菜,勿喷),然后选择了Libreoffice的方式,期间也是解决了很多bug,然后最终成功
之前在网上看到也有使用openoffice的,我之前也试过,转成pdf格式全变了,尤其是表格,后来发现的兼容性更好的Libreoffice,下面开始安装。
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下调试一定会没法退出程序,这个参数的意思就是无界面运行。新建一个maven工程,pom.xml
:
<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.0modelVersion>
<groupId>net.blf2groupId>
<artifactId>test-docx4jartifactId>
<version>0.1version>
<name>Testdocx4jname>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8project.reporting.outputEncoding>
<java.version>1.8java.version>
<skipTests>trueskipTests>
properties>
<dependencies>
<dependency>
<groupId>org.docx4jgroupId>
<artifactId>docx4jartifactId>
<version>3.3.7version>
dependency>
<dependency>
<groupId>org.docx4jgroupId>
<artifactId>docx4j-export-foartifactId>
<version>3.3.1version>
dependency>
<dependency>
<groupId>org.openofficegroupId>
<artifactId>bootstrap-connectorartifactId>
<version>0.1.1version>
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调用的方法,如果谁知道怎么做,请留言告诉我。