原文链接:Create complex Word (.docx) documents programatically with docx4j
原文作者:jos.dirksen
发表日期:2012年2月7日
两个月前,我需要用一些表格和段落创建动态的Word文档。过去我使用过POI做这些事情,但我发现它非常难用并且在我创建更加复杂的文档时它总不能很好地工作。所以在一番四处搜索之后,对于这个项目我决定使用docx4j。
根据官方网站的说法,Docx4j是一个:
"docx4j is a Java library for creating and manipulating Microsoft Open XML (Word docx, Powerpoint pptx, and Excel xlsx) files.
It is similar to Microsoft's OpenXML SDK, but for Java. "
在这篇文章中,我会向你展示几个可以用于生成word文档内容的示例,更具体地说,我们会看一下下面的两个例子:
例如,一个非常基本的模版看起像这样:
在这篇文章中会向你展示如何填充这个模版最终得到这个:
首先,我们创建一个可用作模版的简单的word文档。对于此只需打开Word,创建新文档然后保存为template.docx,这就是我们将要用于添加内容的word文档。我们需要做的第一件事是使用docx4j将这个文档加载进来,你可以使用下面的几行代码做这件事:
private WordprocessingMLPackage getTemplate(String name) throws Docx4JException, FileNotFoundException { WordprocessingMLPackage template = WordprocessingMLPackage.load(new FileInputStream(new File(name))); return template; }
private static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) { List<Object> result = new ArrayList<Object>(); if (obj instanceof JAXBElement) obj = ((JAXBElement<?>) obj).getValue(); if (obj.getClass().equals(toSearch)) result.add(obj); else if (obj instanceof ContentAccessor) { List<?> children = ((ContentAccessor) obj).getContent(); for (Object child : children) { result.addAll(getAllElementFromObject(child, toSearch)); } } return result; }
private void replacePlaceholder(WordprocessingMLPackage template, String name, String placeholder ) { List<Object> texts = getAllElementFromObject(template.getMainDocumentPart(), Text.class); for (Object text : texts) { Text textElement = (Text) text; if (textElement.getValue().equals(placeholder)) { textElement.setValue(name); } } }
private void writeDocxToStream(WordprocessingMLPackage template, String target) throws IOException, Docx4JException { File f = new File(target); template.save(f); }
按这种方式,我们也可以向word文档添加更加复杂的内容,确定如何添加特定内容最简单的方式就是查看word文档的XML源码,这会告诉你需要什么样的包装及Word如何编排XML。在下一个例子中我们会看一下怎样添加一个段落。
你可能想知道为什么我们需要添加段落?我们已经可以添加文本,难道段落不就是一大段的文本吗?好吧,既是也不是,一个段落确实看起来像是一大段文本,但你需要考虑的是换行符,如果你像前面一样添加一个Text元素并且在文本中添加换行符,它们并不会出现,当你想要换行符时,你就需要创建一个新的段落。然而,幸运的是这对于Docx4j来说也非常地容易。
做这个需要下面的几步:
private void replaceParagraph(String placeholder, String textToAdd, WordprocessingMLPackage template, ContentAccessor addTo) { // 1. get the paragraph List<Object> paragraphs = getAllElementFromObject(template.getMainDocumentPart(), P.class); P toReplace = null; for (Object p : paragraphs) { List<Object> texts = getAllElementFromObject(p, Text.class); for (Object t : texts) { Text content = (Text) t; if (content.getValue().equals(placeholder)) { toReplace = (P) p; break; } } } // we now have the paragraph that contains our placeholder: toReplace // 2. split into seperate lines String as[] = StringUtils.splitPreserveAllTokens(textToAdd, '\n'); for (int i = 0; i < as.length; i++) { String ptext = as[i]; // 3. copy the found paragraph to keep styling correct P copy = (P) XmlUtils.deepCopy(toReplace); // replace the text elements from the copy List<?> texts = getAllElementFromObject(copy, Text.class); if (texts.size() > 0) { Text textToReplace = (Text) texts.get(0); textToReplace.setValue(ptext); } // add the paragraph to the document addTo.getContent().add(copy); } // 4. remove the original one ((ContentAccessor)toReplace.getParent()).getContent().remove(toReplace); }
String placeholder = "SJ_EX1"; String toAdd = "jos\ndirksen"; replaceParagraph(placeholder, toAdd, template, template.getMainDocumentPart());
我准备展示的最后一个例子是如何向一个word模版添加表格,一个更适合的表述应该是,如何填充word模版中预定义的表格。就像我们对文本和段落所做的一样,将要替换占位符。为了本例要在你的word文档中添加一个简单的表格(你可以设置喜欢的样式),在表格中添加一个“仿制行”(原文:dummy row,傀儡行?假的行?不知道怎样翻译,意思就是模版行)作为内容的模版。在代码中我们将要查找到该行,复制它,并且在Java代码中使用新行替换内容,如下:
Map<String,String> repl1 = new HashMap<String, String>(); repl1.put("SJ_FUNCTION", "function1"); repl1.put("SJ_DESC", "desc1"); repl1.put("SJ_PERIOD", "period1"); Map<String,String> repl2 = new HashMap<String, String>(); repl2.put("SJ_FUNCTION", "function2"); repl2.put("SJ_DESC", "desc2"); repl2.put("SJ_PERIOD", "period2"); Map<String,String> repl3 = new HashMap<String, String>(); repl3.put("SJ_FUNCTION", "function3"); repl3.put("SJ_DESC", "desc3"); repl3.put("SJ_PERIOD", "period3"); replaceTable(new String[]{"SJ_FUNCTION","SJ_DESC","SJ_PERIOD"}, Arrays.asList(repl1,repl2,repl3), template);
private void replaceTable(String[] placeholders, List<Map<String, String>> textToAdd, WordprocessingMLPackage template) throws Docx4JException, JAXBException { List<Object> tables = getAllElementFromObject(template.getMainDocumentPart(), Tbl.class); // 1. find the table Tbl tempTable = getTemplateTable(tables, placeholders[0]); List<Object> rows = getAllElementFromObject(tempTable, Tr.class); // first row is header, second row is content if (rows.size() == 2) { // this is our template row Tr templateRow = (Tr) rows.get(1); for (Map<String, String> replacements : textToAdd) { // 2 and 3 are done in this method addRowToTable(tempTable, templateRow, replacements); } // 4. remove the template row tempTable.getContent().remove(templateRow); } }
private Tbl getTemplateTable(List<Object> tables, String templateKey) throws Docx4JException, JAXBException { for (Iterator<Object> iterator = tables.iterator(); iterator.hasNext();) { Object tbl = iterator.next(); List<?> textElements = getAllElementFromObject(tbl, Text.class); for (Object text : textElements) { Text textElement = (Text) text; if (textElement.getValue() != null && textElement.getValue().equals(templateKey)) return (Tbl) tbl; } } return null; }
private static void addRowToTable(Tbl reviewtable, Tr templateRow, Map<String, String> replacements) { Tr workingRow = (Tr) XmlUtils.deepCopy(templateRow); List<?> textElements = getAllElementFromObject(workingRow, Text.class); for (Object object : textElements) { Text text = (Text) object; String replacementValue = (String) replacements.get(text.getValue()); if (replacementValue != null) text.setValue(replacementValue); } reviewtable.getContent().add(workingRow); }
这篇文章就到这里,使用段落和表格你可以创建很多不同风格的文档,而且这与通常生成的文档风格能很好地匹配。相同的方式也适用于向word文档中添加其它类型的内容。