使用freemarker 导出word文档

/**
 * ClassName:WordDownload<p>
 * Description:word文档的打印下载(用freemarker导出word文档)<p>
 *             ①:使用Microsoft Office Word新建一个word原件  把文中需要动态修改的类容改为${xxxxx}<p>
 *                最后另存为Word 20003 XML文档(*.xml)类型<p>
 *             ②:然后编辑xml文件(本人直接在eclipse中打开的) 将${xxxxx}中间的xml标记全部删除  ${xxxxx}<p>
 *                要连在一起不能断开  如果需要循环插入的 需要在将${xxxxx}包括的xml标记前后加上<p>
 *                <#list maps as map>……${xxxxx}……</#list><p>
 *             ③:最后另存为.ftl后缀文件(不改也可以用)  但是编辑完后一定不能用word打开ftl模板文件,否则会重置②的操作<p>
 * Company:<p>
 * @author yangchuanjie
 * @date Mar 11, 2018 4:11:21 PM
 */
public class WordDownload {
		/**
		 * Configuration 是一个存放应用级别(application level)公共配置信息,以及模版(Template)可使用的全局共享变量的一个对象。<p>
		 * 同时它还负责模版(Template)实例的创建以及缓存。<p>
		 * Configuration 实际上是freemarker.template.Configuration 对象的实例,使用其构造函数创建。<p>
		 * 通常应用使用一个共享的单实例Configuration 对象。<p>
		 * Configuration 对象可被Template 对象的方法使用,<p>
		 * 每一个模版实例都关联与一个Configuration 实例,它是通过Template 的构造函数被关联进去的,<p>
		 * 通常是你使用这个方法来Configuration.getTemplate 获得模版对象的。<p>
		 * 注意:<p>
		 * Configuration 对象初始化时已经包含一些共享转换器变量: 所以它是非线程安全的;<p>
		 */
		private static Configuration configuration = null;
		/**
		 * 利用WordDownload的类加载器动态获取模板文件的位置<p>
		 * 类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例<p>
		 */
	//	private static final String templateFloder = WordDownload.class.getResource("./").getPath()+"wordtemplate";
		
