用Open XML SDK读取Excel

news/2024/7/21 4:57:15 标签: Excel, XML, Linq, Office, 工作
  Excel的便捷使得其在非开发人员的办公中非常流行,而Excel确实也提供了很多有用的功能。很多时候我们还需要以Excel为数据源来进行处理或者将Excel作为模板来生成一些报表。在Open XML SDK没出来之前,我们大多采用引用Office类库的方法来做处理,但这样的操作显得很麻烦。而Open XML SDK的出现,让我们可以以一个很自然的方式来处理这些数据,本文我们就一起了解一下如何使用Open XML SDK来通过LINQ to XML的方式操作Excel

这些代码是建立在Open XML SDK CTP 2基础上的,使用前请先下载Open XML Format SDK 2.0。点击这里下载。SDK默认会安装在C:\Program Files (x86)\Open XML Format SDK\V2.0 (64bit)目录下,lib子目录下的DocumentFormat.OpenXml.dll必须被引用到项目中。

Excel也罢,word也罢,他们都是通过Open XML的标准来组织特定标记的。其实,你只要理解这些标记的含义,你可以不用任何工具自己来解析XML得到你想要的内容和格式。而Open XML SDK提供给我们的是更统一的解析方式。通过下边传统的DOM解析,你可以得到一个Excelworksheet集合。

public static List<String> GetSheets(String strFileName)

{

    //  Fill this collection with a list of all the sheets.

    List<String> sheets = new List<String>();

 

    using (SpreadsheetDocument xlPackage = SpreadsheetDocument.Open(strFileName, false))

    {

        WorkbookPart workbook = xlPackage.WorkbookPart;

        Stream workbookstr = workbook.GetStream();

        XmlDocument doc = new XmlDocument();

        doc.Load(workbookstr);

 

        XmlNamespaceManager nsManager = new XmlNamespaceManager(doc.NameTable);

        nsManager.AddNamespace("default", doc.DocumentElement.NamespaceURI);

        XmlNodeList nodelist = doc.SelectNodes("//default:sheets/default:sheet", nsManager);

 

        foreach (XmlNode node in nodelist)

        {

            String sheetName = String.Empty;

            sheetName = node.Attributes["name"].Value;

            sheets.Add(sheetName);

        }

    }

 

    return sheets;

}

  对于每一个工作簿而言,Open XML Format SDK将它以不同对象之间的关系来组织。在下边的图中你可以看到这些内嵌的对象间的关系。需要说明的是,他们都代表在Excel中不同的应用,例如Columns,你可以通过它得到你的工作簿中的列。TablePart则列举了在工作簿中以Table(可以理解为以数据表,具有过滤,排序,汇总等功能)方式展现的数据区域。

而我们可能更关心的是如何来得到行、列单元格内的值。当然,worksheet除了这些对象(关系)集合外,它也通过直观的行(Row)、列(Cell)来组织内容区域。通过LINQ to XML我们可以很容易的通过Descendents来得到。

IEnumerable<Sheet> sheets = document.WorkbookPart.Workbook.Descendants<Sheet>().Where(s => s.Name == strSheet);

if (sheets.Count() == 0)

{

    // The specified worksheet does not exist.

    return null;

}

 

WorksheetPart worksheetPart = (WorksheetPart)document.WorkbookPart.GetPartById(sheets.First().Id);

Worksheet worksheet = worksheetPart.Worksheet;

 

//Ignore row header

IEnumerable<Row> rows = worksheet.Descendants<Row>();

foreach (Row row in rows)

{

    foreach (Cell cell in row)

    {

    ……

}

}

  能够对RowCell进行遍历,这就容易多了。你可以很容易的取出某一行某一列,或者所有列的数据并输出(当然,同等的道理,你也可以写入),操作起来是不是很方便呢?但不要忘记了,你很难得到cell的值:cell.InnerText并不是在任何时候都有效的。对于s类型的列(SharedStringTable类型),它的值是存储在子元素内的。

public static String GetValue(Cell cell, SharedStringTablePart stringTablePart)

{

    if (cell.ChildElements.Count == 0)

        return null;

    //get cell value

    String value = cell.CellValue.InnerText;

    //Look up real value from shared string table

    if ((cell.DataType != null) && (cell.DataType == CellValues.SharedString))

        value = stringTablePart.SharedStringTable

        .ChildElements[Int32.Parse(value)]

        .InnerText;

    return value;

}

  这时,你可以通过String columnValue = GetValue(cell, tablePart);的方式来取得单元格值。tablePart就是WorkbookPart.SharedStringTablePart,它存储了所有以SharedStringTable方式存储数据的子元素。

再扩展一下如果你想将一个Excel工作簿以强类型展示,那该如何做呢?列明,每一个行代表一个对象元素,通过反射来来对对象赋值。请注意在工作簿中并不是每个列都能和你的类属性对应的,所以必须判断。当然,你也可以通过中间元素来产生映射扩展。

 

//get SharedStringTablePart to get the cell value.

SharedStringTablePart tablePart = document.WorkbookPart.SharedStringTablePart;

 

//Column headers

String[] cellHeaders = null;

String[] cellValues = null;

 

//Ignore row header

IEnumerable<Row> rows = worksheet.Descendants<Row>();

foreach (Row row in rows)

