|  | /******************************************************************************* | 
|  | * 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; | 
|  | using OfficeOpenXml.FormulaParsing; | 
|  | using OfficeOpenXml.FormulaParsing.LexicalAnalysis; | 
|  | using OfficeOpenXml.Packaging; | 
|  | using OfficeOpenXml.Utils; | 
|  |  | 
|  | namespace OfficeOpenXml; | 
|  |  | 
|  | /// <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; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | } |