背景:最近要生成word文档,刚开始考虑了POI,但是POI插入图片有bug,而且比较难用,Docx4J比POI容易一些而且功能更强大,遂打算将Docx4J用到项目中去。更多信息见这里
使用准备
- 在gradle中引入依赖
"org.docx4j:docx4j:3.3.1",
"org.docx4j:docx4j-export-fo:3.3.1",
-
一个能解压的软件 如:7zip
-
能格式化XML文件的文本编辑器 如:Notepad++(需要装一个插件:XML Tools) 将xml的默认打开方式提前设置为用此软件打开
第一部分 简单文本替换
1. 将需要生成的word文件保存为docx格式
2. 在需要替换字符的地方写上${parameter} (注意:这里要根据你的需要设定参数,parameter只是示例)
3. 使用7zip打开docx文件,即打开压缩包(注意:不是解压,解压了就压缩不回去了)
4. 进入word文件夹,打开document.xml
5. 点击Notepad++菜单栏中的插件:XMl Tools,然后点击 pretty XML With Line Brekes格式化XMl文件,或者选择你喜欢的方式
6. 第二步写的${parameter}在xml中可能为下面的样子
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>${</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>parameter</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>}</w:t>
</w:r>
7. 将他们”撮合”到一个中去,如下:
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:szCs w:val="21"/>
</w:rPr>
<w:t>${parameter}</w:t>
</w:r>
8. 然后 保存文件,切到7zip,这时7zip会提醒你更新压缩包的的文件,点击确定就行了
9. 然后就可以替换参数生成简单的word文档了!
/**
* 创建Docx的主方法
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param paragraphParameters 段落参数
* @param imageParameters 书签和图片
* @return
*/
private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters)
throws Exception {
@Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
//第一步 替换字符参数
if (parameters != null) {
replaceParameters(documentPart, parameters);
}
...............
return wordMLPackage;
}
/**
* 替换模板中的参数
*
* @param documentPart
* @param parameters
* @throws JAXBException
* @throws Docx4JException
*/
private static void replaceParameters(MainDocumentPart documentPart,
HashMap<String, String> parameters)
throws JAXBException, Docx4JException {
documentPart.variableReplace(parameters);
}
第二部分 段落替换(换行)
10. 替换段落:将需要替换为段落的地方按照 第一部分 处理
11. 替换段落的工具方法
/**
* 针对一个特定的类来搜索指定元素以及它所有的孩子,例如,获取文档中所有的表格、表格中所有的行以及其它类似的操作
*
* @param obj
* @param toSearch
* @return
*/
private static List<Object> getAllElementFromObject(Object obj, Class<?> toSearch) {
List<Object> result = new ArrayList<>();
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;
}
/**
* @param content
* @Description: 在一个段落后追加新的段落内容
*/
public static void appendParaRContent(P p, String content) {
List<?> texts = getAllElementFromObject(p, Text.class);
if (texts.size() > 0) {
Text textToReplace = (Text) texts.get(0);
textToReplace.setValue("");
}
if (content != null) {
R run = new R();
p.getContent().add(run);
String[] contentArr = content.split("\n");
Text text = new Text();
text.setSpace("preserve");
text.setValue(" " + contentArr[0]);
run.getContent().add(text);
for (int i = 1, len = contentArr.length; i < len; i++) {
Br br = new Br();
run.getContent().add(br);// 换行
text = new Text();
text.setSpace("preserve");
text.setValue(" " + contentArr[i]);
run.getContent().add(text);
}
}
}
/**
* 从Content中获得内容(返回Content的文本)
*
* @param content
* @return
*/
public static String getStringFromContent(List<Object> content) {
String temp = "";
for (Object o : content) {
if (o.getClass() == R.class) {
final Object text = ((JAXBElement) ((R) o).getContent().get(0)).getValue();
if (text.getClass() == Text.class) {
temp += ((Text) text).getValue();
}
}
}
return temp;
}
12. 最后就是替换段落的方法了
/**
* 根据字符串参数替换段落
*
* @param documentPart
* @param paragraphParameters
*/
private static void replaceParagraph(MainDocumentPart documentPart,
HashMap<String, String> paragraphParameters)
throws JAXBException, Docx4JException {
//获得所有的段落内容
final List<Object> allElementFromObject = getAllElementFromObject(documentPart, P.class);
for (Object paragraph : allElementFromObject) {
final P para = (P) paragraph;
if (!isNullOrEmpty(para.getContent())) {
final List<Object> content = para.getContent();
final String stringFromContent = getStringFromContent(content);
final String s = paragraphParameters.get(stringFromContent);
if (s != null) {
appendParaRContent(para, s);
}
}
}
}
13. 调用
/**
* 创建Docx的主方法
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param paragraphParameters 段落参数
* @param imageParameters 书签和图片
* @return
*/
private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters)
throws Exception {
@Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
......
//第二步 替换段落
if (paragraphParameters != null) {
replaceParagraph(documentPart, paragraphParameters);
}
......
return wordMLPackage;
}
第三部分 替换书签为图片
13. 准备:在需要插入图片的地方增加书签
14. 替换书签为图片的方法
/**
* 替换书签为图片
*
* @param wordMLPackage
* @param documentPart
* @param imageParameters
* @throws Exception
*/
private static void replaceBookMarkWithImage(WordprocessingMLPackage wordMLPackage,
MainDocumentPart documentPart,
Map<String, String> imageParameters)
throws Exception {
Document wmlDoc = documentPart.getContents();
Body body = wmlDoc.getBody();
// 提取正文中所有段落
List<Object> paragraphs = body.getContent();
// 提取书签并创建书签的游标
RangeFinder rt = new RangeFinder("CTBookmark", "CTMarkupRange");
new TraversalUtil(paragraphs, rt);
// 遍历书签
for (CTBookmark bm : rt.getStarts()) {
String bookmarkName = bm.getName();
String imagePath = imageParameters.get(bookmarkName);
if (imagePath != null) {
File imageFile = new File(imagePath);
InputStream imageStream = new FileInputStream(imageFile);
// 读入图片并转化为字节数组,因为docx4j只能字节数组的方式插入图片
byte[] bytes = IOUtils.toByteArray(imageStream);
// 创建一个行内图片
BinaryPartAbstractImage imagePart = BinaryPartAbstractImage.createImagePart(wordMLPackage, bytes);
// 最后一个是限制图片的宽度,缩放的依据
Inline inline = imagePart.createImageInline(null, null, 0, 1, false, 800);
// 获取该书签的父级段落
P p = (P) (bm.getParent());
ObjectFactory factory = new ObjectFactory();
// 新建一个Run
R run = factory.createR();
// drawing 画布
Drawing drawing = factory.createDrawing();
drawing.getAnchorOrInline()
.add(inline);
run.getContent()
.add(drawing);
p.getContent()
.add(run);
}
}
}
15.调用
/**
* 创建Docx的主方法
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param paragraphParameters 段落参数
* @param imageParameters 书签和图片
* @return
*/
private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters)
throws Exception {
@Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
......
//第三步 插入图片
if (imageParameters != null) {
replaceBookMarkWithImage(wordMLPackage, documentPart, imageParameters);
}
return wordMLPackage;
}
第四部分 附上几个工具方法
/**
* 创建Docx的主方法
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param paragraphParameters 段落参数
* @param imageParameters 书签和图片
* @return
*/
private static WordprocessingMLPackage CreateWordprocessingMLPackageFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters)
throws Exception {
@Cleanup InputStream docxStream = DocxProducer.class.getResourceAsStream(templatePath);
WordprocessingMLPackage wordMLPackage = WordprocessingMLPackage.load(docxStream);
MainDocumentPart documentPart = wordMLPackage.getMainDocumentPart();
//第一步 替换字符参数
if (parameters != null) {
replaceParameters(documentPart, parameters);
}
//第二步 替换段落
if (paragraphParameters != null) {
replaceParagraph(documentPart, paragraphParameters);
}
//第三步 插入图片
if (imageParameters != null) {
replaceBookMarkWithImage(wordMLPackage, documentPart, imageParameters);
}
return wordMLPackage;
}
/**
* 创建Docx并保存
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param imageParameters 书签和图片
* @param savePath 保存docx的路径
* @return
*/
public static void CreateDocxFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String savePath)
throws Exception {
WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);
//保存
saveDocx(wordMLPackage, savePath);
}
/**
* 创建Docx并加密保存
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param imageParameters 书签和图片
* @param savePath 保存docx的路径
* @return
*/
public static void CreateEncryptDocxFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String savePath,
String passWord)
throws Exception {
WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);
//加密
ProtectDocument protection = new ProtectDocument(wordMLPackage);
protection.restrictEditing(STDocProtect.READ_ONLY, passWord);
//保存
saveDocx(wordMLPackage, savePath);
}
/**
* 创建Docx并加密,返回InputStream
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param imageParameters 书签和图片
* @return
*/
public static InputStream CreateEncryptDocxStreamFromTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String passWord)
throws Exception {
WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);
//加密
ProtectDocument protection = new ProtectDocument(wordMLPackage);
protection.restrictEditing(STDocProtect.READ_ONLY, passWord);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
wordMLPackage.save(baos);
ByteArrayDataSource bads =
new ByteArrayDataSource(baos.toByteArray(), "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
return bads.getInputStream();
}
/**
* 根据模板创建docx文档并放到response的outputstream中
*
* @param templatePath
* @param parameter
* @param fileName
* @param response
*/
public static void CreateEncryptDocxToResponseFromTemplate(String templatePath,
HashMap<String, String> parameter,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String fileName,
HttpServletResponse response) throws Exception {
final InputStream inputStream = CreateEncryptDocxStreamFromTemplate(templatePath, parameter, paragraphParameters, imageParameters, UUID.randomUUID().toString());
response.setContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document");
response.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "utf-8"));
copy(inputStream, response.getOutputStream());
}
/**
* 根据模板创建docx文档返回File
*
* @param templatePath
* @param parameter
* @param fileName
*/
public static File CreateEncryptDocxFileFromTemplate(String templatePath,
HashMap<String, String> parameter,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String fileName) throws Exception {
final InputStream inputStream = CreateEncryptDocxStreamFromTemplate(templatePath, parameter, paragraphParameters, imageParameters, UUID.randomUUID().toString());
File file = new File(fileName);
inputStreamToFile(inputStream, file);
return file;
}
/**
* 从Docx模板文件创建Docx然后转化为pdf
*
* @param templatePath 模板docx路径
* @param parameters 参数和值
* @param imageParameters 书签和图片
* @param savePath 保存pdf的路径
* @return
*/
public static void CreatePDFFromDocxTemplate(String templatePath,
HashMap<String, String> parameters,
HashMap<String, String> paragraphParameters,
HashMap<String, String> imageParameters,
String savePath)
throws Exception {
WordprocessingMLPackage wordMLPackage = CreateWordprocessingMLPackageFromTemplate(templatePath, parameters, paragraphParameters, imageParameters);
//转化成PDF
convertDocxToPDF(wordMLPackage, savePath);
}
/**
* 保存当前Docx文件
*/
private static void saveDocx(WordprocessingMLPackage wordMLPackage,
String savePath)
throws FileNotFoundException, Docx4JException {
Docx4J.save(wordMLPackage, new File(savePath), Docx4J.FLAG_SAVE_ZIP_FILE);
}
Original link:https://izhangzhihao.github.io//2016/12/02/Docx4J-Word模板实战/