zoukankan      html  css  js  c++  java
  • 利用OpenXml生成Word2007文档

    一、OpenXml简介

    利用C#生成Word文档并非一定要利用OpenXml技术,至少可以使用微软提供的Office相关组件来编程,不过对于Office2007(确切的说是Word、Excel和PowerPoint2007)及以上版本,微软提供了这些信息组织的另外一种思路:OpenXml技术。

            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为例,其他与此思路一致)。

      二、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对你太关键了。

     三、编程操作OpenXml文件

    如果再不来点代码示例啥的,我想肯定有人觉得上当受骗了,看着这个标题点进来,结果发现啥代码都没有,看了一点似懂非懂的文字。Ok,接下来,我们就一起来创建一个名为Test.docx的Word文档。这个Word文档包含了Word里常用的一部分内容:段落、表格、样式、标题等。如果你需要生成更多的内容,文章的第四部分会介绍个方法。

        (1)准备环境:下载并安装OpenXMLSDKv2.msiOpenXMLSDKTool.msi,创建OpenXmlTest的ConsoleApplication。

    添加如下引用:

     C:Program FilesReference AssembliesMicrosoftFramework.NETFrameworkv4.0ProfileClientWindowsBase.dll
     C:Program FilesOpen XML SDKV2.0libDocumentFormat.OpenXml.dll  

        (2)创建文档:

    在Program.cs文件中编辑如下:

    [csharp] view plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    1. ①:先创建CreateParagraphStyle方法,为文档创建并添加样式  
    [html] view plaincopy
     
    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 plaincopy
     
    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 plaincopy
     
    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编程时,非常有用的资源。

        四、进行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)。当你可以按照预定的要求,把一个最简单的文档生成时,其他的任务就是逻辑处理,程序设计部分的东西了,接下来的任务就是坐等着上千页的文档自动生成出来。

        五、关于效率和面临的问题

        通过OpenXml技术去完成Word文档的生成、修改,在公司里我做了个数据库文档生成工具,内容就是对于数据库中每个表,以表名为一级标题,然后选出这个表的表结构、索引(如果有)、约束(如果有)、序列这四个表出来,名称作为二级标题,选出来的内容作为表格。数据库中所有的表部分处理完后,再选出函数和存储过程这些东西,每个函数作为一级标题,选出该函数或者存储过程相关的信息作为表格,插入Word文档中。最后整个文档超过1000页,从数据库里取数据开始,到最终文档生成为40S左右。当然,公司的电脑处理速度为2.93G,而且实际上我程序的代码没怎么优化,所以这个时间还是有减低的空间。

    一个问题就是,自己通过编程生成的word文档其实它的内容只能算是包括了最基本的组成部件,可能还有些内容不全,表现之一就是我生成的文档不能被WPS打开。解决办法就是用Word打开后,随便编辑点啥,然后保存下。Word为自动补齐所缺少的内容。

  • 相关阅读:
    爱福窝在线装修设计软件测评
    关于简书首页模式的思考和畅想
    这些O2O比你们更靠谱儿
    iOS动画——Layer Animations
    最大流, 最小割问题及算法实现
    浅谈iOS学习之路
    iOS架构师之路:慎用继承
    iOS架构师之路:控制器(View Controller)瘦身设计
    IOS中的编码规范
    关闭键盘导致tableView:didSelectRowAtIndexPath:失效解决办法
  • 原文地址:https://www.cnblogs.com/Alex80/p/4971185.html
Copyright © 2011-2022 走看看