Signature

docx4j使用小结

Mar 31, 2016

又忙活了一段时间,新项目还在搭,趁着有空总结一下前段时间用过的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有点慢,在本地调试接口的时候每次都是响应时间都过了还没好,但放到服务器上就没有问题了。