黃聰:利用OpenXml生成Word2007文檔(轉)

原文:http://blog.csdn.net/francislaw/article/details/7568317html

 
 

    1、OpenXml簡介

利用C#生成Word文檔並不是必定要利用OpenXml技術,至少可使用微軟提供的Office相關組件來編程,不過對於Office2007(確切的說是Word、Excel和PowerPoint2007)及以上版本,微軟提供了這些信息組織的另一種思路:OpenXml技術。ide

        OpenXml是微軟office2007及以後版本里,對Office信息內容(Word、Excel和PowerPoint)的一種組織方式,當你建立一個Word2007文檔:XXX.docx後,它其實是一組符合必定規範的格式化xml文件,若是你把後綴名更改成XXX.zip,或者,直接用7-zip相似的解壓軟件解壓後,你會獲得一系列的xml文件。這些xml文件有用來描述類容的,有用來描述形式的,還有用來自我描述(描述XXX.docx這一組文檔自己的)。撇開其餘一切,這樣的一系列xml文件來實現一個Word文檔(Excel和PowerPoint都很類似)至少能夠實現兩點優勢:內容和表現形式的分離,文檔的自我描述。這無論對於信息內容、表現形式的管理,仍是對於文檔的擴展性,都提供了比較大的便利。Ok,優勢啥的,那是微軟的事,咱們仍是比較關注OpenXml對於咱們通常開發者有何便利,尤爲對於咱們經過編程去操做Word文檔(如下皆以Word爲例,其餘與此思路一致)。函數

  2、OpenXmlSDK的由來

其實,最容易想到的思路就是:既然Word文檔時一系列的格式化的xml文件,那我直接按照這些xml文件的規範,去經過C#寫xml文件,按照OpenXml的標準去生成節點,節點名、節點屬性、值、內容等啥的。先不說這些xml文件格式如何苛刻,內容有多繁瑣,咱們建立這些xml文檔有啥用呢?這樣不過是生成了一些xml文件,難道把這些文件的父文件夾後綴更改成.docx,就自動變成word文檔了嗎?工具

        固然不會。。這些xml文件並不是是孤立的。當用Office工具建立文檔時,它會自動生成相應的xml文件,並組織成一個包(即正如咱們所看到的一個.docx文件。)。對於咱們用編程來實現時,確定是先去生成某個東西A,對應於全部xml文件所組成的包,而後利用A,去生成A裏的各類內部部件,即對應於包裏的各個xml文件。如此以來,咱們所生成的xml文件纔會相互關聯起來,且最終確實能夠組合成一個Word文檔。學習

既然都想到先去建立一個A對應於文件包,建立A裏的內部部件去對應xml文件。對應、對應,何須要咱們本身去生成xml文件呢?畢竟咱們想要的是生成word文檔,生成word文檔裏的段落、文本、樣式、表格等等。因此,在咱們編程時,若是能夠直接像通常處理那樣,直接去生成咱們所建立的東西,好比一個段落P,而不用關心實際的xml文件表示這個P須要怎樣的結構,須要哪些屬性等等,甚至不用關係咱們要生成哪些xml文件,這些xml文件如何組織等等。對咱們編程而言,咱們只是經過某些C#類、方法,生成了一些文檔、一些段落、一些樣式,而這些東西如何轉變成OpenXml文件,轉換成怎麼樣的Xml文件,這些若是都能自動完成,咱們豈不是要輕鬆不少。字體

沒錯,OpenXmlSDK就是來完成這樣的工做:它爲咱們提供了一系列的API,經過這些API,咱們能夠直接去描述咱們想要生成的東西:文檔、段落、表格、樣式等等。這些API根據OpenXml的標準,去生成對應的xml文件,並組織對應的xml文件,最終生成以OpenXml的形式組織內容的Word文檔。優化

