VSTO学习之路:使用Open XML SDK for Office解析Excel数据

Excel2007以后,工作簿是以XML文本保存数据的,用压缩工具打开工作簿文件,结构大致如下:

文件夹下基本全部是xml文本文件。微软提供了Open XML SDK 2.5 for Office,其中定义了和这些xml文件相关的类,帮助我们使用xml的形式解析工作簿里的数据和内容。跳转到微软官方资料          跳转到Open XML SDK 2.5 for Microsoft Office下载连接

在安装了 Open XML SDK 2.5 之后,在项目或应用程序中,添加对以下组件的引用。

  • DocumentFormat.OpenXml
  • WindowsBase

package翻译:程序包;

1、创建SpreadsheetDocument对象

1 using DocumentFormat.OpenXml.Packaging;
2
3 //要解析的工作簿路径
4 string path = @"E:\studyvs\test.xlsx";
5 //如果是准备新建工作簿,可使用Create方法
6 SpreadsheetDocument xlPackag = SpreadsheetDocument.Open(path, true);

2、从workbook.xml开始入手

用记事本打开workbook.xml,看到一些有关工作簿的重要信息,比如:有几张工作表,表名称都是什么等。

workbook.xml中有一个sheets节点,我们尝试拿到它,并解析出三个工作表的名称。

ps:注意name属性是工作表名称的标识。

继续补充前面的代码:

 1 using DocumentFormat.OpenXml.Packaging;
 2 using DocumentFormat.OpenXml.Spreadsheet;
 3
 4 //要解析的工作簿路径
 5 string path = @"E:\studyvs\test.xlsx";
 6 SpreadsheetDocument xlPackag = SpreadsheetDocument.Open(path, true);
 7 //创建对象用于准备解析workbook.xml
 8 WorkbookPart wkpart = xlPackag.WorkbookPart;
 9 //Sheets类型定义在DocumentFormat.OpenXml.Spreadsheet命名空间下
10 Sheets sheets = wkpart.Workbook.Sheets;
11 foreach (Sheet sheet in sheets)
12 {
13      string s = string.Format("工作表名称为{0}的id是{1}", sheet.Name, sheet.SheetId);
14      System.Windows.Forms.MessageBox.Show(s);
15 }
16 //SpreadsheetDocument使用完毕后,要关闭。
17 xlPackag.Close();

要获得workbook.xml下的sheets元素还可可使用另一种表达方式:

IEnumerable<Sheet> sheets = wkpart.Workbook.Descendants<Sheet>();

需要注意的是:这里仅仅是拿到了三个工作表的名称,但并不能直接得到工作表下的数据,因为workbook.xml的sheets节点提供的仅仅就像我们看到的那样,只有有限的信息。但不要紧,workbookpart对象提供了GetPartById()方法,可以通过Id找到我们需要的。

3、工作表数据初接触

工作表mySheet中保存了一些数据,我们尝试把这些数据读取出来。

这个工作表的已用区域是A1:C4,数据类型是数字,文本和日期。

打开sheet1.xml,看一下文件内容:

我们看到sheetData下保存了工作表的单元格数据(有些数据似乎“不对”,这个等等再说)。

row元素表示了“行”,r表示行的索引。

c元素--cell,表示了“单元格”,r--CellReference表示了单元格的地址,t--DataType,表示了单元格的值类型。s--style

 1 //要解析的工作簿路径
 2 string path = @"E:\studyvs\test.xlsx";
 3 SpreadsheetDocument xlPackag = SpreadsheetDocument.Open(path, true);
 4 //创建对象用于解析workbook.xml
 5 WorkbookPart wkpart = xlPackag.WorkbookPart;
 6 IEnumerable<WorksheetPart> sheetparts = wkpart.WorksheetParts;
 7 //找出工作表纬度不是"A1"的worksheetpart。(在本例中指内容非空的工作表)
 8 WorksheetPart sheetpart = sheetparts.Single(p => p.Worksheet.SheetDimension.Reference != "A1");
 9 Worksheet sheet = sheetpart.Worksheet;
10 //拿到工作表的已使用范围
11 string refaddress = sheet.SheetDimension.Reference;
12 //拿到工作表使用的行数
13 int rowcount = sheet.Descendants<Row>().Count();
14 string s = string.Format("该工作表的引用区域是{0},行数是{1}行", refaddress, rowcount.ToString());
15 System.Windows.Forms.MessageBox.Show(s);
16 //-----该工作表的引用区域是A1:C4,行数是4行
17 //SpreadsheetDocument使用完毕后,要关闭。
18 xlPackag.Close();

worksheetpar得到的worksheet并没有name属性或元素之类的东东,所以使用这种方法无法使用工作表名称筛选工作表,前文已提到,要使用工作表名称筛选想要的工作表,可通过workbookpart的GetPartbyId方法。

