| /******************************************************************************* |
| * 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 2009-10-01 |
| * Jan Källman License changed GPL-->LGPL 2011-12-27 |
| *******************************************************************************/ |
| using System; |
| using System.Collections; |
| using System.Collections.Generic; |
| using System.Xml; |
| using System.IO; |
| using OfficeOpenXml.Utils; |
| namespace OfficeOpenXml |
| { |
| /// <summary> |
| /// The collection of worksheets for the workbook |
| /// </summary> |
| public class ExcelWorksheets : XmlHelper, IEnumerable<ExcelWorksheet> |
| { |
| #region Private Properties |
| private ExcelPackage _pck; |
| private Dictionary<int, ExcelWorksheet> _worksheets; |
| private XmlNamespaceManager _namespaceManager; |
| #endregion |
| #region ExcelWorksheets Constructor |
| internal ExcelWorksheets(ExcelPackage pck, XmlNamespaceManager nsm, XmlNode topNode) : |
| base(nsm, topNode) |
| { |
| _pck = pck; |
| _namespaceManager = nsm; |
| _worksheets = new Dictionary<int, ExcelWorksheet>(); |
| int positionID = 1; |
| |
| foreach (XmlNode sheetNode in topNode.ChildNodes) |
| { |
| if (sheetNode.NodeType == XmlNodeType.Element) |
| { |
| string name = sheetNode.Attributes["name"].Value; |
| //Get the relationship id |
| string relId = sheetNode.Attributes["r:id"].Value; |
| int sheetID = Convert.ToInt32(sheetNode.Attributes["sheetId"].Value); |
| |
| //Hidden property |
| eWorkSheetHidden hidden = eWorkSheetHidden.Visible; |
| XmlNode attr = sheetNode.Attributes["state"]; |
| if (attr != null) |
| hidden = TranslateHidden(attr.Value); |
| |
| var sheetRelation = pck.Workbook.Part.GetRelationship(relId); |
| Uri uriWorksheet = UriHelper.ResolvePartUri(pck.Workbook.WorkbookUri, sheetRelation.TargetUri); |
| |
| //add the worksheet |
| if (sheetRelation.RelationshipType.EndsWith("chartsheet")) |
| { |
| _worksheets.Add(positionID, new ExcelChartsheet(_namespaceManager, _pck, relId, uriWorksheet, name, sheetID, positionID, hidden)); |
| } |
| else |
| { |
| _worksheets.Add(positionID, new ExcelWorksheet(_namespaceManager, _pck, relId, uriWorksheet, name, sheetID, positionID, hidden)); |
| } |
| positionID++; |
| } |
| } |
| } |
| |
| private eWorkSheetHidden TranslateHidden(string value) |
| { |
| switch (value) |
| { |
| case "hidden": |
| return eWorkSheetHidden.Hidden; |
| case "veryHidden": |
| return eWorkSheetHidden.VeryHidden; |
| default: |
| return eWorkSheetHidden.Visible; |
| } |
| } |
| #endregion |
| #region ExcelWorksheets Public Properties |
| /// <summary> |
| /// Returns the number of worksheets in the workbook |
| /// </summary> |
| public int Count |
| { |
| get { return (_worksheets.Count); } |
| } |
| #endregion |
| private const string ERR_DUP_WORKSHEET = "A worksheet with this name already exists in the workbook"; |
| internal const string WORKSHEET_CONTENTTYPE = @"application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; |
| internal const string CHARTSHEET_CONTENTTYPE = @"application/vnd.openxmlformats-officedocument.spreadsheetml.chartsheet+xml"; |
| #region ExcelWorksheets Public Methods |
| /// <summary> |
| /// Foreach support |
| /// </summary> |
| /// <returns>An enumerator</returns> |
| public IEnumerator<ExcelWorksheet> GetEnumerator() |
| { |
| return (_worksheets.Values.GetEnumerator()); |
| } |
| #region IEnumerable Members |
| |
| IEnumerator IEnumerable.GetEnumerator() |
| { |
| return (_worksheets.Values.GetEnumerator()); |
| } |
| |
| #endregion |
| #region Add Worksheet |
| /// <summary> |
| /// Adds a new blank worksheet. |
| /// </summary> |
| /// <param name="Name">The name of the workbook</param> |
| public ExcelWorksheet Add(string Name) |
| { |
| int sheetID; |
| Uri uriWorksheet; |
| lock (_worksheets) |
| { |
| Name = ValidateFixSheetName(Name); |
| if (GetByName(Name) != null) |
| { |
| throw (new InvalidOperationException(ERR_DUP_WORKSHEET + " : " + Name)); |
| } |
| GetSheetURI(ref Name, out sheetID, out uriWorksheet, false); |
| Packaging.ZipPackagePart worksheetPart = _pck.Package.CreatePart(uriWorksheet, WORKSHEET_CONTENTTYPE, _pck.Compression); |
| |
| //Create the new, empty worksheet and save it to the package |
| StreamWriter streamWorksheet = new StreamWriter(worksheetPart.GetStream(FileMode.Create, FileAccess.Write)); |
| XmlDocument worksheetXml = CreateNewWorksheet(false); |
| worksheetXml.Save(streamWorksheet); |
| |
| string rel = CreateWorkbookRel(Name, sheetID, uriWorksheet, false); |
| |
| int positionID = _worksheets.Count + 1; |
| ExcelWorksheet worksheet; |
| |
| { |
| worksheet = new ExcelWorksheet(_namespaceManager, _pck, rel, uriWorksheet, Name, sheetID, positionID, eWorkSheetHidden.Visible); |
| } |
| |
| _worksheets.Add(positionID, worksheet); |
| return worksheet; |
| } |
| } |
| |
| string CreateWorkbookRel(string Name, int sheetID, Uri uriWorksheet, bool isChart) |
| { |
| //Create the relationship between the workbook and the new worksheet |
| var rel = _pck.Workbook.Part.CreateRelationship(UriHelper.GetRelativeUri(_pck.Workbook.WorkbookUri, uriWorksheet), Packaging.TargetMode.Internal, ExcelPackage.schemaRelationships + "/" + (isChart ? "chartsheet" : "worksheet")); |
| |
| //Create the new sheet node |
| XmlElement worksheetNode = _pck.Workbook.WorkbookXml.CreateElement("sheet", ExcelPackage.schemaMain); |
| worksheetNode.SetAttribute("name", Name); |
| worksheetNode.SetAttribute("sheetId", sheetID.ToString()); |
| worksheetNode.SetAttribute("id", ExcelPackage.schemaRelationships, rel.Id); |
| |
| TopNode.AppendChild(worksheetNode); |
| return rel.Id; |
| } |
| private void GetSheetURI(ref string Name, out int sheetID, out Uri uriWorksheet, bool isChart) |
| { |
| Name = ValidateFixSheetName(Name); |
| |
| //First find maximum existing sheetID |
| sheetID = 0; |
| foreach(var ws in this) |
| { |
| if (ws.SheetID > sheetID) |
| { |
| sheetID = ws.SheetID; |
| } |
| } |
| // we now have the max existing values, so add one |
| sheetID++; |
| |
| // add the new worksheet to the package |
| if (isChart) |
| { |
| uriWorksheet = new Uri("/xl/chartsheets/chartsheet" + sheetID.ToString() + ".xml", UriKind.Relative); |
| } |
| else |
| { |
| uriWorksheet = new Uri("/xl/worksheets/sheet" + sheetID.ToString() + ".xml", UriKind.Relative); |
| } |
| } |
| |
| internal string ValidateFixSheetName(string Name) |
| { |
| //remove invalid characters |
| if (ValidateName(Name)) |
| { |
| if (Name.IndexOf(':') > -1) Name = Name.Replace(":", " "); |
| if (Name.IndexOf('/') > -1) Name = Name.Replace("/", " "); |
| if (Name.IndexOf('\\') > -1) Name = Name.Replace("\\", " "); |
| if (Name.IndexOf('?') > -1) Name = Name.Replace("?", " "); |
| if (Name.IndexOf('[') > -1) Name = Name.Replace("[", " "); |
| if (Name.IndexOf(']') > -1) Name = Name.Replace("]", " "); |
| } |
| |
| if (Name.Trim() == "") |
| { |
| throw new ArgumentException("The worksheet can not have an empty name"); |
| } |
| if (Name.Length > 31) Name = Name.Substring(0, 31); //A sheet can have max 31 char's |
| return Name; |
| } |
| /// <summary> |
| /// Validate the sheetname |
| /// </summary> |
| /// <param name="Name">The Name</param> |
| /// <returns>True if valid</returns> |
| private bool ValidateName(string Name) |
| { |
| return System.Text.RegularExpressions.Regex.IsMatch(Name, @":|\?|/|\\|\[|\]"); |
| } |
| |
| /// <summary> |
| /// Creates the XML document representing a new empty worksheet |
| /// </summary> |
| /// <returns></returns> |
| internal XmlDocument CreateNewWorksheet(bool isChart) |
| { |
| XmlDocument xmlDoc = new XmlDocument(); |
| XmlElement elemWs = xmlDoc.CreateElement(isChart ? "chartsheet" : "worksheet", ExcelPackage.schemaMain); |
| elemWs.SetAttribute("xmlns:r", ExcelPackage.schemaRelationships); |
| xmlDoc.AppendChild(elemWs); |
| |
| |
| if (isChart) |
| { |
| XmlElement elemSheetPr = xmlDoc.CreateElement("sheetPr", ExcelPackage.schemaMain); |
| elemWs.AppendChild(elemSheetPr); |
| |
| XmlElement elemSheetViews = xmlDoc.CreateElement("sheetViews", ExcelPackage.schemaMain); |
| elemWs.AppendChild(elemSheetViews); |
| |
| XmlElement elemSheetView = xmlDoc.CreateElement("sheetView", ExcelPackage.schemaMain); |
| elemSheetView.SetAttribute("workbookViewId", "0"); |
| elemSheetView.SetAttribute("zoomToFit", "1"); |
| |
| elemSheetViews.AppendChild(elemSheetView); |
| } |
| else |
| { |
| XmlElement elemSheetViews = xmlDoc.CreateElement("sheetViews", ExcelPackage.schemaMain); |
| elemWs.AppendChild(elemSheetViews); |
| |
| XmlElement elemSheetView = xmlDoc.CreateElement("sheetView", ExcelPackage.schemaMain); |
| elemSheetView.SetAttribute("workbookViewId", "0"); |
| elemSheetViews.AppendChild(elemSheetView); |
| |
| XmlElement elemSheetFormatPr = xmlDoc.CreateElement("sheetFormatPr", ExcelPackage.schemaMain); |
| elemSheetFormatPr.SetAttribute("defaultRowHeight", "15"); |
| elemWs.AppendChild(elemSheetFormatPr); |
| |
| XmlElement elemSheetData = xmlDoc.CreateElement("sheetData", ExcelPackage.schemaMain); |
| elemWs.AppendChild(elemSheetData); |
| } |
| return xmlDoc; |
| } |
| #endregion |
| |
| /// <summary> |
| /// Returns the worksheet at the specified position. |
| /// </summary> |
| /// <param name="PositionID">The position of the worksheet. 1-base</param> |
| /// <returns></returns> |
| public ExcelWorksheet this[int PositionID] |
| { |
| get |
| { |
| if (_worksheets.ContainsKey(PositionID)) |
| { |
| return _worksheets[PositionID]; |
| } |
| else |
| { |
| throw (new IndexOutOfRangeException("Worksheet position out of range.")); |
| } |
| } |
| } |
| |
| /// <summary> |
| /// Returns the worksheet matching the specified name |
| /// </summary> |
| /// <param name="Name">The name of the worksheet</param> |
| /// <returns></returns> |
| public ExcelWorksheet this[string Name] |
| { |
| get |
| { |
| return GetByName(Name); |
| } |
| } |
| #endregion |
| internal ExcelWorksheet GetBySheetID(int localSheetID) |
| { |
| foreach (ExcelWorksheet ws in this) |
| { |
| if (ws.SheetID == localSheetID) |
| { |
| return ws; |
| } |
| } |
| return null; |
| } |
| private ExcelWorksheet GetByName(string Name) |
| { |
| if (string.IsNullOrEmpty(Name)) return null; |
| ExcelWorksheet xlWorksheet = null; |
| foreach (ExcelWorksheet worksheet in _worksheets.Values) |
| { |
| if (worksheet.Name.Equals(Name, StringComparison.InvariantCultureIgnoreCase)) |
| xlWorksheet = worksheet; |
| } |
| return (xlWorksheet); |
| } |
| #region MoveBefore and MoveAfter Methods |
| /// <summary> |
| /// Moves the source worksheet to the position before the target worksheet |
| /// </summary> |
| /// <param name="sourceName">The name of the source worksheet</param> |
| /// <param name="targetName">The name of the target worksheet</param> |
| public void MoveBefore(string sourceName, string targetName) |
| { |
| Move(sourceName, targetName, false); |
| } |
| |
| /// <summary> |
| /// Moves the source worksheet to the position before the target worksheet |
| /// </summary> |
| /// <param name="sourcePositionId">The id of the source worksheet</param> |
| /// <param name="targetPositionId">The id of the target worksheet</param> |
| public void MoveBefore(int sourcePositionId, int targetPositionId) |
| { |
| Move(sourcePositionId, targetPositionId, false); |
| } |
| |
| /// <summary> |
| /// Moves the source worksheet to the position after the target worksheet |
| /// </summary> |
| /// <param name="sourceName">The name of the source worksheet</param> |
| /// <param name="targetName">The name of the target worksheet</param> |
| public void MoveAfter(string sourceName, string targetName) |
| { |
| Move(sourceName, targetName, true); |
| } |
| |
| /// <summary> |
| /// Moves the source worksheet to the position after the target worksheet |
| /// </summary> |
| /// <param name="sourcePositionId">The id of the source worksheet</param> |
| /// <param name="targetPositionId">The id of the target worksheet</param> |
| public void MoveAfter(int sourcePositionId, int targetPositionId) |
| { |
| Move(sourcePositionId, targetPositionId, true); |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="sourceName"></param> |
| public void MoveToStart(string sourceName) |
| { |
| var sourceSheet = this[sourceName]; |
| if (sourceSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet to move '{0}'", sourceName)); |
| } |
| Move(sourceSheet.PositionID, 1, false); |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="sourcePositionId"></param> |
| public void MoveToStart(int sourcePositionId) |
| { |
| Move(sourcePositionId, 1, false); |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="sourceName"></param> |
| public void MoveToEnd(string sourceName) |
| { |
| var sourceSheet = this[sourceName]; |
| if (sourceSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet to move '{0}'", sourceName)); |
| } |
| Move(sourceSheet.PositionID, _worksheets.Count, true); |
| } |
| |
| /// <summary> |
| /// |
| /// </summary> |
| /// <param name="sourcePositionId"></param> |
| public void MoveToEnd(int sourcePositionId) |
| { |
| Move(sourcePositionId, _worksheets.Count, true); |
| } |
| |
| private void Move(string sourceName, string targetName, bool placeAfter) |
| { |
| var sourceSheet = this[sourceName]; |
| if (sourceSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet to move '{0}'", sourceName)); |
| } |
| var targetSheet = this[targetName]; |
| if (targetSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet to move '{0}'", targetName)); |
| } |
| Move(sourceSheet.PositionID, targetSheet.PositionID, placeAfter); |
| } |
| |
| private void Move(int sourcePositionId, int targetPositionId, bool placeAfter) |
| { |
| // Bugfix: if source and target are the same worksheet the following code will create a duplicate |
| // which will cause a corrupt workbook. /swmal 2014-05-10 |
| if (sourcePositionId == targetPositionId) return; |
| |
| lock (_worksheets) |
| { |
| var sourceSheet = this[sourcePositionId]; |
| if (sourceSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet at position '{0}'", sourcePositionId)); |
| } |
| var targetSheet = this[targetPositionId]; |
| if (targetSheet == null) |
| { |
| throw new Exception(string.Format("Move worksheet error: Could not find worksheet at position '{0}'", targetPositionId)); |
| } |
| if (sourcePositionId == targetPositionId && _worksheets.Count < 2) |
| { |
| return; //--- no reason to attempt to re-arrange a single item with itself |
| } |
| |
| var index = 1; |
| var newOrder = new Dictionary<int, ExcelWorksheet>(); |
| foreach (var entry in _worksheets) |
| { |
| if (entry.Key == targetPositionId) |
| { |
| if (!placeAfter) |
| { |
| sourceSheet.PositionID = index; |
| newOrder.Add(index++, sourceSheet); |
| } |
| |
| entry.Value.PositionID = index; |
| newOrder.Add(index++, entry.Value); |
| |
| if (placeAfter) |
| { |
| sourceSheet.PositionID = index; |
| newOrder.Add(index++, sourceSheet); |
| } |
| } |
| else if (entry.Key == sourcePositionId) |
| { |
| //--- do nothing |
| } |
| else |
| { |
| entry.Value.PositionID = index; |
| newOrder.Add(index++, entry.Value); |
| } |
| } |
| _worksheets = newOrder; |
| |
| MoveSheetXmlNode(sourceSheet, targetSheet, placeAfter); |
| } |
| } |
| |
| private void MoveSheetXmlNode(ExcelWorksheet sourceSheet, ExcelWorksheet targetSheet, bool placeAfter) |
| { |
| lock (TopNode.OwnerDocument) |
| { |
| var sourceNode = TopNode.SelectSingleNode(string.Format("d:sheet[@sheetId = '{0}']", sourceSheet.SheetID), _namespaceManager); |
| var targetNode = TopNode.SelectSingleNode(string.Format("d:sheet[@sheetId = '{0}']", targetSheet.SheetID), _namespaceManager); |
| if (sourceNode == null || targetNode == null) |
| { |
| throw new Exception("Source SheetId and Target SheetId must be valid"); |
| } |
| if (placeAfter) |
| { |
| TopNode.InsertAfter(sourceNode, targetNode); |
| } |
| else |
| { |
| TopNode.InsertBefore(sourceNode, targetNode); |
| } |
| } |
| } |
| |
| #endregion |
| } // end class Worksheets |
| } |