为什么使用docx
office 2007以后,docx以逐步替代doc文件,docx文件比doc文件所占用空间更小,其本质上是一个XML文件。故Apache开发的POI围绕其打造了一系列的处理工具。本次需求使用的类以XWPF开头。包括XWPFDocument、XWPFParagraph、XWPFRun……
XWPF(XML字处理器格式):用于读取和写入MS-Word的扩展文件 .docx ,适用office 2007以后版本
HWPF:用于读取和写入MS-Word的.doc扩展文件,适用office 2007以前版本
占位符${……}
替换文本,构建模板
存在问题:docx格式下编辑占位符时,可能出现${……}
没法形成整体,导致代码获取run时占位符不完整,无法识别替换
解决方法:先把docx格式另存为xml格式,并把完整的${……}
从别处复制到目标位置上。保险起见可以用vscode或其他xml软件预览以确定${……}
是否是完整连续的。
调用模板插入文本——代码实现
//获取模板 (OPC是一个文件容器技术。被微软创建,用来存储XML。)
OPCPackage opcPackage = POIXMLDocument.openPackage(templatePath);
//读取文件(XWPFDocument类简化xml的底层结构,方便操作,但还不是个稳定成熟的api)
//存在问题,sonarlint可能会要求把该行代码放进try(……){}里,实现运行完后释放。但实际上try结束后执行的是.close()方法,导致缓存中已被替换的文本会写入模板文件并覆盖掉模板文本,从而使模板丢失。
//解决方式,不放入try(……)中,没有close的必要性。
XWPFDocument doc = new XWPFDocument(opcPackage);
//获取段落
List<XWPFParagraph> paragraphList = doc.getParagraphs();
//获取run.在Word文档中段落的最小的操作单位是XWPFRun,正常的一个段落,会被分割成多个小的XWPFRun,这些XWPFRun组合在一起就是一个完整的段落。
List<XWPFRun> runs = par.getRuns();
/*
操作文本
run.xxx();
*/
//替换占位符
String text = run.getText();
……
text = text.replace("${……}", value);
run.setText(text, pos); //pos为插入run中的位置
//把处理完的doc写入到新文件里
FileOutputStream fileOutputStream = new FileOutputStream(file);
doc.write(fileOutputStream);
此处可以关闭文件输出流,但不要doc.close(),会导致修改完的文本回填覆盖掉模板文件。
来看官方解释这个close方法:
XWPFDocument.close()
Closes the underlying OPCPackage from which this document was read, if there is one.
"关闭底层的被读取的OPCPackage文件 "
再看OPCPackage.close()
Close the open, writable package and save its content. If your package is open read only, then you should call revert() when finished with the package. This method is not thread-safe.
“关闭这个打开的、可写入的包,并保存它的文本内容。如果这个包打开后只进行读取,结束使用这个包后应该调用revert()方法。这个方法是线程不安全的。”
附带看OPCPackage.revert()
Close the package WITHOUT saving its content. Reinitialize this package and cancel all changes done to it.
“关闭包而不保存它的内容。初始化这个包并取消所有的更改。”
由此可见把打开docx的动作放进try(……)里是不合理的,会导致覆盖发生。若需要关闭读取文件,需使用opcPackage.revert()。