下面的演示和上面的目的相同,都是拿到mySheet工作表的已用单元格区域地范围,和已使用的行数。

 1 string path = @"E:\studyvs\test.xlsx";
 2 SpreadsheetDocument xlPackag = SpreadsheetDocument.Open(path, true);
 3 WorkbookPart wkpart = xlPackag.WorkbookPart;
 4 IEnumerable<WorksheetPart> sheetparts = wkpart.WorksheetParts;
 5 //关键:拿到工作表名为mySheet的id
 6 string relationId = wkpart.Workbook.Descendants<Sheet>().Single(sht => sht.Name == "mySheet").Id;
 7 //GetPartById方法得到的是OpenXMLPart对象,需要显式转换为WorksheetPart对象
 8 WorksheetPart sheetpart =(WorksheetPart) wkpart.GetPartById(relationId);
 9 Worksheet sheet = sheetpart.Worksheet;
10 string refaddress = sheet.SheetDimension.Reference;
11 int rowcount = sheet.Descendants<Row>().Count();
12 string s = string.Format("该工作表的引用区域是{0},行数是{1}行", refaddress, rowcount.ToString());
13 System.Windows.Forms.MessageBox.Show(s);
14 xlPackag.Close();

差不多可以开始真正取单元格里的数据了,那些才是我们更关心的。取单元格的数据可能是像下面这样的模式:

 1 WorksheetPart sheetpart =(WorksheetPart) wkpart.GetPartById(sheetId);
 2 Worksheet sheet = sheetpart.Worksheet;
 3 foreach (Row row in sheet.Descendants<Row>())
 4 {
 5      foreach (Cell cell in row)
 6          {
 7              //value元素
 8              CellValue cellvalue = cell.CellValue;
 9              //value元素的值
10              string value = cellvalue.InnerText;
11              //...
12           }
13 }

然而真正的麻烦来了。当我们在xml文件中去搜寻需要的数据时,发现如果单元格的值是数字,那么c元素中v元素的innertext是正确的(日期也表现为数字)。如果单元络的值是“文本”,那么其c元素中的v元素的innertext中一些看上去好像没什么规律的数字。

此时我们就要来第一次认识一下sharedStrings.xml这个文件了

4、认识sharedStrings.xml文件和共享字符串表SharedStringTable<sst>

打开该文件。

si:SharedStringItem    t:text

对比一下mySheet中的元素,在mySheet中A1的值是0,B1的值3。可以发现,当mySheet中c元素的t--datatype属性的值是“s--string”时,v的innertext的值是sharedStrings.xml中"si"元素的索引,其真实值是si中的t元素的innertext。

事实上,这样做的好处之一使一个工作簿中重复出现的文本字符串可以生复使用,是可以节省文件所占用的空间。当c-cell元素中有t属性时,意味着该单元格的值是一个文本,v-cellvalue的innertext使用SharedStringTable中的索引来引用真实值。

5、取单元格数据的相对完整的代码

技巧:当c元素有属性t时,其值就是文本型。通过这一特点,判断innertext是单元格的真实值,还是sharedstringtable中的索引。从而以正确的方式得到真实值。

 1 using DocumentFormat.OpenXml.Packaging;
 2 using DocumentFormat.OpenXml.Spreadsheet;
 3
 4 string path = @"E:\studyvs\test.xlsx";
 5 SpreadsheetDocument xlPackag = SpreadsheetDocument.Open(path, true);
 6 WorkbookPart wkpart = xlPackag.WorkbookPart;
 7 IEnumerable<WorksheetPart> sheetparts = wkpart.WorksheetParts;
 8 string sheetId = wkpart.Workbook.Descendants<Sheet>().Single(sht => sht.Name == "mySheet").Id;
 9 //System.Windows.Forms.MessageBox.Show(sheetId);
10 WorksheetPart sheetpart =(WorksheetPart) wkpart.GetPartById(sheetId);
11 Worksheet sheet = sheetpart.Worksheet;
12 //row元素  cell元素 cellvalue元素
13 foreach (Row row in sheet.Descendants<Row>())
14 {
15     foreach (Cell cell in row)
16     {
17           CellValue cellvalue = cell.CellValue;
18           string value = cellvalue.InnerText;
19           if (cell.GetAttributes().Any(atr=>atr.LocalName=="t")==true)
20           {
21               //使用workbookpart对象得到sharedstringtable
22                value = wkpart.SharedStringTablePart.SharedStringTable.ChildElements[int.Parse(value)].InnerText;
23           }
24           string s = string.Format("单元格{0}的值是{1}", cell.CellReference, value);
25           System.Windows.Forms.MessageBox.Show(s);
26      }
27 }
28 xlPackag.Close();

上面的方法,把所有值,不论什么类型,都处理做了文本型。仍需要进一步改进,可能才真正适合需求。还有如果,在拿数值的时候,还要获得单元格应用的样式,也需要进一步的分析使用什么方法更合适和方便。

时间: 2024-10-24 13:06:21

VSTO学习之路:使用Open XML SDK for Office解析Excel数据的相关文章

