using DocumentFormat.OpenXml.Packaging; using DocumentFormat.OpenXml.Spreadsheet; using System; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; namespace Tango.Documents { /// /// Represents a class for reading excel document rows by the specified model. /// public class ExcelReader : IDisposable { private SpreadsheetDocument document; //Will contain the Excel document. private List names; //Will contain the collection of Excel defined names. private bool throwError; //Determines whether to throw exceptions while parsing the Excel file. private string sheetN; //Will contain the specified Excel sheet name. private Stream fileStream; //Will contain the Excel file stream. #region Constructors /// /// Initializes a new instance of the ExcelReader. /// /// Document Stream. /// Throw exceptions if encountering an error while parsing the document. public ExcelReader(Stream source, bool throwOnError = true) { sheetN = String.Empty; fileStream = source; document = SpreadsheetDocument.Open(source, false); names = new List(); if (document.WorkbookPart.Workbook.GetFirstChild() != null) { foreach (DefinedName name in document.WorkbookPart.Workbook.GetFirstChild()) names.Add(name); } throwError = throwOnError; } /// /// Initializes a new instance of ExcelReader. /// /// File path to the Excel document. /// Throw exceptions if encountering an error while parsing the document. public ExcelReader(string templatePath, bool throwOnError = true) : this(new FileStream(templatePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite), throwOnError) { } #endregion #region Public Methods public List GetDataByIndex(String sheetName, int firstRowIndex) { sheetN = sheetName; List results = new List(); Worksheet currWorksheet = GetWorkSheetPart().Worksheet; int rowCount = currWorksheet.Descendants().Count(); PropertyInfo[] properties = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); for (int rowIndex = firstRowIndex; rowIndex < rowCount + 1; rowIndex++) { Row row = GetRow(currWorksheet, rowIndex); if (row == null) break; T newObj = (T)Activator.CreateInstance(); List cells = GetRowCells(row).ToList(); for (int cellIndex = 0; cellIndex < Math.Min(properties.Count(), cells.Count); cellIndex++) { Cell cell = cells[cellIndex] as Cell; String value = CellValue(cell); properties[cellIndex].SetValue(newObj, Convert.ChangeType(value, properties[cellIndex].PropertyType)); } results.Add(newObj); } return results; } /// /// Parse the document and populate the specified model collection using reflections. /// /// Type of model. /// Document Sheet name. /// Collection of models reflecting the parsed rows. public List GetData(String sheetName) { sheetN = sheetName; List ret = new List(); PropertyInfo[] PropColl = typeof(T).GetProperties(); names = (from pr in PropColl join n in names on pr.Name equals n.Name.ToString() select n).ToList(); if (this.names.Any()) { int Rowindex = GetFirstRowNumber() + 1; Worksheet currWorksheet = GetWorkSheetPart().Worksheet; int rowCount = currWorksheet.Descendants().Count(); while (Rowindex <= rowCount) { string a = ""; try { Row curr = GetRow(currWorksheet, Rowindex); if (curr == null) break; bool isEmptyRow = true; T newObj = (T)Activator.CreateInstance(); foreach (DefinedName name in names) { Stopwatch watchCol = new Stopwatch(); watchCol.Start(); var t = curr.ElementAtOrDefault(12); Cell theCell = curr.Descendants().Where(c => c.CellReference == GetColumn(name) + Rowindex.ToString()).FirstOrDefault(); PropertyInfo Prop = PropColl.FirstOrDefault(pr => pr.Name == name.Name); a = CellValue(theCell); if (a != null) { isEmptyRow = false; SetPropValue(a, newObj, Prop); } } if (isEmptyRow) break; ret.Add(newObj); } catch (Exception ex) { if (throwError) { throw ex; } } Rowindex++; } } return ret.ToList(); } /// /// Disposes the document stream. /// public void Close() { fileStream.Close(); fileStream.Dispose(); } #endregion #region Private Methods ///returns an empty cell when a blank cell is encountered /// public static IEnumerable GetRowCells(Row row) { int currentCount = 0; foreach (Cell cell in row.Descendants()) { string columnName = GetColumnName(cell.CellReference); int currentColumnIndex = ConvertColumnNameToNumber(columnName); for (; currentCount < currentColumnIndex; currentCount++) { yield return new DocumentFormat.OpenXml.Spreadsheet.Cell(); } yield return cell; currentCount++; } } /// /// Given a cell name, parses the specified cell to get the column name. /// /// Address of the cell (ie. B2) /// Column Name (ie. B) public static string GetColumnName(string cellReference) { // Match the column name portion of the cell name. var regex = new System.Text.RegularExpressions.Regex("[A-Za-z]+"); var match = regex.Match(cellReference); return match.Value; } /// /// Given just the column name (no row index), /// it will return the zero based column index. /// /// Column Name (ie. A or AB) /// Zero based index if the conversion was successful /// thrown if the given string /// contains characters other than uppercase letters public static int ConvertColumnNameToNumber(string columnName) { var alpha = new System.Text.RegularExpressions.Regex("^[A-Z]+$"); if (!alpha.IsMatch(columnName)) throw new ArgumentException(); char[] colLetters = columnName.ToCharArray(); Array.Reverse(colLetters); int convertedValue = 0; for (int i = 0; i < colLetters.Length; i++) { char letter = colLetters[i]; int current = i == 0 ? letter - 65 : letter - 64; // ASCII 'A' = 65 convertedValue += current * (int)Math.Pow(26, i); } return convertedValue; } /// /// Cells the value. /// /// The cell. /// private string CellValue(Cell cell) { if (cell == null) return null; string value = (cell.CellFormula == null) ? cell.InnerText : cell.CellValue.InnerText; if (String.IsNullOrEmpty(value)) return null; if (cell.DataType != null) { switch (cell.DataType.Value) { case CellValues.SharedString: var stringTable = document.WorkbookPart.SharedStringTablePart; if (stringTable != null) { value = stringTable.SharedStringTable. ElementAt(int.Parse(value)).InnerText; } break; case CellValues.Boolean: switch (value) { case "0": value = "FALSE"; break; default: value = "TRUE"; break; } break; } } return value; } /// /// Determines whether [is empty row] [the specified row]. /// /// The row. /// private bool IsEmptyRow(Row row) { Worksheet currWorksheet = GetWorkSheetPart().Worksheet; foreach (DefinedName name in names) { Cell theCell = row.Descendants(). Where(c => c.CellReference == GetColumn(name) + row.RowIndex.ToString()).FirstOrDefault(); if (theCell == null || theCell.CellValue == null) continue; if (!string.IsNullOrEmpty(theCell.CellValue.Text)) return false; } return true; } /// /// Gets the first row number. /// /// private int GetFirstRowNumber() { Regex rowPatern = new Regex("^.*\\!\\$.*\\$(\\d*)$"); Match match = rowPatern.Match(names[0].Text); return Convert.ToInt32(match.Groups[1].Value); } /// /// Gets the column. /// /// The name. /// private string GetColumn(DefinedName name) { Regex rowPatern = new Regex("^.*\\!\\$(.*)\\$\\d*$"); Match match = rowPatern.Match(name.Text); return match.Groups[1].Value; } /// /// Gets the row. /// /// The worksheet. /// Index of the row. /// private Row GetRow(Worksheet worksheet, int rowIndex) { var sheetData = worksheet.GetFirstChild(); var elements = sheetData.Elements(); var row = elements.Where(r => r.RowIndex == rowIndex).FirstOrDefault(); return row; } /// /// Gets the work sheet part. /// /// private WorksheetPart GetWorkSheetPart() { //get worksheet based on defined name var sheet = document.WorkbookPart.Workbook.Descendants().FirstOrDefault(x => x.Name.Value == sheetN); string relId = sheet.Id; return (WorksheetPart)document.WorkbookPart.GetPartById(relId); } /// /// Gets the name of the sheet. /// /// private string GetSheetName() { Regex rowPatern = new Regex("^(.*)\\!\\$.*\\$\\d*$"); Match match = rowPatern.Match(names[0].Text); return match.Groups[1].Value; } /// /// Sets the property value. /// /// /// The value. /// The object. /// The property. private static void SetPropValue(object val, T obj, PropertyInfo prop) { try { Type PType = prop.PropertyType; if (val == null) prop.SetValue(obj, null, null); else if (PType == typeof(int) || PType == typeof(int?)) prop.SetValue(obj, Convert.ToInt32(val), null); else if (PType == typeof(Int16) || PType == typeof(Int16?)) prop.SetValue(obj, Convert.ToInt16(val), null); else if (PType == typeof(Int64) || PType == typeof(Int64?)) prop.SetValue(obj, Convert.ToInt64(val), null); else if (PType == typeof(Double) || PType == typeof(Double?)) { double? tDouble = null; if (val.ToString().ToLower().Contains("e")) tDouble = double.Parse(val.ToString(), NumberStyles.Any); else tDouble = Convert.ToDouble(val); prop.SetValue(obj, tDouble, null); } else if (PType == typeof(decimal) || PType == typeof(decimal?)) { decimal? tDecimal = null; if (val.ToString().ToLower().Contains("e")) tDecimal = decimal.Parse(val.ToString(), NumberStyles.Any); else tDecimal = Convert.ToDecimal(val); prop.SetValue(obj, tDecimal, null); } else if (PType == typeof(DateTime) || PType == typeof(DateTime?)) prop.SetValue(obj, Convert.ToDateTime(val), null); else if (PType == typeof(bool) || PType == typeof(bool?)) prop.SetValue(obj, Convert.ToBoolean(val), null); else if (PType == typeof(byte) || PType == typeof(byte?)) prop.SetValue(obj, Convert.ToByte(val), null); else if (PType == typeof(System.Windows.Media.Color)) { //Do nothing.. } else prop.SetValue(obj, val, null); } catch (Exception ex) { throw ex; } } #endregion #region IDisposable public void Dispose() { Close(); } #endregion } }