下载源代码
阅读本文需要有 XML 规范基础 及 .net XML 解析基础
本文示例代码使用 Visual Stdio 2005 、Office Execl 2007
一、首先我们来看看Execl XML的开头
下面这段是文档的开头,其中定义了一些有用的命名空间,本文要用到的是: ss:"urn:schemas-microsoft-com:office:spreadsheet",
它代表了工作表空间。
<?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 字段中的保存了表格的行、列数目等属性 :
<Worksheet ss:Name="Sheet1">
<Table ss:ExpandedColumnCount="7" ss:ExpandedRowCount="13" x:FullColumns="1"
x:FullRows="1" ss:DefaultColumnWidth="54" ss:DefaultRowHeight="13.5">
接下来是关键部分,也是最乱的地方,表的数据区。
<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文件
//有时候用户需要打开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.构造游标、命名空间
//用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.查询节点 //找到指定WorkSheet中Name属性,Select需要节点的绝对路径
XPathNodeIterator^ node = navigator->Select("/ss:Workbook/ss:Worksheet/@ss:Name",manager);
XPathNavigator^ child = node->Current;
//未知原因SelectChild()查询子结点总是失败
4.操作节点游标 //移动到节点的第一个属性
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.如果用户指定的结束行号大于表中的行号,则以表中的末尾行号为准 运行效果:
数据解析工作非常辛苦、烦闷,同时也需要极大的耐心。希望我的这篇文章能给大家提供一些帮助。
|