using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Xml.Linq; using InABox.Core; using NPOI.SS.Formula.Functions; using NPOI.SS.UserModel; using NPOI.XSSF.UserModel; using NCell = NPOI.SS.UserModel.ICell; using NRow = NPOI.SS.UserModel.IRow; using NFont = NPOI.SS.UserModel.IFont; using NSheet = NPOI.SS.UserModel.ISheet; using NDataFormat = NPOI.SS.UserModel.IDataFormat; using NCellStyle = NPOI.SS.UserModel.ICellStyle; using NCellType = NPOI.SS.UserModel.CellType; using NPOI.SS.Util; using NPOI.OpenXmlFormats.Spreadsheet; using System.Security.Policy; using System.Drawing; using NPOI.HSSF.Util; using NPOI.HSSF.UserModel; using NPOI.XWPF.UserModel; namespace InABox.Scripting { public class RowEnumerator : IEnumerator { public Sheet Sheet { get; } private IEnumerator _enumerator { get; set; } public Row Current => new Row((_enumerator.Current as NRow)!, Sheet); object IEnumerator.Current => new Row((_enumerator.Current as NRow)!, Sheet); internal RowEnumerator(IEnumerator enumerator, Sheet sheet) { _enumerator = enumerator; Sheet = sheet; } public bool MoveNext() { return _enumerator.MoveNext(); } public void Reset() { _enumerator.Reset(); } public void Dispose() { } } public class Sheet : ISheet { private NSheet _sheet; public string Name => _sheet.SheetName; public int FirstRow => _sheet.FirstRowNum; public int LastRow => _sheet.LastRowNum; public Spreadsheet Spreadsheet { get; } ISpreadsheet ISheet.Spreadsheet => Spreadsheet; internal Sheet(NSheet sheet, Spreadsheet spreadsheet) { _sheet = sheet; Spreadsheet = spreadsheet; } public IEnumerable Rows() { var enumerator = _sheet.GetRowEnumerator(); var row = 0; while (enumerator.MoveNext() && row <= int.MaxValue) { yield return new Row((NRow)enumerator.Current, this); row++; } } public IEnumerator RowEnumerator() { return new RowEnumerator(_sheet.GetRowEnumerator(), this); } public IRow NewRow() { var row = _sheet.CreateRow(_sheet.LastRowNum + 1); return new Row(row, this); } public IRow? GetRow(int row) { var nRow = _sheet.GetRow(row); if (nRow is null) return null; return new Row(nRow, this); } public float GetRowHeight(int row) { return _sheet.GetRow(row)?.HeightInPoints ?? _sheet.DefaultRowHeightInPoints; } public float GetDefaultRowHeight() => _sheet.DefaultRowHeightInPoints; public float GetColumnWidth(int column) { if (_sheet.IsColumnHidden(column)) return 0f; var width = _sheet.GetColumnWidth(column) / 256f; if (width <= 0f) return float.MinValue; return (float)width; } public ISheet SetColumnWidth(int column, float charWidth) { _sheet.SetColumnWidth(column, (int)Math.Round(charWidth * 256)); return this; } public ISheet MergeCells(int firstRow, int lastRow, int firstColumn, int lastColumn) { var range = new CellRangeAddress(firstRow, lastRow, firstColumn, lastColumn); _sheet.AddMergedRegion(range); return this; } public IEnumerable GetMergedCells() { foreach(var region in _sheet.MergedRegions) { yield return new CellRange(region.FirstRow, region.LastRow, region.FirstColumn, region.LastColumn); } } } public class Row : IRow { private NRow _row; public int RowNumber => _row.RowNum; public int FirstColumn => _row.FirstCellNum; public int LastColumn => _row.LastCellNum; public Sheet Sheet { get; } ISheet IRow.Sheet => Sheet; internal Row(NRow row, Sheet sheet) { _row = row; Sheet = sheet; } public string ExtractString(int column, bool uppercase = false) { try { var result = ""; var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK); if (cell.CellType == NCellType.Numeric) result = cell.NumericCellValue.ToString(); else result = cell.StringCellValue; if (string.IsNullOrWhiteSpace(result)) result = ""; return uppercase ? result.ToUpper() : result; } catch (Exception e) { throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message)); } } public DateTime ExtractDateTime(int column) { try { var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK); try { return cell.DateCellValue ?? DateTime.MinValue; } catch { var sDate = cell.StringCellValue; DateTime.TryParse(sDate, out var result); return result; } } catch (Exception e) { throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message)); } } public double? ExtractDouble(int column) { try { double result = 0.0F; var cell = _row.GetCell(column, MissingCellPolicy.CREATE_NULL_AS_BLANK); if (cell.CellType == NCellType.Numeric || cell.CellType == NCellType.Formula) result = cell.NumericCellValue; else if (cell.CellType == NCellType.String) result = double.Parse(cell.StringCellValue); return result; } catch (FormatException) { return null; } catch (Exception e) { throw new Exception(string.Format("Row {0}:[{1}:{2}]: {3}", _row.Sheet.SheetName, _row.RowNum + 1, column, e.Message)); } } public int GetColumn(string name, bool throwException = true) { var cells = _row.GetEnumerator(); while (cells.MoveNext()) { var cell = cells.Current; if (!string.IsNullOrWhiteSpace(cell.StringCellValue) && cell.StringCellValue.ToUpper().Trim().Equals(name.ToUpper().Trim())) { return cell.ColumnIndex; } } if (!throwException) { return -1; } throw new Exception("Unable to find Column: " + name); } public ICell? GetCell(int column) { var nCell = _row.GetCell(column); if (nCell is null) return null; return new Cell(nCell, this); } public ICell NewCell(int column) { var cell = _row.CreateCell(column); return new Cell(cell, this); } public IEnumerable Cells() { foreach(var cell in _row) { yield return new Cell(cell, this); } } } public class Cell : ICell { private NCell _cell; IRow ICell.Row => Row; public Row Row { get; } internal Cell(NCell cell, Row row) { _cell = cell; Row = row; } private CellType ConvertCellType(NCellType type) { return type switch { NCellType.Formula => CellType.Numeric, NCellType.Numeric => CellType.Numeric, NCellType.Error => CellType.Error, NCellType.String => CellType.String, NCellType.Boolean => CellType.Boolean, NCellType.Blank => CellType.Blank, _ or NCellType.Unknown => CellType.Unknown }; } public CellType GetCellType() { if (_cell.CellType == NCellType.Formula) { return ConvertCellType(_cell.CachedFormulaResultType); } else if(_cell.CellType == NCellType.Numeric && DateUtil.IsCellDateFormatted(_cell)) { return CellType.Date; } return ConvertCellType(_cell.CellType); } public string GetValue() { if (_cell.CellType == NCellType.Formula) { if (_cell.CachedFormulaResultType == NCellType.Numeric) return string.Format("{0:F}", _cell.NumericCellValue.ToString()); return _cell.StringCellValue; } return _cell.ToString() ?? ""; } public bool? GetBoolValue() { try { if (_cell.CellType == NCellType.Boolean) return _cell.BooleanCellValue; return null; } catch (Exception) { return null; } } public double? GetDoubleValue() { try { double result = 0.0F; if (_cell.CellType == NCellType.Numeric || _cell.CellType == NCellType.Formula) result = _cell.NumericCellValue; else if (_cell.CellType == NCellType.String) result = double.Parse(_cell.StringCellValue); return result; } catch (Exception) { return null; } } public DateTime GetDateTimeValue() { try { return _cell.DateCellValue ?? DateTime.MinValue; } catch { var sDate = _cell.StringCellValue; if(!DateTime.TryParse(sDate, out var result)){ return DateTime.MinValue; } return result; } } public byte? GetByteValue() { try { byte result = 0; if (_cell.CellType == NCellType.Numeric || _cell.CellType == NCellType.Formula) result = (byte)_cell.NumericCellValue; else if (_cell.CellType == NCellType.String) result = byte.Parse(_cell.StringCellValue); return result; } catch (Exception) { return null; } } public ICell SetValue(bool value) { _cell.SetCellValue(value); return this; } public ICell SetValue(double value) { _cell.SetCellValue(value); return this; } public ICell SetValue(string value) { _cell.SetCellValue(value); return this; } public ICell SetValue(byte value) { _cell.SetCellValue(value); return this; } public ICell SetValue(DateTime value) { _cell.SetCellValue(value); return this; } public ICell SetBlank() { _cell.SetBlank(); return this; } public ICellStyle GetStyle() { return new CellStyle(_cell.CellStyle, Row.Sheet.Spreadsheet); } public ICell SetStyle(ICellStyle style) { _cell.CellStyle = (style as CellStyle)!._style; return this; } } public class SheetEnumerator : IEnumerator { public Spreadsheet Spreadsheet { get; } public Sheet Current => new(_enumerator.Current, Spreadsheet); private IEnumerator _enumerator { get; } object IEnumerator.Current => new Sheet(_enumerator.Current, Spreadsheet); internal SheetEnumerator(IEnumerator enumerator, Spreadsheet spreadsheet) { _enumerator = enumerator; Spreadsheet = spreadsheet; } public void Dispose() { _enumerator.Dispose(); } public bool MoveNext() { return _enumerator.MoveNext(); } public void Reset() { _enumerator.Reset(); } } public class DataFormat : IDataFormat { public short FormatIndex { get; } public DataFormat(short format) { FormatIndex = format; } } public class Font : IFont { public Spreadsheet Spreadsheet { get; set; } internal NFont _font { get; } public bool Bold { get => _font.IsBold; set => _font.IsBold = value; } public bool Italic { get => _font.IsItalic; set => throw new NotImplementedException(); } public UnderlineType Underline { get => _font.Underline switch { FontUnderlineType.None => UnderlineType.None, FontUnderlineType.Single => UnderlineType.Single, FontUnderlineType.Double => UnderlineType.Double, FontUnderlineType.SingleAccounting => UnderlineType.SingleAccounting, FontUnderlineType.DoubleAccounting => UnderlineType.DoubleAccounting, _ => UnderlineType.None, }; set { _font.Underline = value switch { UnderlineType.None => FontUnderlineType.None, UnderlineType.Single => FontUnderlineType.Single, UnderlineType.Double => FontUnderlineType.Double, UnderlineType.SingleAccounting => FontUnderlineType.SingleAccounting, UnderlineType.DoubleAccounting => FontUnderlineType.DoubleAccounting, _ => FontUnderlineType.None }; } } public Color Colour { get { if(_font is XSSFFont xFont) { return CellStyle.ConvertColour(xFont.GetXSSFColor()); } else if(_font is HSSFFont hFont && Spreadsheet.Workbook is HSSFWorkbook workbook) { return CellStyle.ConvertColour(hFont.GetHSSFColor(workbook)); } else { return CellStyle.ColourFromIndex(_font.Color); } } } public double FontSize { get => _font.FontHeightInPoints; set => _font.FontHeightInPoints = value; } public Font(NFont font, Spreadsheet spreadsheet) { _font = font; Spreadsheet = spreadsheet; } } public class CellStyle : ICellStyle { internal NCellStyle _style { get; } public IDataFormat DataFormat { get => new DataFormat(_style.DataFormat); set => _style.DataFormat = value.FormatIndex; } ISpreadsheet ICellStyle.Spreadsheet => Spreadsheet; public Spreadsheet Spreadsheet { get; } public Color Background => ConvertColour(_style.FillForegroundColorColor); public Color Foreground => ColourFromIndex(_style.GetFont(Spreadsheet.Workbook).Color); public IFont Font => new Font(_style.GetFont(Spreadsheet.Workbook), Spreadsheet); public CellAlignment VerticalAlignment => _style.VerticalAlignment switch { NPOI.SS.UserModel.VerticalAlignment.Top => CellAlignment.Start, NPOI.SS.UserModel.VerticalAlignment.Bottom => CellAlignment.End, NPOI.SS.UserModel.VerticalAlignment.Justify => CellAlignment.Justify, _ => CellAlignment.Middle }; public CellAlignment HorizontalAlignment => _style.Alignment switch { NPOI.SS.UserModel.HorizontalAlignment.Center => CellAlignment.Middle, NPOI.SS.UserModel.HorizontalAlignment.Right => CellAlignment.End, NPOI.SS.UserModel.HorizontalAlignment.Justify => CellAlignment.Justify, _ => CellAlignment.Start }; public bool WrapText => _style.WrapText; public CellStyle(NCellStyle style, Spreadsheet spreadsheet) { _style = style; Spreadsheet = spreadsheet; } public static Color ColourFromIndex(short index) { int indexNum = index; var hashIndex = HSSFColor.GetIndexHash(); HSSFColor? indexed = null; if (hashIndex.ContainsKey(indexNum)) indexed = hashIndex[indexNum]; if (indexed != null) { byte[] rgb = new byte[3]; rgb[0] = (byte)indexed.GetTriplet()[0]; rgb[1] = (byte)indexed.GetTriplet()[1]; rgb[2] = (byte)indexed.GetTriplet()[2]; return Color.FromArgb(255, rgb[0], rgb[1], rgb[2]); } return Color.Empty; } public static Color ConvertColour(IColor? colour) { if(colour is null) { return Color.Empty; } if(colour is ExtendedColor extendedColour) { if (extendedColour.IsIndexed) { return ColourFromIndex(extendedColour.Index); } else { var rgb = extendedColour.RGBWithTint; return Color.FromArgb(255, rgb[0], rgb[1], rgb[2]); } } else if(colour is HSSFColor hssfColour) { var rgb = hssfColour.RGB; return Color.FromArgb(255, rgb[0], rgb[1], rgb[2]); } else { Logger.Send(LogType.Error, "", $"Unknown NPOI Colour class {colour.GetType()}"); return Color.Empty; } } } public class Spreadsheet : ISpreadsheet { public IWorkbook Workbook; private NDataFormat DataFormat; private Spreadsheet(IWorkbook workbook) { Workbook = workbook; DataFormat = Workbook.CreateDataFormat(); } public Spreadsheet(string fileName) : this(WorkbookFactory.Create(fileName)) { } public Spreadsheet(Stream file) : this(WorkbookFactory.Create(file)) { } public Spreadsheet() : this(new XSSFWorkbook()) { } public ISheet GetSheet(int index) { return new Sheet(Workbook.GetSheetAt(index), this); } public ISheet GetSheet(string name) { return new Sheet(Workbook.GetSheet(name), this); } public IEnumerator SheetEnumerator() { var enumerator = Workbook.GetEnumerator(); return new SheetEnumerator(enumerator, this); } public IEnumerable Sheets() { for (var i = 0; i < Workbook.NumberOfSheets; i++) yield return GetSheet(i); } public void Write(FileStream file) { Workbook.Write(file); } public void Write(string filename, FileMode mode = FileMode.Create) { using(var stream = new FileStream(filename, FileMode.Create)) { Workbook.Write(stream); } } public ISheet NewSheet(string name) { var sheet = Workbook.CreateSheet(name); return new Sheet(sheet, this); } public ISheet NewSheet() { var sheet = Workbook.CreateSheet(); return new Sheet(sheet, this); } public ICellStyle NewStyle() { var style = Workbook.CreateCellStyle(); var x = style.GetDataFormatString(); return new CellStyle(style, this); } public IDataFormat GetDataFormat(string format) { var dataFormat = DataFormat.GetFormat(format); return new DataFormat(dataFormat); } } }