POI操作word填充数据,合并多个word为一个,遇到一些问题的解决

POI操作word填充数据,合并多个word为一个,遇到一些问题的解决

最近搞一个向word模板中替换占位符 填充数据,然后将多个word合并在一起的方法。网上一搜有很多资料,现在在这儿对过程中遇到的一些问题进行描述。 测试工程在这儿需要的可以点击下载.包括所需要的所有jar包和测试程序和word模板等。

问题描述

java 调用poi操作word,我用的是2007以上的word,格式是.docx,不适用于2007以前的.doc 格式的 word ,遇到的问题主要有三个:

  1. word段落中的占位符替换不成功
  2. word表格中的占位符替换之后样式丢失
  3. word合并,设置分页符之后,出现空白页的情况

问题分析

  1. 占位符替换不成功,主要是因为 段落的中的占位符 解析成块时 被拆分开了,所以没匹配上。
    在这里插入图片描述
    解决办法是,对段落XWPFParagraph进行解析的时候,对解析出来的块XWPFRun的内容进行判断,若存在占位符的开始标记,则开始存储块XWPFRun,并将块的内容进行拼接,最后遇到占位符的结束标记,则将存储的块 保留第一个,后面的块的内容都置为空,再将拼接的内容进行占位符替换 后赋给保留的第一个块。详细逻辑 看后面代码。

  2. 表格中的替换,之前使用的是 在遍历到表格的单元格的时候,直接将内容清空,然后将替换好的内容填充进去,导致样式丢失了,所以不能这样替换。应该是遍历到单元格后,获取单元格中段落,调用段落中的替换占位符的方法,这样就能够保留表格内容的样式了。
    相关代码如下:

/**
		 * 替换表格里面的变量
		 * @param doc 要替换的文档
		 * @param params 参数
		 */
		private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {
			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();
				for (XWPFTableRow row : rows) {
					cells = row.getTableCells();
					for (XWPFTableCell cell : cells) {
						
						//这种方法会导致表格中的格式丢失
						/*String cellTextString = cell.getText();
	                    for (Entry<String, Object> e : params.entrySet()) {
	                        if (cellTextString.contains("${"+e.getKey()+"}"))
	                            cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());
	                    }
	                    cell.removeParagraph(0);
	                    if(cellTextString.contains("${") && cellTextString.contains("}")){
	                    	cellTextString = "";
	                    }
	                    cell.setText(cellTextString);*/
						
						//调用段落替换占位符的方式
						paras = cell.getParagraphs();
						for (XWPFParagraph para : paras) {
							replaceInPara(para, params);
						}
						
					}
				}
			}
		}
  1. 拼接word,想要的效果是另起一页进行拼接,所以设置分页符(不设置分页符则是内容处进行拼接),但是会出现,空白页的情况。原因是 设置分页符时,会在页眉页脚增加一些东西,若果你这页内容比较满,设置分页符之后,就超出了一页,拼接时候 再另起一页,中间就空白了一页。解决方式是,将模板内容不要设置太满。但是内容会因为我填充的数据进行变化,所以不好控制。我最后发现,设置分页符拼接后的word页眉有个标记,这个标记就是控制分页的,所以我将这个标记复制到我的模板的页眉上,然后我程序中就不设置分页符,拼接后的word也是另起一页的效果,也不会出现空白页了。
    分页符这个就是控制分页的分页符

相关代码如下:

//两个对象进行追加
	   public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {
		   //设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异
		   //解决方法是,在模板头上增加分页符
		   //XWPFParagraph p = document2.createParagraph();
		   //p.setPageBreak(true);	
		   CTBody src1Body = document1.getDocument().getBody();
		   CTBody src2Body = document2.getDocument().getBody();
		    XmlOptions optionsOuter = new XmlOptions();
		    optionsOuter.setSaveOuter();
		    String appendString = src2Body.xmlText(optionsOuter);
		    String srcString = src1Body.xmlText();
		    String prefix = srcString.substring(0,srcString.indexOf(">")+1);
		    String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));
		    String sufix = srcString.substring( srcString.lastIndexOf("<") );
		    String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
		    CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);
		    src1Body.set(makeBody);
		    return document1;
		}

最后整合

最后将整个逻辑调整了下,做了测试感觉还行,问题是都解决了,但是后续还要和项目整合,还需要将一些方法给拆开。上面给了 测试工程的下载链接,资源都是网上找的。
模板:
在这里插入图片描述
替换后的效果:
在这里插入图片描述

下面是完整代码:

package com.cn.gwssi.demo;

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.poi.POIXMLDocument;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.apache.xmlbeans.XmlOptions;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTBody;


/**
 * 对word模板填充数据,并合并成一个word
 */
public class POIMergeDocUtil {
	
