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文档:

/**
	 * 
	 * <p>描述: 生成docx文件</p>  
	 * @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<String,Object> 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<String, Object> 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<? extends ZipEntry> 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文档:

/**
	 * 
	 * <p>描述:合并多个docx文件 </p>  
	 * @author 范相如  
	 * @date 2018年8月13日  
	 * @param list  分文档路径地址
	 * @param path	合并后最终文档路径
	 * @return
	 */
	public static File mergeDocx(List<String> list,String path){
		List<InputStream>  inList=new ArrayList<InputStream>();
		for(int i=0;i<list.size();i++)
			try {
				inList.add(new FileInputStream(list.get(i)));
			} catch (FileNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		try {
			InputStream   inputStream=mergeDocx(inList);
			saveTemplate(inputStream,path);
		} catch (Docx4JException | IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return new File(path);
	}
private static InputStream mergeDocx(final List<InputStream> streams)  
			throws Docx4JException, IOException {  

		WordprocessingMLPackage target = null;  
		final File generated = File.createTempFile("generated", ".docx");  

		int chunkId = 0;  
		Iterator<InputStream> 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):

/**
	 * 
	* <p>描述: 创建报告所需柱状图</p>  
	* @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<String, Object> 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<XWPFParagraph> paragraphList = document.getParagraphs();  
						processParagraphs(paragraphList, param, document);                    
						//处理表格  
						Iterator<XWPFTable> it = document.getTablesIterator();  
						while (it.hasNext()) {  
							XWPFTable table = it.next();  
							List<XWPFTableRow> rows = table.getRows();  
							for (XWPFTableRow row : rows) {  
								List<XWPFTableCell> cells = row.getTableCells();  
								for (XWPFTableCell cell : cells) {  
									List<XWPFParagraph> 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<String, Object> 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<XWPFParagraph> paragraphList,Map<String, Object> 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<String, Object> 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<String> list=new ArrayList<String>();
			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<String, Object> param = new HashMap<String, Object>();  
			Map<String,Object> header = new HashMap<String, Object>();  
			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<String, Object> param1 = new HashMap<String, Object>();  
			Map<String,Object> header1 = new HashMap<String, Object>();  
			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服务器。。。。。。。。。

  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值