需求:Android端根据数据,生成word保存到本地.
查了一些资料好像android 没什么正式的word操作库,基本都是用Java API,Apache POI实现的,但是这个版本比较多android这边兼容不太好,坑比较多…
这边本来是需要word操作雷达图,折线图,柱状图这个些的,需要POI4.x版本,各种姿势依赖,好像都问题,编译或者运行各种不行(可能本人太菜…),没办法只能用3.x版本,图表再说吧…
1.gradle 依赖POI
app目录build.gradle,添加完整依赖:
dependencies {
...
implementation group: 'org.apache.poi', name: 'poi-ooxml', version: '3.17'
implementation group: 'org.apache.xmlbeans', name: 'xmlbeans', version: '3.1.0'
implementation 'javax.xml.stream:stax-api:1.0'
implementation 'com.fasterxml:aalto-xml:1.2.2'
...
}
2.新增段落
/**
* 添加段落内容
*
* @param document word文档对象 XWPFDocument document = new XWPFDocument()
* @param fontSize 字体大小
* @param fontFamily 字体类型
* @param text 标题
*/
public static void addParagraph(XWPFDocument document, int fontSize, String fontFamily, String text) {
//创建空白word文档
//XWPFDocument document = new XWPFDocument()
XWPFParagraph paragraph = document.createParagraph();
//内容左对齐
paragraph.setAlignment(ParagraphAlignment.valueOf(STJc.INT_LEFT));
XWPFRun run = paragraph.createRun();
run.setFontSize(13);
run.setFontFamily("黑体");
run.addTab();
run.setText(text);
run.addBreak();
}
3.新增表格
- document.createTable(1, 2);
创建1行 2列表格
table.getRow(0).getCell(0).setText()
table.getRow(0).getCell(1).setText() - document.createTable();
默认创建1行1列
table.getRow(0).getCell(0).setText()
table.getRow(0).addNewTableCell().setText()
/**
* 添加表格
* XWPFDocument document = new XWPFDocument()
*
* @param document word文档对象.
*/
public static void addTable(XWPFDocument document) {
//创建表格 1行 list.size 列
//XWPFTable table = document.createTable(1, 2);
XWPFTable table = document.createTable();
CTTbl tTbl = table.getCTTbl();
CTTblPr tTblPr = tTbl.getTblPr() == null ? tTbl.addNewTblPr() : tTbl.getTblPr();
CTTblWidth tblWidth = tTblPr.isSetTblW() ? tTblPr.getTblW() : tTblPr.addNewTblW();
//设置表格宽度
tblWidth.setType(STTblWidth.DXA);
tblWidth.setW(new BigInteger("8000"));
table.getRow(0).setHeight(1000);
//第一列
setCellStr(table.getRow(0).getCell(0), "主题一", 3000);
//setCellStr(table.getRow(0).getCell(1), "孤勇者-陈奕迅", 5000);
//第二列
setCellStr(table.getRow(0).addNewTableCell(), "孤勇者-陈奕迅", 5000);
XWPFTableRow row_1 = table.createRow();
row_1.setHeight(1000);
setCellStr(row_1.getCell(0), "主题二", 3000);
setCellStr(row_1.getCell(1), "再回首-岩贵|再回首恍然如梦", 5000);
XWPFTableRow row_2 = table.createRow();
row_2.setHeight(1000);
setCellStr(row_2.getCell(0), "主题三", 3000);
setCellStr(row_2.getCell(1), "光辉岁月-黄家驹", 5000);
}
设置单元格属性
注意水平居中:这个有点坑,差了好多,都是ctTc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);,因为版本问题需要改成ctTc.getPArray()[0].addNewPPr().addNewJc().setVal(STJc.CENTER);,编译不报错,一运行就找不到方法,关键代码你点进去还有这个方法…
private static void setCellStr(XWPFTableCell cell, String str, int w) {
CTTc ctTc = cell.getCTTc();
CTTcPr ctTcPr = ctTc.addNewTcPr();
ctTcPr.addNewTcW().setW(BigInteger.valueOf(w));
//上下居中
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
//水平居中
//ctTc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
ctTc.getPArray()[0].addNewPPr().addNewJc().setVal(STJc.CENTER);
cell.setText(str);
}
4.根据模版生成新的word
模版我是放在assets目录,然后判断字段是是否包含$.然后替换成我们需要的内容.
4.1替换段落
/**
* 替换段落文本
*
* @param document docx解析对象
* @param textMap 需要替换的信息集合
*/
public static void changeText(XWPFDocument document, Map<String, String> textMap) {
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//判断此段落时候需要进行替换
String text = paragraph.getText();
Log.i(TAG, "changeText:行 " + text);
if (text.contains("$")) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Log.i(TAG, "changeText:行内 " + run.getText(run.getTextPosition()));
run.setText(changeValue(run.toString(), textMap), 0);
}
}
}
}
4.2替换表格
/**
* 替换表格对象方法
*
* @param document docx解析对象
* @param textMap 需要替换的信息集合
*/
public static void changeTable(XWPFDocument document, Map<String, String> textMap) {
//获取表格对象集合
List<XWPFTable> tables = document.getTables();
for (int i = 0; i < tables.size(); i++) {
Log.i(TAG, "changeTable: 1" + tables.get(i).getText());
//只处理行数大于等于2的表格,且不循环表头
XWPFTable table = tables.get(i);
if (table.getRows().size() > 1) {
//判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
if (table.getText().contains("$")) {
List<XWPFTableRow> rows = table.getRows();
//遍历表格,并替换模板
eachTable(rows, textMap);
}
}
}
}
5.完整工具代码
public class PoiWordUtils {
private static final String TAG = "";
/**
* 添加段落标题
*
* @param document word文档对象 XWPFDocument document = new XWPFDocument()
* @param fontSize 字体大小
* @param fontFamily 字体类型
* @param isBreak 是否换行
* @param title 标题
*/
public static void addParagraphTitle(XWPFDocument document, int fontSize, String fontFamily, boolean isBreak, int gravity, String title) {
XWPFParagraph paragraph = document.createParagraph();
//设置段落居中 / STJc.INT_LEFT - STJc.INT_RIGHT
paragraph.setAlignment(ParagraphAlignment.valueOf(gravity));
//行间距
//paragraph.setSpacingBefore(1);
XWPFRun run = paragraph.createRun();
run.setFontSize(fontSize);
run.setFontFamily("宋体");
run.setBold(true);
run.setText(title);
//换行
if (isBreak)
run.addBreak();
run.addTab();
}
/**
* 添加段落内容
*
* @param document word文档对象 XWPFDocument document = new XWPFDocument()
* @param fontSize 字体大小
* @param fontFamily 字体类型
* @param text 标题
*/
public static void addParagraph(XWPFDocument document, int fontSize, String fontFamily, String text) {
XWPFParagraph paragraph = document.createParagraph();
//内容左对齐
paragraph.setAlignment(ParagraphAlignment.valueOf(STJc.INT_LEFT));
XWPFRun run = paragraph.createRun();
run.setFontSize(13);
run.setFontFamily("黑体");
run.addTab();
run.setText(text);
run.addBreak();
}
/**
* 添加表格
* XWPFDocument document = new XWPFDocument()
*
* @param document word文档对象.
*/
public static void addTable(XWPFDocument document) {
//创建表格 1行 list.size 列
//XWPFTable table = document.createTable(1, 2);
XWPFTable table = document.createTable();
CTTbl tTbl = table.getCTTbl();
CTTblPr tTblPr = tTbl.getTblPr() == null ? tTbl.addNewTblPr() : tTbl.getTblPr();
CTTblWidth tblWidth = tTblPr.isSetTblW() ? tTblPr.getTblW() : tTblPr.addNewTblW();
//设置表格宽度
tblWidth.setType(STTblWidth.DXA);
tblWidth.setW(new BigInteger("8000"));
table.getRow(0).setHeight(1000);
//第一列
setCellStr(table.getRow(0).getCell(0), "主题一", 3000);
//第二列
//setCellStr(table.getRow(0).getCell(1), "孤勇者-陈奕迅", 5000);
setCellStr(table.getRow(0).addNewTableCell(), "孤勇者-陈奕迅", 5000);
XWPFTableRow row_1 = table.createRow();
row_1.setHeight(1000);
setCellStr(row_1.getCell(0), "主题二", 3000);
setCellStr(row_1.getCell(1), "再回首-岩贵|再回首恍然如梦", 5000);
XWPFTableRow row_2 = table.createRow();
row_2.setHeight(1000);
setCellStr(row_2.getCell(0), "主题三", 3000);
setCellStr(row_2.getCell(1), "光辉岁月-黄家驹", 5000);
}
private static void setCellStr(XWPFTableCell cell, String str, int w) {
CTTc ctTc = cell.getCTTc();
CTTcPr ctTcPr = ctTc.addNewTcPr();
ctTcPr.addNewTcW().setW(BigInteger.valueOf(w));
//上下居中
cell.setVerticalAlignment(XWPFTableCell.XWPFVertAlign.CENTER);
//水平居中
//ctTc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
ctTc.getPArray()[0].addNewPPr().addNewJc().setVal(STJc.CENTER);
cell.setText(str);
}
/**-----------------------------------------模版相关---------------------------------------------**/
/**
* 根据模板生成新word文档
* 判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
*
* @param inputUrl 模板存放地址
* @param outputUrl 新文档存放地址
* @param textMap 需要替换的信息集合
* @return 成功返回true, 失败返回false
*/
public static boolean changWord(String inputUrl, String outputUrl,
Map<String, String> textMap) {
//模板转换默认成功
boolean changeFlag = true;
try {
//获取docx解析对象
XWPFDocument document = new XWPFDocument(POIXMLDocument.openPackage(inputUrl));
//解析替换文本段落对象
changeText(document, textMap);
//解析替换表格对象
changeTable(document, textMap);
//生成新的word
File file = new File(outputUrl);
FileOutputStream stream = new FileOutputStream(file);
document.write(stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
changeFlag = false;
}
return changeFlag;
}
public static boolean changWord(InputStream is, String outputUrl,
Map<String, String> textMap) {
//模板转换默认成功
boolean changeFlag = true;
try {
//获取docx解析对象
XWPFDocument document = new XWPFDocument(is);
//解析替换文本段落对象
changeText(document, textMap);
//解析替换表格对象
changeTable(document, textMap);
//生成新的word
File file = new File(outputUrl);
FileOutputStream stream = new FileOutputStream(file);
document.write(stream);
stream.close();
} catch (IOException e) {
e.printStackTrace();
changeFlag = false;
}
return changeFlag;
}
/**
* 替换段落文本
*
* @param document docx解析对象
* @param textMap 需要替换的信息集合
*/
public static void changeText(XWPFDocument document, Map<String, String> textMap) {
//获取段落集合
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
//判断此段落时候需要进行替换
String text = paragraph.getText();
Log.i(TAG, "changeText:行 " + text);
if (text.contains("$")) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
//替换模板原来位置
Log.i(TAG, "changeText:行内 " + run.getText(run.getTextPosition()));
run.setText(changeValue(run.toString(), textMap), 0);
}
}
}
}
/**
* 替换表格对象方法
*
* @param document docx解析对象
* @param textMap 需要替换的信息集合
*/
public static void changeTable(XWPFDocument document, Map<String, String> textMap) {
//获取表格对象集合
List<XWPFTable> tables = document.getTables();
for (int i = 0; i < tables.size(); i++) {
Log.i(TAG, "changeTable: 1" + tables.get(i).getText());
//只处理行数大于等于2的表格,且不循环表头
XWPFTable table = tables.get(i);
if (table.getRows().size() > 1) {
//判断表格是需要替换还是需要插入,判断逻辑有$为替换,表格无$为插入
if (table.getText().contains("$")) {
List<XWPFTableRow> rows = table.getRows();
//遍历表格,并替换模板
eachTable(rows, textMap);
}
}
}
}
/**
* 遍历表格
*
* @param rows 表格行对象
* @param textMap 需要替换的信息集合
*/
public static void eachTable(List<XWPFTableRow> rows, Map<String, String> textMap) {
for (XWPFTableRow row : rows) {
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
Log.i(TAG, "eachTable: 2 " + cell.getText());
//判断单元格是否需要替换
if (cell.getText().contains("$")) {
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
Log.i(TAG, "eachTable: 3" + paragraph.getText());
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
Log.i(TAG, "eachTable: 4 " + run.getText(run.getTextPosition()));
run.setText(changeValue(run.toString(), textMap), 0);
}
}
}
}
}
}
/**
* 匹配传入信息集合与模板
*
* @param value 模板需要替换的区域
* @param textMap 传入信息集合
* @return 模板需要替换区域信息集合对应值
*/
public static String changeValue(String value, Map<String, String> textMap) {
Set<Map.Entry<String, String>> textSets = textMap.entrySet();
for (Map.Entry<String, String> textSet : textSets) {
//匹配模板与替换值 格式${key}
String key = "${" + textSet.getKey() + "}";
if (value.contains(key)) {
value = textSet.getValue();
}
}
//模板未匹配到区域替换为空
if (value.contains("$")) {
value = "";
}
return value;
}
}
6.测试
创建空白word,写入标题 段落,表格
String path = FileUtils.getAppRootPth(this) + File.separator + "word"
+ File.separator + "测试3.100.docx";
try (XWPFDocument document = new XWPFDocument(); FileOutputStream fos = new FileOutputStream(path)) {
//标题
PoiWordUtils.addParagraphTitle(document, 20, null,true,STJc.INT_CENTER, "POI段落标题");
//小标题
PoiWordUtils.addParagraphTitle(document, 15, null,false,STJc.INT_LEFT, "一丶内容示例");
//段落
PoiWordUtils.addParagraph(document, -1, null, "POI是Apache软件" +
"基金会用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft" +
" Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写," +
"意为“简洁版的模糊实现”。\n" +
"所以POI的主要功能是可以用Java操作Microsoft Office的相关文件,但是一般我们都是用来操作" +
"Excel相关文件。");
PoiWordUtils.addParagraph(document, -1, null, "POI是Apache软件" +
"基金会用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft" +
" Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写," +
"意为“简洁版的模糊实现”。\n" +
"所以POI的主要功能是可以用Java操作Microsoft Office的相关文件,但是一般我们都是用来操作" +
"Excel相关文件。");
PoiWordUtils.addParagraphTitle(document, 15, null,false,STJc.INT_LEFT, "二丶内容示例");
PoiWordUtils.addParagraph(document, -1, null, "POI是Apache软件" +
"基金会用Java编写的免费开源的跨平台的 Java API,Apache POI提供API给Java程序对Microsoft" +
" Office格式档案读和写的功能。POI为“Poor Obfuscation Implementation”的首字母缩写," +
"意为“简洁版的模糊实现”。\n" +
"所以POI的主要功能是可以用Java操作Microsoft Office的相关文件,但是一般我们都是用来操作" +
"Excel相关文件。");
PoiWordUtils.addTable(document);
//写入本地
document.write(fos);
运行结果.
根据模版生成新的word
Map<String, String> testMap = new HashMap<>();
testMap.put("Title", "POI模版替换");
testMap.put("FirstHeading", "规则说明");
testMap.put("FirstHeadingContent", "1.玩家同一场景,以个人为单位进行战斗.\n2.可以对其他玩家进行攻击\n3.一场游戏时间为10分钟");
testMap.put("SecondHeading", "奖励说明");
testMap.put("A", "主题A");
testMap.put("a", "主题A详情");
testMap.put("B", "主题B");
testMap.put("b", "主题B详情");
testMap.put("C", "主题C");
testMap.put("c", "主题C详情");
try {
InputStream is = getAssets().open("谈话结果主题摘要模板.docx");
//InputStream is = getAssets().open("阶段性风险评估报告模版.docx");
String path = FileUtils.getAppRootPth(this) + File.separator + "word" + File.separator + "测试24.docx";
//boolean b = WordUtils.changWord(is, path, testMap, testList);
boolean b = WordUtils.changWord(is, path, testMap);
Log.i(TAG, "testWord: " + b);
} catch (IOException e) {
e.printStackTrace();
}
模版:
替换之后: