1.开始

DocumentFormat.OpenXml是ms官方给一个操作office三大件新版文件格式(.xlsx,.docx,.pptx)的组件;特色是它定义了OpenXml所包含的所有对象(たぶん),能做到精确微调文件内容格式;因此它没有EppPlus那么容易上手,性能也很看使用者的水平。。

DocumentFormat.OpenXml的语法很接近直接操作xml,所以使用它来操作Excel,得先熟悉Excel的xml文档结构:

↑已经忘记从哪里找来的了; WorkbookPart包含4个重要子节点:

  • WorkSheetPart:表格数据内容就在这里面,结构最复杂的部分,Workheet的子节点除了Colmns、SheetData还有合并单元格集合MergeCells(图中缺失);
  • WorkSheet:存放表单id及命名(sheet1, Sheet2...),这里有excel的坑,如果包含多个Sheet直接Sheets.First()有可能获取到最后一张Sheet,最好根据Name来搜索;
  • WorkbootStylePart:存放样式;
  • SharedStringTablePart(上图中缺失):共享字符串集合,字符串默认会存在里面,然后Cell引用其数组下标,这也是类似保存1w行"一二三亖"的.xlsx比.txt小的原因
了解了这些想用DocumentFormat.OpenXml从零开始new一个样式还看得过去的Excel文档依然很麻烦(相信用EppPlus、NPOI也一样),而且OpenXml的同级节点还强调先后顺序,错了用excel打开就会报错,相信没人想这么做;正确的姿势应该是加载模板写入数据另存为,模板中把要用到的样式都定义好,然后用代码拷贝对应单元格的样式。
 
先定义一些扩展方法,不然用起来会很累:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Xml;
  5. using DocumentFormat.OpenXml;
  6. using DocumentFormat.OpenXml.Packaging;
  7. using DocumentFormat.OpenXml.Spreadsheet;
  8.  
  9. namespace EOpenXml
  10. {
  11. public static class OpenXmlExcelExtentions
  12. {
  13. public static Sheet GetSheet(this WorkbookPart workbookPart, string sheetName)
  14. {
  15. return workbookPart.Workbook
  16. .GetFirstChild<Sheets>()
  17. .Elements<Sheet>().Where(s => s.Name == sheetName).FirstOrDefault();
  18. }
  19.  
  20. /// <summary>
  21. /// Given a worksheet and a row index, return the row.
  22. /// </summary>
  23. /// <param name="sheetData"></param>
  24. /// <param name="rowIndex"></param>
  25. /// <returns></returns>
  26. public static Row GetRow(this SheetData sheetData, uint rowIndex)
  27. {
  28. return sheetData.
  29. Elements<Row>().Where(r => r.RowIndex == rowIndex).FirstOrDefault();
  30. }
  31. public static Cell GetCell(this SheetData sheetData, string columnName, uint rowIndex)
  32. {
  33. Row row = GetRow(sheetData, rowIndex);
  34.  
  35. if (row == null)
  36. return null;
  37.  
  38. return row.Elements<Cell>().Where(c => string.Compare
  39. (c.CellReference.Value, columnName +
  40. rowIndex, true) == ).FirstOrDefault();
  41. }
  42.  
  43. // https://msdn.microsoft.com/en-us/library/office/cc861607.aspx
  44. // Given a column name, a row index, and a WorksheetPart, inserts a cell into the worksheet.
  45. // If the cell already exists, returns it.
  46. public static Cell GetOrCreateCell(this SheetData sheetData, string columnName, uint rowIndex)
  47. {
  48. string cellReference = columnName + rowIndex;
  49.  
  50. // If the worksheet does not contain a row with the specified row index, insert one.
  51. Row row;
  52. if (sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).Count() != )
  53. {
  54. row = sheetData.Elements<Row>().Where(r => r.RowIndex == rowIndex).First();
  55. }
  56. else
  57. {
  58. row = new Row() { RowIndex = rowIndex };
  59. sheetData.Append(row);
  60. }
  61.  
  62. return row.GetOrCreateCell(cellReference);
  63. }
  64. public static Cell GetOrCreateCell(this Row row, string cellReference)
  65. {
  66. // If there is not a cell with the specified column name, insert one.
  67. if (row.Elements<Cell>().Where(c => c?.CellReference?.Value == cellReference).Count() > )
  68. {
  69. return row.Elements<Cell>().Where(c => c.CellReference.Value == cellReference).First();
  70. }
  71. else
  72. {
  73. // Cells must be in sequential order according to CellReference. Determine where to insert the new cell.
  74. Cell refCell = null;
  75. foreach (Cell cell in row.Elements<Cell>())
  76. {
  77. if (cell.CellReference.Value.Length == cellReference.Length)
  78. {
  79. if (string.Compare(cell.CellReference.Value, cellReference, true) > )
  80. {
  81. refCell = cell;
  82. break;
  83. }
  84. }
  85. }
  86.  
  87. Cell newCell = new Cell() { CellReference = cellReference };
  88. row.InsertBefore(newCell, refCell);
  89. return newCell;
  90. }
  91. }
  92.  
  93. public static string GetValue(this Cell cell, SharedStringTablePart shareStringPart)
  94. {
  95. if (cell == null)
  96. return null;
  97. string cellvalue = cell.InnerText;
  98. if (cell.DataType != null)
  99. {
  100. if (cell.DataType == CellValues.SharedString)
  101. {
  102. int id = -;
  103. if (Int32.TryParse(cellvalue, out id))
  104. {
  105. SharedStringItem item = GetItem(shareStringPart, id);
  106. if (item.Text != null)
  107. {
  108. //code to take the string value
  109. cellvalue = item.Text.Text;
  110. }
  111. else if (item.InnerText != null)
  112. {
  113. cellvalue = item.InnerText;
  114. }
  115. else if (item.InnerXml != null)
  116. {
  117. cellvalue = item.InnerXml;
  118. }
  119. }
  120. }
  121. }
  122. return cellvalue;
  123. }
  124. public static string GetValue(this Cell cell, string[] shareStringPartValues)
  125. {
  126. if (cell == null)
  127. return null;
  128. string cellvalue = cell.InnerText;
  129. if (cell.DataType != null)
  130. {
  131. if (cell.DataType == CellValues.SharedString)
  132. {
  133. int id = -;
  134. if (Int32.TryParse(cellvalue, out id))
  135. {
  136. cellvalue = shareStringPartValues[id];
  137. }
  138. }
  139. }
  140. return cellvalue;
  141. }
  142.  
  143. public static Cell SetValue(this Cell cell, object value = null, SharedStringTablePart shareStringPart = null, int shareStringItemIndex = -, uint styleIndex = )
  144. {
  145. if (value == null)
  146. {
  147. cell.CellValue = new CellValue();
  148. if (shareStringItemIndex != -)
  149. {
  150. cell.CellValue = new CellValue(shareStringItemIndex.ToString());
  151. cell.DataType = new EnumValue<CellValues>(CellValues.SharedString);
  152. }
  153. }
  154. else if (value is string str)
  155. {
  156. if (shareStringPart == null)
  157. {
  158. cell.CellValue = new CellValue(str);
  159. cell.DataType = new EnumValue<CellValues>(CellValues.String);
  160. }
  161. else
  162. {
  163. // Insert the text into the SharedStringTablePart.
  164. int index = shareStringPart.GetOrInsertItem(str, false);
  165. // Set the value of cell
  166. cell.CellValue = new CellValue(index.ToString());
  167. cell.DataType = new EnumValue<CellValues>(CellValues.SharedString);
  168. }
  169. }
  170. else if (value is int || value is short || value is long ||
  171. value is float || value is double || value is uint ||
  172. value is ulong || value is ushort || value is decimal)
  173. {
  174. cell.CellValue = new CellValue(value.ToString());
  175. cell.DataType = new EnumValue<CellValues>(CellValues.Number);
  176. }
  177. else if (value is DateTime date)
  178. {
  179. cell.CellValue = new CellValue(date.ToString("yyyy-MM-dd")); // ISO 861
  180. cell.DataType = new EnumValue<CellValues>(CellValues.Date);
  181. }
  182. else if (value is XmlDocument xd)
  183. {
  184. if (shareStringPart == null)
  185. {
  186. throw new Exception("Param [shareStringPart] can't be null when value type is XmlDocument.");
  187. }
  188. else
  189. {
  190. int index = shareStringPart.GetOrInsertItem(xd.OuterXml, true);
  191. // Set the value of cell
  192. cell.CellValue = new CellValue(index.ToString());
  193. cell.DataType = new EnumValue<CellValues>(CellValues.SharedString);
  194. }
  195. }
  196.  
  197. if (styleIndex != )
  198. cell.StyleIndex = styleIndex;
  199.  
  200. return cell;
  201. }
  202.  
  203. // https://msdn.microsoft.com/en-us/library/office/gg278314.aspx
  204. // Given text and a SharedStringTablePart, creates a SharedStringItem with the specified text
  205. // and inserts it into the SharedStringTablePart. If the item already exists, returns its index.
  206. public static int GetOrInsertItem(this SharedStringTablePart shareStringPart, string content, bool isXml)
  207. {
  208. // If the part does not contain a SharedStringTable, create one.
  209. if (shareStringPart.SharedStringTable == null)
  210. {
  211. shareStringPart.SharedStringTable = new SharedStringTable();
  212. }
  213.  
  214. int i = ;
  215.  
  216. // Iterate through all the items in the SharedStringTable. If the text already exists, return its index.
  217. foreach (SharedStringItem item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
  218. {
  219. if ((!isXml && item.InnerText == content) || (isXml && item.OuterXml == content))
  220. {
  221. return i;
  222. }
  223.  
  224. i++;
  225. }
  226.  
  227. // The text does not exist in the part. Create the SharedStringItem and return its index.
  228. if (isXml)
  229. shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(content));
  230. else
  231. shareStringPart.SharedStringTable.AppendChild(new SharedStringItem(new Text(content)));
  232. shareStringPart.SharedStringTable.Save();
  233.  
  234. return i;
  235. }
  236. private static SharedStringItem GetItem(this SharedStringTablePart shareStringPart, int id)
  237. {
  238. return shareStringPart.SharedStringTable.Elements<SharedStringItem>().ElementAt(id);
  239. }
  240.  
  241. /// <summary>
  242. /// https://docs.microsoft.com/en-us/office/open-xml/how-to-merge-two-adjacent-cells-in-a-spreadsheet
  243. /// </summary>
  244. /// <param name="worksheet"></param>
  245. /// <returns></returns>
  246. public static MergeCells GetOrCreateMergeCells(this Worksheet worksheet)
  247. {
  248. MergeCells mergeCells;
  249. if (worksheet.Elements<MergeCells>().Count() > )
  250. {
  251. mergeCells = worksheet.Elements<MergeCells>().First();
  252. }
  253. else
  254. {
  255. mergeCells = new MergeCells();
  256.  
  257. // Insert a MergeCells object into the specified position.
  258. if (worksheet.Elements<CustomSheetView>().Count() > )
  259. {
  260. worksheet.InsertAfter(mergeCells, worksheet.Elements<CustomSheetView>().First());
  261. }
  262. else if (worksheet.Elements<DataConsolidate>().Count() > )
  263. {
  264. worksheet.InsertAfter(mergeCells, worksheet.Elements<DataConsolidate>().First());
  265. }
  266. else if (worksheet.Elements<SortState>().Count() > )
  267. {
  268. worksheet.InsertAfter(mergeCells, worksheet.Elements<SortState>().First());
  269. }
  270. else if (worksheet.Elements<AutoFilter>().Count() > )
  271. {
  272. worksheet.InsertAfter(mergeCells, worksheet.Elements<AutoFilter>().First());
  273. }
  274. else if (worksheet.Elements<Scenarios>().Count() > )
  275. {
  276. worksheet.InsertAfter(mergeCells, worksheet.Elements<Scenarios>().First());
  277. }
  278. else if (worksheet.Elements<ProtectedRanges>().Count() > )
  279. {
  280. worksheet.InsertAfter(mergeCells, worksheet.Elements<ProtectedRanges>().First());
  281. }
  282. else if (worksheet.Elements<SheetProtection>().Count() > )
  283. {
  284. worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetProtection>().First());
  285. }
  286. else if (worksheet.Elements<SheetCalculationProperties>().Count() > )
  287. {
  288. worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetCalculationProperties>().First());
  289. }
  290. else
  291. {
  292. worksheet.InsertAfter(mergeCells, worksheet.Elements<SheetData>().First());
  293. }
  294. worksheet.Save();
  295. }
  296. return mergeCells;
  297. }
  298.  
  299. /// <summary>
  300. /// Given the names of two adjacent cells, merges the two cells.
  301. /// Create the merged cell and append it to the MergeCells collection.
  302. /// When two cells are merged, only the content from one cell is preserved:
  303. /// the upper-left cell for left-to-right languages or the upper-right cell for right-to-left languages.
  304. /// </summary>
  305. /// <param name="mergeCells"></param>
  306. /// <param name="cell1Name"></param>
  307. /// <param name="cell2Name"></param>
  308. public static void MergeTwoCells(this MergeCells mergeCells, string cell1Name, string cell2Name)
  309. {
  310. MergeCell mergeCell = new MergeCell() { Reference = new StringValue(cell1Name + ":" + cell2Name) };
  311. mergeCells.Append(mergeCell);
  312. }
  313.  
  314. public static IEnumerable<string> GetItemValues(this SharedStringTablePart shareStringPart)
  315. {
  316. foreach (var item in shareStringPart.SharedStringTable.Elements<SharedStringItem>())
  317. {
  318. if (item.Text != null)
  319. {
  320. //code to take the string value
  321. yield return item.Text.Text;
  322. }
  323. else if (item.InnerText != null)
  324. {
  325. yield return item.InnerText;
  326. }
  327. else if (item.InnerXml != null)
  328. {
  329. yield return item.InnerXml;
  330. }
  331. else
  332. {
  333. yield return null;
  334. }
  335. };
  336. }
  337. public static XmlDocument GetCellAssociatedSharedStringItemXmlDocument(this SheetData sheetData, string columnName, uint rowIndex, SharedStringTablePart shareStringPart)
  338. {
  339. Cell cell = GetCell(sheetData, columnName, rowIndex);
  340. if (cell == null)
  341. return null;
  342. if (cell.DataType == CellValues.SharedString)
  343. {
  344. int id = -;
  345. if (Int32.TryParse(cell.InnerText, out id))
  346. {
  347. SharedStringItem ssi = shareStringPart.GetItem(id);
  348. var doc = new XmlDocument();
  349. doc.LoadXml(ssi.OuterXml);
  350. return doc;
  351. }
  352. }
  353. return null;
  354. }
  355. }
  356. }

2.插入数据

  1. private static void GenerateExcel()
  2. {
  3. using (MemoryStream mem = new MemoryStream())
  4. {
  5. using (var temp = File.OpenRead(@"E:\template.xlsx"))
  6. {
  7. temp.CopyTo(mem);
  8. }
  9.  
  10. using (SpreadsheetDocument doc = SpreadsheetDocument.Open(mem, true))
  11. {
  12. WorkbookPart wbPart = doc.WorkbookPart;
  13. Worksheet worksheet = wbPart.WorksheetParts.First().Worksheet;
  14. //statement to get the sheetdata which contains the rows and cell in table
  15. SheetData sheetData = worksheet.GetFirstChild<SheetData>();
  16.  
  17. SharedStringTablePart shareStringPart;
  18. if (wbPart.GetPartsOfType<SharedStringTablePart>().Any())
  19. shareStringPart = wbPart.GetPartsOfType<SharedStringTablePart>().First();
  20. else
  21. shareStringPart = wbPart.AddNewPart<SharedStringTablePart>();
  22. //假设模板第一行是Title不用动,把要用到的样式都定义在了第二行的单元格里
  23. var secondRow = sheetData.GetRow();
  24. uint[] lineStyles = secondRow.Elements<Cell>().Select(c => c.StyleIndex.Value).ToArray();
  25. sheetData.RemoveChild(secondRow);
  26. //从第二行开始循环插入4列1000数据
  27. uint currentRowIndex = ;
  28. for (int i = ;i<;i++)
  29. {
  30. Row row = new Row();
  31. row.RowIndex = currentRowIndex;//设置行号
  32. row.AppendChild(new Cell().SetValue(, shareStringPart, styleIndex: lineStyles[]));
  33. row.AppendChild(new Cell().SetValue(DateTime.Now, shareStringPart, styleIndex: lineStyles[]));
  34. row.AppendChild(new Cell().SetValue(3.1415926535, shareStringPart, styleIndex: lineStyles[]));
  35. row.AppendChild(new Cell().SetValue("通商宽衣", shareStringPart, styleIndex: lineStyles[]));//这里慢
  36. sheetData.AppendChild(row);
  37. currentRowIndex++;
  38. }
  39. wbPart.Workbook.Save();
  40. doc.SaveAs($@"E:\Temp_{DateTime.Now.ToString("yyMMddHHmm")}.xlsx");
  41. doc.Close();
  42. }
  43. mem.Close();
  44. }
  45. }

