程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> 用 XmlReader 讀取 Excel 2007 文件 實現代碼

用 XmlReader 讀取 Excel 2007 文件 實現代碼

編輯:關於C#
 

在我最近開發的一個網頁查詢的項目中,客戶提供的數據是多個 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 


在這個程序中也是用 XmlReader 類來讀取 xl/worksheets/sheet1.xml 文件。
 

 
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved