在近期的 MOSS 專案中,需求是根據 InfoPath 表單內容與 Word 模板生成對應的 Word 文件。若使用 Word COM API,會面臨後台進程管理、多用戶並發等棘手問題。因此選擇使用 OpenXML SDK 進行純伺服器端處理,避免安裝 Office 且效能更佳。
首先需引用 WindowsBase.dll,並定義必要的常數:
const string documentRelationshipType = "https://schemas.openxmlformats.org/officeDocument/2006/relationships/officeDocument";
const string strUri = "https://schemas.openxmlformats.org/wordprocessingml/2006/main";
static List<string> bookmarkText;
string bookmarkName;
以下方法會遍歷文件中的所有書籤起始標籤(w:bookmarkStart),提取書籤名稱與現有內容,存入 DataTable 供後續繫結:
void bindTable(string fileName) {
dt = new DataTable();
dt.Columns.Add("標籤", typeof(string));
dt.Columns.Add("內容", typeof(string));
DataRow dr;
Stream strm = null;
PackagePart documentPart;
Package package;
using (SPSite site = new SPSite("https://ascentn-moss:8686/")) {
SPWeb web = site.OpenWeb();
strm = web.GetFile(fileName).OpenBinaryStream();
package = Package.Open(strm, FileMode.Open, FileAccess.ReadWrite);
foreach (PackageRelationship documentRelationship in package.GetRelationshipsByType(documentRelationshipType)) {
NameTable nt = new NameTable();
XmlNamespaceManager nsManager = new XmlNamespaceManager(nt);
nsManager.AddNamespace("w", strUri);
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), documentRelationship.TargetUri);
documentPart = package.GetPart(documentUri);
XmlDocument xdoc = new XmlDocument();
xdoc.Load(documentPart.GetStream());
XmlNodeList nodeList = xdoc.SelectNodes("//w:bookmarkStart", nsManager);
foreach (XmlNode node in nodeList) {
dr = dt.NewRow();
if (node.NextSibling.Name == "w:bookmarkEnd") {
// 空書籤
bookmarkText = new List<string>();
bookmarkName = node.Attributes["w:name"].Value;
bookmarkText.Add(" ");
dr[0] = bookmarkName;
} else {
// 含內容書籤
bookmarkName = node.Attributes["w:name"].Value;
dr[0] = bookmarkName;
string bookmarkId = node.Attributes["w:id"].Value;
bookmarkText = new List<string>();
XmlNode nextParentNode = node.ParentNode;
XmlNode nodeIterate = node.NextSibling;
while (nodeIterate.Name != "w:bookmarkEnd") {
if (nodeIterate.Name == "w:r")
bookmarkText.Add(nodeIterate.InnerText);
nodeIterate = nodeIterate.NextSibling;
if (nodeIterate == null) {
// 跨段落處理
if (nextParentNode.NextSibling.Attributes.Count != 0) {
if (nextParentNode.NextSibling.Attributes["w:id"].Value == bookmarkId) {
nodeIterate = nextParentNode.NextSibling;
}
} else {
nextParentNode = nextParentNode.NextSibling;
nodeIterate = nextParentNode.FirstChild;
bookmarkText.Add('\n' + nodeIterate.InnerText);
}
}
}
}
dt.Rows.Add(dr);
}
}
package.Close();
}
}
取得書籤清單後,透過 Web Service 複製模板檔案,再以 OpenXML 逐一替換書籤內容,最後移除書籤標籤本身:
#region 處理書籤
string spUrl = (string)ds["SPServer"];
APWS.Service apws = new APWS.Service();
apws.Url = spUrl + "/spsws/Service.asmx";
apws.Credentials = WFSystem.GetSystemUserCredential();
System.IO.MemoryStream fileStream = new System.IO.MemoryStream();
string newfilename = fileLocation.EndsWith("/")
? fileLocation + fileName + template.Substring(template.LastIndexOf('.'))
: fileLocation + "/" + fileName + template.Substring(template.LastIndexOf('.'));
apws.SPCopyFile(template, newfilename);
fileStream.Write(apws.SPOpenBinary(newfilename), 0, apws.SPGetFileLenght(newfilename));
originalDocPackage = Package.Open(fileStream, FileMode.Open, FileAccess.ReadWrite);
foreach (PackageRelationship relationship in originalDocPackage.GetRelationshipsByType(documentRelationshipType)) {
Uri documentUri = PackUriHelper.ResolvePartUri(new Uri("/", UriKind.Relative), relationship.TargetUri);
originalDocumentPart = originalDocPackage.GetPart(documentUri);
break;
}
originalXmlDocument = new XmlDocument();
originalXmlDocument.Load(originalDocumentPart.GetStream());
NameTable nt = new NameTable();
XmlNamespaceManager xmlNamespaceManager = new XmlNamespaceManager(nt);
xmlNamespaceManager.AddNamespace("w", strUri);
#region 多書籤迴圈檢索
XmlNodeList bookmarknodeList = originalXmlDocument.SelectNodes("//w:bookmarkStart", xmlNamespaceManager);
XmlNodeList bookmarknodeEndList = originalXmlDocument.SelectNodes("//w:bookmarkEnd", xmlNamespaceManager);
XmlNode parentNodeOfBookMark;
foreach (XmlNode xmlnode in bookmarknodeList) {
foreach (string columnName in hvalues.Keys) {
if (xmlnode.Attributes["w:name"].Value == columnName) {
if (xmlnode.NextSibling.Name == "w:r") {
xmlnode.NextSibling.RemoveChild(xmlnode.NextSibling.LastChild);
XmlElement textElement = originalXmlDocument.CreateElement("w:t", strUri);
xmlnode.NextSibling.AppendChild(textElement);
XmlNode textNode = originalXmlDocument.CreateNode(XmlNodeType.Text, "w:t", strUri);
// 日期格式處理
if (hvalues[columnName].ToString().EndsWith("T00:00:00"))
textNode.Value = hvalues[columnName].ToString().Substring(0, hvalues[columnName].ToString().IndexOf('T'));
else
textNode.Value = hvalues[columnName].ToString();
textElement.AppendChild(textNode);
}
}
}
parentNodeOfBookMark = xmlnode.ParentNode;
parentNodeOfBookMark.RemoveChild(xmlnode);
}
foreach (XmlNode xmlnode in bookmarknodeEndList) {
parentNodeOfBookMark = xmlnode.ParentNode;
parentNodeOfBookMark.RemoveChild(xmlnode);
}
#endregion
// 儲存文件
originalXmlDocument.Save(originalDocumentPart.GetStream(FileMode.Create, FileAccess.Write));
byte[] bs = fileStream.ToArray();
apws.SPSaveBinary(newfilename, bs);
originalDocPackage.Close();
#endregion
此方案完全在伺服器端運行,無需安裝 Office,適合高併發環境。完整原始碼可參考:https://www.bianceng.net/dotnet/201212/808.htm