在我最近开发的一个网页查询的项目中,客户提供的数据是多个 Excel 2007 文件,这些文件都很大,有的有十几万行(注意:Excel 2003 文件不能超过 65,536 行)。这些 Excel 2007 文件需要定期批量转换为网页程序可以读取的专用二进制格式文件。我们知道,Microsoft Office System 2007 引入了一个新的文件格式:Office Open XML 格式。她是基于 XML 和 ZIP 归档技术创建的,可以使用任何平台的能够处理 XML 或者 ZIP 文件的工具来访问并且修改文档内容。所以我们就可以使用 Microsoft .NET
Framework 2.0 的强大 XML 类库来读取 Excel 2007 文件并转换为网页程序所需的专用二进制格式文件。当然,也可以使用 System.IO.Packaging 名称空间中的类库,但是她位于 .NET Framework 3.0
SDK (WinFX) 的 WindowsBase.dll 中。微软网站上有几篇很有用的文章:“Office (2007) Open XML 文件格式简介”和“如何操作 Office Open XML 格式文档”。
下面,就来看看 Excel 2007 Open XML 文件的结构吧:
上图是一个名为 test1.xlsx 的 Excel 2007 文件。我没有 Office 2007 软件,只有正版的 Office 2003 软件。所以需要到微软网站下载一个“Microsoft Office Word、Excel 和 PowerPoint 2007 文件格式兼容包”,就可以在 Office 2003 中编辑 Office Open XML 文档了。test1.xlsx 文件其实是一个 zip 文件。为了分析其结构,我们现在把她解压到 D:/Test/test1/ 目录下。第一个重要的文件是 xl/workbook.xml,如下图所示:
该文件中的每个“<sheet>”元素都代表 Excel 2007 文件中的一个工作表,工作表的名称就是其“name”属性的值,在上图中是“好人”和“坏人”。然后根据“<sheet>”元素“r:id”属性的值(如上图中的“rId1”)到 xl/_rels/workbook.xml.rels 文件中寻找相应工作表数据实际存放的 xml 文件,如下图所示:
从图中可以看中,第一个工作表“好人”的数据实际存放在 worksheets/sheet1.xml 文件中,该文件的内容如下图所示:
上图中的“<dimension>”元素的“ref”属性的值(“B2:C4”)表示该工作表的范围。“<sheetData>”元素表示工作的数据,其子元素“<row>”表示工作表中的一行,“<row>”的子元素“<c>”表示该行中的单元格。如果“<c>”元素有“t”属性的话,“<c>”元素的子元素“<v>”的值就是各工作表共享的字符串的索引。否则的话,“<v>”元素的值就是该单元格的值。各工作表共享的字符串存放在 xl/sharedStrings.xml 文件中,如下图所示:
上图中,“<sst>”元素的子元素“<si>”就代表了共享的字符串,其值就是“<si>”元素的子元素“<t>”的值。
下面就看看我们的测试程序吧:
源程序的整体结构如下图所示:
我们先看看 XlsxFile.cs 吧:
该程序已经有很详细的注释了。在该程序中用 XmlDocument 类来读 xl/workbook.xml 文件和 xl/_rels/workbook.xml.rels 文件,是因为这两个文件都是非常小的文件。然后再来看看 XlsxFile.SharedStrings.cs 吧:
这下必须用 XmlReader 类来读取 xl/sharedStrings.xml 了,因为这个 xml 文件可能很大。最后是 XlsxFile.Sheet.cs 了:
在这个程序中也是用 XmlReader 类来读取 xl/worksheets/sheet1.xml 文件。
下面,就来看看 Excel 2007 Open XML 文件的结构吧:
上图是一个名为 test1.xlsx 的 Excel 2007 文件。我没有 Office 2007 软件,只有正版的 Office 2003 软件。所以需要到微软网站下载一个“Microsoft Office Word、Excel 和 PowerPoint 2007 文件格式兼容包”,就可以在 Office 2003 中编辑 Office Open XML 文档了。test1.xlsx 文件其实是一个 zip 文件。为了分析其结构,我们现在把她解压到 D:/Test/test1/ 目录下。第一个重要的文件是 xl/workbook.xml,如下图所示:
该文件中的每个“<sheet>”元素都代表 Excel 2007 文件中的一个工作表,工作表的名称就是其“name”属性的值,在上图中是“好人”和“坏人”。然后根据“<sheet>”元素“r:id”属性的值(如上图中的“rId1”)到 xl/_rels/workbook.xml.rels 文件中寻找相应工作表数据实际存放的 xml 文件,如下图所示:
从图中可以看中,第一个工作表“好人”的数据实际存放在 worksheets/sheet1.xml 文件中,该文件的内容如下图所示:
上图中的“<dimension>”元素的“ref”属性的值(“B2:C4”)表示该工作表的范围。“<sheetData>”元素表示工作的数据,其子元素“<row>”表示工作表中的一行,“<row>”的子元素“<c>”表示该行中的单元格。如果“<c>”元素有“t”属性的话,“<c>”元素的子元素“<v>”的值就是各工作表共享的字符串的索引。否则的话,“<v>”元素的值就是该单元格的值。各工作表共享的字符串存放在 xl/sharedStrings.xml 文件中,如下图所示:
上图中,“<sst>”元素的子元素“<si>”就代表了共享的字符串,其值就是“<si>”元素的子元素“<t>”的值。
下面就看看我们的测试程序吧:
源程序的整体结构如下图所示:
我们先看看 XlsxFile.cs 吧:
1 using System;
2 using System.IO;
3 using System.Xml;
4 using Skyiv.Ben.Common;
5
6 namespace Skyiv.OfficeHelper
7 {
8 /// <summary>
9 /// Excel 2007 文件
10 /// </summary>
11 sealed partial class XlsxFile : IDisposable
12 {
13 string fileName; // Excel 2007 文件的文件名
14 Sheet[] sheets; // Excel 2007 文件的各工作表
15 FileStream fileStream { get { return new FileStream(fileName, FileMode.Open, FileAccess.Read); } }
16
17 /// <summary>
18 /// Excel 2007 文件的构造函数
19 /// </summary>
20 /// <param name="fileName">Excel 2007 文件的文件名</param>
21 public XlsxFile(string fileName)
22 {
23 this.fileName = fileName;
24 }
25
26 /// <summary>
27 /// Excel 2007 文件的各工作表
28 /// </summary>
29 public Sheet[] Sheets
30 {
31 get
32 {
33 if (sheets == null)
34 {
35 using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/workbook.xml"))
36 {
37 // xl/workbook.xml 文件的内容举例如下:
38 // <workbook>
39 // <sheets>
40 // <sheet name="好人" sheetId="1" r:id="rId1" />
41 // <sheet name="坏人" sheetId="2" r:id="rId2" />
42 // </sheets>
43 // </workboo>
44 XmlDocument xmlDocument = new XmlDocument();
45 xmlDocument.Load(zs);
46 XmlNodeList elms = xmlDocument.DocumentElement["sheets"].ChildNodes;
47 sheets = new Sheet[elms.Count];
48 for (int i = 0; i < elms.Count; i++)
49 {
50 XmlAttributeCollection attrs = elms[i].Attributes;
51 sheets[i] = new Sheet(attrs["name"].Value, GetXmlFileName(attrs["r:id"].Value), SharedStrings, fileStream);
52 }
53 }
54 }
55 return sheets;
56 }
57 }
58
59 /// <summary>
60 /// 根据“标识”给出表示工作表的 XML 文件名
61 /// </summary>
62 /// <param name="id">标识</param>
63 /// <returns>表示工作表的 XML 文件名</returns>
64 string GetXmlFileName(string id)
65 {
66 string value;
67 using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/_rels/workbook.xml.rels"))
68 {
69 // xl/_rels/workbook.xml.rels 文件的内容举例如下:
70 // <Relationships>
71 // <Relationship Id="rId1" Target="worksheets/sheet1.xml" />
72 // <Relationship Id="rId2" Target="worksheets/sheet2.xml" />
73 // </Relationships>
74 XmlDocument xmlDocument = new XmlDocument();
75 xmlDocument.Load(zs);
76 value = XmlHelper.GetElementById(xmlDocument, id).Attributes["Target"].Value;
77 }
78 return value;
79 }
80
81 public void Dispose()
82 {
83 if (sheets == null) return;
84 foreach (Sheet sheet in sheets) sheet.Dispose();
85 }
86 }
87 }
88
2 using System.IO;
3 using System.Xml;
4 using Skyiv.Ben.Common;
5
6 namespace Skyiv.OfficeHelper
7 {
8 /// <summary>
9 /// Excel 2007 文件
10 /// </summary>
11 sealed partial class XlsxFile : IDisposable
12 {
13 string fileName; // Excel 2007 文件的文件名
14 Sheet[] sheets; // Excel 2007 文件的各工作表
15 FileStream fileStream { get { return new FileStream(fileName, FileMode.Open, FileAccess.Read); } }
16
17 /// <summary>
18 /// Excel 2007 文件的构造函数
19 /// </summary>
20 /// <param name="fileName">Excel 2007 文件的文件名</param>
21 public XlsxFile(string fileName)
22 {
23 this.fileName = fileName;
24 }
25
26 /// <summary>
27 /// Excel 2007 文件的各工作表
28 /// </summary>
29 public Sheet[] Sheets
30 {
31 get
32 {
33 if (sheets == null)
34 {
35 using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/workbook.xml"))
36 {
37 // xl/workbook.xml 文件的内容举例如下:
38 // <workbook>
39 // <sheets>
40 // <sheet name="好人" sheetId="1" r:id="rId1" />
41 // <sheet name="坏人" sheetId="2" r:id="rId2" />
42 // </sheets>
43 // </workboo>
44 XmlDocument xmlDocument = new XmlDocument();
45 xmlDocument.Load(zs);
46 XmlNodeList elms = xmlDocument.DocumentElement["sheets"].ChildNodes;
47 sheets = new Sheet[elms.Count];
48 for (int i = 0; i < elms.Count; i++)
49 {
50 XmlAttributeCollection attrs = elms[i].Attributes;
51 sheets[i] = new Sheet(attrs["name"].Value, GetXmlFileName(attrs["r:id"].Value), SharedStrings, fileStream);
52 }
53 }
54 }
55 return sheets;
56 }
57 }
58
59 /// <summary>
60 /// 根据“标识”给出表示工作表的 XML 文件名
61 /// </summary>
62 /// <param name="id">标识</param>
63 /// <returns>表示工作表的 XML 文件名</returns>
64 string GetXmlFileName(string id)
65 {
66 string value;
67 using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/_rels/workbook.xml.rels"))
68 {
69 // xl/_rels/workbook.xml.rels 文件的内容举例如下:
70 // <Relationships>
71 // <Relationship Id="rId1" Target="worksheets/sheet1.xml" />
72 // <Relationship Id="rId2" Target="worksheets/sheet2.xml" />
73 // </Relationships>
74 XmlDocument xmlDocument = new XmlDocument();
75 xmlDocument.Load(zs);
76 value = XmlHelper.GetElementById(xmlDocument, id).Attributes["Target"].Value;
77 }
78 return value;
79 }
80
81 public void Dispose()
82 {
83 if (sheets == null) return;
84 foreach (Sheet sheet in sheets) sheet.Dispose();
85 }
86 }
87 }
88
该程序已经有很详细的注释了。在该程序中用 XmlDocument 类来读 xl/workbook.xml 文件和 xl/_rels/workbook.xml.rels 文件,是因为这两个文件都是非常小的文件。然后再来看看 XlsxFile.SharedStrings.cs 吧:
1 using System;
2 using System.IO;
3 using System.Xml;
4 using Skyiv.Ben.Common;
5
6 namespace Skyiv.OfficeHelper
7 {
8 partial class XlsxFile
9 {
10 string[] sharedStrings;
11
12 /// <summary>
13 /// Excel 2007 文件中各工作表共享的字符串
14 /// </summary>
15 string[] SharedStrings
16 {
17 get
18 {
19 if (sharedStrings == null)
20 {
21 Stream zs = null;
22 try
23 {
24 zs = Zip.GetZipInputStream(fileStream, "xl/sharedStrings.xml"); // 可能引发(FileNotFoundException)
25 // xl/sharedStrings.xml 文件的内容举例如下:
26 // <sst count="56" uniqueCount="2">
27 // <si><t>任盈盈</t><phoneticPr fontId="1" type="noConversion" /></si>
28 // <si><t /></si>
29 // </sst>
30 using (XmlReader reader = XmlReader.Create(zs))
31 {
32 while (reader.Read()) if (reader.IsStartElement("sst")) break;
33 sharedStrings = new string[Convert.ToInt32(reader.GetAttribute("uniqueCount"))];
34 for (int count = 0; ; count++)
35 {
36 reader.Read(); // 执行后(reader)的值: <si> or </sst>
37 if (!reader.IsStartElement("si")) break;
38 reader.ReadStartElement("si"); // 执行后(reader)的值: <t> or <t />
39 sharedStrings[count] = reader.ReadElementString("t").Trim(); // 执行后(reader)的值: </si> or <与<t>同级的元素>
40 if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("t"); // 执行后(reader)的值: </si>
41 }
42 }
43 }
44 catch (FileNotFoundException)
45 {
46 sharedStrings = new string[0]; // 如果没有找到 xl/sharedStrings.xml 文件
47 }
48 finally
49 {
50 if (zs != null) zs.Close();
51 }
52 }
53 return sharedStrings;
54 }
55 }
56 }
57 }
58
2 using System.IO;
3 using System.Xml;
4 using Skyiv.Ben.Common;
5
6 namespace Skyiv.OfficeHelper
7 {
8 partial class XlsxFile
9 {
10 string[] sharedStrings;
11
12 /// <summary>
13 /// Excel 2007 文件中各工作表共享的字符串
14 /// </summary>
15 string[] SharedStrings
16 {
17 get
18 {
19 if (sharedStrings == null)
20 {
21 Stream zs = null;
22 try
23 {
24 zs = Zip.GetZipInputStream(fileStream, "xl/sharedStrings.xml"); // 可能引发(FileNotFoundException)
25 // xl/sharedStrings.xml 文件的内容举例如下:
26 // <sst count="56" uniqueCount="2">
27 // <si><t>任盈盈</t><phoneticPr fontId="1" type="noConversion" /></si>
28 // <si><t /></si>
29 // </sst>
30 using (XmlReader reader = XmlReader.Create(zs))
31 {
32 while (reader.Read()) if (reader.IsStartElement("sst")) break;
33 sharedStrings = new string[Convert.ToInt32(reader.GetAttribute("uniqueCount"))];
34 for (int count = 0; ; count++)
35 {
36 reader.Read(); // 执行后(reader)的值: <si> or </sst>
37 if (!reader.IsStartElement("si")) break;
38 reader.ReadStartElement("si"); // 执行后(reader)的值: <t> or <t />
39 sharedStrings[count] = reader.ReadElementString("t").Trim(); // 执行后(reader)的值: </si> or <与<t>同级的元素>
40 if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("t"); // 执行后(reader)的值: </si>
41 }
42 }
43 }
44 catch (FileNotFoundException)
45 {
46 sharedStrings = new string[0]; // 如果没有找到 xl/sharedStrings.xml 文件
47 }
48 finally
49 {
50 if (zs != null) zs.Close();
51 }
52 }
53 return sharedStrings;
54 }
55 }
56 }
57 }
58
这下必须用 XmlReader 类来读取 xl/sharedStrings.xml 了,因为这个 xml 文件可能很大。最后是 XlsxFile.Sheet.cs 了:
1 using System;
2 using System.IO;
3 using System.Xml;
4 using System.Collections.Generic;
5 using Skyiv.Ben.Common;
6
7 namespace Skyiv.OfficeHelper
8 {
9 partial class XlsxFile
10 {
11 /// <summary>
12 /// Execl 2007 文件中的工作表
13 /// </summary>
14 public sealed class Sheet : IDisposable
15 {
16 string[] sharedStrings; // 各工作表共享的字符串
17 Stream stream; // 用于读取本工作表的文件流
18 XmlReader reader; // 用于读取本工作表的 XML 数据读取器
19 string dimension; // 本工作表的范围,如:“A1”、“B2:C4”
20 int rowCount; // 本工作表的有效行数
21 string name; // 本工作表的名称
22
23 public string Dimension { get { return dimension; } }
24 public int RowCount { get { return rowCount; } }
25 public string Name { get { return name; } }
26
27 /// <summary>
28 /// 表示 Excel 2007 文件中的工作表的类的构造函数
29 /// </summary>
30 /// <param name="name">本工作表的名称</param>
31 /// <param name="fileName">工作表的 XML 文件名</param>
32 /// <param name="sharedStrings">各工作表共享的字符串</param>
33 /// <param name="fileStream">表示整个 Excel 2007 文件的流</param>
34 public Sheet(string name, string fileName, string[] sharedStrings, Stream fileStream)
35 {
36 this.name = name;
37 this.sharedStrings = sharedStrings;
38 stream = Zip.GetZipInputStream(fileStream, "xl/" + fileName);
39 reader = XmlReader.Create(stream);
40 while (reader.Read()) if (reader.IsStartElement("dimension")) break;
41 dimension = reader.GetAttribute("ref"); // 本工作表的范围:<dimension ref="B2:C4" />
42 rowCount = GetRowCount(dimension); // 根据工作表的范围计算有效行数
43 while (reader.Read()) if (reader.IsStartElement("sheetData")) break;
44 }
45
46 /// <summary>
47 /// 读取本工作表的中一行
48 /// </summary>
49 /// <returns>读取的行的各字段的内容。如果已经没有可读的行则返回 null。</returns>
50 public string[] ReadRow()
51 {
52 // 表示工作表的 XML 文件(如:xl/worksheets/sheet1.xml)的内容举例如下:
53 // <worksheet>
54 // <dimension ref="B2:C4" />
55 // <sheetData>
56 // <row r="2" spans="2:3" />
57 // <row r="4" spans="2:3">
58 // <c r="B4" />
59 // <c r="C4" t="s"><v>1</v></c>
60 // </row>
61 // </sheetData>
62 // </worksheet>
63 // 注意:在该 XML 文件中可能省略某些空行和空单元格,而本方法忽略这些空行和空单元格。
64 // 但本方法不忽略 XML 文件中的空行“<row />”和空单元格“<c />”。
65 if (!reader.IsStartElement("row")) reader.Read();
66 if (!reader.IsStartElement("row")) return null; // 没有可读的行
67 List<string> list = new List<string>();
68 for (; ; )
69 {
70 reader.Read(); // 执行后(reader)的值: <c> or </row> or (other for <row />)
71 if (!reader.IsStartElement("c")) break; // 没有可读的单元格
72 if (reader.IsEmptyElement) list.Add(""); // 空单元格“<c />”
73 else // “<c><v>1</v></c>”
74 {
75 string attr = reader.GetAttribute("t"); // 如果“<c>”元素的“t”属性不为空,则“<v>”元素的值指向各工作表共享的字符串
76 reader.ReadStartElement("c"); // 执行后(reader)的值: <v> or <v />
77 list.Add(GetValue(attr, reader.ReadElementString("v"))); // 执行后(reader)的值: </c> or <与<v>同级的元素>
78 if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("v"); // 执行后(reader)的值: </c>
79 }
80 }
81 return list.ToArray();
82 }
83
84 /// <summary>
85 /// 根据工作表的范围计算有效行数
86 /// </summary>
87 /// <param name="dimension">工作表的范围,如:“A1”、“B2:C4”</param>
88 /// <returns>有效行数</returns>
89 int GetRowCount(string dimension)
90 {
91 if (string.IsNullOrEmpty(dimension)) return -1;
92 string[] ss = dimension.Split(':');
93 if (ss.Length == 1) return 1;
94 if (ss.Length != 2) return -1;
95 return GetRowNumber(ss[1]) - GetRowNumber(ss[0]) + 1;
96 }
97
98 /// <summary>
99 /// 根据单元格的坐标计算单元格的行号
100 /// </summary>
101 /// <param name="str">单元格的坐标,如“C4”</param>
102 /// <returns>单元格的行号</returns>
103 int GetRowNumber(string str)
104 {
105 int i;
106 for (i = 0; i < str.Length; i++) if (char.IsDigit(str, i)) break;
107 return int.Parse(str.Substring(i));
108 }
109
110 /// <summary>
111 /// 给出单元格的值,可能是各工作表共享的字符串
112 /// </summary>
113 /// <param name="attr">“<c>”元素的“t”属性的值</param>
114 /// <param name="value">“<v>”元素的值</param>
115 /// <returns>单元格的值</returns>
116 string GetValue(string attr, string value)
117 {
118 if (attr != null)
119 {
120 int index;
121 if (!int.TryParse(value, out index)) throw new Exception("共享字符串索引(" + value + ")必须是整数");
122 if (index < 0 || index >= sharedStrings.Length) throw new Exception("共享字符串索引("
123 + index + ")必须在(0)到(" + (sharedStrings.Length - 1) + ")之间");
124 value = sharedStrings[index];
125 }
126 return value;
127 }
128
129 public void Dispose()
130 {
131 if (reader != null) reader.Close();
132 if (stream != null) stream.Close();
133 reader = null;
134 stream = null;
135 }
136 }
137 }
138 }
139
2 using System.IO;
3 using System.Xml;
4 using System.Collections.Generic;
5 using Skyiv.Ben.Common;
6
7 namespace Skyiv.OfficeHelper
8 {
9 partial class XlsxFile
10 {
11 /// <summary>
12 /// Execl 2007 文件中的工作表
13 /// </summary>
14 public sealed class Sheet : IDisposable
15 {
16 string[] sharedStrings; // 各工作表共享的字符串
17 Stream stream; // 用于读取本工作表的文件流
18 XmlReader reader; // 用于读取本工作表的 XML 数据读取器
19 string dimension; // 本工作表的范围,如:“A1”、“B2:C4”
20 int rowCount; // 本工作表的有效行数
21 string name; // 本工作表的名称
22
23 public string Dimension { get { return dimension; } }
24 public int RowCount { get { return rowCount; } }
25 public string Name { get { return name; } }
26
27 /// <summary>
28 /// 表示 Excel 2007 文件中的工作表的类的构造函数
29 /// </summary>
30 /// <param name="name">本工作表的名称</param>
31 /// <param name="fileName">工作表的 XML 文件名</param>
32 /// <param name="sharedStrings">各工作表共享的字符串</param>
33 /// <param name="fileStream">表示整个 Excel 2007 文件的流</param>
34 public Sheet(string name, string fileName, string[] sharedStrings, Stream fileStream)
35 {
36 this.name = name;
37 this.sharedStrings = sharedStrings;
38 stream = Zip.GetZipInputStream(fileStream, "xl/" + fileName);
39 reader = XmlReader.Create(stream);
40 while (reader.Read()) if (reader.IsStartElement("dimension")) break;
41 dimension = reader.GetAttribute("ref"); // 本工作表的范围:<dimension ref="B2:C4" />
42 rowCount = GetRowCount(dimension); // 根据工作表的范围计算有效行数
43 while (reader.Read()) if (reader.IsStartElement("sheetData")) break;
44 }
45
46 /// <summary>
47 /// 读取本工作表的中一行
48 /// </summary>
49 /// <returns>读取的行的各字段的内容。如果已经没有可读的行则返回 null。</returns>
50 public string[] ReadRow()
51 {
52 // 表示工作表的 XML 文件(如:xl/worksheets/sheet1.xml)的内容举例如下:
53 // <worksheet>
54 // <dimension ref="B2:C4" />
55 // <sheetData>
56 // <row r="2" spans="2:3" />
57 // <row r="4" spans="2:3">
58 // <c r="B4" />
59 // <c r="C4" t="s"><v>1</v></c>
60 // </row>
61 // </sheetData>
62 // </worksheet>
63 // 注意:在该 XML 文件中可能省略某些空行和空单元格,而本方法忽略这些空行和空单元格。
64 // 但本方法不忽略 XML 文件中的空行“<row />”和空单元格“<c />”。
65 if (!reader.IsStartElement("row")) reader.Read();
66 if (!reader.IsStartElement("row")) return null; // 没有可读的行
67 List<string> list = new List<string>();
68 for (; ; )
69 {
70 reader.Read(); // 执行后(reader)的值: <c> or </row> or (other for <row />)
71 if (!reader.IsStartElement("c")) break; // 没有可读的单元格
72 if (reader.IsEmptyElement) list.Add(""); // 空单元格“<c />”
73 else // “<c><v>1</v></c>”
74 {
75 string attr = reader.GetAttribute("t"); // 如果“<c>”元素的“t”属性不为空,则“<v>”元素的值指向各工作表共享的字符串
76 reader.ReadStartElement("c"); // 执行后(reader)的值: <v> or <v />
77 list.Add(GetValue(attr, reader.ReadElementString("v"))); // 执行后(reader)的值: </c> or <与<v>同级的元素>
78 if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("v"); // 执行后(reader)的值: </c>
79 }
80 }
81 return list.ToArray();
82 }
83
84 /// <summary>
85 /// 根据工作表的范围计算有效行数
86 /// </summary>
87 /// <param name="dimension">工作表的范围,如:“A1”、“B2:C4”</param>
88 /// <returns>有效行数</returns>
89 int GetRowCount(string dimension)
90 {
91 if (string.IsNullOrEmpty(dimension)) return -1;
92 string[] ss = dimension.Split(':');
93 if (ss.Length == 1) return 1;
94 if (ss.Length != 2) return -1;
95 return GetRowNumber(ss[1]) - GetRowNumber(ss[0]) + 1;
96 }
97
98 /// <summary>
99 /// 根据单元格的坐标计算单元格的行号
100 /// </summary>
101 /// <param name="str">单元格的坐标,如“C4”</param>
102 /// <returns>单元格的行号</returns>
103 int GetRowNumber(string str)
104 {
105 int i;
106 for (i = 0; i < str.Length; i++) if (char.IsDigit(str, i)) break;
107 return int.Parse(str.Substring(i));
108 }
109
110 /// <summary>
111 /// 给出单元格的值,可能是各工作表共享的字符串
112 /// </summary>
113 /// <param name="attr">“<c>”元素的“t”属性的值</param>
114 /// <param name="value">“<v>”元素的值</param>
115 /// <returns>单元格的值</returns>
116 string GetValue(string attr, string value)
117 {
118 if (attr != null)
119 {
120 int index;
121 if (!int.TryParse(value, out index)) throw new Exception("共享字符串索引(" + value + ")必须是整数");
122 if (index < 0 || index >= sharedStrings.Length) throw new Exception("共享字符串索引("
123 + index + ")必须在(0)到(" + (sharedStrings.Length - 1) + ")之间");
124 value = sharedStrings[index];
125 }
126 return value;
127 }
128
129 public void Dispose()
130 {
131 if (reader != null) reader.Close();
132 if (stream != null) stream.Close();
133 reader = null;
134 stream = null;
135 }
136 }
137 }
138 }
139
在这个程序中也是用 XmlReader 类来读取 xl/worksheets/sheet1.xml 文件。