{

    if (row.RowIndex == 1)

    {

        cellHeaders = new String[row.Count()];

    }

    cellValues = new String[row.Count()];

    int i = 0;

 

    foreach (Cell cell in row)

    {                       

        String columnValue = GetValue(cell, tablePart);

 

        //The first row is header

        if (row.RowIndex == 1)

        {

            cellHeaders[i] = columnValue;

        }

        else

        {

            cellValues[i] = columnValue;

        }

        i++;

    }

    if (row.RowIndex > 1)

    {

        products.Add(ProductConverter.Convert(cellValues, cellHeaders));

    }

}

对最终的单元格值集合到Product对象的转换,我们通过ProductConverter类来完成。在这里,你可以通过反射来完成,但枚举出所有你可能用到的类型是你不得不面对的问题。

 

foreach (PropertyInfo pi in product.GetType().GetProperties())

{

    for (int i = 0; i < cellHeader.Length; i++)

    {

        if (pi.Name.Equals(cellHeader[i], StringComparison.OrdinalIgnoreCase))

        {

            //get property type

            String propertyType = pi.PropertyType.Name;

            switch (propertyType)

            {

                case "Int32":

                    pi.SetValue(product, int.Parse(cellValues[i]), null);

                    break;

                case "DateTime":

                    pi.SetValue(product, System.DateTime.Parse(cellValues[i]), null);

                    break;

                case "Decimal":

                    pi.SetValue(product, Decimal.Parse(cellValues[i]), null);

                    break;

                case "Double":

                    pi.SetValue(product, Double.Parse(cellValues[i]), null);

                    break;

                case "String":

                    pi.SetValue(product, cellValues[i], null);

                    break;

            }

            break;

        }

    }

}

通过将数据展现到UI上,你可以验证你的工作 是否成功:

List<Product> products = SpreadSheetFunction.GetProducts(strFileName, "Products");

this.dataGridView1.DataSource = products;

其实对于Excel中的Table可能更有意思。因为你可以通过它来实现过滤,排序,汇总,你会感觉它特别方便(起码比Reporting Service来得快多了)。我们会再介绍如何通过更简单的办法来对Excel Table操作。


http://www.niftyadmin.cn/n/594534.html

相关文章

计算机导论上机实训实验结果与总结,计算机上机实训的总结参考

计算机上机实训的总结参考一&#xff0e;实习目的&#xff1a;本次CAD上机实习是为了利用计算机绘图软件autoCAD进行工程制图&#xff0c;把握auto CAD用于工程制图的基本操作&#xff0c;掌握该软件的基本使用方法&#xff0c;提高学生运用所学知识解决问题的能力&#xff0c;…

php get的用法,PHP SplFileInfo getPath()用法及代码示例

SplFileInfo::getPath()函数是PHP中标准PHP库(SPL)的内置函数&#xff0c;用于返回不带文件名的路径。用法:string SplFileInfo::getPath( void )参数&#xff1a;该函数不接受任何参数。返回值&#xff1a;此函数返回文件路径(不包括文件名)。以下示例程序旨在说明PHP中的SplF…

python学习笔记全过程_Python学习笔记(第七章)

浅谈返回值学过c语言的同学&#xff0c;最常见的返回值是这样的return 0;我们长谈返回值的类型是整型&#xff0c;我们的意思是指它会返回一个整型的值&#xff0c;前面提到Python会动态分配类型&#xff0c;这里也一样Python能动态的确定函数的类型&#xff0c;而且函数还能返…

java的urlRewriter

现在UrlRewriter技术有两个技术平台的&#xff0c;一个就是在Java方向的&#xff0c;另一个就是.NET方向的。今天我们讲的是Java方向的应用。首先让我们了解它的工作原理&#xff0c;说白了它就是一个简单的过滤器&#xff08;Filter&#xff09; &#xff0c;看看源码你就会很…

10个必需的iOS开发工具和资源

界面总不是一件很容易事&#xff0c;尤其是iPhone/iPad的界面&#xff0c;做过iOS开发的程序员&#xff0c;一定会感到开发iPhone/iPad的界面是一件多么不容易的事。下面的文章来自10 Essential iOS Developer Tools & Resources&#xff0c; 这个文章介绍了十个iOS开发的基…

404页面不能加载css,用css js写的一个404页面模板

如果是thinkphp框架, 先配置config.php 404地址TMPL_EXCEPTION_FILE > ./Application/Home/View/Public/404.htmlCSS代码html {background: #f7f7f7;}body {background: #fff;color: #333;font-family: "MicrosoftYaHei" , "微软雅黑" ,Verdana,Arial;m…

feed新闻阅读器Liferea 1.12.2更新

2019独角兽企业重金招聘Python工程师标准>>> Liferea 1.12.2已经发布&#xff0c;一个新的bug修复版。 引入了插件安装程序&#xff0c;可以轻松发现和安装第三方插件。 Liferea 1.12.2的全面变更包括&#xff1a; 添加一个插件安装程序插件&#xff0c;可以发现并自…

服务器不稳定 如何让百度重新收录网站,只需三个问题就能让被百度K掉的网站重新收录...

本人经营着6个站&#xff0c;其中有3个站有被百度K过的经历&#xff0c;与所有被K过站的站长一样&#xff0c;都有着痛苦的经历。痛定思痛&#xff0c;日子还是要过的&#xff0c;网站还是要做的&#xff0c;怎么让网站重新被百度收录呢?本人最终探索出一套独特的让百度重新收…