paramMap:{xxx:xxxx}
mainDocumentPart.variableReplace(paramMap);
3、如果含有多个table的话,次使用模板替换的功能的话,
3.1 要知道table在文档中出现的顺序
3.2 要在List<Map>中保证有对应的key值
结果集 [{bs.xm:111,bs.fz:xxx,bs.nmsamt:xxx,bs.ncsamt:xxx},.....]
进行替换时,要调用的方法:
首先要找到table,第二部找到table的模板行,先添加模板行,在进行数据的替换。依次填充即可,最后删除掉模板行,即填充完成。
ps:当要替换的table中要进行一些样式操作,此时我的做法是找到每一个td,进行设置
下面是一些工具方法:
此类是对模板的docx进行模板规范化操作
public class DocxUtils {
* 去任意XML标签
private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>");
* start符号
private static final char PREFIX = '$';
* 中包含
private static final char LEFT_BRACE = '{';
private static final char RIGHT_BRACE = '}';
* 未开始
private static final int NONE_START = -1;
* 未开始
private static final int NONE_START_INDEX = -1;
private static final int PREFIX_STATUS = 1;
* 左括号
private static final int LEFT_BRACE_STATUS = 2;
* 右括号
private static final int RIGHT_BRACE_STATUS = 3;
* cleanDocumentPart
* @param documentPart
public static boolean cleanDocumentPart(MainDocumentPart documentPart) throws Exception {
if (documentPart == null) {
return false;
Document document = documentPart.getContents();
String wmlTemplate =
XmlUtils.marshaltoString(document, true, false, Context.jc);
document = (Document) XmlUtils.unwrap(doCleanDocumentPart(wmlTemplate, Context.jc));
documentPart.setContents(document);
return true;
* @param obj
* @param toSearch
* @return
public 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;
* doCleanDocumentPart
* @param wmlTemplate
* @param jc
* @return
* @throws JAXBException
private static Object doCleanDocumentPart(String wmlTemplate, JAXBContext jc) throws JAXBException {
// 进入变量块位置
int curStatus = NONE_START;
// 开始位置
int keyStartIndex = NONE_START_INDEX;
// 当前位置
int curIndex = 0;
char[] textCharacters = wmlTemplate.toCharArray();
StringBuilder documentBuilder = new StringBuilder(textCharacters.length);
documentBuilder.append(textCharacters);
// 新文档
StringBuilder newDocumentBuilder = new StringBuilder(textCharacters.length);
// 最后一次写位置
int lastWriteIndex = 0;
for (char c : textCharacters) {
switch (c) {
case PREFIX:
// TODO 不管其何状态直接修改指针,这也意味着变量名称里面不能有PREFIX
keyStartIndex = curIndex;
curStatus = PREFIX_STATUS;
break;
case LEFT_BRACE:
if (curStatus == PREFIX_STATUS) {
curStatus = LEFT_BRACE_STATUS;
break;
case RIGHT_BRACE:
if (curStatus == LEFT_BRACE_STATUS) {
// 接上之前的字符
newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex, keyStartIndex));
// 结束位置
int keyEndIndex = curIndex + 1;
// 替换
String rawKey = documentBuilder.substring(keyStartIndex, keyEndIndex);
// 干掉多余标签
String mappingKey = XML_PATTERN.matcher(rawKey).replaceAll("");
/* if (!mappingKey.equals(rawKey)) {
char[] rawKeyChars = rawKey.toCharArray();
// 保留原格式
StringBuilder rawStringBuilder = new StringBuilder(rawKey.length());
// 去掉变量引用字符
for (char rawChar : rawKeyChars) {
if (rawChar == PREFIX || rawChar == LEFT_BRACE || rawChar == RIGHT_BRACE) {
continue;
rawStringBuilder.append(rawChar);
// FIXME 要求变量连在一起
String variable = mappingKey.substring(2, mappingKey.length() - 1);
int variableStart = rawStringBuilder.indexOf(variable);
if (variableStart > 0) {
rawStringBuilder = rawStringBuilder.replace(variableStart, variableStart + variable.length(), mappingKey);
newDocumentBuilder.append(rawStringBuilder.toString());
} else {*/
newDocumentBuilder.append(mappingKey);
lastWriteIndex = keyEndIndex;
curStatus = NONE_START;
keyStartIndex = NONE_START_INDEX;
default:
break;
curIndex++;
// 余部
if (lastWriteIndex < documentBuilder.length()) {
newDocumentBuilder.append(documentBuilder.substring(lastWriteIndex));
return XmlUtils.unmarshalString(newDocumentBuilder.toString(), jc);
//一下为一些设置样式的方法
//设置样式
public static void fillCellData(Tc tc, String fontFamily, boolean isBold) {
ObjectFactory factory = Context.getWmlObjectFactory();
P p = (P) XmlUtils.unwrap(tc.getContent().get(0));
Text t = null;
R run = null;
List<Object> texts = DocxUtils.getAllElementFromObject(p, Text.class);
List<Object> rs = DocxUtils.getAllElementFromObject(p, R.class);
if (StringUtil.isNotEmpty(texts)) {
t = (Text) texts.get(0);
String value = t.getValue();
if (GfrTool.GfrIsEmpty(value)) {
t.setValue("");
} else {
return;
boolean isNewR = false;
if (StringUtil.isNotEmpty(rs)) {
run = (R) rs.get(0);
} else {
//设置表格内容的对齐方式
run = factory.createR();
isNewR = true;
//设置表给内字体样式
run.setRPr(getRpr(fontFamily, isBold));
TcPr tcPr = tc.getTcPr();
BooleanDefaultTrue bdt = factory.createBooleanDefaultTrue();
tcPr.setNoWrap(bdt);
if (isNewR) {
run.getContent().add(t);
p.getContent().add(run);
* 设置缩进
* @param tc
* @param rowLevel
public static void setIdent(Tc tc, Object rowLevel) {
ObjectFactory factory = Context.getWmlObjectFactory();
P p = (P) XmlUtils.unwrap(tc.getContent().get(0));
PPr pPr = p.getPPr();
PPrBase.Ind ind = pPr.getInd();
Boolean isNew = false;
if (StringUtil.isEmpty(ind)) {
ind = new PPrBase.Ind();
isNew = true;
PPrBase.Spacing spacing = pPr.getSpacing();
if (StringUtil.isEmpty(spacing)) {
spacing = new PPrBase.Spacing();
isNew = true;
Integer level = Integer.valueOf(StringUtil.nvl(rowLevel, "1"));
String ident = String.valueOf((level - 1) * 150);
spacing.setBefore(new BigInteger(ident));
ind.setFirstLine(new BigInteger(ident));
ind.setFirstLineChars(new BigInteger(ident));
if (isNew) {
pPr.setInd(ind);
pPr.setSpacing(spacing);
* 设置tc边框
* @param tc
* @param dataMap 查询数据
public static void setTcBorder(Tc tc, Map<String, Object> dataMap,Boolean isFirst) {
if (!GfrTool.valuesNotEmpty(tc, dataMap)) {
return;
List<String> bottoms = getBorderData(dataMap,isFirst);
if (!StringUtil.isNotEmpty(bottoms)) {
return;
TcPr tcPr = tc.getTcPr();
TcPrInner.TcBorders tcBorders = tcPr.getTcBorders();
Boolean isNew = false;
if (StringUtil.isEmpty(tcBorders)) {
tcBorders = new TcPrInner.TcBorders();
isNew = true;
CTBorder border = getBorder();
for (String flag : bottoms) {
flag = StringUtil.nvl(flag, BORDER_BOTTOM);
switch (flag) {
case BORDER_BOTTOM:
tcBorders.setBottom(border);
break;
case BORDER_LEFT:
tcBorders.setLeft(border);
break;
case BORDER_TOP:
tcBorders.setTop(border);
break;
case BORDER_RIGHT:
tcBorders.setRight(border);
break;
if (isNew) {
tcPr.setTcBorders(tcBorders);
* 得到CTBorder
* @return
public static CTBorder getBorder() {
CTBorder ctBorder = new CTBorder();
ctBorder.setSz(new BigInteger("4"));
ctBorder.setColor("black");
//ctBorder.setSpace(new BigInteger("10"));
ctBorder.setVal(STBorder.SINGLE);
return ctBorder;
* 获得设置边框的单元格的方向集合
* @param dataMap
* @return
private static List<String> getBorderData(Map<String, Object> dataMap,Boolean isFirst) {
if (StringUtil.isEmpty(dataMap)) {
return null;
List<String> vars = new ArrayList<>();
BORDER_LIST.stream().forEach(v -> {
Object o = dataMap.get(v);
if (!StringUtil.isEmpty(o) && Integer.valueOf(o.toString()) == Integer.valueOf(BT.toString())) {
vars.add(v);
if(isFirst){
if(!vars.contains(BORDER_TOP)){
vars.add(BORDER_TOP);
return vars;
* 设置样式
* @param fontFamily
* @param isBold
* @return
private static RPr getRpr(String fontFamily, boolean isBold) {
ObjectFactory factory = Context.getWmlObjectFactory();
RPr rPr = factory.createRPr();
RFonts rf = new RFonts();
rf.setAscii(fontFamily);
rf.setHAnsi(fontFamily);
rPr.setRFonts(rf);
BooleanDefaultTrue bdt = Context.getWmlObjectFactory().createBooleanDefaultTrue();
rPr.setBCs(bdt);
if (isBold) {
rPr.setB(bdt);
return rPr;
* @Description: 跨列合并
public void mergeCellsHorizontal(Tbl tbl, int row, int fromCell, int toCell) {
if (row < 0 || fromCell < 0 || toCell < 0) {
return;
List<Object> trs = DocxUtils.getAllElementFromObject(tbl,Tr.class);
if (row > trs.size()) {
return;
Tr tr = (Tr) trs.get(row);
List<Object> tcList = DocxUtils.getAllElementFromObject(tr,Tc.class);
for (int cellIndex = fromCell, len = Math
.min(tcList.size() - 1, toCell); cellIndex <= len; cellIndex++) {
Tc tc = (Tc) tcList.get(cellIndex);
TcPr tcPr = tc.getTcPr();
TcPrInner.HMerge hMerge = tcPr.getHMerge();
if (hMerge == null) {
hMerge = new TcPrInner.HMerge();
tcPr.setHMerge(hMerge);
if (cellIndex == fromCell) {
hMerge.setVal("restart");
} else {
hMerge.setVal("continue");
* @Description: 跨列合并
public static void mergeCellsHorizontal(Tr tr, int fromCell, int toCell) {
List<Object> tcList = DocxUtils.getAllElementFromObject(tr,Tc.class);
for (int cellIndex = fromCell, len = Math
.min(tcList.size() - 1, toCell); cellIndex <= len; cellIndex++) {
Tc tc = (Tc) tcList.get(cellIndex);
TcPr tcPr = tc.getTcPr();
TcPrInner.HMerge hMerge = tcPr.getHMerge();
if (hMerge == null) {
hMerge = new TcPrInner.HMerge();
tcPr.setHMerge(hMerge);
if (cellIndex == fromCell) {
hMerge.setVal("restart");
} else {
hMerge.setVal("continue");
* @Description: 跨行合并
public static void mergeCellsVertically(Tbl tbl, int col, int fromRow, int toRow) {
if (col < 0 || fromRow < 0 || toRow < 0) {
return;
for (int rowIndex = fromRow; rowIndex <= toRow; rowIndex++) {
Tc tc = getTc(tbl, rowIndex, col);
if (tc == null) {
break;
TcPr tcPr = tc.getTcPr();
TcPrInner.VMerge vMerge = tcPr.getVMerge();
if (vMerge == null) {
vMerge = new TcPrInner.VMerge();
tcPr.setVMerge(vMerge);
if (rowIndex == fromRow) {
vMerge.setVal("restart");
} else {
vMerge.setVal("continue");
* @Description:得到指定位置的表格
public static Tc getTc(Tbl tbl, int row, int cell) {
if (row < 0 || cell < 0) {
return null;
List<Object> trList = DocxUtils.getAllElementFromObject(tbl,Tr.class);
if (row >= trList.size()) {
return null;
List<Object> tcList = DocxUtils.getAllElementFromObject((Tr)trList.get(row),Tc.class);
if (cell >= tcList.size()) {
return null;
return (Tc)tcList.get(cell);
用到此处,基本已经满足本人的需求,因此目前只用到此处,未做更深入的研究
1、本次主要使用docx4j对docx文件进行文本、和表格的数据替换功能。2、替换的参数写法:${xxx}。要进行替换的时候用paramMap:{xxx:xxxx}mainDocumentPart.variableReplace(paramMap);3、如果含有多个table的话,次使用模板替换的功能的话, 3.1 要知道table在文档中出现的顺序 3.2 要...
https://www.cnblogs.com/qlqwjy/p/9866484.html
https://my.oschina.net/haiyangyiba/blog/2246089
https://github.com/plutext/docx4j/
1.引入docx4j
本案例使用maven仓库引入jar包
<dependency>
<groupId>org.docx4j</groupId&g
*请注意,2.x与1.x完全不同,一切都在不断变化。
docx4js是一个javascript docx解析器。
最初的目标是支持docx,pptx和xlsx,但这是一项巨大的工作,因此到目前为止,我仅限于docx。
为了性能起见,实现不保留已解析的结构。 它仅遍历docx内容,并标识docx模型,然后一个个地调用通过的访问者。 无论内容和样式如何,都具有相同的策略。 这种方法可以用更少的内存完成更多的工作。
docx中有很多信息,但是客户端应用程序通常只关心其中的一部分,例如仅内容,仅结构,某些样式或某些属性。 客户端应用程序能够按TYPE处理特殊的单词模型。
单词模型的属性通常会影响样式,但是我不了解所有样式,因此我懒于仅迭代每个属性和一些未知的子元素,因此客户端应用程序可以捕获您所知道的所有信息。
几个月前,我需要创建一个包含许多表和段落的动态Word文档。 过去,我曾使用POI来实现此目的,但是我发现它很难使用,并且在创建更复杂的文档时对我来说效果不佳。 因此,对于这个项目,经过一番搜索,我决定使用docx4j 。 Docx4j,根据他们的网站是:
“ docx4j是一个Java库,用于创建和处理Microsoft Open XML(Word docx,Powerpoint p...
<!-- https://mvnrepository.com/artifact/org.docx4j/docx4j -->
<dependency>
<groupId>org.docx4j</groupId>
主要是想要用此功插件操作docx,主要的操作就是操作段落等信息,另外,也想实现替换docx的内容,实现根据模板动态生成内容的效果,也想用此插件实现docx转换pdf。
word的格式其实可以用xml来表现,docx4j也应该是基于xml来操作docx文档的。xml就比较好理解了。我们都是通过doc树的形式操作docx,只不过对于docx4j来说根节点是一个package,我们可以从根节点...
docx4j 简介、中文文档、中英对照文档 下载;docx4j、org.docx4j、中文文档、中英对照文档、下载、包含jar包、原API文档、源代码、Maven依赖信息文件、翻译后的API文档、docx4j、中英对照文档、jar包、java;
docx4j-3.3.5.jar
docx4j是一个开源的Java库,用于创建和操作Microsoft Word文档(.docx文件)。它提供了一组API,可以方便地创建、读取、修改和生成.docx文件。在Maven项目中使用docx4j时,需要在pom.xml文件中添加以下依赖:
```xml
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>8.2.5</version>
</dependency>
其中,版本号可以根据需要进行调整。除此之外,还可以添加其他可选依赖,如PDF转换、HTML导出等功能:
```xml
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-ReferenceImpl</artifactId>
<version>8.2.5</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-ImportXHTML</artifactId>
<version>8.2.5</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>8.2.5</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-pdf</artifactId>
<version>8.2.5</version>
</dependency>
这些可选依赖可以根据具体需求进行选择。