龙空技术网

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

码农世界 337

前言:

目前朋友们对“java word文档”大概比较注重,你们都想要分析一些“java word文档”的相关文章。那么小编在网上搜集了一些有关“java word文档””的相关知识,希望各位老铁们能喜欢,各位老铁们一起来学习一下吧!

简单说下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里面,生成带数据的xmltemplate.process(param, out);if (out != null) {out.close();}// 带数据的xml生成docxFile 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 blocke.printStackTrace();}try {InputStream inputStream=mergeDocx(inList);saveTemplate(inputStream,path);} catch (Docx4JException | IOException e) {// TODO Auto-generated catch blocke.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) documentOutputStream 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-2013if(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){//获取段落的textboolean 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创建图片,并替换图1String 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创建图片,并替换图1String 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服务器

转:

标签: #java word文档 #java导出word文档多重循环如何做