| /******************************************************************************* | |
| * 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.Text.RegularExpressions; | |
| using System.Xml; | |
| namespace AppsheetEpplus; | |
| /// <summary> | |
| /// The collection of worksheets for the workbook | |
| /// </summary> | |
| public class ExcelWorksheets : XmlHelper, IEnumerable<ExcelWorksheet> { | |
| private readonly ExcelPackage _pck; | |
| private readonly ExcelWorkbook _workbook; | |
| private Dictionary<int, ExcelWorksheet> _worksheets = new(); | |
| private readonly XmlNamespaceManager _namespaceManager; | |
| internal ExcelWorksheets( | |
| ExcelPackage pck, | |
| ExcelWorkbook workbook, | |
| XmlNamespaceManager nsm, | |
| XmlNode topNode) | |
| : base(nsm, topNode) { | |
| _pck = pck; | |
| _workbook = workbook; | |
| _namespaceManager = nsm; | |
| 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 = _workbook.Part.GetRelationship(relId); | |
| Uri uriWorksheet = UriHelper.ResolvePartUri( | |
| ExcelWorkbook.WorkbookUri, | |
| sheetRelation.TargetUri); | |
| //add the worksheet | |
| if (sheetRelation.RelationshipType.EndsWith("chartsheet")) { | |
| _worksheets.Add( | |
| positionId, | |
| new ExcelChartsheet( | |
| _namespaceManager, | |
| pck, | |
| _workbook, | |
| uriWorksheet, | |
| name, | |
| sheetId, | |
| positionId, | |
| hidden)); | |
| } else { | |
| _worksheets.Add( | |
| positionId, | |
| new( | |
| _namespaceManager, | |
| pck, | |
| _workbook, | |
| 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; | |
| } | |
| } | |
| /// <summary> | |
| /// Returns the number of worksheets in the workbook | |
| /// </summary> | |
| public int Count => (_worksheets.Count); | |
| private const string _errDupWorksheet = | |
| "A worksheet with this name already exists in the workbook"; | |
| internal const string _worksheetContentType = | |
| "application/vnd.openxmlformats-officedocument.spreadsheetml.worksheet+xml"; | |
| /// <summary> | |
| /// Foreach support | |
| /// </summary> | |
| /// <returns>An enumerator</returns> | |
| public IEnumerator<ExcelWorksheet> GetEnumerator() { | |
| return (_worksheets.Values.GetEnumerator()); | |
| } | |
| IEnumerator IEnumerable.GetEnumerator() { | |
| return (_worksheets.Values.GetEnumerator()); | |
| } | |
| /// <summary> | |
| /// Adds a new blank worksheet. | |
| /// </summary> | |
| /// <param name="name">The name of the workbook</param> | |
| public ExcelWorksheet Add(string name) { | |
| name = ValidateFixSheetName(name); | |
| if (GetByName(name) != null) { | |
| throw (new InvalidOperationException(_errDupWorksheet + " : " + name)); | |
| } | |
| GetSheetUri(ref name, out var sheetId, out var uriWorksheet, false); | |
| // Create the new worksheet | |
| var rel = _pck.CreateXmlDocument( | |
| uriWorksheet, | |
| _worksheetContentType, | |
| ExcelPackage._schemaRelationships + "/worksheet", | |
| CreateNewWorksheet(false)); | |
| // Add worksheet to the workbook | |
| XmlElement worksheetNode = _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); | |
| int positionId = _worksheets.Count + 1; | |
| ExcelWorksheet worksheet = new( | |
| _namespaceManager, | |
| _pck, | |
| _workbook, | |
| uriWorksheet, | |
| name, | |
| sheetId, | |
| positionId, | |
| eWorkSheetHidden.Visible); | |
| _worksheets.Add(positionId, worksheet); | |
| return worksheet; | |
| } | |
| 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("/xl/chartsheets/chartsheet" + sheetId + ".xml", UriKind.Relative); | |
| } else { | |
| uriWorksheet = new("/xl/worksheets/sheet" + sheetId + ".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 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; | |
| } | |
| /// <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]; | |
| } | |
| 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] => GetByName(name); | |
| 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); | |
| } | |
| } // end class Worksheets |