完美解决docx4j变量替换的问题
作者:GreenHand2333 发布时间:2021-08-19 21:00:25
标签:docx4j,变量,替换
docx4j变量替换的问题
最近工作上需要自己完成word文档变量替换的问题
把里面的变量给替换成数据库里的值,但是由于在word文档渲染成xml的时候,会通过某些原因把字段放在不同层次的xml标签
上面是docx4j文档说的原因,大概是字体格式不同(我的问题是用了粗体 ${ 和 正常中文是不同格式的),拼写语法问题,编辑顺序。
在StackOverflow 找了很久解决方案,Variableprepare.prepare方法确实测试后能解决部分替换问题,但还是不能满足我的需求。
阅读源码后重新清扫了一下字符串。
测试代码
public static void main(String[] args) throws Exception {
File file = new File("C:\\Users\\scoli\\Desktop\\t.docx");
WordprocessingMLPackage doc = WordprocessingMLPackage.load(new FileInputStream(file));
Docx4jUtils.cleanDocumentPart(doc.getMainDocumentPart());
Map map = new HashMap();
map.put("订单编号", "1");
OutputStream outputStream = new FileOutputStream(new File("C:\\Users\\scoli\\Desktop\\test1.docx"));
MainDocumentPart mainDocumentPart = doc.getMainDocumentPart();
if (!map.isEmpty()) {
// 替换文本内容
mainDocumentPart.variableReplace(map);
}
// 输出word文件
doc.save(outputStream);
outputStream.flush();
}
docx4j版本
<!--docx4j支持 start-->
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j</artifactId>
<version>3.3.6</version>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>3.3.6</version>
</dependency>
<!--docx4j支持 end-->
下面是工具类
package zwy.saas.common.util;
import org.docx4j.XmlUtils;
import org.docx4j.jaxb.Context;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.wml.Document;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.servlet.http.HttpServletResponse;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;
import java.util.Map;
import java.util.regex.Pattern;
/**
* 关于文件操作的工具类
*
* @author kaizen
* @date 2018-10-23 17:21:36
*/
public final class Docx4jUtils {
private static final Logger logger = LoggerFactory.getLogger(Docx4jUtils.class);
/**
* 替换变量并下载word文档
*
* @param inputStream
* @param map
* @param response
* @param fileName
*/
public static void downloadDocUseDoc4j(InputStream inputStream, Map<String, String> map,
HttpServletResponse response, String fileName) {
try {
// 设置响应头
fileName = URLEncoder.encode(fileName, "UTF-8");
response.setContentType("application/octet-stream;charset=UTF-8");
response.setCharacterEncoding("utf-8");
response.setHeader("Content-Disposition", "attachment; filename=" + fileName + ".docx");
response.setHeader("Access-Control-Expose-Headers", "Content-Disposition");
OutputStream outs = response.getOutputStream();
Docx4jUtils.replaceDocUseDoc4j(inputStream,map,outs);
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 替换变量并输出word文档
* @param inputStream
* @param map
* @param outputStream
*/
public static void replaceDocUseDoc4j(InputStream inputStream, Map<String, String> map,
OutputStream outputStream) {
try {
WordprocessingMLPackage doc = WordprocessingMLPackage.load(inputStream);
MainDocumentPart mainDocumentPart = doc.getMainDocumentPart();
if (null != map && !map.isEmpty()) {
// 将${}里的内容结构层次替换为一层
Docx4jUtils .cleanDocumentPart(mainDocumentPart);
// 替换文本内容
mainDocumentPart.variableReplace(map);
}
// 输出word文件
doc.save(outputStream);
outputStream.flush();
} catch (Exception e) {
logger.error(e.getMessage(), e);
}
}
/**
* 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(DocxVariableClearUtils.doCleanDocumentPart(wmlTemplate, Context.jc));
documentPart.setContents(document);
return true;
}
/**
* 清扫 docx4j 模板变量字符,通常以${variable}形式
* <p>
* XXX: 主要在上传模板时处理一下, 后续
*
* @author liliang
* @since 2018-11-07
*/
private static class DocxVariableClearUtils {
/**
* 去任意XML标签
*/
private static final Pattern XML_PATTERN = Pattern.compile("<[^>]*>");
private DocxVariableClearUtils() {
}
/**
* 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;
/**
* 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);
}
}
}
来源:https://blog.csdn.net/qq_35598240/article/details/84439929


猜你喜欢
- Spring-Context的作用spring-context提供应用程序上下文,这是Spring的依赖注入容器,它可能总是在以某种方式使用
- 效果图:后来又出了两篇,也可以看一下Android选择与上传图片之PictureSelector教程Android选择与上传图片之Matis
- Linux下的五种I/O模型1)阻塞I/O(blocking I/O)2)非阻塞I/O (nonblocking I/O)3) I/O复用(
- 一、使用JDK生成WSDL的对象类1、cmd进入JDK的bin文件中执行命令 wsimport -keep -p com.demo.clie
- 一. 依赖管理Ⅰ. 部分dependency导入时为啥不需要指定版本?我们创建项目时添加的依赖并没有帮我们指定版本号<>,那Sp
- private void button2_Click(object sender, EventArgs e) &nbs
- 1. mapper.xml设置resultTyperesultType="com.alibaba.fastjson.JSONObj
- 前言:上午写代码时还好好的,下午不知道怎么回事突然就不显示logcat日志了,觉得很奇怪,于是开始找各种解决办法!现象如图所示,logcat
- 本文实例为大家分享了java查找图中两点之间所有路径的具体代码,基于邻接表,供大家参考,具体内容如下图类:package graph1;im
- java jdbc连接和使用jdbc导入驱动//jar是已经打包好的class文件集,可以引用到其他工程中 //Build Pa
- 前言前一篇文章我们熟悉了HikariCP连接池,也了解到它的性能很高,今天我们讲一下另一款比较受欢迎的连接池:Druid,这是阿里开源的一款
- 我就废话不多说了,大家还是直接看代码吧~ public static void main(String[] args) { &n
- 我们在使用一些开源调度系统(比如:elastic-job等)的时候,对于任务的执行时间通常都是有规律性的,可能是每隔半小时执行一次,或者每天
- 代码案例一:private void button1_Click(object sender, EventArgs e) &n
- 消息的保存路径消息发送端发送消息到 broker 上以后,消息是如何持久化的?数据分片kafka 使用日志文件的方式来保存生产者和发送者的消
- DAO模式是接口的一个典型应用。1. StudenDaoListImpl.java与StudentDaoArrayImpl.java有何不同
- 昨天遇到了点问题解决浪费了一些时间(导致更新内容较少)回顾下问题项目出现Unable to import maven project: Se
- 前言各位精通CRUD的老司机,相信大家在工作中mybatis或者mybatisplus使用的肯定是比较多的,那么大家或多或少都应该对下面的行
- 前言本文主要给大家介绍了关于JDK8新增的原子性操作类LongAdder的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的
- MainActivity如下:package cc.ab;import android.os.Bundle;import android.p