單純從開發引用類庫而言,OpenXMLSDKv2.msi這個3.8M的文件足矣,安裝它以後,把相應的dll文件添加到項目中便可。但若是你想更輕鬆的開發,好比,想看看一個Word文檔轉換爲怎樣的xml文件了,甚至想把這些OpenXml轉換爲對應的C#代碼,OpenXMLSDKTool.msi這個106M的工具就頗有必要了。反正如今網速這麼快,100多M算啥呢?況且,當你實際開發使用後,你會發現OpenXMLSDKTool.msi對你太關鍵了。spa

 3、編程操做OpenXml文件

若是再不來點代碼示例啥的,我想確定有人以爲上當受騙了,看着這個標題點進來,結果發現啥代碼都沒有,看了一點似懂非懂的文字。Ok,接下來,咱們就一塊兒來建立一個名爲Test.docx的Word文檔。這個Word文檔包含了Word裏經常使用的一部份內容:段落、表格、樣式、標題等。若是你須要生成更多的內容,文章的第四部分會介紹個方法。

    (1)準備環境:下載並安裝OpenXMLSDKv2.msiOpenXMLSDKTool.msi,建立OpenXmlTest的ConsoleApplication。

添加以下引用:

 C:\Program Files\Reference Assemblies\Microsoft\Framework\.NETFramework\v4.0\Profile\Client\WindowsBase.dll
 C:\Program Files\Open XML SDK\V2.0\lib\DocumentFormat.OpenXml.dll  

    (2)建立文檔:

在Program.cs文件中編輯以下:

 

[csharp]  view plain copy
 
  1. using System;  
  2. using System.Collections.Generic;  
  3. using System.Linq;  
  4. using System.Text;  
  5. <strong>using DocumentFormat.OpenXml;  
  6. using DocumentFormat.OpenXml.Wordprocessing;  
  7. using DocumentFormat.OpenXml.Packaging;</strong>  
  8.   
  9. namespace OpenXmlTest  
  10. {  
  11.     class Program  
  12.     {  
  13.         static void Main(string[] args)  
  14.         {  
  15.             // ①:建立WordprocessingDocument實例doc,對應於TEST.docx文件  
  16.             using (WordprocessingDocument doc=WordprocessingDocument.Create(@"D:\Test.docx",WordprocessingDocumentType.Document))  
  17.             {  
  18.                 // ②:爲doc添加MainDocumentPart部分  
  19.                 MainDocumentPart mainPart = doc.AddMainDocumentPart();  
  20.   
  21.                 // ③:爲mainPart添加Document,對應於Word裏的文檔內容部分  
  22.                 mainPart.Document = new Document();  
  23.   
  24.                 // ④:爲Document添加Body,以後全部於內容相關的均在此body中  
  25.                 Body body=mainPart.Document.AppendChild(new Body());  
  26.   
  27.                 // ⑤:添加段落P,P中包含一個文本「TEST」  
  28.                 Paragraph p=mainPart.Document.Body.AppendChild(new Paragraph());  
  29.                 p.AppendChild(new Run(new Text("TEST")));  
  30.             }  
  31.         }  
  32.     }  
  33. }  

    先總結下Word文檔中的關鍵元素:

 

WordprocessingML 元素

Open XML SDK 2.0 類

p(段落)

Paragraph

pPr(段落屬性)

ParagraphProperties

r

Run

t(文本)

Text


 

    (3)向文檔中添加表格

 

[html]  view plain copy
 
  1. // 新建Table  
  2. Table tb = new Table();  
  3.   
  4. // 新建行  
  5. TableRow row = new TableRow();  
  6.   
  7. // 新建、單元格  
  8. TableCell cel = new TableCell(new Paragraph(new Run(new Text("TableCell1"))));  
  9.   
  10. // 關聯行、單元格和表格  
  11. row.AppendChild(cel);  
  12. tb.AppendChild(row);  
  13.   
  14. // 把表格添加到文檔中  
  15. body.Append(tb);  
固然,這個Table實際沒有邊框,也沒有其餘樣式,僅有最基本的內容。邊框、樣式等都可經過Table對應的屬性來建立。

 

 

    (4)向文檔添加樣式。

