docx4j使用小结
又忙活了一段时间,新项目还在搭,趁着有空总结一下前段时间用过的docx4j。
当时的需求是这样的,客户提供了word的报表模板,我们需要根据查出的数据导出为pdf。
面临的问题是:word模板比较复杂,不是单纯的填空,要根据数据的情况分多行展现。
最后我选择了用docx4j来实现。
百度百科:docx4j是一个Java类库用于创建和操作Microsoft Open XML(Word docx, Powerpoint pptx, 和 Excel xlsx)文件。
需要注意的是,它操作的是.docx、.pptx和.xlsx文件,这些后缀的文件本质上与.doc、.ppt和.xls是不同的。
可以这样理解:docx是基于xml文件来实现的,当你用压缩工作打开一个.docx文档,你就能看到里面的xml了。
这就是为什么docx4j严重依赖JAXB了,因为它要操作的其实是xml文件。
来看一下官方github的demo:
新建一个.docx文件,CreateWordprocessingMLDocument.java
public static void main(String[] args) throws Exception {
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.createPackage();
MainDocumentPart mdp = wordMLPackage.getMainDocumentPart();
// Example 1: add text in Title style
mdp.addStyledParagraphOfText("Title", "Example 1");
// Example 2: add normal paragraph (no explicit style)
mdp.addParagraphOfText("Example 2");
// Example 4: Here is an easier way:
String str = "<w:p xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" ><w:r><w:rPr><w:b /></w:rPr><w:t>Example 4</w:t></w:r></w:p>";
mdp.addObject(org.docx4j.XmlUtils.unmarshalString(str));
// Example 5: Let's add a table
int writableWidthTwips = wordMLPackage.getDocumentModel().getSections().get(0).getPageDimensions().getWritableWidthTwips();
int cols = 3;
int cellWidthTwips = new Double(Math.floor((writableWidthTwips/cols))).intValue();
Tbl tbl = TblFactory.createTable(3, 3, cellWidthTwips);
mdp.addObject(tbl);
// Pretty print the main document part
System.out.println(XmlUtils.marshaltoString(mdp.getJaxbElement(), true, true) );
// save it
String filename = System.getProperty("user.dir") + "/OUT_CreateWordprocessingMLDocument.docx";
wordMLPackage.save(new java.io.File(filename) );
System.out.println("Saved " + filename);
}
example 3 有点烦,就给去掉了。
MainDocumentPart mdp = wordMLPackage.getMainDocumentPart();
这段代码mdp获取的到的,其实就是用压缩工具打开的.docx文档中的document.xml文件的内容, 而我们对mdp操作也就是直接操作document.xml,最后通过document.xml来生成.docx文档。
上面的所有的例子也就是修改document.xml。
大家看example 4 ,可以直接把xml的内容放进document.xml中。
根据我的需求,既然word模板有了,我就解压出来找到document.xml,把里面的内容复制出来再清空。通过代码自己去新增,所以我需要修改复制出来的内容,在需要赋值的地方,把查出来的值放进去。虽然比较笨,但还是保持了word文档的一致性,简直一模一样。一些代码如下:
for (RepairMaterialsPojo material : pojo.getMaterials()) {
String tr = "<w:tr><w:tblPrEx><w:tblBorders><w:top w:color=\"auto\" w:space=\"0\" w:sz=\"12\" w:val=\"single\"/><w:left w:color=\"auto\" w:space=\"0\" w:sz=\"12\" w:val=\"single\"/><w:bottom w:color=\"auto\" w:space=\"0\" w:sz=\"12\" w:val=\"single\"/><w:right w:color=\"auto\" w:space=\"0\" w:sz=\"12\" w:val=\"single\"/><w:insideH w:color=\"auto\" w:space=\"0\" w:sz=\"6\" w:val=\"single\"/><w:insideV w:color=\"auto\" w:space=\"0\" w:sz=\"6\" w:val=\"single\"/></w:tblBorders><w:tblLayout w:type=\"fixed\"/><w:tblCellMar><w:top w:type=\"dxa\" w:w=\"0\"/><w:left w:type=\"dxa\" w:w=\"108\"/><w:bottom w:type=\"dxa\" w:w=\"0\"/><w:right w:type=\"dxa\" w:w=\"108\"/></w:tblCellMar></w:tblPrEx><w:trPr><w:trHeight w:hRule=\"atLeast\" w:val=\"302\"/></w:trPr><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"719\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + index + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"2682\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getMaterialName() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"1433\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getMaterialModel() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"813\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getUnit() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"813\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getQuantity() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"756\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getUnitPrice() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"816\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getTotalCost() + "</w:t></w:r></w:p></w:tc><w:tc><w:tcPr><w:tcW w:type=\"dxa\" w:w=\"968\"/><w:vAlign w:val=\"center\"/></w:tcPr><w:p><w:pPr><w:jc w:val=\"center\"/><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr></w:pPr><w:r><w:rPr><w:rFonts w:hint=\"eastAsia\"/></w:rPr><w:t>" + material
.getRemark() + "</w:t></w:r></w:p></w:tc></w:tr>";
tbl_tr.append(tr);
index++;
}
最后生成pdf比较简单:
OutputStream os = new FileOutputStream(destPath);
Docx4J.toPDF(wordMLPackage, os);
遇到了一个坑,就是生成pdf有点慢,在本地调试接口的时候每次都是响应时间都过了还没好,但放到服务器上就没有问题了。