docx4j优势:
免费开源可商用,服务器无需安装office软件等
docx4j劣势:
性能效率相对于其他框架而言较差
复杂的word排版内容可能无法转换,对格式要求较高
适用场景:
对性能效率要求不高,word内容排版简单,后续无很强的应用需求
项目框架环境:JDK1.8、SpringBoot2.3.1.RELEASE
maven
<docx4j.version>8.2.9</docx4j.version>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-JAXB-Internal</artifactId>
<version>${docx4j.version}</version>
</dependency>
<dependency>
<groupId>org.docx4j</groupId>
<artifactId>docx4j-export-fo</artifactId>
<version>${docx4j.version}</version>
</dependency>
在word中插入${xxx},即可替换xxx内容
对应传递的map的key-val:xxx:替换内容
注:${ } 符号不可省略
import lombok.extern.slf4j.Slf4j;
import org.docx4j.Docx4J;
import org.docx4j.XmlUtils;
import org.docx4j.convert.out.FOSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFonts;
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.springframework.util.CollectionUtils;
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;
@Slf4j
public final class Docx4jUtils {
/**
* 替换变量并输出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 (!CollectionUtils.isEmpty(map)) {
Docx4jUtils.cleanDocumentPart(mainDocumentPart);
mainDocumentPart.variableReplace(map);
}
// 输出word文件
doc.save(outputStream);
outputStream.flush();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
/**
* 替换变量并输出PDF文档
* @param inputStream
* @param map
* @param outputStream
* */
public static void replaceDocOutputPDF(InputStream inputStream, Map<String, String> map,
OutputStream outputStream) {
try {
WordprocessingMLPackage doc = WordprocessingMLPackage.load(inputStream);
MainDocumentPart mainDocumentPart = doc.getMainDocumentPart();
if (!CollectionUtils.isEmpty(map)) {
Docx4jUtils.cleanDocumentPart(mainDocumentPart);
mainDocumentPart.variableReplace(map);
}
setFontMapper(doc);
FOSettings foSettings = Docx4J.createFOSettings();
foSettings.setWmlPackage(doc);
Docx4J.toFO(foSettings, outputStream, Docx4J.FLAG_EXPORT_PREFER_XSL);
outputStream.flush();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
private static void setFontMapper(WordprocessingMLPackage mlPackage) throws Exception {
Mapper fontMapper = new IdentityPlusMapper();
fontMapper.put("隶书", PhysicalFonts.get("LiSu"));
fontMapper.put("宋体", PhysicalFonts.get("SimSun"));
fontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
fontMapper.put("黑体", PhysicalFonts.get("SimHei"));
fontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
fontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
fontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
fontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
fontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
fontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
fontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
fontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
fontMapper.put("等线", PhysicalFonts.get("SimSun"));
fontMapper.put("等线 Light", PhysicalFonts.get("SimSun"));
fontMapper.put("华文琥珀", PhysicalFonts.get("STHupo"));
fontMapper.put("华文隶书", PhysicalFonts.get("STLiti"));
fontMapper.put("华文新魏", PhysicalFonts.get("STXinwei"));
fontMapper.put("华文彩云", PhysicalFonts.get("STCaiyun"));
fontMapper.put("方正姚体", PhysicalFonts.get("FZYaoti"));
fontMapper.put("方正舒体", PhysicalFonts.get("FZShuTi"));
fontMapper.put("华文细黑", PhysicalFonts.get("STXihei"));
fontMapper.put("宋体扩展",PhysicalFonts.get("simsun-extB"));
fontMapper.put("仿宋_GB2312",PhysicalFonts.get("FangSong_GB2312"));
fontMapper.put("新細明體",PhysicalFonts.get("SimSun"));
//解决宋体(正文)和宋体(标题)的乱码问题
PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
mlPackage.setFontMapper(fontMapper);
}
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;
}
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);
}
}
}