守望者--AIR技术交流

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

搜索
热搜: ANE FlasCC 炼金术
查看: 1243|回复: 0

[项目讨论] 用 XmlReader 读取 Excel 2007 文件

[复制链接]
  • TA的每日心情
    擦汗
    2018-4-10 15:18
  • 签到天数: 447 天

    [LV.9]以坛为家II

    1742

    主题

    2094

    帖子

    13万

    积分

    超级版主

    Rank: 18Rank: 18Rank: 18Rank: 18Rank: 18

    威望
    562
    贡献
    29
    金币
    51757
    钢镚
    1422

    开源英雄守望者

    发表于 2015-3-31 15:27:53 | 显示全部楼层 |阅读模式
    在我最近开发的一个网页查询的项目中,客户提供的数据是多个 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. namespace Skyiv.OfficeHelper
    6. {
    7.    /// <summary>
    8.    /// Excel 2007 文件
    9.    /// </summary>
    10.    sealed partial class XlsxFile : IDisposable
    11.    {
    12.      string fileName; // Excel 2007 文件的文件名
    13.      Sheet[] sheets;  // Excel 2007 文件的各工作表
    14.      FileStream fileStream { get { return new FileStream(fileName, FileMode.Open, FileAccess.Read); } }

    15.      /// <summary>
    16.      /// Excel 2007 文件的构造函数
    17.      /// </summary>
    18.      /// <param name="fileName">Excel 2007 文件的文件名</param>
    19.      public XlsxFile(string fileName)
    20.      {
    21.        this.fileName = fileName;
    22.      }

    23.      /// <summary>
    24.      /// Excel 2007 文件的各工作表
    25.      /// </summary>
    26.      public Sheet[] Sheets
    27.      {
    28.        get
    29.        {
    30.          if (sheets == null)
    31.          {
    32.            using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/workbook.xml"))
    33.            {
    34.              // xl/workbook.xml 文件的内容举例如下:
    35.              // <workbook>
    36.              //   <sheets>
    37.              //     <sheet name="好人" sheetId="1" r:id="rId1" />
    38.              //     <sheet name="坏人" sheetId="2" r:id="rId2" />
    39.              //   </sheets>
    40.              // </workboo>
    41.              XmlDocument xmlDocument = new XmlDocument();
    42.              xmlDocument.Load(zs);
    43.              XmlNodeList elms = xmlDocument.DocumentElement["sheets"].ChildNodes;
    44.              sheets = new Sheet[elms.Count];
    45.              for (int i = 0; i < elms.Count; i++)
    46.              {
    47.                XmlAttributeCollection attrs = elms[i].Attributes;
    48.                sheets[i] = new Sheet(attrs["name"].Value, GetXmlFileName(attrs["r:id"].Value), SharedStrings, fileStream);
    49.              }
    50.            }
    51.          }
    52.          return sheets;
    53.        }
    54.      }

    55.      /// <summary>
    56.      /// 根据“标识”给出表示工作表的 XML 文件名
    57.      /// </summary>
    58.      /// <param name="id">标识</param>
    59.      /// <returns>表示工作表的 XML 文件名</returns>
    60.      string GetXmlFileName(string id)
    61.      {
    62.        string value;
    63.        using (Stream zs = Zip.GetZipInputStream(fileStream, "xl/_rels/workbook.xml.rels"))
    64.        {
    65.          // xl/_rels/workbook.xml.rels 文件的内容举例如下:
    66.          // <Relationships>
    67.          //   <Relationship Id="rId1" Target="worksheets/sheet1.xml" />
    68.          //   <Relationship Id="rId2" Target="worksheets/sheet2.xml" />
    69.          // </Relationships>
    70.          XmlDocument xmlDocument = new XmlDocument();
    71.          xmlDocument.Load(zs);
    72.          value = XmlHelper.GetElementById(xmlDocument, id).Attributes["Target"].Value;
    73.        }
    74.        return value;
    75.      }

    76.      public void Dispose()
    77.      {
    78.        if (sheets == null) return;
    79.        foreach (Sheet sheet in sheets) sheet.Dispose();
    80.      }
    81.    }
    82. }
    复制代码
    该程序已经有很详细的注释了。在该程序中用 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. namespace Skyiv.OfficeHelper
    6. {
    7.    partial class XlsxFile
    8.    {
    9.      string[] sharedStrings;

    10.      /// <summary>
    11.      /// Excel 2007 文件中各工作表共享的字符串
    12.      /// </summary>
    13.      string[] SharedStrings
    14.      {
    15.        get
    16.        {
    17.          if (sharedStrings == null)
    18.          {
    19.            Stream zs = null;
    20.            try
    21.            {
    22.              zs = Zip.GetZipInputStream(fileStream, "xl/sharedStrings.xml"); // 可能引发(FileNotFoundException)
    23.              // xl/sharedStrings.xml 文件的内容举例如下:
    24.              // <sst count="56" uniqueCount="2">
    25.              //   <si><t>任盈盈</t><phoneticPr fontId="1" type="noConversion" /></si>
    26.              //   <si><t /></si>
    27.              // </sst>
    28.              using (XmlReader reader = XmlReader.Create(zs))
    29.              {
    30.                while (reader.Read()) if (reader.IsStartElement("sst")) break;
    31.                sharedStrings = new string[Convert.ToInt32(reader.GetAttribute("uniqueCount"))];
    32.                for (int count = 0; ; count++)
    33.                {
    34.                  reader.Read();                                               // 执行后(reader)的值: <si> or </sst>
    35.                  if (!reader.IsStartElement("si")) break;
    36.                  reader.ReadStartElement("si");                               // 执行后(reader)的值: <t>  or <t />
    37.                  sharedStrings[count] = reader.ReadElementString("t").Trim(); // 执行后(reader)的值: </si> or <与<t>同级的元素>
    38.                  if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("t"); // 执行后(reader)的值: </si>
    39.                }
    40.              }
    41.            }
    42.            catch (FileNotFoundException)
    43.            {
    44.              sharedStrings = new string[0]; // 如果没有找到 xl/sharedStrings.xml 文件
    45.            }
    46.            finally
    47.            {
    48.              if (zs != null) zs.Close();
    49.            }
    50.          }
    51.          return sharedStrings;
    52.        }
    53.      }
    54.    }
    55. }
    复制代码
    这下必须用 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. namespace Skyiv.OfficeHelper
    7. {
    8.    partial class XlsxFile
    9.    {
    10.      /// <summary>
    11.      /// Execl 2007 文件中的工作表
    12.      /// </summary>
    13.      public sealed class Sheet : IDisposable
    14.      {
    15.        string[] sharedStrings; // 各工作表共享的字符串
    16.        Stream stream;          // 用于读取本工作表的文件流
    17.        XmlReader reader;       // 用于读取本工作表的 XML 数据读取器
    18.        string dimension;       // 本工作表的范围,如:“A1”、“B2:C4”
    19.        int rowCount;           // 本工作表的有效行数
    20.        string name;            // 本工作表的名称

    21.        public string Dimension { get { return dimension; } }
    22.        public int RowCount { get { return rowCount; } }
    23.        public string Name { get { return name; } }

    24.        /// <summary>
    25.        /// 表示 Excel 2007 文件中的工作表的类的构造函数
    26.        /// </summary>
    27.        /// <param name="name">本工作表的名称</param>
    28.        /// <param name="fileName">工作表的 XML 文件名</param>
    29.        /// <param name="sharedStrings">各工作表共享的字符串</param>
    30.        /// <param name="fileStream">表示整个 Excel 2007 文件的流</param>
    31.        public Sheet(string name, string fileName, string[] sharedStrings, Stream fileStream)
    32.        {
    33.          this.name = name;
    34.          this.sharedStrings = sharedStrings;
    35.          stream = Zip.GetZipInputStream(fileStream, "xl/" + fileName);
    36.          reader = XmlReader.Create(stream);
    37.          while (reader.Read()) if (reader.IsStartElement("dimension")) break;
    38.          dimension = reader.GetAttribute("ref"); // 本工作表的范围:<dimension ref="B2:C4" />
    39.          rowCount = GetRowCount(dimension); // 根据工作表的范围计算有效行数
    40.          while (reader.Read()) if (reader.IsStartElement("sheetData")) break;
    41.        }

    42.        /// <summary>
    43.        /// 读取本工作表的中一行
    44.        /// </summary>
    45.        /// <returns>读取的行的各字段的内容。如果已经没有可读的行则返回 null。</returns>
    46.        public string[] ReadRow()
    47.        {
    48.          // 表示工作表的 XML 文件(如:xl/worksheets/sheet1.xml)的内容举例如下:
    49.          // <worksheet>
    50.          //   <dimension ref="B2:C4" />
    51.          //   <sheetData>
    52.          //     <row r="2" spans="2:3" />
    53.          //     <row r="4" spans="2:3">
    54.          //       <c r="B4" />
    55.          //       <c r="C4" t="s"><v>1</v></c>
    56.          //     </row>
    57.          //   </sheetData>
    58.          // </worksheet>
    59.          // 注意:在该 XML 文件中可能省略某些空行和空单元格,而本方法忽略这些空行和空单元格。
    60.          // 但本方法不忽略 XML 文件中的空行“<row />”和空单元格“<c />”。
    61.          if (!reader.IsStartElement("row")) reader.Read();
    62.          if (!reader.IsStartElement("row")) return null; // 没有可读的行
    63.          List<string> list = new List<string>();
    64.          for (; ; )
    65.          {
    66.            reader.Read(); // 执行后(reader)的值: <c> or </row> or (other for <row />)
    67.            if (!reader.IsStartElement("c")) break; // 没有可读的单元格
    68.            if (reader.IsEmptyElement) list.Add(""); // 空单元格“<c />”
    69.            else                                     // “<c><v>1</v></c>”
    70.            {
    71.              string attr = reader.GetAttribute("t"); // 如果“<c>”元素的“t”属性不为空,则“<v>”元素的值指向各工作表共享的字符串
    72.              reader.ReadStartElement("c");                            // 执行后(reader)的值: <v> or <v />
    73.              list.Add(GetValue(attr, reader.ReadElementString("v"))); // 执行后(reader)的值: </c> or <与<v>同级的元素>
    74.              if (reader.NodeType != XmlNodeType.EndElement) reader.ReadToNextSibling("v"); // 执行后(reader)的值: </c>
    75.            }
    76.          }
    77.          return list.ToArray();
    78.        }

    79.        /// <summary>
    80.        /// 根据工作表的范围计算有效行数
    81.        /// </summary>
    82.        /// <param name="dimension">工作表的范围,如:“A1”、“B2:C4”</param>
    83.        /// <returns>有效行数</returns>
    84.        int GetRowCount(string dimension)
    85.        {
    86.          if (string.IsNullOrEmpty(dimension)) return -1;
    87.          string[] ss = dimension.Split(':');
    88.          if (ss.Length == 1) return 1;
    89.          if (ss.Length != 2) return -1;
    90.          return GetRowNumber(ss[1]) - GetRowNumber(ss[0]) + 1;
    91.        }

    92.        /// <summary>
    93.        /// 根据单元格的坐标计算单元格的行号
    94.        /// </summary>
    95.        /// <param name="str">单元格的坐标,如“C4”</param>
    96.        /// <returns>单元格的行号</returns>
    97.        int GetRowNumber(string str)
    98.        {
    99.          int i;
    100.          for (i = 0; i < str.Length; i++) if (char.IsDigit(str, i)) break;
    101.          return int.Parse(str.Substring(i));
    102.        }

    103.        /// <summary>
    104.        /// 给出单元格的值,可能是各工作表共享的字符串
    105.        /// </summary>
    106.        /// <param name="attr">“<c>”元素的“t”属性的值</param>
    107.        /// <param name="value">“<v>”元素的值</param>
    108.        /// <returns>单元格的值</returns>
    109.        string GetValue(string attr, string value)
    110.        {
    111.          if (attr != null)
    112.          {
    113.            int index;
    114.            if (!int.TryParse(value, out index)) throw new Exception("共享字符串索引(" + value + ")必须是整数");
    115.            if (index < 0 || index >= sharedStrings.Length) throw new Exception("共享字符串索引("
    116.              + index + ")必须在(0)到(" + (sharedStrings.Length - 1) + ")之间");
    117.            value = sharedStrings[index];
    118.          }
    119.          return value;
    120.        }

    121.        public void Dispose()
    122.        {
    123.          if (reader != null) reader.Close();
    124.          if (stream != null) stream.Close();
    125.          reader = null;
    126.          stream = null;
    127.        }
    128.      }
    129.    }
    130. }
    复制代码
    在这个程序中也是用 XmlReader 类来读取 xl/worksheets/sheet1.xml 文件。






    完整的源程序可到以下地址下载:

    http://files.cnblogs.com/skyivben/OpenXmlTest-src.7z

    编译后的可执行文件的下载地址:

    http://files.cnblogs.com/skyivben/OpenXmlTest-bin.7z

    本程序需要 ICSharpCode.SharpZipLib 0.85.4.369,可到以下网址下载:

    http://www.icsharpcode.net/OpenSource/SharpZipLib/Download.aspx




    本文来自:http://www.cnblogs.com/skyivben/archive/2007/09/23/903582.html

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有帐号?立即注册

    x
    守望者AIR技术交流社区(www.airmyth.com)
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    
    关闭

    站长推荐上一条 /4 下一条

    QQ|手机版|Archiver|网站地图|小黑屋|守望者 ( 京ICP备14061876号

    GMT+8, 2019-10-23 17:16 , Processed in 0.059255 second(s), 34 queries .

    守望者AIR

    守望者AIR技术交流社区

    本站成立于 2014年12月31日

    快速回复 返回顶部 返回列表