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

一. 前言

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

  二. DocX的基本原理

     Word有一个开放的文件格式,叫做Office Open XML。Office 从2007版本开始用它。它的基本方法是将文本和格式存储成xml,把其他资源(图片等)存储成独立文件,并将其进行Zip压缩。这样的好处是它的体积远比03版本的office文件小得多,但也造成了一部分不兼容性。因此,别指望DocX支持Office2003.

     当理解Word实质上是XML以后,就不难了解如何操作Word了。理论上说,你不需要任何工具就能对它进行操作,当然复杂性极高。微软推出了Open XML SDK, 专门帮助.NET语言与Office实现互操作,这也成了COM组件外的新选择,但它的安装包有100M, 对很多部署来说,难度不小。

    这个组件DocX本身实际上是对XML操作的封装库,如果你有兴趣看它的源代码,基本核心就是XML的字符串组装和拼接,添加一个图表的本质就是在对应XML标签下面再增加一个图表的子文档。

    看到字符串拼接,有经验的同学肯定站出来会问性能如何。它没有使用StringBuilder,但本身性能不差,我生成100页的图文并茂的Word文档也是瞬间的事情,所以,没有关系。

    三. 自动文档生成的方法

     对程序开发来说,最常见的需求便是自动生成文档,完全从0生成图文并茂,排版合理的文档对程序员来说不现实,代码多得海了去了。所以很多人的做法是字符串替换,通过替换特定文字来操作,但这样显然是相当不专业的。

    比较合理的做法,是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;
            //}
        }

修改RelationID生成函数

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

///
        /// Insert a chart in document
        ///
        public void InsertChart(Chart chart,Paragraph paragraph=null)
        {
            // Create a new chart part uri.
            String chartPartUriPath = String.Empty;
            Int32 chartIndex = 1;
            do
            {
                chartPartUriPath = String.Format
                (
                    "/word/charts/chart{0}.xml",
                    chartIndex
                );
                chartIndex++;
            } while (package.PartExists(new Uri(chartPartUriPath, UriKind.Relative)));

            // Create chart part.
            PackagePart chartPackagePart = package.CreatePart(new Uri(chartPartUriPath, UriKind.Relative), "application/vnd.openxmlformats-officedocument.drawingml.chart+xml");

            // Create a new chart relationship
            String relID = GetNextFreeRelationshipID();
         PackageRelationship rel = mainPart.CreateRelationship(chartPackagePart.Uri, TargetMode.Internal, "http://schemas.openxmlformats.org/officeDocument/2006/relationships/chart", relID);

            // Save a chart info the chartPackagePart
            using (TextWriter tw = new StreamWriter(chartPackagePart.GetStream(FileMode.Create, FileAccess.Write)))
                chart.Xml.Save(tw);

            // Insert a new chart into a paragraph.

            Paragraph p = paragraph ?? this.InsertParagraph()
//如果指定了paragraph,则从这个段落插入
            XElement chartElement = new XElement(
                XName.Get("r", DocX.w.NamespaceName),
                new XElement(
                    XName.Get("drawing", DocX.w.NamespaceName),
                    new XElement(
                        XName.Get("inline", DocX.wp.NamespaceName),
                        new XElement(XName.Get("extent", DocX.wp.NamespaceName), new XAttribute("cx", "5486400"), new XAttribute("cy", "3200400")),
                        new XElement(XName.Get("effectExtent", DocX.wp.NamespaceName), new XAttribute("l", "0"), new XAttribute("t", "0"), new XAttribute("r", "19050"), new XAttribute("b", "19050")),
                        new XElement(XName.Get("docPr", DocX.wp.NamespaceName), new XAttribute("id", "1"), new XAttribute("name", "chart")),
                        new XElement(
                            XName.Get("graphic", DocX.a.NamespaceName),
                            new XElement(
                                XName.Get("graphicData", DocX.a.NamespaceName),
                                new XAttribute("uri", DocX.c.NamespaceName),
                                new XElement(
                                    XName.Get("chart", DocX.c.NamespaceName),
                                    new XAttribute(XName.Get("id", DocX.r.NamespaceName), relID)
                                )
                            )
                        )
                    )
               ));
            p.Xml.Add(chartElement);
        }

在指定位置插入图表

和源代码对比,很容易就能看出两者的区别。

我能添加更多的图表吗?当然可以。源代码只内置了三种图表: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. 域更新不正常

  如果这几个问题能解决,那确实是最好不过的了。除此之外,其实还有很多小问题,一方面期待作者解决,另外一方面如果项目需求紧急的话,索性我们自己先改了得了。代码还是很容易理解的。

  我尝试把它用在项目中,经过测试,发现基本稳定,大家可以尝试采纳。

  有任何问题,欢迎随时交流,如果您觉得对您有帮助,请点推荐,谢谢!

时间: 2024-10-20 12:56:33

开源Word读写组件DocX 的深入研究和问题总结的相关文章

开源Word读写组件DocX介绍与入门

来源:http://i.cnblogs.com/EditPosts.aspx?opt=1 读写Offic格式的文档,大家多少都有用到,可能方法也很多,组件有很多.这里不去讨论其他方法的优劣,只是向大家介绍一款开源的读写word文档的组件. 读写Excel有NPOI,读写Word,那看看DocX吧. .NET下的开源轻量级Word 2007/2010格式读写组件DocX,很小巧,能够满足大部分工作需求吧,最重要的是可以不用庞大的Office.   数据字典生成工具之旅(5):DocX组件读取与写入

详解一款开源免费的.NET文档操作组件DocX(.NET组件介绍之一)_实用技巧

在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息.由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量. 在.NET项目中如果用户提出了相关文档操作的需求,开发者较多的会使用到微软自行提供的插件,在一定程度上简化了开发人员的工作量,但是同时也给用户带来了一些困扰,例如需要安装庞大的office,在用户体验性就会降低很多,并且在国内,很多人都还是使用wps,这就导致一部分只安装了wps的使用者很是为难,在对Excel的操作方面,有一个NPO

.NET中开源文档操作组件DocX的介绍与使用_基础应用

前言 相信大家应该都有所体会,在目前的软件项目中,都会较多的使用到对文档的操作,用于记录和统计相关业务信息.由于系统自身提供了对文档的相关操作,所以在一定程度上极大的简化了软件使用者的工作量. 在.NET项目中如果用户提出了相关文档操作的需求,开发者较多的会使用到微软自行提供的插件,在一定程度上简化了开发人员的工作量,但是同时也给用户带来了一些困扰,例如需要安装庞大的office,在用户体验性就会降低很多,并且在国内,很多人都还是使用wps,这就导致一部分只安装了wps的使用者很是为难,在对Ex

.NET平台开源项目速览(1)SharpConfig配置文件读写组件

原文:.NET平台开源项目速览(1)SharpConfig配置文件读写组件 在.NET平台日常开发中,读取配置文件是一个很常见的需求.以前都是使用System.Configuration.ConfigurationSettings来操作,这个说实话,搞起来比较费劲.不知道大家有没有同感.所以更多时候我还是喜欢使用开源的东西,更加方便简洁,也稳定.省去自己的麻烦.今天就介绍一个非常精致的.NET平台开源的操作配置文件(cfg/ini)的组件SharpConfig.走过路过,千万不要错过!上周我在这

Word 2010组件中新增的“文档导航”功能

工作中我们常常需要处理一些比较长的文档,想要重新组织文档内容要用鼠标滚轮来回滚动,既麻烦又很容易出错.不过如果你已经用上了office 2010,就不会再被这个问题困扰了.使用word 2010组件中新增的"文档导航"功能,再长的文档你也能轻松掌控了. 在Word 2010中打开一篇较长的文档后,切换到"视图"选项卡,勾选"导航窗格". 开启导航窗格 导航功能开启后,在文档左侧会出现一个导航栏. 导航窗格 在导航栏的搜索框中输入要查找的关键字后你

利用Mono.Cecil动态修改程序集来破解商业组件(仅用于研究学习)

原文:利用Mono.Cecil动态修改程序集来破解商业组件(仅用于研究学习)      Mono.Cecil是一个强大的MSIL的注入工具,利用它可以实现动态创建程序集,也可以实现拦截器横向切入动态方法,甚至还可以修改已有的程序集,并且它支持多个运行时框架上例如:.net2.0/3.5/4.0,以及silverlight程序 官方地址:http://www.mono-project.com/Cecil      首先,我先假想有一个这样的商业组件,该组件满足了以下条件: 1. 该程序集的代码被混

关于Linux开源项目基础组件make编译流程

 关于Linux开源项目基础组件make编译流程 很多Linux开源项目都会用到编译出可执行文件的make,这个是有一套流程的. 首先,GNU构建系统:https://en.wikipedia.org/wiki/GNU_build_system 使用Autotool来编译和管理整个产品的生产流程. 就是下面这张图: 这里面需要先了解make和makefile make:https://en.wikipedia.org/wiki/Make_(software) makefile:https:/

PowerDNS v3.0 RC1发布 跨平台开源DNS服务组件

PowerDNS 是一个跨平台的开源DNS服务组件,PowerDNS同时有Win32和Linux/Unix的版本. PowerDNS在Win32下使用 Access的mdb文件记录DNS信息,而在Linux/Unix下则使用MySQL来记录DNS信息.无论是mdb亦或MySQL,备份是非常方便的 事情. PowerDNS 3.0 完全支持 DNSSEC,提供自动化签名.翻转(rollovers).和证书维护,其他方面还包括支持.TSIG.MyDNS-compact 后端.also-notify.

PDNS-Admin v1.2发布 跨平台的开源DNS服务组件

PowerDNS 是一个跨平台的开源DNS服务组件.PowerDNS Administrator 或者 PDNS-Admin ,是一个基于 Web 的 PowerDNS 管理程序.新版本增加对新的数据库的支持,修复了一些bug,还有新功能,修复了安全方面的问题. The biggest update for this release is the inclusion of MySQLi, pgSQL, and SQLite3 support. All of these provide more