文章目录

NPOI是用于读写Excel和Word的插件包。

它是Apache POI的.NET版。

总之,Apache POI是一个Java的强大的、开源的Office文档处理包,而NPOI是它的.NET版本。所以在.NET平台下用NPOI来读写Office文档应该是优先级比较高的,一是稳定,二是强大,三是背后支持力量庞大。
在网上介绍时有一点非常突出,
使用 NPOI 你就可以在没有安装 Office 或者相应环境的机器上对 WORD/EXCEL 文档进行读写。
我觉得很多人在.NET平台读写Office文档时,应该都会优先考虑使用Office自带的库(Microsoft.Office.Interop.Excel之类),但是这类库兼容性极差,而且由于我电脑上又装了WPS,与Office会冲突,这类的库的使用就更难了(虽然最后通过全部卸载,重装Office勉强解决)。说了这么多,就想表达,它不需要Office库的这点很重要,一是冲突问题,二是你总不能要求客户机都装了兼容的Office吧。
注意:
本文只介绍Excel的基本使用。且例子均使用.xlsx格式的excel,因为现在已经2022年了,应该主流是.xlsx了,而且两者使用相差不大。
NuGet下搜索NPOI,找到合适的版本安装即可。
安装后,多了四个库,每个库的用处从命名中可以看出一二,

在正式用之前,需要知道里面常用类的含义和一些基本概念。
using NPOI.XSSF.UserModel;
...
// 新建工作簿对象
XSSFWorkbook workBook = new XSSFWorkbook();
// 写入文件
workBook.Write(new FileStream(@"test.xlsx", FileMode.Create));
之后,你在相应的目录下就能看到test.xlsx文件了,但此时若用excel打开,会报错“文件可能损坏”;原因是你的文件中只有一个工作簿,没有为工作簿创建工作表。若你用“桌面右键>新建MicroSoft Excel工作表”的方式建一个.xlsx的方式,则可以正常打开。两者区别在于桌面右键创建,会往里面加表。解决方法很简单,你只需要在创建工作簿之后,为工作簿创建一个工作表:
XSSFSheet newSheet = (XSSFSheet)workBook.CreateSheet("mySheet");
回到上节工作簿的例子,工作表相当于本子中的内容,如果一本本子只有一张皮,而没有内容,那又有什么意义呢,对吧?
一个excel文件创建好之后,那心急的人肯定就想往里面写东西了。代码如下:
XSSFWorkbook workBook = new XSSFWorkbook();
ISheet sheet = workBook.CreateSheet("mySheet");
// 修改单元格的值
sheet.GetRow(0).GetCell(0).SetCellValue("一个值");
workBook.Write(new FileStream(@"D:/aa.xlsx", FileMode.OpenOrCreate, FileAccess.ReadWrite));
程序运行起来同样会报错,因为你还没有创建单元格,就往单元格中写东西了。把上面修改单元格值的语句换成下面即可,
sheet.CreateRow(0).CreateCell(0).SetCellValue("一个值");
通常来讲,你需要先创建工作簿,再创建工作表,再为工作表创建行,然后再为指定行创建单元格,再去修改单元格的值。
犯上面两个错误,主要是平时可视化编辑excel文件习惯了,右键创建好excel文件,在excel文件里直接修改单元格的值,一切都是那么自然。其实在可视化操作时,Office工具为我们做了很多事情了。
上面两个代码示例中,已经出现了保存的影子,工作簿调用Write方法,写入文件流即可:
workBook.Write(new FileStream(...));
这边再介绍下,.NET中读写Excel的一般写法,
首先是读Excel,读文件往往没有什么问题,
using(FileStream fs = new FileStream(@"D:/a3.xlsx", FileMode.Open, FileAccess.Read))
{
XSSFWorkbook workBook = new XSSFWorkbook(fs);
ISheet sheet = workBook.GetSheetAt(0);
for(int r = 0; r < 10; r++)
{
for(int c = 0; c < 10; c++)
{
// 读取单元格内容(前提是单元格存在)
sheet.GetRow(r).GetCell(c);
}
}
}
然后是修改已有Excel,
IWorkbook workBook = null;
using(FileStream fs = new FileStream(@"D:/a3.xlsx", FileMode.Open, FileAccess.Read))
{
workBook = new XSSFWorkbook(fs);
ISheet sheet = workBook.GetSheetAt(0);
sheet.GetRow(3).GetCell(3).SetCellValue(33);
sheet.GetRow(4).GetCell(3).SetCellValue(22);
sheet.GetRow(5).GetCell(3).SetCellValue(22);
sheet.GetRow(6).GetCell(3).SetCellValue(33);
}
using(FileStream fs = new FileStream(@"D:/a3.xlsx", FileMode.Create, FileAccess.Write))
{
workBook.Write(fs);
}
修改内容的示例中,在第二次打开文件流,FileMode枚举使用的是Create而不是Open,关于这点,网上的说法是,Open会在文件末尾写入内容,Create则是覆盖内容(在文件已存在的情况下),所以使用Open时,会在已存在的xlsx文件末尾写入workbook内容导致文件损坏,
详情看原文链接
而对FileMode.Open与FileMode.Create具体的底层区别,官方文档并没有明确说明。
当然,这种说法我不是很赞同。
FileStream类中有两个属性Length和Position,含义是流的长度与流中的位置,
如果用FileMode.Open和FileMode.Create打开同一个文件后,观察这两个属性的值,你会发现,
FileMode.Open下,Length的值即文件原本的长度,Position是0;而Create,Length与Position都是0。
官方文档中有以下说明,Create模式下,若文件已存在,则会截断文件,

