/******************************************************************************* * You may amend and distribute as you like, but don't remove this header! * * EPPlus provides server-side generation of Excel 2007/2010 spreadsheets. * See http://www.codeplex.com/EPPlus for details. * * Copyright (C) 2011 Jan Källman * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * The GNU Lesser General Public License can be viewed at http://www.opensource.org/licenses/lgpl-license.php * If you unfamiliar with this license or have questions about it, here is an http://www.gnu.org/licenses/gpl-faq.html * * All code and executables are provided "as is" with no warranty either express or implied. * The author accepts no liability for any damage or loss of business that this product may cause. * * Code change notes: * * Author Change Date * ****************************************************************************** * Jan Källman Initial Release 2011-11-02 * Jan Källman Total rewrite 2010-03-01 * Jan Källman License changed GPL-->LGPL 2011-12-27 *******************************************************************************/ using System; using System.Xml; using System.Collections.Generic; using System.IO; using System.IO.Packaging; using System.Configuration; using OfficeOpenXml.Drawing; using System.Diagnostics; using OfficeOpenXml.Style; using System.Globalization; using System.Text; using System.Security; using OfficeOpenXml.Drawing.Chart; using OfficeOpenXml.Style.XmlAccess; using System.Text.RegularExpressions; using OfficeOpenXml.Drawing.Vml; using OfficeOpenXml.Table; using OfficeOpenXml.DataValidation; using OfficeOpenXml.Table.PivotTable; using System.ComponentModel; using System.Drawing; using OfficeOpenXml.ConditionalFormatting; using System.Web; namespace OfficeOpenXml { /// /// Worksheet hidden enumeration /// public enum eWorkSheetHidden { /// /// The worksheet is visible /// Visible, /// /// The worksheet is hidden but can be shown by the user via the user interface /// Hidden, /// /// The worksheet is hidden and cannot be shown by the user via the user interface /// VeryHidden } /// /// Represents an Excel worksheet and provides access to its properties and methods /// public sealed class ExcelWorksheet : XmlHelper { internal class Formulas { internal int Index { get; set; } internal string Address { get; set; } internal bool IsArray { get; set; } public string Formula { get; set; } public int StartRow { get; set; } public int StartCol { get; set; } } /// /// Collection containing merged cell addresses /// /// public class MergeCellsCollection : IEnumerable { private List _list = new List(); internal MergeCellsCollection() { } internal List List { get {return _list;} } public T this[int Index] { get { return _list[Index]; } } public int Count { get { return _list.Count; } } internal void Remove(T Item) { _list.Remove(Item); } #region IEnumerable Members public IEnumerator GetEnumerator() { return _list.GetEnumerator(); } #endregion #region IEnumerable Members System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return _list.GetEnumerator(); } #endregion } internal RangeCollection _cells; internal RangeCollection _columns; internal RangeCollection _rows; internal Dictionary _sharedFormulas = new Dictionary(); internal Dictionary _arrayFormulas = new Dictionary(); internal RangeCollection _formulaCells; internal int _minCol = ExcelPackage.MaxColumns; internal int _maxCol = 0; internal List _hyperLinkCells; //Used when saving the sheet #region Worksheet Private Properties internal ExcelPackage _package; private Uri _worksheetUri; private string _name; private int _sheetID; private int _positionID; private string _relationshipID; private XmlDocument _worksheetXml; internal ExcelWorksheetView _sheetView; internal ExcelHeaderFooter _headerFooter; #endregion #region ExcelWorksheet Constructor /// /// A worksheet /// /// Namespacemanager /// Package /// Relationship ID /// URI /// Name of the sheet /// Sheet id /// Position /// hide public ExcelWorksheet(XmlNamespaceManager ns, ExcelPackage excelPackage, string relID, Uri uriWorksheet, string sheetName, int sheetID, int positionID, eWorkSheetHidden hide) : base(ns, null) { SchemaNodeOrder = new string[] { "sheetPr", "tabColor", "outlinePr", "pageSetUpPr", "dimension", "sheetViews", "sheetFormatPr", "cols", "sheetData", "sheetProtection", "protectedRanges","scenarios", "autoFilter", "sortState", "dataConsolidate", "customSheetViews", "customSheetViews", "mergeCells", "phoneticPr", "conditionalFormatting", "dataValidations", "hyperlinks", "printOptions", "pageMargins", "pageSetup", "headerFooter", "linePrint", "rowBreaks", "colBreaks", "customProperties", "cellWatches", "ignoredErrors", "smartTags", "drawing", "legacyDrawing", "legacyDrawingHF", "picture", "oleObjects", "activeXControls", "webPublishItems", "tableParts" , "extLst" }; _package = excelPackage; _relationshipID = relID; _worksheetUri = uriWorksheet; _name = sheetName; _sheetID = sheetID; _positionID = positionID; Hidden = hide; _names = new ExcelNamedRangeCollection(Workbook,this); CreateXml(); TopNode = _worksheetXml.DocumentElement; } #endregion /// /// The Uri to the worksheet within the package /// internal Uri WorksheetUri { get { return (_worksheetUri); } } /// /// The PackagePart for the worksheet within the package /// internal PackagePart Part { get { return (_package.Package.GetPart(WorksheetUri)); } } /// /// The ID for the worksheet's relationship with the workbook in the package /// internal string RelationshipID { get { return (_relationshipID); } } /// /// The unique identifier for the worksheet. /// internal int SheetID { get { return (_sheetID); } } /// /// The position of the worksheet. /// internal int PositionID { get { return (_positionID); } set { _positionID = value; } } #region Worksheet Public Properties /// /// The index in the worksheets collection /// public int Index { get { return (_positionID); } } /// /// Address for autofilter /// /// public ExcelAddressBase AutoFilterAddress { get { string address = GetXmlNodeString("d:autoFilter/@ref"); if (address == "") { return null; } else { return new ExcelAddressBase(address); } } internal set { SetXmlNodeString("d:autoFilter/@ref", value.Address); } } /// /// Returns a ExcelWorksheetView object that allows you to set the view state properties of the worksheet /// public ExcelWorksheetView View { get { if (_sheetView == null) { XmlNode node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager); if (node == null) { CreateNode("d:sheetViews/d:sheetView"); //this one shouls always exist. but check anyway node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager); } _sheetView = new ExcelWorksheetView(NameSpaceManager, node, this); } return (_sheetView); } } /// /// The worksheet's display name as it appears on the tab /// public string Name { get { return (_name); } set { if (value == _name) return; Name=_package.Workbook.Worksheets.ValidateFixSheetName(Name); _package.Workbook.SetXmlNodeString(string.Format("d:sheets/d:sheet[@sheetId={0}]/@name", _sheetID), value); _name = value; } } internal ExcelNamedRangeCollection _names; /// /// Provides access to named ranges /// public ExcelNamedRangeCollection Names { get { return _names; } } /// /// Indicates if the worksheet is hidden in the workbook /// public eWorkSheetHidden Hidden { get { string state=_package.Workbook.GetXmlNodeString(string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", _sheetID)); if (state == "hidden") { return eWorkSheetHidden.Hidden; } else if (state == "veryHidden") { return eWorkSheetHidden.VeryHidden; } return eWorkSheetHidden.Visible; } set { if (value == eWorkSheetHidden.Visible) { _package.Workbook.DeleteNode(string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", _sheetID)); } else { string v; v=value.ToString(); v=v.Substring(0,1).ToLower()+v.Substring(1); _package.Workbook.SetXmlNodeString(string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", _sheetID),v ); } } } double _defaultRowHeight = double.NaN; /// /// Get/set the default height of all rows in the worksheet /// public double DefaultRowHeight { get { if(double.IsNaN(_defaultRowHeight)) { _defaultRowHeight = GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight"); if(double.IsNaN(_defaultRowHeight)) { _defaultRowHeight = 15; // Excel default height } } return _defaultRowHeight; } set { _defaultRowHeight = value; SetXmlNodeString("d:sheetFormatPr/@defaultRowHeight", value.ToString(CultureInfo.InvariantCulture)); SetXmlNodeBool("d:sheetFormatPr/@customHeight", value != 15); if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth"))) { DefaultColWidth = 9.140625; } } } /// /// Get/set the default width of all rows in the worksheet /// public double DefaultColWidth { get { double ret = GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth"); if (double.IsNaN(ret)) { ret = 9.140625; // Excel's default width } return ret; } set { SetXmlNodeString("d:sheetFormatPr/@defaultColWidth", value.ToString(CultureInfo.InvariantCulture)); if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight"))) { DefaultRowHeight = 15; } } } /** **/ const string outLineSummaryBelowPath = "d:sheetPr/d:outlinePr/@summaryBelow"; /// /// Summary rows below details /// public bool OutLineSummaryBelow { get { return GetXmlNodeBool(outLineSummaryBelowPath); } set { SetXmlNodeString(outLineSummaryBelowPath, value ? "1" : "0"); } } const string outLineSummaryRightPath = "d:sheetPr/d:outlinePr/@summaryRight"; /// /// Summary rows to right of details /// public bool OutLineSummaryRight { get { return GetXmlNodeBool(outLineSummaryRightPath); } set { SetXmlNodeString(outLineSummaryRightPath, value ? "1" : "0"); } } const string outLineApplyStylePath = "d:sheetPr/d:outlinePr/@applyStyles"; /// /// Automatic styles /// public bool OutLineApplyStyle { get { return GetXmlNodeBool(outLineApplyStylePath); } set { SetXmlNodeString(outLineApplyStylePath, value ? "1" : "0"); } } const string tabColorPath = "d:sheetPr/d:tabColor/@rgb"; /// /// Color of the sheet tab /// public Color TabColor { get { string col = GetXmlNodeString(tabColorPath); if (col == "") { return Color.Empty; } else { return Color.FromArgb(int.Parse(col, System.Globalization.NumberStyles.AllowHexSpecifier)); } } set { SetXmlNodeString(tabColorPath, value.ToArgb().ToString("X")); } } const string codeModuleNamePath = "d:sheetPr/@codeName"; internal string CodeModuleName { get { return GetXmlNodeString(codeModuleNamePath); } set { SetXmlNodeString(codeModuleNamePath, value); } } internal void CodeNameChange(string value) { CodeModuleName = value; } public VBA.ExcelVBAModule CodeModule { get { if (_package.Workbook.VbaProject != null) { return _package.Workbook.VbaProject.Modules[CodeModuleName]; } else { return null; } } } #region WorksheetXml /// /// The XML document holding the worksheet data. /// All column, row, cell, pagebreak, merged cell and hyperlink-data are loaded into memory and removed from the document when loading the document. /// public XmlDocument WorksheetXml { get { return (_worksheetXml); } } internal ExcelVmlDrawingCommentCollection _vmlDrawings = null; /// /// Vml drawings. underlaying object for comments /// internal ExcelVmlDrawingCommentCollection VmlDrawingsComments { get { if (_vmlDrawings == null) { CreateVmlCollection(); } return _vmlDrawings; } } internal ExcelCommentCollection _comments = null; /// /// Collection of comments /// public ExcelCommentCollection Comments { get { if (_comments == null) { CreateVmlCollection(); _comments = new ExcelCommentCollection(_package, this, NameSpaceManager); } return _comments; } } private void CreateVmlCollection() { var vmlNode = _worksheetXml.DocumentElement.SelectSingleNode("d:legacyDrawing/@r:id", NameSpaceManager); if (vmlNode == null) { _vmlDrawings = new ExcelVmlDrawingCommentCollection(_package, this, null); } else { if (Part.RelationshipExists(vmlNode.Value)) { var rel = Part.GetRelationship(vmlNode.Value); var vmlUri = PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); _vmlDrawings = new ExcelVmlDrawingCommentCollection(_package, this, vmlUri); _vmlDrawings.RelId = rel.Id; } } } private void CreateXml() { _worksheetXml = new XmlDocument(); _worksheetXml.PreserveWhitespace = ExcelPackage.preserveWhitespace; PackagePart packPart = _package.Package.GetPart(WorksheetUri); string xml = ""; // First Columns, rows, cells, mergecells, hyperlinks and pagebreakes are loaded from a xmlstream to optimize speed... bool doAdjust = _package.DoAdjustDrawings; _package.DoAdjustDrawings = false; Stream stream = packPart.GetStream(); XmlTextReader xr = new XmlTextReader(stream); xr.ProhibitDtd = true; LoadColumns(xr); //columnXml long start = stream.Position; LoadCells(xr); long end = stream.Position; LoadMergeCells(xr); LoadHyperLinks(xr); LoadRowPageBreakes(xr); LoadColPageBreakes(xr); //...then the rest of the Xml is extracted and loaded into the WorksheetXml document. stream.Seek(0, SeekOrigin.Begin); Encoding encoding; xml = GetWorkSheetXml(stream, start, end, out encoding); //first char is invalid sometimes?? if (xml[0] != '<') LoadXmlSafe(_worksheetXml, xml.Substring(1, xml.Length - 1), encoding); else LoadXmlSafe(_worksheetXml, xml, encoding); _package.DoAdjustDrawings = doAdjust; ClearNodes(); } private void LoadRowPageBreakes(XmlTextReader xr) { if(!ReadUntil(xr, "rowBreaks","colBreaks")) return; while (xr.Read()) { if (xr.LocalName == "brk") { int id; if (int.TryParse(xr.GetAttribute("id"), out id)) { Row(id).PageBreak = true; } } else { break; } } } private void LoadColPageBreakes(XmlTextReader xr) { if (!ReadUntil(xr, "colBreaks")) return; while (xr.Read()) { if (xr.LocalName == "brk") { int id; if (int.TryParse(xr.GetAttribute("id"), out id)) { Column(id).PageBreak = true; } } else { break; } } } private void ClearNodes() { if (_worksheetXml.SelectSingleNode("//d:cols", NameSpaceManager)!=null) { _worksheetXml.SelectSingleNode("//d:cols", NameSpaceManager).RemoveAll(); } if (_worksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager) != null) { _worksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager).RemoveAll(); } if (_worksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager) != null) { _worksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager).RemoveAll(); } if (_worksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager) != null) { _worksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager).RemoveAll(); } if (_worksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager) != null) { _worksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager).RemoveAll(); } } const int BLOCKSIZE=8192; private string GetWorkSheetXml(Stream stream, long start, long end, out Encoding encoding) { StreamReader sr = new StreamReader(stream); int length = 0; char[] block; int pos; StringBuilder sb = new StringBuilder(); Match startmMatch, endMatch; do { int size = stream.Length < BLOCKSIZE ? (int)stream.Length : BLOCKSIZE; block = new char[size]; pos = sr.ReadBlock(block, 0, size); sb.Append(block,0,pos); length += size; } while (length < start + 20 && length < end); startmMatch = Regex.Match(sb.ToString(), string.Format("(<[^>]*{0}[^>]*>)", "sheetData")); if (!startmMatch.Success) //Not found { encoding = sr.CurrentEncoding; return sb.ToString(); } else { string s = sb.ToString(); string xml = s.Substring(0, startmMatch.Index); if(startmMatch.Value.EndsWith("/>")) { xml += s.Substring(startmMatch.Index, s.Length - startmMatch.Index); } else { if (sr.Peek() != -1) { if (end - BLOCKSIZE > 0) { long endSeekStart = end - BLOCKSIZE - 4096 < 0 ? 0 : (end - BLOCKSIZE - 4096); stream.Seek(endSeekStart, SeekOrigin.Begin); int size = (int)(stream.Length-endSeekStart); block = new char[size]; sr = new StreamReader(stream); pos = sr.ReadBlock(block, 0, size); sb = new StringBuilder(); sb.Append(block, 0, pos); s = sb.ToString(); } } endMatch = Regex.Match(s, string.Format("(]*{0}[^>]*>)", "sheetData")); xml += "" + s.Substring(endMatch.Index + endMatch.Length, s.Length - (endMatch.Index + endMatch.Length)); } if (sr.Peek() > -1) { xml += sr.ReadToEnd(); } encoding = sr.CurrentEncoding; return xml; } } private void GetBlockPos(string xml, string tag, ref int start, ref int end) { Match startmMatch, endMatch; startmMatch = Regex.Match(xml.Substring(start), string.Format("(<[^>]*{0}[^>]*>)", tag)); //"<[a-zA-Z:]*" + tag + "[?]*>"); if (!startmMatch.Success) //Not found { start = -1; end = -1; return; } var startPos=startmMatch.Index+start; if(startmMatch.Value.Substring(startmMatch.Value.Length-2,1)=="/") { end = startPos + startmMatch.Length; } else { endMatch = Regex.Match(xml.Substring(start), string.Format("(]*{0}[^>]*>)", tag)); if (endMatch.Success) { end = endMatch.Index + endMatch.Length + start; } } start = startPos; } private bool ReadUntil(XmlTextReader xr,params string[] tagName) { if (xr.EOF) return false; while (!Array.Exists(tagName, tag => xr.LocalName.EndsWith(tag))) { xr.Read(); if (xr.EOF) return false; } return (xr.LocalName.EndsWith(tagName[0])); } private void LoadColumns (XmlTextReader xr)//(string xml) { var colList = new List(); if (ReadUntil(xr, "cols", "sheetData")) { //if (xml != "") //{ //var xr=new XmlTextReader(new StringReader(xml)); while(xr.Read()) { if(xr.LocalName!="col") break; int min = int.Parse(xr.GetAttribute("min")); int style; if (xr.GetAttribute("style") == null || !int.TryParse(xr.GetAttribute("style"), out style)) { style = 0; } ExcelColumn col = new ExcelColumn(this, min); col._columnMax = int.Parse(xr.GetAttribute("max")); col.StyleID = style; col.Width = xr.GetAttribute("width") == null ? 0 : double.Parse(xr.GetAttribute("width"), CultureInfo.InvariantCulture); col.BestFit = xr.GetAttribute("bestFit") != null && xr.GetAttribute("bestFit") == "1" ? true : false; col.Collapsed = xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed") == "1" ? true : false; col.Phonetic = xr.GetAttribute("phonetic") != null && xr.GetAttribute("phonetic") == "1" ? true : false; col.OutlineLevel = xr.GetAttribute("outlineLevel") == null ? 0 : int.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture); col.Hidden = xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false; colList.Add(col); } } _columns = new RangeCollection(colList); } /// /// Read until the node is found. If not found the xmlreader is reseted. /// /// The reader /// Text to search for /// Alternative text to search for /// private static bool ReadXmlReaderUntil(XmlTextReader xr, string nodeText, string altNode) { do { if (xr.LocalName == nodeText || xr.LocalName == altNode) return true; } while(xr.Read()); xr.Close(); return false; } /// /// Load Hyperlinks /// /// The reader private void LoadHyperLinks(XmlTextReader xr) { if(!ReadUntil(xr, "hyperlinks", "rowBreaks", "colBreaks")) return; while (xr.Read()) { if (xr.LocalName == "hyperlink") { int fromRow, fromCol, toRow, toCol; ExcelCell.GetRowColFromAddress(xr.GetAttribute("ref"), out fromRow, out fromCol, out toRow, out toCol); ulong id = ExcelCell.GetCellID(_sheetID, fromRow, fromCol); ExcelCell cell = _cells[id] as ExcelCell; if (xr.GetAttribute("id", ExcelPackage.schemaRelationships) != null) { cell.HyperLinkRId = xr.GetAttribute("id", ExcelPackage.schemaRelationships); var uri = Part.GetRelationship(cell.HyperLinkRId).TargetUri; try { if (uri.IsAbsoluteUri) { cell.Hyperlink = new ExcelHyperLink(HttpUtility.UrlDecode(uri.OriginalString)); } else { cell.Hyperlink = new ExcelHyperLink(HttpUtility.UrlDecode(uri.OriginalString), UriKind.Relative); } } catch { //We should never end up here, but to aviod unhandled exceptions we just set the uri here. JK cell.Hyperlink = uri; } Part.DeleteRelationship(cell.HyperLinkRId); //Delete the relationship, it is recreated when we save the package. } else if (xr.GetAttribute("location") != null) { ExcelHyperLink hl = new ExcelHyperLink(xr.GetAttribute("location"), xr.GetAttribute("display")); hl.RowSpann = toRow - fromRow; hl.ColSpann = toCol - fromCol; cell.Hyperlink = hl; } string tt = xr.GetAttribute("tooltip"); if (!string.IsNullOrEmpty(tt)) { ((ExcelHyperLink)cell.Hyperlink).ToolTip = tt; } } else { break; } } } /// /// Load cells /// /// The reader private void LoadCells(XmlTextReader xr) { var cellList=new List(); var rowList = new List(); var formulaList = new List(); ReadUntil(xr, "sheetData", "mergeCells", "hyperlinks", "rowBreaks", "colBreaks"); ExcelCell cell = null; xr.Read(); while (!xr.EOF) { while (xr.NodeType == XmlNodeType.EndElement) { xr.Read(); } if (xr.LocalName == "row") { int row = Convert.ToInt32(xr.GetAttribute("r")); if (xr.AttributeCount > 2 || (xr.AttributeCount == 2 && xr.GetAttribute("spans") != null)) { rowList.Add(AddRow(xr, row)); } xr.Read(); } else if (xr.LocalName == "c") { if (cell != null) cellList.Add(cell); cell = new ExcelCell(this, xr.GetAttribute("r")); if (xr.GetAttribute("t") != null) cell.DataType = xr.GetAttribute("t"); cell.StyleID = xr.GetAttribute("s") == null ? 0 : int.Parse(xr.GetAttribute("s")); xr.Read(); } else if (xr.LocalName == "v") { cell._value = GetValueFromXml(cell, xr); xr.Read(); } else if (xr.LocalName == "f") { string t = xr.GetAttribute("t"); if (t == null) { cell._formula = xr.ReadElementContentAsString(); formulaList.Add(cell); } else if (t == "shared") { string si = xr.GetAttribute("si"); if (si != null) { cell._sharedFormulaID = int.Parse(si); string address = xr.GetAttribute("ref"); string formula = xr.ReadElementContentAsString(); if (formula != "") { _sharedFormulas.Add(cell.SharedFormulaID, new Formulas() { Index = cell.SharedFormulaID, Formula = formula, Address = address, StartRow = cell.Row, StartCol = cell.Column }); } } else { xr.Read(); //Something is wrong in the sheet, read next } } else if (t == "array") //TODO: Array functions are not support yet. Read the formula for the start cell only. { string address = xr.GetAttribute("ref"); cell._formula = xr.ReadElementContentAsString(); cell._sharedFormulaID = GetMaxShareFunctionIndex(true); //We use the shared formula id here, just so we can use the same dictionary for both Array and Shared forulas. _sharedFormulas.Add(cell._sharedFormulaID, new Formulas() { Index = cell._sharedFormulaID, Formula = cell._formula, Address = address, StartRow = cell.Row, StartCol = cell.Column, IsArray = true }); } else // ??? some other type { xr.Read(); //Something is wrong in the sheet, read next } } else if (xr.LocalName == "is") //Inline string { xr.Read(); if (xr.LocalName == "t") { cell._value = xr.ReadInnerXml(); } else { cell._value = xr.ReadOuterXml(); cell.IsRichText = true; } } else { break; } } if (cell != null) cellList.Add(cell); _cells = new RangeCollection(cellList); _rows = new RangeCollection(rowList); _formulaCells = new RangeCollection(formulaList); } /// /// Load merged cells /// /// private void LoadMergeCells(XmlTextReader xr) { if(ReadUntil(xr, "mergeCells", "hyperlinks", "rowBreaks", "colBreaks") && !xr.EOF) { while (xr.Read()) { if (xr.LocalName != "mergeCell") break; string address = xr.GetAttribute("ref"); int fromRow, fromCol, toRow, toCol; ExcelCellBase.GetRowColFromAddress(address, out fromRow, out fromCol, out toRow, out toCol); for (int row = fromRow; row <= toRow; row++) { for (int col = fromCol; col <= toCol; col++) { Cell(row, col).Merge = true; } } _mergedCells.List.Add(address); } } } /// /// Update merged cells /// /// The writer private void UpdateMergedCells(StreamWriter sw) { sw.Write(""); foreach (string address in _mergedCells) { sw.Write("", address); } sw.Write(""); } /// /// Reads a row from the XML reader /// /// The reader /// The row number /// private ExcelRow AddRow(XmlTextReader xr, int row) { ExcelRow r = new ExcelRow(this, row); r.Collapsed = xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed")== "1" ? true : false; if (xr.GetAttribute("ht") != null) r.Height = double.Parse(xr.GetAttribute("ht"), CultureInfo.InvariantCulture); r.Hidden = xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false; ; r.OutlineLevel = xr.GetAttribute("outlineLevel") == null ? 0 : int.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture); ; r.Phonetic = xr.GetAttribute("ph") != null && xr.GetAttribute("ph") == "1" ? true : false; ; r.StyleID = xr.GetAttribute("s") == null ? 0 : int.Parse(xr.GetAttribute("s"), CultureInfo.InvariantCulture); r.CustomHeight = xr.GetAttribute("customHeight") == null ? false : xr.GetAttribute("customHeight")=="1"; return r; } private object GetValueFromXml(ExcelCell cell, XmlTextReader xr) { object value; //XmlNode vnode = colNode.SelectSingleNode("d:v", NameSpaceManager); //if (vnode == null) return null; if (cell.DataType == "s") { int ix = xr.ReadElementContentAsInt(); value = _package.Workbook._sharedStringsList[ix].Text; cell.IsRichText = _package.Workbook._sharedStringsList[ix].isRichText; } else if (cell.DataType == "str") { value = xr.ReadElementContentAsString(); } else if (cell.DataType == "b") { value = (xr.ReadElementContentAsString()!="0"); } else { int n = cell.Style.Numberformat.NumFmtID; string v = xr.ReadElementContentAsString(); if ((n >= 14 && n <= 22) || (n >= 45 && n <= 47)) { double res; if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out res)) { value = DateTime.FromOADate(res); } else { value = ""; } } else { double d; if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out d)) { value = d; } else { value = double.NaN; } } } return value; } //private string GetSharedString(int stringID) //{ // string retValue = null; // XmlNodeList stringNodes = xlPackage.Workbook.SharedStringsXml.SelectNodes(string.Format("//d:si", stringID), NameSpaceManager); // XmlNode stringNode = stringNodes[stringID]; // if (stringNode != null) // retValue = stringNode.InnerText; // return (retValue); //} #endregion #region HeaderFooter /// /// A reference to the header and footer class which allows you to /// set the header and footer for all odd, even and first pages of the worksheet /// /// /// To format the text you can use the following format /// /// PrefixDescription /// &UUnderlined /// &EDouble Underline /// &K:xxxxxxColor. ex &K:FF0000 for red /// &"Font,Regular Bold Italic"Changes the font. Regular or Bold or Italic or Bold Italic can be used. ex &"Arial,Bold Italic" /// &nnChange font size. nn is an integer. ex &24 /// &GPlaceholder for images. Images can not be added by the library, but its possible to use in a template. /// /// public ExcelHeaderFooter HeaderFooter { get { if (_headerFooter == null) { XmlNode headerFooterNode = TopNode.SelectSingleNode("d:headerFooter", NameSpaceManager); if (headerFooterNode == null) headerFooterNode= CreateNode("d:headerFooter"); _headerFooter = new ExcelHeaderFooter(NameSpaceManager, headerFooterNode, this); } return (_headerFooter); } } #endregion #region "PrinterSettings" /// /// Printer settings /// public ExcelPrinterSettings PrinterSettings { get { var ps = new ExcelPrinterSettings(NameSpaceManager, TopNode, this); ps.SchemaNodeOrder = SchemaNodeOrder; return ps; } } #endregion #endregion // END Worksheet Public Properties #region Worksheet Public Methods /// /// Provides access to an individual cell within the worksheet. /// /// The row number in the worksheet /// The column number in the worksheet /// internal ExcelCell Cell(int row, int col) { ulong cellID=ExcelCell.GetCellID(SheetID, row, col); if (!_cells.ContainsKey(cellID)) { _cells.Add(new ExcelCell(this, row, col)); } return _cells[cellID] as ExcelCell; } /// /// Provides access to a range of cells /// public ExcelRange Cells { get { return new ExcelRange(this, 1, 1, ExcelPackage.MaxRows, ExcelPackage.MaxColumns); } } /// /// Provides access to the selected range of cells /// public ExcelRange SelectedRange { get { return new ExcelRange(this, View.SelectedRange); } } MergeCellsCollection _mergedCells = new MergeCellsCollection(); /// /// Addresses to merged ranges /// public MergeCellsCollection MergedCells { get { return _mergedCells; } } /// /// Provides access to an individual row within the worksheet so you can set its properties. /// /// The row number in the worksheet /// public ExcelRow Row(int row) { ExcelRow r; ulong id = ExcelRow.GetRowID(_sheetID, row); if (_rows.ContainsKey(id)) { r = _rows[id] as ExcelRow; } else { r = new ExcelRow(this, row); _rows.Add(r); } return r; } /// /// Provides access to an individual column within the worksheet so you can set its properties. /// /// The column number in the worksheet /// public ExcelColumn Column(int col) { ExcelColumn column; ulong id=ExcelColumn.GetColumnID(_sheetID, col); if (_columns.ContainsKey(id)) { column = _columns[id] as ExcelColumn; if (column.ColumnMin != column.ColumnMax) { int maxCol = column.ColumnMax; column.ColumnMax=col; ExcelColumn copy = CopyColumn(column, col + 1, maxCol); } } else { foreach (ExcelColumn checkColumn in _columns) { if (col > checkColumn.ColumnMin && col <= checkColumn.ColumnMax) { int maxCol = checkColumn.ColumnMax; checkColumn.ColumnMax = col - 1; if (maxCol > col) { ExcelColumn newC = CopyColumn(checkColumn, col + 1, maxCol); } return CopyColumn(checkColumn, col,col); } } column = new ExcelColumn(this, col); _columns.Add(column); } return column; } /// /// Returns the name of the worksheet /// /// The name of the worksheet public override string ToString() { return Name; } internal ExcelColumn CopyColumn(ExcelColumn c, int col, int maxCol) { ExcelColumn newC = new ExcelColumn(this, col); newC.ColumnMax = maxCol; if (c.StyleName != "") newC.StyleName = c.StyleName; else newC.StyleID = c.StyleID; newC._width = c._width; newC._hidden = c.Hidden; newC.OutlineLevel = c.OutlineLevel; newC.Phonetic = c.Phonetic; newC.BestFit = c.BestFit; _columns.Add(newC); return newC; } /// /// Selects a range in the worksheet. The active cell is the topmost cell. /// Make the current worksheet active. /// /// An address range public void Select(string Address) { Select(Address, true); } /// /// Selects a range in the worksheet. The actice cell is the topmost cell. /// /// A range of cells /// Make the sheet active public void Select(string Address, bool SelectSheet) { int fromCol, fromRow, toCol, toRow; //Get rows and columns and validate as well ExcelCell.GetRowColFromAddress(Address, out fromRow, out fromCol, out toRow, out toCol); if (SelectSheet) { View.TabSelected = true; } View.SelectedRange = Address; View.ActiveCell = ExcelCell.GetAddress(fromRow, fromCol); } /// /// Selects a range in the worksheet. The active cell is the topmost cell of the first address. /// Make the current worksheet active. /// /// An address range public void Select(ExcelAddress Address) { Select(Address, true); } /// /// Selects a range in the worksheet. The active cell is the topmost cell of the first address. /// /// A range of cells /// Make the sheet active public void Select(ExcelAddress Address, bool SelectSheet) { if (SelectSheet) { View.TabSelected = true; } string selAddress = ExcelCellBase.GetAddress(Address.Start.Row, Address.Start.Column) + ":" + ExcelCellBase.GetAddress(Address.End.Row, Address.End.Column); if (Address.Addresses != null) { foreach (var a in Address.Addresses) { selAddress += " " + ExcelCellBase.GetAddress(a.Start.Row, a.Start.Column) + ":" + ExcelCellBase.GetAddress(a.End.Row, a.End.Column); } } View.SelectedRange = selAddress; View.ActiveCell = ExcelCell.GetAddress(Address.Start.Row, Address.Start.Column); } #region InsertRow /// /// Inserts a new row into the spreadsheet. Existing rows below the position are /// shifted down. All formula are updated to take account of the new row. /// /// The position of the new row /// Number of rows to insert public void InsertRow(int rowFrom, int rows) { InsertRow(rowFrom, rows, 0); } /// /// Inserts a new row into the spreadsheet. Existing rows below the position are /// shifted down. All formula are updated to take account of the new row. /// /// The position of the new row /// Number of rows to insert. /// Copy Styles from this row. Applied to all inserted rows public void InsertRow(int rowFrom, int rows, int copyStylesFromRow) { //Insert the new row into the collection ulong copyRowID=ExcelRow.GetRowID(SheetID, copyStylesFromRow); List copyStylesCells=new List(); if (copyStylesFromRow > 0) { int startIndex = _cells.IndexOf(copyRowID); startIndex = ~startIndex; while(startIndex < _cells.Count && (_cells[startIndex] as ExcelCell).Row==copyStylesFromRow) { copyStylesCells.Add(_cells[startIndex++] as ExcelCell); } } ulong rowID=ExcelRow.GetRowID(SheetID, rowFrom); _cells.InsertRows(rowID, rows); _rows.InsertRows(rowID, rows); _formulaCells.InsertRowsUpdateIndex(rowID, rows); if (_comments != null) _comments._comments.InsertRowsUpdateIndex(rowID, rows); if (_vmlDrawings != null) _vmlDrawings._drawings.InsertRowsUpdateIndex(rowID, rows); foreach (ExcelCell cell in _formulaCells) { if (cell.SharedFormulaID < 0) { cell.Formula = ExcelCell.UpdateFormulaReferences(cell.Formula, rows, 0, rowFrom, 0); } else { throw new Exception("Shared formula error"); } } FixSharedFormulasRows(rowFrom, rows); FixMergedCells(rowFrom, rows,false); //Copy the styles foreach (ExcelCell cell in copyStylesCells) { Cells[rowFrom, cell.Column, rowFrom + rows - 1, cell.Column].StyleID = cell.StyleID; } } /// /// Adds a value to the row of merged cells to fix for inserts or deletes /// /// /// /// private void FixMergedCells(int position, int rows, bool delete) { List removeIndex = new List(); for (int i = 0; i < _mergedCells.Count; i++) { ExcelAddressBase addr = new ExcelAddressBase(_mergedCells[i]), newAddr ; if (delete) { newAddr=addr.DeleteRow(position, rows); if (newAddr == null) { removeIndex.Add(i); continue; } } else { newAddr = addr.AddRow(position, rows); } //The address has changed. if (newAddr._address != addr._address) { //Set merged prop for cells for (int row = newAddr._fromRow; row <= newAddr._toRow; row++) { for (int col = newAddr._fromCol; col <= newAddr._toCol; col++) { Cell(row, col).Merge = true; } } } _mergedCells.List[i] = newAddr._address; } for (int i = removeIndex.Count - 1; i >= 0; i--) { _mergedCells.List.RemoveAt(removeIndex[i]); } } private void FixSharedFormulasRows(int position, int rows) { List added = new List(); List deleted = new List(); foreach (int id in _sharedFormulas.Keys) { var f = _sharedFormulas[id]; int fromCol, fromRow, toCol, toRow; ExcelCell.GetRowColFromAddress(f.Address, out fromRow, out fromCol, out toRow, out toCol); if (position >= fromRow && position+(Math.Abs(rows)) <= toRow) //Insert/delete is whithin the share formula address { if (rows > 0) //Insert { f.Address = ExcelCell.GetAddress(fromRow, fromCol) + ":" + ExcelCell.GetAddress(position - 1, toCol); if (toRow != fromRow) { Formulas newF = new Formulas(); newF.StartCol = f.StartCol; newF.StartRow = position + rows; newF.Address = ExcelCell.GetAddress(position + rows, fromCol) + ":" + ExcelCell.GetAddress(toRow + rows, toCol); newF.Formula = ExcelCell.TranslateFromR1C1(ExcelCell.TranslateToR1C1(f.Formula, f.StartRow, f.StartCol), position, f.StartCol); added.Add(newF); } } else { if (fromRow - rows < toRow) { f.Address = ExcelCell.GetAddress(fromRow, fromCol, toRow+rows, toCol); } else { f.Address = ExcelCell.GetAddress(fromRow, fromCol) + ":" + ExcelCell.GetAddress(toRow + rows, toCol); } } } else if (position <= toRow) { if (rows > 0) //Insert before shift down { f.StartRow += rows; //f.Formula = ExcelCell.UpdateFormulaReferences(f.Formula, rows, 0, position, 0); //Recalc the cells positions f.Address = ExcelCell.GetAddress(fromRow + rows, fromCol) + ":" + ExcelCell.GetAddress(toRow + rows, toCol); } else { //Cells[f.Address].SetSharedFormulaID(int.MinValue); if (position <= fromRow && position + Math.Abs(rows) > toRow) //Delete the formula { deleted.Add(f); } else { toRow = toRow + rows < position - 1 ? position - 1 : toRow + rows; if (position <= fromRow) { fromRow = fromRow + rows < position ? position : fromRow + rows; } f.Address = ExcelCell.GetAddress(fromRow, fromCol, toRow, toCol); Cells[f.Address].SetSharedFormulaID(f.Index); //f.StartRow = fromRow; //f.Formula = ExcelCell.UpdateFormulaReferences(f.Formula, rows, 0, position, 0); } } } } AddFormulas(added, position, rows); //Remove formulas foreach (Formulas f in deleted) { _sharedFormulas.Remove(f.Index); } //Fix Formulas added = new List(); foreach (int id in _sharedFormulas.Keys) { var f = _sharedFormulas[id]; UpdateSharedFormulaRow(ref f, position, rows, ref added); } AddFormulas(added, position, rows); } private void AddFormulas(List added, int position, int rows) { //Add new formulas foreach (Formulas f in added) { f.Index = GetMaxShareFunctionIndex(false); _sharedFormulas.Add(f.Index, f); Cells[f.Address].SetSharedFormulaID(f.Index); } } private void UpdateSharedFormulaRow(ref Formulas formula, int startRow, int rows, ref List newFormulas) { int fromRow,fromCol, toRow, toCol; int newFormulasCount = newFormulas.Count; ExcelCellBase.GetRowColFromAddress(formula.Address, out fromRow, out fromCol, out toRow, out toCol); //int refSplits = Regex.Split(formula.Formula, "#REF!").GetUpperBound(0); string formualR1C1; if (rows > 0 || fromRow <= startRow) { formualR1C1 = ExcelRangeBase.TranslateToR1C1(formula.Formula, formula.StartRow, formula.StartCol); formula.Formula = ExcelRangeBase.TranslateFromR1C1(formualR1C1, fromRow, formula.StartCol); } else { formualR1C1 = ExcelRangeBase.TranslateToR1C1(formula.Formula, formula.StartRow-rows, formula.StartCol); formula.Formula = ExcelRangeBase.TranslateFromR1C1(formualR1C1, formula.StartRow, formula.StartCol); } //bool isRef = false; //Formulas restFormula=formula; string prevFormualR1C1 = formualR1C1; for (int row = fromRow; row <= toRow; row++) { for (int col = fromCol; col <= toCol; col++) { string newFormula; string currentFormulaR1C1; if (rows > 0 || row < startRow) { newFormula = ExcelCellBase.UpdateFormulaReferences(ExcelCell.TranslateFromR1C1(formualR1C1, row, col), rows, 0, startRow, 0); currentFormulaR1C1 = ExcelRangeBase.TranslateToR1C1(newFormula, row, col); } else { newFormula = ExcelCellBase.UpdateFormulaReferences(ExcelCell.TranslateFromR1C1(formualR1C1, row-rows, col), rows, 0, startRow, 0); currentFormulaR1C1 = ExcelRangeBase.TranslateToR1C1(newFormula, row, col); } if (currentFormulaR1C1 != prevFormualR1C1) //newFormula.Contains("#REF!")) { //if (refSplits == 0 || Regex.Split(newFormula, "#REF!").GetUpperBound(0) != refSplits) //{ //isRef = true; if (row == fromRow && col == fromCol) { formula.Formula = newFormula; } else { if (newFormulas.Count == newFormulasCount) { formula.Address = ExcelCellBase.GetAddress(formula.StartRow, formula.StartCol, row - 1, col); } else { newFormulas[newFormulas.Count - 1].Address = ExcelCellBase.GetAddress(newFormulas[newFormulas.Count - 1].StartRow, newFormulas[newFormulas.Count - 1].StartCol, row - 1, col); } var refFormula = new Formulas(); refFormula.Formula = newFormula; refFormula.StartRow = row; refFormula.StartCol = col; newFormulas.Add(refFormula); //restFormula = null; prevFormualR1C1 = currentFormulaR1C1; } } // } // else // { // isRef = false; // } //} //else //{ // isRef = false; //} //if (restFormula==null) //{ //if (newFormulas.Count == newFormulasCount) //{ // formula.Address = ExcelCellBase.GetAddress(formula.StartRow, formula.StartCol, row - 1, col); //} //else //{ // newFormulas[newFormulas.Count - 1].Address = ExcelCellBase.GetAddress(newFormulas[newFormulas.Count - 1].StartRow, newFormulas[0].StartCol, row - 1, col); //} //restFormula = new Formulas(); //restFormula.Formula = newFormula; //restFormula.StartRow = row; //restFormula.StartCol = col; //newFormulas.Add(restFormula); //} } } if (rows < 0 && formula.StartRow > startRow) { if (formula.StartRow + rows < startRow) { formula.StartRow = startRow; } else { formula.StartRow += rows; } } if (newFormulas.Count > newFormulasCount) { newFormulas[newFormulas.Count - 1].Address = ExcelCellBase.GetAddress(newFormulas[newFormulas.Count - 1].StartRow, newFormulas[newFormulas.Count - 1].StartCol, toRow, toCol); } } #endregion #region DeleteRow /// /// Deletes the specified row from the worksheet. /// /// The number of the start row to be deleted /// Number of rows to delete public void DeleteRow(int rowFrom, int rows) { ulong rowID = ExcelRow.GetRowID(SheetID, rowFrom); _cells.DeleteRows(rowID, rows, true); _rows.DeleteRows(rowID, rows, true); _formulaCells.DeleteRows(rowID, rows, false); if (_comments != null) _comments._comments.DeleteRows(rowID, rows, false); if (_vmlDrawings != null) _vmlDrawings._drawings.DeleteRows(rowID, rows, false); foreach (ExcelCell cell in _formulaCells) { cell._formula = ExcelCell.UpdateFormulaReferences(cell.Formula, -rows, 0, rowFrom, 0); cell._formulaR1C1 = ""; } FixSharedFormulasRows(rowFrom, -rows); FixMergedCells(rowFrom, rows,true); } /// /// Deletes the specified row from the worksheet. /// /// The number of the start row to be deleted /// Number of rows to delete /// Not used. Rows are always shifted public void DeleteRow(int rowFrom, int rows, bool shiftOtherRowsUp) { if (shiftOtherRowsUp) { DeleteRow(rowFrom, rows); } else { ulong rowID = ExcelRow.GetRowID(SheetID, rowFrom); _cells.DeleteRows(rowID, rows, true); _rows.DeleteRows(rowID, rows, true); _formulaCells.DeleteRows(rowID, rows, false); if (_comments != null) _comments._comments.DeleteRows(rowID, rows, false); if (_vmlDrawings != null) _vmlDrawings._drawings.DeleteRows(rowID, rows, false); } } #endregion /// /// Get the cell value from thw worksheet /// /// The row number /// The row number /// The value public object GetValue(int Row, int Column) { ulong cellID = ExcelCell.GetCellID(SheetID, Row, Column); if (_cells.ContainsKey(cellID)) { var cell = ((ExcelCell)_cells[cellID]); if (cell.IsRichText) { return (object)Cells[Row, Column].RichText.Text; } else { return cell.Value; } } else { return null; } } /// /// Get a strongly typed cell value from the worksheet /// /// The type /// The row number /// The row number /// The value. If the value can't be converted to the specified type, the default value will be returned public T GetValue(int Row, int Column) { ulong cellID=ExcelCell.GetCellID(SheetID, Row, Column); if (!_cells.ContainsKey(cellID)) { return default(T); } var cell=((ExcelCell)_cells[cellID]); if (cell.IsRichText) { return (T)(object)Cells[Row, Column].RichText.Text; } else { return GetTypedValue(cell.Value); } } //Thanks to Michael Tran for parts of this method internal T GetTypedValue(object v) { if (v == null) { return default(T); } Type fromType = v.GetType(); Type toType = typeof(T); if (fromType == toType) { return (T)v; } var cnv = TypeDescriptor.GetConverter(fromType); if (toType == typeof(DateTime)) //Handle dates { if (fromType == typeof(TimeSpan)) { return ((T)(object)(new DateTime(((TimeSpan)v).Ticks))); } else if (fromType == typeof(string)) { DateTime dt; if (DateTime.TryParse(v.ToString(), out dt)) { return (T)(object)(dt); } else { return default(T); } } else { if (cnv.CanConvertTo(typeof(double))) { return (T)(object)(DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double)))); } else { return default(T); } } } else if (toType == typeof(TimeSpan)) //Handle timespan { if (fromType == typeof(DateTime)) { return ((T)(object)(new TimeSpan(((DateTime)v).Ticks))); } else if (fromType == typeof(string)) { TimeSpan ts; if (TimeSpan.TryParse(v.ToString(), out ts)) { return (T)(object)(ts); } else { return default(T); } } else { if (cnv.CanConvertTo(typeof(double))) { return (T)(object)(new TimeSpan(DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double))).Ticks)); } else { try { // Issue 14682 -- "GetValue() won't convert strings" // As suggested, after all special cases, all .NET to do it's // preferred conversion rather than simply returning the default return (T)Convert.ChangeType(v, typeof(T)); } catch (Exception) { // This was the previous behaviour -- no conversion is available. return default(T); } } } } else { if (cnv.CanConvertTo(toType)) { return (T)cnv.ConvertTo(v, typeof(T)); } else { if (toType.IsGenericType && toType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) { toType = Nullable.GetUnderlyingType(toType); if (cnv.CanConvertTo(toType)) { return (T)cnv.ConvertTo(v, typeof(T)); } } if(fromType==typeof(double) && toType==typeof(decimal)) { return (T)(object)Convert.ToDecimal(v); } else if (fromType == typeof(decimal) && toType == typeof(double)) { return (T)(object)Convert.ToDouble(v); } else { return default(T); } } } } /// /// Set the value of a cell /// /// The row number /// The column number /// The value public void SetValue(int Row, int Column, object Value) { Cell(Row, Column).Value = Value; } /// /// Set the value of a cell /// /// The Excel address /// The value public void SetValue(string Address, object Value) { int row, col; ExcelAddressBase.GetRowCol(Address, out row, out col, true); if (row < 1 || col < 1 || row > ExcelPackage.MaxRows && col > ExcelPackage.MaxColumns) { throw new ArgumentOutOfRangeException("Address is invalid or out of range"); } Cell(row, col).Value = Value; } #region MergeCellId /// /// Get MergeCell Index No /// /// /// /// public int GetMergeCellId(int row, int column) { for (int i = 0; i < _mergedCells.Count; i++) { ExcelRange range = Cells[_mergedCells[i]]; if (range.Start.Row <= row && row <= range.End.Row) { if (range.Start.Column <= column && column <= range.End.Column) { return i + 1; } } } return 0; } #endregion #endregion // END Worksheet Public Methods #region Worksheet Private Methods #region Worksheet Save /// /// Saves the worksheet to the package. /// internal void Save() // Worksheet Save { DeletePrinterSettings(); if (_worksheetXml != null) { if (TopNode.Name == "chartsheet") { return; } // save the header & footer (if defined) if (_headerFooter != null) HeaderFooter.Save(); if (_cells.Count > 0) { this.SetXmlNodeString("d:dimension/@ref", Dimension.Address); } if (_drawings != null && _drawings.Count == 0) { //Remove node if no drawings exists. DeleteNode("d:drawing"); } SaveComments(); HeaderFooter.SaveHeaderFooterImages(); SaveTables(); SavePivotTables(); SaveXml(); } if (Drawings.UriDrawing!=null) { if (Drawings.Count == 0) { Part.DeleteRelationship(Drawings._drawingRelation.Id); _package.Package.DeletePart(Drawings.UriDrawing); } else { PackagePart partPack = Drawings.Part; Drawings.DrawingXml.Save(partPack.GetStream(FileMode.Create, FileAccess.Write)); foreach (ExcelDrawing d in Drawings) { if (d is ExcelChart) { ExcelChart c = (ExcelChart)d; c.ChartXml.Save(c.Part.GetStream(FileMode.Create, FileAccess.Write)); } } } } } /// /// Delete the printersettings relationship and part. /// private void DeletePrinterSettings() { //Delete the relationship from the pageSetup tag XmlAttribute attr = (XmlAttribute)WorksheetXml.SelectSingleNode("//d:pageSetup/@r:id", NameSpaceManager); if (attr != null) { string relID = attr.Value; //First delete the attribute from the XML attr.OwnerElement.Attributes.Remove(attr); if(Part.RelationshipExists(relID)) { PackageRelationship rel = Part.GetRelationship(relID); Uri printerSettingsUri = PackUriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); Part.DeleteRelationship(rel.Id); //Delete the part from the package if(_package.Package.PartExists(printerSettingsUri)) { _package.Package.DeletePart(printerSettingsUri); } } } } private void SaveComments() { if (_comments != null) { if (_comments.Count == 0) { if (_comments.Uri != null) { Part.DeleteRelationship(_comments.RelId); _package.Package.DeletePart(_comments.Uri); } RemoveLegacyDrawingRel(VmlDrawingsComments.RelId); } else { if (_comments.Uri == null) { _comments.Uri=new Uri(string.Format(@"/xl/comments{0}.xml", SheetID), UriKind.Relative); } if(_comments.Part==null) { _comments.Part = _package.Package.CreatePart(_comments.Uri, "application/vnd.openxmlformats-officedocument.spreadsheetml.comments+xml", _package.Compression); var rel = Part.CreateRelationship(PackUriHelper.GetRelativeUri(WorksheetUri, _comments.Uri), TargetMode.Internal, ExcelPackage.schemaRelationships+"/comments"); } _comments.CommentXml.Save(_comments.Part.GetStream()); } } if (_vmlDrawings != null) { if (_vmlDrawings.Count == 0) { if (_vmlDrawings.Uri != null) { Part.DeleteRelationship(_vmlDrawings.RelId); _package.Package.DeletePart(_vmlDrawings.Uri); } } else { if (_vmlDrawings.Uri == null) { _vmlDrawings.Uri = XmlHelper.GetNewUri(_package.Package, @"/xl/drawings/vmlDrawing{0}.vml"); } if (_vmlDrawings.Part == null) { _vmlDrawings.Part = _package.Package.CreatePart(_vmlDrawings.Uri, "application/vnd.openxmlformats-officedocument.vmlDrawing", _package.Compression); var rel = Part.CreateRelationship(PackUriHelper.GetRelativeUri(WorksheetUri, _vmlDrawings.Uri), TargetMode.Internal, ExcelPackage.schemaRelationships + "/vmlDrawing"); SetXmlNodeString("d:legacyDrawing/@r:id", rel.Id); _vmlDrawings.RelId = rel.Id; } _vmlDrawings.VmlDrawingXml.Save(_vmlDrawings.Part.GetStream()); } } } /// /// Save all table data /// private void SaveTables() { foreach (var tbl in Tables) { if (tbl.ShowHeader || tbl.ShowTotal) { int colNum = tbl.Address._fromCol; foreach (var col in tbl.Columns) { if (tbl.ShowHeader) { Cell(tbl.Address._fromRow, colNum).Value = col.Name; } if (tbl.ShowTotal) { if (col.TotalsRowFunction == RowFunctions.Custom) { Cell(tbl.Address._toRow, colNum).Formula = col.TotalsRowFormula; } else if (col.TotalsRowFunction != RowFunctions.None) { switch (col.TotalsRowFunction) { case RowFunctions.Average: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "101"); break; case RowFunctions.Count: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "102"); break; case RowFunctions.CountNums: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "103"); break; case RowFunctions.Max: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "104"); break; case RowFunctions.Min: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "105"); break; case RowFunctions.StdDev: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "107"); break; case RowFunctions.Var: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "110"); break; case RowFunctions.Sum: Cell(tbl.Address._toRow, colNum).Formula = GetTotalFunction(col, "109"); break; default: throw (new Exception("Unknown RowFunction enum")); } } else { Cell(tbl.Address._toRow, colNum).Value = col.TotalsRowLabel; } } if (!string.IsNullOrEmpty(col.CalculatedColumnFormula)) { int fromRow = tbl.ShowHeader ? tbl.Address._fromRow + 1 : tbl.Address._fromRow; int toRow = tbl.ShowTotal ? tbl.Address._toRow - 1 : tbl.Address._toRow; for (int row = fromRow; row <= toRow; row++) { Cell(row, colNum).Formula = col.CalculatedColumnFormula; } } colNum++; } } if (tbl.Part == null) { tbl.TableUri = GetNewUri(_package.Package, @"/xl/tables/table{0}.xml", tbl.Id); tbl.Part = _package.Package.CreatePart(tbl.TableUri, "application/vnd.openxmlformats-officedocument.spreadsheetml.table+xml", Workbook._package.Compression); var stream = tbl.Part.GetStream(FileMode.Create); tbl.TableXml.Save(stream); var rel = Part.CreateRelationship(PackUriHelper.GetRelativeUri(WorksheetUri, tbl.TableUri), TargetMode.Internal, ExcelPackage.schemaRelationships + "/table"); tbl.RelationshipID = rel.Id; CreateNode("d:tableParts"); XmlNode tbls = TopNode.SelectSingleNode("d:tableParts",NameSpaceManager); var tblNode = tbls.OwnerDocument.CreateElement("tablePart",ExcelPackage.schemaMain); tbls.AppendChild(tblNode); tblNode.SetAttribute("id",ExcelPackage.schemaRelationships, rel.Id); } else { var stream = tbl.Part.GetStream(FileMode.Create); tbl.TableXml.Save(stream); } } } private void SavePivotTables() { foreach (var pt in PivotTables) { if (pt.DataFields.Count > 1) { XmlElement parentNode; if(pt.DataOnRows==true) { parentNode = pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager) as XmlElement; if (parentNode == null) { pt.CreateNode("d:rowFields"); parentNode = pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager) as XmlElement; } } else { parentNode = pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager) as XmlElement; if (parentNode == null) { pt.CreateNode("d:colFields"); parentNode = pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager) as XmlElement; } } if (parentNode.SelectSingleNode("d:field[@ x= \"-2\"]", pt.NameSpaceManager) == null) { XmlElement fieldNode = pt.PivotTableXml.CreateElement("field", ExcelPackage.schemaMain); fieldNode.SetAttribute("x", "-2"); parentNode.AppendChild(fieldNode); } } pt.PivotTableXml.Save(pt.Part.GetStream(FileMode.Create)); pt.CacheDefinition.CacheDefinitionXml.Save(pt.CacheDefinition.Part.GetStream(FileMode.Create)); } } private static string GetTotalFunction(ExcelTableColumn col,string FunctionNum) { return string.Format("SUBTOTAL({0},[{1}])", FunctionNum, col.Name); } private void SaveXml() { //Create the nodes if they do not exist. CreateNode("d:cols"); CreateNode("d:sheetData"); CreateNode("d:mergeCells"); CreateNode("d:hyperlinks"); CreateNode("d:rowBreaks"); CreateNode("d:colBreaks"); string xml = _worksheetXml.OuterXml; StreamWriter sw=new StreamWriter(Part.GetStream(FileMode.Create, FileAccess.Write)); int colStart = 0, colEnd = 0; GetBlockPos(xml, "cols", ref colStart, ref colEnd); sw.Write(xml.Substring(0, colStart)); var colBreaks = new List(); if (_columns.Count > 0) { UpdateColumnData(sw); } int cellStart = colEnd, cellEnd = colEnd; GetBlockPos(xml, "sheetData", ref cellStart, ref cellEnd); sw.Write(xml.Substring(colEnd, cellStart - colEnd)); var rowBreaks = new List(); UpdateRowCellData(sw); int mergeStart = cellEnd, mergeEnd = cellEnd; GetBlockPos(xml, "mergeCells", ref mergeStart, ref mergeEnd); sw.Write(xml.Substring(cellEnd, mergeStart - cellEnd)); if (_mergedCells.Count > 0) { UpdateMergedCells(sw); } int hyperStart = mergeEnd, hyperEnd = mergeEnd; GetBlockPos(xml, "hyperlinks", ref hyperStart, ref hyperEnd); sw.Write(xml.Substring(mergeEnd, hyperStart - mergeEnd)); if (_hyperLinkCells.Count > 0) { UpdateHyperLinks(sw); } int rowBreakStart = hyperEnd, rowBreakEnd = hyperEnd; GetBlockPos(xml, "rowBreaks", ref rowBreakStart, ref rowBreakEnd); sw.Write(xml.Substring(hyperEnd, rowBreakStart - hyperEnd)); //if (rowBreaks.Count > 0) //{ UpdateRowBreaks(sw); //} int colBreakStart = rowBreakEnd, colBreakEnd = rowBreakEnd; GetBlockPos(xml, "colBreaks", ref colBreakStart, ref colBreakEnd); sw.Write(xml.Substring(rowBreakEnd, colBreakStart - rowBreakEnd)); //if (colBreaks.Count > 0) //{ UpdateColBreaks(sw); //} sw.Write(xml.Substring(colBreakEnd, xml.Length - colBreakEnd)); sw.Flush(); sw.Close(); } private void UpdateColBreaks(StreamWriter sw) { StringBuilder breaks = new StringBuilder(); int count = 0; foreach (ExcelColumn col in _columns) { if (col.PageBreak) { breaks.AppendFormat("", col.ColumnMin); count++; } } if (count > 0) { sw.Write(string.Format("{1}", count, breaks.ToString())); } } private void UpdateRowBreaks(StreamWriter sw) { StringBuilder breaks=new StringBuilder(); int count = 0; foreach(ExcelRow row in _rows) { if (row.PageBreak) { breaks.AppendFormat("", row.Row); count++; } } if (count>0) { sw.Write(string.Format("{1}", count, breaks.ToString())); } } /// /// Inserts the cols collection into the XML document /// private void UpdateColumnData(StreamWriter sw) { //ExcelColumn prevCol = null; //commented out 11/1-12 JK //foreach (ExcelColumn col in _columns) //{ // if (prevCol != null) // { // if(prevCol.ColumnMax != col.ColumnMin-1) // { // prevCol._columnMax=col.ColumnMin-1; // } // } // prevCol = col; //} sw.Write(""); foreach (ExcelColumn col in _columns) { ExcelStyleCollection cellXfs = _package.Workbook.Styles.CellXfs; sw.Write(" 0) { sw.Write(" outlineLevel=\"{0}\" ", col.OutlineLevel); if (col.Collapsed) { if (col.Hidden) { sw.Write(" collapsed=\"1\""); } else { sw.Write(" collapsed=\"1\" hidden=\"1\""); //Always hidden } } } if (col.Phonetic) { sw.Write(" phonetic=\"1\""); } long styleID = col.StyleID >= 0 ? cellXfs[col.StyleID].newID : col.StyleID; if (styleID > 0) { sw.Write(" style=\"{0}\"", styleID); } sw.Write(" />"); //if (col.PageBreak) //{ // colBreaks.Add(col.ColumnMin); //} } sw.Write(""); } /// /// Insert row and cells into the XML document /// private void UpdateRowCellData(StreamWriter sw) { ExcelStyleCollection cellXfs = _package.Workbook.Styles.CellXfs; _hyperLinkCells = new List(); int row = -1; foreach (ExcelRow r in _rows) { int nextCell = ~_cells.IndexOf(r.RowID); if (nextCell >= 0 && (nextCell >= _cells.Count || ((ExcelCell)_cells[nextCell]).Row != r.Row)) { _cells.Add(r); } } StringBuilder sbXml = new StringBuilder(); var ss = _package.Workbook._sharedStrings; sw.Write(""); foreach (IRangeID r in _cells) { if (r is ExcelCell) { ExcelCell cell = (ExcelCell)r; long styleID = cell.StyleID >= 0 ? cellXfs[cell.StyleID].newID : cell.StyleID; //Add the row element if it's a new row if (row != cell.Row) { WriteRow(sw, cellXfs, row, cell.Row); row = cell.Row; } if (cell.SharedFormulaID >= 0) { var f = _sharedFormulas[cell.SharedFormulaID]; if (f.Address.IndexOf(':') > 0) { if (f.StartCol == cell.Column && f.StartRow == cell.Row) { if (f.IsArray) { sw.Write("{3}", cell.CellAddress, styleID < 0 ? 0 : styleID, f.Address, SecurityElement.Escape(f.Formula)); } else { sw.Write("{4}", cell.CellAddress, styleID < 0 ? 0 : styleID, f.Address, cell.SharedFormulaID, SecurityElement.Escape(f.Formula)); } } else if (f.IsArray) { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); } else { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID, cell.SharedFormulaID); } } else { // We can also have a single cell array formula if(f.IsArray) { sw.Write("{3}", cell.CellAddress, styleID < 0 ? 0 : styleID, string.Format("{0}:{1}", f.Address, f.Address), SecurityElement.Escape(f.Formula)); } else { sw.Write("", f.Address, styleID < 0 ? 0 : styleID); sw.Write("{0}", SecurityElement.Escape(f.Formula)); } } } else if (cell.Formula != "") { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); sw.Write("{0}", SecurityElement.Escape(cell.Formula)); } else { if (cell._value == null) { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); } else { if ((cell._value.GetType().IsPrimitive || cell._value is double || cell._value is decimal || cell._value is DateTime || cell._value is TimeSpan) && cell.DataType != "s") { string s; try { if (cell._value is DateTime) { s = ((DateTime)cell.Value).ToOADate().ToString(CultureInfo.InvariantCulture); } else if (cell._value is TimeSpan) { s = new DateTime(((TimeSpan)cell.Value).Ticks).ToOADate().ToString(CultureInfo.InvariantCulture); ; } else { if (cell._value is double && double.IsNaN((double)cell._value)) { s = "0"; } else if (cell._value is double && double.IsInfinity((double)cell._value)) { s="#NUM!"; } else { s = Convert.ToDouble(cell._value, CultureInfo.InvariantCulture).ToString("g15", CultureInfo.InvariantCulture); } } } catch { s = "0"; } if (cell._value is bool) { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); } else if (cell._value is double && double.IsInfinity((double)cell._value)) { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); } else { sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); } sw.Write("{0}", s); } else { int ix; if (!ss.ContainsKey(cell._value.ToString())) { ix = ss.Count; ss.Add(cell._value.ToString(), new ExcelWorkbook.SharedStringItem() { isRichText = cell.IsRichText, pos = ix }); } else { ix = ss[cell.Value.ToString()].pos; } sw.Write("", cell.CellAddress, styleID < 0 ? 0 : styleID); sw.Write("{0}", ix); } } } //Update hyperlinks. if (cell.Hyperlink != null) { _hyperLinkCells.Add(cell.CellID); } } else //ExcelRow { int newRow=((ExcelRow)r).Row; WriteRow(sw, cellXfs, row, newRow); row = newRow; } } if (row != -1) sw.Write(""); sw.Write(""); } private void WriteRow(StreamWriter sw, ExcelStyleCollection cellXfs, int prevRow, int row) { if (prevRow != -1) sw.Write(""); ulong rowID = ExcelRow.GetRowID(SheetID, row); sw.Write("