/*************************************************************************** Copyright (c) Microsoft Corporation 2011. This code is licensed using the Microsoft Public License (Ms-PL). The text of the license can be found here: http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx ***************************************************************************/ using System; using System.Collections.Generic; using System.Linq; using System.Xml.Linq; using DocumentFormat.OpenXml.Packaging; using System.Xml; namespace OpenXmlPowerTools { /// /// Provides access to worksheet operations /// public class WorksheetAccessor { private static XNamespace ns; private static XNamespace relationshipsns; static WorksheetAccessor() { ns = "http://schemas.openxmlformats.org/spreadsheetml/2006/main"; relationshipsns = "http://schemas.openxmlformats.org/officeDocument/2006/relationships"; } /// /// Returns a worksheet located at a specific index /// /// Index for the worksheet to be returned /// public static WorksheetPart Get(SpreadsheetDocument document, int worksheetIndex) { return document.WorkbookPart.WorksheetParts.ElementAt(worksheetIndex); } /// /// Returns the worksheet corresponding to the specified name /// /// Name for the worksheet to be returned /// public static WorksheetPart Get(SpreadsheetDocument document, string worksheetName) { XDocument workbook = document.WorkbookPart.GetXDocument(); XElement worksheetXelement = workbook.Root.Element(ns + "sheets") .Elements(ns + "sheet") .Where(s => s.Attribute("name").Value.ToLower().Equals(worksheetName.ToLower())).FirstOrDefault(); return (WorksheetPart)document.WorkbookPart .GetPartById(worksheetXelement.Attribute(relationshipsns + "id").Value); //return parentDocument.Document.WorkbookPart.WorksheetParts.FirstOrDefault(worksheet => worksheet.Uri.OriginalString.Split(new string[]{"/"},StringSplitOptions.RemoveEmptyEntries).Last().ToLower().Equals(worksheetName.ToLower()+".xml")); } /// /// Adds a given worksheet to the document /// /// Worksheet document to add /// Worksheet part just added public static WorksheetPart Add(SpreadsheetDocument doc, XDocument worksheet) { // Associates base content to a new worksheet part WorkbookPart workbook = doc.WorkbookPart; WorksheetPart worksheetPart = workbook.AddNewPart(); worksheetPart.PutXDocument(worksheet); // Associates the worksheet part to the workbook part XDocument document = doc.WorkbookPart.GetXDocument(); int sheetId = document.Root .Element(ns + "sheets") .Elements(ns + "sheet") .Count() + 1; int worksheetCount = document.Root .Element(ns + "sheets") .Elements(ns + "sheet") .Where( t => t.Attribute("name").Value.StartsWith("sheet", StringComparison.OrdinalIgnoreCase) ) .Count() + 1; // Adds content to workbook document to reference worksheet document document.Root .Element(ns + "sheets") .Add( new XElement(ns + "sheet", new XAttribute("name", string.Format("sheet{0}", worksheetCount)), new XAttribute("sheetId", sheetId), new XAttribute(relationshipsns + "id", workbook.GetIdOfPart(worksheetPart)) ) ); doc.WorkbookPart.PutXDocument(); return worksheetPart; } /// /// Creates element structure needed to describe an empty worksheet /// /// Document with contents for an empty worksheet private static XDocument CreateEmptyWorksheet() { XDocument document = new XDocument( new XElement(ns + "worksheet", new XAttribute("xmlns", ns), new XAttribute(XNamespace.Xmlns + "r", relationshipsns), new XElement(ns + "sheetData") ) ); return document; } /// /// Adds a value to a cell inside a worksheet document /// /// document to add values /// Row /// Column /// Value to add private static void AddValue(XDocument worksheet, int row, int column, string value) { //Set the cell reference string cellReference = GetColumnId(column) + row.ToString(); double numericValue; //Determining if value for cell is text or numeric bool valueIsNumeric = double.TryParse(value, out numericValue); //Creating the new cell element (markup) XElement newCellXElement = valueIsNumeric ? new XElement(ns + "c", new XAttribute("r", cellReference), new XElement(ns + "v", numericValue) ) : new XElement(ns + "c", new XAttribute("r", cellReference), new XAttribute("t", "inlineStr"), new XElement(ns + "is", new XElement(ns + "t", value) ) ); // Find the row containing the cell to add the value to XName rowName = "r"; XElement rowElement = worksheet.Root .Element(ns + "sheetData") .Elements(ns + "row") .Where( t => t.Attribute(rowName).Value == row.ToString() ) .FirstOrDefault(); if (rowElement == null) { //row element does not exist //create a new one rowElement = CreateEmptyRow(row); //row elements must appear in order inside sheetData element if (worksheet.Root .Element(ns + "sheetData").HasElements) { //if there are more rows already defined at sheetData element //find the row with the inmediate higher index for the row containing the cell to set the value to XElement rowAfterElement = FindRowAfter(worksheet, row); //if there is a row with an inmediate higher index already defined at sheetData if (rowAfterElement != null) { //add the new row before the row with an inmediate higher index rowAfterElement.AddBeforeSelf(rowElement); } else { //this row is going to be the one with the highest index (add it as the last element for sheetData) worksheet.Root.Element(ns + "sheetData").Elements(ns + "row").Last().AddAfterSelf(rowElement); } } else { //there are no other rows already defined at sheetData //Add a new row elemento to sheetData worksheet .Root .Element(ns + "sheetData") .Add( rowElement //= CreateEmptyRow(row) ); } //Add the new cell to the row Element rowElement.Add(newCellXElement); } else { //row containing the cell to set the value to is already defined at sheetData //look if cell already exist at that row XElement currentCellXElement = rowElement .Elements(ns + "c") .Where( t => t.Attribute("r").Value == cellReference ).FirstOrDefault(); if (currentCellXElement == null) { //cell element does not exist at row indicated as parameter //find the inmediate right column for the cell to set the value to XElement columnAfterXElement = FindColumAfter(worksheet, row, column); if (columnAfterXElement != null) { //Insert the new cell before the inmediate right column columnAfterXElement.AddBeforeSelf(newCellXElement); } else { //There is no inmediate right cell //Add the new cell as the last element for the row rowElement.Add(newCellXElement); } } else { //cell alreay exist //replace the current cell with that with the new value currentCellXElement.ReplaceWith(newCellXElement); } } } /// /// Finds the row with the inmediate higher index for a specific row /// /// Worksheet for finding the row /// Row index to look for inmediate higher row /// private static XElement FindRowAfter(XDocument worksheet, int row) { XElement rowAfterXElement = worksheet.Root .Element(ns + "sheetData") .Elements(ns + "row").FirstOrDefault(r => System.Convert.ToInt32(r.Attribute("r").Value) > row); return rowAfterXElement; } private static XElement FindColumAfter(XDocument worksheet, int row, int column) { XElement columnAfterXElement = worksheet.Root .Element(ns + "sheetData") .Elements(ns + "row").FirstOrDefault(r => System.Convert.ToInt32(r.Attribute("r").Value) == row) .Elements(ns + "c").FirstOrDefault(c => GetColumnNumberFromCellReference(c.Attribute("r").Value, row) > GetColumnNumberFromCellReference(GetColumnId(column) + row, row)); return columnAfterXElement; } /// /// Returns the column number from the cell reference received as parameter /// /// Cell reference to obtain the column number from /// Row containing the cell to obtain the column number from /// private static int GetColumnNumberFromCellReference(string cellReference, int row) { int columnNumber = 0; //Removing row number from cell reference string columnReference = cellReference.Remove(cellReference.Length - row.ToString().Length); int charPosition = 1; int charValue = 0; foreach (char c in columnReference) { //Getting the Unicode value for the current char in cell reference charValue = System.Convert.ToInt32(c); if (charPosition < columnReference.Length) { //we have not reached the last character in cell reference //we need to multiply the charValue (from 0 to 25) by 26 and add the result of powering 26 to current char position in cell reference //65 is the Unicode value for "A" letter //26 is the number of letters in English alphabet columnNumber += (((charValue - 65) * 26) + (System.Convert.ToInt32(Math.Pow(26, charPosition++)))); } else { //This is the last character in cell reference //we substract 64 instead of 65 because we want to get a one-based index for last character (instead of a zero-based index for previous characters) columnNumber += (charValue - 64); } } return columnNumber; } /// /// Creates tags to describe an empty row /// /// /// private static XElement CreateEmptyRow(int row) { XElement rowElement = new XElement(ns + "row", new XAttribute("r", row.ToString()) ); return rowElement; } /// /// Gets the column Id for a given column number /// /// Column number /// Column Id public static string GetColumnId(int columnNumber) { int alfa = (int)'Z' - (int)'A' + 1; if (columnNumber <= alfa) return ((char)((int)'A' + columnNumber - 1)).ToString(); else return GetColumnId( (int)((columnNumber - 1) / alfa) ) + ( (char)( (int)'A' + (int)((columnNumber - 1) % alfa) ) ).ToString(); } /// /// Creates a worksheet document and inserts data into it /// /// List of values that will act as the header /// Values for worksheet content /// Header row /// internal static WorksheetPart Create(SpreadsheetDocument document, List headerList, string[][] valueTable, int headerRow) { XDocument xDocument = CreateEmptyWorksheet(); for (int i = 0; i < headerList.Count; i++) { AddValue(xDocument, headerRow, i + 1, headerList[i]); } int rows = valueTable.GetLength(0); int cols = valueTable[0].GetLength(0); for (int i = 0; i < rows; i++) { for (int j = 0; j < cols; j++) { AddValue(xDocument, i + headerRow + 1, j + 1, valueTable[i][j]); } } WorksheetPart part = Add(document, xDocument); return part; } /// /// Get value for a cell in a worksheet /// /// /// /// /// Author:Johann Granados Company: Staff DotNet Creation Date: 8/30/2008 /// public static string GetValue(SpreadsheetDocument document, WorksheetPart worksheet, short column, int row) { XDocument worksheetXDocument = XDocument.Load(new XmlTextReader(worksheet.GetStream())); XElement cellValueXElement = GetCell(worksheetXDocument, column, row); if (cellValueXElement != null) { if (cellValueXElement.Element(ns + "v") != null) { return GetSharedString(document, System.Convert.ToInt32(cellValueXElement.Value)); } else { return cellValueXElement.Element(ns + "is").Element(ns + "t").Value; } } else { return string.Empty; } } private static string GetSharedString(SpreadsheetDocument document, int index) { XDocument sharedStringsXDocument = XDocument.Load(new XmlTextReader(document.WorkbookPart.SharedStringTablePart.GetStream())); return sharedStringsXDocument.Root.Elements().ElementAt(index).Value; } /// /// Set the value for a specific cell /// /// Worksheet part containing the cell to be affected /// Initial column for setting the value /// Initial row for setting the value /// Final column for setting the value /// Final row for setting the value /// Cell value public static OpenXmlPowerToolsDocument SetCellValue(SmlDocument doc, string worksheetName, int fromRow, int toRow, int fromColumn, int toColumn, string value) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc)) { using (SpreadsheetDocument document = streamDoc.GetSpreadsheetDocument()) { WorksheetPart worksheet = Get(document, worksheetName); XDocument worksheetXDocument = XDocument.Load(new XmlTextReader(worksheet.GetStream())); for (int row = fromRow; row <= toRow; row++) { for (int col = fromColumn; col <= toColumn; col++) { AddValue(worksheetXDocument, row, col, value); } } XmlWriter worksheetWriter = XmlTextWriter.Create(worksheet.GetStream(System.IO.FileMode.Create)); worksheetXDocument.WriteTo(worksheetWriter); worksheetWriter.Flush(); worksheetWriter.Close(); } return streamDoc.GetModifiedDocument(); } } /// /// Apply a cell style to a specific cell /// /// worksheet containing the cell to be affected /// Starting Cell Column /// Ending Cell Column /// Starting Cell Row /// Ending Cell Row /// Cell Style public static OpenXmlPowerToolsDocument SetCellStyle(SmlDocument doc, string worksheetName, short fromColumn, short toColumn, int fromRow, int toRow, string cellStyle) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc)) { using (SpreadsheetDocument document = streamDoc.GetSpreadsheetDocument()) { WorksheetPart worksheet = Get(document, worksheetName); XDocument worksheetXDocument = XDocument.Load(new XmlTextReader(worksheet.GetStream())); for (int row = fromRow; row <= toRow; row++) { for (short col = fromColumn; col <= toColumn; col++) { XElement cellXelement = GetCell(worksheetXDocument, col, row); cellXelement.SetAttributeValue("s", SpreadSheetStyleAccessor.GetCellStyleIndex(document, cellStyle)); } } XmlWriter worksheetWriter = XmlTextWriter.Create(worksheet.GetStream(System.IO.FileMode.Create)); worksheetXDocument.WriteTo(worksheetWriter); worksheetWriter.Flush(); worksheetWriter.Close(); } return streamDoc.GetModifiedDocument(); } } /// /// Set the width for a range of columns /// /// Worksheet containing the columns to be affected /// Initial column to affect /// Final column to affect /// Column width public static OpenXmlPowerToolsDocument SetColumnWidth(SmlDocument doc, string worksheetName, short fromColumn, short toColumn, int width) { using (OpenXmlMemoryStreamDocument streamDoc = new OpenXmlMemoryStreamDocument(doc)) { using (SpreadsheetDocument document = streamDoc.GetSpreadsheetDocument()) { WorksheetPart worksheet = Get(document, worksheetName); //Get the worksheet markup XDocument worksheetXDocument = XDocument.Load(new XmlTextReader(worksheet.GetStream())); //Look for worksheet cols element XElement colsXElement = worksheetXDocument.Root.Element(ns + "cols"); if (colsXElement == null) { //cols elements does not exist //create a new one colsXElement = new XElement(ns + "cols"); //create a new col element (for setting the width) //the col element could span more than one column -span is controlled by min (initial column) and max (final column) attributes colsXElement.Add(new XElement(ns + "col", new XAttribute("min", fromColumn.ToString()), new XAttribute("max", toColumn.ToString()), new XAttribute("width", width.ToString()), new XAttribute("customWidth", "1"))); //cols element must be added before worksheet sheetData element worksheetXDocument.Root.Element(ns + "sheetData").AddBeforeSelf(colsXElement); } else { //look for a col element for the column range indicated for fromColumn and toColumn XElement colXElement = colsXElement.Elements(ns + "col") .Where(c => (System.Convert.ToInt32(c.Attribute("min").Value) == fromColumn) && (System.Convert.ToInt32(c.Attribute("max").Value) == toColumn)).FirstOrDefault(); if (colXElement != null) { //col element does exist //change its width value colXElement.SetAttributeValue("width", width); } else { //col element does not exist //create a new one colsXElement.Add(new XElement(ns + "col", new XAttribute("min", fromColumn.ToString()), new XAttribute("max", toColumn.ToString()), new XAttribute("width", width.ToString()), new XAttribute("customWidth", "1"))); } } //Update the worksheet part markup at worksheet part stream XmlWriter worksheetWriter = XmlTextWriter.Create(worksheet.GetStream(System.IO.FileMode.Create)); worksheetXDocument.WriteTo(worksheetWriter); worksheetWriter.Flush(); worksheetWriter.Close(); } return streamDoc.GetModifiedDocument(); } } /// /// Return a XElement representing the markup for a specific cell in a SpreadsheetML document /// /// Worksheet markup /// Cell column /// Cell row /// private static XElement GetCell(XDocument worksheetXDocument, short column, int row) { return worksheetXDocument.Root .Element(ns + "sheetData") .Elements(ns + "row") .Where( r => r.Attribute("r").Value.Equals(row.ToString())).FirstOrDefault() .Elements(ns + "c").Where(c => c.Attribute("r").Value.Equals(GetColumnId(column) + row.ToString())).FirstOrDefault(); } } }