FreeMarker 导出word(唯其如此wps打开)
做了个简单例子用freemarker导出word,讲数据库查询出的信息以word形式导出来,简单来说就两步,建立模板,查出数据写入模板。
第一步:建立模板
建立word调好格式,将其中的变量用${参数名}来代替,有需要展现图片的业务的话,则在要展示的图片的位置预先插入一张图片,例如:
然后将该word另存为Word xml(wps格式)格式的文件,再将该xml文件更改文件格式为ftl,下载firstobject XML Editor,F8格式化该文件浏览样式;
第二步:服务器获取数据
public void createWord(HttpServletRequest request,List<HouseInspectionItems> houseInspectionItemsList,HouseInspection houseInspection) throws IOException{
//houseInspectionItemsList为上一个方法传入的数据
Configuration configuration = new Configuration();
configuration.setDefaultEncoding("UTF-8");
Map<String,Object> dataMap=new HashMap<String,Object>();
//填充数据
dataMap.put("propertyName", houseInspection.getPtorname());//物业名称
dataMap.put("houseaddr", houseInspection.getComname()+houseInspection.getAddr());
dataMap.put("ptorname", houseInspection.getPtorname());
dataMap.put("ptorphone", houseInspection.getPtorphone());
dataMap.put("houseinfo", houseInspection.getAddr());
dataMap.put("finishDate", houseInspection.getFinishDate()!=null ?DateUtils.formatDate(houseInspection.getFinishDate(), "yyyy年MM月dd日"):"");//完成日期
List<HouseInspectionItems> list = new ArrayList<HouseInspectionItems>();
List<Map<String,Object>> ridList = new ArrayList<Map<String,Object>>();
int ki = 7;//循环体外声明图片id值,从7开始,是避免与已经存在的声明图片id冲突
for (int i = 0; i < houseInspectionItemsList.size(); i++) {
HouseInspectionItems hii = houseInspectionItemsList.get(i);
List<HouseProblem> hpList = houseProblemService.findHPListByInsIdAndItemsId(hii.getId(),houseInspection.getId());//根据验房id和检查项目id查找对应问题列表
hii.setItemsName(hii.getItemsName());//检查项目名
hii.setHouseProblemList(hpList); //问题列表
for(int j = 0;j<hpList.size();j++){
HouseProblem hp = hpList.get(j);
if(StringUtils.isNotBlank(hp.getHouseProblemImg())){//有图片需要转码
List<Map<String,Object>> hPImgWordList = new ArrayList<Map<String,Object>>();
List<String> strList = Arrays.asList(hp.getHouseProblemImg().split(","));
int tt = 1;
for(String imgStr:strList){
Map<String,Object> mm = new HashMap<String, Object>();
Map<String,Object> idMap = new HashMap<String,Object>();
mm.put("imgurl",getImageString(imgStr));
mm.put("rid", "rId"+ki);
mm.put("ttcount", tt);
// mm.put("pkgname","/word/media/image"+ki+".jpg");
mm.put("pkgname","/word/media/image"+ki);
hPImgWordList.add(mm);
idMap.put("rid", "rId"+ki);
// idMap.put("target", "media/image"+ki+".jpg");
idMap.put("target", "media/image"+ki);
ki++;
tt++;
ridList.add(idMap);
}
hp.sethPImgWordList(hPImgWordList);
}
hp.setProblemDescription("问题"+(j+1)+":"+hp.getProblemDescription());//问题描述
hp.setHandleStatus(DictUtils.getDictLabel(hp.getHandleStatus(), "houseProblem_handle_status", "0"));
}
list.add(hii);
}
dataMap.put("hilist", list);
dataMap.put("ridList", ridList);
configuration.setDirectoryForTemplateLoading(new File(request.getSession().getServletContext().getRealPath("/template")));//获取保存的ftl格式文件路径
Template t=null;
try {
t = configuration.getTemplate("wordModel.ftl"); //获取ftl模板
} catch (IOException e) {
e.printStackTrace();
}
// File outFile = new File("D:/word/报告"+houseInspection.getHouid()+DateUtils.formatDate(new Date(), "yyyyMMddHHmmss")+".doc");
// // 使用Windows的界面风格选择下载路径
// try
// {
// // 是windows
// if (System.getProperty("os.name").toUpperCase().indexOf("WINDOWS") != -1)
// {
// UIManager.setLookAndFeel("com.sun.java.swing.plaf.windows.WindowsLookAndFeel");
// }
// } catch (Exception e)
// {
// System.out.println("设置界面异常!");
// e.printStackTrace();
// }
//
// JFileChooser chooser = new JFileChooser();
// chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
// int returnVal = chooser.showOpenDialog(null);
// if(returnVal == javax.swing.JFileChooser.APPROVE_OPTION) {
// path = chooser.getSelectedFile().getPath();
// }
File desktopDir = FileSystemView.getFileSystemView().getHomeDirectory();
String path = desktopDir.getAbsolutePath();
File outFile = new File(path+"/报告"+houseInspection.getHouid()+DateUtils.formatDate(new Date(), "yyyyMMddHHmmss")+".doc");
//如果输出目标文件夹不存在,则创建
// if (!outFile.getParentFile().exists()){
// outFile.getParentFile().mkdirs();
// }
Writer out = null;
try { //将模板和数据模型合并生成文件
out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outFile),"utf-8"));
} catch (FileNotFoundException e1) {
e1.printStackTrace();
}
try {
t.process(dataMap, out);
out.flush();
out.close();
} catch (TemplateException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
简要介绍以上的代码:模板里的${hii.itemsName}为检查项目名,是列表形式,检查项目名下面是问题列表,然后每个问题下面又是该问题下的图片列表。
然后从服务器以url形式获取图片并压缩并base64编码
/**
* 获取图片并压缩编码
*/
public String getImageString(String fileName) throws IOException {
BASE64Encoder encoder = new BASE64Encoder();
// System.out.println("fileName====="+fileName);
// File file = new File(fileName);
// FileInputStream fis = new FileInputStream(file);
// byte[] imgData = new byte[fis.available()];
// fis.read(imgData);
// fis.close();
// return encoder.encode(imgData);
URL url = new URL(fileName);
InputStream inStream = null ;
ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
try {
inStream = url.openStream();
int len = -1;
while ((len = inStream.read(buffer)) != -1) {
outSteam.write(buffer, 0, len);
}
} catch (IOException e) {
e.printStackTrace();
}
finally{
try{
if(outSteam!=null){
outSteam.close();
}
if(inStream!=null){
inStream.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
byte[] data = outSteam.toByteArray();
//图片压缩
ByteArrayInputStream is = new ByteArrayInputStream(data);
ImageWriter imgWrier;
ImageWriteParam imgWriteParams;
BufferedImage src = null;
ByteArrayOutputStream out = null;
// 指定写图片的方式为 jpg
imgWrier = ImageIO.getImageWritersByFormatName("jpg").next();
imgWriteParams = new javax.imageio.plugins.jpeg.JPEGImageWriteParam(null);
// 要使用压缩,必须指定压缩方式为MODE_EXPLICIT
imgWriteParams.setCompressionMode(imgWriteParams.MODE_EXPLICIT);
// 这里指定压缩的程度,参数qality是取值0~1范围内,
imgWriteParams.setCompressionQuality(0.1f);
imgWriteParams.setProgressiveMode(imgWriteParams.MODE_DISABLED);
// ColorModel colorModel = ColorModel.getRGBdefault();
// 指定压缩时使用的色彩模式
// imgWriteParams.setDestinationType(new javax.imageio.ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16)));
try
{
src = ImageIO.read(is);
out = new ByteArrayOutputStream(data.length);
imgWrier.reset();
// 必须先指定 out值,才能调用write方法, ImageOutputStream可以通过任何 OutputStream构造
imgWrier.setOutput(ImageIO.createImageOutputStream(out));
// 调用write方法,就可以向输入流写图片
imgWrier.write(null, new IIOImage(src, null, null), imgWriteParams);
out.flush();
out.close();
is.close();
data = out.toByteArray();
}catch(Exception e){
e.printStackTrace();
}
finally{
try{
if(out!=null){
out.close();
}
if(is!=null){
is.close();
}
}catch (IOException e) {
e.printStackTrace();
}
}
// return encoder.encode(outSteam.toByteArray());
return encoder.encode(data);
}
第三步:修改模板文件
1、将开头部分声明图片的那行代码替换掉 以list形式循环声明所引入的图片
<#list ridList as imgg>
<Relationship Id="${imgg.rid}" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="${imgg.target}.jpg"/>
</#list>
2、循环list
<w:p>
<w:pPr>
<w:jc w:val="both"/>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b w:val="0"/>
<w:bCs w:val="0"/>
<w:sz w:val="21"/>
<w:szCs w:val="21"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b w:val="0"/>
<w:bCs w:val="0"/>
<w:sz w:val="21"/>
<w:szCs w:val="21"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>验房报告问题:</w:t>
</w:r>
</w:p>
<#list hilist as hii>
<w:p>
<w:pPr>
<w:ind w:firstLine="420" w:firstLineChars="0"/>
<w:jc w:val="both"/>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:sz w:val="21"/>
<w:szCs w:val="21"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:sz w:val="21"/>
<w:szCs w:val="21"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>${hii.itemsName}</w:t>
</w:r>
</w:p>
<#list hii.houseProblemList as hp>
<w:p>
<w:pPr>
<w:ind w:left="420" w:leftChars="0" w:firstLine="420" w:firstLineChars="0"/>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
</w:rPr>
<w:t>${hp.problemDescription}</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:tab/>
<w:t></w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:tab/>
<w:t></w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:tab/>
<w:t></w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:tab/>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:t>当前状态:${hp.handleStatus}</w:t>
</w:r>
</w:p>
<w:p>
<w:pPr>
<w:ind w:left="420" w:leftChars="0" w:firstLine="420" w:firstLineChars="0"/>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
<#list hp.hPImgWordList as hpi>
<w:r>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
<w:pict>
<v:shape id="_x0000_i1026" o:spt="75" alt="20160810141549" type="#_x0000_t75" style="height:100pt;width:100pt;" filled="f" o:preferrelative="t" stroked="f" coordsize="21600,21600"
o:gfxdata="">
<v:path/>
<v:fill on="f" focussize="0,0"/>
<v:stroke on="f"/>
<v:imagedata r:id="${hpi.rid}" o:title="20160810141549"/>
<o:lock v:ext="edit" aspectratio="t"/>
<w10:wrap type="none"/>
<w10:anchorlock/>
</v:shape>
</w:pict>
</w:r>
<#assign x = "${hpi.ttcount}"?number />
<#if x % 3 == 0>
<w:p>
<w:pPr>
<w:ind w:left="420" w:leftChars="0" w:firstLine="420" w:firstLineChars="0"/>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
</w:p>
</#if>
</#list>
</w:p>
</#list>
</#list>
<w:p>
<w:pPr>
<w:rPr>
<w:rFonts w:hint="eastAsia"/>
<w:b/>
<w:bCs/>
<w:lang w:val="en-US" w:eastAsia="zh-CN"/>
</w:rPr>
</w:pPr>
</w:p>
其中主要代码为:
<#list hilist as hii>//循环检查项目列表
<w:t>${hii.itemsName}</w:t>
<#list hii.houseProblemList as hp> //循环该检查项目下的问题list
<w:t>${hp.problemDescription}</w:t>
<w:t>当前状态:${hp.handleStatus}</w:t>
<#list hp.hPImgWordList as hpi> //循环该问题下的图片list
<v:imagedata r:id="${hpi.rid}" o:title="20160810141549"/>
</#list>
</#list>
</#list>
在引入图片的前面有设置代码格式
<v:shape id="_x0000_i1026" o:spt="75" alt="20160810141549" type="#_x0000_t75" style="height:100pt;width:100pt;" filled="f" o:preferrelative="t" stroked="f" coordsize="21600,21600"
o:gfxdata="">
其中的style可以设置图片的大小
在ftl文件里所插入的图片全部在下面,或者可以直接找到预先插入的那张图片,这很容易找到,图片引入的是很长很长很长的一段base64编码,然后替换掉
<#list hilist as hii>
<#list hii.houseProblemList as hp>
<#list hp.hPImgWordList as hpi>
<pkg:part pkg:name="${hpi.pkgname}.jpg" pkg:contentType="image/jpg">
<pkg:binaryData>${hpi.imgurl}</pkg:binaryData>
</pkg:part>
</#list>
</#list>
</#list>
有图片的稍微麻烦些,尤其是list形式的图片,03版word引入图片是这样的顺序,首先头部文件先是声明图片,写入多少图片就声明多少(所以头部做了一遍声明所有图片的循环),其中的target指向文件尾部的图片,尾部的图片也是写入多少就定义多少,然后文件中哪里需要插入该图片,则将id引向头部声明图片的id(其中target指向该图片的id)。<w:p></w:p>这应该是换行标志,所以<#list>一般放在<w:p>之前(个人推测)。
以上是整个思路,代码部分没有做优化,很粗糙,可以直接忽视,重点不在那里,而在于模板修改部分,由于需要循环图片,没有查到这方面的资料,或者查到的不符合,自己琢磨出的方法,已经过检测可以实现,对这方面认识粗浅,望不误人子弟,看看就好,有什么不对或有需要修改的地方,望各位看官不吝指正。