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


閱讀目錄

 

 

在各類應用系統開發中,和Word相關的應用可謂相當廣泛。如各類MIS系統、各種和實際業務結合緊密的系統、需要制式報表的系統等,都需要對Word進行操作,典型的應用包括:

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

2、Word的二次開發。通過Word自帶的宏,利用VBA(Visual Basic Appplication)進行開發,實現各種復雜的自動化功能。

3、前台不顯示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來說,開發的難度都要大很多,原因來自以下方面:

1、Word 的對象結構復雜。由於Wrod有着久遠的歷史,這既是它的優勢也是它的包袱,它必須要保持足夠的兼容性,因此DOC文檔結構也就變得非常的復雜了。在Word中,有着復雜的對象結構,如Application、Document、BookMarks、Range、Selection、Paragraph等,它們之間既有層級關系,還有嵌套關系,有時為了一個小小的功能,卻無法找到操作的對象。

clip_image001

Word 的對象結構

2、Word功能復雜。作為微軟的拳頭產品,多年以來,Word的功能越來越強大。盡管大多數的功能對於二次開發來說是完全用不到的,但還得去了解和學習,這就需要付出額外的代價。以Find為例,其參數竟然高達15個,如下所示:

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

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

3、版本問題。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的對象結構后,就可以考慮怎樣操作了。

1、如何打開和關閉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;

2、寫入

由於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[]{@"
1、關於公開信息搜密,正確的是
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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM