| /******************************************************************************* | 
 |  * 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; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 | } |