FreeMaker + xml 导出word
首先介绍几种java导出word方案
1、Jacob是Java-COM Bridge的缩写,它在Java与微软的COM组件之间构建一座桥梁。使用Jacob自带的DLL动态链接库,并通过JNI的方式实现了在Java平台上对COM程序的调用。DLL动态链接库的生成需要windows平台的支持。
2、 Apache POI包括一系列的API,它们可以操作基于MicroSoft OLE 2 Compound Document Format的各种格式文件,可以通过这些API在Java中读写Excel、Word等文件。他的excel处理很强大,对于word还局限于读取,目前只能实现一些简单文件的操作,不能设置样式。
3、 Java2word是一个在java程序中调用 MS Office Word 文档的组件(类库)。该组件提供了一组简单的接口,以便java程序调用他的服务操作Word 文档。
这些服务包括: 打开文档、新建文档、查找文字、替换文字,插入文字、插入图片、插入表格,在书签处插入文字、插入图片、插入表格等。填充数据到表格中读取表格数据 ,1.1版增强的功能: 指定文本样式,指定表格样式。如此,则可动态排版word文档。
4、 iText操作Excel还行。对于复杂的大量的word也是噩梦。用法很简单, 但是功能很少, 不能设置打印方向等问题。
5、 JSP输出样式基本不达标,而且要打印出来就更是惨不忍睹。
6、 用XML做就很简单了。Word从2003开始支持XML格式,大致的思路是先用office2003或者2007编辑好word的样式,然后另存为xml,将xml翻译为FreeMarker模板,最后用java来解析FreeMarker模板并输出Doc。经测试这样方式生成的word文档完全符合office标准,样式、内容控制非常便利,打印也不会变形,生成的文档和office中编辑文档完全一样。
看过方案后就知道了 第 6 种方案效果好点,下面我们就举个例子介绍下这个方案
首先要制作模板 模板里的需要传入的数据用${data} 填充,在代码里给其赋值即可
模板制作好后 ,点击另存为 xml 文件 如:data.xml
如果xml需要动态填充 可以用
<!-- 循环开始 -->
<#list personnelView as e>
</#list>
<!-- 循环结束 -->
<!-- 循环开始 -->
<#list personnelView as e> // personnelView为循环集合
<w:tr wsp:rsidR="001E023B" wsp:rsidTr="004E45BD">
<w:tblPrEx>
<w:tblCellMar>
<w:top w:w="0" w:type="dxa"/>
<w:bottom w:w="0" w:type="dxa"/>
</w:tblCellMar>
</w:tblPrEx>
<w:trPr>
<w:trHeight w:val="405"/>
</w:trPr>
<w:tc>
<w:tcPr>
<w:tcW w:w="1920" w:type="dxa"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:pPr>
<w:jc w:val="center"/>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e_index +1}</w:t> //e_index 为索引从0 开始
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="1083" w:type="dxa"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e.COLUMN_1}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="1142" w:type="dxa"/>
<w:gridSpan w:val="3"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:pPr>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e.COLUMN_2}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="1578" w:type="dxa"/>
<w:gridSpan w:val="3"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e.COLUMN_3}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="1168" w:type="dxa"/>
<w:gridSpan w:val="2"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e.COLUMN_4}</w:t>
</w:r>
</w:p>
</w:tc>
<w:tc>
<w:tcPr>
<w:tcW w:w="2181" w:type="dxa"/>
<w:gridSpan w:val="3"/>
<w:vAlign w:val="center"/>
</w:tcPr>
<w:p wsp:rsidR="001E023B" wsp:rsidRDefault="001D2E3B" wsp:rsidP="004E45BD">
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t>${e.COLUMN_5}</w:t>
</w:r>
</w:p>
</w:tc>
</w:tr>
</#list>
<!-- 循环结束 -->
二、详细介绍
1、以上呢都是我看别人的东西复制过来的其实都一样FreeMaker 最主要的一个特点就是整理模板数据替换用${data};data就是你的数据
那在java中保存的数据时用map来存放数据
如:Map<String, Object> root = new HashMap<String, Object>();、
如现在要保存一个实体user;
User的对象有id,name,sex,menun(菜单),picstr(头像)这些属性
User userObj=new User();
在保存的时候只要将数据库中的数据封装到user中然后通过root.put(“user”, userObj);
在模板相应的位置中代替这些值如名称:${user.name},性别${user.sex}等
当遇到对象有子集的时候也就是用户有几个菜单时可以使用<#list user.menun as m>在这个中间又可以得到自己想要的东西如${m.menunName}</#list>当然这个是循环的对于它循环的索引直接使用变量名+下划线index即可如:${m_index};
2、这些东西估计大家都会现在说说插入目录问题
因为目录需要内容创建完成之后才能刷新目录所以在此处我寻求好多方法不知道怎么使用最后我折中想了个办法就是在xml转为word完成之后我另存一份word;在另存之前我先刷新目录这样就把目录问题解决了;废话少说例子上:
public void xmlToWord(String docfile, String htmlfile) {
ActiveXComponent app =null;
try {
app=new ActiveXComponent("Word.Application"); // 启动word
app.setProperty("Visible", new Variant(false));
Dispatch docs = app.getProperty("Documents").toDispatch();
Dispatch doc = Dispatch.invoke(
docs,
"Open",
Dispatch.Method,
new Object[] { docfile, new Variant(false),
new Variant(true) }, new int[1]).toDispatch();
Dispatch selection=app.getProperty("Selection").toDispatch();
Dispatch find = Dispatch.call(selection,"Find").toDispatch();
/* 设置要查找的内容 */
Dispatch.put(find,"Text","目录");
/* 向前查找 */
Dispatch.put(find,"Forward","True");
/* 设置格式 */
Dispatch.put(find,"Format","True");
/* 大小写匹配 */
Dispatch.put(find,"MatchCase","True");
/* 全字匹配 */
Dispatch.put(find,"MatchWholeWord","True");
/* 查找并选中 */
Dispatch.call(find,"Execute").getBoolean();
/* 取得ActiveDocument、TablesOfContents、range对象 */
Dispatch ActiveDocument = app.getProperty("ActiveDocument").toDispatch();
Dispatch TablesOfContents = Dispatch.get(ActiveDocument,"TablesOfContents").toDispatch();
Dispatch.call(selection,"MoveRight"); //移动光标到右边
Dispatch.call(selection, "TypeParagraph"); //换行
Dispatch range = Dispatch.get(selection, "Range").toDispatch();
/* 增加目录 */
Dispatch.call(TablesOfContents,"Add",range,new Variant(true),new Variant(1),new Variant(3),new Variant(true),new Variant(""),new Variant(true),new Variant(true));
Dispatch.invoke(doc, "SaveAs", Dispatch.Method, new Object[] {
htmlfile, new Variant(WORD_DOC) }, new int[1]);
Variant f = new Variant(false);
Dispatch.call(doc, "Close", f);
} finally {
if(app!=null)
app.invoke("Quit", new Variant[] {});
}
}
原理就是我在模板中有一页单独为目录留下来有目录两字,通过查找目录两字并且定位光标到目录之后并且换行;然后添加目录保存即可;
3、图片问题
相信大家都知道在word里面插入一张图片保存为xml的时候图片的位置会产生出很长的一串字符当你看到这个的时候那么替换图片你有希望;观察发现是二进制字符串所以你在准备数据的时候也要提前把需要保存的图片转成二进制字符串(将图片的字节流进行Base64编码)并存到变量中如picstr并在xml中替换${user.picstr}
<w:pict>
<v:shapetype id="_x0000_t75" coordsize="21600,21600" o:spt="75" o:preferrelative="t" path="m@4@5l@4@11@9@11@9@5xe" filled="f" stroked="f">
<v:stroke joinstyle="miter"/>
<v:formulas>
<v:f eqn="if lineDrawn pixelLineWidth 0"/>
<v:f eqn="sum @0 1 0"/>
<v:f eqn="sum 0 0 @1"/>
<v:f eqn="prod @2 1 2"/>
<v:f eqn="prod @3 21600 pixelWidth"/>
<v:f eqn="prod @3 21600 pixelHeight"/>
<v:f eqn="sum @0 0 1"/>
<v:f eqn="prod @6 1 2"/>
<v:f eqn="prod @7 21600 pixelWidth"/>
<v:f eqn="sum @8 21600 0"/>
<v:f eqn="prod @7 21600 pixelHeight"/>
<v:f eqn="sum @10 21600 0"/>
</v:formulas>
<v:path o:extrusionok="f" gradientshapeok="t" o:connecttype="rect"/>
<o:lock v:ext="edit" aspectratio="t"/>
</v:shapetype>
<w:binData w:name="wordml://06000001.png">${user.picstr?if_exists}
</w:binData>
<v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:297pt;height:284.25pt">
<v:imagedata src="wordml://06000001.png" o:title="gisImage"/>
</v:shape>
</w:pict>
这样图的问题呢就解决了。但是大多时候需求不是这样的是有多个图片需要同时出现虽然变量你已经替换但是你会发现图片都出来了但是都是显示的是同一张图片。仔细研究发现图片在替换变量的时候不只是要替换内容的那块同时也要注意名称的地方也就是w:name="wordml://06000001.png">06000001.png这个就是他在word中生存的名称因为图片的不确定性质所以你的名称就无法定下来所以灵机一动把名称也弄成随机变量(这个地方随机变量你可以在java端处理好这边用变量替换或者使用现成的变量那就是使用list循环)因为图片是多个势必你要使用<#list image as i>这个时候${i_index}这个是变化的那么图片名称就改为
<w:binData w:name="${"wordml://0600000"+i_index+1+".jpg"}">${i.picStr?if_exists}
</w:binData>
<v:shape id="_x0000_i1025" type="#_x0000_t75" style="width:297pt;height:284.25pt">
<v:imagedata src="${"wordml://0600000"+i_index+1+".jpg"}" o:title="gisImage2"/>
</v:shape>
在这里啰嗦下一定要注意双引号图片问题完美解决;
4、页眉页脚问题好多人在制定模板的时候都忘记了设置页眉页脚页面设置等问题;一旦模板做好并且把变量替换完成之后那么再使用word就无法打开xml文件了当然你就无法再设置word的东西了所以只能通过代码来添加嘿嘿本人就遇到过;当然有人也说我可以重新做反正也不多我设置好了在替换变量对于这种想法的人我表示理解……;等你的模板什么时候有几十页的时候,或者循环套好几层的时候我看你肿么办。言归正传:
页面页脚添加问题:从新新建一个word把你想要在模板中实现好的所有东西都设置好然后copy这些代码到xml问题解决呵呵:上代码:
<w:style w:type="paragraph" w:styleId="a6">
<w:name w:val="header"/>
<wx:uiName wx:val="页眉"/>
<w:basedOn w:val="a"/>
<w:rsid w:val="00302287"/>
<w:pPr>
<w:pStyle w:val="a6"/>
<w:pBdr>
<w:bottom w:val="single" w:sz="6" wx:bdrwidth="15" w:space="1" w:color="auto"/>
</w:pBdr>
<w:tabs>
<w:tab w:val="center" w:pos="4153"/>
<w:tab w:val="right" w:pos="8306"/>
</w:tabs>
<w:snapToGrid w:val="off"/>
<w:jc w:val="center"/>
</w:pPr>
<w:rPr>
<wx:font wx:val="Times New Roman"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
</w:style>
<w:style w:type="paragraph" w:styleId="a7">
<w:name w:val="footer"/>
<wx:uiName wx:val="页脚"/>
<w:basedOn w:val="a"/>
<w:rsid w:val="00302287"/>
<w:pPr>
<w:pStyle w:val="a7"/>
<w:tabs>
<w:tab w:val="center" w:pos="4153"/>
<w:tab w:val="right" w:pos="8306"/>
</w:tabs>
<w:snapToGrid w:val="off"/>
<w:jc w:val="left"/>
</w:pPr>
<w:rPr>
<wx:font wx:val="Times New Roman"/>
<w:sz w:val="18"/>
<w:sz-cs w:val="18"/>
</w:rPr>
</w:style>
其实最主要的是下面这段:
<w:hdr w:type="odd">
<wx:pBdrGroup>
<wx:borders>
<wx:bottom wx:val="solid" wx:bdrwidth="15" wx:space="1" wx:color="auto"/>
</wx:borders>
<w:p wsp:rsidR="00302287" wsp:rsidRDefault="00302287">
<w:pPr>
<w:pStyle w:val="a6"/>
</w:pPr>
<w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>测振报告</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>${vibrometerMissionVo.testReport.reportTitle?if_exists}</w:t>
</w:r>
<w:pict>
<v:shapetype id="_x0000_t136" coordsize="21600,21600" o:spt="136" adj="10800" path="m@7,l@8,m@5,21600l@6,21600e">
<v:formulas>
<v:f eqn="sum #0 0 10800"/>
<v:f eqn="prod #0 2 1"/>
<v:f eqn="sum 21600 0 @1"/>
<v:f eqn="sum 0 0 @2"/>
<v:f eqn="sum 21600 0 @3"/>
<v:f eqn="if @0 @3 0"/>
<v:f eqn="if @0 21600 @1"/>
<v:f eqn="if @0 0 @2"/>
<v:f eqn="if @0 @4 21600"/>
<v:f eqn="mid @5 @6"/>
<v:f eqn="mid @8 @5"/>
<v:f eqn="mid @7 @8"/>
<v:f eqn="mid @6 @7"/>
<v:f eqn="sum @6 0 @5"/>
</v:formulas>
<v:path textpathok="t" o:connecttype="custom" o:connectlocs="@9,0;@10,10800;@11,21600;@12,10800" o:connectangles="270,180,90,0"/>
<v:textpath on="t" fitshape="t"/>
<v:handles>
<v:h position="#0,bottomRight" xrange="6629,14971"/>
</v:handles>
<o:lock v:ext="edit" text="t" shapetype="t"/>
</v:shapetype>
<v:shape id="PowerPlusWaterMarkObject3" o:spid="_x0000_s4099" type="#_x0000_t136" style="position:absolute;left:0;text-align:left;margin-left:0;margin-top:0;width:526.9pt;height:58.5pt;rotation:315;z-index:-1;mso-position-horizontal:center;mso-position-horizontal-relative:margin;mso-position-vertical:center;mso-position-vertical-relative:margin" o:allowincell="f" fillcolor="gray" stroked="f">
<v:fill opacity=".5"/>
<v:textpath style="font-family:"黑体";font-size:1pt" string="${watermark}"/>
</v:shape>
</w:pict>
</w:r>
</w:p>
</wx:pBdrGroup>
</w:hdr>
<w:ftr w:type="even">
<w:p wsp:rsidR="00302287" wsp:rsidRDefault="00302287">
<w:pPr>
<w:pStyle w:val="a7"/>
</w:pPr>
</w:p>
</w:ftr>
<w:ftr w:type="odd">
<w:p wsp:rsidR="00302287" wsp:rsidRDefault="00302287">
<w:pPr>
<w:pStyle w:val="a7"/>
</w:pPr>
</w:p>
</w:ftr>
<w:ftr w:type="odd">
<w:p wsp:rsidR="00302287" wsp:rsidRDefault="00302287">
<w:pPr>
<w:pStyle w:val="a4"/>
<w:ind w:left-chars="1190" w:first-line-chars="450"/>
<w:rPr>
<w:rFonts w:hint="fareast"/>
</w:rPr>
</w:pPr>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>第</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:instrText> PAGE </w:instrText>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:noProof/>
</w:rPr>
<w:t>1</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="end"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>页</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>共</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="begin"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:instrText> NUMPAGES </w:instrText>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="separate"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:noProof/>
</w:rPr>
<w:t>1</w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
</w:rPr>
<w:fldChar w:fldCharType="end"/>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
</w:rPr>
<w:t> </w:t>
</w:r>
<w:r>
<w:rPr>
<w:rStyle w:val="a8"/>
<w:rFonts w:hint="fareast"/>
<wx:font wx:val="宋体"/>
</w:rPr>
<w:t>页</w:t>
</w:r>
</w:p>
</w:ftr>
实现出来的样子就是
但是往往人们还需要设置页面边距,页面板式首页不同/奇偶不同等问题
<w:pgSz w:w="12240" w:h="15840" w:code="9"/>
<w:pgMar w:top="1440" w:right="1800" w:bottom="1440" w:left="1800" w:header="720" w:footer="720" w:gutter="0"/>
<w:pgNumType w:start="0"/>
<w:titlePg/>
<w:cols w:space="425"/>
<w:docGrid w:type="lines" w:line-pitch="312"/>
关于页面设置方面的代码都会在这里产生你只要细细看就会发现相应的代码
做个简单解释: <w:titlePg/>首页不同,<w:pgNumType w:start="0"/>
页面的起始也为第0页效果为:
以上内容写的有点乱希望大家见谅因为是自己使用过后的一点总结希望对大家有所帮助有需要QQ交流:316681406
另外附送FreeMaker 详细api