前兩天看到了asxinyu大神的【原創】開源Word讀寫組件DocX介紹與入門,正好我也有相似的自動生成word文檔得需求,因而便仔細的研究了這個DocX。 我也把它融入到個人項目當中並進行了實踐。工具果真牛叉,但也有一些問題,後邊一併列出來。html
Word有一個開放的文件格式,叫作Office Open XML。Office 從2007版本開始用它。它的基本方法是將文本和格式存儲成xml,把其餘資源(圖片等)存儲成獨立文件,並將其進行Zip壓縮。這樣的好處是它的體積遠比03版本的office文件小得多,但也形成了一部分不兼容性。所以,別期望DocX支持Office2003.git
當理解Word實質上是XML之後,就不難了解如何操做Word了。理論上說,你不須要任何工具就能對它進行操做,固然複雜性極高。微軟推出了Open XML SDK, 專門幫助.NET語言與Office實現互操做,這也成了COM組件外的新選擇,但它的安裝包有100M, 對不少部署來講,難度不小。程序員
這個組件DocX自己其實是對XML操做的封裝庫,若是你有興趣看它的源代碼,基本核心就是XML的字符串組裝和拼接,添加一個圖表的本質就是在對應XML標籤下面再增長一個圖表的子文檔。數據庫
看到字符串拼接,有經驗的同窗確定站出來會問性能如何。它沒有使用StringBuilder,但自己性能不差,我生成100頁的圖文並茂的Word文檔也是瞬間的事情,因此,沒有關係。app
對程序開發來講,最多見的需求即是自動生成文檔,徹底從0生成圖文並茂,排版合理的文檔對程序員來講不現實,代碼多得海了去了。因此不少人的作法是字符串替換,經過替換特定文字來操做,但這樣顯然是至關不專業的。ide
比較合理的作法,是Office裏面的「域」。域的本質,對程序員來講就是表達式,這個變量能夠是文檔字數,文檔頁數(這是Office裏面自帶的屬性)。域至關牛逼,甚至能夠鏈接到一個數據庫,一個按鈕,乃至一篇網頁上去,真心無所不能。函數
一樣你也能夠本身添加屬性,也就是所謂「自定義屬性」。工具
如何查看文檔中的全部自定義域呢?性能
在Word最上面的「插入」卷展欄下選擇文檔部件->域,以下圖所示,並在域控制框中左側選擇DocProperty,便可看到全部的屬性:測試
怎麼在文檔中插入一個自定義域呢?一種作法是,隨便在上圖中選擇一個域(好比Author),點擊肯定,就會在插入的位置生成一個域。 而後點擊右鍵,選擇‘切換域代碼’,便可改變裏面的域定義:
修改Author,變成你想要的屬性,就能夠了,把這個東西拷到別的地方,再修改下域代碼,就是一個新的域定義了。這些域定義,能夠被咱們用程序操做來替換。
因爲域實質上是表達式,因此涉及一個計算過程。能夠選擇打印時自動更新,或者Ctrl+A全選,而後F9,就可所有更新全部域表達式。
至於如何在程序中操做域,【原創】開源Word讀寫組件DocX介紹與入門已經介紹的很清楚了,就是變量賦值,你能夠在表格中添加域,而後就動態填寫了表格。因此就不介紹了。
可是,在實際開發中,有個致命的問題: 當你經過模板,爲自定義屬性賦值,生成新文檔後,打開新文檔這些域並無自動更新,這是很是麻煩的,由於客戶可不想打開文檔後發現那些核心數據都是奇怪的東西,讓他們去摁F9自動更新域更是不可能。但要命的是,有些域被更新了,有些域沒有更新,從域定義上來看,沒有任何區別,這究竟是怎麼回事? 若是有大神解決了這個問題,請不吝賜教!
說到這裏,有一個良好排版的模板,加上自動替換的功能,文檔生成應該差很少了吧?不,還要插入圖表和圖片,這些用域暫時還很差解決。
插入圖片的問題,關鍵是插入位置,你須要找到要插入的位置所在的段落(Paragraph).個人作法是,用Linq查詢,經過定位關鍵字的作法找到段落,而後插入之便可。雖然粗糙,但還能用。插入表格相似。
但,怎麼插入圖表?所謂圖表,就是柱狀圖餅狀圖等等的東西?雖然官方示例裏有生成圖表的功能,但我用Word2013怎麼都打不開: 「該文檔有問題」。百思不得其解,花了半天才在別人的2010上打開,大喊坑爹(所以,DocX對Office2013的兼容性不夠!)
那好吧,咱們用Word2010或者07總能夠了吧?但目前版本的源代碼,只能往文檔的最後添加圖表,由於只有一個這樣的函數:
這不是坑爹呢麼?另外有時候插入圖表或圖片會出錯,顯示XML錯誤,創建鏈接的ID重複! 更是坑爹。
在這個地方會拋異常。
通過分析,是上面那個生成RelationshipID的函數出錯了, 後來,索性改了這個函數的方法,直接從GUID生成,這樣就不會錯了,代碼以下。
private string GetNextFreeRelationshipID() { String guid = String.Empty; do { guid = Guid.NewGuid().ToString(); } while (Char.IsDigit(guid[0])); return guid; //string id = //( // from r in mainPart.GetRelationships() // select r.Id //).Max(); //// The convension for ids is rid01, rid02, etc //string newId = id.Replace("rId", ""); //int result; //if (int.TryParse(newId, out result)) // return ("rId" + (result + 1)); //else //{ // String guid = String.Empty; // do // { // guid = Guid.NewGuid().ToString(); // } while (Char.IsDigit(guid[0])); // return guid; //} }
至於只能在文檔最後添加圖表的問題,我作了如下的代碼修改:
1 /// 2 /// Insert a chart in document 3 /// 4 public void InsertChart(Chart chart,Paragraph paragraph=null) 5 { 6 // Create a new chart part uri. 7 String chartPartUriPath = String.Empty; 8 Int32 chartIndex = 1; 9 do 10 { 11 chartPartUriPath = String.Format 12 ( 13 "/word/charts/chart{0}.xml", 14 chartIndex 15 ); 16 chartIndex++; 17 } while (package.PartExists(new Uri(chartPartUriPath, UriKind.Relative))); 18 19 // Create chart part. 20 PackagePart chartPackagePart = package.CreatePart(new Uri(chartPartUriPath, UriKind.Relative), "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"); 21 22 // Create a new chart relationship 23 String relID = GetNextFreeRelationshipID(); 24 PackageRelationship rel = mainPart.CreateRelationship(chartPackagePart.Uri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", relID); 25 26 // Save a chart info the chartPackagePart 27 using (TextWriter tw = new StreamWriter(chartPackagePart.GetStream(FileMode.Create, FileAccess.Write))) 28 chart.Xml.Save(tw); 29 30 // Insert a new chart into a paragraph. 31 32 Paragraph p = paragraph ?? this.InsertParagraph() 33 //若是指定了paragraph,則從這個段落插入 34 XElement chartElement = new XElement( 35 XName.Get("r", DocX.w.NamespaceName), 36 new XElement( 37 XName.Get("drawing", DocX.w.NamespaceName), 38 new XElement( 39 XName.Get("inline", DocX.wp.NamespaceName), 40 new XElement(XName.Get("extent", DocX.wp.NamespaceName), new XAttribute("cx", "5486400"), new XAttribute("cy", "3200400")), 41 new XElement(XName.Get("effectExtent", DocX.wp.NamespaceName), new XAttribute("l", "0"), new XAttribute("t", "0"), new XAttribute("r", "19050"), new XAttribute("b", "19050")), 42 new XElement(XName.Get("docPr", DocX.wp.NamespaceName), new XAttribute("id", "1"), new XAttribute("name", "chart")), 43 new XElement( 44 XName.Get("graphic", DocX.a.NamespaceName), 45 new XElement( 46 XName.Get("graphicData", DocX.a.NamespaceName), 47 new XAttribute("uri", DocX.c.NamespaceName), 48 new XElement( 49 XName.Get("chart", DocX.c.NamespaceName), 50 new XAttribute(XName.Get("id", DocX.r.NamespaceName), relID) 51 ) 52 ) 53 ) 54 ) 55 )); 56 p.Xml.Add(chartElement); 57 }
和源代碼對比,很容易就能看出二者的區別。
我能添加更多的圖表嗎?固然能夠。源代碼只內置了三種圖表:Pie, Line和Bar,不能知足要求,既然充分理解了它的原理,不妨咱們擴展它吧:
public class PieChart : Chart { #region Properties public override Boolean IsAxisExist { get { return false; } } public override Int16 MaxSeriesCount { get { return 1; } } #endregion #region Methods protected override XElement CreateChartXml() { return XElement.Parse(@"<c:pieChart xmlns:c=""http://schemas.openxmlformats.org/drawingml/2006/chart""> </c:pieChart>"); } #endregion }
注意看上面PieChart的定義,只要修改CreateChartXml函數,修改pieChart變成你想要的圖表就能夠了,經過一個工廠方法指定枚舉類型生成想要的圖表。至於Word支持什麼類型的圖表,在微軟的技術文章這裏能夠看到更詳細的類型。我又建立了四五個新類型。知足了個人須要。
用了這個組件,感覺良多,這哥們和我同樣的在校學生,,但已經作了這樣的開源項目,下載量超過18000+。 雖然理論不必定多牛逼,但確實知足廣大人民羣衆須要了,400KB的dll文件,直接解決了.NET平臺word生成這一剛性需求。 但做者確實下了功夫,大量的XML轉換和分析,純粹的體力活啊!
1. 對Office2013的支持不夠
2. API遠沒達到完善,例如沒法良好的操做圖表類型,只能使用默認值。
3. 代碼欠重構,可得到更好的程序風格和性能的提高。
4. 域更新不正常
若是這幾個問題能解決,那確實是最好不過的了。除此以外,其實還有不少小問題,一方面期待做者解決,另一方面若是項目需求緊急的話,索性咱們本身先改了得了。代碼仍是很容易理解的。
我嘗試把它用在項目中,通過測試,發現基本穩定,你們能夠嘗試採納。
有任何問題,歡迎隨時交流,若是您以爲對您有幫助,請點推薦,謝謝!