Java生成复杂word文档 结合freemarker+docx4j+POI

        简单说下java生成word文档的各个组件优缺点(详细网上有很多),POI、JXL等过于原生,如果制作简单的几页word文档还能接受,如果文档十几二十页。。。会头疼死,并且word一旦大了以后,样式很容易乱,很不美观;freemarker利用模板生成word文档,开发相对简单,但是freemarker是利用xml标签传入模板的,一旦在模板里加了部分标签(例如 list),模板就不可以用office打开了,再去调整模板样式,或者增加内容,又或者需要增加很多list,会无从下手,除非对xml标签很熟练。。。。

所以以下所有工作均是针对复杂的word文档

        首先说下报告制作的总体思路:使用freemarker进行模板式开发,以数据列表为界限(或者章节,具体以业务为准)将word模板拆分为多个模板,方便后期业务要求调整模板,docx4j进行多个文档合并,POI调整文档细节或者插入图片等。

注意事项:

  1. 报告模板划分时,以数据列表为界限(章节也可以),避免出现单个模板中存在复杂word内容。
  2. Freemarker生成分word文档时,一定要生成docx格式,即2007版以上的word文档。
  3. Freemarker生成docx与doc文档的模板获取方法不同。
  4. 文档合并方法支持生成doc和docx格式,建议生成docx,方便POI或freemarker再调整文档格式(实际上我是先生成各个word模板,然后分别插入图片或调整样式,最后合并文档)。
  5. 项目使用时,注意方法中使用的临时路径信息可用。

下面是封装的相关方法(都是我实际代码用的,拷贝时请稍加调整):

1、创建单个docx文档:

/**
	 * 
	 * 

描述: 生成docx文件

* @author XXX * @date 2018年8月13日 * @param templateName 模板名称 (请勿带后缀) * @param templatePath 模板路径:请从templates下开始填写 * @param userCode 用户名称:用以区分生成的文件 * @param sign 标记创建报告的类型 (gjfs-国检分省,) * @param date 报告创建日期:请精确至秒 * @param data 报告填充的数据 * @return * @throws FileNotFoundException */ public static String creatDocx(String templateName,String templatePath,String userCode,String sign,String date,Map data) throws FileNotFoundException { //获取跟目录 File path = new File(ResourceUtils.getURL("classpath:").getPath()); //如果上传目录为/static/images/upload/,则可以如下获取: File upload = new File(path.getAbsolutePath(),"templates/"+templatePath); if(!upload.exists()) upload.mkdirs(); System.out.println("upload url:"+upload.getAbsolutePath()); // 路径 String templatepath = upload.getAbsolutePath(); String docxname = "test.docx";//空白docx文件即可 //String templateName = "test.xml";模板名称 //结果文件 String resxmlpath = "d:/report/"+userCode+"/sign/"+templateName+"(date).xml"; String reswordpath = "d:/report/"+userCode+"/sign/"+templateName+"(date).docx"; //如果文件不存在则自动创建 File xmlFile = new File("d:/report/"+userCode+"/sign/"); File docxFile = new File("d:/report/"+userCode+"/sign/"); if(!xmlFile.exists()) xmlFile.mkdirs(); if(!docxFile.exists()) docxFile.mkdirs(); // 生成文档 try { File file = generate(templatepath, docxname, templateName+".xml", resxmlpath, reswordpath, data); return reswordpath; } catch (Exception e) { e.printStackTrace(); } return null; } private static File generate(String templatepath, String docxname, String xmlname, String resxmlpath, String reswordpath, Map param) throws Exception { Configuration cfg = new Configuration(); cfg.setDirectoryForTemplateLoading(new File(templatepath)); Template template = cfg.getTemplate(xmlname); template.setOutputEncoding("UTF-8"); Writer out = new FileWriter(new File(resxmlpath)); // 数据放到模板xml里面,生成带数据的xml template.process(param, out); if (out != null) { out.close(); } // 带数据的xml生成docx File file = new File(resxmlpath); File docxFile = new File(templatepath + "/" + docxname); ZipFile zipFile = new ZipFile(docxFile); Enumeration zipEntrys = zipFile.entries(); ZipOutputStream zipout = new ZipOutputStream(new FileOutputStream(reswordpath)); int len = -1; byte[] buffer = new byte[1024]; while (zipEntrys.hasMoreElements()) { ZipEntry next = zipEntrys.nextElement(); InputStream is = zipFile.getInputStream(next); // 把输入流的文件传到输出流中 如果是word/document.xml由我们输入 zipout.putNextEntry(new ZipEntry(next.toString())); if ("word/document.xml".equals(next.toString())) { InputStream in = new FileInputStream(file); while ((len = in.read(buffer)) != -1) { zipout.write(buffer, 0, len); } in.close(); } else { while ((len = is.read(buffer)) != -1) { zipout.write(buffer, 0, len); } is.close(); } } zipout.close(); return new File(reswordpath); }

2、合并多个docx文档:

/**
	 * 
	 * 

描述:合并多个docx文件

* @author 范相如 * @date 2018年8月13日 * @param list 分文档路径地址 * @param path 合并后最终文档路径 * @return */ public static File mergeDocx(List list,String path){ List inList=new ArrayList(); for(int i=0;i streams) throws Docx4JException, IOException { WordprocessingMLPackage target = null; final File generated = File.createTempFile("generated", ".docx"); int chunkId = 0; Iterator it = streams.iterator(); while (it.hasNext()) { InputStream is = it.next(); if (is != null) { if (target == null) { // Copy first (master) document OutputStream os = new FileOutputStream(generated); os.write(IOUtils.toByteArray(is)); os.close(); target = WordprocessingMLPackage.load(generated); } else { // Attach the others (Alternative input parts) insertDocx(target.getMainDocumentPart(), IOUtils.toByteArray(is), chunkId++); } } } if (target != null) { target.save(generated); return new FileInputStream(generated); } else { return null; } } private static void saveTemplate(InputStream fis,String toDocPath){ FileOutputStream fos; int bytesum = 0; int byteread = 0; try { fos = new FileOutputStream(toDocPath); byte[] buffer = new byte[1444]; while ( (byteread = fis.read(buffer)) != -1) { bytesum += byteread; //字节数 文件大小 fos.write(buffer, 0, byteread); } fis.close(); fos.close(); } catch (FileNotFoundException e1) { e1.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } } // 插入文档 private static void insertDocx(MainDocumentPart main, byte[] bytes, int chunkId) { try { AlternativeFormatInputPart afiPart = new AlternativeFormatInputPart( new PartName("/part" + chunkId + ".docx")); // afiPart.setContentType(new ContentType(CONTENT_TYPE)); afiPart.setBinaryData(bytes); Relationship altChunkRel = main.addTargetPart(afiPart); CTAltChunk chunk = Context.getWmlObjectFactory().createCTAltChunk(); chunk.setId(altChunkRel.getId()); main.addObject(chunk); } catch (Exception e) { e.printStackTrace(); } }

3、创建图片(使用的jfree):

/**
	 * 
	* 

描述: 创建报告所需柱状图

* @author XXX * @date 2018年8月21日 * @param pqi * @param pci * @param rqi * @param rdi * @param userCode */ public static String createImg(String pqi,String pci,String rqi,String rdi,String userCode) { //数据集 DefaultCategoryDataset dataSet = new DefaultCategoryDataset(); dataSet.addValue(Double.valueOf(pqi), "", "PQI"); dataSet.addValue(Double.valueOf(pci), "", "PCI"); dataSet.addValue(Double.valueOf(rqi), "", "RQI"); if(null!=null || !rdi.equals("")) { dataSet.addValue(Double.valueOf(rdi), "", "RDI"); } // 柱状图 JFreeChart jfreeChart = ChartFactory.createBarChart ("", "", "", dataSet, PlotOrientation.VERTICAL, false,false, false); CategoryPlot plot = jfreeChart.getCategoryPlot(); // 初始化柱子颜色 String[] colorValues = getColors( pqi,pci,rqi,rdi); CustomRender renderer = new CustomRender(colorValues); renderer.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator()); renderer.setBaseItemLabelsVisible(true); renderer.setBaseItemLabelPaint(Color.BLACK);//设置数值颜色,默认黑色 // 设置柱子宽度 renderer.setMaximumBarWidth(0.3); plot.setRenderer(renderer);//将修改后的属性值保存到图中 // 设置总的背景颜色 jfreeChart.setBackgroundPaint(ChartColor.WHITE); // 获得图表对象 CategoryPlot p = jfreeChart.getCategoryPlot(); // 设置图的背景颜色 p.setBackgroundPaint(ChartColor.WHITE); // 设置表格线颜色 p.setRangeGridlinePaint(ChartColor.BLACK); try{ // 保存图片到指定文件夹"d:/report/"+userCode+"/sign/" ChartUtilities.saveChartAsPNG(new File("d:/report/"+userCode+"/sign/BarChart.png"), jfreeChart, 500, 300); return "d:/report/"+userCode+"/sign/BarChart.png"; } catch (Exception e){ System.err.println("Problem occurred creating chart."); } return ""; }

4、根据特殊字符替换为图片:

/**
	 * 替换word中的自定义字符串以及图片(适用于word2003+ 版本)
	 * 
	 * 注:2003版本word不支持替换图片,2007版本以上可以替换图片
	 *  
	 * @param filePath
	 * @param param
	 * @param fileName
	 * @param request
	 * @param response
	 * @return
	 */
	public static String replaceAndGenerateWord(String filePath, Map param, String fileName, 
			HttpServletRequest request, HttpServletResponse response){
		String[] sp = filePath.split("\\.");
		//判断文件是否有后缀名
		if(sp.length > 0){
			try{
				//处理docx文档 2007-2013
				if(sp[sp.length - 1].equalsIgnoreCase("docx")){ 
					CustomXWPFDocument document = null;
					OPCPackage pack = POIXMLDocument.openPackage(filePath);  
					document = new CustomXWPFDocument(pack);  
					if (param != null && param.size() > 0) {   
						//处理段落  
						List paragraphList = document.getParagraphs();  
						processParagraphs(paragraphList, param, document);                    
						//处理表格  
						Iterator it = document.getTablesIterator();  
						while (it.hasNext()) {  
							XWPFTable table = it.next();  
							List rows = table.getRows();  
							for (XWPFTableRow row : rows) {  
								List cells = row.getTableCells();  
								for (XWPFTableCell cell : cells) {  
									List paragraphListTable =  cell.getParagraphs();  
									processParagraphs(paragraphListTable, param, document);  
								}  
							}  
						} 
						createDir(tempFilePath);
						FileOutputStream fos = new FileOutputStream(new File(tempFilePath.concat(fileName)));
						document.write(fos);
						fos.flush();
						fos.close();
						doExport(fileName, tempFilePath.concat(fileName), request, response);
						return tempFilePath.concat(fileName);
					}
					//处理doc文档 97-2003
				}else if(sp[sp.length - 1].equalsIgnoreCase("doc")){
					HWPFDocument document = null;   
					document = new HWPFDocument(new FileInputStream(filePath));  
					Range range = document.getRange();  
					for (Map.Entry entry : param.entrySet()) {
						Object value = entry.getValue();
						if(value instanceof String){
							range.replaceText(entry.getKey(), entry.getValue().toString()); 
						}else if(value instanceof Map){
							//TODO word2003暂时不能处理图片
						}
					}
					createDir(tempFilePath);
					FileOutputStream fos = new FileOutputStream(new File(tempFilePath.concat(fileName)));
					document.write(fos);
					fos.flush();
					fos.close();
					doExport(fileName, tempFilePath.concat(fileName), request, response);
					return tempFilePath.concat(fileName);
				}                                       
			}catch(Exception e){
				return "fail";
			}
		}else{
			return "fail";
		}
		return "success";
	}
/** 
	 * 处理段落 
	 * @param paragraphList 
	 * @throws FileNotFoundException 
	 * @throws InvalidFormatException 
	 */  
	public static void processParagraphs(List paragraphList,Map param,CustomXWPFDocument doc) 
			throws InvalidFormatException, FileNotFoundException{  
		if(paragraphList != null && paragraphList.size() > 0){   
			//首选循环段落
			for(XWPFParagraph paragraph:paragraphList){ 
				//获取段落的text
				boolean needDel = false;
				String text = paragraph.getText();                          
				if(text != null){
					for (Entry entry : param.entrySet()) {  
						String key = entry.getKey();                       
						Object value = entry.getValue();
						//替换                      
						if(value instanceof String){
							String text2 = text.replace(key, value.toString());
							if(!text2.equals(text)){
								needDel = true;
							}
							text = text2;
						}else if(value instanceof Map){
							if(text.indexOf(key) != -1){
								//特殊处理图片
								int length = paragraph.getRuns().size();
								//将原有的Run去掉
								if (length > 0) {               
									for (int i = (length - 1); i >= 0; i--) {
										paragraph.removeRun(i);
									}                                                       
								}
								String imgurl = (String)((Map) value).get("content");
								String type = (String)((Map) value).get("type");
								int width = (Integer) ((Map) value).get("width");
								int height = (Integer) ((Map) value).get("height");
								String blipId = doc.addPictureData(new FileInputStream(new File(imgurl)), getPictureType(type));
								doc.createPicture(blipId,doc.getNextPicNameNumber(getPictureType(type)), width, height,paragraph);                          
							}
						}
					}  
				}
				int length = paragraph.getRuns().size();
				//将原有的Run去掉(原因是paragraph将XWPFRun分割成了一个乱七八糟的数组,例:${1}$,这个获取出来的是[$,{,1,},$],不能满足我们替换的要求,这里进行特殊处理)
				if(needDel){
					if (length > 0) {               
						for (int i = (length - 1); i >= 0; i--) {
							paragraph.removeRun(i);
						}
						//在段落里面插入我们替换过后的文本
						XWPFRun newRun = paragraph.insertNewRun(0);
						newRun.setText(text, 0);
						paragraph.addRun(newRun);  
					}
				}           
			}  
		}  
	}  

	/** 
	 * 根据图片类型,取得对应的图片类型代码 
	 * @param picType 
	 * @return int 
	 */  
	private static int getPictureType(String picType){  
		int res = CustomXWPFDocument.PICTURE_TYPE_PICT;  
		if(picType != null){
			if(picType.equalsIgnoreCase("png")){  
				res = CustomXWPFDocument.PICTURE_TYPE_PNG;  
			}else if(picType.equalsIgnoreCase("dib")){  
				res = CustomXWPFDocument.PICTURE_TYPE_DIB;  
			}else if(picType.equalsIgnoreCase("emf")){  
				res = CustomXWPFDocument.PICTURE_TYPE_EMF;  
			}else if(picType.equalsIgnoreCase("jpg") || picType.equalsIgnoreCase("jpeg")){  
				res = CustomXWPFDocument.PICTURE_TYPE_JPEG;  
			}else if(picType.equalsIgnoreCase("wmf")){  
				res = CustomXWPFDocument.PICTURE_TYPE_WMF;  
			}  
		}  
		return res;  
	}  

	/**
	 * 导出
	 * 
	 * @param fileName
	 * @param filePath
	 * @param request
	 * @param response
	 */
	public static void doExport(String fileName, String filePath, HttpServletRequest request, HttpServletResponse response){
		BufferedInputStream bis = null;
		BufferedOutputStream bos = null;
		File file = null;
		//	        HttpServletResponse response = (HttpServletResponse)RpcContext.getContext().getResponse(HttpServletResponse.class); 
		try {
			file = new File(filePath);
			//	        HttpServletRequest request = (HttpServletRequest)RpcContext.getContext().getRequest(HttpServletRequest.class);
			request.setCharacterEncoding("UTF-8");
			String agent = request.getHeader("User-Agent").toUpperCase();
			if ((agent.indexOf("MSIE") > 0) || ((agent.indexOf("RV") != -1) && (agent.indexOf("FIREFOX") == -1)))
				fileName = URLEncoder.encode(fileName, "UTF-8");
			else {
				fileName = new String(fileName.getBytes("UTF-8"), "ISO8859-1");
			}
			response.setContentType("application/x-msdownload;");
			response.setHeader("Content-disposition", "attachment; filename=" + fileName);
			response.setHeader("Content-Length", String.valueOf(file.length()));
			bis = new BufferedInputStream(new FileInputStream(file));
			bos = new BufferedOutputStream(response.getOutputStream());

			byte[] buff = new byte[2048];
			int bytesRead;
			while (-1 != (bytesRead = bis.read(buff, 0, buff.length)))
				bos.write(buff, 0, bytesRead);
		}
		catch (Exception e) {
			//	          System.out.println("导出文件失败!");
		} finally {
			try {
				if (bis != null) {
					bis.close();
				}
				if (bos != null) {
					bos.close();
				}
				//file.delete();
			} catch (Exception e) {
				//	            LOGGER.error("导出文件关闭流出错!", e);
			}
		}
	} 

	/**
	 * 创建目录
	 * @param basePath
	 */
	public static void createDir(String basePath)
	{
		File file = new File(basePath);
		if (!file.exists())
			file.mkdirs();
	}

 

5、示例代码:

String file1 = DocxUtils.creatDocx("0-首页", "report/gjfs/", userCode, "", "", dataMap1);
			String file2 = DocxUtils.creatDocx("1-1.概况", "report/gjfs/", userCode, "", "", dataMap2);
			String file3 = DocxUtils.creatDocx("2-2.11概述图1表2", "report/gjfs/", userCode, "", "", dataMap3);
			String file4 = DocxUtils.creatDocx("2-2.12表3", "report/gjfs/", userCode, "", "", dataMap4);
			String file5 = DocxUtils.creatDocx("2-2.13表4", "report/gjfs/", userCode, "", "", dataMap5);
			String file6 = DocxUtils.creatDocx("2-2.14表5", "report/gjfs/", userCode, "", "", dataMap6);
			String file7 = DocxUtils.creatDocx("2-2.21图2表6", "report/gjfs/", userCode, "", "", dataMap7);
			String file8 = DocxUtils.creatDocx("2-2.22表7", "report/gjfs/", userCode, "", "", dataMap8);
			String file9 = DocxUtils.creatDocx("2-2.22表8", "report/gjfs/", userCode, "", "", dataMap9);
			
			String file11 = DocxUtils.creatDocx("3-附件1、路况评定PQI计算方法说明", "report/gjfs/", userCode, "", "", dataMap11);
			
			session.setAttribute(uid, "60");
			//生成docx文档,方便后期调整样式或插入图片
			String reswordpath = "d:/report/"+userCode+"/sign/result.docx";

			List list=new ArrayList();
			list.add(file1);
			list.add(file2);
			//file3创建图片,并替换图1
			String imgPath = DocxUtils.createImg(dataMap3.get("pqi")+"", dataMap3.get("pci")+"", 
					dataMap3.get("rqi")+"", dataMap3.get("rdi")+"", userCode);
			Map param = new HashMap();  
			Map header = new HashMap();  
			header.put("width", 500);  
			header.put("height", 350);  
			header.put("type", "png");  
			header.put("content", imgPath);  
			param.put("imgoo",header);
			file3 = DocxUtils.replaceAndGenerateWord(file3, param, "result1.docx", null, null);  
			list.add(file3);
			list.add(file4);
			list.add(file5);
			list.add(file6);
			
			//file7创建图片,并替换图1
			String imgPath1 = DocxUtils.createImg(dataMap7.get("pqi")+"", dataMap7.get("pci")+"", 
					dataMap7.get("rqi")+"", "", userCode);
			Map param1 = new HashMap();  
			Map header1 = new HashMap();  
			header1.put("width", 500);  
			header1.put("height", 350);  
			header1.put("type", "png");  
			header1.put("content", imgPath1);  
			param1.put("imgoo",header1);
			file7 = DocxUtils.replaceAndGenerateWord(file7, param1, "result2.docx", null, null);  
			list.add(file7);
			list.add(file8);
			list.add(file9);
			
			list.add(file11);
			
			File file = DocxUtils.mergeDocx(list, reswordpath);
			
			return file;

以上代码研究了将近两个星期,发现java开源组件在操作word上还是挺鸡肋的,另外jacob等组件没有使用,是因为我们是linux服务器。。。。。。。。。

你可能感兴趣的:(Java,java生成word,freemarker,poi,java生成复杂word,java,word)