接下來開始討論比較蛋疼的問題:樣式,一般咱們生成文檔只要關注於內容就好了,樣式、格式啥的,愛漂亮的人本身打開文檔本身經過Office去設置吧。(玩笑,樣式啥的確實蛋疼,但同時也很重要)尤爲對於生成格式化的文檔,一般都是上百甚至上千頁的內容,若是每一項都本身去設置格式,那作這樣文檔生成工具的初衷根本未達到。

首先要明白一個問題:樣式這些東西是OpenXml文檔自身所包含的信息,而不是Office工具好比Word2007所提供的。也就是說,當OpenXml文件自己包含有樣式相關的信息時,經過編程設置文本的字體、顏色、樣式啥的纔有意義。不要幻想着Office工具自己帶的有樣式,因此我只要在編程建立文檔時設置好對應的屬性,好比字體=「宋體」,樣式=「Head 1」就能達到預期,前提是你確保你的OpenXml文檔裏確實包含對"宋體"、「Head 1」這些樣式相關的定義。

最簡單的實現方式是:本身手動經過Word建立一個空白的Word文檔,此時它會自動包含包括經常使用樣式、格式、字體等的xml文件。而後,在編程控制內容時,設置好內容對應的屬性。固然,這種思路對於經常使用的樣式或者格式有用,但「經常使用」如何定義,我不肯定,惟一肯定的是對於手動建立的Word文檔裏所使用過的樣式、字體等定義,對應的OpenXml文檔必定包含有對應的定義。

第二中思路則比較蛋疼了:本身經過編程去建立本身須要的樣式,而後在內容輸出時,設置對應的屬性。沒辦法,若是你想要程序更加智能,只須要一個按鈕就能生成全部你須要的文檔,那你的代碼所作的工做就更多。

樣式千千萬萬,但經過編程的思路都很一致,不過是:建立樣式,向文檔添加所建立的樣式,在內容輸出部分使用所建立的樣式。結合到後面會講到的偷懶的方法,如下僅以Heading1,和Heading2爲例介紹,

 

[html]  view plain copy
 
  1. ①:先建立CreateParagraphStyle方法,爲文檔建立並添加樣式  
