简单说下java生成word文档的各个组件优缺点(详细网上有很多),POI、JXL等过于原生,如果制作简单的几页word文档还能接受,如果文档十几二十页。。。会头疼死,并且word一旦大了以后,样式很容易乱,很不美观;freemarker利用模板生成word文档,开发相对简单,但是freemarker是利用xml标签传入模板的,一旦在模板里加了部分标签(例如 list),模板就不可以用office打开了,再去调整模板样式,或者增加内容,又或者需要增加很多list,会无从下手,除非对xml标签很熟练。。。。 所以以下所有工作均是针对复杂的word文档 首先说下报告制作的总体思路:使用freemarker进行模板式开发,以数据列表为界限(或者章节,具体以业务为准)将word模板拆分为多个模板,方便后期业务要求调整模板,docx4j进行多个文档合并,POI调整文档细节或者插入图片等。 注意事项: 报告模板划分时,以数据列表为界限(章节也可以),避免出现单个模板中存在复杂word内容。 Freemarker生成分word文档时,一定要生成docx格式,即2007版以上的word文档。 Freemarker生成docx与doc文档的模板获取方法不同。 文档合并方法支持生成doc和docx格式,建议生成docx,方便POI或freemarker再调整文档格式(实际上我是先生成各个word模板,然后分别插入图片或调整样式,最后合并文档)。 项目使用时,注意方法中使用的临时路径信息可用。 下面是封装的相关方法(都是我实际代码用的,拷贝时请稍加调整): 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服务器 |
Archiver|知识站 ( 鲁ICP备20004068号-1 )
GMT+8, 2020-6-23 22:50 , Processed in 0.019733 second(s), 17 queries .