作者: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。
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功能速查手册,帮助更多的人上手。有兴趣的出版社可以通过
联系我。
作者:Tony Qu
NAnt的脚本引擎非常强大,bat能干的事,它都能干,绝对是有过之而无不及。本文中将列出一些常用任务,其他的我就不一一例举了,大家可以通过http://nant.sourceforge.net/release/0.85/help/tasks/index.html自学。
NAnt常用任务
a. 声明变量
参考:http://nant.sourceforge.net/release/0.85/help/tasks/property.html
当我们声明完一个变量之后需要引用它的时候,我们可以用${propertyname}来获得它的值,这与php中的变量引用$a是类似的。
b. 创建目录
参考:http://nant.sourceforge.net/release/0.85/help/tasks/mkdir.html
c. 删除文件
参考:http://nant.sourceforge.net/release/0.85/help/tasks/delete.html
c. 执行第三方程序
参考:http://nant.sourceforge.net/release/0.85/help/tasks/exec.html
d. 拷贝文件
参考:http://nant.sourceforge.net/release/0.85/help/tasks/copy.html
拷贝是非常平凡的build动作,通常我们会对拷贝的内容作一定得筛选,比如所有的dll文件,这时我们可以用下面的语句:
<copy todir="c:\release">
<fileset basedir="c:\npoi\bin">
<include name="*.dll" />
</fileset>
</copy>
这里的意思是将c:\npoi\bin目录下的所有dll拷贝到c:\release目录下。
e. zip打包
参考:http://nant.sourceforge.net/release/0.85/help/tasks/zip.html
f. if语法
参考:http://nant.sourceforge.net/release/0.85/help/tasks/if.html
值得注意的是,很多语句支持if属性,也就是说如果条件满足才执行该任务。例如
<exec program="ping" if=”${a=1}”>
<arg value="nant.sourceforge.net" />
</exec>
这个声明的意思是只有当变量a等于1时,才会pint nant.sourceforge.net,这与下面的语句是等同的:
<if test=”${a=1}”>
<exec program="ping">
<arg value="nant.sourceforge.net" />
</exec>
</if>
g. foreach语法
参考:http://nant.sourceforge.net/release/0.85/help/tasks/foreach.html
如何使用NAntContrib的任务
NAntContrib最新版本是0.85,其中增加了很多额外的task,但NAnt自己无法自动找到这些task,所以必须在build文件的开头增加一句声明,如下
<loadtasks assembly="<nantcontrib path>/bin/NAnt.Contrib.Tasks.dll" />
黄色部分需要替换成你本地的NAntContrib路径。
NAnt.Contrib常用任务
a. cd - 切换当前工作目录
<cd dir="subdir" />
b. msbuild - 调用MSBuild进行编译,但必须安装MSBuild
详见http://nantcontrib.sourceforge.net/release/latest/help/tasks/msbuild.html
c. vb6 - 编译vb6的项目,但必须安装VB6
详见http://nantcontrib.sourceforge.net/release/latest/help/tasks/vb6.html
d. svn相关
svn:http://nantcontrib.sourceforge.net/release/latest/help/tasks/svn.html
svn-checkout:http://nantcontrib.sourceforge.net/release/latest/help/tasks/svn-checkout.html
svn-update:http://nantcontrib.sourceforge.net/release/latest/help/tasks/svn-update.html
e. sql - 执行sql脚本
详见http://nantcontrib.sourceforge.net/release/latest/help/tasks/sql.html
f. nunitreport - 生成NUnit报表
详见http://nantcontrib.sourceforge.net/release/latest/help/tasks/nunitreport.html
其他task请见http://nantcontrib.sourceforge.net/release/latest/help/tasks/
此文是在与客服沟通后发出,不存在误解,我是实在没找到合适的渠道提这个bug,所以才想借助下技术圈的力量,看看有没有在招行干活的兄弟,给你们相关部门提点建议。
我用招行专业版5年了,一直觉得招行的专业版很不错,又是国内唯一一个用.NET做前端的银行系统,所以一直是铁杆粉丝,但是最近用外汇功能有点不爽,想帮助系统尽快改进,所以才有了此文。
事情大致经过
最近用信用卡在国外网站刷了点美元,大概$500左右,本想拿一卡通里的美金还,大概有$700,肯定是够还的,为保证现汇还款成功还特地用专业版开通了现汇还美金功能,如下图所示。
下面是点修改后的设置界面,这个界面正常人看了都能明白 已经开通了美元自动还款功能,而且是现汇还款
我这里大概解释下,现汇和现钞的区别,现汇就是直接用你账户里的美金还,现钞就是用你账户里的RMB按照当天汇率购买等值美金,然后还款。到目前为止,我自然觉得我的操作完成了,但事实并非如此。
上个月25日,我正好想查一下帐,突然发觉账户里的RMB被扣了很多,我就纳闷了,怎么还是用现钞还,难道我设置错误了,跑进去一看设置是对的,非常非常奇怪,于是打电话给招行客服,得到的答复是他们还有一个设置尽然是和这个界面相关的:
这里便是客服所说的自动购汇设置,从界面上看,我基本没有看出自动购汇和现汇还款有任何关系,除了界面最下面有一段小字:
其中第六条说的意思大致是,如果同时设置了现汇还款和自动购汇,是先按照自动购汇做的,即使美元账号里有足够的美元,我的妈呀,这是什么乱七八糟的规定啊,谁会去看这行小字,而且这个是默认设置,开通专业版的时候就开着的功能,要不是客服说这个是相关的,我估计这辈子我也不会去看这个设置。最恶心的是刚才设置现汇还款的界面压根就没有提到自动购汇是相关的,设置的时候也没有任何的警告窗口,这是最诡异的地方,因为现汇还款按常理说如果金额足够的话,不存在兑换问题。
在网上搜了下,原来不止我一个人有这样的遭遇:
http://life.forex.com.cn/wbyhk/2010-03/1149479.htm
http://www.xiaojb.com/archives/personal/cmbchina_crea.shtml
接下来我们来分析下这个功能到底应该怎么整,这样才能让我们更好的理解哪个更优先的问题。
客户需求:用账户中的美元现汇还信用卡中的美元,如果不够则用RMB还
系统相关模块:自动还款、自动购汇、多币种管理、一卡通与信用卡转账功能
大致逻辑:
IF 开通美元现汇还款 THEN
IF 一卡通中的美元 >= 信用卡美元借款 THEN
用美元还美元
ELSE
用人民币购汇,然后还款(这时需要自动购汇)
ELSE (开通美元现钞还款)
计算需要多少RMB才能够完成购汇还款
IF 一卡通中的RMB >= 信用卡还款所需的RMB THEN
用RMB购汇还美元
ELSE
金额不足,无法还款
当然啦,这里只是一个大致逻辑,和实际系统可能还是有差距的,但有一点是明确的,自动购汇设置理应在美元现汇不够还或者现钞还款时才有效,但是这个条件根本不成立,这也是为什么像我这样的用户会纳闷为什么现汇还款设置没效果,因为用户觉得现汇还款始终是美元和美元在打交道,而不是美元和RMB在打交道,所以在金额足够的情况下不存在购汇问题,但是客服人员却一直在强调自动购汇优先级最高,这个优先级的结论其实根本不成立,因为这两件事根本没关系,何来的优先级,如果是现钞还款,她和我说优先级我倒还觉得有点道理。
综上所述,现汇还款在美元足够还的情况下,根本没必要和自动购汇去比优先级,因为这时是现汇还款,不涉及购汇,所以也没必要考虑自动购汇开不开,始终都应该先用账户中的美元还信用卡中的美元。
作者:Tony Qu
CC.NET的配置文件位于安装目录的server目录下,有个叫做ccnet.config的文件,只有配置了这个文件,CC.NET才能正常运行。
通过CCNetConfig我们可以学到很多关于ccnet.config的配置内容,首先来看一个非常重要的概念——触发器。
值得注意的是,这里的触发器都仅针对当前项目有效,这也是为什么这些trigger基本没有target,比如说IntervalTrigger,所有的属性都没有提到触发的对象,。
CC.NET提供了6种触发器:
IntervalTrigger,顾名思义,就是每隔一定时间触发任务的触发器。
FilterTrigger不能单独工作,而必须与其他Trigger一起使用,之所以叫FilterTrigger是因为它是用来过滤触发条件,例如我们原本设置了一个IntervalTrigger,每5分钟触发一次,但我只希望这个IntervalTrigger只在工作时间触发,那么我们就可以用FilterTrigger来限制IntervalTrigger的触发时间为9:00 ~ 18:00。
MultiTrigger类似于WPF中的MultiTrigger,可同时执行多个触发器,并对触发器结果做And或Or操作。
ScheduleTrigger和Windows自带的计划任务的功能非常类似,可以在特定时间触发任务。
ProjectTrigger是仅当指定的CC.NET项目build完成后才会触发任务的触发器。
UrlTrigger是仅当某个特定的Url页面的内容发生变化时才会触发任务的触发器。
下面来举个例子:
场景1:我希望设置一个daily build的触发器,每天凌晨1:00触发一次,仅工作日做,双休日不做(不考虑节假日)。
由于是特定时间循环触发,我们可以用ScheduleTrigger来实现,代码如下:
<scheduleTrigger time="1:00" buildCondition="ForceBuild" name="ScheduledTask1"> <weekDays> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> </weekDays> </scheduleTrigger>
在CCNetConfig里面设置则如下图所示:
场景2: 持续集成需要每隔一定时间触发一次build,已确定之前的checkin都是可运行的,所以我们需要设定一个每周一到周五的工作时间(9:00 ~ 18:00)触发,并且每隔15分钟触发一次的触发器(不考虑时区、时差问题)。
这里我们需要使用IntervalTrigger+FilterTrigger,FilterTrigger是用于过滤时间的,即9:00 ~ 18:00。
<filterTrigger startTime="09:00" endTime="18:00"> <trigger type="intervalTrigger" seconds="600" /> <weekDays> <weekDay>Monday</weekDay> <weekDay>Tuesday</weekDay> <weekDay>Wednesday</weekDay> <weekDay>Thursday</weekDay> <weekDay>Friday</weekDay> </weekDays> </filterTrigger>
在CCNetConfig里面设置则如下图所示:
场景3: 当项目A的build成功之后才触发当前项目build(通常项目A是某个核心代码库,当前项目对其具有依赖性),由于是仅当成功才触发,所以triggerStatus=Success
<triggers> <projectTrigger project="task2"> <triggerStatus>Success</triggerStatus> <innerTrigger type="intervalTrigger" seconds="1800" buildCondition="ForceBuild" /> </projectTrigger> </triggers>
在CCNetConfig里面设置则如下图所示:
Starcite上海招聘Java工程师/QA,具体要求请见http://job.cnblogs.com/Offer/EnterpriseInfo.aspx?key=3743










