开源Word读写组件DocX 的深刻研究和问题总结

 

一. 前言

     前两天看到了asxinyu大神的【原创】开源Word读写组件DocX介绍与入门,正好我也有相似的自动生成word文档得需求,因而便仔细的研究了这个DocX。 我也把它融入到个人项目当中并进行了实践。工具果真牛叉,但也有一些问题,后边一并列出来。html

  二. DocX的基本原理

     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,便可看到全部的属性:测试

imageimage

  怎么在文档中插入一个自定义域呢?一种作法是,随便在上图中选择一个域(好比Author),点击肯定,就会在插入的位置生成一个域。 而后点击右键,选择‘切换域代码’,便可改变里面的域定义:

image

image

  修改Author,变成你想要的属性,就能够了,把这个东西拷到别的地方,再修改下域代码,就是一个新的域定义了。这些域定义,能够被咱们用程序操做来替换。

  因为域实质上是表达式,因此涉及一个计算过程。能够选择打印时自动更新,或者Ctrl+A全选,而后F9,就可所有更新全部域表达式。

   至于如何在程序中操做域,【原创】开源Word读写组件DocX介绍与入门已经介绍的很清楚了,就是变量赋值,你能够在表格中添加域,而后就动态填写了表格。因此就不介绍了。

    可是,在实际开发中,有个致命的问题: 当你经过模板,为自定义属性赋值,生成新文档后,打开新文档这些域并无自动更新,这是很是麻烦的,由于客户可不想打开文档后发现那些核心数据都是奇怪的东西,让他们去摁F9自动更新域更是不可能。但要命的是,有些域被更新了,有些域没有更新,从域定义上来看,没有任何区别,这究竟是怎么回事? 若是有大神解决了这个问题,请不吝赐教!

 

四. 插入图表的困扰 

   说到这里,有一个良好排版的模板,加上自动替换的功能,文档生成应该差很少了吧?不,还要插入图表和图片,这些用域暂时还很差解决。

   插入图片的问题,关键是插入位置,你须要找到要插入的位置所在的段落(Paragraph).个人作法是,用Linq查询,经过定位关键字的作法找到段落,而后插入之便可。虽然粗糙,但还能用。插入表格相似。

   但,怎么插入图表?所谓图表,就是柱状图饼状图等等的东西?虽然官方示例里有生成图表的功能,但我用Word2013怎么都打不开: “该文档有问题”。百思不得其解,花了半天才在别人的2010上打开,大喊坑爹(所以,DocX对Office2013的兼容性不够!)

   那好吧,咱们用Word2010或者07总能够了吧?但目前版本的源代码,只能往文档的最后添加图表,由于只有一个这样的函数:

image

这不是坑爹呢么?另外有时候插入图表或图片会出错,显示XML错误,创建链接的ID重复! 更是坑爹。

   image

在这个地方会抛异常。

通过分析,是上面那个生成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;
            //}
        }
修改RelationID生成函数

 

 至于只能在文档最后添加图表的问题,我作了如下的代码修改:

 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. 域更新不正常

  若是这几个问题能解决,那确实是最好不过的了。除此以外,其实还有不少小问题,一方面期待做者解决,另一方面若是项目需求紧急的话,索性咱们本身先改了得了。代码仍是很容易理解的。

  我尝试把它用在项目中,通过测试,发现基本稳定,你们能够尝试采纳。

  有任何问题,欢迎随时交流,若是您以为对您有帮助,请点推荐,谢谢!

相关文章
相关标签/搜索