關於格式複雜的Docx套版列印
記得前幾天在網路上閒逛時看到了這兩篇文章 (再探文件套表 - Word套表與轉存PDF、關於Docx套版列印功能程式),正巧這問題幾個月前剛好處理過,而且需求更加的機車棘手,所以趁著還有記憶時趕緊記錄一下。
故事是這樣的,原本客戶都是利用Word來製作了一份排版的漂漂亮亮的報告,但這流程就快被我們開發中的線上報告系統給取代掉了。由於這份報告有固定的區塊,但區塊裡的內容就會依情況由客戶自由發揮了,並且內容不單單只有純文字,還會需要格式設定,如字型顏色、粗斜體和自由插入表格等等的編排。
當然,線上製作這塊好解決,放個WYSIWYG編輯器就能搞定了,問題在於客戶要的是產生出來提供列印的PDF要跟他之前美美的Word格式一模一樣。(一模一樣~ 一模一樣~~ 一模一樣~~~~)
聽到這需求心裡的第一個反應是當初答應沒問題可以做的業務是哪個傢伙 XD
好家在的是這個專案是用ASP.NET開發的。
由於變更的部份會包含格式,所以單純地用文字取代的方式無法滿足需求,經過了一段時間的研究,發現了這個可以直接將Html轉為OpenXML格式的元件 - Html to OpenXml
1. 先安裝Open XML SDK 2.0,再將HtmlToOpenXml.dll放進專案裡。
2. 定位的部份,因為要直接餵進OpenXML的Element物件,所以並不能單純的用文字取代的方式來做,需要使用RTF控制項。
開發人員頁籤得先到選項中開啟
再來設定控制項的標籤名字好方便搜尋。
3. 程式碼的部份如下
1: public static void GenHtml2DocxFromTemplate(string templateFilePath, Dictionary<string, string> values)
2: {
3: using (WordprocessingDocument package = WordprocessingDocument.Open(templateFilePath, true))
4: {
5: MainDocumentPart mainPart = getMainDocumentPart(package);
6:
7: HtmlConverter converter = new HtmlConverter(mainPart);
8:
9: foreach (string tag in values.Keys)
10: {
11: SdtElement block = findBlockByTag(package, tag);
12:
13: if (block != null)
14: {
15: appendHtmlContent(block, converter, values[tag]);
16: }
17: }
18: mainPart.Document.Save();
19: }
20: }
21:
22: private static MainDocumentPart getMainDocumentPart(WordprocessingDocument package)
23: {
24: MainDocumentPart mainPart = package.MainDocumentPart;
25: if (mainPart == null)
26: {
27: mainPart = package.AddMainDocumentPart();
28: new Document(new Body()).Save(mainPart);
29: }
30: return mainPart;
31: }
32:
33: private static SdtElement findBlockByTag(WordprocessingDocument package, string tag)
34: {
35: Tag elementTag = package.MainDocumentPart.Document.Descendants<Tag>()
36: .Where(
37: element => element.Val == tag
38: ).SingleOrDefault();
39:
40: if (elementTag == null) return null;
41:
42: return elementTag.Parent.Parent as SdtElement;
43: }
44:
45: private static void appendHtmlContent(SdtElement block, HtmlConverter converter, string html)
46: {
47: clearContent(block);
48:
49: var paragraphs = converter.Parse(html);
50:
51: foreach (var paragraph in paragraphs)
52: {
53: getSdtContent(block).Append(paragraph);
54: }
55: }
56:
57: private static void clearContent(SdtElement block)
58: {
59: getSdtContent(block).RemoveAllChildren();
60: }
61:
62: private static OpenXmlElement getSdtContent(SdtElement block)
63: {
64: OpenXmlElement oxElement = block.Elements()
65: .Where(elememt => elememt.LocalName == "sdtContent").SingleOrDefault();
66:
67: if (oxElement != null && oxElement.FirstChild is TableCell)
68: return oxElement.FirstChild;
69: return oxElement;
70: }
4. 最後就能得到以下的結果
完美!!!
這種做法不僅可以先利用Word做好範本的排版,也能透過HTML WYSIWYG編輯器再做第二層的排版,自由度大增。另外再透過Word另存PDF的功能,便能直接做出可媲美Word功能的線上報告產生器。