一、我使用的java导出word,由XML+FreeMarker来实现的,因此需要以下工具:
1、office。这里不能使用wps,因为wps由word文档转为xml文档时,解析会发生错误,导致最终模板和设想的样子有一定的区别。
2、xml编辑工具,这里我推荐使用Firstobject free XML editor。
3、引用FreeMarker的jar包。
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.23</version>
</dependency>
二、模板制作
1、模板制作一定要谨慎,最好是样式全部调试好了,位置都OK了,需求基本不会变了再制作,因为每次修改哪怕一点点东西,都有可能导致全部从头再来一次,非常麻烦。
2、尽量使用office制作,不要用wps制作,然后再用office开发,因为这两者编码和解析方式都有很大的不同,可能导致你导出的word样式变形。
3、如果导出的样式涉及到了图片(多张图片)时,一定要使用小图片制作,最好只有几十K的,不要使用大图片然后缩小,没用的,base64还是会占那么多位置,替换图片时长到你怀疑人生。二是,如果最终导出是有多张图片的,一定要使用多张图片,如果是复制粘贴的,最终xml里只有一段base64,给替换造成非常大的麻烦。
4、如果调试好自己要的所有样式后,另存为Word 2003XML,wps没有这个模式,只有xml格式,使用这个模式最终效果并不理想。转好xml格式后,打开Firstobject free XML editor,直接将xml扔进去就行了,F8调整格式,左边类似目录层级(只用看body就行了),右边为具体源码。
5、替换源码里相应位置的信息换成freemarker的标识。其实就是Map<String, Object>中key。如:姓名替换为${name}
6、 如果还有地方需要循环使用时,可以使用:<#list maps as map></#list> maps是Map<String, Object>中key,值为数组,map为自定义;如果有的内容需要一定条件下使用时,可以使用:<#if name??></#if>,判断循环是否为空:<#if orgList?? && (orgList?size > 0) > </#if>。
7、图片处理(我认为最麻烦的地方)。
(1)在加入了图片占位的地方,会看到一片base64编码后的代码,把base64替换成"{image},也就是Map<String, Object>中key,值必须要处理成base64。(意味着传入的值必须是base64)
(2)这时如果不对模板图片大小做处理,导出来的图片大小就是你模板的大小,基本就是变形+严重缩小,因此还需要对图片大小进行替换。如:<v:shape id="_x0000_i1030" type="#_x0000_t75" style=“width:60pt;height:60pt”>中的60pt整体替换为你输入的值,替换后为:<v:shape id="_x0000_i1030" type="#_x0000_t75" style=“width:{problemsImagesList.w4};height:{problemsImagesList.h4}”>,这里的w4和h4为自己图片大小。
(3)如果你的图片需要循环输出,你还需要改动图片的引用,如果不改动,第二组循环图片还是第一组的。如:
<w:binData w:name="{“wordml://0200000”+0000086+".jpg"}" 和<v:imagedata src="{“wordml://0200000”+0000086+".jpg"}" o:title=“d”/>中的00000086替换掉,替换换后为:<w:binData w:name="{“wordml://0200000”+problemsImagesList_index+4+".jpg"}" 和<v:imagedata src="{“wordml://0200000”+problemsImagesList_index+4+".jpg"}" o:title=“d”/>,这里叫什么无所谓,只要上下两处引用相同,和这一次循环的其他东方图片不同即可。
注意这里有美元符号,不懂为什么放到文章中,会隐藏很多,因此全部美元符号都去掉了
8、至此模板准备基本就OK啦,只需要在Firstobject free XML editor里另存为flt格式就行了,注意flt不要再用word打开了,如果你打开了,那么恭喜你,模板从头再来一次吧。
三、接下来就是在java里组装你需要导入到模板里的数据啦,其实我个人认为,模板只要为问题,这一步就很轻松了,使用的几个工具类我就直接扔出来就行了。
1、生成word的doc格式的类
在这里插入代码片package com.gykj.utils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import freemarker.template.Configuration;
import freemarker.template.Template;
public class WordUtils {
//配置信息,代码本身写的还是很可读的,就不过多注解了
private static Configuration configuration = null;
//这里注意的是利用WordUtils的类加载器动态获得模板文件的位置
private static final String templateFolder = WordUtils.class.getClassLoader().getResource("").getPath(); //"D:/patrol";//
//private static final String templateFolder = "H:/我的项目/lm/lm/web/src/main/webapp/WEB-INF/templates";
static {
configuration = new Configuration();
configuration.setDefaultEncoding("utf-8");
try {
configuration.setDirectoryForTemplateLoading(new File(templateFolder));
} catch (IOException e) {
e.printStackTrace();
}
}
private WordUtils() {
throw new AssertionError();
}
public static void exportMillCertificateWord(HttpServletRequest request, HttpServletResponse response, Map map,String title,String ftlFile,String flieUrl) throws IOException {
Template freemarkerTemplate = configuration.getTemplate(ftlFile);
File file = null;
InputStream fin = null;
//ServletOutputStream out = null;
FileOutputStream out = null;
try {
// 调用工具类的createDoc方法生成Word文档
file = createDoc(map,freemarkerTemplate);
fin = new FileInputStream(file);
String fileName = title + ".doc";
/*response.setCharacterEncoding("utf-8");
response.setContentType("application/msword");
// 设置浏览器以下载的方式处理该文件名
String fileName = title + ".doc";
response.setHeader("Content-Disposition", "attachment;filename="
.concat(String.valueOf(URLEncoder.encode(fileName, "UTF-8"))));
out = response.getOutputStream(); */
File file2 = new File(flieUrl+fileName);
if (!file2.exists()) {
file2.createNewFile();
}
out = new FileOutputStream(file2);
byte[] buffer = new byte[512]; // 缓冲区
int bytesToRead = -1;
// 通过循环将读入的Word文件的内容输出到浏览器中
while((bytesToRead = fin.read(buffer)) != -1) {
out.write(buffer, 0, bytesToRead);
}
} finally {
if(fin != null) fin.close();
if(out != null) out.close();
if(file != null) file.delete(); // 删除临时文件
}
}
private static File createDoc(Map<?, ?> dataMap, Template template) {
String name = "sellPlan.doc";
File f = new File(name);
Template t = template;
try {
// 这个地方不能使用FileWriter因为需要指定编码类型否则生成的Word文档会因为有无法识别的编码而无法打开
Writer w = new OutputStreamWriter(new FileOutputStream(f), "utf-8");
t.process(dataMap, w);
w.close();
} catch (Exception ex) {
ex.printStackTrace();
throw new RuntimeException(ex);
}
return f;
}
}
2、将图片转为base64的类,这里我因为需求问题做了等比例缩放功能,缩放到多大由double widthMax = 350; double heightMax = 350;这两个值确定,这个大小基本就是你存图片表格的大小。
package com.gykj.utils;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.imageio.ImageIO;
import sun.misc.BASE64Encoder;
public class getImageBase {
//获得图片的base64码
//@SuppressWarnings("deprecation")
public static Map<String, Object> getBase64(String imgsrc) {
// if(src==null||src==""){
// return "";
// }
// File file = new File(src);
// if(!file.exists()) {
// return "";
// }
// InputStream in = null;
// byte[] data = null;
// try {
// in = new FileInputStream(file);
// } catch (FileNotFoundException e1) {
// e1.printStackTrace();
// }
// try {
// data = new byte[in.available()];
// in.read(data);
// in.close();
// } catch (IOException e) {
// e.printStackTrace();
// }
// BASE64Encoder encoder = new BASE64Encoder();
// return encoder.encode(data);
Map<String, Object> map = new HashMap<String, Object>();
try {
double rate = 0;
int widthdist = 0;
int heightdist = 0;
double widthMax = 350;
double heightMax = 350;
File srcfile = new File(imgsrc);
// 检查图片文件是否存在
if (!srcfile.exists()) {
System.out.println("文件不存在");
}
// 如果比例不为空则说明是按比例压缩
//获得源图片的宽高存入数组中
int[] results = getImgWidthHeight(srcfile);
//计算具体等比比例
int width = results[0];
int height = results[1];
double widthRate = widthMax/width;
double heightRate = heightMax/height;
if (heightRate > widthRate) {
rate = widthRate;
}else {
rate = heightRate;
}
if (results == null || results[0] == 0 || results[1] == 0) {
return null;
} else {
//按比例缩放或扩大图片大小,将浮点型转为整型
widthdist = (int) (results[0] * rate);
heightdist = (int) (results[1] * rate);
}
// 开始读取文件并进行压缩
Image src = ImageIO.read(srcfile);
// 构造一个类型为预定义图像类型之一的 BufferedImage
BufferedImage tag = new BufferedImage((int) widthdist, (int) heightdist, BufferedImage.TYPE_INT_RGB);
//绘制图像 getScaledInstance表示创建此图像的缩放版本,返回一个新的缩放版本Image,按指定的width,height呈现图像
//Image.SCALE_SMOOTH,选择图像平滑度比缩放速度具有更高优先级的图像缩放算法。
tag.getGraphics().drawImage(src.getScaledInstance(widthdist, heightdist, Image.SCALE_SMOOTH), 0, 0, null);
// //创建文件输出流
// FileOutputStream out = new FileOutputStream(imgdist);
// //将图片按JPEG压缩,保存到out中
// JPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(out);
// encoder.encode(tag);
// //关闭文件输出流
// out.close();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(tag, "jpg", baos);
byte[] bytes = baos.toByteArray();
BASE64Encoder encoder = new BASE64Encoder();
map.put("img", encoder.encodeBuffer(bytes).trim());
map.put("w", widthdist);
map.put("h", heightdist);
map.put("name", imgsrc);
baos.close();
return map;
} catch (Exception ef) {
ef.printStackTrace();
}
return null;
}
/**
* 获取图片宽度和高度
*
* @param 图片路径
* @return 返回图片的宽度
*/
public static int[] getImgWidthHeight(File file) {
InputStream is = null;
BufferedImage src = null;
int result[] = { 0, 0 };
try {
// 获得文件输入流
is = new FileInputStream(file);
// 从流里将图片写入缓冲图片区
src = ImageIO.read(is);
result[0] =src.getWidth(null); // 得到源图片宽
result[1] =src.getHeight(null);// 得到源图片高
is.close(); //关闭输入流
} catch (Exception ef) {
ef.printStackTrace();
}
return result;
}
}
3、最后的值测试样例类以及测试模板,因此篇幅原因不上来了,如需要,留言邮箱即可
4、需要demo的人可能有点多,我没法及时回复,就放百度云盘了:
https://pan.baidu.com/s/1fgjJBIbtzXJNnCwLlI7F0Q
提取码:u6qq