用XmlReader 读取 Excel 2007 文件

在我最近开发的一个网页查询的项目中,客户提供的数据是多个 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 吧:

 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 

该程序已经有很详细的注释了。在该程序中用 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 

这下必须用 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 
 完整的源程序可到以下地址下载:
http://www.cnblogs.com/Files/skyivben/OpenXmlTest-src.7z
编译后的可执行文件的下载地址:
http://www.cnblogs.com/Files/skyivben/OpenXmlTest-bin.7z
本程序需要 ICSharpCode.SharpZipLib 0.85.4.369,可到以下网址下载:
http://www.icsharpcode.net/OpenSource/SharpZipLib/Download.aspx

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值