啥叫截断,从描述来看,就是文件大小视为0(估计底层就是偏移一下文件结束指针的操作)。
于是我又做了一个实验,观察两种模式写入之后,Excel文件的大小发生的变化,结果如下,
首先是原文件的长度:

然后是,Create模式下的长度:

最后是Open模式下的长度:

结论呼之欲出(有兴趣想深入探究的可以看.NET源码看底层实现),Open模式下,原文件的内容保留了,而写入流从头开始写入,覆盖了前面一部分,导致文件内容错乱,后面部分解析出问题。(事后我用文本内容对比工具对比了,两种模式下文件开头部分内容相同,但Open模式残留了原文件的末尾部分)
所以在使用上,写入Excel时,得使用FileMode.Create模式。
FileStream fs = new FileStream(filepath, FileMode.Open, FileAccess.ReadWrite);
// 1. 获取工作簿对象
IWorkbook workbook = new XSSFWorkbook(fs); // 2007
// IWorkbook workbook = new HSSFWorkbook(fs); // 2003
// 2. 获取工作表对象(第一个表,序号从0开始)
ISheet sheet = workbook.GetSheetAt(0);
// 3. 获取工作表的行(第一行)
IRow row = sheet.GetRow(0);
// 4. 获取指定行的单元格
ICell cell = row.GetCell(0);
// 5. 获取单元格样式
ICellStyle cellStyle = cell.CellStyle;
// 6. 创建工作簿对象
XSSFWorkbook workBook= new XSSFWorkbook();
// 7. 创建工作表对象
XSSFSheet newSheet = (XSSFSheet)workBook.CreateSheet("new sheet");
// 8. 创建工作表的行
XSSFRow newRow = (XSSFRow)newSheet.CreateRow(0);
// 9. 创建单元格
XSSFCell newCell = (XSSFCell)newRow.CreateCell(0);
// 10. 单元格写值
newCell.SetCellValue(1);
// 11. 设置Sheet名称
workBook.SetSheetName(0, "第一张表");
// 12. 设置单元格内容
newCell.SetCellValue(11);
// 13. 得到工作簿中Sheet数量
workBook.NumberOfSheets
// 14. 保存excel文件
workBook.Write(new FileStream(@"pathName", FileMode.Create, FileAccess.ReadWrite));
sheet.ForceFormulaRecalculation = true;
我正在学习如何使用Nokogiri,根据这段代码我遇到了一些问题:require'rubygems'require'mechanize'post_agent=WWW::Mechanize.newpost_page=post_agent.get('http://www.vbulletin.org/forum/showthread.php?t=230708')puts"\nabsolutepathwithtbodygivesnil"putspost_page.parser.xpath('/html/body/div/div/div/div/div/table/tbody/tr/td/div
我有一个Ruby程序,它使用rubyzip压缩XML文件的目录树。gem。我的问题是文件开始变得很重,我想提高压缩级别,因为压缩时间不是问题。我在rubyzipdocumentation中找不到一种为创建的ZIP文件指定压缩级别的方法。有人知道如何更改此设置吗?是否有另一个允许指定压缩级别的Ruby库? 最佳答案 这是我通过查看rubyzip内部创建的代码。level=Zlib::BEST_COMPRESSIONZip::ZipOutputStream.open(zip_file)do|zip|Dir.glob("**/*")d
类classAprivatedeffooputs:fooendpublicdefbarputs:barendprivatedefzimputs:zimendprotecteddefdibputs:dibendendA的实例a=A.new测试a.foorescueputs:faila.barrescueputs:faila.zimrescueputs:faila.dibrescueputs:faila.gazrescueputs:fail测试输出failbarfailfailfail.发送测试[:foo,:bar,:zim,:dib,:gaz].each{|m|a.send(m)resc
很好奇,就使用rubyonrails自动化单元测试而言,你们正在做什么?您是否创建了一个脚本来在cron中运行rake作业并将结果邮寄给您?git中的预提交Hook?只是手动调用?我完全理解测试,但想知道在错误发生之前捕获错误的最佳实践是什么。让我们理所当然地认为测试本身是完美无缺的,并且可以正常工作。下一步是什么以确保他们在正确的时间将可能有害的结果传达给您? 最佳答案 不确定您到底想听什么,但是有几个级别的自动代码库控制:在处理某项功能时,您可以使用类似autotest的内容获得关于哪些有效,哪些无效的即时反馈。要确保您的提
假设我做了一个模块如下:m=Module.newdoclassCendend三个问题:除了对m的引用之外,还有什么方法可以访问C和m中的其他内容?我可以在创建匿名模块后为其命名吗(就像我输入“module...”一样)?如何在使用完匿名模块后将其删除,使其定义的常量不再存在? 最佳答案 三个答案:是的,使用ObjectSpace.此代码使c引用你的类(class)C不引用m:c=nilObjectSpace.each_object{|obj|c=objif(Class===objandobj.name=~/::C$/)}当然这取决于
我正在尝试使用ruby和Savon来使用网络服务。测试服务为http://www.webservicex.net/WS/WSDetails.aspx?WSID=9&CATID=2require'rubygems'require'savon'client=Savon::Client.new"http://www.webservicex.net/stockquote.asmx?WSDL"client.get_quotedo|soap|soap.body={:symbol=>"AAPL"}end返回SOAP异常。检查soap信封,在我看来soap请求没有正确的命名空间。任何人都可以建议我
关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。
我在我的项目目录中完成了compasscreate.和compassinitrails。几个问题:我已将我的.sass文件放在public/stylesheets中。这是放置它们的正确位置吗?当我运行compasswatch时,它不会自动编译这些.sass文件。我必须手动指定文件:compasswatchpublic/stylesheets/myfile.sass等。如何让它自动运行?文件ie.css、print.css和screen.css已放在stylesheets/compiled。如何在编译后不让它们重新出现的情况下删除它们?我自己编译的.sass文件编译成compiled/t
我想将html转换为纯文本。不过,我不想只删除标签,我想智能地保留尽可能多的格式。为插入换行符标签,检测段落并格式化它们等。输入非常简单,通常是格式良好的html(不是整个文档,只是一堆内容,通常没有anchor或图像)。我可以将几个正则表达式放在一起,让我达到80%,但我认为可能有一些现有的解决方案更智能。 最佳答案 首先,不要尝试为此使用正则表达式。很有可能你会想出一个脆弱/脆弱的解决方案,它会随着HTML的变化而崩溃,或者很难管理和维护。您可以使用Nokogiri快速解析HTML并提取文本:require'nokogiri'h
我想为Heroku构建一个Rails3应用程序。他们使用Postgres作为他们的数据库,所以我通过MacPorts安装了postgres9.0。现在我需要一个postgresgem并且共识是出于性能原因你想要pggem。但是我对我得到的错误感到非常困惑当我尝试在rvm下通过geminstall安装pg时。我已经非常明确地指定了所有postgres目录的位置可以找到但仍然无法完成安装:$envARCHFLAGS='-archx86_64'geminstallpg--\--with-pg-config=/opt/local/var/db/postgresql90/defaultdb/po