最近一个项目,需要根据word模板,写入相应的数据,生成新的文件。运用最基础的poi写word文件。本次项目遇到了多种形式的word内容,
第一:基础的paragraph内容;
第二:基于表格的paragraph内容(固定表格);
第三:list表格(动态表格);
如下图:
前两个的核心思想都是运用正则查找到需要写入数据的地方(用${XXX}的方式标识),然后调用XWPFRun的setText()方法设置文本,第三个重点在于手动添加行。下面附上各个部分的代码:
以流的形式读取模板文件
path = request.getSession().getServletContext()
.getRealPath(".../XXX.docx");
FileInputStream is = null;
is = new FileInputStream(new File(path));
XWPFDocument doc = new XWPFDocument(is);
获取需要写入模板的数据
需要写入的数据有两种形式的,一种是map,一种是list,map很简单,key对应模板中${}里面的内容即可,list我是采用List
// 获取paragraph的数据
Map<String, Object> paraData = getParaData();
// 获取table的数据
List<String[]> tblData = getTblData();
替换段落里面的变量
this.replaceInPara(doc, paraData);
private void replaceInPara(XWPFDocument doc, Map<String, Object> paramData) {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph para;
while (iterator.hasNext()) {
para = iterator.next();
this.replaceInPara(para, paramData);
}
}
private void replaceInPara(XWPFParagraph para, Map<String, Object> paramData) {
List<XWPFRun> runs;
Matcher matcher;
Map<String, Object> mapAttr = new HashMap<String, Object>();
if (this.matcher(para.getParagraphText()).find()) {
runs = para.getRuns();
for (int i = 0; i < runs.size(); i++) {
XWPFRun run = runs.get(i);
String runText = run.toString();
matcher = this.matcher(runText);
if (matcher.find()) {
while ((matcher = this.matcher(runText)).find()) {
mapAttr = getWordXWPFRunStyle(run);
runText = matcher.replaceFirst(String.valueOf(paramData
.get(matcher.group(1))));
}
// 直接调用XWPFRun的setText()方法设置文本时,在底层会重新创建一个XWPFRun,把文本附加在当前文本后面,
// 所以我们不能直接设值,需要先删除当前run,然后再自己手动插入一个新的run。
para.removeRun(i);
XWPFRun runNew = para.insertNewRun(i);
setWordXWPFRunStyle(runNew, mapAttr, runText);
}
}
}
}
private Map<String, Object> getWordXWPFRunStyle(XWPFRun runOld) {
Map<String, Object> mapAttr = new HashMap<String, Object>();
mapAttr.put("Color", runOld.getColor());
if (-1 == runOld.getFontSize()) {
mapAttr.put("FontSize", 10);
} else {
mapAttr.put("FontSize", runOld.getFontSize());
}
mapAttr.put("Subscript", runOld.getSubscript());
mapAttr.put("Underline", runOld.getUnderline());
mapAttr.put("FontFamily", runOld.getFontFamily());
mapAttr.put("Bold", runOld.isBold());
mapAttr.put("Italic", runOld.isItalic());
return mapAttr;
}
private XWPFRun setWordXWPFRunStyle(XWPFRun runNew,
Map<String, Object> mapAttr, String text) {
runNew.setColor((String) mapAttr.get("Color"));
if ("-1".equals(mapAttr.get("FontSize").toString())) {
// 五号字只能处理为10磅,无法设置为10.5磅
runNew.setFontSize(10);
} else {
runNew.setFontSize((Integer) mapAttr.get("FontSize"));
}
runNew.setBold((boolean) mapAttr.get("Bold"));
runNew.setItalic((boolean) mapAttr.get("Italic"));
runNew.setUnderline((UnderlinePatterns) mapAttr.get("Underline"));
runNew.setText(text);
runNew.setSubscript((VerticalAlign) mapAttr.get("Subscript"));
runNew.setFontFamily((String) mapAttr.get("FontFamily"));
return runNew;
}
private Matcher matcher(String str) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}",
Pattern.CASE_INSENSITIVE);
Matcher matcher = pattern.matcher(str);
return matcher;
}
此处的难点在于需要保留模板当中的字体格式,用了两个方法做了这件事情mapAttr = getWordXWPFRunStyle(run);先获取,放到map中,然后再通过setWordXWPFRunStyle(runNew, mapAttr, runText)set进去,但是如果字号设置是五号字,而不是pt的话就会出现异常值-1,需要处理,但是又无法设置10.5磅(五号字),所以这边是有小问题的,不知道是否还有更好的办法。
往表格里面写入数据
this.replaceInTable(doc, tblData, paraData);
private void replaceInTable(XWPFDocument doc, List<String[]> listData, Map paraData) {
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
while (iterator.hasNext()) {
table = iterator.next();
rows = table.getRows();
if (this.matcher(table.getText()).find()) {
// 模板固定表格赋值(根据${}匹配赋值)
for (XWPFTableRow row : rows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
this.replaceInPara(para, paraData);
}
}
}
} else {
// 模板LIST表格赋值(需手动增加行)
this.replaceInTbl(table, listData);
}
}
}
private void replaceInTbl(XWPFTable table, List<String[]> tableList) {
// 创建行,根据需要插入的数据添加新行,不处理表头
for (int i = 1; i < tableList.size(); i++) {
XWPFTableRow targetRow = table.insertNewTableRow(i + 1);
targetRow.getCtRow().setTrPr(table.getRow(i).getCtRow().getTrPr());
List<XWPFTableCell> cellList = table.getRow(i).getTableCells();
XWPFTableCell targetCell = null;
for (XWPFTableCell sourceCell : cellList) {
targetCell = targetRow.addNewTableCell();
targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
targetCell.getParagraphs().get(0).getCTP().setPPr(
sourceCell.getParagraphs().get(0).getCTP().getPPr());
}
}
// 遍历表格插入数据
List<XWPFTableRow> rows = table.getRows();
for (int i = 1; i < rows.size(); i++) {
XWPFTableRow newRow = table.getRow(i);
List<XWPFTableCell> cells = newRow.getTableCells();
for (int j = 0; j < cells.size(); j++) {
XWPFTableCell cell = cells.get(j);
cell.setText(tableList.get(i - 1)[j]);
}
}
//如果合计行需要合并单元格的话
mergeCellsHorizontal(table, tableList.size(), 0, 2);
}
public void mergeCellsHorizontal(XWPFTable table, int row, int fromCell,
int toCell) {
for (int cellIndex = fromCell; cellIndex <= toCell; cellIndex++) {
XWPFTableCell cell = table.getRow(row).getCell(cellIndex);
if (cellIndex == fromCell) {
// The first merged cell is set with RESTART merge value
cell.getCTTc().addNewTcPr().addNewHMerge()
.setVal(STMerge.RESTART);
} else {
// Cells which join (merge) the first one, are set with CONTINUE
cell.getCTTc().addNewTcPr().addNewHMerge()
.setVal(STMerge.CONTINUE);
}
}
}
这边就需要根据模板当中是否有配置${}来进行区分了,是固定表格还是需要手动增加行数的,如果是固定表格的话,只需要和paragraph一样赋值就行了,关键就是获取一个个单元格。而非固定表格,在增加行的时候遇到挺多坑的,有用过table.createRow(),但是创建之后是没有边框的;有用过table.addNewRowBetween(),但是最新的poi jar包根本没有实现这个方法QAQ,所以根本不起作用;有用过table.addRow(),空行是创建成功了,但是写数据的时候全部写进同一个row里面了。。最后查到了上面的方法,创建newRow,再创建newCell,然后复制模板行的每一个单元格的格式,最后根据需求是否合并单元格,针对表格当中居左居中还是居右没有做处理
最后输出流,关闭输入输出流
OutputStream os = new FileOutputStream("D:\\XXX.docx");
doc.write(os);
this.close(os);
this.close(is);
private void close(InputStream is) {
if (is != null) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void close(OutputStream os) {
if (os != null) {
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
———————————————– 完 ——————————————–