	public static void main(String[] args) throws Exception {
		List<String> paths = new ArrayList<String>();
		paths.add("d:\\1.docx");
		paths.add("d:\\2.docx");
		paths.add("d:\\3.docx");
	   
		Map<String, String> param = new  HashMap<String, String>();
		param.put("username", "梅西");
		param.put("country", "阿根廷");
		param.put("type", "挖煤");
		
		String destDocx = "d:\\test.docx";
		
		exportWordUtils(paths,param,destDocx);
		System.out.println("执行成功=====");
	}
	
	/**
	 * 对多个word模板填充数据,并合并成一个word输出
	 * @param paths
	 * @param param
	 * @param destDocx
	 * @throws Exception
	 */
	public static void exportWordUtils(List<String> paths,Map<String,String> param,String destDocx) throws Exception{
	   OutputStream dest = null;
	   List<XWPFDocument> xwpfDocuments = new ArrayList<XWPFDocument>();
	   //循环向word填充数据
	   for (String path : paths) {
		   XWPFDocument xwpfDocument = generateWord(param,path);
		   xwpfDocuments.add(xwpfDocument);
	   }
	   
	   //合并word
	   XWPFDocument xwpfDocument = xwpfDocuments.get(0);
	   for (int i = 1; i < xwpfDocuments.size(); i++) {
		   xwpfDocument=mergeWord(xwpfDocument,xwpfDocuments.get(i));	
	   }
	   
	   //输出word
	   dest = new FileOutputStream(destDocx);
	   xwpfDocument.write(dest);
	   
	   IOUtils.closeQuietly(dest);
   }
	   /**
	    * 替换word占位符的内容
	    * @param param
	    * @param filePath
	    * @return
	    */
	   public static XWPFDocument generateWord(Map<String, String> param, String filePath) {
		   XWPFDocument doc = null;
		   try {
			   doc = new XWPFDocument(POIXMLDocument.openPackage(filePath));
			   if (param != null && param.size() > 0){
				   //处理段落
				   replaceInPara(doc, param);
				   //替换表格里面的变量
				   replaceInTable(doc, param);
			   }
		   } catch (Exception e) {
			   e.printStackTrace();
		   }
		   return doc;
	   }
	   
	   /**
		 * 遍历word段落信息
		 * @param doc 要替换的文档
		 * @param params 参数
		 */
		private static void replaceInPara(XWPFDocument doc, Map<String, String> params) {
			Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
			XWPFParagraph para;
			while (iterator.hasNext()) {
				para = iterator.next();
				replaceInPara(para, params);
			}
		}
		
		/**
		 * 替换段落里面的变量
		 * @param para 要替换的段落
		 * @param params 参数
		 */
		private static void replaceInPara(XWPFParagraph para, Map<String, String> param) {
			List<XWPFRun> runs;
			String tempString = "";
			char lastChar = ' ';
			if (matcher(para.getParagraphText()).find()) {
				runs = para.getRuns();
				Set<XWPFRun> runSet = new HashSet<XWPFRun>();
				for (XWPFRun run : runs) {
	         	   	String text = run.getText(0);
	         	   	System.out.println("=======>"+text);
	                if(text==null)continue;
	                text = replaceText(text,param);
	                run.setText("",0);
	                run.setText(text,0);
	                  
	                for(int i=0;i<text.length();i++){
	             	   char ch = text.charAt(i);
	                    if(ch == '$'){
	                       runSet = new HashSet<XWPFRun>();
	                       runSet.add(run);
	                       tempString = text;
	                    }else if(ch == '{'){
	                 	   if(lastChar == '$'){
	                 		   if(runSet.contains(run)){
	                    				  
	                 		   }else{
	                 			   runSet.add(run);
	                 			   tempString = tempString+text;
	                 		   }
	                 	   }else{
	                 		   runSet = new HashSet<XWPFRun>(); 
	                 		   tempString = "";
	                 	   }
	                    }else if(ch == '}'){
	                 	   if(tempString!=null&&tempString.indexOf("${")>=0){
	                 		   if(runSet.contains(run)){
	                				  
	                 		   }else{
	                 			   runSet.add(run);
	                 			   tempString = tempString+text;
	                 		   }
	                 	   }else{
	                 		   runSet = new HashSet<XWPFRun>(); 
	                 		   tempString = ""; 
	                 	   }
	                 	   if(runSet.size()>0){
	                 		   String replaceText = replaceText(tempString,param);
	                 		   if(!replaceText.equals(tempString)){
	                 			   int index = 0;
	                 			   XWPFRun aRun = null;
	                 			   for(XWPFRun tempRun:runSet){
	                 				   tempRun.setText("",0);
	                 				   if(index==0){
	                 					   aRun = tempRun;
	                 				   }
	                 				   index++;
	                 			   }
	                 			   aRun.setText(replaceText,0);
	                 		   }
	                 		   runSet = new HashSet<XWPFRun>(); 
	                 		   tempString = ""; 
	                 	   }
	                    }else{
	                 	   if(runSet.size()<=0)continue;
	                 	   if(runSet.contains(run))continue;
	                 	   runSet.add(run);
	                 	   tempString = tempString+text;
	                    }
	                    lastChar = ch;
	                }
	            }
				
				//这种方法会导致占位符被拆开解析,不能识别替换掉
				/*for (int i = 0; i < runs.size(); i++) {
					XWPFRun run = runs.get(i);
					String runText = run.toString();
					System.out.println("====>run:"+runText);
					matcher = this.matcher(runText);
					if (matcher.find()) {
						while ((matcher = this.matcher(runText)).find()) {
							runText = matcher.replaceFirst(String.valueOf(param.get(matcher.group(1))));
						}
						// 直接调用XWPFRun的setText()方法设置文本时,在底层会重新创建一个XWPFRun,把文本附加在当前文本后面,
						// 所以我们不能直接设值,需要先删除当前run,然后再自己手动插入一个新的run。
						para.removeRun(i);
						if(runText.equals("null")){
							runText="";
						}
						para.insertNewRun(i).setText(runText);
					}
				}*/
			}
		}
		