[html]  view plain copy
 
  1. // 爲文檔建立段落樣式  
  2.         static void CreateParagraphStyle(WordprocessingDocument doc)  
  3.         {  
  4.             // 進入文檔控制樣式部分  
  5.             StyleDefinitionsPart styleDefinitionsPart;  
  6.             styleDefinitionsPart = doc.MainDocumentPart.AddNewPart<StyleDefinitionsPart>();  
  7.             Styles root = new Styles();  
  8.             root.Save(styleDefinitionsPart);  
  9.   
  10.             Styles styles = styleDefinitionsPart.Styles;  
  11.             if (styles == null)  
  12.             {  
  13.                 styleDefinitionsPart.Styles = new Styles();  
  14.                 styleDefinitionsPart.Styles.Save();  
  15.             }  
  16.   
  17.             // 建立樣式Heading1  
  18.             Style style2 = new Style() { Type = StyleValues.Paragraph, StyleId = "1" };  
  19.             StyleName styleName2 = new StyleName() { Val = "heading 1" };  
  20.             BasedOn basedOn1 = new BasedOn() { Val = "a" };  
  21.             NextParagraphStyle nextParagraphStyle1 = new NextParagraphStyle() { Val = "a" };  
  22.             LinkedStyle linkedStyle1 = new LinkedStyle() { Val = "1Char" };  
  23.             UIPriority uIPriority1 = new UIPriority() { Val = 9 };  
  24.             PrimaryStyle primaryStyle2 = new PrimaryStyle();  
  25.             Rsid rsid2 = new Rsid() { Val = "00B74129" };  
  26.             StyleParagraphProperties styleParagraphProperties2 = new StyleParagraphProperties();  
  27.             KeepNext keepNext1 = new KeepNext();  
  28.             KeepLines keepLines1 = new KeepLines();  
  29.             SpacingBetweenLines spacingBetweenLines1 = new SpacingBetweenLines() { Before = "340", After = "330", Line = "578", LineRule = LineSpacingRuleValues.Auto };  
  30.             OutlineLevel outlineLevel1 = new OutlineLevel() { Val = 0 };  
  31.             styleParagraphProperties2.Append(keepNext1);  
  32.             styleParagraphProperties2.Append(keepLines1);  
  33.             styleParagraphProperties2.Append(spacingBetweenLines1);  
  34.             styleParagraphProperties2.Append(outlineLevel1);  
  35.             StyleRunProperties styleRunProperties1 = new StyleRunProperties();  
  36.             Bold bold1 = new Bold();  
  37.             BoldComplexScript boldComplexScript1 = new BoldComplexScript();  
  38.             Kern kern2 = new Kern() { Val = (UInt32)44U };  
  39.             FontSize fontSize2 = new FontSize() { Val = "44" };  
  40.             FontSizeComplexScript fontSizeComplexScript2 = new FontSizeComplexScript() { Val = "44" };  
  41.             styleRunProperties1.Append(bold1);  
  42.             styleRunProperties1.Append(boldComplexScript1);  
  43.             styleRunProperties1.Append(kern2);  
  44.             styleRunProperties1.Append(fontSize2);  
  45.             styleRunProperties1.Append(fontSizeComplexScript2);  
  46.             style2.Append(styleName2);  
  47.             style2.Append(basedOn1);  
  48.             style2.Append(nextParagraphStyle1);  
  49.             style2.Append(linkedStyle1);  
  50.             style2.Append(uIPriority1);  
  51.             style2.Append(primaryStyle2);  
  52.             style2.Append(rsid2);  
  53.             style2.Append(styleParagraphProperties2);  
  54.             style2.Append(styleRunProperties1);  
  55.   
  56.             // 建立樣式heading2  
  57.             Style style3 = new Style() { Type = StyleValues.Paragraph, StyleId = "2" };  
  58.             StyleName styleName3 = new StyleName() { Val = "heading 2" };  
  59.             BasedOn basedOn2 = new BasedOn() { Val = "a" };  
  60.             NextParagraphStyle nextParagraphStyle2 = new NextParagraphStyle() { Val = "a" };  
  61.             LinkedStyle linkedStyle2 = new LinkedStyle() { Val = "2Char" };  
  62.             UIPriority uIPriority2 = new UIPriority() { Val = 9 };  
  63.             UnhideWhenUsed unhideWhenUsed1 = new UnhideWhenUsed();  
  64.             PrimaryStyle primaryStyle3 = new PrimaryStyle();  
  65.             Rsid rsid3 = new Rsid() { Val = "00B74129" };  
  66.   
  67.             StyleParagraphProperties styleParagraphProperties3 = new StyleParagraphProperties();  
  68.             KeepNext keepNext2 = new KeepNext();  
  69.             KeepLines keepLines2 = new KeepLines();  
  70.             SpacingBetweenLines spacingBetweenLines2 = new SpacingBetweenLines() { Before = "260", After = "260", Line = "416", LineRule = LineSpacingRuleValues.Auto };  
  71.             OutlineLevel outlineLevel2 = new OutlineLevel() { Val = 1 };  
  72.             styleParagraphProperties3.Append(keepNext2);  
  73.             styleParagraphProperties3.Append(keepLines2);  
  74.             styleParagraphProperties3.Append(spacingBetweenLines2);  
  75.             styleParagraphProperties3.Append(outlineLevel2);  
  76.             StyleRunProperties styleRunProperties2 = new StyleRunProperties();  
  77.             RunFonts runFonts2 = new RunFonts() { AsciiTheme = ThemeFontValues.MajorHighAnsi, HighAnsiTheme = ThemeFontValues.MajorHighAnsi, EastAsiaTheme = ThemeFontValues.MajorEastAsia, ComplexScriptTheme = ThemeFontValues.MajorBidi };  
  78.             Bold bold2 = new Bold();  
  79.             BoldComplexScript boldComplexScript2 = new BoldComplexScript();  
  80.             FontSize fontSize3 = new FontSize() { Val = "32" };  
  81.             FontSizeComplexScript fontSizeComplexScript3 = new FontSizeComplexScript() { Val = "32" };  
  82.             styleRunProperties2.Append(runFonts2);  
  83.             styleRunProperties2.Append(bold2);  
  84.             styleRunProperties2.Append(boldComplexScript2);  
  85.             styleRunProperties2.Append(fontSize3);  
  86.             styleRunProperties2.Append(fontSizeComplexScript3);  
  87.             style3.Append(styleName3);  
  88.             style3.Append(basedOn2);  
  89.             style3.Append(nextParagraphStyle2);  
  90.             style3.Append(linkedStyle2);  
  91.             style3.Append(uIPriority2);  
  92.             style3.Append(unhideWhenUsed1);  
  93.             style3.Append(primaryStyle3);  
  94.             style3.Append(rsid3);  
  95.             style3.Append(styleParagraphProperties3);  
  96.             style3.Append(styleRunProperties2);  
  97.   
  98.             // 把樣式添加入文檔中  
  99.             styles.Append(style2);  
  100.             styles.Append(style3);  
  101.         }  

 

    ②建立ApplyStyleToParagraph方法,爲段落設定樣式:

 

