技术点 | 优点 | 缺点 |
---|---|---|
Jacob | ||
Java2word | ||
FreeMarker | ||
PageOffice | ||
Apache POI |
整体流程
和 一些通用方法
做个记录,很多都是站在巨人的肩膀上。 <dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.1.1</version>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.1.1</version>
</dependency>
public static void exportWord() {
InputStream inputDocxTemplate = null;
OutputStream outputStream = null;
XWPFDocument document = null;
try {
log.info("1.导入word模板");
inputDocxTemplate = EverydayLearnApplication.class.getResourceAsStream("/template/WordExportTemplate.docx");
document = new XWPFDocument(inputDocxTemplate);
log.info("2.操作逻辑");
log.info("2.1 替换 文档中的 文本 占位符");
// 构建替换 Map
Map<String, String> replaceTextMap = new HashMap<>();
replaceTextMap.put("${name}", "任初心");
replaceTextMap.put("${age}", "18");
replaceTextMap.put("${sex}", "男");
replaceTextMap.put("${hobby}", "跑步");
replaceTextMap.put("${contactWay}", "12345678910");
// 替换文本占位符
doReplaceText(document, replaceTextMap);
log.info("2.2 操作已有表格");
// 获取表格
XWPFTable oneTable = document.getTableArray(0);
// 构建数据, 内层 List 每一个值表示一个单元格的数据
List<List<String>> rowDataList = new ArrayList<>();
List<String> oneRow = new ArrayList<>();
oneRow.add("框架");
oneRow.add("SpringCloud");
oneRow.add("熟悉");
List<String> twoRow = new ArrayList<>();
twoRow.add("数据库");
twoRow.add("MongoDB");
twoRow.add("熟悉");
rowDataList.add(oneRow);
rowDataList.add(twoRow);
// 插入数据
insertDataToTable(oneTable, rowDataList);
log.info("2.3 替换 表格 占位符");
// 第一行(表头)
List<String> titleList = Arrays.asList("技术分类", "技术点", "熟悉程度");
// 内容行
List<List<String>> tableDataList = new ArrayList<>();
// 用上面构建的数据,就不再重新建了
tableDataList.add(oneRow);
tableDataList.add(twoRow);
// 替换表格占位符
doReplaceTable(document, "${table}", titleList, tableDataList);
log.info("2.4 替换 图片占位符");
URL resource = EverydayLearnApplication.class.getClassLoader().getResource("template/picture/good.png");
String picturePath = resource.getPath();
String decodePicturePath = URLDecoder.decode(picturePath, "UTF-8");
log.info("picturePath:{}", decodePicturePath);
// 替换图片占位符
doReplacePicture(document, "${picture}", decodePicturePath);
log.info("3.word 导出");
ApplicationHome ah = new ApplicationHome();
File fileHone = ah.getDir();
String filePath = fileHone.getParentFile().getAbsolutePath() + File.separator + "export" + File.separator;
String fileName = "演示模板.docx";
FileUtil.mkdirs(filePath);
File file = new File(filePath + fileName);
outputStream = new FileOutputStream(file);
document.write(outputStream);
outputStream.flush();
outputStream.close();
document.close();
inputDocxTemplate.close();
} catch (IOException e) {
log.error("word导出异常:{}", e.toString());
} finally {
closeStream(outputStream);
closeStream(inputDocxTemplate);
if (null != document) {
try {
document.close();
} catch (IOException e) {
log.error("word文档关流失败:{}", e.toString());
}
}
}
}
/**
* 插入图片, 替换文档中的图片占位符
*
* @param document word模板对象
* @param key 图片占位符
* @param picturePath 图片所在路径
*/
private static void doReplacePicture(XWPFDocument document, String key, String picturePath) {
FileInputStream inputStream = null;
try {
inputStream = new FileInputStream(new File(picturePath));
List<XWPFParagraph> paragraphList = document.getParagraphs();
quit: for (int i = 0; i < paragraphList.size(); i++) {
List<XWPFRun> runList = paragraphList.get(i).getRuns();
for (int j = 0; j < runList.size(); j++) {
String runText = runList.get(j).getText(0);
if (StrUtil.isNotBlank(runText) && runList.get(j).getText(0).indexOf(key) != -1) {
// 获取光标
XmlCursor cursor = paragraphList.get(i).getCTP().newCursor();
// 插入新的段落
XWPFParagraph newParagraph = document.insertNewParagraph(cursor);
newParagraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = newParagraph.createRun();
run.addPicture(inputStream, Document.PICTURE_TYPE_PNG, "good.png", Units.toEMU(300), Units.toEMU(160));
// 删除占位符
document.removeBodyElement(document.getPosOfParagraph(paragraphList.get(i+1)));
break quit;
}
}
}
inputStream.close();
} catch (Exception e) {
log.error("替换图片异常:{}", e.toString());
} finally {
closeStream(inputStream);
}
}
/**
* 插入的新表格, 替换文档中的表格占位符
*
* @param document word模板对象
* @param key 表格占位符
* @param titleList 第一行(表头)
* @param tableDataList 表格数据内容
*/
private static void doReplaceTable(XWPFDocument document, String key, List<String> titleList, List<List<String>> tableDataList) {
List<XWPFParagraph> paragraphList = document.getParagraphs();
for (int i = 0; i < paragraphList.size(); i++) {
List<XWPFRun> runList = paragraphList.get(i).getRuns();
for (int j = 0; j < runList.size(); j++) {
String runText = runList.get(j).getText(0);
if (StrUtil.isNotBlank(runText) && runList.get(j).getText(0).indexOf(key) != -1) {
// 获取光标
XmlCursor cursor = paragraphList.get(i).getCTP().newCursor();
// 插入新的表格
XWPFTable newTable = document.insertNewTbl(cursor);
// 设置表格样式
setTableStyle(newTable);
// 创建表格的第一行(表格创建后,默认有一个单元格)
XWPFTableCell titleOneCell = newTable.getRow(0).getCell(0);
titleOneCell.setWidth("auto");
setCellTitleStyle(titleOneCell, titleList.get(0));
for (int p = 1; p < titleList.size() ; p++) {
XWPFTableCell titleCell = newTable.getRow(0).createCell();
titleCell.setWidth("auto");
setCellTitleStyle(titleCell, titleList.get(p));
}
// 根据数据量创建表格其他行
for (List<String> rowDataList : tableDataList) {
XWPFTableRow row = newTable.createRow();
for (int p = 0; p < titleList.size(); p++) {
XWPFTableCell cell = row.getCell(p);
cell.setText(rowDataList.get(p));
setCellDataStyle(cell);
}
}
// 删除占位符
paragraphList.get(i).removeRun(0);
}
}
}
}
/**
* 设置表格样式
*
* @param table 表格对象
*/
private static void setTableStyle(XWPFTable table) {
CTTblPr tblPr = table.getCTTbl().getTblPr();
tblPr.getTblW().setType(STTblWidth.DXA);
tblPr.getTblW().setW(new BigInteger("8000"));
tblPr.addNewJc().setVal(STJc.CENTER);
}
/**
* 设置表格第一行(表头)单元格的样式
*
* @param titleCell 表头单元格
* @param text 单元格文本内容
*/
private static void setCellTitleStyle(XWPFTableCell titleCell, String text) {
XWPFParagraph xwpfParagraph = titleCell.addParagraph();
xwpfParagraph.setAlignment(ParagraphAlignment.CENTER);
XWPFRun run = xwpfParagraph.createRun();
run.setText(text);
run.setFontSize(14);
run.setBold(true);
titleCell.removeParagraph(0);
}
/**
* 设置单元格 文本内容 样式:居中
*
* @param cell 单元格
*/
public static void setCellDataStyle(XWPFTableCell cell) {
// 设置表格样式一致,默认是左对齐
CTTc cttc = cell.getCTTc();
CTTcPr ctPr = cttc.addNewTcPr();
ctPr.addNewVAlign().setVal(STVerticalJc.CENTER);
cttc.getPList().get(0).addNewPPr().addNewJc().setVal(STJc.CENTER);
}
/**
* 往表格中插入数据
*
* @param table 表格对象
* @param rowList 要插入的数据
*/
private static void insertDataToTable(XWPFTable table, List<List<String>> rowList) {
// 根据传入数据数量 创建行
for (List<String> row : rowList) {
table.createRow();
}
List<XWPFTableRow> rows = table.getRows();
// 第一行为标题,从第二行开始操作
for (int i = 1; i < rows.size(); i++) {
List<XWPFTableCell> cells = rows.get(i).getTableCells();
for (int j = 0; j < cells.size(); j++) {
XWPFTableCell cell = cells.get(j);
cell.setText(rowList.get(i - 1).get(j));
// 设置样式为居中
setCellDataStyle(cell);
}
}
}
/**
* 替换 word 模板的占位符
*
* @param document Api文档模板
* @param replaceTextMap 要替换的文本信息 Map<占位符, 要替换的值>
*/
private static void doReplaceText(XWPFDocument document, Map<String, String> replaceTextMap) {
List<XWPFParagraph> paragraphs = document.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
String paragraphText = paragraph.getText();
if (whetherNeedReplace(paragraphText)) {
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
String runText = run.getText(0);
for (Map.Entry<String, String> entry : replaceTextMap.entrySet()) {
String key = entry.getKey();
if (runText.indexOf(key) != -1) {
String value = entry.getValue();
String replaceValue = runText.replace(key, value);
run.setText(replaceValue, 0);
}
}
}
}
}
}
/**
* 判断段落的文本是否需要替换
*
* @param paragraphText 段落文本
* @return true 需要替换, false 不需要替换
*/
private static boolean whetherNeedReplace(String paragraphText) {
boolean needReplace = false;
if (paragraphText.indexOf("$") != -1) {
needReplace = true;
}
return needReplace;
}
/**
* 关闭输入流
*
* @param inputStream 输入流对象
*/
public static void closeStream(InputStream inputStream) {
if (null != inputStream) {
try {
inputStream.close();
} catch (IOException e) {
log.error("输出流关闭失败:{}", e.toString());
}
}
}
/**
* 关闭输出流 对象
*
* @param outputStream
*/
public static void closeStream(OutputStream outputStream) {
if (null != outputStream) {
try {
outputStream.close();
} catch (IOException e) {
log.error("输出流关闭失败:{}", e.toString());
}
}
}
具体操作:
1.打开保存后的.xml 文件,复制内容到在线解析网站(这边附了一个)
2.搜索替换成一个整体,如下所示:
3.保存修改,将".xml"文件改为“.docx"文件
// 表示一个docx文档,其可以用来读docx文档,也可以用来写docx文档
XWPFDocument
// 表示一个段落,由多个XWPFRun组成
XWPFParagraph
// 表示一段文本,一个段落可由多个 XWPFRun 组成
XWPFRun
// 表示一个表格
XWPFTable
// 表示表格的一行
XWPFTableRow
// 表示表格的一个单元格
XWPFTableCell
// 表示一张图片
XWPFPicture
// 表示超链接
XWPFHyperlink
// 表示docx文件中的图表
XWPFChar
/**
* 设置段落内文本上下对齐方式【段落-中文板式-文本对齐方式】
* @param textAlignment {@link STTextAlignment#CENTER}
*/
public static void setTextAlignment(XWPFParagraph paragraph , STTextAlignment.Enum textAlignment){
CTP ctp = paragraph.getCTP();
CTPPr ctpPr = ctp.isSetPPr() ? ctp.getPPr() : ctp.addNewPPr();
CTTextAlignment ctTextAlignment = CTTextAlignment.Factory.newInstance();
ctTextAlignment.setVal(textAlignment);
ctpPr.setTextAlignment(ctTextAlignment);
}
/**
* 调整段落间距【对应word段落设置-间距设置】
* @param before 段前 磅
* @param after 段后 磅
* @param multiple 几倍行距
*/
public static void setParagraphSpacing(XWPFParagraph paragraph , float before , float after , float multiple) {
CTPPr ppr = paragraph.getCTP().getPPr();
if (ppr == null) {
ppr = paragraph.getCTP().addNewPPr();
}
CTSpacing spacing = ppr.isSetSpacing()? ppr.getSpacing() : ppr.addNewSpacing();
spacing.setBefore(Math.round(before * 20));
spacing.setAfter(Math.round(after * 20));
spacing.setLine(Math.round(240 * multiple));
spacing.setLineRule(STLineSpacingRule.AUTO);
}
/**
* 设置段落缩进
* @param left 左缩进值 磅
* @param right 右缩进值 磅
*/
public static void setParagraphInd(XWPFParagraph paragraph , float left , float right){
CTPPr ppr = paragraph.getCTP().getPPr();
if (ppr == null) {
ppr = paragraph.getCTP().addNewPPr();
}
CTInd ctInd = ppr.isSetInd() ? ppr.getInd() : ppr.addNewInd();
if(left >= 0){
ctInd.setLeft(Math.round(left * 20));
}
if(right >= 0){
ctInd.setRight(Math.round(right * 20));
}
}
/**
* 设置文本填充色
*/
public static void setFillColor(XWPFRun run , String color){
CTR ctr = run.getCTR();
CTRPr rPr = ctr.isSetRPr() ? ctr.getRPr() : ctr.addNewRPr();
CTShd ctShd = rPr.sizeOfShdArray() > 0 ? rPr.getShdArray(0) : rPr.addNewShd();
ctShd.setFill(color);
}
/**
* 设置文本 Run位置【对应word字体 - 高级 - 字符间距】
* @param positionValue 【上下位置调整正负】 单位 磅
* @param spacingValue 【字符间距调整 宽窄-正负】单位 磅
*/
public static void setPositionRun(XWPFRun run , float positionValue , float spacingValue){
CTRPr rPr = run.getCTR().getRPr();
if(rPr == null){
rPr = run.getCTR().addNewRPr();
}
CTSignedHpsMeasure position = rPr.sizeOfPositionArray() > 0 ? rPr.getPositionArray(0) : null;
if(position == null){
rPr.addNewPosition().setVal(positionValue * 2);
}else{
position.setVal(positionValue * 2);
}
CTSignedTwipsMeasure spacing = rPr.sizeOfSpacingArray() > 0 ? rPr.getSpacingArray(0) : null;
if(spacing == null){
rPr.addNewSpacing().setVal(spacingValue * 20);
}else{
spacing.setVal(spacingValue * 20);
}
}