		/**
		 * 模板文件和工程在同一目录下时 不用写根目录 相反 则写全
		 */
		private static final String templateFloder = "/YCJ_IT/eclipse-project/utils/bin/com/ycj/utils/download/wordtemplate";
		static {
			configuration = new Configuration();
			configuration.setDefaultEncoding("utf-8");
			try {
				/**
				 * 指定了一个文件系统中的目录,FreeMarker 将会在此目录记载模版,此目录必须存在,否在会抛出异常。
				 */
				configuration.setDirectoryForTemplateLoading(new File(templateFloder));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		/**
		 * 不能使用构造函数
		 */
		private WordDownload() {
			throw new AssertionError("This method should never be called");
		}
		/**
		 * @Title:createDoc
		 * @Description:创建doc临时文件
		 * @param dataMap
		 * @param template
		 * @return
		 * @throws
		 * @return File
		 * @author yangchuanjie
		 * @date Mar 12, 2018 9:33:33 AM
		 */
		private static File createDoc(Map<?, ?> dataMap,Template template){
			String name = "temporaryFile.doc";
			File file = new File(name);
			Template t  = template;
			try {
				/**
				 * 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开  <p>
				 * utputStreamWriter/InputStreamReader 是转换流,指的是将字节流转化为字符流。<p>
				 * FileOutputStream,写入文件的输出流  是用来操作文件的字节输入流,File代表操作的目的,OutputStream代表这是个输出的字节流<p>
				 */
				Writer w = new OutputStreamWriter(new FileOutputStream(file),"UTF-8");
				/**
				 * 合并数据模型和模版  
				 * 
				 * 执行期间的异常:当你调用Template.process(...)方法的时候,会抛出两类异常:<p>
				 * IOException 往输出写数据时候发生的错误;<p>
				 * freemarker.template.TemplatException其他运行期产生的异常,比如一个最常见的错误就是模版引用了一个不存在的变量;<p>
				 */
				t.process(dataMap, w);
				w.close();
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				throw new RuntimeException(e);
			}
			return file;
		}
		/**
		 * @Title:exportWordByBrowser
		 * @Description:TODO
		 * @param request
		 * @param response
		 * @param map 
		 * @param title word文件名
		 * @param ftlFile 模板文件名
		 * @throws IOException 
		 * @throws
		 * @return void
		 * @author yangchuanjie
		 * @date Mar 11, 2018 11:20:53 PM
		 */
		public static void exportWordByBrowser(HttpServletRequest request,HttpServletResponse response,Map map,
				String title,String ftlFile) throws IOException{
			/**
			 * 加载解析模版期的异常:当你通过Configuration.getTemplate()方法获取模版的时候(如果模版之前没有被缓存),将会产生两类异常:<p>
			 * ①:IOException:由于模版没有找到,或在读取模版的时候发生其他的IO异常,比如你没有读取该文件的权限等等;<p>
			 * ②:freemarker.core.ParseException 由于模版文件的语法使用不正确;<p>
			 */
			Template template = configuration.getTemplate(ftlFile);
			File file = null;  
	        InputStream fin = null;  
	        ServletOutputStream out = null; //向客户端发送数据的字节输出流(进水龙头)
	        try {  
	            // 调用工具类的createDoc方法生成Word文档  
	            file = createDoc(map,template);  
	            fin = new FileInputStream(file);  //从文件读入的输入流(出水龙头)
	            /**
	             * 指定对服务器响应进行重新编码的编码。同时,浏览器也是根据这个参数来对其接收到的数据进行重新编码(或者称为解码)
	             */
	            response.setCharacterEncoding("utf-8");  
	            response.setContentType("application/msword");  //类容类型 --doc
	           
	            // 设置浏览器以下载的方式处理该文件名  
	            String fileName = title+ ".doc";
	            /**
	             * Content-Disposition:激活文件下载对话框,它的文件名框自动填充了头中指定的文件名。<p>
	             * attachment:以附件的形式下载<p>
	             * URLEncoder.encode(fileName, "UTF-8")使用指定的编码机制将字符串转换为 application/x-www-form-urlencoded 格局。<p>
	             * URLDecoder.decode(fileName, "UTF-8");使用指定的编码机制对 application/x-www-form-urlencoded 字符串解码。<p>
	             * concat String中的字符串拼接方法
	             */
	            response.setHeader("Content-Disposition", "attachment;filename="  
	                    .concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));  
	  
	            out = response.getOutputStream();  //向客户端发送数据的字节输出流	           
	            /**
	             * 每次从数据源中读取和缓冲区大小(二进制位)相同的数据并将其存在缓冲区中。<p>
	             * byte数组中存放的仍然是0-255的整数,将二进制转换为十进制这个过程是read方法实现的。 <p>
	             * read方法在读取数据的时候仍然是按照字节来读取的<p>
	             */
	            byte[] buffer = new byte[1024];  // 缓冲区  
	            int bytesToRead = -1;  
	            // 通过循环将读入的Word文件的内容输出到浏览器中  
	            while((bytesToRead = fin.read(buffer)) != -1) {  
	            	//将指定 byte 数组中从偏移量 0 开始的 bytesToRead 个字节写入此输出流。 
	                out.write(buffer, 0, bytesToRead);	                
	               //如果文件大于1024字节,就一次读1024个字节,然后写出,写出后在读剩下的,依次循环输出,不会覆盖文件中的数据
	            }  
	          
	        } finally {  
	            if(fin != null) fin.close();  
	            if(out != null) out.close();  
	            if(file != null) file.delete(); // 删除临时文件  
	        }  
	    
		}
		
		public static void main(String[] args) {
			/**
			 * 构建一个File类的实例并不会在机器上创建一个文件.不管文件是否存在都可以创建任意文件名的File实例<p>
			 * 该类的出现是对文件系统的中的文件以及文件夹进行对象的封装。可以通过对象的思想来操作文件以及文件夹。  <p>                                   
			 * 可以用面向对象的处理问题,通过该对象的方法,可以得到文件或文件夹的信息方便了对文件与文件夹的属性信息进行操作。<p>
			 * 1、在java中stream代表一种数据流(源),javaio的底层数据元,---(想像成水龙头)<p>
			 * 2、任何有能力产生数据流(源)的javaio对象就可以看作是一个InputStream对象既然它能产生出数据,我们就可以将数据取出,<p>
			 * java对封装的通用方法就read()方法了--(出水龙头)<p>
			 * 3、任何有能力接收数据源(流)的javaio对象我们就可以看作是一个OutputStream对象同样,它能接收数据,<p>
			 * 我们就可以调用它的write方法,来让它接收数据--(进水龙头了,呵呵)<p>
			 */
			File file = new File("niao");
			File file1 = new File("me/niao");
//			File file2 = new File("you/me.doc"); 
			File file2 = new File("me.doc");
			System.out.println(file.exists());
			System.out.println(file1.exists());
			System.out.println(file2.exists());
			/**
			 * 没有写文件根目录时 会默认创建在项目下
			 */
			/*if(!file.exists()){
				file.mkdir();//如果新建的文件目录的上级目录不存在则mkdir()回报异常不能成功创建文件夹,
			}
			if(!file1.exists()){
				file1.mkdirs();//如果新建的文件目录的上级目录不存在mkdirs()会将目录与上级目录一起创建。 
			}
			if(!file2.exists()){
				try {
					file2.createNewFile();//创建文件   指定的目录必须存在  否则会报io异常:系统找不到指定的路径
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}*/
			System.out.println(file.getAbsolutePath());
		}
}

猜你喜欢

转载自blog.csdn.net/YCJ_xiyang/article/details/79560285