[html]  view plain copy
 
  1. // 對段落應用格式  
  2.         static void ApplyStyleToParagraph(Paragraph paragraph, string styleN)  
  3.         {  
  4.             // 進入該段落的ParagraphProperties部分,若是沒有,建立新的。  
  5.             if (paragraph.Elements<ParagraphProperties>().Count() == 0)  
  6.             {  
  7.                 paragraph.PrependChild<ParagraphProperties>(new ParagraphProperties());  
  8.             }  
  9.   
  10.             ParagraphProperties pPr = paragraph.ParagraphProperties;  
  11.   
  12.             // 設置StyleId  
  13.             if (pPr.ParagraphStyleId == null)  
  14.                 pPr.ParagraphStyleId = new ParagraphStyleId();  
  15.             pPr.ParagraphStyleId.Val = styleN;  
  16.         }  

    ③:Main方法中輸出文檔:

 

 

[html]  view plain copy
 
  1. static void Main(string[] args)  
  2.         {  
  3.             // ①:建立WordprocessingDocument實例doc,對應於TEST.docx文件  
  4.             using (WordprocessingDocument doc=WordprocessingDocument.Create(@"D:\Test.docx",WordprocessingDocumentType.Document))  
  5.             {  
  6.                 // ②:爲doc添加MainDocumentPart部分  
  7.                 MainDocumentPart mainPart = doc.AddMainDocumentPart();  
  8.   
  9.                 // ③:爲mainPart添加Document,對應於Word裏的文檔內容部分  
  10.                 mainPart.Document = new Document();  
  11.   
  12.                 // ④:爲Document添加Body,以後全部於內容相關的均在此body中  
  13.                 Body body=mainPart.Document.AppendChild(new Body());  
  14.   
  15.                 <strong>// 爲文檔添加樣式  
  16.                 CreateParagraphStyle(doc);  
  17.   
  18.                 // 建立段落Heading1  
  19.                 Paragraph p1=mainPart.Document.Body.AppendChild(new Paragraph(new Run(new Text("標題1"))));  
  20.   
  21.                 // 爲段落應用樣式  
  22.                 ApplyStyleToParagraph(p1,"1");  
  23.   
  24.                 // 建立段落Heading2  
  25.                 Paragraph p2 = mainPart.Document.Body.AppendChild(new Paragraph(new Run(new Text("標題2"))));  
  26.   
  27.                 // 爲段落應用樣式  
  28.                 ApplyStyleToParagraph(p2, "2");</strong>  
  29.                   
  30.             }  
  31.         }  

 

總結下思路:經過編程的方法操做OpenXml文檔的基本思路爲:從WordprocessingDocument這一級開始,一級一級找到對應的元素,設置對應元素的內容和屬性。涉及到格式相關時,則是先建立好格式,再應用格式。最終生成整個完整的文檔。

