還有什么不能做?——細談在C#中讀寫Excel系列文章之二


  在前一篇文章中我談到了Excel 2007以后版本的文件其實就是一個zip壓縮包,里面包含了該Excel文件的所有內容和使用的資源,大部分文件都是以XML的形式存放的。再來回顧一下解壓之后的文件夾結構,

  其中比較重要的是xl文件夾,里面包含了一些很有用的信息。sharedStrings.xml文件存放了Excel中所有的字符串數據。

  worksheets文件夾中存放了所有的工作表信息,如果你的Excel文件中包含多個工作表,則這里你會發現有多個與工作表對應的xml文件。

  來看一下sharedStrings.xml和工作表xml文件之間的關系。首先找到對應的工作表xml文件,如上圖所示文件夾中的sheet3.xml,找到該xml文件的sheetData節點,下面包含有row節點,用來表示該工作表中的每一行,屬性r代表行號。子節點c表示了該行中的每一個單元格,屬性r表示單元格在Excel工作表的位置,如“A1”。注意屬性t="s",它表示該單元格的內容是存儲在sharedStrings.xml文件中的,其子節點v的值用來說明該值在sharedStrings.xml文檔中的位置。

  sharedStrings.xml文件中包含有很多si節點,其中存放的就是Excel文件里各個工作表中的字符串數據。上圖中v節點的值代表的就是sharedStrings.xml文件中si節點的序號,該序號從1開始計數。通過這個關系,我們就可以在sharedStrings.xml文件中找到我們想要的數據了。

  弄清楚這個關系之后,我們再來看看如何通過代碼取到我們想要的數據。從.NET Framework 3.0之后提供了一個新的類庫可以幫助我們直接讀取Excel包里面的文件。如果你的.NET工程是普通的Windows應用程序,那可以直接在工程中引用WindowsBase.dll類庫。如果你的工程是非Windows應用程序,如Silverlight,則無法引用該類庫,在后續的文章中我還會再介紹如何在Silverlight工程中讀取Excel包里的文件。

  還是直接上代碼吧,寫太多文字大家看得也累!

  1 using System;
  2 using System.Collections.Generic;
  3 using System.ComponentModel;
  4 using System.Data;
  5 using System.Drawing;
  6 using System.Linq;
  7 using System.Text;
  8 using System.Windows.Forms;
  9 using System.IO;
 10 using System.IO.Packaging;
 11 using System.Xml;
 12 using System.Xml.Linq;
 13 
 14 namespace ExcelPackageOperator
 15 {
 16     public partial class Form1 : Form
 17     {
 18         public Form1()
 19         {
 20             InitializeComponent();
 21 
 22             List<Cell> parsedCells = new List<Cell>();
 23             string fileName = @"C:\Users\jaxu\Desktop\Example.xlsx"; 
 24 
 25             using(Package xlsxPackage = Package.Open(fileName, FileMode.Open, FileAccess.Read))
 26             {
 27                 // load sharedStrings.xml document from Excel package
 28                 PackagePartCollection allParts = xlsxPackage.GetParts();
 29                 PackagePart sharedStringsPart = (from part in allParts
 30                                                  where part.Uri.OriginalString.Equals("/xl/sharedStrings.xml")
 31                                                  select part).Single();
 32 
 33                 // load to XElement from sharedStrings.xml document
 34                 XElement sharedStringsElement = XElement.Load(XmlReader.Create(sharedStringsPart.GetStream())); 
 35 
 36                 // read all fixed text to a dictionary from sharedStrings document
 37                 Dictionary<int, string> sharedStrings = new Dictionary<int, string>(); 
 38                 ParseSharedStrings(sharedStringsElement, sharedStrings); 
 39                 
 40                 // get worksheet 1
 41                 XElement worksheetElement = GetWorksheet(3, allParts); 
 42                 
 43                 IEnumerable<XElement> cells = from c in worksheetElement.Descendants(ExcelNamespaces.excelNamespace + "c") 
 44                                               select c; 
 45                 
 46                 foreach (XElement cell in cells) 
 47                 {
 48                     if (cell.HasElements && cell.Attribute("t") != null && cell.Attribute("t").Value.Equals("s"))
 49                     {
 50                         string cellPosition = cell.Attribute("r").Value;
 51                         int index = IndexOfNumber(cellPosition);
 52                         string column = cellPosition.Substring(0, index);
 53                         int row = Convert.ToInt32(cellPosition.Substring(index, cellPosition.Length - index));
 54                         int valueIndex = Convert.ToInt32(cell.Descendants(ExcelNamespaces.excelNamespace + "v").Single().Value);
 55 
 56                         parsedCells.Add(new Cell(column, row, sharedStrings[valueIndex]));
 57                     }
 58                 } 
 59             }
 60             
 61             //From here is additional code not covered in the posts, just to show it works
 62             foreach (Cell cell in parsedCells)            
 63             {
 64                 this.textBox1.Text += cell.ToString() + "\r\n";
 65                 //Console.WriteLine(cell);            
 66             }
 67         }
 68 
 69         private void ParseSharedStrings(XElement SharedStringsElement, Dictionary<int, string> sharedStrings) 
 70         { 
 71             IEnumerable<XElement> sharedStringsElements = from s in SharedStringsElement.Descendants(ExcelNamespaces.excelNamespace + "si") 
 72                                                           select s; 
 73             
 74             int Counter = 0; 
 75             foreach (XElement sharedString in sharedStringsElements) 
 76             { 
 77                 sharedStrings.Add(Counter, sharedString.Value); Counter++; 
 78             } 
 79         }         
 80         
 81         private XElement GetWorksheet(int worksheetID, PackagePartCollection allParts) 
 82         { 
 83             PackagePart worksheetPart = (from part in allParts 
 84                                          where part.Uri.OriginalString.Equals(String.Format("/xl/worksheets/sheet{0}.xml", worksheetID)) 
 85                                          select part).Single(); 
 86             return XElement.Load(XmlReader.Create(worksheetPart.GetStream())); 
 87         }
 88         
 89         private int IndexOfNumber(string value) 
 90         { 
 91             for (int counter = 0; counter < value.Length; counter++) 
 92             { 
 93                 if (char.IsNumber(value[counter])) 
 94                 { 
 95                     return counter; 
 96                 }
 97             }
 98             
 99             return 0; 
100         }
101     }
102 
103     internal static class ExcelNamespaces 
104     { 
105         internal static XNamespace excelNamespace = XNamespace.Get("http://schemas.openxmlformats.org/spreadsheetml/2006/main"); 
106         internal static XNamespace excelRelationshipsNamepace = XNamespace.Get("http://schemas.openxmlformats.org/officeDocument/2006/relationships");    
107     }
108 
109     public class Cell 
110     {
111         public Cell(string column, int row, string data) 
112         {
113             this.Column = column; 
114             this.Row = row; 
115             this.Data = data; 
116         }
117         
118         public override string ToString() 
119         {
120             return string.Format("{0}:{1} - {2}", Row, Column, Data); 
121         }
122         
123         public string Column 
124         {
125             get; set; 
126         }
127         
128         public int Row 
129         {
130             get; set; 
131         }
132         
133         public string Data 
134         {
135             get; set; 
136         }
137     }
138 }

  1. 使用Package類直接打開Excel文件,該類屬於System.IO.Packing命名空間,必須確保工程中引用了WindowsBase.dll類庫。

  2. 通過GetParts()方法讀取包里面的內容。注意LINQ語句取到的是sharedStrings.xml文件。

  3. 將讀取到的sharedStrings.xml文件加載到XElement對象中,然后通過PaseSharedStrings這個自定義函數讀取里面所有的si子節點並存放到一個字典里。

  4. 自定義函數GetWorksheet用來將指定的工作表xml從包中讀取出來加載到XElement對象中。

  5. 程序接下來的部分就是遍歷工作表xml中所有的節點c,並從屬性r中找出單元格的行編號和列編號。然后將准備好的數據存儲到parsedCells集合中,該集合的類型是一個自定義的Cell對象,該對象包含三個屬性(行編號、列編號和數據內容)和一個改寫的ToString()方法,該方法用來定義數據輸出格式。

  注意數據是從字典中取出來的,字典的Key值就是節點r的子節點v的值!還記得文章一開始我們介紹的sharedStrings.xml文件和工作表xml文件之間的關系嗎?這里就是借用的這個關系。另外在遍歷工作表xml中的節點c時是過濾掉了那些不包含有屬性t="s"的節點的,因為這些節點直接存儲了數據,而不需要再從sharedStrings.xml去取。

  6. 輸出parsedCells集合中的數據。

  好了,程序的結構就是這樣。代碼可能稍稍有點長,只看主要的部分,其實還是很簡單的:借用WindowsBase類庫中的System.IO.Packing.Package對象來讀取Excel包里的內容,然后找出sharedStrings.xml文件中的所有字符串數據並存放到一個字典中,遍歷你想要操作的工作表xml文件,按單元格的位置從字典中找出對應的數據顯示出來。

  不過還是有一個遺憾。在使用.NET現有的功能時總會遇到一些問題!該對象並沒有提供對Excel文件的修改和保存功能,至少我沒有發現。也就是說它可以允許我們在不調用COM組件的情況下非常方便地讀取Excel文件中的數據,包括字符串和數字。另外還有一個問題就是該類庫不能在Silverlight工程中引用,如果你剛好想在Silverlight工程中使用該功能那么只能想其它的方法了。不過有一個問題值得思考,那就是既然Excel是一個zip壓縮包,而且里面大部分的數據都是以xml文件的格式存儲的,我們何不自己寫代碼解壓然后讀取或者修改xml文件呢?甚至我們還可以把修改之后的文件重新打包還原成一個新的Excel文件,這樣的話我們就可以實現Excel文件內容的修改,而且也可以在Silverlight工程中使用了。

  我會在接下來的文章中介紹如何把Excel文件作為zip壓縮包來進行操作,包括讀取和修改。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM