开源Word读写组件DocX的深入研究和问题总结
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
开源Word读写组件DocX的深⼊研究和问题总结
⼀. 前⾔
前两天看到了⼤神的,正好我也有类似的⾃动⽣成word⽂档得需求,于是便仔细的研究了这个DocX。
我也把它融⼊到我的项⽬当中并进⾏了实践。
⼯具果然⽜叉,但也有⼀些问题,后边⼀并列出来。
⼆. DocX的基本原理
Word有⼀个开放的⽂件格式,叫做。
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,就可全部更新所有域表达式。
⾄于如何在程序中操作域,已经介绍的很清楚了,就是变量赋值,你可以在表格中添加域,然后就动态填写了表格。
所以就不介绍了。
但是,在实际开发中,有个致命的问题:当你通过模板,为⾃定义属性赋值,⽣成新⽂档后,打开新⽂档这些域并没有⾃动更新,这是⾮常⿇烦的,因为客户可不想打开⽂档后发现那些核⼼数据都是奇怪的东西,让他们去摁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;
//(
// 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///
4public void InsertChart(Chart chart,Paragraph paragraph=null)
5 {
6// Create a new chart part uri.
7 String chartPartUriPath = String.Empty;
8 Int32 chartIndex = 1;
9do
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, "/officeDocument/2006/relationships/chart", relID); 25
26// Save a chart info the chartPackagePart
27using (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", spaceName),
36new XElement(
37 XName.Get("drawing", spaceName),
38new XElement(
39 XName.Get("inline", spaceName),
40new XElement(XName.Get("extent", spaceName), new XAttribute("cx", "5486400"), new XAttribute("cy", "3200400")),
41new XElement(XName.Get("effectExtent", spaceName), new XAttribute("l", "0"), new XAttribute("t", "0"), new XAttribute("r", "19050"), new XAttribute("b", "19050")), 42new XElement(XName.Get("docPr", spaceName), new XAttribute("id", "1"), new XAttribute("name", "chart")),
43new XElement(
44 XName.Get("graphic", spaceName),
45new XElement(
46 XName.Get("graphicData", spaceName),
47new XAttribute("uri", spaceName),
48new XElement(
49 XName.Get("chart", spaceName),
50new XAttribute(XName.Get("id", spaceName), relID)
51 )
52 )
53 )
54 )
55 ));
56 p.Xml.Add(chartElement);
57 }
在指定位置插⼊图表
和源代码对⽐,很容易就能看出两者的区别。
我能添加更多的图表吗?当然可以。
源代码只内置了三种图表:Pie, Line和Bar,不能满⾜要求,既然充分理解了它的原理,不妨我们扩展它吧:
public class PieChart : Chart
{
public override Boolean IsAxisExist
{
get
{
return false;
}
}
public override Int16 MaxSeriesCount
{
get
{
return1;
}
}
#endregion
#region Methods
protected override XElement CreateChartXml()
{
return XElement.Parse(@"<c:pieChart xmlns:c=""/drawingml/2006/chart"">
</c:pieChart>");
}
#endregion
}
注意看上⾯PieChart的定义,只要修改CreateChartXml函数,修改pieChart变成你想要的图表就可以了,通过⼀个⼯⼚⽅法指定枚举类型⽣成想要的图表。
⾄于Word⽀持什么类型的图表,在微软的技术⽂章可以看到更详细的类型。
我⼜创建了四五个新类型。
满⾜了我的需要。
五. 总结
⽤了这个组件,感受良多,这哥们和我⼀样的在校学⽣,,但已经做了这样的开源项⽬,下载量超过18000+。
虽然理论不⼀定多⽜逼,但确实满⾜⼴⼤⼈民群众需要了,400KB的dll⽂件,直接解决了.NET平台word⽣成这⼀刚性需求。
但作者确实下了功夫,⼤量的XML转换和分析,纯粹的体⼒活啊!
1. 对Office2013的⽀持不够
2. API远没达到完善,例如⽆法良好的操作图表类型,只能使⽤默认值。
3. 代码⽋重构,可获得更好的程序风格和性能的提升。
4. 域更新不正常
如果这⼏个问题能解决,那确实是最好不过的了。
除此之外,其实还有很多⼩问题,⼀⽅⾯期待作者解决,另外⼀⽅⾯如果项⽬需求紧急的话,索性我们⾃⼰先改了得了。
代码还是很容易理解的。
我尝试把它⽤在项⽬中,经过测试,发现基本稳定,⼤家可以尝试采纳。
有任何问题,欢迎随时交流,如果您觉得对您有帮助,请点推荐,谢谢!。