using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text; using System.IO; using System.Xml.Linq; using System.Collections; using System.Data; namespace FastExcel { /// /// Excel Worksheet /// public class Worksheet { /// /// Collection of rows in this worksheet /// public IEnumerable Rows { get; set; } /// /// Heading Names /// public IEnumerable Headings { get; set; } /// /// Index of this worksheet (Starts at 1) /// public int Index { get; internal set; } /// /// Name of this worksheet /// public string Name { get; set; } /// /// Is there any existing heading rows /// public int ExistingHeadingRows { get; set; } private int? InsertAfterIndex { get; set; } /// /// Template /// public bool Template { get; set; } internal string Headers { get; set; } internal string Footers { get; set; } /// /// Fast Excel /// public FastExcel FastExcel { get; private set; } internal string FileName { get { return GetFileName(Index); } } /// /// Get the internal file name of this worksheet /// internal static string GetFileName(int index) { return string.Format("xl/worksheets/sheet{0}.xml", index); } private const string DEFAULT_HEADERS = ""; private const string DEFAULT_FOOTERS = ""; /// /// Constructor /// public Worksheet() { } /// /// Constructor /// public Worksheet(FastExcel fastExcel) { FastExcel = fastExcel; } /// /// Populate rows /// public void PopulateRows(IEnumerable rows, int existingHeadingRows = 0, bool usePropertiesAsHeadings = false) { if ((rows.FirstOrDefault() as IEnumerable) == null) { PopulateRowsFromObjects(rows, existingHeadingRows, usePropertiesAsHeadings); } else { PopulateRowsFromIEnumerable(rows as IEnumerable>, existingHeadingRows); } } /// /// Populate rows from datatable /// public void PopulateRowsFromDataTable(DataTable table, int existingHeadingRows = 0) { var rowNumber = existingHeadingRows + 1; var headingColumnNumber = 1; var headingCells = (from c in table.Columns.OfType() select new Cell(headingColumnNumber++, c.ColumnName)).ToArray(); var headingRow = new Row(rowNumber++, headingCells); var newRows = new List() { headingRow }; for (var rowIndex = 0; rowIndex < table.Rows.Count; rowIndex++) { var cells = new List(); for (var columnIndex = 0; columnIndex < table.Columns.Count; columnIndex++) { object value = table.Rows[rowIndex].ItemArray[columnIndex]; if (value == null) continue; var cell = new Cell(columnIndex + 1, value); cells.Add(cell); } var row = new Row(rowNumber++, cells); newRows.Add(row); } Rows = newRows; } /// /// Get Header Column Name from [ExcelColumn(Name="column1")] or the property name /// private string GetHeaderName(PropertyInfo propertyInfo) { var descriptionAttribute = propertyInfo.GetCustomAttribute(); if (descriptionAttribute != null && !string.IsNullOrWhiteSpace(descriptionAttribute.Name)) { return descriptionAttribute.Name; } return propertyInfo.Name; } private void PopulateRowsFromObjects(IEnumerable rows, int existingHeadingRows = 0, bool usePropertiesAsHeadings = false) { int rowNumber = existingHeadingRows + 1; // Get all properties IEnumerable properties = typeof(T).GetRuntimeProperties(); var newRows = new List(); if (usePropertiesAsHeadings) { Headings = properties.Select(GetHeaderName); int headingColumnNumber = 1; IEnumerable headingCells = (from h in Headings select new Cell(headingColumnNumber++, h)).ToArray(); Row headingRow = new Row(rowNumber++, headingCells); newRows.Add(headingRow); } foreach (T rowObject in rows) { var cells = new List(); int columnNumber = 1; // Get value from each property foreach (PropertyInfo propertyInfo in properties) { object value = propertyInfo.GetValue(rowObject, null); if (value != null) { Cell cell = new Cell(columnNumber, value); cells.Add(cell); } columnNumber++; } var row = new Row(rowNumber++, cells); newRows.Add(row); } Rows = newRows; } private void PopulateRowsFromIEnumerable(IEnumerable> rows, int existingHeadingRows = 0) { int rowNumber = existingHeadingRows + 1; var newRows = new List(); foreach (IEnumerable rowOfObjects in rows) { var cells = new List(); int columnNumber = 1; foreach (object value in rowOfObjects) { if (value != null) { var cell = new Cell(columnNumber, value); cells.Add(cell); } columnNumber++; } Row row = new Row(rowNumber++, cells); newRows.Add(row); } Rows = newRows; } /// /// Add a row using a collection of value objects /// /// Collection of objects public void AddRow(params object[] cellValues) { if (Rows == null) { Rows = new List(); } var cells = new List(); int columnNumber = 1; foreach (object value in cellValues) { if (value != null) { var cell = new Cell(columnNumber++, value); cells.Add(cell); } else { columnNumber++; } } var row = new Row(Rows.Count() + 1, cells); (Rows as List).Add(row); } /// /// Note: This method is slow /// public void AddValue(int rowNumber, int columnNumber, object value) { if (Rows == null) { Rows = new List(); } Row row = (from r in Rows where r.RowNumber == rowNumber select r).FirstOrDefault(); Cell cell = null; if (row == null) { cell = new Cell(columnNumber, value); row = new Row(rowNumber, new List { cell }); (Rows as List).Add(row); } if (cell == null) { cell = (from c in row.Cells where c.ColumnNumber == columnNumber select c).FirstOrDefault(); if (cell == null) { cell = new Cell(columnNumber, value); (row.Cells as List).Add(cell); } } } /// /// Merges the parameter into the current DatSet object, the parameter takes precedence /// /// A DataSet to merge public void Merge(Worksheet data) { // Merge headings if (Headings == null || !Headings.Any()) { Headings = data.Headings; } // Merge rows Rows = data.MergeRows(Rows); // data.Rows = MergeRows(data.Rows); } private IEnumerable MergeRows(IEnumerable rows) { foreach (var row in Rows.Union(rows).GroupBy(r => r.RowNumber)) { int count = row.Count(); if (count == 1) { yield return row.First(); } else { row.First().Merge(row.Skip(1).First()); yield return row.First(); } } } /// /// Does the file exist /// public bool Exists { get { return !string.IsNullOrEmpty(FileName); } } internal void Read(int? sheetNumber = null, string sheetName = null, int existingHeadingRows = 0) { GetWorksheetProperties(FastExcel, sheetNumber, sheetName); Read(existingHeadingRows); } /// /// Read the worksheet /// /// public void Read(int existingHeadingRows = 0) { FastExcel.CheckFiles(); FastExcel.PrepareArchive(); ExistingHeadingRows = existingHeadingRows; IEnumerable rows = null; var headings = new List(); using (Stream stream = FastExcel.Archive.GetEntry(FileName).Open()) { var document = XDocument.Load(stream); int skipRows = 0; var rowElement = document.Descendants().FirstOrDefault(d => d.Name.LocalName == "row"); if (rowElement != null) { var possibleHeadingRow = new Row(rowElement, this); if (ExistingHeadingRows == 1 && possibleHeadingRow.RowNumber == 1) { foreach (var headerCell in possibleHeadingRow.Cells) { headings.Add(headerCell.Value.ToString()); } } } rows = GetRows(document.Descendants().Where(d => d.Name.LocalName == "row").Skip(skipRows)); } Headings = headings; Rows = rows; } /// /// Returns cells using provided range /// /// Definition of range to use /// public IEnumerable GetCellsInRange(CellRange cellRange) { IEnumerable rows; if (cellRange.RowEnd.HasValue) { rows = (from row in Rows where row.RowNumber >= cellRange.RowStart && row.RowNumber <= cellRange.RowEnd select row); } else { rows = (from row in Rows where row.RowNumber >= cellRange.RowStart select row); } var rangeResult = new List(); foreach (Row row in rows) { rangeResult.InsertRange(rangeResult.Count, (from cell in row.Cells where cell.ColumnNumber >= Cell.GetExcelColumnNumber(cellRange.ColumnStart) && cell.ColumnNumber <= Cell.GetExcelColumnNumber(cellRange.ColumnEnd) select cell).ToList()); } return rangeResult; } private IEnumerable GetRows(IEnumerable rowElements) { foreach (var rowElement in rowElements) { yield return new Row(rowElement, this); } } /// /// Read the existing sheet and copy some of the existing content /// /// Worksheet stream /// Saves the header and footer to the worksheet internal void ReadHeadersAndFooters(StreamReader stream, ref Worksheet worksheet) { var headers = new StringBuilder(); var footers = new StringBuilder(); bool headersComplete = false; bool rowsComplete = false; int existingHeadingRows = worksheet.ExistingHeadingRows; while (stream.Peek() >= 0) { string line = stream.ReadLine(); int currentLineIndex = 0; if (!headersComplete) { if (line.Contains("")) { currentLineIndex = line.IndexOf(""); headers.Append(line.Substring(0, currentLineIndex)); //remove the read section from line line = line.Substring(currentLineIndex, line.Length - currentLineIndex); headers.Append(""); // Headers complete now skip any content and start footer headersComplete = true; footers = new StringBuilder(); footers.Append(""); //There is no rows rowsComplete = true; } else if (line.Contains("")) { currentLineIndex = line.IndexOf(""); headers.Append(line.Substring(0, currentLineIndex)); //remove the read section from line line = line.Substring(currentLineIndex, line.Length - currentLineIndex); headers.Append(""); // Headers complete now skip any content and start footer headersComplete = true; footers = new StringBuilder(); footers.Append(""); } else { headers.Append(line); } } if (headersComplete && !rowsComplete) { if (existingHeadingRows == 0) { rowsComplete = true; } if (!rowsComplete) { while (!string.IsNullOrEmpty(line) && existingHeadingRows != 0) { if (line.Contains("")) { int index = line.IndexOf("") + "".Length; headers.Append(line.Substring(index, currentLineIndex - index)); //remove the read section from line line = line.Substring(currentLineIndex, line.Length - currentLineIndex); existingHeadingRows--; } else { int index = line.IndexOf("")) { currentLineIndex = line.IndexOf("") + "".Length; headers.Append(line.Substring(0, currentLineIndex)); //remove the read section from line line = line.Substring(currentLineIndex, line.Length - currentLineIndex); existingHeadingRows--; } } } if (existingHeadingRows == 0) { rowsComplete = true; } } if (rowsComplete) { if (line.Contains("")) { int index = line.IndexOf("") + "".Length; var foot = line.Substring(index, line.Length - index); footers.Append(foot); } else if (line.Contains("")) { int index = line.IndexOf("") + "".Length; footers.Append(line.Substring(index, line.Length - index)); } else { footers.Append(line); } } } worksheet.Headers = headers.ToString(); worksheet.Footers = footers.ToString(); } /// /// Get worksheet file name from xl/workbook.xml /// internal void GetWorksheetProperties(FastExcel fastExcel, int? sheetNumber = null, string sheetName = null) { GetWorksheetPropertiesAndValidateNewName(fastExcel, sheetNumber, sheetName); } private bool GetWorksheetPropertiesAndValidateNewName(FastExcel fastExcel, int? sheetNumber = null, string sheetName = null, string newSheetName = null) { FastExcel = fastExcel; bool newSheetNameExists = false; FastExcel.CheckFiles(); FastExcel.PrepareArchive(); //If index has already been loaded then we can skip this function if (Index != 0) { return true; } if (!sheetNumber.HasValue && string.IsNullOrEmpty(sheetName)) { throw new Exception("No worksheet name or number was specified"); } using (Stream stream = FastExcel.Archive.GetEntry("xl/workbook.xml").Open()) { var document = XDocument.Load(stream); if (document == null) { throw new Exception("Unable to load workbook.xml"); } var sheetsElements = document.Descendants().Where(d => d.Name.LocalName == "sheet").ToList(); XElement sheetElement = null; if (sheetNumber.HasValue) { if (sheetNumber.Value <= sheetsElements.Count) { sheetElement = sheetsElements[sheetNumber.Value - 1]; } else { throw new Exception(string.Format("There is no sheet at index '{0}'", sheetNumber)); } } else if (!string.IsNullOrEmpty(sheetName)) { sheetElement = (from sheet in sheetsElements from attribute in sheet.Attributes() where attribute.Name == "name" && attribute.Value.Equals(sheetName, StringComparison.OrdinalIgnoreCase) select sheet).FirstOrDefault(); if (sheetElement == null) { throw new Exception(string.Format("There is no sheet named '{0}'", sheetName)); } if (!string.IsNullOrEmpty(newSheetName)) { newSheetNameExists = (from sheet in sheetsElements from attribute in sheet.Attributes() where attribute.Name == "name" && attribute.Value.Equals(newSheetName, StringComparison.OrdinalIgnoreCase) select sheet).Any(); if (FastExcel.MaxSheetNumber == 0) { FastExcel.MaxSheetNumber = (from sheet in sheetsElements from attribute in sheet.Attributes() where attribute.Name == "sheetId" select int.Parse(attribute.Value)).Max(); } } } Index = sheetsElements.IndexOf(sheetElement) + 1; Name = (from attribute in sheetElement.Attributes() where attribute.Name == "name" select attribute.Value).FirstOrDefault(); } if (!Exists) { throw new Exception("No worksheet was found with the name or number was specified"); } if (string.IsNullOrEmpty(newSheetName)) { return false; } else { return !newSheetNameExists; } } internal void ValidateNewWorksheet(FastExcel fastExcel, int? insertAfterSheetNumber = null, string insertAfterSheetName = null) { if (string.IsNullOrEmpty(Name)) { // TODO possibly could calulcate a new worksheet name throw new Exception("Name for new worksheet is not specified"); } // Get worksheet details var previousWorksheet = new Worksheet(fastExcel); bool isNameValid = previousWorksheet.GetWorksheetPropertiesAndValidateNewName(fastExcel, insertAfterSheetNumber, insertAfterSheetName, Name); InsertAfterIndex = previousWorksheet.Index; if (!isNameValid) { throw new Exception(string.Format("Worksheet name '{0}' already exists", Name)); } fastExcel.MaxSheetNumber += 1; Index = fastExcel.MaxSheetNumber; if (string.IsNullOrEmpty(Headers)) { Headers = DEFAULT_HEADERS; } if (string.IsNullOrEmpty(Footers)) { Footers = DEFAULT_FOOTERS; } } internal WorksheetAddSettings AddSettings { get { if (InsertAfterIndex.HasValue) { return new WorksheetAddSettings() { Name = Name, SheetId = Index, InsertAfterSheetId = InsertAfterIndex.Value }; } else { return null; } } } } }