以上就生成了一个Excel打开不会报任何格式错误提示的标准.xlsx文件;但有需要优化的地方:在每次插入字符串的时候会去循环共享字符集,调用shareStringPart.GetItemValues().ToArray()可以将集合全部存到数组或Dictionary<string,int>里面会快很多,如果清楚集合内容就不用去判断重复了,当然也可以简单粗暴的保存为CellValues.InlineString,这样在重复字符串比较多的时候两种方式所生成的文件大小会有很大差异

3.快速遍历

  1. public static void Read()
  2. {
  3. using (var sd = SpreadsheetDocument.Open(@"E:\temp.xlsx", false))
  4. {
  5. WorkbookPart wbPart = sd.WorkbookPart;
  6. SharedStringTablePart shareStringPart;
  7. if (wbPart.GetPartsOfType<SharedStringTablePart>().Count() > )
  8. shareStringPart = wbPart.GetPartsOfType<SharedStringTablePart>().First();
  9. else
  10. shareStringPart = wbPart.AddNewPart<SharedStringTablePart>();
  11. string[] shareStringItemValues = shareStringPart.GetItemValues().ToArray();
  12.  
  13. WorksheetPart worksheetPart = wbPart.WorksheetParts.First();
  14. uint dataRowStart = ;//数据开始行
  15. OpenXmlReader reader = OpenXmlReader.Create(worksheetPart);
  16. while (reader.Read())
  17. {
  18. if (reader.ElementType == typeof(Worksheet))
  19. {
  20. reader.ReadFirstChild();
  21. }
  22.  
  23. if (reader.ElementType == typeof(Row))
  24. {
  25. Row r = (Row)reader.LoadCurrentElement();
  26. if (r.RowIndex < dataRowStart)
  27. continue;
  28. foreach (Cell c in r.Elements<Cell>())
  29. {
  30. if (c.CellReference != null && c.CellReference.HasValue)
  31. {
  32. string cv = c.GetValue(shareStringItemValues);
  33. Console.WriteLine(cv);
  34. if (c.CellReference.Value == "B" + r.RowIndex)
  35. Console.WriteLine("刚读取的是B列");
  36. }
  37. }
  38. }
  39. }
  40. sd.Close();
  41. }
  42. }

