幾個月前,我需要創建一個包含許多表和段落的動態Word文檔。 過去,我曾使用POI來實現此目的,但是我發現它很難使用,并且在創建更復雜的文檔時對我來說效果不佳。 因此,對于這個項目,經過一番搜索,我決定使用docx4j 。 Docx4j,根據他們的網站是:
“ docx4j是一個Java庫,用于創建和處理Microsoft Open XML(Word docx,Powerpoint pptx和Excel xlsx)文件。
它類似于Microsoft的OpenXML SDK,但適用于Java。 ”
在本文中,我將向您展示幾個示例,您可以使用這些示例來生成Word文檔的內容。 更具體地說,我們將看以下兩個示例:
- 加載模板Word文檔以添加內容并另存為新文檔
- 將段落添加到此模板文檔
- 將表添加到此模板文檔
這里的一般方法是首先創建一個Word文檔,其中包含最終文檔的布局和主要樣式。 在本文檔中,您將需要添加占位符(簡單字符串),我們將使用這些占位符來搜索并替換為真實內容。
例如,一個非常基本的模板如下所示:
在本文中,我們將向您展示如何填寫此內容,以便獲得:
加載模板Word文檔以添加內容并另存為新文檔
首先是第一件事。 讓我們創建一個簡單的Word文檔,將其用作模板。 為此,只需打開Word,創建一個新文檔并將其另存為template.docx。 這是我們用來向其添加內容的單詞模板。 我們需要做的第一件事是用docx4j加載該文檔。 您可以使用以下一段Java代碼:
private WordprocessingMLPackage getTemplate(String name) throws Docx4JException, FileNotFoundException {WordprocessingMLPackage template = WordprocessingMLPackage.load(new FileInputStream(new File(name)));return template;}
這將返回一個Java對象,該對象表示完整的(此時)空文檔。 現在,我們可以使用Docx4J API在此Word文檔中添加,刪除和修改內容。 Docx4J有許多幫助程序類,可用于遍歷此文檔。 我確實寫了一些幫助程序,盡管它們確實使查找特定的占位符并將其替換為實際內容變得非常容易。 讓我們看看其中之一。 該操作是對幾個JAXB操作的包裝,使您可以搜索特定元素及其所有子元素來查找某個類。 例如,您可以使用它來獲取文檔中的所有表,表中的所有行等等。
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;}
沒什么復雜的,但真的很有幫助。 讓我們看看如何使用此操作。 對于此示例,我們將使用不同的值替換一個簡單的文本占位符。 例如,這是您用來動態設置文檔標題的內容。 不過,首先,在您創建的Word模板中添加一個自定義占位符。 我將為此使用SJ_EX1。 我們將用我們的名字替換這個值。 docx4j中的基本文本元素由org.docx4j.wml.Text類表示。 要替換此簡單的占位符,我們要做的就是調用此方法:
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);}}}
這將查找文檔中的所有Text元素,并將匹配的元素替換為我們指定的值。 現在,我們要做的就是將文檔寫回到文件中。
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 paragraphList<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 linesString 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 correctP copy = (P) XmlUtils.deepCopy(toReplace);// replace the text elements from the copyList texts = getAllElementFromObject(copy, Text.class);if (texts.size() > 0) {Text textToReplace = (Text) texts.get(0);textToReplace.setValue(ptext);}// add the paragraph to the documentaddTo.getContent().add(copy);}// 4. remove the original one((ContentAccessor)toReplace.getParent()).getContent().remove(toReplace);}
在此方法中,我們用提供的文本替換段落的內容,然后將新段落替換為用addTo指定的參數。
String placeholder = "SJ_EX1";String toAdd = "jos\ndirksen";replaceParagraph(placeholder, toAdd, template, template.getMainDocumentPart());
如果您在Word模板中使用更多內容來運行此程序,則會注意到這些段落將出現在文檔的底部。 原因是將段落添加回了主文檔。 如果您希望將段落添加到文檔中的特定位置(通常需要這樣做),則可以將其包裝在1×1無邊界表格中。 該表被視為段落的父級,可以在此處添加新段落。
將表添加到此模板文檔
我想展示的最后一個示例是如何向單詞模板添加表格。 實際上,更好的描述是如何在Word模板中填充預定義的表格。 就像我們對簡單的文本和段落所做的一樣,我們將替換占位符。 對于此示例,向您的Word文檔中添加一個簡單的表格(您可以隨意設置樣式)。 向此表添加1個啞行,用作內容模板。 在代碼中,我們將查找該行,將其復制,并將內容替換為來自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);
現在,這個replaceTable方法是什么樣的。
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 tableTbl tempTable = getTemplateTable(tables, placeholders[0]);List<Object> rows = getAllElementFromObject(tempTable, Tr.class);// first row is header, second row is contentif (rows.size() == 2) {// this is our template rowTr templateRow = (Tr) rows.get(1);for (Map<String, String> replacements : textToAdd) {// 2 and 3 are done in this methodaddRowToTable(tempTable, templateRow, replacements);}// 4. remove the template rowtempTable.getContent().remove(templateRow);}}
此方法查找表,獲取第一行,并為每個提供的地圖在表中添加新行。 返回之前,它將刪除模板行。 此方法使用兩個幫助器:addRowToTable和getTemplateTable。 我們首先來看最后一個:
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;}
此函數只是查看表是否包含我們的占位符之一。 如果是這樣,則返回該表。 addRowToTable操作也非常簡單。
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文檔中填寫套利表,同時保留表的布局和樣式。
到本文為止。 使用段落和表格,您可以創建許多不同類型的文檔,這與最常生成的文檔類型非常匹配。 但是,也可以使用這種方法將其他類型的內容添加到Word文檔中。
參考:來自Smart Java博客的JCG合作伙伴 Jos Dirksen 使用docx4j以編程方式創建復雜的Word(.docx)文檔 。
翻譯自: https://www.javacodegeeks.com/2012/07/java-word-docx-documents-with-docx4j.html