NPOISpreadsheet.cs 19 KB

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