NPOISpreadsheet.cs 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Xml.Linq;
  6. using InABox.Core;
  7. using NPOI.SS.Formula.Functions;
  8. using NPOI.SS.UserModel;
  9. using NPOI.XSSF.UserModel;
  10. using NCell = NPOI.SS.UserModel.ICell;
  11. using NRow = NPOI.SS.UserModel.IRow;
  12. using NFont = NPOI.SS.UserModel.IFont;
  13. using NSheet = NPOI.SS.UserModel.ISheet;
  14. using NDataFormat = NPOI.SS.UserModel.IDataFormat;
  15. using NCellStyle = NPOI.SS.UserModel.ICellStyle;
  16. using NPOI.SS.Util;
  17. using NPOI.OpenXmlFormats.Spreadsheet;
  18. using System.Security.Policy;
  19. using System.Drawing;
  20. using NPOI.HSSF.Util;
  21. using NPOI.HSSF.UserModel;
  22. namespace InABox.Scripting
  23. {
  24. public class RowEnumerator : IEnumerator<Row>
  25. {
  26. public Sheet Sheet { get; }
  27. private IEnumerator _enumerator { get; set; }
  28. public Row Current => new Row((_enumerator.Current as NRow)!, Sheet);
  29. object IEnumerator.Current => new Row((_enumerator.Current as NRow)!, Sheet);
  30. internal RowEnumerator(IEnumerator enumerator, Sheet sheet)
  31. {
  32. _enumerator = enumerator;
  33. Sheet = sheet;
  34. }
  35. public bool MoveNext()
  36. {
  37. return _enumerator.MoveNext();
  38. }
  39. public void Reset()
  40. {
  41. _enumerator.Reset();
  42. }
  43. public void Dispose()
  44. {
  45. }
  46. }
  47. public class Sheet : ISheet
  48. {
  49. private NSheet _sheet;
  50. public string Name => _sheet.SheetName;
  51. public int FirstRow => _sheet.FirstRowNum;
  52. public int LastRow => _sheet.LastRowNum;
  53. public Spreadsheet Spreadsheet { get; }
  54. ISpreadsheet ISheet.Spreadsheet => Spreadsheet;
  55. internal Sheet(NSheet sheet, Spreadsheet spreadsheet)
  56. {
  57. _sheet = sheet;
  58. Spreadsheet = spreadsheet;
  59. }
  60. public IEnumerable<IRow> Rows()
  61. {
  62. var enumerator = _sheet.GetRowEnumerator();
  63. var row = 0;
  64. while (enumerator.MoveNext() && row <= int.MaxValue)
  65. {
  66. yield return new Row((NRow)enumerator.Current, this);
  67. row++;
  68. }
  69. }
  70. public IEnumerator<IRow> RowEnumerator()
  71. {
  72. return new RowEnumerator(_sheet.GetRowEnumerator(), this);
  73. }
  74. public IRow NewRow()
  75. {
  76. var row = _sheet.CreateRow(_sheet.LastRowNum + 1);
  77. return new Row(row, this);
  78. }
  79. public IRow? GetRow(int row)
  80. {
  81. var nRow = _sheet.GetRow(row);
  82. if (nRow is null) return null;
  83. return new Row(nRow, this);
  84. }
  85. public float GetRowHeight(int row)
  86. {
  87. return _sheet.GetRow(row)?.HeightInPoints ?? _sheet.DefaultRowHeightInPoints;
  88. }
  89. public float GetColumnWidth(int column)
  90. {
  91. if (_sheet.IsColumnHidden(column)) return 0f;
  92. var width = _sheet.GetColumnWidth(column) / 256f;
  93. if (width <= 0f) return float.MinValue;
  94. return width;
  95. }
  96. public ISheet SetColumnWidth(int column, float charWidth)
  97. {
  98. _sheet.SetColumnWidth(column, (int)Math.Round(charWidth * 256));
  99. return this;
  100. }
  101. public ISheet MergeCells(int firstRow, int lastRow, int firstColumn, int lastColumn)
  102. {
  103. var range = new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn);
  104. _sheet.AddMergedRegion(range);
  105. return this;
  106. }
  107. public IEnumerable<CellRange> GetMergedCells()
  108. {
  109. foreach(var region in _sheet.MergedRegions)
  110. {
  111. yield return new CellRange(region.FirstRow, region.LastRow, region.FirstColumn, region.LastColumn);
  112. }
  113. }
  114. }
  115. public class Row : IRow
  116. {
  117. private NRow _row;
  118. public int RowNumber => _row.RowNum;
  119. public int FirstColumn => _row.FirstCellNum;
  120. public int LastColumn => _row.LastCellNum;
  121. public Sheet Sheet { get; }
  122. ISheet IRow.Sheet => Sheet;
  123. internal Row(NRow row, Sheet sheet)
  124. {
  125. _row = row;
  126. Sheet = sheet;
  127. }
  128. public string ExtractString(int column, bool uppercase = false)
  129. {
  130. try
  131. {
  132. var result = "";
  133. var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK);
  134. if (cell.CellType == CellType.Numeric)
  135. result = cell.NumericCellValue.ToString();
  136. else
  137. result = cell.StringCellValue;
  138. if (string.IsNullOrWhiteSpace(result))
  139. result = "";
  140. return uppercase ? result.ToUpper() : result;
  141. }
  142. catch (Exception e)
  143. {
  144. throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message));
  145. }
  146. }
  147. public DateTime ExtractDateTime(int column)
  148. {
  149. try
  150. {
  151. var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK);
  152. try
  153. {
  154. return cell.DateCellValue;
  155. }
  156. catch
  157. {
  158. var sDate = cell.StringCellValue;
  159. DateTime.TryParse(sDate, out var result);
  160. return result;
  161. }
  162. }
  163. catch (Exception e)
  164. {
  165. throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message));
  166. }
  167. }
  168. public double? ExtractDouble(int column)
  169. {
  170. try
  171. {
  172. double result = 0.0F;
  173. var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK);
  174. if (cell.CellType == CellType.Numeric || cell.CellType == CellType.Formula)
  175. result = cell.NumericCellValue;
  176. else if (cell.CellType == CellType.String)
  177. result = double.Parse(cell.StringCellValue);
  178. return result;
  179. }
  180. catch (FormatException)
  181. {
  182. return null;
  183. }
  184. catch (Exception e)
  185. {
  186. throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message));
  187. }
  188. }
  189. public int GetColumn(string name, bool throwException = true)
  190. {
  191. var cells = _row.GetEnumerator();
  192. while (cells.MoveNext())
  193. {
  194. var cell = cells.Current;
  195. if (!string.IsNullOrWhiteSpace(cell.StringCellValue) && cell.StringCellValue.ToUpper().Trim().Equals(name.ToUpper().Trim()))
  196. {
  197. return cell.ColumnIndex;
  198. }
  199. }
  200. if (!throwException)
  201. {
  202. return -1;
  203. }
  204. throw new Exception("Unable to find Column: " + name);
  205. }
  206. public ICell? GetCell(int column)
  207. {
  208. var nCell = _row.GetCell(column);
  209. if (nCell is null) return null;
  210. return new Cell(nCell, this);
  211. }
  212. public ICell NewCell(int column)
  213. {
  214. var cell = _row.CreateCell(column);
  215. return new Cell(cell, this);
  216. }
  217. public IEnumerable<ICell> Cells()
  218. {
  219. foreach(var cell in _row)
  220. {
  221. yield return new Cell(cell, this);
  222. }
  223. }
  224. }
  225. public class Cell : ICell
  226. {
  227. private NCell _cell;
  228. IRow ICell.Row => Row;
  229. public Row Row { get; }
  230. internal Cell(NCell cell, Row row)
  231. {
  232. _cell = cell;
  233. Row = row;
  234. }
  235. public string GetValue()
  236. {
  237. if (_cell.CellType == CellType.Formula)
  238. {
  239. if (_cell.CachedFormulaResultType == CellType.Numeric)
  240. return string.Format("{0:F}", _cell.NumericCellValue.ToString());
  241. return _cell.StringCellValue;
  242. }
  243. return _cell.ToString() ?? "";
  244. }
  245. public bool? GetBoolValue()
  246. {
  247. try
  248. {
  249. if (_cell.CellType == CellType.Boolean)
  250. return _cell.BooleanCellValue;
  251. return null;
  252. }
  253. catch (Exception)
  254. {
  255. return null;
  256. }
  257. }
  258. public double? GetDoubleValue()
  259. {
  260. try
  261. {
  262. double result = 0.0F;
  263. if (_cell.CellType == CellType.Numeric || _cell.CellType == CellType.Formula)
  264. result = _cell.NumericCellValue;
  265. else if (_cell.CellType == CellType.String)
  266. result = double.Parse(_cell.StringCellValue);
  267. return result;
  268. }
  269. catch (Exception)
  270. {
  271. return null;
  272. }
  273. }
  274. public DateTime GetDateTimeValue()
  275. {
  276. try
  277. {
  278. return _cell.DateCellValue;
  279. }
  280. catch
  281. {
  282. var sDate = _cell.StringCellValue;
  283. if(!DateTime.TryParse(sDate, out var result)){
  284. return DateTime.MinValue;
  285. }
  286. return result;
  287. }
  288. }
  289. public byte? GetByteValue()
  290. {
  291. try
  292. {
  293. byte result = 0;
  294. if (_cell.CellType == CellType.Numeric || _cell.CellType == CellType.Formula)
  295. result = (byte)_cell.NumericCellValue;
  296. else if (_cell.CellType == CellType.String)
  297. result = byte.Parse(_cell.StringCellValue);
  298. return result;
  299. }
  300. catch (Exception)
  301. {
  302. return null;
  303. }
  304. }
  305. public ICell SetValue(bool value)
  306. {
  307. _cell.SetCellValue(value);
  308. return this;
  309. }
  310. public ICell SetValue(double value)
  311. {
  312. _cell.SetCellValue(value);
  313. return this;
  314. }
  315. public ICell SetValue(string value)
  316. {
  317. _cell.SetCellValue(value);
  318. return this;
  319. }
  320. public ICell SetValue(byte value)
  321. {
  322. _cell.SetCellValue(value);
  323. return this;
  324. }
  325. public ICell SetValue(DateTime value)
  326. {
  327. _cell.SetCellValue(value);
  328. return this;
  329. }
  330. public ICell SetBlank()
  331. {
  332. _cell.SetBlank();
  333. return this;
  334. }
  335. public ICellStyle GetStyle()
  336. {
  337. return new CellStyle(_cell.CellStyle, Row.Sheet.Spreadsheet);
  338. }
  339. public ICell SetStyle(ICellStyle style)
  340. {
  341. _cell.CellStyle = (style as CellStyle)!._style;
  342. return this;
  343. }
  344. }
  345. public class SheetEnumerator : IEnumerator<Sheet>
  346. {
  347. public Spreadsheet Spreadsheet { get; }
  348. public Sheet Current => new(_enumerator.Current, Spreadsheet);
  349. private IEnumerator<NSheet> _enumerator { get; }
  350. object IEnumerator.Current => new Sheet(_enumerator.Current, Spreadsheet);
  351. internal SheetEnumerator(IEnumerator<NSheet> enumerator, Spreadsheet spreadsheet)
  352. {
  353. _enumerator = enumerator;
  354. Spreadsheet = spreadsheet;
  355. }
  356. public void Dispose()
  357. {
  358. _enumerator.Dispose();
  359. }
  360. public bool MoveNext()
  361. {
  362. return _enumerator.MoveNext();
  363. }
  364. public void Reset()
  365. {
  366. _enumerator.Reset();
  367. }
  368. }
  369. public class DataFormat : IDataFormat
  370. {
  371. public short FormatIndex { get; }
  372. public DataFormat(short format)
  373. {
  374. FormatIndex = format;
  375. }
  376. }
  377. public class Font : IFont
  378. {
  379. public Spreadsheet Spreadsheet { get; set; }
  380. internal NFont _font { get; }
  381. public bool Bold { get => _font.IsBold; set => _font.IsBold = value; }
  382. public bool Italic { get => _font.IsItalic; set => throw new NotImplementedException(); }
  383. public UnderlineType Underline
  384. {
  385. get => _font.Underline switch
  386. {
  387. FontUnderlineType.None => UnderlineType.None,
  388. FontUnderlineType.Single => UnderlineType.Single,
  389. FontUnderlineType.Double => UnderlineType.Double,
  390. FontUnderlineType.SingleAccounting => UnderlineType.SingleAccounting,
  391. FontUnderlineType.DoubleAccounting => UnderlineType.DoubleAccounting,
  392. _ => UnderlineType.None,
  393. };
  394. set
  395. {
  396. _font.Underline = value switch
  397. {
  398. UnderlineType.None => FontUnderlineType.None,
  399. UnderlineType.Single => FontUnderlineType.Single,
  400. UnderlineType.Double => FontUnderlineType.Double,
  401. UnderlineType.SingleAccounting => FontUnderlineType.SingleAccounting,
  402. UnderlineType.DoubleAccounting => FontUnderlineType.DoubleAccounting,
  403. _ => FontUnderlineType.None
  404. };
  405. }
  406. }
  407. public Color Colour {
  408. get
  409. {
  410. if(_font is XSSFFont xFont)
  411. {
  412. return CellStyle.ConvertColour(xFont.GetXSSFColor());
  413. }
  414. else if(_font is HSSFFont hFont && Spreadsheet.Workbook is HSSFWorkbook workbook)
  415. {
  416. return CellStyle.ConvertColour(hFont.GetHSSFColor(workbook));
  417. }
  418. else
  419. {
  420. return CellStyle.ColourFromIndex(_font.Color);
  421. }
  422. }
  423. }
  424. public double FontSize { get => _font.FontHeightInPoints; set => _font.FontHeightInPoints = value; }
  425. public Font(NFont font, Spreadsheet spreadsheet)
  426. {
  427. _font = font;
  428. Spreadsheet = spreadsheet;
  429. }
  430. }
  431. public class CellStyle : ICellStyle
  432. {
  433. internal NCellStyle _style { get; }
  434. public IDataFormat DataFormat {
  435. get => new DataFormat(_style.DataFormat);
  436. set => _style.DataFormat = value.FormatIndex;
  437. }
  438. ISpreadsheet ICellStyle.Spreadsheet => Spreadsheet;
  439. public Spreadsheet Spreadsheet { get; }
  440. public Color Background => ConvertColour(_style.FillBackgroundColorColor);
  441. public Color Foreground => ConvertColour(_style.FillForegroundColorColor);
  442. public IFont Font => new Font(_style.GetFont(Spreadsheet.Workbook), Spreadsheet);
  443. public CellStyle(NCellStyle style, Spreadsheet spreadsheet)
  444. {
  445. _style = style;
  446. Spreadsheet = spreadsheet;
  447. }
  448. public static Color ColourFromIndex(short index)
  449. {
  450. int indexNum = index;
  451. var hashIndex = HSSFColor.GetIndexHash();
  452. HSSFColor? indexed = null;
  453. if (hashIndex.ContainsKey(indexNum))
  454. indexed = hashIndex[indexNum];
  455. if (indexed != null)
  456. {
  457. byte[] rgb = new byte[3];
  458. rgb[0] = (byte)indexed.GetTriplet()[0];
  459. rgb[1] = (byte)indexed.GetTriplet()[1];
  460. rgb[2] = (byte)indexed.GetTriplet()[2];
  461. return Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
  462. }
  463. return Color.Empty;
  464. }
  465. public static Color ConvertColour(IColor? colour)
  466. {
  467. if(colour is null)
  468. {
  469. return Color.Empty;
  470. }
  471. if(colour is ExtendedColor extendedColour)
  472. {
  473. if (extendedColour.IsIndexed)
  474. {
  475. return ColourFromIndex(extendedColour.Index);
  476. }
  477. else
  478. {
  479. var argb = extendedColour.ARGB;
  480. return Color.FromArgb(argb[0], argb[1], argb[2], argb[3]);
  481. }
  482. }
  483. else if(colour is HSSFColor hssfColour)
  484. {
  485. var rgb = hssfColour.RGB;
  486. return Color.FromArgb(255, rgb[0], rgb[1], rgb[2]);
  487. }
  488. else
  489. {
  490. Logger.Send(LogType.Error, "", $"Unknown NPOI Colour class {colour.GetType()}");
  491. return Color.Empty;
  492. }
  493. }
  494. }
  495. public class Spreadsheet : ISpreadsheet
  496. {
  497. public IWorkbook Workbook;
  498. private NDataFormat DataFormat;
  499. private Spreadsheet(IWorkbook workbook)
  500. {
  501. Workbook = workbook;
  502. DataFormat = Workbook.CreateDataFormat();
  503. }
  504. public Spreadsheet(string fileName) : this(WorkbookFactory.Create(fileName)) { }
  505. public Spreadsheet(FileStream file) : this(WorkbookFactory.Create(file)) { }
  506. public Spreadsheet() : this(new XSSFWorkbook()) { }
  507. public ISheet GetSheet(int index)
  508. {
  509. return new Sheet(Workbook.GetSheetAt(index), this);
  510. }
  511. public ISheet GetSheet(string name)
  512. {
  513. return new Sheet(Workbook.GetSheet(name), this);
  514. }
  515. public IEnumerator<ISheet> SheetEnumerator()
  516. {
  517. var enumerator = Workbook.GetEnumerator();
  518. return new SheetEnumerator(enumerator, this);
  519. }
  520. public IEnumerable<ISheet> Sheets()
  521. {
  522. for (var i = 0; i < Workbook.NumberOfSheets; i++)
  523. yield return GetSheet(i);
  524. }
  525. public void Write(FileStream file)
  526. {
  527. Workbook.Write(file);
  528. }
  529. public void Write(string filename, FileMode mode = FileMode.Create)
  530. {
  531. using(var stream = new FileStream(filename, FileMode.Create))
  532. {
  533. Workbook.Write(stream);
  534. }
  535. }
  536. public ISheet NewSheet(string name)
  537. {
  538. var sheet = Workbook.CreateSheet(name);
  539. return new Sheet(sheet, this);
  540. }
  541. public ISheet NewSheet()
  542. {
  543. var sheet = Workbook.CreateSheet();
  544. return new Sheet(sheet, this);
  545. }
  546. public ICellStyle NewStyle()
  547. {
  548. var style = Workbook.CreateCellStyle();
  549. var x = style.GetDataFormatString();
  550. return new CellStyle(style, this);
  551. }
  552. public IDataFormat GetDataFormat(string format)
  553. {
  554. var dataFormat = DataFormat.GetFormat(format);
  555. return new DataFormat(dataFormat);
  556. }
  557. }
  558. }