VSTO学习之路:学习使用Epplus(1)

关于读取其它工作簿数据的几个方式的比较: 1.VBA的GetObject方法,会调用Excel程序打开工作簿(虽然不可见,但确实是打开的) 2.SQL,使用繁琐缺少灵活,不支持单元格样式的操作,也似乎不支持delete语句删除源数据. 3.使用Open XML SDK,基于Open XML,不依赖于Excel程序,但步骤繁琐. 4.Epplus,基于OpenXML,简单灵活,不依赖Excel程序打开工作簿,处理数据的速度快. 下载:Epplus,引用Epplus,然后  using Office

VSTO学习之路:学习使用Epplus——读写VBA代码

创建xlsm工作簿: 宏工作簿,必须有VBProject对象,至少要有一个工作表 1 string path = @"E:\studyvs\open xml\test.xlsm"; 2 var package = new ExcelPackage(); 3 package.Workbook.Worksheets.Add("Sheet1"); 4 //创建工程对象 5 package.Workbook.CreateVBAProject(); 6 //保存工作簿 7 p

VSTO学习之路:使用CustomXMLPart存储数据

假设两种情形: 1.当有一些数据要使用,但不想让用户直接看到,常规的方式是放在一个表里,然后隐藏 2.当数据来自服务器.远端服务器,但文档要带到一个离线环境使用 此时可以考虑在工作簿中创建一个CustomXMLPart,要创建它非常简单,可以使用CustomXMLParts接口的Add方法创建CustomXMLPart,CustomXMLPart接口和CustomXMLParts接口定义在 using Microsoft.Office.Core; 命名空间下,其中CustomXMLPart的定义

VSTO学习之路:ListObject控件的复杂数据绑定初步

先做两个准备:一个实体类 +  一个集合            目标:把集合绑定到宿主控件(主要演示编程方式做绑定). public class Person { public string Name { get; set; } public int Age { get; set; } public char Gender { get; set; } } 1 public List<Person> PersonList() 2 { 3 List<Person> persons =

VSTO学习之路:文档级项目的任务窗格

引言:下图类似“剪帖画”的是几个Excel中的命令栏.其中“文档操作”是可自由定制的,文档操作面板无法直接使用(不可见),在本图里可以看到,是因为我写代码在上面添加了一个TextBox控件. 命令栏是Commandbars集合成员,属于Commandbar类型.使用VBA写段代码罗列出所有的命令栏: 1 Sub ShowCommandbar() 2 Dim bars As Office.CommandBars 3 Set bars = Application.CommandBars 4 Dim

Android学习之路——Android四大组件之activity(二)数据的传递

上一篇讲了activity的创建和启动,这一篇,我们来讲讲activity的数据传递 activity之间的数据传递,这里主要介绍的是activity之间简单数据的传递,直接用bundle传递基本数据类型的数据.另一种数据类型是parcelable和serialable 用bundle 传递数据有两种情况,这篇文章就分别从两个方面说明一下. 一.利用bundle传递基本数据类型 1.启动时传递数据,使用intent的put方法,将数据写入bundle中,然后startActivity(inten

Spark学习之路 (九)SparkCore的调优之数据倾斜调优

摘抄自:https://tech.meituan.com/spark-tuning-pro.html 数据倾斜调优 调优概述 有的时候,我们可能会遇到大数据计算中一个最棘手的问题——数据倾斜,此时Spark作业的性能会比期望差很多.数据倾斜调优,就是使用各种技术方案解决不同类型的数据倾斜问题,以保证Spark作业的性能. 数据倾斜发生时的现象 绝大多数task执行得都非常快,但个别task执行极慢.比如,总共有1000个task,997个task都在1分钟之内执行完了,但是剩余两三个task却要

Android开发学习之路--网络编程之xml、json

一般网络数据通过http来get,post,那么其中的数据不可能杂乱无章,比如我要post一段数据,肯定是要有一定的格式,协议的.常用的就是xml和json了.在此先要搭建个简单的服务器吧,首先呢下载xampp,然后安装之类的就不再多讲了,参考http://cnbin.github.io/blog/2015/06/05/mac-an-zhuang-he-shi-yong-xampp/.安装好后,启动xampp,之后在浏览器输入localhost或者127.0.0.1就可以看到如下所示了: 这个就

Revit学习之路01_Revit基础

序言: 此分类记录自己的Revit学习之路.初学者难免一叶障目,不见泰山,欢迎一起交流学习! Revit介绍 Revit专为建筑信息模型(BIM)构建的一款建模软件.随着BIM概念在国内的兴起,各大设计院对Revit建模的需求增多,相关的基于Revit的二次开发也逐渐增多.具体介绍不在搬运. Revit是专为BIM构建的软件,了解Revit必须先了解BIM.BIM的概念不仅仅是二维图纸到三维模型的变化,BIM是一个信息管理和交互的平台.Revit既然作为专为BIM构建的软件,那么他就必须能够管理