由于项目需求,需要根据用户提供的word模板,填充动态内容生成新的word,为了记录自己的踩坑日记,记录一下。
Apache POI 是用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对文档读和写的功能。
这里给出官网链接-POI官网,同时下载版本也在官网链接中,可进行对应版本下载。
同时在了解POI库的过程中,还了解到poi-ti库,也就是word模板引擎,基于Apache POI,其实也就是在POI库的基础上做了一层API的封装,对应jar包中是包含了poi的,这里给出中文文档有兴趣的可以了解下-poi-ti API文档,源码github链接-poi-ti源码。这个开源库对java做了很好的适配,后台开发如果涉及到操作word,excel等文档读写完全可以使用,使用也很简单,只需要根据中文文档集成即可。
唯一遗憾的是poi-ti该库的作者只对java库做了适配,确不适用Android,我在研究过程中引入jar包确一直无法编译通过。如果大家还想使用poi-ti 大家可以移步另一位博主写的博客,这里给出链接-Android使用poi_ti生成word,这位博主使用低版本的poi-ti在Android 端完成了编译通过,我也有测试使用,但是不符合我项目要求,果断放弃。
本文主要使用poi来完成对word的读写以及excel文档的读写。
有2种方式,一种直接下载对应版本的jar包,链接这-jar下载链接,另外一种是gradle依赖方式。
注意在这里有一个坑,word分为doc和docx,excel也分为.xls和.xlsx,针对这个poi也做了区分,如下表:
类型 | API | 对应jar |
doc/xls | HWPF (HWPFDocument-doc格式文件对象 /HSSFWorkbook-xls格式文件对象) | poi-scratchpad |
docx/xlsx | XWPF (XWPFDocument- docx格式文件对象 /XSSFWorkbook-.xlsx格式文件对象) | poi-ooxml |
我使用的是docx/xlsx,对应gradle中的依赖如下所示:
读写word docx文件通过xwpf模块来进行的,核心API为XWPFDocument。一个XWPFDocument代表一个docx文档,其可以用来读docx写文档。本文用到XWPFDocument中有下面这几种对象:
XWPFParagraph | 一个段落 |
XWPFRun | 具有相同属性的一段文本 |
XWPFTable | 一个表格 |
XWPFTableRow | 表格的一行 |
XWPFTableCell | 表格对应的一个单元格 |
介绍完对应API后我们开始通过模板来动态填充模板中的内容以及表格,从而生成新的模板。文中给出我们项目中的word模板,如下:
可以看出基本信息一栏是固定的,检测数据下面一栏是动态列表。针对固定的我们可以采取通过poi进行读模板,读取到后将对应标记的{{a}}等等字符串给替换成我们真正需要的值。那针对动态列表的渲染,我们可以通过循环的方式,一直复制当前行,给下面循环插入一行来完成。代码如下:
DocxTool类中export封装的方法主要方法是下面这个:由于隐私原因数据就不做展示,我都是从本地Android sqlite中取得,可以根据实际业务来做调整。
HashMap<String, Object> map = new HashMap<>();为文档中标记的{{a}}等等字符串,用来做替换, List<RowRenderData> labors = new ArrayList<>();为封装的动态渲染表格数据。 XWPFTableRow twoRow = table.getRow(copyRow)以测试模板中第7行固定作为动态表格复制, XWPFTableRow row = table.insertNewTableRow(goodsStartRow++);然后向下面循环插入,插入后在对每个单元格进行数据赋值。
DocxTool类中searchAndReplace封装的方法则是替换并导出,主要方法如下:
public static void searchAndReplace(Activity activity, String Name, XWPFDocument document, HashMap<String, Object> map) {
try {
/**
* 替换表格中的指定文字
*/
//获取文档中所有的表格,每个表格是一个元素
Iterator<XWPFTable> itTable = document.getTablesIterator();
while (itTable.hasNext()) {
XWPFTable table = (XWPFTable) itTable.next(); //获取表格内容
int count = table.getNumberOfRows(); //表格的行数
//遍历表格行的对象
for (int i = 0; i < count; i++) {
XWPFTableRow row = table.getRow(i); //表格每行的内容
List<XWPFTableCell> cells = row.getTableCells(); //每个单元格的内容
//遍历表格的每行单元格对象
for (int j = 0; j < cells.size(); j++) {
XWPFTableCell cell = cells.get(j); //获取每个单元格的内容
List<XWPFParagraph> paragraphs = cell.getParagraphs(); //获取单元格里所有的段落
for (XWPFParagraph paragraph : paragraphs) {
//获取段落的内容
List<XWPFRun> run = paragraph.getRuns();
// 遍历段落文字对象
for (int o = 0; o < run.size(); o++) {
// 获取段落对象
if (run.get(o) == null || run.get(o).equals("")) {
continue;
}
String sectionItem = run.get(o).getText(run.get(o).getTextPosition()); //获取段落内容
if (sectionItem == null || sectionItem.equals("")) { //段落为空跳过
continue;
}
//遍历自定义表单关键字,替换Word文档中表格单元格的内容
for (String key : map.keySet()) {
// 替换内容
sectionItem = sectionItem.replace(key, String.valueOf(map.get(key)));
run.get(o).setText(sectionItem, 0);
}
}
}
}
}
}
//创建生成的文件
String ps = context.getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS).getPath();
File path = new File(ps, "docx-export");
if (!path.exists()) {
path.mkdir();
}
String date = Utils.getCurTimeString().replace("-", "").replace(" ", "").replace(":", "");
String dirName = date + ".docx";
File File = new File(path, dirName);
if (File.exists()) {
File.delete();
}
FileOutputStream outputStream = new FileOutputStream(File);
document.write(outputStream);
document.close();
outputStream.flush();
outputStream.close();
new Handler(Looper.getMainLooper()).post(() -> {
ExprotWordDialog wordDialog = new ExprotWordDialog(activity, s -> {
Uri photoURI = null;
Intent intent = new Intent(Intent.ACTION_VIEW);
try {
if (Build.VERSION.SDK_INT >= 24) {
photoURI = FileProvider.getUriForFile(activity,
"自己的包名" + ".file.provider",
new File(File.getPath()));
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);//给目标文件临时授权
} else {
photoURI = Uri.fromFile(new File(File.getPath()));
}
intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
intent.setDataAndType(photoURI, "application/vnd.openxmlformats-officedocument.wordprocessingml.document");
activity.startActivity(intent);
} catch (Exception e) {
Log.e("=", e.toString());
Toast.makeText(activity, "查看文档出错", Toast.LENGTH_SHORT).show();
}
});
wordDialog.show();
wordDialog.setFilename("文件名:" + dirName);
wordDialog.setPath("本地存储路径:" + path.getPath());
});
} catch (Exception e) {
Log.e("==", e.toString() + "替换docx出错");
e.printStackTrace();
}
}
到此,模板数据填充以及动态列表渲染就结束了。
arraylist为封装得动态数据
记录下来,也是为了记录自己的踩坑日记。