近期碰到一个稍微头疼的需求,将word模版中的参数替换为实际值,其中包括段落、列表(行数不够时自动递增)、页眉;本文以docx文档为例,其中代码有其他地方参考,如有冒犯,还请海涵;
模版:
实现效果:
模版替换规则(可自定义):
规则:
a、类型 LIST--列表下拉;DIC--字典转为多选框;{字段名}--此为主表普通字段名;
b、格式 {#数据源名(或字典名)##字段名##类型#}
c、列表下拉 例 {#GZ##XZGX_XZGXLX_NAME##LIST##false#}--false 表示不换行,true表示换行
d、字典转为多选框 例 {#HR_ZPGL_FBZT##RCK_HHZYY##DIC##false#}
注意事项:1、本文采用poi 4.0.x,具体原因,查看API http://poi.apache.org/help/faq.html
2、模版中的{#YESORNO##YSSQB_ZSSQTY_CODE##DIC##false#} 不要直接在word上书写,要从别的地方粘贴,这样不会出现解析在多个XWPFRun中的问题
以下为代码参考:
@Override
public InputStream expFile(DynaBean template, DynaBean dataBean, Map dataSource, JSONObject returnObj) {
String fileKey="";
if(StringUtil.isNotEmpty(template.getStr("OFFICETEM_FILE"))){
fileKey=template.getStr("OFFICETEM_FILE").split("\\*")[1];
}
if(StringUtil.isEmpty(fileKey)){
returnObj.put("errorMsg","未上传模版文件!");
return null;
}
FileBO downloadFileBO=documentBusService.readFile(fileKey);
if(downloadFileBO==null){
returnObj.put("errorMsg","模版文件未找到!");
return null;
}
//文件名
String fileName = template.getStr("OFFICETEM_NAME")+ DateUtils.formatDate(new Date(),"yyyyMMdd")+".docx";
returnObj.put("fileName",fileName);
InputStream downloadFile=downloadFileBO.getFile();
try {
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
XWPFDocument doc = new XWPFDocument(downloadFile);
//替换页眉中的变量
this.replaceHeaderOverWrite(doc,dataBean,dataSource);
//替换段落里面的变量
this.replaceInParaOverWrite(doc,dataBean,dataSource);
//替换表格里面的变量
this.replaceInTableOverWrite(doc,dataBean,dataSource);
doc.write(outputStream);
doc.close();
downloadFile.close();
//输出转输入
ByteArrayInputStream inputStream = IoUtil.toStream(outputStream.toByteArray());
return inputStream;
} catch (Exception e) {
e.printStackTrace();
returnObj.put("errorMsg","模版文件未找到!");
}
}
/***
*功能描述
* 替换页眉中的内容
* @author wenzhe.zhou
* @date 2020/5/25
* @param doc
* @param dynaBean
* @param dataSource
* @return void
*/
private void replaceHeaderOverWrite(XWPFDocument doc,DynaBean dynaBean,Map dataSource) throws Exception {
List<XWPFHeader> xwpfHeaderList = doc.getHeaderList();
Iterator iterator = xwpfHeaderList.iterator();
XWPFParagraph para;
XWPFHeader xwpfHeader;
while (iterator.hasNext()) {
xwpfHeader = (XWPFHeader) iterator.next();
List<XWPFParagraph> xwpfParagraphList = xwpfHeader.getParagraphs();
Iterator iteratorPara = xwpfParagraphList.iterator();
while (iteratorPara.hasNext()){
para = (XWPFParagraph) iteratorPara.next();
this.replaceInParaOverWrite(para,dynaBean,dataSource);
}
}
}
/***
*功能描述
* 替换段落里面的变量
* @author wenzhe.zhou
* @date 2020/5/9
* @param doc 要替换的文档
* @param dynaBean 数据源
* @param dataSource
* @return void
*/
private void replaceInParaOverWrite(XWPFDocument doc,DynaBean dynaBean,Map dataSource) throws Exception {
Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
XWPFParagraph para;
while (iterator.hasNext()) {
para = iterator.next();
this.replaceInParaOverWrite(para,dynaBean,dataSource);
}
}
/**
*功能描述
* 替换段落里面的变量
* @author wenzhe.zhou
* @date 2020/5/9
* @param para
* @param dynaBean
* @param dataSource
* @return void
*/
private void replaceInParaOverWrite(XWPFParagraph para,DynaBean dynaBean,Map dataSource) throws Exception {
List<XWPFRun> runs;
Boolean matcher;
if (this.matcher(para.getParagraphText())) {
runs = para.getRuns();
for (int i=0; i<runs.size(); i++) {
XWPFRun run = runs.get(i);
int fontSize = run.getFontSize();
String fontFamily = run.getFontFamily();
String runText = run.toString().trim();
Boolean isDic =false;
matcher = this.matcher(runText);
if (matcher|| ObjectUtil.isNotNull(dynaBean.get(runText))) {
if ((this.matcher(runText))) {
//普通字段
if (!runText.contains("ONE")&&!runText.contains("LIST")&&!runText.contains("DIC")){
runText = runText.replaceAll("\\{","").replaceAll("}","").replaceAll("#","");
if (Objects.equals(runText.trim(),"")){
runText ="";
}else {
runText = getRangeData( dynaBean,runText.trim(),dataSource);
}
} else if (runText.contains("DIC")){
//字典
isDic = true;
}
else {
runText ="";
}
}else {
runText = getRangeData( dynaBean,runText.trim(),dataSource);
}
//直接调用XWPFRun的setText()方法设置文本时,在底层会重新创建一个XWPFRun,把文本附加在当前文本后面,
//所以我们不能直接设值,需要先删除当前run,然后再自己手动插入一个新的run。
para.removeRun(i);
XWPFRun newRun=para.insertNewRun(i);
//字典
if (isDic){
List<String> valueList = substringData(runText);
String dicCode =valueList.get(0);
String paramValue = valueList.get(1);
//是否换行
Boolean isBreak = false;
if (valueList.size()>3){
isBreak= ObjectUtil.isNotEmpty(valueList.get(3))?Boolean.valueOf(valueList.get(3)):false;
}
paramValue = getRangeData( dynaBean,paramValue.trim(),dataSource);
//设置字典
this.setCheckBox(dicCode,paramValue,newRun,isBreak);
}else {
newRun.setText(runText);
}
//设置字体大小、格式
if (ObjectUtil.isNotNull(fontFamily)){
newRun.setFontFamily(fontFamily);
}
if (fontSize>=0){
newRun.setFontSize(fontSize);
}
}
}
}
}
/***
*功能描述
* 替换表格里面的变量
* @author wenzhe.zhou
* @date 2020/5/9
* @param doc
* @param dynaBean
* @param dataSource
* @return void
*/
private void replaceInTableOverWrite(XWPFDocument doc,DynaBean dynaBean,Map dataSource) throws Exception {
//模版table
Iterator<XWPFTable> iterator = doc.getTablesIterator();
XWPFTable table;
List<XWPFTableRow> rows;
List<XWPFTableCell> cells;
List<XWPFParagraph> paras;
//循环所有的文本进行添加定位
while (iterator.hasNext()) {
List<Map<String,Object>> mapList = new ArrayList<>(16);
//要删除的row
List<Integer> removeStringList = new ArrayList<>(16);
int rowIndex =0;
table = iterator.next();
rows = table.getRows();
Iterator iteratorRow = rows.iterator();
if (CollectionUtil.isNotEmpty(dataSource)){
while (iteratorRow.hasNext()){
//是否添加
Boolean isInsert = false;
XWPFTableRow row = (XWPFTableRow) iteratorRow.next();
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
List<XWPFRun> runs = para.getRuns();
for (int i=0; i<runs.size(); i++) {
XWPFRun run = runs.get(i);
String runText = run.toString().replaceAll("\\{","").replaceAll("}","");
//此表格为下拉框时
if (runText.contains("LIST")){
List<String> codeList=substringData(runText);
//数据源编码
String funcCode = codeList.get(0);
//此为表格下拉框时
//数据源
List<Object> objects = (List<Object>) dataSource.get(funcCode);
if (CollectionUtil.isNotEmpty(objects)&&!isInsert){
for (int j=0;j<objects.size();j++){
Map<String,Object> mapData = new HashMap<>(16);
mapData.put("data",objects.get(j));
mapData.put("table",table);
mapData.put("row",row);
mapData.put("rowIndex",rowIndex);
mapList.add(mapData);
rowIndex++;
}
isInsert = true;
removeStringList.add(rowIndex);
}
}
}
}
}
rowIndex++;
}
}
//添加row 并替换内容
if (CollectionUtil.isNotEmpty(mapList)){
for (Map map:mapList){
Map<String,Object> data = (Map<String, Object>) map.get("data");
XWPFTable xwpfTable = (XWPFTable) map.get("table");
XWPFTableRow row = (XWPFTableRow) map.get("row");
int rowNum = (int) map.get("rowIndex");
copyRowOverWrite(xwpfTable,row,rowNum,data);
}
}
//删除row--删除用来标记的row
if (CollectionUtil.isNotEmpty(removeStringList)){
int afterRemoveNumber = 0;
for (Integer removeIndex:removeStringList){
table.removeRow(removeIndex-afterRemoveNumber);
afterRemoveNumber++;
}
}
//循环所有的文本进行替换
List<XWPFTableRow> newrows = table.getRows();
//遍历表格,替换模版
for (XWPFTableRow row : newrows) {
cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
paras = cell.getParagraphs();
for (XWPFParagraph para : paras) {
this.replaceInParaOverWrite(para,dynaBean,dataSource);
}
}
}
}
}
/*****
*功能描述
* 复制行
* @author wenzhe.zhou
* @date 2020/5/15
* @param table
* @param sourceRow
* @param rowIndex
* @param data
* @return void
*/
public void copyRowOverWrite(XWPFTable table,XWPFTableRow sourceRow,int rowIndex,Map<String,Object> data) throws Exception {
//在表格指定位置新增一行
XWPFTableRow targetRow = table.insertNewTableRow(rowIndex);
//复制行属性
targetRow.getCtRow().setTrPr(sourceRow.getCtRow().getTrPr());
List<XWPFTableCell> cellList = sourceRow.getTableCells();
if (null == cellList) {
return;
}
//复制列及其属性和内容
XWPFTableCell targetCell = null;
for (XWPFTableCell sourceCell : cellList) {
Boolean isDic = false;
//字段名
String key ="";
//数据源名或者字典名
String dataSourceName = "";
//字典是否换行
Boolean isBreak =false;
String runText = sourceCell.getText().replaceAll("\\{","").replaceAll("}","");
if (runText.contains("DIC")){
isDic = true;
}
List<String> valueList = substringData(runText);
if (CollectionUtil.isNotEmpty(valueList)){
dataSourceName = valueList.get(0);
key = valueList.get(1);
if (valueList.size()>3){
isBreak = ObjectUtil.isNotEmpty(valueList.get(3))?Boolean.valueOf(valueList.get(3)):false;
}
}
targetCell = targetRow.addNewTableCell();
//列属性
targetCell.getCTTc().setTcPr(sourceCell.getCTTc().getTcPr());
//段落属性
if(sourceCell.getParagraphs()!=null&&sourceCell.getParagraphs().size()>0){
targetCell.getParagraphs().get(0).getCTP().setPPr(sourceCell.getParagraphs().get(0).getCTP().getPPr());
if(sourceCell.getParagraphs().get(0).getRuns()!=null&&sourceCell.getParagraphs().get(0).getRuns().size()>0){
XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
//字典
if (isDic){
//数据源中的值
String paramValue = ObjectUtil.isNotEmpty(data.get(key))?data.get(key).toString():"";
//字典
this.setCheckBox(dataSourceName,paramValue,cellR,isBreak);
cellR.setBold(sourceCell.getParagraphs().get(0).getRuns().get(0).isBold());
}else {
cellR.setText(ObjectUtil.isNotEmpty(data.get(key))?data.get(key).toString():"");
cellR.setBold(sourceCell.getParagraphs().get(0).getRuns().get(0).isBold());
}
}else{
targetCell.setText(ObjectUtil.isNotEmpty(data.get(key))?data.get(key).toString():"");
}
}else{
targetCell.setText(ObjectUtil.isNotEmpty(data.get(key))?data.get(key).toString():"");
}
}
}
/***
*功能描述
* @author wenzhe.zhou
* @date 2020/5/22
* @param dicCode 字典CODE
* @param paramValue 此字典项对应得值
* @param newRun 当前的 XWPFRun
* @return void
*/
public void setCheckBox(String dicCode,String paramValue,XWPFRun newRun,Boolean isBreak) throws Exception {
List<CTSym> ctSymList=newRun.getCTR().getSymList();
List<Map<String,Object>> runTexts = getDicDataText(dicCode);
for (Map<String,Object> map:runTexts){
String str = (String) map.get("text");
String val = (String) map.get("code");
String charStr ="00A3";
try{
//判断选中
if (paramValue.contains(val)){
charStr ="0052";
}
CTSym ctSym=getCTSym("Wingdings 2",charStr);
ctSymList.add(ctSym);
}catch (Exception e){
throw e;
}
newRun.setText(str+" ");
if (isBreak){
newRun.addBreak();
}
}
}
/****
*功能描述
* 获取字典值
* @author wenzhe.zhou
* @date 2020/5/20
* @param dicName
* @return java.util.List<java.util.Map<java.lang.String,java.lang.Object>>
*/
private List<Map<String,Object>> getDicDataText(String dicName) {
List<Map<String,Object>> resultList = new ArrayList<>(16);
//获取此数据字典
List<DictionaryItemVo> dicList=dictionaryManager.getDicList(dicName,new QueryInfo(),false);
if (CollectionUtil.isNotEmpty(dicList)){
for (DictionaryItemVo dictionaryItemVo:dicList){
Map<String,Object> map = new HashMap<>(16);
String text = dictionaryItemVo.getText();
String code =dictionaryItemVo.getCode();
map.put("text",text);
map.put("code",code);
resultList.add(map);
}
}
return resultList;
}
/**
*功能描述
* 截取字符串
* "#表##值##类型#"
* @author wenzhe.zhou
* @date 2020/5/20
* @param text
* @return java.lang.String
*/
public List<String> substringData(String text){
String regex = "#([^#]+)#";
List<String> paramList = new ArrayList<>(16);
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
paramList.add(matcher.group(1));
}
return paramList;
}
/***
*功能描述
*添加特殊字符的xml
* @author wenzhe.zhou
* @date 2020/5/20
* @param wingType
* @param charStr
* @return org.openxmlformats.schemas.wordprocessingml.x2006.main.CTSym
*/
public CTSym getCTSym(String wingType, String charStr) throws Exception {
CTSym sym = CTSym.Factory
.parse("<xml-fragment w:font=\""
+ wingType
+ "\" w:char=\""
+ charStr
+ "\" xmlns:w=\"http://schemas.openxmlformats.org/wordprocessingml/2006/main\" xmlns:wne=\"http://schemas.microsoft.com/office/word/2006/wordml\"> </xml-fragment>");
return sym;
}
/***
*功能描述
* 验证格式
* @author wenzhe.zhou
* @date 2020/5/9
* @param str
* @return java.lang.Boolean
*/
private Boolean matcher(String str) {
return str.contains("{")||str.contains("}");
}
/***
*功能描述
* 获取要填充的数据
* @author wenzhe.zhou
* @date 2020/5/9
* @param dataBean
* @param key
* @param dataSource
* @return java.lang.String
*/
private String getRangeData(DynaBean dataBean,String key,Map dataSource){
//自己实现数据逻辑
return “”;
}