4.总结

DocumentFormat.OpenXml不友好但操作透明,使用前最好先自行封装下,习惯之后相信能用的很爽。

以上です。

使用DocumentFormat.OpenXml操作Excel文件.xlsx的更多相关文章

  1. C# 操作 Excel 文件(.xls 或 .xlsx)

    在.net中,常用的操作excel文件的方式,有三种: OLE DB的形式, 第三方框架NPOI, Office组件. 总结: 通过对比,在读取大数据量的excel文件,建议用OLE DB的形式,把e ...

  2. java使用Apache POI操作excel文件

    官方介绍 HSSF is the POI Project's pure Java implementation of the Excel '97(-2007) file format. XSSF is ...

  3. 记录python接口自动化测试--把操作excel文件的方法封装起来(第五目)

    前面补充了如何来操作excel文件,这次把如何获取excel文件的sheet对象.行数.单元格数据的方法进行封装,方便后面调用 handle_excel.py# coding:utf-8 import ...

  4. python3:操作excel文件

    前提:自动化接口测试中,可以将用例放在excel中管理.结合实际情况讲解如何操作excel文件 1.安装xlrd:pip install xlrd 2.导入模块:import xlrd 3.打开Exc ...

  5. python 操作Excel文件

    1   安装xlrd.xlwt.xlutils cmd下输入: pip install xlrd        #读取excel pip install xlwt        #写入excel pi ...

  6. C#项目中操作Excel文件——使用NPOI库

    转载自:http://blog.csdn.net/dcrmg/article/details/52356236# 感谢-牧野- 实际C#项目中经常会涉及到需要对本地Excel文件进行操作,特别是一些包 ...

  7. C#使用oledb操作excel文件的方法

    本文实例讲述了C#使用oledb操作excel文件的方法.分享给大家供大家参考.具体分析如下: 不管什么编程语言都会提供操作Excel文件的方式,C#操作Excel主要有以下几种方式: 1.Excel ...

  8. java使用POI操作excel文件,实现批量导出,和导入

    一.POI的定义 JAVA中操作Excel的有两种比较主流的工具包: JXL 和 POI .jxl 只能操作Excel 95, 97, 2000也即以.xls为后缀的excel.而poi可以操作Exc ...

  9. 用Python的pandas框架操作Excel文件中的数据教程

    用Python的pandas框架操作Excel文件中的数据教程 本文的目的,是向您展示如何使用pandas 来执行一些常见的Excel任务.有些例子比较琐碎,但我觉得展示这些简单的东西与那些你可以在其 ...