		/**
		 * 替换表格里面的变量
		 * @param doc 要替换的文档
		 * @param params 参数
		 */
		private static void replaceInTable(XWPFDocument doc, Map<String, String> params) {
			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();
				for (XWPFTableRow row : rows) {
					cells = row.getTableCells();
					for (XWPFTableCell cell : cells) {
						
						//这种方法会导致表格中的格式丢失
						/*String cellTextString = cell.getText();
	                    for (Entry<String, Object> e : params.entrySet()) {
	                        if (cellTextString.contains("${"+e.getKey()+"}"))
	                            cellTextString = cellTextString.replace("${"+e.getKey()+"}", e.getValue().toString());
	                    }
	                    cell.removeParagraph(0);
	                    if(cellTextString.contains("${") && cellTextString.contains("}")){
	                    	cellTextString = "";
	                    }
	                    cell.setText(cellTextString);*/
						
						//调用段落替换占位符的方式
						paras = cell.getParagraphs();
						for (XWPFParagraph para : paras) {
							replaceInPara(para, params);
						}
						
					}
				}
			}
		}
		
		/**
		 * 替换占位符
		 * @param text
		 * @param map
		 * @return
		 */
	   	private static String replaceText(String text, Map<String, String> map) {
			if(text != null){
				/*for (Entry<String, String> entry : map.entrySet()) {
					if (text.contains("${"+entry.getKey()+"}")){
						text = text.replace("${"+entry.getKey()+"}", entry.getValue().toString());
					}
				}*/
				
				Matcher matcher = matcher(text);
				if (matcher.find()) {
					while ((matcher = matcher(text)).find()) {
						text = matcher.replaceFirst(String.valueOf(map.get(matcher.group(1))));
					}
					if(text.equals("null")){
						text="";
					}
				}
			}
			return text;
		}
		
		/**
		 * 正则匹配字符串
		 * @param str
		 * @return
		 */
		private static Matcher matcher(String str) {
			Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}", Pattern.CASE_INSENSITIVE);
			Matcher matcher = pattern.matcher(str);
			return matcher;
		}
	   
	   //两个对象进行追加
	   public static XWPFDocument mergeWord(XWPFDocument document1,XWPFDocument document2) throws Exception {
		   //设置分页符---当刚好一页数据时,会导致出现空白页,后面出现分页符导致格式有一点差异
		   //解决方法是,在模板头上增加分页符
		   //XWPFParagraph p = document2.createParagraph();
		   //p.setPageBreak(true);	
		   CTBody src1Body = document1.getDocument().getBody();
		   CTBody src2Body = document2.getDocument().getBody();
		    XmlOptions optionsOuter = new XmlOptions();
		    optionsOuter.setSaveOuter();
		    String appendString = src2Body.xmlText(optionsOuter);
		    String srcString = src1Body.xmlText();
		    String prefix = srcString.substring(0,srcString.indexOf(">")+1);
		    String mainPart = srcString.substring(srcString.indexOf(">")+1,srcString.lastIndexOf("<"));
		    String sufix = srcString.substring( srcString.lastIndexOf("<") );
		    String addPart = appendString.substring(appendString.indexOf(">") + 1, appendString.lastIndexOf("<"));
		    CTBody makeBody = CTBody.Factory.parse(prefix+mainPart+addPart+sufix);
		    src1Body.set(makeBody);
		    return document1;
		}
	}

  • 9
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值