利用 .Net XML 解析 Execl XML单元格

阅读本文需要有 XML 规范基础 及 .net XML 解析基础

本文示例代码使用 Visual Stdio 2005 、Office Execl 2007

一、首先我们来看看Execl XML的开头

下面这段是文档的开头,其中定义了一些有用的命名空间,本文要用到的是:

ss:"urn:schemas-microsoft-com:office:spreadsheet"

它代表了工作表空间:

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] <?xml version="1.0" ?>
<?mso-application progid="Excel.Sheet"?>
<workbook xmlns="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:o="urn:schemas-microsoft-com:office:office"
xmlns:x="urn:schemas-microsoft-com:office:excel"
xmlns:ss="urn:schemas-microsoft-com:office:spreadsheet"
xmlns:html="http://www.w3.org/TR/REC-html40">
</workbook>

中间一些跳过不管,直接看Worksheet工作表段落。Worksheet字段的Name属性表示工作表名称 Table 字段中的保存了表格的行、列数目等属性:

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] <Worksheet ss:Name="Sheet1">
<Table ss:ExpandedColumnCount="7" ss:ExpandedRowCount="13" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">

  接下来是关键部分,也是最乱的地方,表的数据区。

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] <Row ss:AutoFitHeight="0"/>
<Row ss:AutoFitHeight="0"/>
<Cell ss:Index="2"><Data ss:Type="String">a</Data></Cell>
<Cell><Data ss:Type="String">b</Data></Cell>
......
</Row>
<Row ss:Index="4" ss:AutoFitHeight="0">
<Cell><Data ss:Type="String">9</Data></Cell>
<Cell><Data ss:Type="String">10</Data></Cell>
</Row>
......

  上面的数据区如下图所示:

  

数据区在下面是其他Table表格、下一个Worksheet工作区、表格样式等信息,这里不详细讨论了。

二、Execl XML数据区的格式框架

首先了解一下构架,从上面的数据区编码可以看出,有如下规律:

整个表以行为基础,标记是Row,行号最小的数据记录在最前面的Row中(但不一定是第一个!,随后会说到),其Index属性表示绝对行号

列是Row的子结点,标记是Cell(同时也表示具体数据元素),该行中列号最小的数据记录在该行最前面的Cell中,其Index属性表示绝对列号

Data标记是Cell的子结点,其属性记录数据的类型等信息,其值就是数据本身

XML中行号、列号的Index属性是该数据在表中的绝对位置;但没有此属性的行、列则用相对位置表示,需要自己解析计算出绝对位置

无论是行还是列,如果其绝对位置是1,那么都没有Index属性;如果不是1,则有Index属性

无论是行还是列,如果两行(列)是相邻的,那么都没有Index属性;如果不相邻,则有Index属性

行、列位置的表示方法 同时存在相对和绝对表示,Index属性的存在不可预知,在解析时一定要注意

Table使用的命名空间是"urn:schemas-microsoft-com:office:spreadsheet"

需要注意的是,根据存盘情况不同,Row的情况会有变化,目前观察有如下几种:

用Execl第二次保存之前:不会自动出现AutoFitHeight属性;绝对行号是1则没有Index属性且是第一个Row;绝对行号是2(且是行号最小的数据)则Index属性(=2)且是第一个Row

用Execl第二次保存之后:所有Row均自动加上AutoFitHeight属性;绝对行号是1则没有Index属性且是第一个Row;绝对行号是2(且是行号最小的数据)则没有Index属性且是第二个Row

由于Row的框架会变,我们的程序就必须覆盖所有情况。不知道MS为什么要这样设计,但是很明显我们写代码时将会比较痛苦......

三、部分解析代码

  全部的Execl单元格解析比较复杂,这里只介绍基本的XML解析。
  .net XML 解析模型

  

  1.读入Execl XML文件

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] //有时候用户需要打开Execl的同时做解析,所以我们使用流来构造XML
//我们的目的只是读出单元格,所以使用XPathDocument可以提高解析性能

//用到的命名空间
using namespace System::IO;
using namespace System::Xml;
using namespace System::Xml::XPath;

//按下面的参数构造流
FileStream^ sr = File::Open("c:\Book1.XML",FileMode::Open,FileAccess::Read,FileShare::ReadWrite);
//用流来构造XPathDocument
XPathDocument^ doc = gcnew XPathDocument(sr);

  2.构造游标、命名空间

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] //用XPathDocument来构造XPathNavigator游标
XPathNavigator^ navigator = doc->CreateNavigator();
//navigator->NameTable是被原子化的XML文档内容
XmlNamespaceManager^ manager = gcnew XmlNamespaceManager(navigator->NameTable);
//添加命名空间,并起个名字叫ss
manager->AddNamespace("ss","urn:schemas-microsoft-com:office:spreadsheet");

  3.查询节点

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] //找到指定WorkSheet中Name属性,Select需要节点的绝对路径
XPathNodeIterator^ node = navigator->Select("/ss:Workbook/ss:Worksheet/@ss:Name",manager);
XPathNavigator^ child = node->Current;
//未知原因SelectChild()查询子结点总是失败

  4.操作节点游标

Copy to ClipboardLiehuo.Net Codes引用的内容:[www.veryhuo.com] //移动到节点的第一个属性
child->MoveToFirstAttribute();
//移动到该节点的下一个属性
child->MoveToNextAttribute();
//如果在属性中则MoveToParent()回到节点,如果在节点上则回到上一级节点
child->MoveToParent();
//移动到下一个同级结点
child->MoveToNext();
//移动到第一个子结点
child->MoveToFirstChild();
//还有其他很多
.....
//得到当前游标位置上的名称(可能是节点名称(标记),也可能是属性名称)
child->Name;
//得到当前游标位置上的值(可能是节点的值,也可能是属性值)
chile->Value; 四、示例代码

  示例代码的功能是取出Execl中指定的一列,并按照这列的值建立同名的文件夹。
  由于Execl XML的特殊结构,程序里面的解析比较复杂,大家下载后有时间可以看看。
  程序现在还有些小问题,需要改进。
  解析思路为:
  1.找到用户要求的Worksheet工作表
  2.游标移动到Table
  3.找到第一个带数据的Row;如果是相对行位置,则计算出其绝对行号(只有可能是1或2)
  4.按照第一个带数据的Row所在的绝对行号,找到用户要求的起始行所在的Row位置;注意其间会混杂相对行位置和绝对行位置
  5.在该Row内找用户要求的列(注意其间会混杂相对列位置和绝对列位置);如果找不到,则下移一行(注意其间会混杂相对行位置和绝对行位置),再找,直到找到该列为止
  6.得到Data的值(取得指定数据项),做操作
  7.如果用户指定的结束行号大于表中的行号,则以表中的末尾行号为准
  
运行效果:

数据解析工作非常辛苦、烦闷,同时也需要极大的耐心。希望我的这篇文章能给大家提供一些帮助。

转自:http://www.cnblogs.com/chaobaojun/

转载请注明:代码家园 » 利用 .Net XML 解析 Execl XML单元格

评论列表(网友评论仅供网友表达个人看法,并不表明本站同意其观点或证实其描述)