NPOISpreadsheet.cs 20 KB

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