內容部分沒問題,原本就該這樣,但對於樣式部分,這麼長的代碼怎麼寫出來?並且對應於Word文檔裏的每個元素,在OpenXml類裏都有對應的屬性、元素去設置,但究竟每一個Word元素對應哪些OpenXmlSDK裏的類、屬性,究竟該如何去組織C#代碼,最終確保能按咱們所想,生成Word文檔。想要熟悉OpenXmlSDK全部的API再去完成工做是不可能,接下來就討論些在進行OpenXml編程時,很是有用的資源。

 

    4、進行OpenXml編程時不可或缺的資源

    (1)MSDN,這個東西,對於OpenXml編程新手而言絕對是快速進入工做任務最好的選擇:內容比較全,組織形式比較易懂,用來學習知識,快速開始工做最爲理想。但缺點是有些內容不夠準確,也不能算不許確,在它特定的環境下是沒問題的,可是問題就在於它沒有明確提出這些環境限制,對咱們新手而言,悲劇就發生了:把它的某些方法、思路應用到一個本身想固然的環境裏,結果就是運行的結果出乎意料,更悲劇的就是來了情緒,爲嘛它能成功我不行,一遍又一遍的重複錯誤的代碼,運行獲得錯誤的結果。。。而後暫時崩潰。

    (2)OpenXMLSDKTool.msi,這個工具,文章前面有簡單的介紹。利用這個工具,其實第三部分寫的那麼多的代碼都不用怎麼理解,只要本身對OpenXml的基本結構有些瞭解後,須要工具生成怎樣的格式、內容,就按照這些格式標準經過Word手動生成一個文檔,而後用OpenXMLSDKTool.msi這個工具去解析這個文檔:重點是個文檔包含了哪些xml文件,明白主要的xml文件是用來幹嗎最好,不明白,就算了,不太影響。而後把每一個xml利用工具反射出對應的C#代碼,這樣能夠很方便的把已生成好的文檔映射爲OpenXmlSdk裏的類和屬性,以及C#代碼的思路就顯而易見了,熟悉好這些思路後,接下來不過就是模仿(其實不是模仿,由於思路本身已經理解了,有了本身的思路),就是Copy下工具生成的代碼裏比較關鍵、但又比較機械的部分,修改下爲我所用。

其實,熟悉了OpenXml的基本結構,利用OpenXMLSDKTool.msi工具的幫助,基本上能夠不用太多知識準備就能夠生成各類Word文檔(Excel、PoperPoint與此徹底相似,由於他們的組織形式也是利用OpenXml)。當你能夠按照預約的要求,把一個最簡單的文檔生成時,其餘的任務就是邏輯處理,程序設計部分的東西了,接下來的任務就是坐等着上千頁的文檔自動生成出來。

 

    5、關於效率和麪臨的問題

    經過OpenXml技術去完成Word文檔的生成、修改,在公司裏我作了個數據庫文檔生成工具,內容就是對於數據庫中每一個表,以表名爲一級標題,而後選出這個表的表結構、索引(若是有)、約束(若是有)、序列這四個表出來,名稱做爲二級標題,選出來的內容做爲表格。數據庫中全部的表部分處理完後,再選出函數和存儲過程這些東西,每一個函數做爲一級標題,選出該函數或者存儲過程相關的信息做爲表格,插入Word文檔中。最後整個文檔超過1000頁,從數據庫裏取數據開始,到最終文檔生成爲40S左右。固然,公司的電腦處理速度爲2.93G,並且實際上我程序的代碼沒怎麼優化,因此這個時間仍是有減低的空間。

一個問題就是,本身經過編程生成的word文檔其實它的內容只能算是包括了最基本的組成部件,可能還有些內容不全,表現之一就是我生成的文檔不能被WPS打開。解決辦法就是用Word打開後,隨便編輯點啥,而後保存下。Word爲自動補齊所缺乏的內容。

相關文章
相關標籤/搜索