带有docx4j的Java Word(.docx)文档

几个月前,我需要创建一个包含许多表和段落的动态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也很容易做到这一点。
我们将通过以下步骤进行操作:

  1. 从模板中找到要替换的段落
  2. 将输入文本分成单独的行
  3. 对于每一行,根据模板中的段落创建一个新段落
  4. 删除原始段落

我们应该已经拥有的辅助方法不应该太难了。

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代码的新行,如下所示:

  1. 查找包含我们的关键字之一的表
  2. 复制用作行模板的行
  3. 对于每行数据,根据行模板向表中添加一行
  4. 删除原始模板行

与我们在段落中显示的方法相同。 首先,让我们看一下如何提供替换数据。 对于此示例,我仅提供了一组哈希图,其中包含要替换的占位符的名称和要替换为其的值。 我还提供了可在表格行中找到的替换令牌。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/372155.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql中distinct关键字,MySQL关键字Distinct的详细介绍

DDLPrepare SQL&#xff1a;?Prepare Data&#xff1a;?查询数据如下图所示&#xff1a;第一种情况&#xff0c;使用Distinct关键字&#xff0c;查询单列数据&#xff0c;如下图所示&#xff1a;结果&#xff1a;对 name 字段进行去重处理&#xff0c;符合预期期望&#xff0…

(二)windows下安装PHPCMS V9

一、准备工作 搭建环境 &#xff1a;参考:Windows下搭建PHP开发环境及相关注意事项PHPCMS V9 &#xff1a;下载适合自己 PHPCMS V9 版本到本地或服务器&#xff0c;下载地址&#xff1a;http://www.phpcms.cn/html/download/说明&#xff1a;官方提供了 2 种不同的编码。包括 G…

JavaFX 2.0布局窗格– HBox和VBox

如果要对JavaFX 2.0中所有不同的布局窗格进行概述&#xff0c;或者想了解有关它们的一些基本知识&#xff0c;请参阅我以前的文章《 JavaFX 2.0中的布局窗格》 。 布局窗格HBox和VBox绝对是JavaFX 2.0中最基本的布局容器。 如您所知&#xff0c;它们的用途是将所有子级布置在一…

单片机串行通信全解析

1.什么是串行通信&#xff1f; 串行通信&#xff08;英语&#xff1a;Serial communication&#xff09;是指在计算机总线或其他数据通道上&#xff0c;每次传输一个位元数据&#xff0c;并连续进行以上单次过程的通信方式。与之对应的是并行通信&#xff0c;它在串行端口上通过…

java type 类型,java中的泛型类型与Type接口

假设我们定义了一个Room的类&#xff0c;表示一个房间public classRoom(){}由于我们建造好房间是&#xff0c;不知道房间以后的用途&#xff0c;他可能用来住人&#xff0c;也有可能用来放货物&#xff0c;因此需要用到泛型。但是我们可能想获取Room这个房间里面进来的的东西的…

vray学习笔记(3)-多维子材质是个什么东西

多维子材质是个什么东西&#xff1f;为什么出现这个概念&#xff1f; 在3dsmax官方网站&#xff0c;我们可以看到它的定义&#xff1a; The Multi/Sub-Object material lets you assign different materials at the sub-object level of your geometry. 意思是多维子材质这个概…

Hello JavaFX 2.0:命令行介绍

我从博客文章Hello JavaFX 2.0&#xff1a;NetBeans 7.1 beta的介绍中&#xff0c;从NetBeans 7.1 beta的角度看了一个无处不在的Hello World示例的简单JavaFX版本。 在本文中&#xff0c;我将介绍仅使用命令行工具通过JavaFX实现的Hello World版本。 JavaFX 2.0 API文档包括ja…

oracle列分区,Oracle数据库分区--实例

分区表通过对分区列进行判断&#xff0c;把满足不同条件的分区列对应的记录保存在不同的分区中。一、何为分区表什么情况下会使用分区表&#xff1f;表中已有大量数据&#xff0c;或预计到表中将会保存大量的数据可以按照预期(月份、区域、dml)对表中的数据执行查询和更新什么是…

ZK实际应用:MVVM –与ZK客户端API一起使用

在之前的文章中&#xff0c;我们使用ZK的MVVM实现了以下功能&#xff1a; 将数据加载到表中 使用表单绑定保存数据 删除条目并以编程方式更新视图 ZK MVVM和ZK MVC实现方式之间的主要区别是&#xff0c;我们不直接在controller&#xff08;ViewModel&#xff09;类中访问和操…

终极JPA查询和技巧列表–第1部分

我们可以在Internet上找到一些JPA“如何做”&#xff0c;在本博客的此处&#xff0c;教您如何使用JPA执行多项任务。 通常&#xff0c;我看到有人问有关使用JPA进行查询的问题。 通常&#xff0c;为了回答此类问题&#xff0c;提供了几个链接&#xff0c;以尝试找到该问题的解决…

Spring集成–第2节–更多世界

这是Spring Integration Session 1的后续活动 第一部分是使用Spring Integration的简单Hello World应用程序。 我想通过考虑其他一些方案来进一步介绍它。 因此&#xff0c;对Hello World应用程序的第一个更改是添加网关组件。 要快速重新访问较早的测试程序&#xff0c;请执行…

oracle 会话实例,返璞归真:Oracle实例级别和会话级别的参数设置辨析

杨廷琨(yangtingkun)云和恩墨 CTO高级咨询顾问&#xff0c;Oracle ACE 总监&#xff0c;ITPUB Oracle 数据库管理版版主参数文件是Oracle数据库文件中级别最低&#xff0c;也是最基本的文件&#xff0c;但是也是数据库实例启动第一个涉及的文件。如果参数文件缺失或者某些参数设…

在多节点集群中运行Cassandra

这篇文章收集了我在多节点中设置Apache Cassandra集群的步骤。 在设置集群时&#xff0c;我已经参考了Cassandra Wiki和Datastax文档。 详细介绍了以下过程&#xff0c;分享了我建立群集的经验。 设置第一个节点 添加其他节点 监视集群– nodetool &#xff0c; jConsole &am…

Oracle 添加 scott 示例用户

学习SQL有一段时间了&#xff0c;但是也忘记的差不多了&#xff0c;今天有赶紧复习复习&#xff0c;然后发现一个问题&#xff0c;为啥之前看的视频教程&#xff0c;马士兵用的Oracle有scott用户和那些表格&#xff0c;而我的没有&#xff1f;难道是Oracle取消了&#xff1f;然…

win8oracle10g安装报错,Win8电脑安装Oracle 10g提示程序异常终止的解决方法

有win8系统用户反映说在安装Oracle 10g的时候&#xff0c;选择高级安装之后&#xff0c;就弹出一个窗口&#xff0c;提示程序异常终止&#xff0c;发生内部错误&#xff0c;导致Oracle 10g安装失败&#xff0c;该怎么解决这样的问题呢&#xff1f;下面随小编一起来看看Win8电脑…

<avatar: frontiers of pandora>技术overview

https://www.eurogamer.net/digitalfoundry-2023-avatar-frontiers-of-pandora-and-snowdrop-the-big-developer-tech-interview https://www.youtube.com/watch?vLRI_qgVSwMY&t394s 主要来自euro gamer上digital foundry对于avatar的开发团队Massive工作室的采访&#xf…

iOS 启动连续闪退保护方案

版权声明&#xff1a;本文由刘笑江原创文章&#xff0c;转载请注明出处: 文章原文链接&#xff1a;https://www.qcloud.com/community/article/79 来源&#xff1a;腾云阁 https://www.qcloud.com/community 一.引言 “如果某个实体表现出以下任何一种特性&#xff0c;它就具备…

实战Java内存泄漏问题分析 -- hazelcast2.0.3使用时内存泄漏 -- 2

hazelcast 提供了3中方法调用startCleanup:第一种是在ConcuurentMapManager的构造函数中&#xff0c;通过调用node的executorManager中的ScheduledExecutorService来创建每秒运行一次cleanup操作的线程&#xff08;代码例如以下&#xff09;。因为这是ConcuurentMapManager构造…

@SuppressLint(NewApi)和@TargetApi()的区别

转自&#xff1a;http://blog.csdn.NET/wbshuang09/article/details/44920549在Android代码中&#xff0c;我们有时会使用比我们在AndroidManifest中设置的android:minSdkVersion版本更高的方法&#xff0c;此时编译器会提示警告&#xff0c;解决方法是在方法上加上SuppressLin…

零基础自学编程前需要知道的知识

你是否适合编程?学习编程后能做什么?如何选择编程语言?有哪些免费的线上学习网站推荐?今天这篇好文将那些自学编程前需要了解和思考的问题都记录下来&#xff0c;希望能给那些刚刚开始或正准备自学编程的朋友们带去一些启发。 你是否适合自学编程 自学编程会是一个漫长而艰…