【雛型】Docx套版列印功能試作

在我的程式開發生涯中,套版輸出指定格式的報表/表單一直是揮之不去的煩人差事,沒什麼營養,偏偏在每個案子裡幾乎都像小強一樣冒出來。

面對這類需求,轉成網頁是下策,因為列印時排版格式常會亂到一塌糊塗,鮮少讓人滿意。在經驗裡,Reporting Service是不錯的選擇(而且免費)。

但有些報表如確認書、通知書,在格式上並非Gird格式,跟Reporting Service最擅長的表格呈現有點差距,數量一多,要將User提供的Word檔一一轉成Reporting Service報表便成了苦差事,尤其某些文件被要求必須模仿到跟原始樣版分毫不差,常為了一兩公釐急死大丈夫。(有時,所謂原有樣版不過是某個User信手捻來的作品,並非官頒公訂,也沒人說過排版必須完全相符,但是你也知道的,各地風俗民情不同,尤其是澳洲...)

當以上情境發生,直接把User提供Word樣版裡文字換一換,照樣生出個Word檔來,在我看來是最簡便直覺省工的解法。

依我所知,動態產生Word要在ASP.NET上實現,有幾種解法,個人評估分析如下:

  1. 利用Office Automation(就如同VBA操作Word/Excel Object Model的做法)
    在Server端(ASP.NET)使用Office Automation技巧有些額外的風險,我自己有過不好的經驗,依MS KB的說法也不建議。
  2. 產出HTML後標註ContentType,讓Word/Excel開啟
    先前曾貼文介紹過這種做法,但逼真度與真實doc/xls有段差距,例如: doc沒法精確分頁、xls不支援多Worksheet... 等等。
  3. 使用3rd Party元件直接產生Office文件
    剛才提到的MS KB裡就有些建議的元件廠商,另外還有一家元件廠商ASPOSE的名字也常被提到。只是,元件的價格通常不便宜。
  4. docx/xlsx OpenXML
    Office 2007在檔案格式上做了大改革,揚離了過去的獨有二進位格式,變成公開的標準,而docx/xlsx,其實是ZIP起來的一堆XML及資源檔。這麼一來,修改XML也比以前搞封閉的二進位檔案格式簡單多了,也因為標準公開,可用的技術資源也多。
    使用docx/xlsx固然便捷,缺點是產出的docx/xlsx Office 2003/2000無法直接開啟,所幸微軟提供了免費的Word Viewer可以用來檢視,另外也有Microsoft Office Word、Excel 及 PowerPoint 2007 檔案格式相容性套件能將docx/xlsx轉存為doc/xls。在我看來,這點是可被容忍的缺陷。因此,我決定設法試做OpenXML的解決方案。

雖然我們可以自行將docx Rename成.zip,解壓縮後取出XML修改再存回壓縮回去,但MS提供了更好的支援服務: (SDK文件裡的範例寫得簡單明瞭,讓我一下子就上手!! 哦哦~~ Open XML Format SDK,我要輕輕為你唱首歌)

因為我想做的套版只是更換特定字串,用1.0的寫法也不算太鳥,加上打算用來Production環境,不想承擔CTP的風險。所以我決定現階段先用1.0 SDK正式版來實作,日後有更複雜運用時再改版。

今天只花了幾個小時,還真的把雛型做出來了:

1.先用Word 2007存一個docx檔案,把其中要動態更換的文字用[$keyName$]的格式標起來。(這種[$...$]的格我還為它取了個名字--Parser Tag,從ASP時代,我就一直用它來玩範本套表的把戲)

2.寫一小段程式,把[$keyName$]要更換的文字轉成Dictionary<string, string>,連同範本的檔案路徑一起傳給套表輔助元件DocxHelper,傳回的byte[]就是套表好的docx檔案內容。

    protected void btnExport_Click(object sender, EventArgs e)
    {
        string template = Server.MapPath("Notice.docx");
 
        Dictionary<string, string> dct = new Dictionary<string, string>()
            { { "today", DateTime.Today.ToString("yyyy-MM-dd") },
              { "name", txtName.Text },
              { "addr", txtAddr.Text },
              { "amount", txtAmount.Text } };
 
        Response.Clear();
        Response.ContentType = "application/octet-stream";
        Response.AddHeader("content-disposition", "attachment;filename=Notice.docx");
        Response.BinaryWrite(
            Darkthread.OpenXml.DocxHelper.MakeDocx(
                Server.MapPath("Notice.docx"), 
                dct)
        );
        Response.End();
    }

3.實際跑起來,按下【匯出DOCX】,TextBox裡的文字就會被填進下載的docx中,很棒吧!!

天哪,我真的中了大樂透了嗎? 不會吧? 莫非有人在耍我? (謎之聲: 不就是你自己嗎? 找時間去看一下精神科好唄?)

我打算在手邊的小案子試行這個新元件,陸續蒐集一些問題跟情境,待元件較為成熟後,到時再推出懶人包跟大家分享。

Published 30 July 2009 12:58 AM 由 Jeffrey
Filed under: ,


意見

# Kevin Lin said on 30 July, 2009 02:00 AM

黑大,這篇太晚出來了啦! 在公司處理這種狀況已經很多了,腦細胞不知死了幾次。

# alan said on 02 August, 2009 03:06 AM

黑大,我在公司有看到前輩用一樣的方法處理這一類user的報表!

但是確是用office XP搞定的!除了程式碼又臭又長之外!還有一個缺點!就是只能直接輸出到印表機就列印!無法像黑大一樣匯出.docx!

直接匯出的好處有

1.能再改完再印!

2.可以選只印第幾頁,

直接印的壞處就是!浪費紙,我常常聽到的抱怨是

印到一半沒紙!印表機自動跳到回收紙夾印,總數有11頁結果印到第10頁沒紙,第11頁就是用回收紙印(背面不是白的),這樣子就造成11頁全都要重印一次>"<粉累

# Tim said on 02 August, 2009 06:55 PM

黑大,

你是我的偶像跟救星!!!

# ABC said on 04 August, 2009 12:41 AM

Darkthread.OpenXml.DocxHelper.MakeDocx

這段沒分享出來交大如如何做

# alan said on 06 August, 2009 09:03 PM

黑大,請問懶人包何時會出^_^

我左等右等上等下等,為何對面的懶人包還不出來

# Jeffrey said on 07 August, 2009 10:57 AM

to ABC, alan, 因為元件還沒被用力操練過,尚未到"懶人可用"的地步,很有可能一堆地方需要修改調整,而我另外也擔心目前用的做法在某些情境下會出錯,因此打算晚幾個月才會丟出來。

PO這篇文的另一個目的也在分享"用OpenXML實作解決方案"的點子,想要早點動手自己試試的朋友,可以先參考OpenXML SDK 1.0 How to: Replace a Document Part in an Office Open XML Package by Using the Open XML API 的範例試看看,原理還蠻簡單的。

# James said on 01 September, 2009 01:29 AM

這種應該只適合用於固定的版型上,當狀況是:依資料內容,版型不同時,還是要思考一下解法XD

# rockzero(Rz) said on 27 September, 2009 08:25 AM

codeplex上面有兩套

DocX  原生(不需word、openxml SDK)

fleXdoc 套樣(不需word、但需openxml SDK 2.0CTP)

不過都在alpha階段(Flexdoc 還沒釋出)

提供給大家參考看看

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 

請輸入以上的數字:

搜尋

Go

<July 2009>
SunMonTueWedThuFriSat
2829301234
567891011
12131415161718
19202122232425
2627282930311
2345678
 
RSS
【工商服務】

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


BlogLook Score and Rank

Syndication