前段時間接到一個web應用自動生成word的需求,現整理了下一些關鍵步驟拿來分享一下。
思路:(注:這裡隻針對word2003版本,其它版本大同小異。)
因為word檔案内部的資料及格式等是通過xml檔案的形式存儲的,是以word檔案可以很友善的實作由doc到xml格式的互相轉換,而操作xml檔案就友善的多了,這樣就實作了與平台無關的各種操作,通過節點的查詢、替換、删除、新增等生成word檔案。是以,根據模闆生成word檔案實質就是由使用者資料替換xml檔案中特殊标簽,然後另存為一個doc檔案的過程。
下面列舉涉及到的一些關鍵步驟(以介紹信為例)
第一步:根據需求制作word模闆
建立一個doc格式的word檔案,根據需要填寫好模闆内容,設定好模闆的格式,包括字型,樣式,空行等等,需要填充的資料使用特殊标簽(如:【※機關名稱※】)預先占位,然後将建立的word檔案另存為xml格式檔案。這樣, word模闆就制作完成了,代碼如下:

第二步:在配置檔案中配置好模闆資訊
新增名為template-rule.xml的配置檔案,每個template節點對應一個模闆類型。每個template中有一個taglist節點,該節點包含的所有子節點包含了模闆所有将要替換、删除節點資訊,節點資訊包括:節點值,節點屬性英文名稱,中文描述,字段類型,可否删除等資訊。在設定這個配置檔案時候,需要注意desc屬性的值必須與模闆xml中的占位符一緻。比如:模闆xml中設定的年份這個錄入項【※年※】需與template-rule.xml中的desc="年"名稱對應,代碼如下:
<?xml version="1.0" encoding="gb2312"?>
<!-- 模闆定義 -->
<templates>
<!-- 說明: s-字元串; d-日期; e-金額; m-大寫金額; ifemptydelete: t-值為空删除父節點,預設為f -->
<template name="recommend-letter" desc="介紹信" templatefile="template4.xml">
<taglist remark="單值标簽清單">
<tag id="1" name="topartment" desc="接收部門" type="s" ifemptydelete="t">#topartment</tag><!--接收部門-->
<tag id="2" name="ownername" desc="姓名" type="s">#ownername</tag><!--姓名-->
<tag id="3" name="countnum" desc="人數" type="s">#countnum</tag><!--人數-->
<tag id="4" name="business" desc="内容" type="s">#business</tag><!--内容-->
<tag id="5" name="usefuldays" desc="有效期" type="s">#usefuldays</tag><!--有效期-->
<tag id="6" name="year" desc="年" type="s">#year</tag><!--年-->
<tag id="7" name="month" desc="月" type="s">#month</tag><!--月-->
<tag id="8" name="day" desc="日" type="s">#day</tag><!--日-->
</taglist>
</template>
</templates>
第三步:編寫java代碼
/**
* 參數及規則
*/
public class ruledto {
/**
* tag名稱
*/
private string parmname;
* tag描述
private string parmdesc;
* tag序号
private string parmseq;
* tag值類型
private string parmtype;
* tag參數名稱
private string parmregular;
* tag值
private string parmvalue;
* tag值為空删除該屬性
private string ifemptydelete;
* 描述: word模闆資訊
public class template {
private string name;//模闆名
private string desc;//模闆描述
private string templatefile;//模闆檔案
private vector<ruledto> rules;//模闆規則
}
public class wordbuilder {
* 根據模闆讀取替換規則
* @param templatename 模闆id
@suppresswarnings("unchecked")
public template loadrules(map<string, string> rulevalue) {
inputstream in = null;
template template = new template();
// 規則配置檔案路徑
string rulefile = "template-rule.xml";
// 模闆規則名稱
string templaterulename = "";
try {
templaterulename = rulevalue.get("rulename");
// 讀取模闆規則檔案
in = this.getclass().getclassloader().getresourceasstream(rulefile);
// 解析模闆規則
saxbuilder sb = new saxbuilder();
document doc = sb.build(in);
element root = doc.getrootelement(); // 得到根元素
list<element> templatelist = root.getchildren();// 所有模闆配置
element element = null;
vector<ruledto> rules = null;
for (int i = 0; i < templatelist.size(); i++) {// 周遊所有模闆
element = (element) templatelist.get(i);
string templatename = element.getattributevalue("name");
if (templaterulename.equalsignorecase(templatename)) {// 查找給定的模闆配置
template.setname(templatename);
template.setdesc(element.getattributevalue("desc"));
template.settemplatefile(element
.getattributevalue("templatefile"));
list<element> taglist = ((element) element.getchildren()
.get(0)).getchildren();// tag清單
element tag = null;
ruledto ruledto = null;
rules = new vector<ruledto>();
for (int j = 0; j < taglist.size(); j++) {
tag = (element) taglist.get(j);
ruledto = new ruledto();
ruledto.setparmname(tag.getattributevalue("name"));
ruledto.setparmdesc("【※"
+ tag.getattributevalue("desc") + "※】");
ruledto.setparmseq(tag.getattributevalue("id"));
ruledto.setparmtype(tag.getattributevalue("type"));
if ("t".equalsignorecase(tag
.getattributevalue("ifemptydelete"))) {// 是否可删除标記
ruledto.setifemptydelete("t");
} else {
ruledto.setifemptydelete("f");
}
ruledto.setparmregular(tag.gettext());
// 值
// 判斷參數類型
string value = (string) ((map<string, string>) rulevalue)
.get(ruledto.getparmregular().replaceall("#",
""));
ruledto.setparmvalue(value);
rules.add(ruledto);
}
template.setrules(rules);
break;
}
}
} catch (filenotfoundexception e) {
e.printstacktrace();
} catch (jdomexception e) {
} catch (ioexception e) {
} finally {
try {
in.close();
} catch (exception e) {
e.printstacktrace();
}
return template;
}
* 查找父節點
public element findelement(element currnode, string parentnodeid) {
// 節點标示為空
if (currnode == null || parentnodeid == null) {
return null;
element pnode = null;
do {
pnode = currnode.getparent();
currnode = pnode;
} while (parentnodeid.equalsignorecase(pnode.getname()));
return pnode;
* 生成word檔案
public string build(template template) {
outputstream fo = null;
// 生成檔案的路徑
string file = "d:\\test\\" + template.getdesc() + ".doc";
// 讀取模闆檔案
in = this.getclass().getclassloader()
.getresourceasstream(template.gettemplatefile());
namespace ns = root.getnamespace();// namespace
// word 03模闆存在<wx:sect>元素
list<element> sectlist = root.getchild("body", ns).getchildren();
element sectelement = (element) sectlist.get(0);
// <w:p>下的标簽集合
list<element> ptaglist = sectelement.getchildren("p", ns);
// <w:tbl>下的标簽集合
list<element> tbltaglist = sectelement.getchildren("tbl", ns);
if (ptaglist != null && ptaglist.size() > 0) {
changevalue4ptag(ptaglist, template.getrules(), ns, null);
if (tbltaglist != null && tbltaglist.size() > 0) {
changevalue4tbltag(tbltaglist, template.getrules(), ns);
// 寫檔案
xmloutputter outp = new xmloutputter(" ", true, "utf-8");
fo = new fileoutputstream(file);
outp.output(doc, fo);
fo.close();
return file;
* 針對<w:body><wx:sect><w:p>這種層級的word模闆, 查找及替換<w:p>下的标簽。
* @param ptaglist :<w:p>集合
* @param rulesvalue :ruledto集合
* @param ns :namespace對象
* @param trchildren :<w:tbl>的子節點<w:tr>集合
private boolean changevalue4ptag(list<element> ptaglist,
vector<ruledto> rulesvalue, namespace ns, list<element> trchildren) {
element p = null;
boolean delflag = false;
for (int i = 0; i < ptaglist.size(); i++) {
boolean delcurrnode = false;// 删除目前節點
boolean delcurrnode4tabwr = false;// 删除table中單行節點
p = (element) ptaglist.get(i);
list<element> pchild = p.getchildren("r", ns);
for (int j = 0; pchild != null && j < pchild.size(); j++) {
element pchildren = (element) pchild.get(j);
element t = pchildren.getchild("t", ns);
if (t != null) {
string text = t.gettexttrim();
if (text.indexof("【※") != -1) {
for (int v = 0; v < rulesvalue.size(); v++) {
ruledto dto = (ruledto) rulesvalue.get(v);
if (text.indexof(dto.getparmdesc().trim()) != -1) {
// 判斷屬性值是否為可空删除
if ("t".equals(dto.getifemptydelete())
&& stringutils.isblank(dto
.getparmvalue())) {
// 删除該節點頂級節點
text = "";
if (trchildren != null) {// 針對<w:tbl>删除該行
element element = ((element) p
.getparent()).getparent();
trchildren.remove(element);
delcurrnode4tabwr = true;
} else {// 針對<w:r>删除段
// ptaglist.remove(p);
ptaglist.remove(pchildren);
delcurrnode = true;
}
break;
} else {
text = text.replaceall(dto.getparmdesc()
.trim(), dto.getparmvalue());
}
}
t.settext(text);
if (delcurrnode4tabwr) {// <w:tbl>table下的行節點已删除
delflag = true;
break;
} else if (delcurrnode) {// <w:p>下的節點已删除
i--;
return delflag;
* 針對含有表格的word模闆, 查找及替換<w:tbl>下的标簽。
* @param tbltaglist :<w:tbl>集合
private void changevalue4tbltag(list<element> tbltaglist,
vector<ruledto> rulesvalue, namespace ns) {
for (int i = 0; tbltaglist != null && i < tbltaglist.size(); i++) {
p = (element) tbltaglist.get(i);
list<element> trchildren = p.getchildren("tr", ns);
for (int j = 0; trchildren != null && j < trchildren.size(); j++) {// 循環<w:tr>
element pchildren = (element) trchildren.get(j);
list<element> tctaglist = pchildren.getchildren("tc", ns);
for (int c = 0; tctaglist != null && c < tctaglist.size(); c++) {// 循環<w:tc>取<w:p>集合
element tcchildren = (element) tctaglist.get(c);
list<element> ptaglist = tcchildren.getchildren("p", ns);
boolean delflag = changevalue4ptag(ptaglist, rulesvalue,
ns, trchildren);
if (delflag) {// 删除行後需要改變trchildren的指針位置
j--;
public static void main(string[] args) throws exception {
wordbuilder word = new wordbuilder();
map<string, string> map = new hashmap<string, string>();
//填充參數
map.put("topartment", "xxx公司");
map.put("ownername", "張三");
map.put("countnum", "5");
map.put("business", "例行檢查");
map.put("usefuldays", "15");
map.put("year", "2014");
map.put("month", "5");
map.put("day", "13");
map.put("rulename", "recommend-letter");
template template = word.loadrules(map);
//直接打開檔案
runtime.getruntime().exec("explorer " + word.build(template));
第四步:大功告成
幾點總結及注意事項:
1. 定義的元素name必須與template_rule.xml中對應相同的name的值一緻,否則需要設定轉換規則。
2. 模闆xml中定義的占位符【※※】中的文字必須與template_rule.xml中對應的desc相同,否則需要設定轉換規則.
3. 在配置好模闆xml後,需要檢查<w:body>标簽下的子節點是否是<wx:sect>标簽(與word版本有關),如果沒有,則必須加上該标簽。
4. 如果要動态删除<w:p>标簽節點,則這個節點的内容需要在模闆中的同一行,如果不是,則可以手動調整模闆xml。
5. 如果需要實作word自動換行功能(關于模闆中換行的方案暫沒有想到更好的),則需要首先計算出對應模闆該行的字數,然後采用空格填充來實作。