最近一个项目,需要根据word模板,写入相应的数据,生成新的文件。运用最基础的poi写word文件。本次项目遇到了多种形式的word内容,

第一:基础的paragraph内容;

第二:基于表格的paragraph内容(固定表格);

第三:list表格(动态表格);

如下图:

怎么将模板ova变成ovf 模板生成word_word


前两个的核心思想都是运用正则查找到需要写入数据的地方(用${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();  
     }  
  }  
}

———————————————– 完 ——————————————–