【3】利用Word模板生成文檔的總結

閱讀目錄程序員

 

 

在各種應用系統開發中,和Word相關的應用可謂至關普遍。如各種MIS系統、各類和實際業務結合緊密的系統、須要制式報表的系統等,都須要對Word進行操做,典型的應用包括:編程

一、內嵌Word。在系統中內嵌Word,這樣,既能夠利用Word強大的功能進行文檔的新建、編輯、修改、排版,同時還節省了用戶對於編輯器操做的學習成本,提升了文檔格式的通用性。app

二、Word的二次開發。經過Word自帶的宏,利用VBA(Visual Basic Appplication)進行開發,實現各類複雜的自動化功能。框架

三、前臺不顯示Word操做界面,而在後臺對Word文檔進行操做。包括:1)讀入word文檔,解析內容,獲取須要的數據;2)把數據寫入Word模板,生成符合格式要求的Word文檔。編輯器

上述應用中,前兩個應用領域相對特定,且須要對Word進行深度的二次開發,本人涉獵有限,於是不進行過多的討論。而對於第三種應用,因爲Word軟件的普及率很是高,基本上能夠把DOC文檔看做是一個通用的文檔結構。同時,Word在格式控制方面功能很是強大。所以,使用Word來製做輸出文件或者報表,不光格式易於控制(用戶能夠在Word中製做好須要的模板,替換真實數據就得到須要的輸出文檔或者報表),用戶的接受度等方面都有很大的優點,近年來愈來愈受到重視。下文主要嘗試討論如何利用Word模板生成須要的Word文檔的實現。函數

Word二次開發概況

1983年,微軟發佈了基於MS-DOS的Word 1.0版,至今已經30餘年了。對於Word的二次開發,也是有着悠久的歷史。就本人的開發經驗而言,在近十年前,就已經在Visual Basic 6.0平臺上,進行內嵌Word的開發,這個在當年也是很是流行的一種開發。時至今日,Word的二次開發仍然是每一個開發者頻繁遇到的問題。學習

可是,Word的開發相對於其餘的二次開發,甚至於相對於同門的也很複雜的Excel來講,開發的難度都要大不少,緣由來自如下方面:測試

一、Word 的對象結構複雜。因爲Wrod有着久遠的歷史,這既是它的優點也是它的包袱,它必需要保持足夠的兼容性,所以DOC文檔結構也就變得很是的複雜了。在Word中,有着複雜的對象結構,如Application、Document、BookMarks、Range、Selection、Paragraph等,它們之間既有層級關係,還有嵌套關係,有時爲了一個小小的功能,卻沒法找到操做的對象。網站

clip_image001

Word 的對象結構ui

二、Word功能複雜。做爲微軟的拳頭產品,多年以來,Word的功能愈來愈強大。儘管大多數的功能對於二次開發來講是徹底用不到的,但還得去了解和學習,這就須要付出額外的代價。以Find爲例,其參數居然高達15個,以下所示:

Find.Execute(FindText, MatchCase, MatchWholeWord, MatchWildcards, MatchSoundsLike, MatchAllWordForms, Forward, Wrap, Format, ReplaceWith, Replace, MatchKashida, MatchDiacritics, MatchAlefHamza, MatchControl)

但大多數狀況下,咱們只會用到FindText、ReplaceWith等極少數參數而已。

三、版本問題。Word的衆多版本也給二次開發帶來不少困擾,開發者必需要對於當前多種Word版本都存在的狀況有所考慮,並作好兼容性的處理才行。

使用DsoFramer進行開發

談到Word的二次開發,就必需要提到DsoFramer。它是微軟提供一款開源的用於在線編輯、調用Word、 Excel 、PowerPoint等的ActiveX控件。國內不少著名的OA中間件,電子印章,簽名留痕等大多數是依此改進而來的。

DsoFramer操做Word很簡單,加載ActiveX控件後就能夠直接操做Office文檔了。以咱們要進行的主要操做——替換文檔中的關鍵字爲例,在Visual Basic中代碼以下:

dso.Open "new.doc" dso.Replace "[標題]","新標題",3 dso.Save "c:\new2.doc" dso.Close

在VB6中加載控件,以下圖所示:

image

因爲DsoFramer是COM時代的產物,適用於VB、VC開發者,在 .Net下開發,或者進行Web應用開發,就顯得有點力不從心。在實際開發中,經常出現一些莫名其妙的錯誤。另外,它的工做模式須要先在界面中打開文檔再進行各類操做,這種模式也不適應Web應用程序的須要。

使用Interop進行開發

微軟在.Net框架下,推出了Microsoft.Office.Interop.Word及其餘的互操做方式,可以更好地對Office文檔進行二次開發。

使用Interop進行二次開發,首先須要瞭解Word的對象結構,完整的Word對象結構圖以下(來自官方的VBA_Word幫助文件):

image

Application: 用來表現WORD應用程序,包含其它全部對象。他的成員常常應用於整個Word,能夠用它的屬性和方法控制Word環境。

Document對象: Document對象是Word編程的核心。當打開一個已有的文檔或建立一個新的文檔時,就建立了一個新的Document對象,新建立的Document將會被添加到Word Documents Collection。

Selection: Selection對象是描述當前選中的區域。若選擇區域爲空,則認爲是當前光標處。

Rang: 是Document的連續部分,根據起始字符的結束字符定議位置。

Bookmark: 相似於Rang,但Bookmark能夠有名字並在保存Document時Bookmark也被保存。

打開關閉和寫入操做

瞭解到Word的對象結構後,就能夠考慮怎樣操做了。

一、如何打開和關閉Application及Document對象。

打開和關閉操做比較簡單,實現代碼以下:

//打開 Microsoft.Office.Interop.Word.Application app = new Microsoft.Office.Interop.Word.Application(); Microsoft.Office.Interop.Word.Document doc = app.Documents.Open(ref fn, ref oMiss, ref oTrue, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oTrue,ref oMiss, ref oMiss, ref oMiss, ref oMiss); //關閉 doc.Close(ref oFalse, ref oMiss, ref oMiss); doc = null; app.Quit(ref oFalse, ref oMiss, ref oMiss); app = null;

二、寫入

因爲Word的結構複雜,要找到寫入的位置就比較複雜。在Interop操做中,能夠對Range的text進行操做,如:

doc.Range.Text="newtext";

批量替換文本

寫入報表,最經常使用的方法,是把模板作好,定義好特徵串,進行替換便可。天然而然咱們想到了經過Word的替換功能來完成。其主要代碼以下:

object s1 = OldString; object s2 = NewString; object rep = Microsoft.Office.Interop.Word.WdReplace.wdReplaceAll; doc.Content.Find.ClearFormatting(); doc.Content.Find.Execute( ref s1, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref oMiss, ref s2, ref rep, ref oMiss, ref oMiss, ref oMiss, ref oMiss);

用簡單的字符串測試,代碼工做正常,可是,用實際的數據測試發現沒法完成替換。追蹤後發現問題:替換的目標字符串不能過長,不然就會替換失敗,這個結果和Word軟件中替換的實際狀況一致。

遍歷段落替換文本

因爲批量查找替換操做不能完成替換成長文本目標,直觀的解決思路就是採用手動的方式,找到一個特徵串替換一個。可是在Interop中,因爲Find對象比較複雜,屢次嘗試沒有成功,比較實驗後,發現能夠採用遍歷方式進行替換。

因爲文檔下有多個段落,於是能夠對文檔中的每一個段落進行遍歷,若是在段落中找到特徵串,就把段落的文字提取出來,放在字符串中,對該字符串進行替換後再從新賦值給這個段落。這種方式須要段落的格式保持一致,這樣就能夠拼出完成段落來了。核心代碼以下:

for (int i = 0; i < doc.Paragraphs.Count; i++) { try //只能用嘗試的方法來進行替換 { if (doc.Paragraphs[i].Range.Text.IndexOf(OldStringArray[k]) >= 0) { doc.Paragraphs[i].Range.Text = doc.Paragraphs[i].Range.Text.Replace(OldStringArray[k], NewStringArray[k]); } } catch { } }

在實際操做中,發現遍歷操做很是容易出錯,緣由在於文檔對象存在着不少的段落,超過了能夠看見的段落數量,所以就必須加入一個錯誤捕獲功能以忽略一些意外的錯誤。

經過這種替換,能夠成功的完成整段的替換,效果以下圖:

image

image 

若是被替換的特徵串並非獨立的段落、或者位於表格中的話,上述代碼可否工做正常呢?以下圖所示,在段落中和表格中增長兩個特徵串進行替換,結果以下圖所示:

image

image

結果能夠看到,表格中雖然順利替換,但格式仍是受到影響。而段落中的文字雖然替換了,格式也被改成統一的格式了。

查找後逐個替換文本

對於一個追求完美的程序員來講,上述的bug是沒法容忍的,儘管它已經能夠湊合使用了,但要忽視的確作不到。根據前面的鋪墊,可能感受到問題的解決還得把Word的內部構造搞清楚。

在網上搜索了好久,都沒辦法找到關於查找和替換的更詳細的解決方法。通過一段時間的困惑以後,忽然發現,其實這些資料我本身自己就有。就是使用VBA開發Office的一系列資料,裏面關於Word的對象結構,有着遠比網上隻言片語靠譜的解答。學習的過程直接跳過,把幾條重要的結論給出來:

1)用Content的Find查找,只能進行批量的查找和替換,若是想找到第一個,停下來,操做,是不行的。

2)上述的「查找——操做」的思路,只能用Selection對象來完成,而Selection對象,Document的屬性中沒有、Content的屬性中也沒有。只有誰有?Application!

3)用Application的Selection的Find找到後,結果就在Selection.Text中,但要替換,只能對Selection.Range.Text進行賦值才行。

下面是實現代碼:

object oFindText=OldString; app.Selection.SetRange(0, 0); app.Selection.Find.Execute(ref oFindText,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oTrue,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oMiss,ref oMiss); if (app.Selection.Find.Found) { app.Selection.Range.Text=NewString; }

再次對上述第二種模板進行替換,結果以下:

image

image

這段來之不易的代碼,固然要保存在CommonCode(v2.0.6)中,之後要調用Word模板實現生成新文檔就很是簡單了,代碼以下:

CommonCode.WordUtil.ReplaceAndSave(Application.StartupPath + "\\temp2.doc", Application.StartupPath + "\\1.doc", new string[] { "[%單選%]", "[%分數%]", "[%數量%]" }, new string[]{@"
一、關於公開信息搜密,正確的是
A.在互聯網公開信息中搜密須要高深的技術
B.在互聯網中的主流網站中不存在祕密
C.只要經過關鍵詞搜索和按期跟蹤網站就可能找到祕密信息
D.公開信息搜密由於方法簡單,因此效果較差,不受重視","98","10"});

結論

對於替換Word模板內容生成Word文檔的需求,在.Net下能夠採用Interop的方式來實現。具體的實現手段,有批量替換、遍歷替換、單步查找並替換等方式。批量替換不能進行長文本的替換故不可用,遍歷段落替換不能對段內的關鍵詞進行保持格式的替換,也不完美。單步查找替換調用全局的查找功能(app.Selection.Find),並可以定位查找到的內容並進行操做,是完成需求的最佳方案。 

單步查找替換實現方案被整合至CommonCode.WordUtil.ReplaceAndSave函數中,能夠直接使用。

Demo下載

說明:引用CommonCode.dll和Microsoft.Office.Interop.Word.dll便可。

原來demo缺了log4net引用,添加

demo-2

相關文章
相關標籤/搜索