.net 2.0下的OOXML神器:NPOI.OpenXml4Net

作者:Tony Qu

NPOI官方博客:http://tonyqus.sinaapp.com | 官方QQ群:189925337

 

可能很多人已经习惯了使用.Net 3.0下的System.IO.Packaging(WindowsBase.dll)来操作Office 2007/2010的文件格式,以至于大家都默许了.net 2.0下无法操作OOXML文件的观点,尽管也有人使用第三方zip类库来操作OOXML文件,但是遇到关系维护之类的问题,就开始纠结了,你必须自己去不断地维护.rels文件(OOXML中用于维护文件内关系的文件,这里不是后缀名,这个文件就叫这个名字。),而且文件的内容越复杂,关系维护就越痛苦。尽管微软出了OpenXml SDK 2.0,但是很遗憾,这套库也是基于.net 3.0的。当然,我倒不是.net 3.0的坚决反对者,只是出于部署方面的考虑,要知道目前基于.net 2.0的应用还是占据相当一部分份额的,尽管.net 3.0/3.5出来也3年了,但是相对于.net 2.0而言,只能算刚刚起步,这也是NPOI始终坚持.net 2.0版本为主线版本的原因。

有人可能要说,.net 3.0/3.5不也是基于.net 2.0的吗?话是这么说,但是部署起来,还是要单独部署.net 3.0包,不是吗?相当于额外增加一套库,就拿我目前的公司来说,我们仍然在用vs2005开发,服务器上也只部署了.net 2.0 framework。

image

poi中有一个库叫OpenXml4j,由Julien Chable于2008年捐赠给POI项目,主要负责OOXML基础操作,如创建、读取、修改、关系维护等。最近NPOI团队完成了OpenXml4j的移植工作,于是就有了NPOI.OpenXml4Net,该组件将包括在NPOI下一个版本中,目前你可以通过googlecode的svn获得完整代码,自行在本地编译。OpenXml4Net使用SharpZip作为底层zip操作库,而非Ionic.Zip,主要原因是SharpZip的设计与java中的zip库更接近,移植相对简单,所以我们选择了这条捷径。不过有一点要向大家说明,OpenXml4Net仅负责底层操作,比如创建部件、创建关系等,但不包括Office上层的功能,如创建xlsx文件、添加单元格等,这只是一个底层操作库,NPOI将在后续版本中陆续增加,Excel 2007, Word 2007, PowerPoint 2007对应的命名空间分别是NPOI.XSSF, NPOI.XWPF, NPOI.XLSF,NPOI.XSSF按计划将在半年内完成(预计在2012年6月或7月发布),这次随本文发布的算是社区预览版,你可以基于这个版本给我们提建议和bug。

从头创建OOXML文件

任何一个OOXML都是一个zip文件,在本例中为了方便打开,我们直接使用.zip作为新建文件的扩展名。

//create ooxml file in memory
Package p = Package.Create();

//create package parts
PackagePartName pn1=new PackagePartName(new Uri("/a/abcd/e",UriKind.Relative),true);
if (!p.ContainPart(pn1))
    p.CreatePart(pn1, MediaTypeNames.Text.Plain);

PackagePartName pn2 = new PackagePartName(new Uri("/b/test.xml", UriKind.Relative), true);
if (!p.ContainPart(pn2))
    p.CreatePart(pn2, MediaTypeNames.Text.Xml);

//save file 
p.Save("test.zip");

//don't forget to close it
p.Close();

这里我们创建了2个部件,分别是位于/a/abcd目录下的e,和位于/b目录下的test.xml。这里有几点值得注意:

a. Package.Create有好几种调用方式,其中一种是上面这种Package.Create(),这样最直接的好处就是可以在内存中创建文件;而Package.Create(path),即事先传入文件名,直接在文件系统上创建文件,不用MemoryStream。当然啦,对于大文件(超过100M)以上的文件,使用Packakge.Create()做会占用较多的内存,所以如果并发量很高的话,建议慎用。

b. 用了p.ContainPart来判断节点是否已经存在,尽管对于新创建的文件这么做意义不大,但是这是个好习惯。

c. 创建PackagePartName的时候,Uri必须是Relative类型的,所以要传UriKind.Relative。这一点.Net做的比较挫,默认Uri都是Absolute的,而且一旦Uri为Relative类型的,基本上调用任何Uri的属性全部会抛异常,这实现够坑爹的。

 

修改已存在的OOXML文件,并保存为新文件

修改已存在的文件也很简单,Package.Open就可以了,但由于最后要保存,务必传入PackageAccess.READ_WRITE,否则会抛异常。

//create ooxml file in memory
Package p = Package.Open("test.zip",PackageAccess.READ_WRITE);
//create package parts
PackagePartName pn3 = new PackagePartName(new Uri("/c.xml", UriKind.Relative), true);
if (!p.ContainPart(pn3))
    p.CreatePart(pn3, MediaTypeNames.Text.Xml);

//save file 
p.Save("test1.zip");

//don't forget to close it
p.Close();

对于已存在的文件,目前仍然有个bug,那就是不能直接保存为当前文件名,存在文件被占用的问题,我们将尝试在正式版中解决这个问题。

 

目前我们仍然在对OpenXml4Net接口进行调整,以提高组件的易用性和稳定性。如果大家发现啥bug或者问题,请直接通过邮件联系我。

下载NPOI.OpenXML4Net请到这里:http://code.google.com/p/npoi/downloads/list

OpenXML4Net的源代码请通过googlecode svn获取

 

今年NPOI计划出一本入门级指导书,名字未定,主要面向NPOI初学者,也可以作为NPOI功能速查手册,帮助更多的人上手。有兴趣的出版社可以通过联系我。

posted @ 2012-01-12 06:34 Tony Qu 阅读(1898) 评论(9) 编辑 收藏

 回复 引用 查看   
#1楼2012-01-12 06:52 | Gabriel Zhang      
一直在用NPOI,如果能直接导出07以上的格式就完美了,呵呵!

 回复 引用 查看   
#2楼2012-01-12 08:07 | 共享天涯      
话说.net3.0和.net3.5其实发布时安装.net环境时安装3个文件就可以了,根本不需要原模原样的安装.net3.5的大框架。
NetFx20SP2_x86.exe
NetFx20SP2_x86zh-CHS.exe
ndp20sp2-kb958481-x86.exe
这是基于x86的机型,x64也是3个文件。

 回复 引用 查看   
#3楼2012-01-12 08:10 | 小龙3      
非常感谢! 操作EXCEL很方便。
 回复 引用 查看   
#4楼2012-01-12 08:18 | sun8134      
呵呵,小问题一个“尽管.net 3.9/3.5出来也3年了”
不过不影响阅读

现在基本是除了2.0就直接上4.0
3.0和3.5的包太大了...

 回复 引用 查看   
#5楼2012-01-12 08:41 | john23.net      
学习了
 回复 引用 查看   
#6楼2012-01-12 10:54 | zhll      
哇哈哈,好东西噢
 回复 引用 查看   
#7楼2012-01-12 11:58 | YLH      
Tony Qu,这个不错啊。 word,excel都支持了吗?
 回复 引用 查看   
#8楼2012-01-13 10:26 | Fengger      
谢谢兄弟的分享,团队的努力给大家节省了很多开发时间,再次感谢兄弟及团队对开发人员的贡献。
 回复 引用 查看   
#9楼2012-02-10 16:15 | like%'远远'%      
顶下