随机推荐

  1. mapreduce 读写Parquet格式数据 Demo

    import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs ...

  2. maker使用说明书

    1.以自带的示例数据为例 dpp_contig.fasta dpp_est.fasta dpp_protein.fasta te_proteins.fasta 2.生成控制文件 控制文件是特定于运行的 ...

  3. 【转】AVL之C++实现

    AVL树的介绍 AVL树是高度平衡的而二叉树.它的特点是:AVL树中任何节点的两个子树的高度最大差别为1. 上面的两张图片,左边的是AVL树,它的任何节点的两个子树的高度差别都<=1:而右边的不 ...

  4. C语言中宏的相关知识

    2019/04/27 16:02 1.宏的定义:宏定义就是预处理命令的一种,它允许用一个标识符来表示一个字符串.格式如下: #define name(宏名) stuff(字符串) 本质就是使用宏名去替 ...

  5. AppDomin学习与分享

    最近学习并分享了appdomin的一些东西,以前没怎么记录过,现在记录一下吧2016-03-17 什么是AppDomin •全称:Application Domin(应用程序域) •定义:AppDom ...

  6. 2018的Java

    少用复制黏贴 程序员很多时候都习惯复制黏贴,这里复制一点,那里复制一点,拼拼凑凑的搞出了一段代码.这是一种常态,毕竟没有必要重复造轮子,在开发的时候,讲究的是效率,讲究速度,有时候也是不得不这样做.但 ...

  7. Spring JDBC最佳实践(2)

    原文地址:https://my.oschina.net/u/218421/blog/38576 使用DataSourceUtils进行Connection的管理由上节代码可知,JdbcTemplate ...

  8. django 中进程监控工具flower的使用

    工程结构:请参考https://www.cnblogs.com/apple2016/p/11425307.html flower官方文档:https://flower.readthedocs.io/e ...

  9. Metasploaitable和侦察httrack-安全牛课堂网络安全之Web渗透测试练习记录

    环境配置 首先在网上下载kali的镜像以及Metasploaitable虚拟机,打开按照网上教程安装好kali虚拟机,另一边打开Metasploaitable虚拟机,进入输入初始账户msfadmin, ...

  10. unity的yield

    这里说的是Unity通过StartCoroutine开启IEnumerator协程里的yield相关 1.yield return 0,yield return null 等待下一帧接着执行下面的内容 ...