前幾天又有網友問起【雛型】Docx套版列印功能試作的程式範例。

當時文章發佈後,網友ABC, alan也問過何時釋出的問題。當時的考量是,我完成的只是PoC(Proof of Concept),尚非經實務驗證可行的解決方案,況且當下專案正式上線在即(文章中寧可用較簡陋的1.0正式版,也不要用2.0 CTP可見一斑),很快就可以檢驗這個做法的彈性及可靠度,等通過考驗再公佈也不遲。無奈,世事多變化,研究出套版方法沒多久,User調整了作業流程規劃,系統不再需要自行套版產生docx... orz 而後來,在其他專案也沒能再遇到實際上場的機會。就這樣,多年下來,這套做法一直維持在PoC階段,只能終年長坐冷板凳,抑鬱不得志,每日藉酒澆愁...

適逢網友再問起,就決定把當時的PoC程式分享出來,供需要參考的朋友自行取用。但必須聲明,這個版本只是PoC,沒實際上過戰場,也跟懶人包不同,在程式考量周延性或完整度上可能仍有欠缺,大家在應用時可想像成是小麥種子,而不是烤好的麵包。也很歡迎有將它實際應用在專案上的朋友能回饋分享自己裁種、收割、烘焙的心得~

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using DocumentFormat.OpenXml.Packaging;
using System.IO;
 
namespace Darkthread.OpenXml
{
    /// <summary>
    /// Ver 1.0 By Jeffrey Lee, 2009-07-29
    /// </summary>
    public class DocxHelper
    {
        /// <summary>
        /// Replace the parser tags in docx document
        /// </summary>
        /// <param name="oxp">OpenXmlPart object</param>
        /// <param name="dct">Dictionary contains parser tags to replace</param>
        private static void parse(OpenXmlPart oxp, Dictionary<string, string> dct)
        {
            string xmlString = null;
            using (StreamReader sr = new StreamReader(oxp.GetStream()))
            { xmlString = sr.ReadToEnd(); }
            foreach (string key in dct.Keys)
                xmlString = xmlString.Replace("[$" + key + "$]", dct[key]);
            using (StreamWriter sw = new StreamWriter(oxp.GetStream(FileMode.Create)))
            { sw.Write(xmlString); }
        }
 
        /// <summary>
        /// Parse template file and replace all parser tags, return the binary content of
        /// new docx file.
        /// </summary>
        /// <param name="templateFile">template file path</param>
        /// <param name="dct">a Dictionary containing parser tags and values</param>
        /// <returns></returns>
        public static byte[] MakeDocx(string templateFile, Dictionary<string, string> dct)
        {
            string tempFile = Path.GetTempPath() + ".docx";
            File.Copy(templateFile, tempFile);
 
            using (WordprocessingDocument wd = WordprocessingDocument.Open(
                tempFile, true))
            {
                //Replace document body
                parse(wd.MainDocumentPart, dct);
                foreach (HeaderPart hp in wd.MainDocumentPart.HeaderParts)
                    parse(hp, dct);
                foreach (FooterPart fp in wd.MainDocumentPart.FooterParts)
                    parse(fp, dct);
            }
            byte[] buff = File.ReadAllBytes(tempFile);
            File.Delete(tempFile);
            return buff;
        }
 
    }
}

PS: 雖然當時程式是配合SDK 1.0寫的,經實測這段程式可配合Open XML Format SDK 2.0運作無誤,專案要參照DocumentFormat.OpenXml以及WindowsBase,即可順利編譯、執行。


Comments

# by A*lan~

黑大響應Open Source的精神,相信一定有神人幫套版程式發揚光大。

# by Geng

感謝黑大的資訊提供,使用在Word套版上很便利,深深感謝你的分享。

# by jason

請問一下 如果有圖片,要怎麼堪入 word

# by Tom

剛玩了一下 Open XML Format SDK 2.5 可運作 不過文字取代那邊[]被其他代碼取代 只用$$是可行的 給黑大按個讚

# by Rola

To Tom: 我是取用黑大部分的程式碼(主要是[$...$]這用法很方便又不會衝突) 今天測試2.5版的依舊正常 你看一下原檔的xml是否有[$...$]不連續?

# by ken

請問要怎麼把圖片放復word檔

# by Jeffrey

to ken, 可透過OpenXML SDK達成,參考:https://msdn.microsoft.com/en-us/library/ee342530(v=office.12).aspx

# by Zach Lai

Hi 黑大, 依照這一篇實作後, 遇到了問題想跟您分享一下, 希望後面的人看到也可以避開這個問題 如果今天是很單純的套表, 不需要自己增加段落之類的, 利用replace的確是可以達到置換的功能, 但如果有自己增加段落, 此時一開始的XML跟增加段落後的XML貌似是兩個獨立的各體, 透過監看可以看到replace是有成功的, 但最後出來只有增加段落後的結果, 自訂的關鍵字不會被替換 以上提供給黑大

# by Jeffrey

to Zach Lai, 實務上我有遇過Tag看似一體在XML中卻是由多個元素組成的狀況,此時就會置換失敗。我用的技巧是將Tag加上顯目提示色彩(確保Tag在同一個XML元素內),換置時再將顯示提示樣式移除,提供你參考。

# by CHONGMAN

師兄你好呀, 我發現你個程式有個BUG, 就是內容是"<" 這個符號, 不能匯出WORD, 可否幫忙解決一下。謝謝!

# by Jeffrey

to CHONGMAN,更換 XML 是我很早期的做法,問題不少,建議改用新做法 https://blog.darkthread.net/blog/word-template-rendering-with-openxml-sdk/

Post a comment