最近弄一个项目,需要读取xml文件里面的内容生成word文档,网上找了很多方案,最后选择用freemarker来处理,一是读写起来比较简单,而是不需要设置复杂的Word格式且一旦格式有变化重新生成模板就成,不需要改java代码。这篇文档有介绍怎么生成xml模板以及静态的例子,我也是参考这篇来处理的。链接:
http://blog.csdn.net/lun379292733/article/details/18673081
我这里写个简单的过程吧,加深下印象。
一、创建模板文件:
1、找到需要做成模板的word文档,将其中的关键数据用EL表达式来替换,譬如:
姓名:wjl
改成:
姓名:${name}
2、将替换成EL之后的word另存为xml(注意保存时选择“只保存数据”);
3、修改xml文件的后缀名,改成ftl格式。
4、我的word模板里面有个需要循环的列表,列表里面的内容我定义成了${content},但是这个内容有很多,类似于循环一样,因此需要添加<#list>来体现循环,如图:
其中,真正的标签是<#list>,fileList是要循环的list,file是别面,用<c:foreach>来说就是:fileList是放在item里面的东西,file类似于var里面的东西。
1、createWordFile用来组装业务,包括读取xml数据生成word,并返回给调用出的结果。
/** * 该方法用来生成word文件 * @param:父级文件夹名称 * @param:需要处理的文件所在目录 * */ public boolean createWordFile(String parentFileName,String newFilePath) throws Exception{ boolean result=false; File xmlFile = new File(newFilePath+File.separator+"200105.xml"); if(xmlFile.isFile() && xmlFile.exists()){//是文件且存在 List list = readWord(xmlFile);//组装数据、以及文件名称 if(list!=null && list.size()>0){ String wordName = list.get(0).toString();//文件名称 Map dataMap = (Map)list.get(1);//集合数据 String templateFilePath = new File("").getAbsolutePath()+File.separator+"config";//设定为当前文件夹 // System.out.println("模板文件所在目录:"+templateFilePath); boolean flag = createWord(dataMap,templateFilePath,"word.ftl",classFile.getAbsolutePath(),wordName+".doc");//生成word if(flag){ result=true; }else{ errorInfo = "word文件生成失败!"; } }else{ errorInfo="无法读取"+(newFilePath+File.separator+"200105.xml")+"文件中的数据信息!"; } } return result; }
2、readWord用来读取xml文件组装数据,并返回数据以及word文件名称(因为word文件名称来源于xml文件)。
/*** * 该方法用来组装数据 * @param xmlFile:需要进行解析的xml文件 * */ public List readWord(File xmlFile) { /** 用于组装word页面需要的数据 */ Map<String, Object> dataMap = new HashMap<String, Object>(); List list = null; StringBuffer wordName=new StringBuffer();//word文档名称:案件编号+名称+内部编号 try { //读取XML SAXReader sax = getSAXReader(); Document doc = sax.read(xmlFile);//读取文档 String fileContent="",appAJ=""; //1、案件编号 Node node = doc.selectSingleNode("//ANJIANBH"); if(node!=null ){ fileContent=node.getText().trim(); }else{ fileContent=""; } wordName.append(fileContent); dataMap.put("caseNum",fileContent);//案件编号 //2、名称 node = doc.selectSingleNode("//FAMINGCZMC"); if(node!=null ){ fileContent=node.getText().trim(); }else{ fileContent=""; } wordName.append(escapeExprSpecialWord(fileContent));//去除特殊字符 dataMap.put("name",fileContent);//名称 //3、内部编号/申请号 fileContent="内部编号:"; node = doc.selectSingleNode("//NEIBUBH"); if(node!=null ){//说明没有内部编号 fileContent="内部编号:"+node.getText().trim(); appAJ = node.getText().trim(); if(appAJ.toUpperCase().startsWith("AJ")){//避免出现小写aj的情况,所以全部转成大写进行比较 //内部编号用最后一个下划线截取 appAJ = appAJ.substring(appAJ.lastIndexOf("_")+1); } wordName.append(appAJ); }else{//那么查找是否有申请号 node = doc.selectSingleNode("//SHENQINGH"); if(node!=null ){ fileContent=node.getText().trim(); wordName.append(fileContent.split(":")[1]); } } dataMap.put("num",fileContent); //4、提交人姓名或名称 node = doc.selectSingleNode("//XINGMINGHMC"); if(node!=null ){ fileContent=node.getText().trim(); }else{ fileContent=""; } dataMap.put("proxy",fileContent); //5、 收到时间 node = doc.selectSingleNode("//QIANMINGSJC"); if(node!=null ){ fileContent=node.getText().trim(); }else{ fileContent=""; } dataMap.put("time",fileContent);//收到时间 //6、收到文件情况 StringBuffer content= new StringBuffer(); Element element=null; List<Map<String, Object>> fileList=new ArrayList<Map<String,Object>>(); List<Element> els = doc.selectNodes("//SHOUDAOWJ"); if(els!=null && els.size()>0){ for(int i=0;i<els.size();i++){ Map<String, Object> map=new HashMap<String, Object>(); element = els.get(i); content.delete(0,content.length());//清空 content.append((i+1)+"、");//序号 node = element.selectSingleNode(".//WENJIANMC");//文件名称 content.append(node.getText().trim()+" "); node = element.selectSingleNode(".//WENJIANGS");//文件格式 content.append(node.getText().trim()+"格式 "); node = element.selectSingleNode(".//WENJIANDX");//文件大小 content.append("文件大小"+node.getText().trim()); // System.out.println(content.toString()); map.put("content", content.toString()); fileList.add(map); } }else{ Map<String, Object> map=new HashMap<String, Object>(); map.put("content",""); fileList.add(map); } dataMap.put("fileList",fileList); // 落款时间 node = doc.selectSingleNode("//SHOUDAOSJ"); if(node!=null ){ fileContent = node.getText().trim(); }else{ fileContent=""; } dataMap.put("date",fileContent);//落款时间 list = new ArrayList(); list.add(wordName); list.add(dataMap); } catch (Exception e) { e.printStackTrace(); return null; } return list; } /** * 该方法用来获取SXAReader对象 * */ public SAXReader getSAXReader(){ SAXReader saxReader = new SAXReader(); /* 在读取文件时,去掉dtd的验证,可以缩短运行时间 */ try { // saxReader.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false);//可能需要网络,所以不用它 saxReader.setFeature(Constants.XERCES_FEATURE_PREFIX + Constants.LOAD_EXTERNAL_DTD_FEATURE, false); //设置不需要校验头文件 } catch (SAXException e) { e.printStackTrace(); } return saxReader; } /** * 转义正则特殊字符 ($()*+.[]?\^{},|/\:?"<>) * @param keyword * @return */ public String escapeExprSpecialWord(String keyword) { if(keyword!=null && keyword.trim().length()>0){ String[] fbsArr = {"/","\\",":","*","?","\"","<",">","|", "(", ")","+", "[", "]", "^", "{", "}","、"}; for (String key : fbsArr) { if (keyword.contains(key)) { keyword = keyword.replace(key,""); } } } return keyword; }
3、createWord用来生成word文档。
/** * 该方法用来生成word文件 * @param dataMap:数据集合 * @param templateFilePath:模板文件所在目录 * @param templateFileName:模板文件名称 * @param docFilePath:生成的doc文件保存路径 *@param docFileName:生成的doc文件名称:名称+.doc */ public static boolean createWord(Map dataMap, String templateFilePath,String templateFileName, String docFilePath,String docFileName){ try { Configuration configuration = new Configuration();//创建配置实例 configuration.setDefaultEncoding("UTF-8");//设置编码 configuration.setDirectoryForTemplateLoading(new File(templateFilePath));//加载模板文件 Template template = configuration.getTemplate(templateFileName);//获取模板 if(docFileName==null || !docFileName.toUpperCase().endsWith(".DOC")){//为空或者不是以doc结尾的,添加后缀名 docFileName = docFileName+".doc"; } File outFile = new File(docFilePath+File.separator+docFileName); //如果输出目标文件夹不存在,则创建 if (!outFile.getParentFile().exists()){ outFile.getParentFile().mkdirs(); } //将模板和数据模型合并生成文件 Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"UTF-8")); //生成文件 template.process(dataMap, out); //关闭流 out.flush(); out.close(); return true; } catch (Exception e) { e.printStackTrace(); return false; } }
三、需要用到的jar包:
freemarker-2.3.16.jar:使用freemarker生成word的主要包dom4j-1.6.1.jar:解析xml文件的主要包
jaxen-1.1-beta-7.jar:解析xml文件的辅助包 已经上传到附件,有需要的可以下载。
三、需要注意的问题:
a、将word另存为xml得到的xml文件格式很乱,可能只有几行,可以通过格式化工具格式化一下。这样子易读性是增强了,但是可能会影响word格式。我这里有个落款时间就是因为格式化后出现了问题。格式化之前落款时间是居右显示,格式化之后竟然居中了,代码没有变化,可能就是中间出现了空格导致了问题。所以格式化之后最好测试话一下,或者不要格式化。 b、得到的xml文件里的$与参数分开了。譬如原本的word里面写的参数为${content},xml之后${content}被拆成了${,content,和}三部分。具体原因和解决办法可以看看 参考文件,我这就是老出现这个问题,弄了好几次还这样子,不得已只能直接改xml文件。 c、put到map里的参数一定要与xml中写的的参数一致,否则会报错。特别是有list的情况下,别名一定不能忘。我的例子就是${file.content}这个file一定不能忘,否则也会报错。值得庆幸的是,报错很明显,一查就能查不出来。