| /******************************************************************************* | |
| * 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-01-01 | |
| * Jan Källman License changed GPL-->LGPL 2011-12-27 | |
| * Richard Tallent Fix escaping of quotes 2012-10-31 | |
| *******************************************************************************/ | |
| using System; | |
| using System.Collections.Generic; | |
| using System.Collections.Immutable; | |
| using System.Globalization; | |
| using System.IO; | |
| using System.Text; | |
| using System.Xml; | |
| namespace AppsheetEpplus; | |
| /// <summary> | |
| /// How the application should calculate formulas in the workbook | |
| /// </summary> | |
| public enum ExcelCalcMode { | |
| /// <summary> | |
| /// Indicates that calculations in the workbook are performed automatically when cell values change. | |
| /// The application recalculates those cells that are dependent on other cells that contain changed values. | |
| /// This mode of calculation helps to avoid unnecessary calculations. | |
| /// </summary> | |
| Automatic, | |
| /// <summary> | |
| /// Indicates tables be excluded during automatic calculation | |
| /// </summary> | |
| AutomaticNoTable, | |
| /// <summary> | |
| /// Indicates that calculations in the workbook be triggered manually by the user. | |
| /// </summary> | |
| Manual, | |
| } | |
| /// <summary> | |
| /// Represents the Excel workbook and provides access to all the | |
| /// document properties and worksheets within the workbook. | |
| /// </summary> | |
| public sealed class ExcelWorkbook : XmlHelper { | |
| internal class SharedStringItem { | |
| internal int pos; | |
| internal string Text; | |
| internal bool isRichText; | |
| } | |
| private readonly ExcelPackage _package; | |
| private ExcelWorksheets _worksheets; | |
| private OfficeProperties _properties; | |
| private ExcelStyles _styles; | |
| internal static ImmutableArray<string> WorkbookSchemaNodeOrder = [ | |
| "fileVersion", | |
| "fileSharing", | |
| "workbookPr", | |
| "workbookProtection", | |
| "bookViews", | |
| "sheets", | |
| "functionGroups", | |
| "functionPrototypes", | |
| "externalReferences", | |
| "definedNames", | |
| "calcPr", | |
| "oleSize", | |
| "customWorkbookViews", | |
| "pivotCaches", | |
| "smartTagPr", | |
| "smartTagTypes", | |
| "webPublishing", | |
| "fileRecoveryPr", | |
| ]; | |
| protected override ImmutableArray<string> SchemaNodeOrder => WorkbookSchemaNodeOrder; | |
| internal ExcelWorkbook(ExcelPackage package, XmlNamespaceManager namespaceManager) | |
| : base(namespaceManager) { | |
| _package = package; | |
| _names = new(this); | |
| _namespaceManager = namespaceManager; | |
| WorkbookXml = package.GetOrCreateXmlDocument( | |
| WorkbookUri, | |
| "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet.main+xml", | |
| ExcelPackage._schemaRelationships + "/officeDocument", | |
| () => CreateEmptyWorkbookXml(namespaceManager)); | |
| _stylesXml = package.GetOrCreateXmlDocument( | |
| StylesUri, | |
| "application/vnd.openxmlformats-officedocument.spreadsheetml.styles+xml", | |
| ExcelPackage._schemaRelationships + "/styles", | |
| CreateEmptyStylesXml); | |
| TopNode = WorkbookXml.DocumentElement; | |
| FullCalcOnLoad = true; //Full calculation on load by default, for both new workbooks and templates. | |
| GetSharedStrings(); | |
| GetExternalReferences(); | |
| GetDefinedNames(); | |
| } | |
| private static XmlDocument CreateEmptyWorkbookXml(XmlNamespaceManager namespaceManager) { | |
| var result = new XmlDocument(namespaceManager.NameTable); | |
| var wbElem = result.CreateElement("workbook", ExcelPackage._schemaMain); | |
| // Add the relationships namespace | |
| wbElem.SetAttribute("xmlns:r", ExcelPackage._schemaRelationships); | |
| result.AppendChild(wbElem); | |
| // Create the bookViews and workbooks element | |
| var bookViews = result.CreateElement("bookViews", ExcelPackage._schemaMain); | |
| wbElem.AppendChild(bookViews); | |
| var workbookView = result.CreateElement("workbookView", ExcelPackage._schemaMain); | |
| bookViews.AppendChild(workbookView); | |
| return result; | |
| } | |
| private static XmlDocument CreateEmptyStylesXml() { | |
| StringBuilder xml = new StringBuilder( | |
| "<styleSheet xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\">"); | |
| xml.Append("<numFmts />"); | |
| xml.Append("<fonts count=\"1\"><font><sz val=\"11\" /><name val=\"Calibri\" /></font></fonts>"); | |
| xml.Append( | |
| "<fills><fill><patternFill patternType=\"none\" /></fill><fill><patternFill patternType=\"gray125\" /></fill></fills>"); | |
| xml.Append( | |
| "<borders><border><left /><right /><top /><bottom /><diagonal /></border></borders>"); | |
| xml.Append("<cellStyleXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" /></cellStyleXfs>"); | |
| xml.Append("<cellXfs count=\"1\"><xf numFmtId=\"0\" fontId=\"0\" xfId=\"0\" /></cellXfs>"); | |
| xml.Append("<cellStyles><cellStyle name=\"Normal\" xfId=\"0\" builtinId=\"0\" /></cellStyles>"); | |
| xml.Append("<dxfs count=\"0\" />"); | |
| xml.Append("</styleSheet>"); | |
| var result = new XmlDocument(); | |
| result.LoadXml(xml.ToString()); | |
| return result; | |
| } | |
| internal readonly Dictionary<string, SharedStringItem> _sharedStrings = new(); //Used when reading cells. | |
| internal List<SharedStringItem> _sharedStringsList = new(); //Used when reading cells. | |
| internal ExcelNamedRangeCollection _names; | |
| internal int _nextTableID = int.MinValue; | |
| internal int _nextPivotTableID = int.MinValue; | |
| private readonly XmlNamespaceManager _namespaceManager; | |
| private FormulaParser _formulaParser; | |
| private FormulaParserManager _parserManager; | |
| internal CellStore<List<Token>> _formulaTokens; | |
| /// <summary> | |
| /// Read shared strings to list | |
| /// </summary> | |
| private void GetSharedStrings() { | |
| if (_package.Package.PartExists(SharedStringsUri)) { | |
| var xml = _package.GetXmlDocument(SharedStringsUri); | |
| XmlNodeList nl = xml.SelectNodes("//d:sst/d:si", NameSpaceManager); | |
| _sharedStringsList = new(); | |
| if (nl != null) { | |
| foreach (XmlNode node in nl) { | |
| XmlNode n = node.SelectSingleNode("d:t", NameSpaceManager); | |
| if (n != null) { | |
| _sharedStringsList.Add( | |
| new() { | |
| Text = ConvertUtil.ExcelDecodeString(n.InnerText), | |
| }); | |
| } else { | |
| _sharedStringsList.Add( | |
| new() { | |
| Text = node.InnerXml, | |
| isRichText = true, | |
| }); | |
| } | |
| } | |
| } | |
| //Delete the shared string part, it will be recreated when the package is saved. | |
| foreach (var rel in Part.GetRelationships()) { | |
| if (rel.TargetUri.OriginalString.EndsWith( | |
| "sharedstrings.xml", | |
| StringComparison.InvariantCultureIgnoreCase)) { | |
| Part.DeleteRelationship(rel.Id); | |
| break; | |
| } | |
| } | |
| _package.Package.DeletePart(SharedStringsUri); //Remove the part, it is recreated when saved. | |
| } | |
| } | |
| internal void GetDefinedNames() { | |
| XmlNodeList nl = WorkbookXml.SelectNodes("//d:definedNames/d:definedName", NameSpaceManager); | |
| if (nl != null) { | |
| foreach (XmlElement elem in nl) { | |
| string fullAddress = elem.InnerText; | |
| ExcelWorksheet nameWorksheet; | |
| if (!int.TryParse(elem.GetAttribute("localSheetId"), out var localSheetId)) { | |
| localSheetId = -1; | |
| nameWorksheet = null; | |
| } else { | |
| nameWorksheet = Worksheets[localSheetId + 1]; | |
| } | |
| var addressType = ExcelAddressBase.IsValid(fullAddress); | |
| ExcelRangeBase range; | |
| ExcelNamedRange namedRange; | |
| if (fullAddress.IndexOf("[") == 0) { | |
| int start = fullAddress.IndexOf("["); | |
| int end = fullAddress.IndexOf("]", start); | |
| if (start >= 0 && end >= 0) { | |
| string externalIndex = fullAddress.Substring(start + 1, end - start - 1); | |
| if (int.TryParse(externalIndex, out var index)) { | |
| if (index > 0 && index <= _externalReferences.Count) { | |
| fullAddress = | |
| fullAddress.Substring(0, start) | |
| + "[" | |
| + _externalReferences[index - 1] | |
| + "]" | |
| + fullAddress.Substring(end + 1); | |
| } | |
| } | |
| } | |
| } | |
| if (addressType == ExcelAddressBase.AddressType.Invalid | |
| || addressType == ExcelAddressBase.AddressType.InternalName | |
| || addressType == ExcelAddressBase.AddressType.ExternalName | |
| || addressType == ExcelAddressBase.AddressType.Formula | |
| || addressType | |
| == ExcelAddressBase | |
| .AddressType | |
| .ExternalAddress) //A value or a formula | |
| { | |
| range = new(this, nameWorksheet, elem.GetAttribute("name"), true); | |
| if (nameWorksheet == null) { | |
| namedRange = _names.Add(elem.GetAttribute("name"), range); | |
| } else { | |
| namedRange = nameWorksheet.Names.Add(elem.GetAttribute("name"), range); | |
| } | |
| if (fullAddress.StartsWith( | |
| "\"")) //String value | |
| { | |
| namedRange.NameValue = fullAddress.Substring(1, fullAddress.Length - 2); | |
| } else if (double.TryParse( | |
| fullAddress, | |
| NumberStyles.Any, | |
| CultureInfo.InvariantCulture, | |
| out var value)) { | |
| namedRange.NameValue = value; | |
| } else { | |
| //if (addressType == ExcelAddressBase.AddressType.ExternalAddress || addressType == ExcelAddressBase.AddressType.ExternalName) | |
| //{ | |
| // var r = new ExcelAddress(fullAddress); | |
| // namedRange.NameFormula = '\'[' + r._wb | |
| //} | |
| //else | |
| //{ | |
| namedRange.NameFormula = fullAddress; | |
| //} | |
| } | |
| } else { | |
| ExcelAddress addr = new ExcelAddress(fullAddress, this, null); | |
| if (localSheetId > -1) { | |
| if (string.IsNullOrEmpty(addr._ws)) { | |
| namedRange = Worksheets[localSheetId + 1].Names.Add( | |
| elem.GetAttribute("name"), | |
| new(this, Worksheets[localSheetId + 1], fullAddress, false)); | |
| } else { | |
| namedRange = Worksheets[localSheetId + 1].Names.Add( | |
| elem.GetAttribute("name"), | |
| new(this, Worksheets[addr._ws], fullAddress, false)); | |
| } | |
| } else { | |
| var ws = Worksheets[addr._ws]; | |
| namedRange = _names.Add(elem.GetAttribute("name"), new(this, ws, fullAddress, false)); | |
| } | |
| } | |
| if (elem.GetAttribute("hidden") == "1" && namedRange != null) { | |
| namedRange.IsNameHidden = true; | |
| } | |
| if (!string.IsNullOrEmpty(elem.GetAttribute("comment"))) { | |
| namedRange.NameComment = elem.GetAttribute("comment"); | |
| } | |
| } | |
| } | |
| } | |
| /// <summary> | |
| /// Provides access to all the worksheets in the workbook. | |
| /// </summary> | |
| public ExcelWorksheets Worksheets { | |
| get { | |
| if (_worksheets == null) { | |
| var sheetsNode = WorkbookXml.DocumentElement.SelectSingleNode( | |
| "d:sheets", | |
| _namespaceManager); | |
| if (sheetsNode == null) { | |
| sheetsNode = CreateNode("d:sheets"); | |
| } | |
| _worksheets = new(_package, this, _namespaceManager, sheetsNode); | |
| } | |
| return (_worksheets); | |
| } | |
| } | |
| /// <summary> | |
| /// Provides access to named ranges | |
| /// </summary> | |
| public ExcelNamedRangeCollection Names => _names; | |
| internal FormulaParser FormulaParser { | |
| get { | |
| if (_formulaParser == null) { | |
| _formulaParser = new(new EpplusExcelDataProvider(this)); | |
| } | |
| return _formulaParser; | |
| } | |
| } | |
| public FormulaParserManager FormulaParserManager { | |
| get { | |
| if (_parserManager == null) { | |
| _parserManager = new(FormulaParser); | |
| } | |
| return _parserManager; | |
| } | |
| } | |
| private ExcelProtection _protection; | |
| /// <summary> | |
| /// Access properties to protect or unprotect a workbook | |
| /// </summary> | |
| public ExcelProtection Protection => _protection ??= new(NameSpaceManager, TopNode); | |
| private ExcelWorkbookView _view; | |
| /// <summary> | |
| /// Access to workbook view properties | |
| /// </summary> | |
| public ExcelWorkbookView View { | |
| get { | |
| if (_view == null) { | |
| _view = new(NameSpaceManager, TopNode, this); | |
| } | |
| return _view; | |
| } | |
| } | |
| /// <summary> | |
| /// URI to the workbook inside the package | |
| /// </summary> | |
| internal static Uri WorkbookUri { get; } = new("/xl/workbook.xml", UriKind.Relative); | |
| /// <summary> | |
| /// URI to the styles inside the package | |
| /// </summary> | |
| private static Uri StylesUri { get; } = new("/xl/styles.xml", UriKind.Relative); | |
| /// <summary> | |
| /// URI to the shared strings inside the package | |
| /// </summary> | |
| private static Uri SharedStringsUri { get; } = new("/xl/sharedStrings.xml", UriKind.Relative); | |
| /// <summary> | |
| /// Returns a reference to the workbook's part within the package | |
| /// </summary> | |
| internal ZipPackagePart Part => (_package.Package.GetPart(WorkbookUri)); | |
| /// <summary> | |
| /// Provides access to the XML data representing the workbook in the package. | |
| /// </summary> | |
| internal XmlDocument WorkbookXml { get; } | |
| private const string _codeModuleNamePath = "d:workbookPr/@codeName"; | |
| internal string CodeModuleName { | |
| get => GetXmlNodeString(_codeModuleNamePath); | |
| set => SetXmlNodeString(_codeModuleNamePath, value); | |
| } | |
| internal void CodeNameChange(string value) { | |
| CodeModuleName = value; | |
| } | |
| private const string _date1904Path = "d:workbookPr/@date1904"; | |
| internal const double _date1904Offset = 365.5 * 4; // offset to fix 1900 and 1904 differences, 4 OLE years | |
| /// <summary> | |
| /// The date systems used by Microsoft Excel can be based on one of two different dates. By default, a serial number of 1 in Microsoft Excel represents January 1, 1900. | |
| /// The default for the serial number 1 can be changed to represent January 2, 1904. | |
| /// This option was included in Microsoft Excel for Windows to make it compatible with Excel for the Macintosh, which defaults to January 2, 1904. | |
| /// </summary> | |
| public bool Date1904 { | |
| get => GetXmlNodeBool(_date1904Path, false); | |
| set { | |
| if (Date1904 != value) { | |
| // Like Excel when the option it's changed update it all cells with Date format | |
| foreach (var item in Worksheets) { | |
| item.UpdateCellsWithDate1904Setting(); | |
| } | |
| } | |
| SetXmlNodeBool(_date1904Path, value, false); | |
| } | |
| } | |
| private readonly XmlDocument _stylesXml; | |
| /// <summary> | |
| /// Package styles collection. Used internally to access style data. | |
| /// </summary> | |
| public ExcelStyles Styles { | |
| get { | |
| if (_styles == null) { | |
| _styles = new(NameSpaceManager, _stylesXml, this); | |
| } | |
| return _styles; | |
| } | |
| } | |
| /// <summary> | |
| /// The office document properties | |
| /// </summary> | |
| public OfficeProperties Properties { | |
| get { | |
| if (_properties == null) { | |
| // Create a NamespaceManager to handle the default namespace, | |
| // and create a prefix for the default namespace: | |
| _properties = new(_package, NameSpaceManager); | |
| } | |
| return _properties; | |
| } | |
| } | |
| private readonly string _calcModePath = "d:calcPr/@calcMode"; | |
| /// <summary> | |
| /// Calculation mode for the workbook. | |
| /// </summary> | |
| public ExcelCalcMode CalcMode { | |
| get { | |
| string calcMode = GetXmlNodeString(_calcModePath); | |
| switch (calcMode) { | |
| case "autoNoTable": | |
| return ExcelCalcMode.AutomaticNoTable; | |
| case "manual": | |
| return ExcelCalcMode.Manual; | |
| default: | |
| return ExcelCalcMode.Automatic; | |
| } | |
| } | |
| set { | |
| switch (value) { | |
| case ExcelCalcMode.AutomaticNoTable: | |
| SetXmlNodeString(_calcModePath, "autoNoTable"); | |
| break; | |
| case ExcelCalcMode.Manual: | |
| SetXmlNodeString(_calcModePath, "manual"); | |
| break; | |
| default: | |
| SetXmlNodeString(_calcModePath, "auto"); | |
| break; | |
| } | |
| } | |
| } | |
| private const string _fullCalcOnLoadPath = "d:calcPr/@fullCalcOnLoad"; | |
| /// <summary> | |
| /// Should Excel do a full calculation after the workbook has been loaded? | |
| /// <remarks>This property is always true for both new workbooks and loaded templates(on load). If this is not the wanted behavior set this property to false.</remarks> | |
| /// </summary> | |
| public bool FullCalcOnLoad { | |
| get => GetXmlNodeBool(_fullCalcOnLoadPath); | |
| set => SetXmlNodeBool(_fullCalcOnLoadPath, value); | |
| } | |
| internal void Save() { | |
| if (Worksheets.Count == 0) { | |
| throw new InvalidOperationException("The workbook must contain at least one worksheet"); | |
| } | |
| DeleteCalcChain(); | |
| UpdateDefinedNamesXml(); | |
| // save the style sheet | |
| Styles.UpdateXml(); | |
| // save all the open worksheets | |
| var isProtected = Protection.LockWindows || Protection.LockStructure; | |
| foreach (ExcelWorksheet worksheet in Worksheets) { | |
| if (isProtected && Protection.LockWindows) { | |
| worksheet.View.WindowProtection = true; | |
| } | |
| worksheet.Save(); | |
| } | |
| _package.Package.CreatePart( | |
| SharedStringsUri, | |
| ExcelPackage._contentTypeSharedString, | |
| SaveSharedStringHandler); | |
| Part.CreateRelationship( | |
| UriHelper.GetRelativeUri(WorkbookUri, SharedStringsUri), | |
| TargetMode.Internal, | |
| ExcelPackage._schemaRelationships + "/sharedStrings"); | |
| // Data validation | |
| ValidateDataValidations(); | |
| } | |
| private void DeleteCalcChain() { | |
| //Remove the calc chain if it exists. | |
| Uri uriCalcChain = new Uri("/xl/calcChain.xml", UriKind.Relative); | |
| if (_package.Package.PartExists(uriCalcChain)) { | |
| Uri calcChain = new Uri("calcChain.xml", UriKind.Relative); | |
| foreach (var relationship in Part.GetRelationships()) { | |
| if (relationship.TargetUri == calcChain) { | |
| Part.DeleteRelationship(relationship.Id); | |
| break; | |
| } | |
| } | |
| // delete the calcChain part | |
| _package.Package.DeletePart(uriCalcChain); | |
| } | |
| } | |
| private void ValidateDataValidations() { | |
| foreach (var sheet in Worksheets) { | |
| if (!(sheet is ExcelChartsheet)) { | |
| sheet.DataValidations.ValidateAll(); | |
| } | |
| } | |
| } | |
| private void SaveSharedStringHandler(StreamWriter sw) { | |
| var cache = new StringBuilder(); | |
| cache.AppendFormat( | |
| "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\" ?><sst xmlns=\"http://schemas.openxmlformats.org/spreadsheetml/2006/main\" count=\"{0}\" uniqueCount=\"{0}\">", | |
| _sharedStrings.Count); | |
| foreach (string t in _sharedStrings.Keys) { | |
| SharedStringItem ssi = _sharedStrings[t]; | |
| if (ssi.isRichText) { | |
| cache.Append("<si>"); | |
| ConvertUtil.ExcelEncodeString(cache, t); | |
| cache.Append("</si>"); | |
| } else { | |
| if (t.Length > 0 | |
| && (t[0] == ' ' | |
| || t[t.Length - 1] == ' ' | |
| || t.Contains(" ") | |
| || t.Contains("\t") | |
| || t.Contains("\n") | |
| || t.Contains( | |
| "\n"))) //Fixes issue 14849 | |
| { | |
| cache.Append("<si><t xml:space=\"preserve\">"); | |
| } else { | |
| cache.Append("<si><t>"); | |
| } | |
| ConvertUtil.ExcelEncodeString(cache, ConvertUtil.ExcelEscapeString(t)); | |
| cache.Append("</t></si>"); | |
| } | |
| if (cache.Length > 0x600000) { | |
| sw.Write(cache.ToString()); | |
| cache = new(); | |
| } | |
| } | |
| cache.Append("</sst>"); | |
| sw.Write(cache.ToString()); | |
| sw.Flush(); | |
| Part.CreateRelationship( | |
| UriHelper.GetRelativeUri(WorkbookUri, SharedStringsUri), | |
| TargetMode.Internal, | |
| ExcelPackage._schemaRelationships + "/sharedStrings"); | |
| } | |
| private void UpdateDefinedNamesXml() { | |
| try { | |
| XmlNode top = WorkbookXml.SelectSingleNode("//d:definedNames", NameSpaceManager); | |
| if (!ExistsNames()) { | |
| if (top != null) { | |
| TopNode.RemoveChild(top); | |
| } | |
| return; | |
| } | |
| if (top == null) { | |
| CreateNode("d:definedNames"); | |
| top = WorkbookXml.SelectSingleNode("//d:definedNames", NameSpaceManager); | |
| } else { | |
| top.RemoveAll(); | |
| } | |
| foreach (ExcelNamedRange name in _names) { | |
| XmlElement elem = WorkbookXml.CreateElement("definedName", ExcelPackage._schemaMain); | |
| top.AppendChild(elem); | |
| elem.SetAttribute("name", name.Name); | |
| if (name.IsNameHidden) { | |
| elem.SetAttribute("hidden", "1"); | |
| } | |
| if (!string.IsNullOrEmpty(name.NameComment)) { | |
| elem.SetAttribute("comment", name.NameComment); | |
| } | |
| SetNameElement(name, elem); | |
| } | |
| foreach (ExcelWorksheet ws in _worksheets) { | |
| if (!(ws is ExcelChartsheet)) { | |
| foreach (ExcelNamedRange name in ws.Names) { | |
| XmlElement elem = WorkbookXml.CreateElement("definedName", ExcelPackage._schemaMain); | |
| top.AppendChild(elem); | |
| elem.SetAttribute("name", name.Name); | |
| elem.SetAttribute("localSheetId", name.LocalSheetId.ToString()); | |
| if (name.IsNameHidden) { | |
| elem.SetAttribute("hidden", "1"); | |
| } | |
| if (!string.IsNullOrEmpty(name.NameComment)) { | |
| elem.SetAttribute("comment", name.NameComment); | |
| } | |
| SetNameElement(name, elem); | |
| } | |
| } | |
| } | |
| } catch (Exception ex) { | |
| throw new("Internal error updating named ranges ", ex); | |
| } | |
| } | |
| private void SetNameElement(ExcelNamedRange name, XmlElement elem) { | |
| if (name.IsName) { | |
| if (string.IsNullOrEmpty(name.NameFormula)) { | |
| if ((name.NameValue.GetType().IsPrimitive | |
| || name.NameValue is double | |
| || name.NameValue is decimal)) { | |
| elem.InnerText = Convert | |
| .ToDouble(name.NameValue, CultureInfo.InvariantCulture) | |
| .ToString("R15", CultureInfo.InvariantCulture); | |
| } else if (name.NameValue is DateTime time) { | |
| elem.InnerText = time.ToOADate().ToString(CultureInfo.InvariantCulture); | |
| } else { | |
| elem.InnerText = "\"" + name.NameValue + "\""; | |
| } | |
| } else { | |
| elem.InnerText = name.NameFormula; | |
| } | |
| } else { | |
| elem.InnerText = name.FullAddressAbsolute; | |
| } | |
| } | |
| /// <summary> | |
| /// Is their any names in the workbook or in the sheets. | |
| /// </summary> | |
| /// <returns>?</returns> | |
| private bool ExistsNames() { | |
| if (_names.Count == 0) { | |
| foreach (ExcelWorksheet ws in Worksheets) { | |
| if (ws is ExcelChartsheet) { | |
| continue; | |
| } | |
| if (ws.Names.Count > 0) { | |
| return true; | |
| } | |
| } | |
| } else { | |
| return true; | |
| } | |
| return false; | |
| } | |
| internal bool ExistsTableName(string name) { | |
| foreach (var ws in Worksheets) { | |
| if (ws.Tables._tableNames.ContainsKey(name)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| internal bool ExistsPivotTableName(string name) { | |
| foreach (var ws in Worksheets) { | |
| if (ws.PivotTables._pivotTableNames.ContainsKey(name)) { | |
| return true; | |
| } | |
| } | |
| return false; | |
| } | |
| internal void AddPivotTable(string cacheId, Uri defUri) { | |
| CreateNode("d:pivotCaches"); | |
| XmlElement item = WorkbookXml.CreateElement("pivotCache", ExcelPackage._schemaMain); | |
| item.SetAttribute("cacheId", cacheId); | |
| var rel = Part.CreateRelationship( | |
| UriHelper.ResolvePartUri(WorkbookUri, defUri), | |
| TargetMode.Internal, | |
| ExcelPackage._schemaRelationships + "/pivotCacheDefinition"); | |
| item.SetAttribute("id", ExcelPackage._schemaRelationships, rel.Id); | |
| var pivotCaches = WorkbookXml.SelectSingleNode("//d:pivotCaches", NameSpaceManager); | |
| pivotCaches.AppendChild(item); | |
| } | |
| internal List<string> _externalReferences = new(); | |
| //internal bool _isCalculated=false; | |
| internal void GetExternalReferences() { | |
| XmlNodeList nl = WorkbookXml.SelectNodes( | |
| "//d:externalReferences/d:externalReference", | |
| NameSpaceManager); | |
| if (nl != null) { | |
| foreach (XmlElement elem in nl) { | |
| string rId = elem.GetAttribute("r:id"); | |
| var rel = Part.GetRelationship(rId); | |
| var part = _package.Package.GetPart(UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri)); | |
| XmlDocument xmlExtRef = new XmlDocument(); | |
| LoadXmlSafe(xmlExtRef, part.GetStream()); | |
| XmlElement book = | |
| xmlExtRef.SelectSingleNode("//d:externalBook", NameSpaceManager) as XmlElement; | |
| if (book != null) { | |
| string rIdExtRef = book.GetAttribute("r:id"); | |
| var relExtRef = part.GetRelationship(rIdExtRef); | |
| if (relExtRef != null) { | |
| _externalReferences.Add(relExtRef.TargetUri.OriginalString); | |
| } | |
| } | |
| } | |
| } | |
| } | |
| internal void ReadAllTables() { | |
| if (_nextTableID > 0) { | |
| return; | |
| } | |
| _nextTableID = 1; | |
| _nextPivotTableID = 1; | |
| foreach (var ws in Worksheets) { | |
| if (!(ws | |
| is ExcelChartsheet)) //Fixes 15273. Chartsheets should be ignored. | |
| { | |
| foreach (var tbl in ws.Tables) { | |
| if (tbl.Id >= _nextTableID) { | |
| _nextTableID = tbl.Id + 1; | |
| } | |
| } | |
| foreach (var pt in ws.PivotTables) { | |
| if (pt.CacheID >= _nextPivotTableID) { | |
| _nextPivotTableID = pt.CacheID + 1; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| } |