前段時間接到一個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;//模板規則
}</ruledto>
複製代碼代碼如下:
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) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} 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文件
*/
@SuppressWarnings("unchecked")
public String build(Template template) {
InputStream in = null;
OutputStream fo = null;
// 產生檔案的路徑
String file = "d://test//" + template.getDesc() + ".doc";
try {
// 讀取範本文件
in = this.getClass().getClassLoader()
.getResourceAsStream(template.getTemplateFile());
SAXBuilder sb = new SAXBuilder();
Document doc = sb.build(in);
Element root = doc.getRootElement(); // 得到根元素
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);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (JDOMException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
in.close();
fo.close();
} catch (Exception e) {
e.printStackTrace();
}
}
return 檔案;
}
/**
* 針對<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>集合
*/
@SuppressWarnings("unchecked")
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--;
delFlag = true;
break;
}
}
}
}
return delFlag;
}
/**
* 針對含有表格的WORD模板, 尋找及替換<w:tbl>下的標籤。
* @param tblTagList :<w:tbl>集合
* @param rulesValue :RuleDTO集合
* @param ns :NameSpace對象
*/
@SuppressWarnings("unchecked")
private void changeValue4TblTag(List<element> tblTagList,
Vector<ruledto> rulesValue, Namespace ns) {
Element p = null;
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));
}
}</string,></string,></element></w:p></w:tc></element></w:tr></element></ruledto></element>< /w:tbl></w:tbl></w:p></w:tbl></w:r></w:tbl></element></element></ruledto></ele ment></w:tr></w:tbl></w:p></w:p></w:p></wx:sect></w:body></element></ w:tbl></element></w:p></element></wx:sect></string,></ruledto></element></ruledto></element></string,>
第四步:大功告成
幾點總結及注意事項:
1. 定義的元素name必須與template_rule.xml中對應相同的name的值一致,否則需要設定轉換規則。
2. 模板xml中定義的佔位符【※※】中的文字必須與template_rule.xml中對應的desc相同,否則需要設定轉換規則.
3. 配置好模板XML後,需要檢查標籤下的子節點是否為標籤(與WORD版本有關),如果沒有,則必須加上該標籤。
4. 如果要動態刪除標籤節點,則這個節點的內容需要在範本中的同一行,如果不是,則可以手動調整範本XML。
5. 如果需要實現WORD自動換行功能(關於模板中換行的方案暫沒有想到更好的),則需要先計算出對應模板該行的字數,然後採用空格填充來實現。