XlsxTables.cs 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463
  1. // Copyright (c) Microsoft. All rights reserved.
  2. // Licensed under the MIT license. See LICENSE file in the project root for full license information.
  3. using System;
  4. using System.Collections.Generic;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Xml.Linq;
  8. using DocumentFormat.OpenXml.Packaging;
  9. using System.IO;
  10. namespace OpenXmlPowerTools
  11. {
  12. public class Table
  13. {
  14. public int Id { get; set; }
  15. public string TableName { get; set; }
  16. public string DisplayName { get; set; }
  17. public XElement TableStyleInfo { get; set; }
  18. public string Ref { get; set; }
  19. public int LeftColumn { get; set; }
  20. public int RightColumn { get; set; }
  21. public int TopRow { get; set; }
  22. public int BottomRow { get; set; }
  23. public int? HeaderRowCount { get; set; }
  24. public int? TotalsRowCount { get; set; }
  25. public string TableType { get; set; } // external data query, data in worksheet, or XML data
  26. public TableDefinitionPart TableDefinitionPart { get; set; }
  27. public WorksheetPart Parent { get; set; }
  28. public Table(WorksheetPart parent) { Parent = parent; }
  29. public IEnumerable<TableColumn> TableColumns()
  30. {
  31. XNamespace x = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
  32. return TableDefinitionPart
  33. .GetXDocument()
  34. .Root
  35. .Element(x + "tableColumns")
  36. .Elements(x + "tableColumn")
  37. .Select((c, i) =>
  38. new TableColumn(this)
  39. {
  40. Id = (int)c.Attribute("id"),
  41. ColumnNumber = this.LeftColumn + i,
  42. Name = (string)c.Attribute("name"),
  43. DataDxfId = (int?)c.Attribute("dataDxfId"),
  44. QueryTableFieldId = (int?)c.Attribute("queryTableFieldId"),
  45. UniqueName = (string)c.Attribute("uniqueName"),
  46. ColumnIndex = i,
  47. }
  48. );
  49. }
  50. public IEnumerable<TableRow> TableRows()
  51. {
  52. string refStart = Ref.Split(':').First();
  53. int rowStart = Int32.Parse(XlsxTables.SplitAddress(refStart)[1]);
  54. string refEnd = Ref.Split(':').ElementAt(1);
  55. int rowEnd = Int32.Parse(XlsxTables.SplitAddress(refEnd)[1]);
  56. int headerRowsCount = HeaderRowCount == null ? 0 : (int)HeaderRowCount;
  57. int totalRowsCount = TotalsRowCount == null ? 0 : (int)TotalsRowCount;
  58. return Parent
  59. .Rows()
  60. .Skip(headerRowsCount)
  61. .PtSkipLast(totalRowsCount)
  62. .Where(r =>
  63. {
  64. int rowId = Int32.Parse(r.RowId);
  65. return rowId >= rowStart && rowId <= rowEnd;
  66. }
  67. )
  68. .Select(r => new TableRow(this) { Row = r });
  69. }
  70. }
  71. public class TableColumn
  72. {
  73. public int Id { get; set; }
  74. public string Name { get; set; }
  75. public int? DataDxfId { get; set; }
  76. public int? QueryTableFieldId { get; set; }
  77. public string UniqueName { get; set; }
  78. public int ColumnNumber { get; set; }
  79. public int ColumnIndex { get; set; }
  80. public Table Parent { get; set; }
  81. public TableColumn(Table parent) { Parent = parent; }
  82. }
  83. public class TableRow
  84. {
  85. public Row Row { get; set; }
  86. public Table Parent { get; set; }
  87. public TableRow(Table parent) { Parent = parent; }
  88. public TableCell this[string columnName]
  89. {
  90. get
  91. {
  92. TableColumn tc = Parent
  93. .TableColumns()
  94. .Where(x => x.Name.ToLower() == columnName.ToLower())
  95. .FirstOrDefault();
  96. if (tc == null)
  97. throw new Exception("Invalid column name: " + columnName);
  98. string[] refs = Parent.Ref.Split(':');
  99. string[] startRefs = XlsxTables.SplitAddress(refs[0]);
  100. string columnAddress = XlsxTables.IndexToColumnAddress(XlsxTables.ColumnAddressToIndex(startRefs[0]) + tc.ColumnIndex);
  101. Cell cell = Row.Cells().Where(c => c.ColumnAddress == columnAddress).FirstOrDefault();
  102. if (cell != null)
  103. {
  104. if (cell.Type == "s")
  105. return new TableCell(cell.SharedString);
  106. else
  107. return new TableCell(cell.Value);
  108. }
  109. else
  110. return new TableCell("");
  111. }
  112. }
  113. }
  114. public class TableCell : IEquatable<TableCell>
  115. {
  116. public string Value { get; set; }
  117. public TableCell(string v)
  118. {
  119. Value = v;
  120. }
  121. public override string ToString()
  122. {
  123. return Value;
  124. }
  125. public override bool Equals(object obj)
  126. {
  127. return this.Value == ((TableCell)obj).Value;
  128. }
  129. bool IEquatable<TableCell>.Equals(TableCell other)
  130. {
  131. return this.Value == other.Value;
  132. }
  133. public override int GetHashCode()
  134. {
  135. return this.Value.GetHashCode();
  136. }
  137. public static bool operator ==(TableCell left, TableCell right)
  138. {
  139. if ((object)left != (object)right) return false;
  140. return left.Value == right.Value;
  141. }
  142. public static bool operator !=(TableCell left, TableCell right)
  143. {
  144. if ((object)left != (object)right) return false;
  145. return left.Value != right.Value;
  146. }
  147. public static explicit operator string(TableCell cell)
  148. {
  149. if (cell == null) return null;
  150. return cell.Value;
  151. }
  152. public static explicit operator bool(TableCell cell)
  153. {
  154. if (cell == null) throw new ArgumentNullException("TableCell");
  155. return cell.Value == "1";
  156. }
  157. public static explicit operator bool?(TableCell cell)
  158. {
  159. if (cell == null) return null;
  160. return cell.Value == "1";
  161. }
  162. public static explicit operator int(TableCell cell)
  163. {
  164. if (cell == null) throw new ArgumentNullException("TableCell");
  165. return Int32.Parse(cell.Value);
  166. }
  167. public static explicit operator int?(TableCell cell)
  168. {
  169. if (cell == null) return null;
  170. return Int32.Parse(cell.Value);
  171. }
  172. public static explicit operator uint(TableCell cell)
  173. {
  174. if (cell == null) throw new ArgumentNullException("TableCell");
  175. return UInt32.Parse(cell.Value);
  176. }
  177. public static explicit operator uint?(TableCell cell)
  178. {
  179. if (cell == null) return null;
  180. return UInt32.Parse(cell.Value);
  181. }
  182. public static explicit operator long(TableCell cell)
  183. {
  184. if (cell == null) throw new ArgumentNullException("TableCell");
  185. return Int64.Parse(cell.Value);
  186. }
  187. public static explicit operator long?(TableCell cell)
  188. {
  189. if (cell == null) return null;
  190. return Int64.Parse(cell.Value);
  191. }
  192. public static explicit operator ulong(TableCell cell)
  193. {
  194. if (cell == null) throw new ArgumentNullException("TableCell");
  195. return UInt64.Parse(cell.Value);
  196. }
  197. public static explicit operator ulong?(TableCell cell)
  198. {
  199. if (cell == null) return null;
  200. return UInt64.Parse(cell.Value);
  201. }
  202. public static explicit operator float(TableCell cell)
  203. {
  204. if (cell == null) throw new ArgumentNullException("TableCell");
  205. return Single.Parse(cell.Value);
  206. }
  207. public static explicit operator float?(TableCell cell)
  208. {
  209. if (cell == null) return null;
  210. return Single.Parse(cell.Value);
  211. }
  212. public static explicit operator double(TableCell cell)
  213. {
  214. if (cell == null) throw new ArgumentNullException("TableCell");
  215. return Double.Parse(cell.Value);
  216. }
  217. public static explicit operator double?(TableCell cell)
  218. {
  219. if (cell == null) return null;
  220. return Double.Parse(cell.Value);
  221. }
  222. public static explicit operator decimal(TableCell cell)
  223. {
  224. if (cell == null) throw new ArgumentNullException("TableCell");
  225. return Decimal.Parse(cell.Value);
  226. }
  227. public static explicit operator decimal?(TableCell cell)
  228. {
  229. if (cell == null) return null;
  230. return Decimal.Parse(cell.Value);
  231. }
  232. public static implicit operator DateTime(TableCell cell)
  233. {
  234. if (cell == null) throw new ArgumentNullException("TableCell");
  235. return new DateTime(1900, 1, 1).AddDays(Int32.Parse(cell.Value) - 2);
  236. }
  237. public static implicit operator DateTime?(TableCell cell)
  238. {
  239. if (cell == null) return null;
  240. return new DateTime(1900, 1, 1).AddDays(Int32.Parse(cell.Value) - 2);
  241. }
  242. }
  243. public class Row
  244. {
  245. public XElement RowElement { get; set; }
  246. public string RowId { get; set; }
  247. public string Spans { get; set; }
  248. public List<Cell> Cells()
  249. {
  250. XNamespace s = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
  251. SpreadsheetDocument doc = (SpreadsheetDocument)Parent.OpenXmlPackage;
  252. SharedStringTablePart sharedStringTable = doc.WorkbookPart.SharedStringTablePart;
  253. IEnumerable<XElement> cells = this.RowElement.Elements(S.c);
  254. var r = cells
  255. .Select(cell => {
  256. var cellType = (string)cell.Attribute("t");
  257. var sharedString = cellType == "s" ?
  258. sharedStringTable
  259. .GetXDocument()
  260. .Root
  261. .Elements(s + "si")
  262. .Skip((int)cell.Element(s + "v"))
  263. .First()
  264. .Descendants(s + "t")
  265. .StringConcatenate(e => (string)e)
  266. : null;
  267. var column = (string)cell.Attribute("r");
  268. var columnAddress = column.Split('0', '1', '2', '3', '4', '5', '6', '7', '8', '9').First();
  269. var columnIndex = XlsxTables.ColumnAddressToIndex(columnAddress);
  270. var newCell = new Cell(this)
  271. {
  272. CellElement = cell,
  273. Row = (string)RowElement.Attribute("r"),
  274. Column = column,
  275. ColumnAddress = columnAddress,
  276. ColumnIndex = columnIndex,
  277. Type = cellType,
  278. Formula = (string)cell.Element(S.f),
  279. Style = (int?)cell.Attribute("s"),
  280. Value = (string)cell.Element(S.v),
  281. SharedString = sharedString
  282. };
  283. return newCell;
  284. });
  285. var ra = r.ToList();
  286. return ra;
  287. }
  288. public WorksheetPart Parent { get; set; }
  289. public Row(WorksheetPart parent) { Parent = parent; }
  290. }
  291. public class Cell
  292. {
  293. public XElement CellElement { get; set; }
  294. public string Row { get; set; }
  295. public string Column { get; set; }
  296. public string ColumnAddress { get; set; }
  297. public int ColumnIndex { get; set; }
  298. public string Type { get; set; }
  299. public string Value { get; set; }
  300. public string Formula { get; set; }
  301. public int? Style { get; set; }
  302. public string SharedString { get; set; }
  303. public Row Parent { get; set; }
  304. public Cell(Row parent) { Parent = parent; }
  305. }
  306. public static class XlsxTables
  307. {
  308. public static IEnumerable<Table> Tables(this SpreadsheetDocument spreadsheet)
  309. {
  310. foreach (var worksheetPart in spreadsheet.WorkbookPart.WorksheetParts)
  311. foreach (var table in worksheetPart.TableDefinitionParts)
  312. {
  313. XDocument tableDefDoc = table.GetXDocument();
  314. Table t = new Table(worksheetPart)
  315. {
  316. Id = (int)tableDefDoc.Root.Attribute("id"),
  317. TableName = (string)tableDefDoc.Root.Attribute("name"),
  318. DisplayName = (string)tableDefDoc.Root.Attribute("displayName"),
  319. TableStyleInfo = tableDefDoc.Root.Element(S.tableStyleInfo),
  320. Ref = (string)tableDefDoc.Root.Attribute("ref"),
  321. TotalsRowCount = (int?)tableDefDoc.Root.Attribute("totalsRowCount"),
  322. //HeaderRowCount = (int?)tableDefDoc.Root.Attribute("headerRowCount"),
  323. HeaderRowCount = 1, // currently there always is a header row
  324. TableType = (string)tableDefDoc.Root.Attribute("tableType"),
  325. TableDefinitionPart = table
  326. };
  327. int leftColumn, topRow, rightColumn, bottomRow;
  328. ParseRange(t.Ref, out leftColumn, out topRow, out rightColumn, out bottomRow);
  329. t.LeftColumn = leftColumn;
  330. t.TopRow = topRow;
  331. t.RightColumn = rightColumn;
  332. t.BottomRow = bottomRow;
  333. yield return t;
  334. }
  335. }
  336. public static void ParseRange(string theRef, out int leftColumn, out int topRow, out int rightColumn, out int bottomRow)
  337. {
  338. // C5:E7
  339. var spl = theRef.Split(':');
  340. string refStart = spl.First();
  341. var refStartSplit = XlsxTables.SplitAddress(refStart);
  342. leftColumn = XlsxTables.ColumnAddressToIndex(refStartSplit[0]);
  343. topRow = Int32.Parse(refStartSplit[1]);
  344. string refEnd = spl.ElementAt(1);
  345. var refEndSplit = XlsxTables.SplitAddress(refEnd);
  346. rightColumn = XlsxTables.ColumnAddressToIndex(refEndSplit[0]);
  347. bottomRow = Int32.Parse(refEndSplit[1]);
  348. }
  349. public static Table Table(this SpreadsheetDocument spreadsheet,
  350. string tableName)
  351. {
  352. return spreadsheet.Tables().Where(t => t.TableName.ToLower() == tableName.ToLower()).FirstOrDefault();
  353. }
  354. public static IEnumerable<Row> Rows(this WorksheetPart worksheetPart)
  355. {
  356. //XNamespace s = "http://schemas.openxmlformats.org/spreadsheetml/2006/main";
  357. var rows = worksheetPart
  358. .GetXDocument()
  359. .Root
  360. .Elements(S.sheetData)
  361. .Elements(S.row)
  362. .Select(r =>
  363. {
  364. var row = new Row(worksheetPart)
  365. {
  366. RowElement = r,
  367. RowId = (string)r.Attribute("r"),
  368. Spans = (string)r.Attribute("spans")
  369. };
  370. return row;
  371. });
  372. return rows;
  373. }
  374. public static string[] SplitAddress(string address)
  375. {
  376. int i;
  377. for (i = 0; i < address.Length; i++)
  378. if (address[i] >= '0' && address[i] <= '9')
  379. break;
  380. if (i == address.Length)
  381. throw new FileFormatException("Invalid spreadsheet. Bad cell address.");
  382. return new[] {
  383. address.Substring(0, i),
  384. address.Substring(i)
  385. };
  386. }
  387. public static string IndexToColumnAddress(int index)
  388. {
  389. if (index < 26)
  390. {
  391. char c = (char)((int)'A' + index);
  392. string s = new string(c, 1);
  393. return s;
  394. }
  395. if (index < 702)
  396. {
  397. int i = index - 26;
  398. int i1 = (int)(i / 26);
  399. int i2 = i % 26;
  400. string s = new string((char)((int)'A' + i1), 1) +
  401. new string((char)((int)'A' + i2), 1);
  402. return s;
  403. }
  404. if (index < 18278)
  405. {
  406. int i = index - 702;
  407. int i1 = (int)(i / 676);
  408. i = i - i1 * 676;
  409. int i2 = (int)(i / 26);
  410. int i3 = i % 26;
  411. string s = new string((char)((int)'A' + i1), 1) +
  412. new string((char)((int)'A' + i2), 1) +
  413. new string((char)((int)'A' + i3), 1);
  414. return s;
  415. }
  416. throw new Exception("Invalid column address");
  417. }
  418. public static int ColumnAddressToIndex(string columnAddress)
  419. {
  420. if (columnAddress.Length == 1)
  421. {
  422. char c = columnAddress[0];
  423. int i = c - 'A';
  424. return i;
  425. }
  426. if (columnAddress.Length == 2)
  427. {
  428. char c1 = columnAddress[0];
  429. char c2 = columnAddress[1];
  430. int i1 = c1 - 'A';
  431. int i2 = c2 - 'A';
  432. return (i1 + 1) * 26 + i2;
  433. }
  434. if (columnAddress.Length == 3)
  435. {
  436. char c1 = columnAddress[0];
  437. char c2 = columnAddress[1];
  438. char c3 = columnAddress[2];
  439. int i1 = c1 - 'A';
  440. int i2 = c2 - 'A';
  441. int i3 = c3 - 'A';
  442. return (i1 + 1) * 676 + (i2 + 1) * 26 + i3;
  443. }
  444. throw new FileFormatException("Invalid spreadsheet: Invalid column address.");
  445. }
  446. }
  447. }