| /******************************************************************************* | 
 | * 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-11-02 | 
 | * Jan Källman          Total rewrite               2010-03-01 | 
 | * Jan Källman		    License changed GPL-->LGPL  2011-12-27 | 
 | *******************************************************************************/ | 
 |  | 
 | using System; | 
 | using System.Collections; | 
 | using System.Collections.Generic; | 
 | using System.Collections.Immutable; | 
 | using System.ComponentModel; | 
 | using System.Globalization; | 
 | using System.IO; | 
 | using System.Security; | 
 | using System.Text; | 
 | using System.Text.RegularExpressions; | 
 | using System.Xml; | 
 |  | 
 | namespace AppsheetEpplus; | 
 |  | 
 | /// <summary> | 
 | /// Worksheet hidden enumeration | 
 | /// </summary> | 
 | public enum eWorkSheetHidden { | 
 |   /// <summary> | 
 |   /// The worksheet is visible | 
 |   /// </summary> | 
 |   Visible, | 
 |  | 
 |   /// <summary> | 
 |   /// The worksheet is hidden but can be shown by the user via the user interface | 
 |   /// </summary> | 
 |   Hidden, | 
 |  | 
 |   /// <summary> | 
 |   /// The worksheet is hidden and cannot be shown by the user via the user interface | 
 |   /// </summary> | 
 |   VeryHidden, | 
 | } | 
 |  | 
 | [Flags] | 
 | internal enum CellFlags { | 
 |   //Merged = 0x1, | 
 |   RichText = 0x2, | 
 |   SharedFormula = 0x4, | 
 |   ArrayFormula = 0x8, | 
 | } | 
 |  | 
 | /// <summary> | 
 | /// Represents an Excel Chartsheet and provides access to its properties and methods | 
 | /// </summary> | 
 | public class ExcelChartsheet : ExcelWorksheet { | 
 |   public ExcelChartsheet( | 
 |       XmlNamespaceManager ns, | 
 |       ExcelPackage pck, | 
 |       ExcelWorkbook workbook, | 
 |       Uri worksheetUri, | 
 |       string name, | 
 |       int sheetId, | 
 |       int positionId, | 
 |       eWorkSheetHidden hidden) | 
 |       : base(ns, pck, workbook, worksheetUri, name, sheetId, positionId, hidden) {} | 
 | } | 
 |  | 
 | /// <summary> | 
 | /// Represents an Excel worksheet and provides access to its properties and methods | 
 | /// </summary> | 
 | public class ExcelWorksheet : XmlHelper, IEqualityComparer<ExcelWorksheet> { | 
 |   internal class Formulas { | 
 |     public Formulas(ISourceCodeTokenizer tokenizer) { | 
 |       _tokenizer = tokenizer; | 
 |     } | 
 |  | 
 |     public static string RemoveDummyFunction(string formula) { | 
 |       const string dummyFunctionConcatenate = "\"&\""; | 
 |       const string dummyFunctionPrefix = "IFERROR(__xludf.DUMMYFUNCTION(\""; | 
 |       const string dummyFunctionSuffix = "\"),"; | 
 |  | 
 |       if (string.IsNullOrEmpty(formula)) { | 
 |         return formula; | 
 |       } | 
 |  | 
 |       // Look for Prefix | 
 |       if (!formula.StartsWith(dummyFunctionPrefix)) { | 
 |         return formula; | 
 |       } | 
 |  | 
 |       // Look for Suffix | 
 |       int index = formula.LastIndexOf(dummyFunctionSuffix); | 
 |       if (index < 0) { | 
 |         return formula; | 
 |       } | 
 |  | 
 |       // Trim Suffix | 
 |       formula = formula.Substring(0, index); | 
 |  | 
 |       // Trim Prefix | 
 |       formula = formula.Replace(dummyFunctionPrefix, ""); | 
 |  | 
 |       // Remove string concatentations from long formulas. | 
 |       // Google break the quoted string into 254 character segments which are concatenated. | 
 |       if (formula.Length >= 254) { | 
 |         formula = formula.Replace(dummyFunctionConcatenate, ""); | 
 |       } | 
 |  | 
 |       // Replace doubled quotes with single quote | 
 |       formula = formula.Replace("\"\"", "\""); | 
 |  | 
 |       // Return formula | 
 |       return formula; | 
 |     } | 
 |  | 
 |     private readonly ISourceCodeTokenizer _tokenizer; | 
 |  | 
 |     internal int Index { get; set; } | 
 |  | 
 |     internal string Address { get; set; } | 
 |  | 
 |     internal bool IsArray { get; set; } | 
 |  | 
 |     public string Formula { get; set; } | 
 |  | 
 |     public int StartRow { get; set; } | 
 |  | 
 |     public int StartCol { get; set; } | 
 |  | 
 |     private IEnumerable<Token> Tokens { get; set; } | 
 |  | 
 |     internal string GetFormula(int row, int column, string worksheet) { | 
 |       if ((StartRow == row && StartCol == column) || IsArray) { | 
 |         return RemoveDummyFunction(Formula); | 
 |       } | 
 |  | 
 |       if (Tokens == null) { | 
 |         Tokens = _tokenizer.Tokenize(RemoveDummyFunction(Formula), worksheet); | 
 |       } | 
 |  | 
 |       string f = ""; | 
 |       foreach (var token in Tokens) { | 
 |         if (token.TokenType == TokenType.ExcelAddress) { | 
 |           var a = new ExcelFormulaAddress(token.Value); | 
 |           f += a.GetOffset(row - StartRow, column - StartCol); | 
 |         } else { | 
 |           f += token.Value; | 
 |         } | 
 |       } | 
 |       return f; | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Collection containing merged cell addresses | 
 |   /// </summary> | 
 |   public class MergeCellsCollection : IEnumerable<string> { | 
 |     internal MergeCellsCollection() {} | 
 |  | 
 |     internal readonly CellStore<int> _cells = new(); | 
 |     private readonly List<string> _list = []; | 
 |  | 
 |     internal List<string> List => _list; | 
 |  | 
 |     public string this[int row, int column] { | 
 |       get { | 
 |         int ix = -1; | 
 |         if (_cells.Exists(row, column, ref ix) | 
 |             && ix >= 0 | 
 |             && ix | 
 |                 < List.Count) //Fixes issue 15075 | 
 |         { | 
 |           return List[ix]; | 
 |         } | 
 |         return null; | 
 |       } | 
 |     } | 
 |  | 
 |     public string this[int index] => _list[index]; | 
 |  | 
 |     internal void Add(ExcelAddressBase address, bool doValidate) { | 
 |       //Validate | 
 |       if (doValidate && Validate(address) == false) { | 
 |         throw (new ArgumentException("Can't merge and already merged range")); | 
 |       } | 
 |       var ix = _list.Count; | 
 |       _list.Add(address.Address); | 
 |       SetIndex(address, ix); | 
 |     } | 
 |  | 
 |     private bool Validate(ExcelAddressBase address) { | 
 |       int ix = 0; | 
 |       if (_cells.Exists(address._fromRow, address._fromCol, ref ix)) { | 
 |         if (ix >= 0 && ix < _list.Count && _list[ix] != null && address.Address == _list[ix]) { | 
 |           return true; | 
 |         } | 
 |         return false; | 
 |       } | 
 |  | 
 |       var cse = new CellsStoreEnumerator<int>( | 
 |           _cells, | 
 |           address._fromRow, | 
 |           address._fromCol, | 
 |           address._toRow, | 
 |           address._toCol); | 
 |       //cells | 
 |       while (cse.Next()) { | 
 |         return false; | 
 |       } | 
 |       //Entire column | 
 |       cse = new(_cells, 0, address._fromCol, 0, address._toCol); | 
 |       while (cse.Next()) { | 
 |         return false; | 
 |       } | 
 |       //Entire row | 
 |       cse = new(_cells, address._fromRow, 0, address._toRow, 0); | 
 |       while (cse.Next()) { | 
 |         return false; | 
 |       } | 
 |       return true; | 
 |     } | 
 |  | 
 |     internal void SetIndex(ExcelAddressBase address, int ix) { | 
 |       if (address._fromRow == 1 | 
 |           && address._toRow | 
 |               == ExcelPackage.MaxRows) //Entire row | 
 |       { | 
 |         for (int col = address._fromCol; col <= address._toCol; col++) { | 
 |           _cells.SetValue(0, col, ix); | 
 |         } | 
 |       } else if (address._fromCol == 1 | 
 |           && address._toCol | 
 |               == ExcelPackage.MaxColumns) //Entire row | 
 |       { | 
 |         for (int row = address._fromRow; row <= address._toRow; row++) { | 
 |           _cells.SetValue(row, 0, ix); | 
 |         } | 
 |       } else { | 
 |         for (int col = address._fromCol; col <= address._toCol; col++) { | 
 |           for (int row = address._fromRow; row <= address._toRow; row++) { | 
 |             _cells.SetValue(row, col, ix); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     public int Count => _list.Count; | 
 |  | 
 |     internal void Remove(string item) { | 
 |       _list.Remove(item); | 
 |     } | 
 |  | 
 |     public IEnumerator<string> GetEnumerator() { | 
 |       return _list.GetEnumerator(); | 
 |     } | 
 |  | 
 |     IEnumerator IEnumerable.GetEnumerator() { | 
 |       return _list.GetEnumerator(); | 
 |     } | 
 |  | 
 |     internal void Clear(ExcelAddressBase destination) { | 
 |       var cse = new CellsStoreEnumerator<int>( | 
 |           _cells, | 
 |           destination._fromRow, | 
 |           destination._fromCol, | 
 |           destination._toRow, | 
 |           destination._toCol); | 
 |       var used = new HashSet<int>(); | 
 |       while (cse.Next()) { | 
 |         var v = cse.Value; | 
 |         if (!used.Contains(v) && _list[v] != null) { | 
 |           var adr = new ExcelAddressBase(_list[v]); | 
 |           if (!(destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Inside | 
 |                       || destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Equal)) { | 
 |             throw (new InvalidOperationException( | 
 |                     string.Format( | 
 |                         "Can't delete merged cells. A range is partly merged with the deleted range. {0}", | 
 |                         adr._address))); | 
 |           } | 
 |           used.Add(v); | 
 |         } | 
 |       } | 
 |  | 
 |       _cells.Clear( | 
 |           destination._fromRow, | 
 |           destination._fromCol, | 
 |           destination._toRow - destination._fromRow + 1, | 
 |           destination._toCol - destination._fromCol + 1); | 
 |       foreach (var i in used) { | 
 |         _list[i] = null; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   internal readonly CellStore<object> _values = new(); | 
 |   internal readonly CellStore<string> _types = new(); | 
 |   internal readonly CellStore<int> _styles = new(); | 
 |   internal readonly CellStore<object> _formulas = new(); | 
 |   internal readonly FlagCellStore _flags = new(); | 
 |   internal CellStore<List<Token>> _formulaTokens; | 
 |  | 
 |   internal readonly CellStore<Uri> _hyperLinks = new(); | 
 |   internal readonly CellStore<ExcelComment> _commentsStore = new(); | 
 |  | 
 |   internal readonly Dictionary<int, Formulas> _sharedFormulas = new(); | 
 |  | 
 |   internal readonly ExcelPackage _package; | 
 |   private readonly ExcelWorkbook _workbook; | 
 |   private ExcelWorksheetView _sheetView; | 
 |  | 
 |   internal static ImmutableArray<string> WorksheetSchemaNodeOrder = [ | 
 |     "sheetPr", | 
 |     "tabColor", | 
 |     "outlinePr", | 
 |     "pageSetUpPr", | 
 |     "dimension", | 
 |     "sheetViews", | 
 |     "sheetFormatPr", | 
 |     "cols", | 
 |     "sheetData", | 
 |     "sheetProtection", | 
 |     "protectedRanges", | 
 |     "scenarios", | 
 |     "autoFilter", | 
 |     "sortState", | 
 |     "dataConsolidate", | 
 |     "customSheetViews", | 
 |     "customSheetViews", | 
 |     "mergeCells", | 
 |     "phoneticPr", | 
 |     "conditionalFormatting", | 
 |     "dataValidations", | 
 |     "hyperlinks", | 
 |     "printOptions", | 
 |     "pageMargins", | 
 |     "pageSetup", | 
 |     "headerFooter", | 
 |     "linePrint", | 
 |     "rowBreaks", | 
 |     "colBreaks", | 
 |     "customProperties", | 
 |     "cellWatches", | 
 |     "ignoredErrors", | 
 |     "smartTags", | 
 |     "drawing", | 
 |     "legacyDrawing", | 
 |     "legacyDrawingHF", | 
 |     "picture", | 
 |     "oleObjects", | 
 |     "activeXControls", | 
 |     "webPublishItems", | 
 |     "tableParts", | 
 |     "extLst", | 
 |   ]; | 
 |  | 
 |   protected override ImmutableArray<string> SchemaNodeOrder => WorksheetSchemaNodeOrder; | 
 |  | 
 |   internal ExcelWorksheet( | 
 |       XmlNamespaceManager ns, | 
 |       ExcelPackage excelPackage, | 
 |       ExcelWorkbook workbook, | 
 |       Uri worksheetUri, | 
 |       string name, | 
 |       int sheetId, | 
 |       int positionId, | 
 |       eWorkSheetHidden hidden) | 
 |       : base(ns, null) { | 
 |     _workbook = workbook; | 
 |     _package = excelPackage; | 
 |     _names = new(Workbook, this); | 
 |  | 
 |     Hidden = hidden; | 
 |     Name = name; | 
 |     PositionID = positionId; | 
 |     SheetID = sheetId; | 
 |  | 
 |     Part = _package.Package.GetPart(worksheetUri); | 
 |     Part.SaveHandler = SaveHandler; | 
 |  | 
 |     // First Columns, rows, cells, mergecells, hyperlinks and pagebreakes are loaded from an | 
 |     // XmlTextReader to optimize speed. | 
 |     using var stream = Part.GetStream(); | 
 |     var xr = new XmlTextReader(stream); | 
 |     xr.ProhibitDtd = true; | 
 |     xr.WhitespaceHandling = WhitespaceHandling.None; | 
 |     LoadColumns(xr); //columnXml | 
 |     long start = stream.Position; | 
 |     LoadCells(xr); | 
 |     var nextElementLength = GetAttributeLength(xr); | 
 |     long end = stream.Position - nextElementLength; | 
 |     LoadMergeCells(xr); | 
 |     LoadHyperLinks(xr); | 
 |     LoadRowPageBreakes(xr); | 
 |     LoadColPageBreakes(xr); | 
 |  | 
 |     // Then the rest of the XML is extracted and loaded into the WorksheetXml document. | 
 |     stream.Seek(0, SeekOrigin.Begin); | 
 |     var xml = GetWorkSheetXml(stream, start, end, out var encoding); | 
 |  | 
 |     //first char is invalid sometimes?? | 
 |     if (xml[0] != '<') { | 
 |       LoadXmlSafe(WorksheetXml, xml.Substring(1, xml.Length - 1), encoding); | 
 |     } else { | 
 |       LoadXmlSafe(WorksheetXml, xml, encoding); | 
 |     } | 
 |     ClearNodes(); | 
 |  | 
 |     TopNode = WorksheetXml.DocumentElement; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// The Zip.ZipPackagePart for the worksheet within the package | 
 |   /// </summary> | 
 |   internal ZipPackagePart Part { get; } | 
 |  | 
 |   /// <summary> | 
 |   /// The unique identifier for the worksheet. | 
 |   /// </summary> | 
 |   internal int SheetID { get; } | 
 |  | 
 |   /// <summary> | 
 |   /// The position of the worksheet. | 
 |   /// </summary> | 
 |   internal int PositionID { get; } | 
 |  | 
 |   /// <summary> | 
 |   /// Address for autofilter | 
 |   /// <seealso cref="ExcelRangeBase.AutoFilter" /> | 
 |   /// </summary> | 
 |   public ExcelAddressBase AutoFilterAddress { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       string address = GetXmlNodeString("d:autoFilter/@ref"); | 
 |       if (address == "") { | 
 |         return null; | 
 |       } | 
 |       return new(address); | 
 |     } | 
 |     internal set { | 
 |       CheckSheetType(); | 
 |       SetXmlNodeString("d:autoFilter/@ref", value.Address); | 
 |     } | 
 |   } | 
 |  | 
 |   internal void CheckSheetType() { | 
 |     if (this is ExcelChartsheet) { | 
 |       throw (new NotSupportedException( | 
 |               "This property or method is not supported for a Chartsheet")); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Returns a ExcelWorksheetView object that allows you to set the view state properties of the worksheet | 
 |   /// </summary> | 
 |   public ExcelWorksheetView View { | 
 |     get { | 
 |       if (_sheetView == null) { | 
 |         XmlNode node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager); | 
 |         if (node == null) { | 
 |           CreateNode("d:sheetViews/d:sheetView"); //this one shouls always exist. but check anyway | 
 |           node = TopNode.SelectSingleNode("d:sheetViews/d:sheetView", NameSpaceManager); | 
 |         } | 
 |         _sheetView = new(NameSpaceManager, node, this); | 
 |       } | 
 |       return (_sheetView); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// The worksheet's display name as it appears on the tab | 
 |   /// </summary> | 
 |   public string Name { get; } | 
 |  | 
 |   private readonly ExcelNamedRangeCollection _names; | 
 |  | 
 |   /// <summary> | 
 |   /// Provides access to named ranges | 
 |   /// </summary> | 
 |   public ExcelNamedRangeCollection Names { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return _names; | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Indicates if the worksheet is hidden in the workbook | 
 |   /// </summary> | 
 |   public eWorkSheetHidden Hidden { | 
 |     get { | 
 |       string state = _workbook.GetXmlNodeString( | 
 |           string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID)); | 
 |       if (state == "hidden") { | 
 |         return eWorkSheetHidden.Hidden; | 
 |       } | 
 |       if (state == "veryHidden") { | 
 |         return eWorkSheetHidden.VeryHidden; | 
 |       } | 
 |       return eWorkSheetHidden.Visible; | 
 |     } | 
 |     set { | 
 |       if (value == eWorkSheetHidden.Visible) { | 
 |         _workbook.DeleteNode(string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID)); | 
 |       } else { | 
 |         string v; | 
 |         v = value.ToString(); | 
 |         v = v.Substring(0, 1).ToLower(CultureInfo.InvariantCulture) + v.Substring(1); | 
 |         _workbook.SetXmlNodeString( | 
 |             string.Format("d:sheets/d:sheet[@sheetId={0}]/@state", SheetID), | 
 |             v); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private double _defaultRowHeight = double.NaN; | 
 |  | 
 |   /// <summary> | 
 |   /// Get/set the default height of all rows in the worksheet | 
 |   /// </summary> | 
 |   public double DefaultRowHeight { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (double.IsNaN(_defaultRowHeight)) { | 
 |         _defaultRowHeight = GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight"); | 
 |         if (double.IsNaN(_defaultRowHeight)) { | 
 |           _defaultRowHeight = 15; // Excel default height | 
 |         } | 
 |       } | 
 |       return _defaultRowHeight; | 
 |     } | 
 |     set { | 
 |       CheckSheetType(); | 
 |       _defaultRowHeight = value; | 
 |       SetXmlNodeString( | 
 |           "d:sheetFormatPr/@defaultRowHeight", | 
 |           value.ToString(CultureInfo.InvariantCulture)); | 
 |       SetXmlNodeBool("d:sheetFormatPr/@customHeight", value != 15); | 
 |  | 
 |       if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth"))) { | 
 |         DefaultColWidth = 9.140625; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get/set the default width of all rows in the worksheet | 
 |   /// </summary> | 
 |   public double DefaultColWidth { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       double ret = GetXmlNodeDouble("d:sheetFormatPr/@defaultColWidth"); | 
 |       if (double.IsNaN(ret)) { | 
 |         ret = 9.140625; // Excel's default width | 
 |       } | 
 |       return ret; | 
 |     } | 
 |     set { | 
 |       CheckSheetType(); | 
 |       SetXmlNodeString( | 
 |           "d:sheetFormatPr/@defaultColWidth", | 
 |           value.ToString(CultureInfo.InvariantCulture)); | 
 |  | 
 |       if (double.IsNaN(GetXmlNodeDouble("d:sheetFormatPr/@defaultRowHeight"))) { | 
 |         DefaultRowHeight = 15; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /** <outlinePr applyStyles="1" summaryBelow="0" summaryRight="0" /> **/ | 
 |   private const string _outLineSummaryBelowPath = "d:sheetPr/d:outlinePr/@summaryBelow"; | 
 |  | 
 |   /// <summary> | 
 |   /// Summary rows below details | 
 |   /// </summary> | 
 |   public bool OutLineSummaryBelow { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return GetXmlNodeBool(_outLineSummaryBelowPath); | 
 |     } | 
 |     set { | 
 |       CheckSheetType(); | 
 |       SetXmlNodeString(_outLineSummaryBelowPath, value ? "1" : "0"); | 
 |     } | 
 |   } | 
 |  | 
 |   private const string _outLineSummaryRightPath = "d:sheetPr/d:outlinePr/@summaryRight"; | 
 |  | 
 |   /// <summary> | 
 |   /// Summary rows to right of details | 
 |   /// </summary> | 
 |   public bool OutLineSummaryRight { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return GetXmlNodeBool(_outLineSummaryRightPath); | 
 |     } | 
 |     set { | 
 |       CheckSheetType(); | 
 |       SetXmlNodeString(_outLineSummaryRightPath, value ? "1" : "0"); | 
 |     } | 
 |   } | 
 |  | 
 |   private const string _outLineApplyStylePath = "d:sheetPr/d:outlinePr/@applyStyles"; | 
 |  | 
 |   /// <summary> | 
 |   /// Automatic styles | 
 |   /// </summary> | 
 |   public bool OutLineApplyStyle { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return GetXmlNodeBool(_outLineApplyStylePath); | 
 |     } | 
 |     set { | 
 |       CheckSheetType(); | 
 |       SetXmlNodeString(_outLineApplyStylePath, value ? "1" : "0"); | 
 |     } | 
 |   } | 
 |  | 
 |   private const string _codeModuleNamePath = "d:sheetPr/@codeName"; | 
 |  | 
 |   internal string CodeModuleName { | 
 |     get => GetXmlNodeString(_codeModuleNamePath); | 
 |     set => SetXmlNodeString(_codeModuleNamePath, value); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// The XML document holding the worksheet data. | 
 |   /// All column, row, cell, pagebreak, merged cell and hyperlink-data are loaded into memory and removed from the document when loading the document. | 
 |   /// </summary> | 
 |   internal XmlDocument WorksheetXml { get; } = new(); | 
 |  | 
 |   internal ExcelVmlDrawingCommentCollection _vmlDrawings; | 
 |  | 
 |   /// <summary> | 
 |   /// Vml drawings. underlaying object for comments | 
 |   /// </summary> | 
 |   internal ExcelVmlDrawingCommentCollection VmlDrawingsComments { | 
 |     get { | 
 |       if (_vmlDrawings == null) { | 
 |         CreateVmlCollection(); | 
 |       } | 
 |       return _vmlDrawings; | 
 |     } | 
 |   } | 
 |  | 
 |   internal ExcelCommentCollection _comments; | 
 |  | 
 |   /// <summary> | 
 |   /// Collection of comments | 
 |   /// </summary> | 
 |   public ExcelCommentCollection Comments { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (_comments == null) { | 
 |         CreateVmlCollection(); | 
 |         _comments = new(_package, this, NameSpaceManager); | 
 |       } | 
 |       return _comments; | 
 |     } | 
 |   } | 
 |  | 
 |   private void CreateVmlCollection() { | 
 |     var vmlNode = WorksheetXml.DocumentElement.SelectSingleNode( | 
 |         "d:legacyDrawing/@r:id", | 
 |         NameSpaceManager); | 
 |     if (vmlNode == null) { | 
 |       _vmlDrawings = new(_package, this, null); | 
 |     } else { | 
 |       if (Part.RelationshipExists(vmlNode.Value)) { | 
 |         var rel = Part.GetRelationship(vmlNode.Value); | 
 |         var vmlUri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); | 
 |  | 
 |         _vmlDrawings = new(_package, this, vmlUri); | 
 |         _vmlDrawings.RelId = rel.Id; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get the lenth of the attributes | 
 |   /// Conditional formatting attributes can be extremly long som get length of the attributes to finetune position. | 
 |   /// </summary> | 
 |   /// <param name="xr"></param> | 
 |   /// <returns></returns> | 
 |   private int GetAttributeLength(XmlTextReader xr) { | 
 |     if (xr.NodeType != XmlNodeType.Element) { | 
 |       return 0; | 
 |     } | 
 |     var length = 0; | 
 |  | 
 |     for (int i = 0; i < xr.AttributeCount; i++) { | 
 |       var a = xr.GetAttribute(i); | 
 |       length += string.IsNullOrEmpty(a) ? 0 : a.Length; | 
 |     } | 
 |     return length; | 
 |   } | 
 |  | 
 |   private void LoadRowPageBreakes(XmlTextReader xr) { | 
 |     if (!ReadUntil(xr, "rowBreaks", "colBreaks")) { | 
 |       return; | 
 |     } | 
 |     while (xr.Read()) { | 
 |       if (xr.LocalName == "brk") { | 
 |         if (xr.NodeType == XmlNodeType.Element) { | 
 |           if (int.TryParse(xr.GetAttribute("id"), out var id)) { | 
 |             Row(id).PageBreak = true; | 
 |           } | 
 |         } | 
 |       } else { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private void LoadColPageBreakes(XmlTextReader xr) { | 
 |     if (!ReadUntil(xr, "colBreaks")) { | 
 |       return; | 
 |     } | 
 |     while (xr.Read()) { | 
 |       if (xr.LocalName == "brk") { | 
 |         if (xr.NodeType == XmlNodeType.Element) { | 
 |           if (int.TryParse(xr.GetAttribute("id"), out var id)) { | 
 |             Column(id).PageBreak = true; | 
 |           } | 
 |         } | 
 |       } else { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private void ClearNodes() { | 
 |     if (WorksheetXml.SelectSingleNode("//d:cols", NameSpaceManager) != null) { | 
 |       WorksheetXml.SelectSingleNode("//d:cols", NameSpaceManager).RemoveAll(); | 
 |     } | 
 |     if (WorksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager) != null) { | 
 |       WorksheetXml.SelectSingleNode("//d:mergeCells", NameSpaceManager).RemoveAll(); | 
 |     } | 
 |     if (WorksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager) != null) { | 
 |       WorksheetXml.SelectSingleNode("//d:hyperlinks", NameSpaceManager).RemoveAll(); | 
 |     } | 
 |     if (WorksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager) != null) { | 
 |       WorksheetXml.SelectSingleNode("//d:rowBreaks", NameSpaceManager).RemoveAll(); | 
 |     } | 
 |     if (WorksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager) != null) { | 
 |       WorksheetXml.SelectSingleNode("//d:colBreaks", NameSpaceManager).RemoveAll(); | 
 |     } | 
 |   } | 
 |  | 
 |   private const int _blocksize = 8192; | 
 |  | 
 |   private string GetWorkSheetXml(Stream stream, long start, long end, out Encoding encoding) { | 
 |     StreamReader sr = new StreamReader(stream); | 
 |     int length = 0; | 
 |     char[] block; | 
 |     int pos; | 
 |     StringBuilder sb = new StringBuilder(); | 
 |     Match startmMatch, | 
 |         endMatch; | 
 |     do { | 
 |       int size = stream.Length < _blocksize ? (int)stream.Length : _blocksize; | 
 |       block = new char[size]; | 
 |       pos = sr.ReadBlock(block, 0, size); | 
 |       sb.Append(block, 0, pos); | 
 |       length += size; | 
 |     } while (length < start + 20 && length < end); | 
 |     startmMatch = Regex.Match(sb.ToString(), string.Format("(<[^>]*{0}[^>]*>)", "sheetData")); | 
 |     if (!startmMatch.Success) //Not found | 
 |     { | 
 |       encoding = sr.CurrentEncoding; | 
 |       return sb.ToString(); | 
 |     } | 
 |     string s = sb.ToString(); | 
 |     string xml = s.Substring(0, startmMatch.Index); | 
 |     if (startmMatch.Value.EndsWith("/>")) { | 
 |       xml += s.Substring(startmMatch.Index, s.Length - startmMatch.Index); | 
 |     } else { | 
 |       if (sr.Peek() != -1) { | 
 |         /**** Fixes issue 14788. Fix by Philip Garrett ****/ | 
 |         long endSeekStart = end; | 
 |  | 
 |         while (endSeekStart >= 0) { | 
 |           endSeekStart = Math.Max(endSeekStart - _blocksize, 0); | 
 |           int size = (int)(end - endSeekStart); | 
 |           stream.Seek(endSeekStart, SeekOrigin.Begin); | 
 |           block = new char[size]; | 
 |           sr = new(stream); | 
 |           pos = sr.ReadBlock(block, 0, size); | 
 |           sb = new(); | 
 |           sb.Append(block, 0, pos); | 
 |           s = sb.ToString(); | 
 |           endMatch = Regex.Match(s, string.Format("(</[^>]*{0}[^>]*>)", "sheetData")); | 
 |           if (endMatch.Success) { | 
 |             break; | 
 |           } | 
 |         } | 
 |       } | 
 |       endMatch = Regex.Match(s, string.Format("(</[^>]*{0}[^>]*>)", "sheetData")); | 
 |       xml += | 
 |           "<sheetData/>" | 
 |               + s.Substring( | 
 |                   endMatch.Index + endMatch.Length, | 
 |                   s.Length - (endMatch.Index + endMatch.Length)); | 
 |     } | 
 |     if (sr.Peek() > -1) { | 
 |       xml += sr.ReadToEnd(); | 
 |     } | 
 |  | 
 |     encoding = sr.CurrentEncoding; | 
 |     return xml; | 
 |   } | 
 |  | 
 |   private void GetBlockPos(string xml, string tag, ref int start, ref int end) { | 
 |     Match startmMatch, | 
 |         endMatch; | 
 |     startmMatch = Regex.Match(xml.Substring(start), string.Format("(<[^>]*{0}[^>]*>)", tag)); //"<[a-zA-Z:]*" + tag + "[?]*>"); | 
 |  | 
 |     if (!startmMatch.Success) //Not found | 
 |     { | 
 |       start = -1; | 
 |       end = -1; | 
 |       return; | 
 |     } | 
 |     var startPos = startmMatch.Index + start; | 
 |     if (startmMatch.Value.Substring(startmMatch.Value.Length - 2, 1) == "/") { | 
 |       end = startPos + startmMatch.Length; | 
 |     } else { | 
 |       endMatch = Regex.Match(xml.Substring(start), string.Format("(</[^>]*{0}[^>]*>)", tag)); | 
 |       if (endMatch.Success) { | 
 |         end = endMatch.Index + endMatch.Length + start; | 
 |       } | 
 |     } | 
 |     start = startPos; | 
 |   } | 
 |  | 
 |   private bool ReadUntil(XmlTextReader xr, params string[] tagName) { | 
 |     if (xr.EOF) { | 
 |       return false; | 
 |     } | 
 |     while (!Array.Exists(tagName, tag => xr.LocalName.EndsWith(tag))) { | 
 |       xr.Read(); | 
 |       if (xr.EOF) { | 
 |         return false; | 
 |       } | 
 |     } | 
 |     return (xr.LocalName.EndsWith(tagName[0])); | 
 |   } | 
 |  | 
 |   private void LoadColumns( | 
 |       XmlTextReader xr) //(string xml) | 
 |   { | 
 |     var colList = new List<IRangeId>(); | 
 |     if (ReadUntil(xr, "cols", "sheetData")) { | 
 |       //if (xml != "") | 
 |       //{ | 
 |       //var xr=new XmlTextReader(new StringReader(xml)); | 
 |       while (xr.Read()) { | 
 |         if (xr.NodeType == XmlNodeType.Whitespace) { | 
 |           continue; | 
 |         } | 
 |         if (xr.LocalName != "col") { | 
 |           break; | 
 |         } | 
 |         if (xr.NodeType == XmlNodeType.Element) { | 
 |           int min = int.Parse(xr.GetAttribute("min")); | 
 |  | 
 |           ExcelColumn col = new ExcelColumn(this, min); | 
 |  | 
 |           col.ColumnMax = int.Parse(xr.GetAttribute("max")); | 
 |           col.Width = | 
 |               xr.GetAttribute("width") == null | 
 |                   ? 0 | 
 |                   : double.Parse(xr.GetAttribute("width"), CultureInfo.InvariantCulture); | 
 |           col.BestFit = | 
 |               xr.GetAttribute("bestFit") != null && xr.GetAttribute("bestFit") == "1" | 
 |                   ? true | 
 |                   : false; | 
 |           col.Collapsed = | 
 |               xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed") == "1" | 
 |                   ? true | 
 |                   : false; | 
 |           col.Phonetic = | 
 |               xr.GetAttribute("phonetic") != null && xr.GetAttribute("phonetic") == "1" | 
 |                   ? true | 
 |                   : false; | 
 |           col.OutlineLevel = (short)(xr.GetAttribute("outlineLevel") == null | 
 |               ? 0 | 
 |               : int.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture)); | 
 |           col.Hidden = | 
 |               xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false; | 
 |           _values.SetValue(0, min, col); | 
 |  | 
 |           if (!(xr.GetAttribute("style") == null | 
 |                       || !int.TryParse(xr.GetAttribute("style"), out var style))) { | 
 |             _styles.SetValue(0, min, style); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Load Hyperlinks | 
 |   /// </summary> | 
 |   /// <param name="xr">The reader</param> | 
 |   private void LoadHyperLinks(XmlTextReader xr) { | 
 |     if (!ReadUntil(xr, "hyperlinks", "rowBreaks", "colBreaks")) { | 
 |       return; | 
 |     } | 
 |     while (xr.Read()) { | 
 |       if (xr.LocalName == "hyperlink") { | 
 |         ExcelCellBase.GetRowColFromAddress( | 
 |             xr.GetAttribute("ref"), | 
 |             out var fromRow, | 
 |             out var fromCol, | 
 |             out int toRow, | 
 |             out var toCol); | 
 |         ExcelHyperLink hl = null; | 
 |         if (xr.GetAttribute("id", ExcelPackage._schemaRelationships) != null) { | 
 |           var rId = xr.GetAttribute("id", ExcelPackage._schemaRelationships); | 
 |           var uri = Part.GetRelationship(rId).TargetUri; | 
 |  | 
 |           // Get Location, if any.  EPPlus Bug 15517 | 
 |           var location = xr.GetAttribute("location"); | 
 |           location = (string.IsNullOrEmpty(location)) ? "" : "#" + location; | 
 |  | 
 |           if (uri.IsAbsoluteUri) { | 
 |             try { | 
 |               hl = new(uri.AbsoluteUri + location); | 
 |             } catch { | 
 |               hl = new(uri.OriginalString + location, UriKind.Absolute); | 
 |             } | 
 |           } else { | 
 |             hl = new(uri.OriginalString + location, UriKind.Relative); | 
 |           } | 
 |  | 
 |           hl.RId = rId; | 
 |           Part.DeleteRelationship(rId); //Delete the relationship, it is recreated when we save the package. | 
 |         } else if (xr.GetAttribute("location") != null) { | 
 |           hl = new(xr.GetAttribute("location"), xr.GetAttribute("display")); | 
 |           hl.RowSpann = toRow - fromRow; | 
 |           hl.ColSpann = toCol - fromCol; | 
 |         } | 
 |  | 
 |         string tt = xr.GetAttribute("tooltip"); | 
 |         if (!string.IsNullOrEmpty(tt)) { | 
 |           hl.ToolTip = tt; | 
 |         } | 
 |         _hyperLinks.SetValue(fromRow, fromCol, hl); | 
 |       } else { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Load cells | 
 |   /// </summary> | 
 |   /// <param name="xr">The reader</param> | 
 |   private void LoadCells(XmlTextReader xr) { | 
 |     //var cellList=new List<IRangeID>(); | 
 |     //var rowList = new List<IRangeID>(); | 
 |     //var formulaList = new List<IRangeID>(); | 
 |     ReadUntil(xr, "sheetData", "mergeCells", "hyperlinks", "rowBreaks", "colBreaks"); | 
 |     ExcelAddressBase address = null; | 
 |     string type = ""; | 
 |     int style = 0; | 
 |     int row = 0; | 
 |     int col = 0; | 
 |     xr.Read(); | 
 |  | 
 |     while (!xr.EOF) { | 
 |       while (xr.NodeType == XmlNodeType.EndElement) { | 
 |         xr.Read(); | 
 |       } | 
 |       if (xr.LocalName == "row") { | 
 |         var r = xr.GetAttribute("r"); | 
 |         if (r == null) { | 
 |           row++; | 
 |         } else { | 
 |           row = Convert.ToInt32(r); | 
 |         } | 
 |  | 
 |         if (DoAddRow(xr)) { | 
 |           _values.SetValue(row, 0, AddRow(xr, row)); | 
 |           if (xr.GetAttribute("s") != null) { | 
 |             _styles.SetValue(row, 0, int.Parse(xr.GetAttribute("s"), CultureInfo.InvariantCulture)); | 
 |           } | 
 |         } | 
 |         xr.Read(); | 
 |       } else if (xr.LocalName == "c") { | 
 |         //if (cell != null) cellList.Add(cell); | 
 |         //cell = new ExcelCell(this, xr.GetAttribute("r")); | 
 |         var r = xr.GetAttribute("r"); | 
 |         if (r == null) { | 
 |           //Handle cells with no reference | 
 |           col++; | 
 |           address = new(row, col, row, col); | 
 |         } else { | 
 |           address = new(r); | 
 |           col = address._fromCol; | 
 |         } | 
 |  | 
 |         //Datetype | 
 |         if (xr.GetAttribute("t") != null) { | 
 |           type = xr.GetAttribute("t"); | 
 |           _types.SetValue(address._fromRow, address._fromCol, type); | 
 |         } else { | 
 |           type = ""; | 
 |         } | 
 |         //Style | 
 |         if (xr.GetAttribute("s") != null) { | 
 |           style = int.Parse(xr.GetAttribute("s")); | 
 |           _styles.SetValue(address._fromRow, address._fromCol, style); | 
 |           _values.SetValue(address._fromRow, address._fromCol, null); //TODO:Better Performance ?? | 
 |         } else { | 
 |           style = 0; | 
 |         } | 
 |         xr.Read(); | 
 |       } else if (xr.LocalName == "v") { | 
 |         SetValueFromXml(xr, type, style, address._fromRow, address._fromCol); | 
 |  | 
 |         xr.Read(); | 
 |       } else if (xr.LocalName == "f") { | 
 |         string t = xr.GetAttribute("t"); | 
 |         if (t == null) { | 
 |           _formulas.SetValue(address._fromRow, address._fromCol, xr.ReadElementContentAsString()); | 
 |           _values.SetValue(address._fromRow, address._fromCol, null); | 
 |           //formulaList.Add(cell); | 
 |         } else if (t == "shared") { | 
 |           string si = xr.GetAttribute("si"); | 
 |           if (si != null) { | 
 |             var sfIndex = int.Parse(si); | 
 |             _formulas.SetValue(address._fromRow, address._fromCol, sfIndex); | 
 |             _values.SetValue(address._fromRow, address._fromCol, null); | 
 |             string fAddress = xr.GetAttribute("ref"); | 
 |             string formula = ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString()); | 
 |             if (formula != "") { | 
 |               _sharedFormulas.Add( | 
 |                   sfIndex, | 
 |                   new(SourceCodeTokenizer.Default) { | 
 |                     Index = sfIndex, | 
 |                     Formula = formula, | 
 |                     Address = fAddress, | 
 |                     StartRow = address._fromRow, | 
 |                     StartCol = address._fromCol, | 
 |                   }); | 
 |             } | 
 |           } else { | 
 |             xr.Read(); //Something is wrong in the sheet, read next | 
 |           } | 
 |         } else if (t | 
 |             == "array") //TODO: Array functions are not support yet. Read the formula for the start cell only. | 
 |         { | 
 |           string aAddress = xr.GetAttribute("ref"); | 
 |           ExcelRange addressRange = new ExcelRange(this, aAddress); | 
 |           string formula = xr.ReadElementContentAsString(); | 
 |           bool isIndexMatchFormula = | 
 |               Regex.IsMatch(formula, @"INDEX\(", RegexOptions.IgnoreCase) | 
 |                   && Regex.IsMatch(formula, @"MATCH\(", RegexOptions.IgnoreCase) | 
 |                   && !aAddress.Contains(":"); | 
 |           if (isIndexMatchFormula) { | 
 |             addressRange.IsArrayFormula = false; | 
 |             for (int colIndex = addressRange.Start.Column; | 
 |                 colIndex <= addressRange.End.Column; | 
 |                 colIndex++) { | 
 |               for (int rowIndex = addressRange.Start.Row; | 
 |                   rowIndex <= addressRange.End.Row; | 
 |                   rowIndex++) { | 
 |                 var afIndex = GetMaxShareFunctionIndex(true); | 
 |                 _formulas.SetValue(rowIndex, colIndex, afIndex); | 
 |                 _values.SetValue(rowIndex, colIndex, null); | 
 |                 _sharedFormulas.Add( | 
 |                     afIndex, | 
 |                     new(SourceCodeTokenizer.Default) { | 
 |                       Index = afIndex, | 
 |                       Formula = formula, | 
 |                       Address = aAddress, | 
 |                       StartRow = address._fromRow, | 
 |                       StartCol = address._fromCol, | 
 |                       IsArray = false, | 
 |                     }); | 
 |               } | 
 |             } | 
 |           } else { | 
 |             addressRange.IsArrayFormula = true; | 
 |             var afIndex = GetMaxShareFunctionIndex(true); | 
 |             for (int colIndex = addressRange.Start.Column; | 
 |                 colIndex <= addressRange.End.Column; | 
 |                 colIndex++) { | 
 |               for (int rowIndex = addressRange.Start.Row; | 
 |                   rowIndex <= addressRange.End.Row; | 
 |                   rowIndex++) { | 
 |                 _formulas.SetValue(rowIndex, colIndex, afIndex); | 
 |                 _values.SetValue(rowIndex, colIndex, null); | 
 |               } | 
 |             } | 
 |             _sharedFormulas.Add( | 
 |                 afIndex, | 
 |                 new(SourceCodeTokenizer.Default) { | 
 |                   Index = afIndex, | 
 |                   Formula = formula, | 
 |                   Address = aAddress, | 
 |                   StartRow = address._fromRow, | 
 |                   StartCol = address._fromCol, | 
 |                   IsArray = true, | 
 |                 }); | 
 |           } | 
 |         } else // ??? some other type | 
 |         { | 
 |           xr.Read(); //Something is wrong in the sheet, read next | 
 |         } | 
 |       } else if (xr.LocalName | 
 |           == "is") //Inline string | 
 |       { | 
 |         xr.Read(); | 
 |         if (xr.LocalName == "t") { | 
 |           _values.SetValue( | 
 |               address._fromRow, | 
 |               address._fromCol, | 
 |               ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString())); | 
 |           //cell._value = xr.ReadInnerXml(); | 
 |         } else { | 
 |           _values.SetValue(address._fromRow, address._fromCol, xr.ReadOuterXml()); | 
 |           _types.SetValue(address._fromRow, address._fromCol, "rt"); | 
 |           _flags.SetFlagValue(address._fromRow, address._fromCol, true, CellFlags.RichText); | 
 |           //cell.IsRichText = true; | 
 |         } | 
 |       } else { | 
 |         break; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private bool DoAddRow(XmlTextReader xr) { | 
 |     var c = xr.GetAttribute("r") == null ? 0 : 1; | 
 |     if (xr.GetAttribute("spans") != null) { | 
 |       c++; | 
 |     } | 
 |     return xr.AttributeCount > c; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Load merged cells | 
 |   /// </summary> | 
 |   /// <param name="xr"></param> | 
 |   private void LoadMergeCells(XmlTextReader xr) { | 
 |     if (ReadUntil(xr, "mergeCells", "hyperlinks", "rowBreaks", "colBreaks") && !xr.EOF) { | 
 |       while (xr.Read()) { | 
 |         if (xr.LocalName != "mergeCell") { | 
 |           break; | 
 |         } | 
 |         if (xr.NodeType == XmlNodeType.Element) { | 
 |           string address = xr.GetAttribute("ref"); | 
 |           _mergedCells.Add(new ExcelAddress(address), false); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Update merged cells | 
 |   /// </summary> | 
 |   /// <param name="sw">The writer</param> | 
 |   private void UpdateMergedCells(StreamWriter sw) { | 
 |     sw.Write("<mergeCells>"); | 
 |     foreach (string address in _mergedCells) { | 
 |       sw.Write("<mergeCell ref=\"{0}\" />", address); | 
 |     } | 
 |     sw.Write("</mergeCells>"); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Reads a row from the XML reader | 
 |   /// </summary> | 
 |   /// <param name="xr">The reader</param> | 
 |   /// <param name="row">The row number</param> | 
 |   /// <returns></returns> | 
 |   private RowInternal AddRow(XmlTextReader xr, int row) { | 
 |     return new() { | 
 |       Collapsed = | 
 |           (xr.GetAttribute("collapsed") != null && xr.GetAttribute("collapsed") == "1" | 
 |               ? true | 
 |               : false), | 
 |       OutlineLevel = | 
 |           (xr.GetAttribute("outlineLevel") == null | 
 |               ? (short)0 | 
 |               : short.Parse(xr.GetAttribute("outlineLevel"), CultureInfo.InvariantCulture)), | 
 |       Height = | 
 |           (xr.GetAttribute("ht") == null | 
 |               ? -1 | 
 |               : double.Parse(xr.GetAttribute("ht"), CultureInfo.InvariantCulture)), | 
 |       Hidden = | 
 |           (xr.GetAttribute("hidden") != null && xr.GetAttribute("hidden") == "1" ? true : false), | 
 |       Phonetic = xr.GetAttribute("ph") != null && xr.GetAttribute("ph") == "1" ? true : false, | 
 |       CustomHeight = | 
 |           xr.GetAttribute("customHeight") == null ? false : xr.GetAttribute("customHeight") == "1", | 
 |     }; | 
 |   } | 
 |  | 
 |   private static readonly DateTime _excelEpoch = new(1899, 12, 30); | 
 |  | 
 |   public static DateTime IncorrectDurationFromOaDate(double value) { | 
 |     // This behavior is wrong. Real OADate values have a discontinuity on 30 December 1899. | 
 |     // For real OADate values, the negative sign applies only to the integer portion of | 
 |     // the float, *not* to the decimal portion. For example, -0.5 and 0.5 both refer to the | 
 |     // same date, and -1.5 is actually 1899-12-29 12:00 (1 day before 1899-12-30 00:00 | 
 |     // plus 0.5 days), *not* 1899-12-28 12:00 (1.5 days before 1899-12-30 00:00). | 
 |     // | 
 |     // Unfortunately, AppSheet's duration-handling code gets this very wrong, and treats the | 
 |     // duration as the offset from 1899-12-30 00:00. This is correct for positive durations, | 
 |     // but it's wrong for negative durations. This code tries to fix the bug that exists in | 
 |     // AppSheet's duration-handling code here, and it succeeds in some cases and fails in | 
 |     // others. | 
 |     // | 
 |     // This code also breaks date/time handling for dates before 1899-12-30 00:00 in some | 
 |     // cases. Specifically, dates end up being offset by one day. | 
 |     // | 
 |     // Regardless, changing this behavior is risky, so this code simply replicates the | 
 |     // existing behavior for | 
 |     if (value >= 0) { | 
 |       return DateTime.FromOADate(value); | 
 |     } | 
 |     // This looks like a very complicated way to call TimeSpan.FromDays(value), but | 
 |     // TimeSpan.FromDays actually only guarantees millisecond precision, and critically | 
 |     // rounding is different on .NET Core, resulting in values like (e.g.) 3:15:00 being | 
 |     // incorrectly rounded. | 
 |     var offset = DateTime.FromOADate(-value) - _excelEpoch; | 
 |     return _excelEpoch - offset; | 
 |   } | 
 |  | 
 |   private void SetValueFromXml(XmlTextReader xr, string type, int styleId, int row, int col) { | 
 |     //XmlNode vnode = colNode.SelectSingleNode("d:v", NameSpaceManager); | 
 |     //if (vnode == null) return null; | 
 |     if (type == "s") { | 
 |       int ix = xr.ReadElementContentAsInt(); | 
 |  | 
 |       // Temporary debugging code to locate intermittent 'Index was out of range' exception. | 
 |       if (ix < 0) { | 
 |         throw new( | 
 |             string.Format( | 
 |                 "ReadElementContentAsInt returned value '{0}' which is less than zero.", | 
 |                 ix)); | 
 |       } | 
 |       if (ix >= _workbook._sharedStringsList.Count) { | 
 |         throw new( | 
 |             string.Format( | 
 |                 "ReadElementContentAsInt returned index value '{0}' which is greater than _sharedStringsList count of {1}.", | 
 |                 ix, | 
 |                 _workbook._sharedStringsList.Count)); | 
 |       } | 
 |  | 
 |       _values.SetValue(row, col, _workbook._sharedStringsList[ix].Text); | 
 |       if (_workbook._sharedStringsList[ix].isRichText) { | 
 |         _flags.SetFlagValue(row, col, true, CellFlags.RichText); | 
 |       } | 
 |     } else if (type == "str") { | 
 |       _values.SetValue(row, col, ConvertUtil.ExcelDecodeString(xr.ReadElementContentAsString())); | 
 |     } else if (type == "b") { | 
 |       _values.SetValue(row, col, (xr.ReadElementContentAsString() != "0")); | 
 |     } else if (type == "e") { | 
 |       _values.SetValue(row, col, GetErrorType(xr.ReadElementContentAsString())); | 
 |     } else { | 
 |       string v = xr.ReadElementContentAsString(); | 
 |       var nf = Workbook.Styles.CellXfs[styleId].NumberFormatId; | 
 |       if ((nf >= 20 && nf <= 21) | 
 |           || (nf >= 45 | 
 |                   && nf | 
 |                       <= 47)) // Duration | 
 |       { | 
 |         if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var res)) { | 
 |           if (Workbook.Date1904) { | 
 |             res += ExcelWorkbook._date1904Offset; | 
 |           } | 
 |           if (res >= -657435.0 && res < 2958465.9999999) { | 
 |             // Get the Duration value expressed as a DateTime. | 
 |             _values.SetValue(row, col, IncorrectDurationFromOaDate(res)); | 
 |           } else { | 
 |             // Cope with Google Sheets export of cells having a formula. | 
 |             // Rather than exporting the native value, they export the formatted value. | 
 |             _values.SetValue(row, col, v); | 
 |           } | 
 |         } else { | 
 |           // Cope with Google Sheets export of cells having a formula. | 
 |           // Rather than exporting the native value, they export the formatted value. | 
 |           _values.SetValue(row, col, v); | 
 |         } | 
 |       } else if ((nf >= 14 && nf <= 19) | 
 |           || (nf | 
 |                   == 22)) // DateTime | 
 |       { | 
 |         if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var res)) { | 
 |           if (Workbook.Date1904) { | 
 |             res += ExcelWorkbook._date1904Offset; | 
 |           } | 
 |           if (res >= -657435.0 && res < 2958465.9999999) { | 
 |             _values.SetValue(row, col, DateTime.FromOADate(res)); | 
 |           } else { | 
 |             // Cope with Google Sheets export of cells having a formula. | 
 |             // Rather than exporting the native value, they export the formatted value. | 
 |             _values.SetValue(row, col, v); | 
 |           } | 
 |         } else { | 
 |           // Cope with Google Sheets export of cells having a formula. | 
 |           // Rather than exporting the native value, they export the formatted value. | 
 |           _values.SetValue(row, col, v); | 
 |         } | 
 |       } else { | 
 |         if (double.TryParse(v, NumberStyles.Any, CultureInfo.InvariantCulture, out var d)) { | 
 |           _values.SetValue(row, col, d); | 
 |         } else { | 
 |           // Cope with Google Sheets export of cells having a formula. | 
 |           // Rather than exporting the native value, they export the formatted value. | 
 |           _values.SetValue(row, col, v); | 
 |  | 
 |           //_values.SetValue(row, col, double.NaN); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private object GetErrorType(string v) { | 
 |     return ExcelErrorValue.Parse(v.ToUpper(CultureInfo.InvariantCulture)); | 
 |     //switch(v.ToUpper()) | 
 |     //{ | 
 |     //    case "#DIV/0!": | 
 |     //        return new ExcelErrorValue.cre(eErrorType.Div0); | 
 |     //    case "#REF!": | 
 |     //        return new ExcelErrorValue(eErrorType.Ref); | 
 |     //    case "#N/A": | 
 |     //        return new ExcelErrorValue(eErrorType.NA); | 
 |     //    case "#NAME?": | 
 |     //        return new ExcelErrorValue(eErrorType.Name); | 
 |     //    case "#NULL!": | 
 |     //        return new ExcelErrorValue(eErrorType.Null); | 
 |     //    case "#NUM!": | 
 |     //        return new ExcelErrorValue(eErrorType.Num); | 
 |     //    default: | 
 |     //        return new ExcelErrorValue(eErrorType.Value); | 
 |     //} | 
 |   } | 
 |  | 
 |   ///// <summary> | 
 |   ///// Provides access to an individual cell within the worksheet. | 
 |   ///// </summary> | 
 |   ///// <param name="row">The row number in the worksheet</param> | 
 |   ///// <param name="col">The column number in the worksheet</param> | 
 |   ///// <returns></returns> | 
 |   //internal ExcelCell Cell(int row, int col) | 
 |   //{ | 
 |   //    return new ExcelCell(_values, row, col); | 
 |   //} | 
 |   /// <summary> | 
 |   /// Provides access to a range of cells | 
 |   /// </summary> | 
 |   public ExcelRange Cells { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return new(this, 1, 1, ExcelPackage.MaxRows, ExcelPackage.MaxColumns); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Provides access to the selected range of cells | 
 |   /// </summary> | 
 |   public ExcelRange SelectedRange { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return new(this, View.SelectedRange); | 
 |     } | 
 |   } | 
 |  | 
 |   private readonly MergeCellsCollection _mergedCells = new(); | 
 |  | 
 |   /// <summary> | 
 |   /// Addresses to merged ranges | 
 |   /// </summary> | 
 |   public MergeCellsCollection MergedCells { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       return _mergedCells; | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Provides access to an individual row within the worksheet so you can set its properties. | 
 |   /// </summary> | 
 |   /// <param name="row">The row number in the worksheet</param> | 
 |   /// <returns></returns> | 
 |   public ExcelRow Row(int row) { | 
 |     //ExcelRow r; | 
 |     //ulong id = ExcelRow.GetRowID(_sheetID, row); | 
 |     //TODO: Fixa. | 
 |     //var v = _values.GetValue(row, 0); | 
 |     //if (v!=null) | 
 |     //{ | 
 |     //    var ri=(RowInternal)v; | 
 |     //    r = new ExcelRow(this, row) | 
 |     //} | 
 |     //else | 
 |     //{ | 
 |     //r = new ExcelRow(this, row); | 
 |     //_values.SetValue(row, 0, r); | 
 |     //_rows.Add(r); | 
 |     //} | 
 |     CheckSheetType(); | 
 |     if (row < 1 || row > ExcelPackage.MaxRows) { | 
 |       throw (new ArgumentException("Row number out of bounds")); | 
 |     } | 
 |     return new(this, row); | 
 |     //return r; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Provides access to an individual column within the worksheet so you can set its properties. | 
 |   /// </summary> | 
 |   /// <param name="col">The column number in the worksheet</param> | 
 |   /// <returns></returns> | 
 |   public ExcelColumn Column(int col) { | 
 |     CheckSheetType(); | 
 |     if (col < 1 || col > ExcelPackage.MaxColumns) { | 
 |       throw (new ArgumentException("Column number out of bounds")); | 
 |     } | 
 |     var column = _values.GetValue(0, col) as ExcelColumn; | 
 |     if (column != null) { | 
 |       if (column.ColumnMin != column.ColumnMax) { | 
 |         int maxCol = column.ColumnMax; | 
 |         column.ColumnMax = col; | 
 |         ExcelColumn copy = CopyColumn(column, col + 1, maxCol); | 
 |       } | 
 |     } else { | 
 |       int r = 0, | 
 |           c = col; | 
 |       if (_values.PrevCell(ref r, ref c)) { | 
 |         column = _values.GetValue(0, c) as ExcelColumn; | 
 |         int maxCol = column.ColumnMax; | 
 |         if (maxCol >= col) { | 
 |           column.ColumnMax = col - 1; | 
 |           if (maxCol > col) { | 
 |             ExcelColumn newC = CopyColumn(column, col + 1, maxCol); | 
 |           } | 
 |           return CopyColumn(column, col, col); | 
 |         } | 
 |       } | 
 |       //foreach (ExcelColumn checkColumn in _columns) | 
 |       //{ | 
 |       //    if (col > checkColumn.ColumnMin && col <= checkColumn.ColumnMax) | 
 |       //    { | 
 |       //        int maxCol = checkColumn.ColumnMax; | 
 |       //        checkColumn.ColumnMax = col - 1; | 
 |       //        if (maxCol > col) | 
 |       //        { | 
 |       //            ExcelColumn newC = CopyColumn(checkColumn, col + 1, maxCol); | 
 |       //        } | 
 |       //        return CopyColumn(checkColumn, col,col); | 
 |       //    } | 
 |       //} | 
 |       column = new(this, col); | 
 |       _values.SetValue(0, col, column); | 
 |       //_columns.Add(column); | 
 |     } | 
 |     return column; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Returns the name of the worksheet | 
 |   /// </summary> | 
 |   /// <returns>The name of the worksheet</returns> | 
 |   public override string ToString() { | 
 |     return Name; | 
 |   } | 
 |  | 
 |   internal ExcelColumn CopyColumn(ExcelColumn c, int col, int maxCol) { | 
 |     ExcelColumn newC = new ExcelColumn(this, col); | 
 |     newC.ColumnMax = maxCol < ExcelPackage.MaxColumns ? maxCol : ExcelPackage.MaxColumns; | 
 |     if (c.StyleName != "") { | 
 |       newC.StyleName = c.StyleName; | 
 |     } else { | 
 |       newC.StyleID = c.StyleID; | 
 |     } | 
 |  | 
 |     newC.OutlineLevel = c.OutlineLevel; | 
 |     newC.Phonetic = c.Phonetic; | 
 |     newC.BestFit = c.BestFit; | 
 |     //_columns.Add(newC); | 
 |     _values.SetValue(0, col, newC); | 
 |     newC._width = c._width; | 
 |     newC._hidden = c._hidden; | 
 |     return newC; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Make the current worksheet active. | 
 |   /// </summary> | 
 |   public void Select() { | 
 |     View.TabSelected = true; | 
 |     //Select(Address, true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Selects a range in the worksheet. The active cell is the topmost cell. | 
 |   /// Make the current worksheet active. | 
 |   /// </summary> | 
 |   /// <param name="address">An address range</param> | 
 |   public void Select(string address) { | 
 |     Select(address, true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Selects a range in the worksheet. The actice cell is the topmost cell. | 
 |   /// </summary> | 
 |   /// <param name="address">A range of cells</param> | 
 |   /// <param name="selectSheet">Make the sheet active</param> | 
 |   public void Select(string address, bool selectSheet) { | 
 |     CheckSheetType(); | 
 |     int toCol, | 
 |         toRow; | 
 |     //Get rows and columns and validate as well | 
 |     ExcelCellBase.GetRowColFromAddress( | 
 |         address, | 
 |         out var fromRow, | 
 |         out var fromCol, | 
 |         out toRow, | 
 |         out toCol); | 
 |  | 
 |     if (selectSheet) { | 
 |       View.TabSelected = true; | 
 |     } | 
 |     View.SelectedRange = address; | 
 |     View.ActiveCell = ExcelCellBase.GetAddress(fromRow, fromCol); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Selects a range in the worksheet. The active cell is the topmost cell of the first address. | 
 |   /// Make the current worksheet active. | 
 |   /// </summary> | 
 |   /// <param name="address">An address range</param> | 
 |   public void Select(ExcelAddress address) { | 
 |     CheckSheetType(); | 
 |     Select(address, true); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Selects a range in the worksheet. The active cell is the topmost cell of the first address. | 
 |   /// </summary> | 
 |   /// <param name="address">A range of cells</param> | 
 |   /// <param name="selectSheet">Make the sheet active</param> | 
 |   public void Select(ExcelAddress address, bool selectSheet) { | 
 |     CheckSheetType(); | 
 |     if (selectSheet) { | 
 |       View.TabSelected = true; | 
 |     } | 
 |     string selAddress = | 
 |         ExcelCellBase.GetAddress(address.Start.Row, address.Start.Column) | 
 |             + ":" | 
 |             + ExcelCellBase.GetAddress(address.End.Row, address.End.Column); | 
 |     if (address.Addresses != null) { | 
 |       foreach (var a in address.Addresses) { | 
 |         selAddress += | 
 |             " " | 
 |                 + ExcelCellBase.GetAddress(a.Start.Row, a.Start.Column) | 
 |                 + ":" | 
 |                 + ExcelCellBase.GetAddress(a.End.Row, a.End.Column); | 
 |       } | 
 |     } | 
 |     View.SelectedRange = selAddress; | 
 |     View.ActiveCell = ExcelCellBase.GetAddress(address.Start.Row, address.Start.Column); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Inserts a new row into the spreadsheet.  Existing rows below the position are | 
 |   /// shifted down.  All formula are updated to take account of the new row. | 
 |   /// </summary> | 
 |   /// <param name="rowFrom">The position of the new row</param> | 
 |   /// <param name="rows">Number of rows to insert</param> | 
 |   public void InsertRow(int rowFrom, int rows) { | 
 |     InsertRow(rowFrom, rows, 0); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Inserts a new row into the spreadsheet.  Existing rows below the position are | 
 |   /// shifted down.  All formula are updated to take account of the new row. | 
 |   /// </summary> | 
 |   /// <param name="rowFrom">The position of the new row</param> | 
 |   /// <param name="rows">Number of rows to insert.</param> | 
 |   /// <param name="copyStylesFromRow">Copy Styles from this row. Applied to all inserted rows</param> | 
 |   public void InsertRow(int rowFrom, int rows, int copyStylesFromRow) { | 
 |     CheckSheetType(); | 
 |     var d = Dimension; | 
 |  | 
 |     if (rowFrom < 1) { | 
 |       throw (new ArgumentOutOfRangeException("rowFrom can't be lesser that 1")); | 
 |     } | 
 |  | 
 |     //Check that cells aren't shifted outside the boundries | 
 |     if (d != null && d.End.Row > rowFrom && d.End.Row + rows > ExcelPackage.MaxRows) { | 
 |       throw (new ArgumentOutOfRangeException( | 
 |               "Can't insert. Rows will be shifted outside the boundries of the worksheet.")); | 
 |     } | 
 |  | 
 |     _values.Insert(rowFrom, 0, rows, 0); | 
 |     _formulas.Insert(rowFrom, 0, rows, 0); | 
 |     _styles.Insert(rowFrom, 0, rows, 0); | 
 |     _types.Insert(rowFrom, 0, rows, 0); | 
 |     _commentsStore.Insert(rowFrom, 0, rows, 0); | 
 |     _hyperLinks.Insert(rowFrom, 0, rows, 0); | 
 |     _flags.Insert(rowFrom, 0, rows, 0); | 
 |  | 
 |     foreach (var f in _sharedFormulas.Values) { | 
 |       if (f.StartRow >= rowFrom) { | 
 |         f.StartRow += rows; | 
 |       } | 
 |       var a = new ExcelAddressBase(f.Address); | 
 |       if (a._fromRow >= rowFrom) { | 
 |         a._fromRow += rows; | 
 |         a._toRow += rows; | 
 |       } else if (a._toRow >= rowFrom) { | 
 |         a._toRow += rows; | 
 |       } | 
 |       f.Address = ExcelCellBase.GetAddress(a._fromRow, a._fromCol, a._toRow, a._toCol); | 
 |       f.Formula = ExcelCellBase.UpdateFormulaReferences(f.Formula, rows, 0, rowFrom, 0); | 
 |     } | 
 |     var cse = new CellsStoreEnumerator<object>(_formulas); | 
 |     while (cse.Next()) { | 
 |       if (cse.Value is string) { | 
 |         cse.Value = ExcelCellBase.UpdateFormulaReferences( | 
 |             cse.Value.ToString(), | 
 |             rows, | 
 |             0, | 
 |             rowFrom, | 
 |             0); | 
 |       } | 
 |     } | 
 |  | 
 |     FixMergedCellsRow(rowFrom, rows, false); | 
 |     if (copyStylesFromRow > 0) { | 
 |       var cseS = new CellsStoreEnumerator<int>( | 
 |           _styles, | 
 |           copyStylesFromRow, | 
 |           0, | 
 |           copyStylesFromRow, | 
 |           ExcelPackage.MaxColumns); //Fixes issue 15068 , 15090 | 
 |       while (cseS.Next()) { | 
 |         for (var r = 0; r < rows; r++) { | 
 |           _styles.SetValue(rowFrom + r, cseS.Column, cseS.Value); | 
 |         } | 
 |       } | 
 |     } | 
 |     foreach (var tbl in Tables) { | 
 |       tbl.Address = tbl.Address.AddRow(rowFrom, rows); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Inserts a new column into the spreadsheet.  Existing columns below the position are | 
 |   /// shifted down.  All formula are updated to take account of the new column. | 
 |   /// </summary> | 
 |   /// <param name="columnFrom">The position of the new column</param> | 
 |   /// <param name="columns">Number of columns to insert</param> | 
 |   public void InsertColumn(int columnFrom, int columns) { | 
 |     InsertColumn(columnFrom, columns, 0); | 
 |   } | 
 |  | 
 |   ///<summary> | 
 |   /// Inserts a new column into the spreadsheet.  Existing column to the left are | 
 |   /// shifted.  All formula are updated to take account of the new column. | 
 |   /// </summary> | 
 |   /// <param name="columnFrom">The position of the new column</param> | 
 |   /// <param name="columns">Number of columns to insert.</param> | 
 |   /// <param name="copyStylesFromColumn">Copy Styles from this column. Applied to all inserted columns</param> | 
 |   public void InsertColumn(int columnFrom, int columns, int copyStylesFromColumn) { | 
 |     CheckSheetType(); | 
 |     var d = Dimension; | 
 |  | 
 |     if (columnFrom < 1) { | 
 |       throw (new ArgumentOutOfRangeException("columnFrom can't be lesser that 1")); | 
 |     } | 
 |     //Check that cells aren't shifted outside the boundries | 
 |     if (d != null | 
 |         && d.End.Column > columnFrom | 
 |         && d.End.Column + columns > ExcelPackage.MaxColumns) { | 
 |       throw (new ArgumentOutOfRangeException( | 
 |               "Can't insert. Columns will be shifted outside the boundries of the worksheet.")); | 
 |     } | 
 |  | 
 |     _values.Insert(0, columnFrom, 0, columns); | 
 |     _formulas.Insert(0, columnFrom, 0, columns); | 
 |     _styles.Insert(0, columnFrom, 0, columns); | 
 |     _types.Insert(0, columnFrom, 0, columns); | 
 |     _commentsStore.Insert(0, columnFrom, 0, columns); | 
 |     _hyperLinks.Insert(0, columnFrom, 0, columns); | 
 |     _flags.Insert(0, columnFrom, 0, columns); | 
 |  | 
 |     foreach (var f in _sharedFormulas.Values) { | 
 |       if (f.StartCol >= columnFrom) { | 
 |         f.StartCol += columns; | 
 |       } | 
 |       var a = new ExcelAddressBase(f.Address); | 
 |       if (a._fromCol >= columnFrom) { | 
 |         a._fromCol += columns; | 
 |         a._toCol += columns; | 
 |       } else if (a._toCol >= columnFrom) { | 
 |         a._toCol += columns; | 
 |       } | 
 |       f.Address = ExcelCellBase.GetAddress(a._fromRow, a._fromCol, a._toRow, a._toCol); | 
 |       f.Formula = ExcelCellBase.UpdateFormulaReferences(f.Formula, 0, columns, 0, columnFrom); | 
 |     } | 
 |  | 
 |     var cse = new CellsStoreEnumerator<object>(_formulas); | 
 |     while (cse.Next()) { | 
 |       if (cse.Value is string) { | 
 |         cse.Value = ExcelCellBase.UpdateFormulaReferences( | 
 |             cse.Value.ToString(), | 
 |             0, | 
 |             columns, | 
 |             0, | 
 |             columnFrom); | 
 |       } | 
 |     } | 
 |  | 
 |     FixMergedCellsColumn(columnFrom, columns, false); | 
 |  | 
 |     var csec = new CellsStoreEnumerator<object>(_values, 0, 1, 0, ExcelPackage.MaxColumns); | 
 |     var lst = new List<ExcelColumn>(); | 
 |     foreach (var col in csec) { | 
 |       if (col is ExcelColumn column) { | 
 |         lst.Add(column); | 
 |       } | 
 |     } | 
 |  | 
 |     for (int i = lst.Count - 1; i >= 0; i--) { | 
 |       var c = lst[i]; | 
 |       if (c._columnMin >= columnFrom) { | 
 |         if (c._columnMin + columns <= ExcelPackage.MaxColumns) { | 
 |           c._columnMin += columns; | 
 |         } else { | 
 |           c._columnMin = ExcelPackage.MaxColumns; | 
 |         } | 
 |  | 
 |         if (c._columnMax + columns <= ExcelPackage.MaxColumns) { | 
 |           c._columnMax += columns; | 
 |         } else { | 
 |           c._columnMax = ExcelPackage.MaxColumns; | 
 |         } | 
 |       } else if (c._columnMax >= columnFrom) { | 
 |         var cc = c._columnMax - columnFrom; | 
 |         c._columnMax = columnFrom - 1; | 
 |         CopyColumn(c, columnFrom + columns, columnFrom + columns + cc); | 
 |       } | 
 |     } | 
 |  | 
 |     if (copyStylesFromColumn > 0) { | 
 |       for (var c = 0; c < columns; c++) { | 
 |         var col = Column(columnFrom + c); | 
 |         col.StyleID = Column(copyStylesFromColumn).StyleID; | 
 |       } | 
 |     } | 
 |     //Adjust tables | 
 |     foreach (var tbl in Tables) { | 
 |       if (columnFrom > tbl.Address.Start.Column && columnFrom <= tbl.Address.End.Column) { | 
 |         InsertTableColumns(columnFrom, columns, tbl); | 
 |       } | 
 |  | 
 |       tbl.Address = tbl.Address.AddColumn(columnFrom, columns); | 
 |     } | 
 |   } | 
 |  | 
 |   private static void InsertTableColumns(int columnFrom, int columns, ExcelTable tbl) { | 
 |     var node = tbl.Columns[0].TopNode.ParentNode; | 
 |     var ix = columnFrom - tbl.Address.Start.Column - 1; | 
 |     var insPos = node.ChildNodes[ix]; | 
 |     ix += 2; | 
 |     for (int i = 0; i < columns; i++) { | 
 |       var name = tbl.Columns.GetUniqueName( | 
 |           string.Format("Column{0}", (ix++).ToString(CultureInfo.InvariantCulture))); | 
 |       XmlElement tableColumn = (XmlElement) | 
 |         tbl.TableXml.CreateNode(XmlNodeType.Element, "tableColumn", ExcelPackage._schemaMain); | 
 |       tableColumn.SetAttribute( | 
 |           "id", | 
 |           (tbl.Columns.Count + i + 1).ToString(CultureInfo.InvariantCulture)); | 
 |       tableColumn.SetAttribute("name", name); | 
 |       insPos = node.InsertAfter(tableColumn, insPos); | 
 |     } //Create tbl Column | 
 |     tbl._cols = new(tbl); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Adds a value to the row of merged cells to fix for inserts or deletes | 
 |   /// </summary> | 
 |   /// <param name="row"></param> | 
 |   /// <param name="rows"></param> | 
 |   /// <param name="delete"></param> | 
 |   private void FixMergedCellsRow(int row, int rows, bool delete) { | 
 |     if (delete) { | 
 |       _mergedCells._cells.Delete(row, 0, rows, 0); | 
 |     } else { | 
 |       _mergedCells._cells.Insert(row, 0, rows, 0); | 
 |     } | 
 |  | 
 |     List<int> removeIndex = []; | 
 |     for (int i = 0; i < _mergedCells.Count; i++) { | 
 |       if (!string.IsNullOrEmpty(_mergedCells[i])) { | 
 |         ExcelAddressBase addr = new(_mergedCells[i]), | 
 |             newAddr; | 
 |         if (delete) { | 
 |           newAddr = addr.DeleteRow(row, rows); | 
 |           if (newAddr == null) { | 
 |             removeIndex.Add(i); | 
 |             continue; | 
 |           } | 
 |         } else { | 
 |           newAddr = addr.AddRow(row, rows); | 
 |           if (newAddr.Address != addr.Address) { | 
 |             //    _mergedCells._cells.Insert(row, 0, rows, 0); | 
 |             _mergedCells.SetIndex(newAddr, i); | 
 |           } | 
 |         } | 
 |  | 
 |         if (newAddr.Address != addr.Address) { | 
 |           _mergedCells.List[i] = newAddr._address; | 
 |         } | 
 |       } | 
 |     } | 
 |     for (int i = removeIndex.Count - 1; i >= 0; i--) { | 
 |       _mergedCells.List.RemoveAt(removeIndex[i]); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Adds a value to the row of merged cells to fix for inserts or deletes | 
 |   /// </summary> | 
 |   /// <param name="column"></param> | 
 |   /// <param name="columns"></param> | 
 |   /// <param name="delete"></param> | 
 |   private void FixMergedCellsColumn(int column, int columns, bool delete) { | 
 |     if (delete) { | 
 |       _mergedCells._cells.Delete(0, column, 0, columns); | 
 |     } else { | 
 |       _mergedCells._cells.Insert(0, column, 0, columns); | 
 |     } | 
 |     List<int> removeIndex = []; | 
 |     for (int i = 0; i < _mergedCells.Count; i++) { | 
 |       if (!string.IsNullOrEmpty(_mergedCells[i])) { | 
 |         ExcelAddressBase addr = new(_mergedCells[i]), | 
 |             newAddr; | 
 |         if (delete) { | 
 |           newAddr = addr.DeleteColumn(column, columns); | 
 |           if (newAddr == null) { | 
 |             removeIndex.Add(i); | 
 |             continue; | 
 |           } | 
 |         } else { | 
 |           newAddr = addr.AddColumn(column, columns); | 
 |           if (newAddr.Address != addr.Address) { | 
 |             _mergedCells.SetIndex(newAddr, i); | 
 |           } | 
 |         } | 
 |  | 
 |         if (newAddr.Address != addr.Address) { | 
 |           _mergedCells.List[i] = newAddr._address; | 
 |         } | 
 |       } | 
 |     } | 
 |     for (int i = removeIndex.Count - 1; i >= 0; i--) { | 
 |       _mergedCells.List.RemoveAt(removeIndex[i]); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Delete the specified row from the worksheet. | 
 |   /// </summary> | 
 |   /// <param name="row">A row to be deleted</param> | 
 |   public void DeleteRow(int row) { | 
 |     DeleteRow(row, 1); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Delete the specified row from the worksheet. | 
 |   /// </summary> | 
 |   /// <param name="rowFrom">The start row</param> | 
 |   /// <param name="rows">Number of rows to delete</param> | 
 |   public void DeleteRow(int rowFrom, int rows) { | 
 |     CheckSheetType(); | 
 |     if (rowFrom < 1 || rowFrom + rows > ExcelPackage.MaxRows) { | 
 |       throw (new ArgumentException( | 
 |               "Row out of range. Spans from 1 to " | 
 |                   + ExcelPackage.MaxRows.ToString(CultureInfo.InvariantCulture))); | 
 |     } | 
 |     _values.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _types.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _formulas.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _styles.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _flags.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _commentsStore.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |     _hyperLinks.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); | 
 |  | 
 |     AdjustFormulasRow(rowFrom, rows); | 
 |     FixMergedCellsRow(rowFrom, rows, true); | 
 |  | 
 |     foreach (var tbl in Tables) { | 
 |       tbl.Address = tbl.Address.DeleteRow(rowFrom, rows); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Delete the specified column from the worksheet. | 
 |   /// </summary> | 
 |   /// <param name="column">The column to be deleted</param> | 
 |   public void DeleteColumn(int column) { | 
 |     DeleteColumn(column, 1); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Delete the specified column from the worksheet. | 
 |   /// </summary> | 
 |   /// <param name="columnFrom">The start column</param> | 
 |   /// <param name="columns">Number of columns to delete</param> | 
 |   public void DeleteColumn(int columnFrom, int columns) { | 
 |     if (columnFrom < 1 || columnFrom + columns > ExcelPackage.MaxColumns) { | 
 |       throw (new ArgumentException( | 
 |               "Column out of range. Spans from 1 to " | 
 |                   + ExcelPackage.MaxColumns.ToString(CultureInfo.InvariantCulture))); | 
 |     } | 
 |     var col = _values.GetValue(0, columnFrom) as ExcelColumn; | 
 |     if (col == null) { | 
 |       var r = 0; | 
 |       var c = columnFrom; | 
 |       if (_values.PrevCell(ref r, ref c)) { | 
 |         col = _values.GetValue(0, c) as ExcelColumn; | 
 |         if (col._columnMax >= columnFrom) { | 
 |           col.ColumnMax = columnFrom - 1; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     _values.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _types.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _formulas.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _styles.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _flags.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _commentsStore.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |     _hyperLinks.Delete(0, columnFrom, ExcelPackage.MaxRows, columns); | 
 |  | 
 |     AdjustFormulasColumn(columnFrom, columns); | 
 |     FixMergedCellsColumn(columnFrom, columns, true); | 
 |  | 
 |     var csec = new CellsStoreEnumerator<object>(_values, 0, columnFrom, 0, ExcelPackage.MaxColumns); | 
 |     foreach (var column in csec) { | 
 |       if (column is ExcelColumn excelColumn) { | 
 |         if (excelColumn._columnMin >= columnFrom) { | 
 |           excelColumn._columnMin -= columns; | 
 |           excelColumn._columnMax -= columns; | 
 |         } | 
 |       } | 
 |     } | 
 |  | 
 |     foreach (var tbl in Tables) { | 
 |       if (columnFrom >= tbl.Address.Start.Column && columnFrom <= tbl.Address.End.Column) { | 
 |         var node = tbl.Columns[0].TopNode.ParentNode; | 
 |         var ix = columnFrom - tbl.Address.Start.Column; | 
 |         for (int i = 0; i < columns; i++) { | 
 |           if (node.ChildNodes.Count > ix) { | 
 |             node.RemoveChild(node.ChildNodes[ix]); | 
 |           } | 
 |         } | 
 |         tbl._cols = new(tbl); | 
 |       } | 
 |  | 
 |       tbl.Address = tbl.Address.DeleteColumn(columnFrom, columns); | 
 |     } | 
 |   } | 
 |  | 
 |   internal void AdjustFormulasRow(int rowFrom, int rows) { | 
 |     var delSf = new List<int>(); | 
 |     foreach (var sf in _sharedFormulas.Values) { | 
 |       var a = new ExcelAddress(sf.Address).DeleteRow(rowFrom, rows); | 
 |       if (a == null) { | 
 |         delSf.Add(sf.Index); | 
 |       } else { | 
 |         sf.Address = a.Address; | 
 |         if (sf.StartRow > rowFrom) { | 
 |           var r = Math.Min(sf.StartRow - rowFrom, rows); | 
 |           sf.Formula = ExcelCellBase.UpdateFormulaReferences(sf.Formula, -r, 0, rowFrom, 0); | 
 |           sf.StartRow -= r; | 
 |         } | 
 |       } | 
 |     } | 
 |     foreach (var ix in delSf) { | 
 |       _sharedFormulas.Remove(ix); | 
 |     } | 
 |     var cse = new CellsStoreEnumerator<object>( | 
 |         _formulas, | 
 |         1, | 
 |         1, | 
 |         ExcelPackage.MaxRows, | 
 |         ExcelPackage.MaxColumns); | 
 |     while (cse.Next()) { | 
 |       if (cse.Value is string) { | 
 |         cse.Value = ExcelCellBase.UpdateFormulaReferences( | 
 |             cse.Value.ToString(), | 
 |             -rows, | 
 |             0, | 
 |             rowFrom, | 
 |             0); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   internal void AdjustFormulasColumn(int columnFrom, int columns) { | 
 |     var delSf = new List<int>(); | 
 |     foreach (var sf in _sharedFormulas.Values) { | 
 |       var a = new ExcelAddress(sf.Address).DeleteColumn(columnFrom, columns); | 
 |       if (a == null) { | 
 |         delSf.Add(sf.Index); | 
 |       } else { | 
 |         sf.Address = a.Address; | 
 |         if (sf.StartCol > columnFrom) { | 
 |           var c = Math.Min(sf.StartCol - columnFrom, columns); | 
 |           sf.Formula = ExcelCellBase.UpdateFormulaReferences(sf.Formula, 0, -c, 0, 1); | 
 |           sf.StartCol -= c; | 
 |         } | 
 |       } | 
 |     } | 
 |     foreach (var ix in delSf) { | 
 |       _sharedFormulas.Remove(ix); | 
 |     } | 
 |     var cse = new CellsStoreEnumerator<object>( | 
 |         _formulas, | 
 |         1, | 
 |         1, | 
 |         ExcelPackage.MaxRows, | 
 |         ExcelPackage.MaxColumns); | 
 |     while (cse.Next()) { | 
 |       if (cse.Value is string) { | 
 |         cse.Value = ExcelCellBase.UpdateFormulaReferences( | 
 |             cse.Value.ToString(), | 
 |             0, | 
 |             -columns, | 
 |             0, | 
 |             columnFrom); | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Deletes the specified row from the worksheet. | 
 |   /// </summary> | 
 |   /// <param name="rowFrom">The number of the start row to be deleted</param> | 
 |   /// <param name="rows">Number of rows to delete</param> | 
 |   /// <param name="shiftOtherRowsUp">Not used. Rows are always shifted</param> | 
 |   public void DeleteRow(int rowFrom, int rows, bool shiftOtherRowsUp) { | 
 |     DeleteRow(rowFrom, rows); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get the cell value from thw worksheet | 
 |   /// </summary> | 
 |   /// <param name="row">The row number</param> | 
 |   /// <param name="column">The row number</param> | 
 |   /// <returns>The value</returns> | 
 |   public object GetValue(int row, int column) { | 
 |     CheckSheetType(); | 
 |     var v = _values.GetValue(row, column); | 
 |     if (v != null) { | 
 |       if (_flags.GetFlagValue(row, column, CellFlags.RichText)) { | 
 |         return Cells[row, column].RichText.Text; | 
 |       } | 
 |       return v; | 
 |     } | 
 |     return null; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get a strongly typed cell value from the worksheet | 
 |   /// </summary> | 
 |   /// <typeparam name="T">The type</typeparam> | 
 |   /// <param name="row">The row number</param> | 
 |   /// <param name="column">The row number</param> | 
 |   /// <returns>The value. If the value can't be converted to the specified type, the default value will be returned</returns> | 
 |   public T GetValue<T>(int row, int column) { | 
 |     CheckSheetType(); | 
 |     //ulong cellID=ExcelCellBase.GetCellID(SheetID, Row, Column); | 
 |     var v = _values.GetValue(row, column); | 
 |     if (v == null) { | 
 |       return default(T); | 
 |     } | 
 |  | 
 |     //var cell=((ExcelCell)_cells[cellID]); | 
 |     if (_flags.GetFlagValue(row, column, CellFlags.RichText)) { | 
 |       return (T)(object)Cells[row, column].RichText.Text; | 
 |     } | 
 |     return GetTypedValue<T>(v); | 
 |   } | 
 |  | 
 |   //Thanks to Michael Tran for parts of this method | 
 |   internal T GetTypedValue<T>(object v) { | 
 |     if (v == null) { | 
 |       return default(T); | 
 |     } | 
 |     Type fromType = v.GetType(); | 
 |     Type toType = typeof(T); | 
 |     if (fromType == toType) { | 
 |       return (T)v; | 
 |     } | 
 |     var cnv = TypeDescriptor.GetConverter(fromType); | 
 |     if (toType | 
 |         == typeof(DateTime)) //Handle dates | 
 |     { | 
 |       if (fromType == typeof(TimeSpan)) { | 
 |         return ((T)(object)(new DateTime(((TimeSpan)v).Ticks))); | 
 |       } | 
 |       if (fromType == typeof(string)) { | 
 |         if (DateTime.TryParse(v.ToString(), out var dt)) { | 
 |           return (T)(object)(dt); | 
 |         } | 
 |         return default(T); | 
 |       } | 
 |       if (cnv.CanConvertTo(typeof(double))) { | 
 |         return (T)(object)(DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double)))); | 
 |       } | 
 |       return default(T); | 
 |     } | 
 |     if (toType | 
 |         == typeof(TimeSpan)) //Handle timespan | 
 |     { | 
 |       if (fromType == typeof(DateTime)) { | 
 |         return ((T)(object)(new TimeSpan(((DateTime)v).Ticks))); | 
 |       } | 
 |       if (fromType == typeof(string)) { | 
 |         if (TimeSpan.TryParse(v.ToString(), out var ts)) { | 
 |           return (T)(object)(ts); | 
 |         } | 
 |         return default(T); | 
 |       } | 
 |       if (cnv.CanConvertTo(typeof(double))) { | 
 |         return (T) | 
 |           (object)(new TimeSpan( | 
 |               DateTime.FromOADate((double)cnv.ConvertTo(v, typeof(double))).Ticks)); | 
 |       } | 
 |       try { | 
 |         // Issue 14682 -- "GetValue<decimal>() won't convert strings" | 
 |         // As suggested, after all special cases, all .NET to do it's | 
 |         // preferred conversion rather than simply returning the default | 
 |         return (T)Convert.ChangeType(v, typeof(T)); | 
 |       } catch (Exception) { | 
 |         // This was the previous behaviour -- no conversion is available. | 
 |         return default(T); | 
 |       } | 
 |     } | 
 |     if (cnv.CanConvertTo(toType)) { | 
 |       return (T)cnv.ConvertTo(v, typeof(T)); | 
 |     } | 
 |     if (toType.IsGenericType && toType.GetGenericTypeDefinition().Equals(typeof(Nullable<>))) { | 
 |       toType = Nullable.GetUnderlyingType(toType); | 
 |       if (cnv.CanConvertTo(toType)) { | 
 |         return (T)cnv.ConvertTo(v, typeof(T)); | 
 |       } | 
 |     } | 
 |  | 
 |     if (fromType == typeof(double) && toType == typeof(decimal)) { | 
 |       return (T)(object)Convert.ToDecimal(v); | 
 |     } | 
 |     if (fromType == typeof(decimal) && toType == typeof(double)) { | 
 |       return (T)(object)Convert.ToDouble(v); | 
 |     } | 
 |     return default(T); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Set the value of a cell | 
 |   /// </summary> | 
 |   /// <param name="row">The row number</param> | 
 |   /// <param name="column">The column number</param> | 
 |   /// <param name="value">The value</param> | 
 |   public void SetValue(int row, int column, object value) { | 
 |     CheckSheetType(); | 
 |     if (row < 1 || column < 1 || row > ExcelPackage.MaxRows && column > ExcelPackage.MaxColumns) { | 
 |       throw new ArgumentOutOfRangeException("Row or Column out of range"); | 
 |     } | 
 |     _values.SetValue(row, column, value); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Set the value of a cell | 
 |   /// </summary> | 
 |   /// <param name="address">The Excel address</param> | 
 |   /// <param name="value">The value</param> | 
 |   public void SetValue(string address, object value) { | 
 |     CheckSheetType(); | 
 |     ExcelCellBase.GetRowCol(address, out var row, out var col, true); | 
 |     if (row < 1 || col < 1 || row > ExcelPackage.MaxRows && col > ExcelPackage.MaxColumns) { | 
 |       throw new ArgumentOutOfRangeException("Address is invalid or out of range"); | 
 |     } | 
 |     _values.SetValue(row, col, value); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get MergeCell Index No | 
 |   /// </summary> | 
 |   /// <param name="row"></param> | 
 |   /// <param name="column"></param> | 
 |   /// <returns></returns> | 
 |   public int GetMergeCellId(int row, int column) { | 
 |     for (int i = 0; i < _mergedCells.Count; i++) { | 
 |       if (!string.IsNullOrEmpty(_mergedCells[i])) { | 
 |         ExcelRange range = Cells[_mergedCells[i]]; | 
 |  | 
 |         if (range.Start.Row <= row && row <= range.End.Row) { | 
 |           if (range.Start.Column <= column && column <= range.End.Column) { | 
 |             return i + 1; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |     return 0; | 
 |   } | 
 |  | 
 |   internal void Save() { | 
 |     DeletePrinterSettings(); | 
 |     if (!(this is ExcelChartsheet)) { | 
 |       var d = Dimension; | 
 |       if (d == null) { | 
 |         DeleteAllNode("d:dimension/@ref"); | 
 |       } else { | 
 |         SetXmlNodeString("d:dimension/@ref", d.Address); | 
 |       } | 
 |       SaveTables(); | 
 |       SavePivotTables(); | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SaveHandler(StreamWriter streamWriter) { | 
 |     //Create the nodes if they do not exist. | 
 |     if (this is ExcelChartsheet) { | 
 |       streamWriter.Write(WorksheetXml.OuterXml); | 
 |     } else { | 
 |       CreateNode("d:cols"); | 
 |       CreateNode("d:sheetData"); | 
 |       CreateNode("d:mergeCells"); | 
 |       CreateNode("d:hyperlinks"); | 
 |       CreateNode("d:rowBreaks"); | 
 |       CreateNode("d:colBreaks"); | 
 |  | 
 |       //StreamWriter sw=new StreamWriter(Part.GetStream(FileMode.Create, FileAccess.Write)); | 
 |       var xml = WorksheetXml.OuterXml; | 
 |       int colStart = 0, | 
 |           colEnd = 0; | 
 |       GetBlockPos(xml, "cols", ref colStart, ref colEnd); | 
 |  | 
 |       streamWriter.Write(xml.Substring(0, colStart)); | 
 |       UpdateColumnData(streamWriter); | 
 |  | 
 |       int cellStart = colEnd, | 
 |           cellEnd = colEnd; | 
 |       GetBlockPos(xml, "sheetData", ref cellStart, ref cellEnd); | 
 |  | 
 |       streamWriter.Write(xml.Substring(colEnd, cellStart - colEnd)); | 
 |       UpdateRowCellData(streamWriter); | 
 |  | 
 |       int mergeStart = cellEnd, | 
 |           mergeEnd = cellEnd; | 
 |  | 
 |       GetBlockPos(xml, "mergeCells", ref mergeStart, ref mergeEnd); | 
 |       streamWriter.Write(xml.Substring(cellEnd, mergeStart - cellEnd)); | 
 |  | 
 |       CleanupMergedCells(_mergedCells); | 
 |       if (_mergedCells.Count > 0) { | 
 |         UpdateMergedCells(streamWriter); | 
 |       } | 
 |  | 
 |       int hyperStart = mergeEnd, | 
 |           hyperEnd = mergeEnd; | 
 |       GetBlockPos(xml, "hyperlinks", ref hyperStart, ref hyperEnd); | 
 |       streamWriter.Write(xml.Substring(mergeEnd, hyperStart - mergeEnd)); | 
 |       UpdateHyperLinks(streamWriter); | 
 |  | 
 |       int rowBreakStart = hyperEnd, | 
 |           rowBreakEnd = hyperEnd; | 
 |       GetBlockPos(xml, "rowBreaks", ref rowBreakStart, ref rowBreakEnd); | 
 |       streamWriter.Write(xml.Substring(hyperEnd, rowBreakStart - hyperEnd)); | 
 |       UpdateRowBreaks(streamWriter); | 
 |  | 
 |       int colBreakStart = rowBreakEnd, | 
 |           colBreakEnd = rowBreakEnd; | 
 |       GetBlockPos(xml, "colBreaks", ref colBreakStart, ref colBreakEnd); | 
 |       streamWriter.Write(xml.Substring(rowBreakEnd, colBreakStart - rowBreakEnd)); | 
 |       UpdateColBreaks(streamWriter); | 
 |       streamWriter.Write(xml.Substring(colBreakEnd, xml.Length - colBreakEnd)); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Delete the printersettings relationship and part. | 
 |   /// </summary> | 
 |   private void DeletePrinterSettings() { | 
 |     //Delete the relationship from the pageSetup tag | 
 |     XmlAttribute attr = (XmlAttribute) | 
 |       WorksheetXml.SelectSingleNode("//d:pageSetup/@r:id", NameSpaceManager); | 
 |     if (attr != null) { | 
 |       string relId = attr.Value; | 
 |       //First delete the attribute from the XML | 
 |       attr.OwnerElement.Attributes.Remove(attr); | 
 |       if (Part.RelationshipExists(relId)) { | 
 |         var rel = Part.GetRelationship(relId); | 
 |         Uri printerSettingsUri = UriHelper.ResolvePartUri(rel.SourceUri, rel.TargetUri); | 
 |         Part.DeleteRelationship(rel.Id); | 
 |  | 
 |         //Delete the part from the package | 
 |         if (_package.Package.PartExists(printerSettingsUri)) { | 
 |           _package.Package.DeletePart(printerSettingsUri); | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Save all table data | 
 |   /// </summary> | 
 |   private void SaveTables() { | 
 |     foreach (var tbl in Tables) { | 
 |       if (tbl.ShowHeader || tbl.ShowTotal) { | 
 |         int colNum = tbl.Address._fromCol; | 
 |         var colVal = new HashSet<string>(); | 
 |         foreach (var col in tbl.Columns) { | 
 |           string n = col.Name.ToLower(CultureInfo.InvariantCulture); | 
 |           if (tbl.ShowHeader) { | 
 |             n = tbl.WorkSheet.GetValue<string>( | 
 |                 tbl.Address._fromRow, | 
 |                 tbl.Address._fromCol + col.Position); | 
 |             if (string.IsNullOrEmpty(n)) { | 
 |               n = col.Name.ToLower(CultureInfo.InvariantCulture); | 
 |             } else { | 
 |               col.Name = n; | 
 |             } | 
 |           } else { | 
 |             n = col.Name.ToLower(CultureInfo.InvariantCulture); | 
 |           } | 
 |  | 
 |           if (colVal.Contains(n)) { | 
 |             throw (new InvalidDataException( | 
 |                     string.Format( | 
 |                         "Table {0} Column {1} does not have a unique name.", | 
 |                         tbl.Name, | 
 |                         col.Name))); | 
 |           } | 
 |           colVal.Add(n); | 
 |           col.Name = ConvertUtil.ExcelEncodeString(col.Name); | 
 |           if (tbl.ShowHeader) { | 
 |             _values.SetValue(tbl.Address._fromRow, colNum, col.Name); | 
 |           } | 
 |           if (tbl.ShowTotal) { | 
 |             SetTableTotalFunction(tbl, col, colNum); | 
 |           } | 
 |           if (!string.IsNullOrEmpty(col.CalculatedColumnFormula)) { | 
 |             int fromRow = tbl.ShowHeader ? tbl.Address._fromRow + 1 : tbl.Address._fromRow; | 
 |             int toRow = tbl.ShowTotal ? tbl.Address._toRow - 1 : tbl.Address._toRow; | 
 |             for (int row = fromRow; row <= toRow; row++) { | 
 |               //Cell(row, colNum).Formula = col.CalculatedColumnFormula; | 
 |               SetFormula(row, colNum, col.CalculatedColumnFormula); | 
 |             } | 
 |           } | 
 |           colNum++; | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SetTableTotalFunction(ExcelTable tbl, ExcelTableColumn col, int colNum = -1) { | 
 |     if (tbl.ShowTotal == false) { | 
 |       return; | 
 |     } | 
 |     if (colNum == -1) { | 
 |       for (int i = 0; i < tbl.Columns.Count; i++) { | 
 |         if (tbl.Columns[i].Name == col.Name) { | 
 |           colNum = tbl.Address._fromCol + i; | 
 |         } | 
 |       } | 
 |     } | 
 |     if (col.TotalsRowFunction == RowFunctions.Custom) { | 
 |       SetFormula(tbl.Address._toRow, colNum, col.TotalsRowFormula); | 
 |     } else if (col.TotalsRowFunction != RowFunctions.None) { | 
 |       switch (col.TotalsRowFunction) { | 
 |         case RowFunctions.Average: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "101")); | 
 |           break; | 
 |         case RowFunctions.Count: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "102")); | 
 |           break; | 
 |         case RowFunctions.CountNums: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "103")); | 
 |           break; | 
 |         case RowFunctions.Max: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "104")); | 
 |           break; | 
 |         case RowFunctions.Min: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "105")); | 
 |           break; | 
 |         case RowFunctions.StdDev: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "107")); | 
 |           break; | 
 |         case RowFunctions.Var: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "110")); | 
 |           break; | 
 |         case RowFunctions.Sum: | 
 |           SetFormula(tbl.Address._toRow, colNum, GetTotalFunction(col, "109")); | 
 |           break; | 
 |         default: | 
 |           throw (new("Unknown RowFunction enum")); | 
 |       } | 
 |     } else { | 
 |       _values.SetValue(tbl.Address._toRow, colNum, col.TotalsRowLabel); | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SetFormula(int row, int col, object value) { | 
 |     _formulas.SetValue(row, col, value); | 
 |     if (!_values.Exists(row, col)) { | 
 |       _values.SetValue(row, col, null); | 
 |     } | 
 |   } | 
 |  | 
 |   internal void SetStyle(int row, int col, int value) { | 
 |     _styles.SetValue(row, col, value); | 
 |     if (!_values.Exists(row, col)) { | 
 |       _values.SetValue(row, col, null); | 
 |     } | 
 |   } | 
 |  | 
 |   private void SavePivotTables() { | 
 |     foreach (var pt in PivotTables) { | 
 |       if (pt.DataFields.Count > 1) { | 
 |         XmlElement parentNode; | 
 |         if (pt.DataOnRows) { | 
 |           parentNode = | 
 |               pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager) as XmlElement; | 
 |           if (parentNode == null) { | 
 |             pt.CreateNode("d:rowFields"); | 
 |             parentNode = | 
 |                 pt.PivotTableXml.SelectSingleNode("//d:rowFields", pt.NameSpaceManager) | 
 |                     as XmlElement; | 
 |           } | 
 |         } else { | 
 |           parentNode = | 
 |               pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager) as XmlElement; | 
 |           if (parentNode == null) { | 
 |             pt.CreateNode("d:colFields"); | 
 |             parentNode = | 
 |                 pt.PivotTableXml.SelectSingleNode("//d:colFields", pt.NameSpaceManager) | 
 |                     as XmlElement; | 
 |           } | 
 |         } | 
 |  | 
 |         if (parentNode.SelectSingleNode("d:field[@ x= \"-2\"]", pt.NameSpaceManager) == null) { | 
 |           XmlElement fieldNode = pt.PivotTableXml.CreateElement("field", ExcelPackage._schemaMain); | 
 |           fieldNode.SetAttribute("x", "-2"); | 
 |           parentNode.AppendChild(fieldNode); | 
 |         } | 
 |       } | 
 |       var ws = Workbook.Worksheets[pt.CacheDefinition.SourceRange.WorkSheet]; | 
 |       var t = ws.Tables.GetFromRange(pt.CacheDefinition.SourceRange); | 
 |       var fields = pt.CacheDefinition.CacheDefinitionXml.SelectNodes( | 
 |           "d:pivotCacheDefinition/d:cacheFields/d:cacheField", | 
 |           NameSpaceManager); | 
 |       int ix = 0; | 
 |       if (fields != null) { | 
 |         var flds = new HashSet<string>(); | 
 |         foreach (XmlElement node in fields) { | 
 |           if (ix >= pt.CacheDefinition.SourceRange.Columns) { | 
 |             break; | 
 |           } | 
 |           var fldName = node.GetAttribute("name"); //Fixes issue 15295 dup name error | 
 |           if (string.IsNullOrEmpty(fldName)) { | 
 |             fldName = | 
 |                 (t == null | 
 |                     ? pt.CacheDefinition.SourceRange.Offset(0, ix++, 1, 1).Value.ToString() | 
 |                     : t.Columns[ix++].Name); | 
 |           } | 
 |           if (flds.Contains(fldName)) { | 
 |             fldName = GetNewName(flds, fldName); | 
 |           } | 
 |           flds.Add(fldName); | 
 |           node.SetAttribute("name", fldName); | 
 |         } | 
 |         foreach (var df in pt.DataFields) { | 
 |           if (string.IsNullOrEmpty(df.Name)) { | 
 |             string name; | 
 |             if (df.Function == DataFieldFunctions.None) { | 
 |               name = df.Field.Name; //Name must be set or Excel will crash on rename. | 
 |             } else { | 
 |               name = df.Function + " of " + df.Field.Name; //Name must be set or Excel will crash on rename. | 
 |             } | 
 |             //Make sure name is unique | 
 |             var newName = name; | 
 |             var i = 2; | 
 |             while (pt.DataFields.ExistsDfName(newName, df)) { | 
 |               newName = name + (i++).ToString(CultureInfo.InvariantCulture); | 
 |             } | 
 |             df.Name = newName; | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private string GetNewName(HashSet<string> flds, string fldName) { | 
 |     int ix = 2; | 
 |     while (flds.Contains(fldName + ix.ToString(CultureInfo.InvariantCulture))) { | 
 |       ix++; | 
 |     } | 
 |     return fldName + ix.ToString(CultureInfo.InvariantCulture); | 
 |   } | 
 |  | 
 |   private static string GetTotalFunction(ExcelTableColumn col, string functionNum) { | 
 |     return string.Format("SUBTOTAL({0},{1}[{2}])", functionNum, col._tbl.Name, col.Name); | 
 |   } | 
 |  | 
 |   private void CleanupMergedCells(MergeCellsCollection mergedCells) { | 
 |     int i = 0; | 
 |     while (i < mergedCells.List.Count) { | 
 |       if (mergedCells[i] == null) { | 
 |         mergedCells.List.RemoveAt(i); | 
 |       } else { | 
 |         i++; | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   private void UpdateColBreaks(StreamWriter sw) { | 
 |     StringBuilder breaks = new StringBuilder(); | 
 |     int count = 0; | 
 |     var cse = new CellsStoreEnumerator<object>(_values, 0, 0, 0, ExcelPackage.MaxColumns); | 
 |     while (cse.Next()) { | 
 |       var col = cse.Value as ExcelColumn; | 
 |       if (col != null && col.PageBreak) { | 
 |         breaks.AppendFormat("<brk id=\"{0}\" max=\"16383\" man=\"1\" />", cse.Column); | 
 |         count++; | 
 |       } | 
 |     } | 
 |     if (count > 0) { | 
 |       sw.Write("<colBreaks count=\"{0}\" manualBreakCount=\"{0}\">{1}</colBreaks>", count, breaks); | 
 |     } | 
 |   } | 
 |  | 
 |   private void UpdateRowBreaks(StreamWriter sw) { | 
 |     StringBuilder breaks = new StringBuilder(); | 
 |     int count = 0; | 
 |     var cse = new CellsStoreEnumerator<object>(_values, 0, 0, ExcelPackage.MaxRows, 0); | 
 |     //foreach(ExcelRow row in _rows) | 
 |     while (cse.Next()) { | 
 |       var row = cse.Value as RowInternal; | 
 |       if (row != null && row.PageBreak) { | 
 |         breaks.AppendFormat("<brk id=\"{0}\" max=\"1048575\" man=\"1\" />", cse.Row); | 
 |         count++; | 
 |       } | 
 |     } | 
 |     if (count > 0) { | 
 |       sw.Write("<rowBreaks count=\"{0}\" manualBreakCount=\"{0}\">{1}</rowBreaks>", count, breaks); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Inserts the cols collection into the XML document | 
 |   /// </summary> | 
 |   private void UpdateColumnData(StreamWriter sw) { | 
 |     var cse = new CellsStoreEnumerator<object>(_values, 0, 1, 0, ExcelPackage.MaxColumns); | 
 |     bool first = true; | 
 |     while (cse.Next()) { | 
 |       if (first) { | 
 |         sw.Write("<cols>"); | 
 |         first = false; | 
 |       } | 
 |       var col = cse.Value as ExcelColumn; | 
 |       ExcelStyleCollection<ExcelXfs> cellXfs = _workbook.Styles.CellXfs; | 
 |  | 
 |       sw.Write("<col min=\"{0}\" max=\"{1}\"", col.ColumnMin, col.ColumnMax); | 
 |       if (col.Hidden) { | 
 |         //sbXml.Append(" width=\"0\" hidden=\"1\" customWidth=\"1\""); | 
 |         sw.Write(" hidden=\"1\""); | 
 |       } else if (col.BestFit) { | 
 |         sw.Write(" bestFit=\"1\""); | 
 |       } | 
 |       sw.Write( | 
 |           string.Format( | 
 |               CultureInfo.InvariantCulture, | 
 |               " width=\"{0}\" customWidth=\"1\"", | 
 |               col.Width)); | 
 |       if (col.OutlineLevel > 0) { | 
 |         sw.Write(" outlineLevel=\"{0}\" ", col.OutlineLevel); | 
 |         if (col.Collapsed) { | 
 |           if (col.Hidden) { | 
 |             sw.Write(" collapsed=\"1\""); | 
 |           } else { | 
 |             sw.Write(" collapsed=\"1\" hidden=\"1\""); //Always hidden | 
 |           } | 
 |         } | 
 |       } | 
 |       if (col.Phonetic) { | 
 |         sw.Write(" phonetic=\"1\""); | 
 |       } | 
 |  | 
 |       var styleId = col.StyleID >= 0 ? cellXfs[col.StyleID].newID : col.StyleID; | 
 |       if (styleId > 0) { | 
 |         sw.Write(" style=\"{0}\"", styleId); | 
 |       } | 
 |       sw.Write(" />"); | 
 |     } | 
 |     if (!first) { | 
 |       sw.Write("</cols>"); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Insert row and cells into the XML document | 
 |   /// </summary> | 
 |   private void UpdateRowCellData(StreamWriter sw) { | 
 |     ExcelStyleCollection<ExcelXfs> cellXfs = _workbook.Styles.CellXfs; | 
 |  | 
 |     int row = -1; | 
 |  | 
 |     var ss = _workbook._sharedStrings; | 
 |     var styles = _workbook.Styles; | 
 |     var cache = new StringBuilder(); | 
 |     cache.Append("<sheetData>"); | 
 |  | 
 |     //Set a value for cells with style and no value set. | 
 |     var cseStyle = new CellsStoreEnumerator<int>( | 
 |         _styles, | 
 |         0, | 
 |         0, | 
 |         ExcelPackage.MaxRows, | 
 |         ExcelPackage.MaxColumns); | 
 |     foreach (var s in cseStyle) { | 
 |       if (!_values.Exists(cseStyle.Row, cseStyle.Column)) { | 
 |         _values.SetValue(cseStyle.Row, cseStyle.Column, null); | 
 |       } | 
 |     } | 
 |  | 
 |     var cse = new CellsStoreEnumerator<object>( | 
 |         _values, | 
 |         1, | 
 |         0, | 
 |         ExcelPackage.MaxRows, | 
 |         ExcelPackage.MaxColumns); | 
 |     //foreach (IRangeID r in _cells) | 
 |     while (cse.Next()) { | 
 |       if (cse.Column > 0) { | 
 |         int styleId = cellXfs[styles.GetStyleId(this, cse.Row, cse.Column)].newID; | 
 |         //Add the row element if it's a new row | 
 |         if (cse.Row != row) { | 
 |           WriteRow(cache, cellXfs, row, cse.Row); | 
 |           row = cse.Row; | 
 |         } | 
 |         object v = cse.Value; | 
 |         object formula = _formulas.GetValue(cse.Row, cse.Column); | 
 |         if (formula is int sfId) { | 
 |           var f = _sharedFormulas[sfId]; | 
 |           if (f.Address.IndexOf(':') > 0) { | 
 |             if (f.StartCol == cse.Column && f.StartRow == cse.Row) { | 
 |               if (f.IsArray) { | 
 |                 cache.AppendFormat( | 
 |                     "<c r=\"{0}\" s=\"{1}\"{5}><f ref=\"{2}\" t=\"array\">{3}</f>{4}</c>", | 
 |                     cse.CellAddress, | 
 |                     styleId < 0 ? 0 : styleId, | 
 |                     f.Address, | 
 |                     SecurityElement.Escape(f.Formula), | 
 |                     GetFormulaValue(v), | 
 |                     GetCellType(v, true)); | 
 |               } else { | 
 |                 cache.AppendFormat( | 
 |                     "<c r=\"{0}\" s=\"{1}\"{6}><f ref=\"{2}\" t=\"shared\"  si=\"{3}\">{4}</f>{5}</c>", | 
 |                     cse.CellAddress, | 
 |                     styleId < 0 ? 0 : styleId, | 
 |                     f.Address, | 
 |                     sfId, | 
 |                     SecurityElement.Escape(f.Formula), | 
 |                     GetFormulaValue(v), | 
 |                     GetCellType(v, true)); | 
 |               } | 
 |             } else if (f.IsArray) { | 
 |               cache.AppendFormat( | 
 |                   "<c r=\"{0}\" s=\"{1}\" />", | 
 |                   cse.CellAddress, | 
 |                   styleId < 0 ? 0 : styleId); | 
 |             } else { | 
 |               cache.AppendFormat( | 
 |                   "<c r=\"{0}\" s=\"{1}\"{4}><f t=\"shared\" si=\"{2}\" />{3}</c>", | 
 |                   cse.CellAddress, | 
 |                   styleId < 0 ? 0 : styleId, | 
 |                   sfId, | 
 |                   GetFormulaValue(v), | 
 |                   GetCellType(v, true)); | 
 |             } | 
 |           } else { | 
 |             // We can also have a single cell array formula | 
 |             if (f.IsArray) { | 
 |               cache.AppendFormat( | 
 |                   "<c r=\"{0}\" s=\"{1}\"{5}><f ref=\"{2}\" t=\"array\">{3}</f>{4}</c>", | 
 |                   cse.CellAddress, | 
 |                   styleId < 0 ? 0 : styleId, | 
 |                   string.Format("{0}:{1}", f.Address, f.Address), | 
 |                   SecurityElement.Escape(f.Formula), | 
 |                   GetFormulaValue(v), | 
 |                   GetCellType(v, true)); | 
 |             } else { | 
 |               cache.AppendFormat("<c r=\"{0}\" s=\"{1}\">", f.Address, styleId < 0 ? 0 : styleId); | 
 |               cache.AppendFormat( | 
 |                   "<f>{0}</f>{1}</c>", | 
 |                   SecurityElement.Escape(f.Formula), | 
 |                   GetFormulaValue(v)); | 
 |             } | 
 |           } | 
 |         } else if (formula != null && formula.ToString() != "") { | 
 |           cache.AppendFormat( | 
 |               "<c r=\"{0}\" s=\"{1}\"{2}>", | 
 |               cse.CellAddress, | 
 |               styleId < 0 ? 0 : styleId, | 
 |               GetCellType(v, true)); | 
 |           cache.AppendFormat( | 
 |               "<f>{0}</f>{1}</c>", | 
 |               SecurityElement.Escape(formula.ToString()), | 
 |               GetFormulaValue(v)); | 
 |         } else { | 
 |           if (v == null && styleId > 0) { | 
 |             cache.AppendFormat( | 
 |                 "<c r=\"{0}\" s=\"{1}\" />", | 
 |                 cse.CellAddress, | 
 |                 styleId < 0 ? 0 : styleId); | 
 |           } else if (v != null) { | 
 |             if ((v.GetType().IsPrimitive | 
 |                         || v is double | 
 |                         || v is decimal | 
 |                         || v is DateTime | 
 |                         || v is TimeSpan)) { | 
 |               //string sv = GetValueForXml(v); | 
 |               cache.AppendFormat( | 
 |                   "<c r=\"{0}\" s=\"{1}\" {2}>", | 
 |                   cse.CellAddress, | 
 |                   styleId < 0 ? 0 : styleId, | 
 |                   GetCellType(v)); | 
 |               cache.AppendFormat("{0}</c>", GetFormulaValue(v)); | 
 |             } else { | 
 |               int ix; | 
 |               if (!ss.ContainsKey(v.ToString())) { | 
 |                 ix = ss.Count; | 
 |                 ss.Add( | 
 |                     v.ToString(), | 
 |                     new() { | 
 |                       isRichText = _flags.GetFlagValue(cse.Row, cse.Column, CellFlags.RichText), | 
 |                       pos = ix, | 
 |                     }); | 
 |               } else { | 
 |                 ix = ss[v.ToString()].pos; | 
 |               } | 
 |               cache.AppendFormat( | 
 |                   "<c r=\"{0}\" s=\"{1}\" t=\"s\">", | 
 |                   cse.CellAddress, | 
 |                   styleId < 0 ? 0 : styleId); | 
 |               cache.AppendFormat("<v>{0}</v></c>", ix); | 
 |             } | 
 |           } | 
 |         } | 
 |       } else //ExcelRow | 
 |       { | 
 |         WriteRow(cache, cellXfs, row, cse.Row); | 
 |         row = cse.Row; | 
 |       } | 
 |       if (cache.Length > 0x600000) { | 
 |         sw.Write(cache.ToString()); | 
 |         cache = new(); | 
 |       } | 
 |     } | 
 |  | 
 |     if (row != -1) { | 
 |       cache.Append("</row>"); | 
 |     } | 
 |     cache.Append("</sheetData>"); | 
 |     sw.Write(cache.ToString()); | 
 |     sw.Flush(); | 
 |   } | 
 |  | 
 |   private object GetFormulaValue(object v) { | 
 |     if (v != null && v.ToString() != "") { | 
 |       return "<v>" + SecurityElement.Escape(GetValueForXml(v)) + "</v>"; //Fixes issue 15071 | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   private string GetCellType(object v, bool allowStr = false) { | 
 |     if (v is bool) { | 
 |       return " t=\"b\""; | 
 |     } | 
 |     if ((v is double d && double.IsInfinity(d)) || v is ExcelErrorValue) { | 
 |       return " t=\"e\""; | 
 |     } | 
 |     if (allowStr | 
 |         && v != null | 
 |         && !(v.GetType().IsPrimitive | 
 |                 || v is double | 
 |                 || v is decimal | 
 |                 || v is DateTime | 
 |                 || v is TimeSpan)) { | 
 |       return " t=\"str\""; | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   private string GetValueForXml(object v) { | 
 |     string s; | 
 |     try { | 
 |       if (v is DateTime time) { | 
 |         double sdv = time.ToOADate(); | 
 |  | 
 |         if (Workbook.Date1904) { | 
 |           sdv -= ExcelWorkbook._date1904Offset; | 
 |         } | 
 |  | 
 |         s = sdv.ToString(CultureInfo.InvariantCulture); | 
 |       } else if (v is TimeSpan span) { | 
 |         s = new DateTime(span.Ticks).ToOADate().ToString(CultureInfo.InvariantCulture); | 
 |         ; | 
 |       } else if (v.GetType().IsPrimitive || v is double || v is decimal) { | 
 |         if (v is double d && double.IsNaN(d)) { | 
 |           s = ""; | 
 |         } else if (v is double d1 && double.IsInfinity(d1)) { | 
 |           s = "#NUM!"; | 
 |         } else { | 
 |           s = Convert | 
 |               .ToDouble(v, CultureInfo.InvariantCulture) | 
 |               .ToString("R15", CultureInfo.InvariantCulture); | 
 |         } | 
 |       } else { | 
 |         s = v.ToString(); | 
 |       } | 
 |     } catch { | 
 |       s = "0"; | 
 |     } | 
 |     return s; | 
 |   } | 
 |  | 
 |   private void WriteRow( | 
 |       StringBuilder cache, | 
 |       ExcelStyleCollection<ExcelXfs> cellXfs, | 
 |       int prevRow, | 
 |       int row) { | 
 |     if (prevRow != -1) { | 
 |       cache.Append("</row>"); | 
 |     } | 
 |     //ulong rowID = ExcelRow.GetRowID(SheetID, row); | 
 |     cache.AppendFormat("<row r=\"{0}\" ", row); | 
 |     RowInternal currRow = _values.GetValue(row, 0) as RowInternal; | 
 |     if (currRow != null) { | 
 |       if (currRow.Hidden) { | 
 |         cache.Append("ht=\"0\" hidden=\"1\" "); | 
 |       } else if (currRow.Height != DefaultRowHeight && currRow.Height >= 0) { | 
 |         cache.AppendFormat(CultureInfo.InvariantCulture, "ht=\"{0}\" ", currRow.Height); | 
 |         if (currRow.CustomHeight) { | 
 |           cache.Append("customHeight=\"1\" "); | 
 |         } | 
 |       } | 
 |  | 
 |       if (currRow.OutlineLevel > 0) { | 
 |         cache.AppendFormat("outlineLevel =\"{0}\" ", currRow.OutlineLevel); | 
 |         if (currRow.Collapsed) { | 
 |           if (currRow.Hidden) { | 
 |             cache.Append(" collapsed=\"1\" "); | 
 |           } else { | 
 |             cache.Append(" collapsed=\"1\" hidden=\"1\" "); //Always hidden | 
 |           } | 
 |         } | 
 |       } | 
 |       if (currRow.Phonetic) { | 
 |         cache.Append("ph=\"1\" "); | 
 |       } | 
 |     } | 
 |     var s = _styles.GetValue(row, 0); | 
 |     if (s > 0) { | 
 |       cache.AppendFormat("s=\"{0}\" customFormat=\"1\"", cellXfs[s].newID); | 
 |     } | 
 |     cache.Append(">"); | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Update xml with hyperlinks | 
 |   /// </summary> | 
 |   /// <param name="sw">The stream</param> | 
 |   private void UpdateHyperLinks(StreamWriter sw) { | 
 |     Dictionary<string, string> hyps = new Dictionary<string, string>(); | 
 |     var cse = new CellsStoreEnumerator<Uri>(_hyperLinks); | 
 |     bool first = true; | 
 |     //foreach (ulong cell in _hyperLinks) | 
 |     while (cse.Next()) { | 
 |       if (first) { | 
 |         sw.Write("<hyperlinks>"); | 
 |         first = false; | 
 |       } | 
 |       //int row, col; | 
 |       var uri = _hyperLinks.GetValue(cse.Row, cse.Column); | 
 |       //ExcelCell cell = _cells[cellId] as ExcelCell; | 
 |       if (uri is ExcelHyperLink link && !string.IsNullOrEmpty(link.ReferenceAddress)) { | 
 |         sw.Write( | 
 |             "<hyperlink ref=\"{0}\" location=\"{1}\" {2}{3}/>", | 
 |             Cells[cse.Row, cse.Column, cse.Row + link.RowSpann, cse.Column + link.ColSpann].Address, | 
 |             ExcelCellBase.GetFullAddress( | 
 |                 SecurityElement.Escape(Name), | 
 |                 SecurityElement.Escape(link.ReferenceAddress)), | 
 |             string.IsNullOrEmpty(link.Display) | 
 |                 ? "" | 
 |                 : "display=\"" + SecurityElement.Escape(link.Display) + "\" ", | 
 |             string.IsNullOrEmpty(link.ToolTip) | 
 |                 ? "" | 
 |                 : "tooltip=\"" + SecurityElement.Escape(link.ToolTip) + "\" "); | 
 |       } else if (uri != null) { | 
 |         Uri hyp; | 
 |         if (uri is ExcelHyperLink hyperLink) { | 
 |           hyp = hyperLink.OriginalUri; | 
 |         } else { | 
 |           hyp = uri; | 
 |         } | 
 |         if (!hyps.ContainsKey(hyp.OriginalString)) { | 
 |           var relationship = Part.CreateRelationship( | 
 |               hyp, | 
 |               TargetMode.External, | 
 |               ExcelPackage._schemaHyperlink); | 
 |           if (uri is ExcelHyperLink hl) { | 
 |             sw.Write( | 
 |                 "<hyperlink ref=\"{0}\" {2}{3}r:id=\"{1}\" />", | 
 |                 ExcelCellBase.GetAddress(cse.Row, cse.Column), | 
 |                 relationship.Id, | 
 |                 string.IsNullOrEmpty(hl.Display) | 
 |                     ? "" | 
 |                     : "display=\"" + SecurityElement.Escape(hl.Display) + "\" ", | 
 |                 string.IsNullOrEmpty(hl.ToolTip) | 
 |                     ? "" | 
 |                     : "tooltip=\"" + SecurityElement.Escape(hl.ToolTip) + "\" "); | 
 |           } else { | 
 |             sw.Write( | 
 |                 "<hyperlink ref=\"{0}\" r:id=\"{1}\" />", | 
 |                 ExcelCellBase.GetAddress(cse.Row, cse.Column), | 
 |                 relationship.Id); | 
 |           } | 
 |         } | 
 |       } | 
 |     } | 
 |     if (!first) { | 
 |       sw.Write("</hyperlinks>"); | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Dimension address for the worksheet. | 
 |   /// Top left cell to Bottom right. | 
 |   /// If the worksheet has no cells, null is returned | 
 |   /// </summary> | 
 |   public ExcelAddressBase Dimension { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (_values.GetDimension(out var fromRow, out var fromCol, out var toRow, out var toCol)) { | 
 |         var addr = new ExcelAddressBase(fromRow, fromCol, toRow, toCol); | 
 |         addr._ws = Name; | 
 |         return addr; | 
 |       } | 
 |       return null; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelSheetProtection _protection; | 
 |  | 
 |   /// <summary> | 
 |   /// Access to sheet protection properties | 
 |   /// </summary> | 
 |   public ExcelSheetProtection Protection { | 
 |     get { | 
 |       if (_protection == null) { | 
 |         _protection = new(NameSpaceManager, TopNode); | 
 |       } | 
 |       return _protection; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelProtectedRangeCollection _protectedRanges; | 
 |  | 
 |   public ExcelProtectedRangeCollection ProtectedRanges { | 
 |     get { | 
 |       if (_protectedRanges == null) { | 
 |         _protectedRanges = new(NameSpaceManager, TopNode); | 
 |       } | 
 |       return _protectedRanges; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelTableCollection _tables; | 
 |  | 
 |   /// <summary> | 
 |   /// Tables defined in the worksheet. | 
 |   /// </summary> | 
 |   public ExcelTableCollection Tables { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (Workbook._nextTableID == int.MinValue) { | 
 |         Workbook.ReadAllTables(); | 
 |       } | 
 |       if (_tables == null) { | 
 |         _tables = new(this); | 
 |       } | 
 |       return _tables; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelPivotTableCollection _pivotTables; | 
 |  | 
 |   /// <summary> | 
 |   /// Pivottables defined in the worksheet. | 
 |   /// </summary> | 
 |   public ExcelPivotTableCollection PivotTables { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (_pivotTables == null) { | 
 |         if (Workbook._nextPivotTableID == int.MinValue) { | 
 |           Workbook.ReadAllTables(); | 
 |         } | 
 |         _pivotTables = new(this); | 
 |       } | 
 |       return _pivotTables; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelConditionalFormattingCollection _conditionalFormatting; | 
 |  | 
 |   /// <summary> | 
 |   /// ConditionalFormatting defined in the worksheet. Use the Add methods to create ConditionalFormatting and add them to the worksheet. Then | 
 |   /// set the properties on the instance returned. | 
 |   /// </summary> | 
 |   /// <seealso cref="ExcelConditionalFormattingCollection"/> | 
 |   public ExcelConditionalFormattingCollection ConditionalFormatting { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (_conditionalFormatting == null) { | 
 |         _conditionalFormatting = new(this); | 
 |       } | 
 |       return _conditionalFormatting; | 
 |     } | 
 |   } | 
 |  | 
 |   private ExcelDataValidationCollection _dataValidation; | 
 |  | 
 |   /// <summary> | 
 |   /// DataValidation defined in the worksheet. Use the Add methods to create DataValidations and add them to the worksheet. Then | 
 |   /// set the properties on the instance returned. | 
 |   /// </summary> | 
 |   /// <seealso cref="ExcelDataValidationCollection"/> | 
 |   public ExcelDataValidationCollection DataValidations { | 
 |     get { | 
 |       CheckSheetType(); | 
 |       if (_dataValidation == null) { | 
 |         _dataValidation = new(this); | 
 |       } | 
 |       return _dataValidation; | 
 |     } | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Returns the style ID given a style name. | 
 |   /// The style ID will be created if not found, but only if the style name exists! | 
 |   /// </summary> | 
 |   /// <param name="styleName"></param> | 
 |   /// <returns></returns> | 
 |   internal int GetStyleId(string styleName) { | 
 |     ExcelNamedStyleXml namedStyle = null; | 
 |     Workbook.Styles.NamedStyles.FindById(styleName, ref namedStyle); | 
 |     if (namedStyle.XfId == int.MinValue) { | 
 |       namedStyle.XfId = Workbook.Styles.CellXfs.FindIndexById(namedStyle.Style.Id); | 
 |     } | 
 |     return namedStyle.XfId; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// The workbook object | 
 |   /// </summary> | 
 |   public ExcelWorkbook Workbook => _workbook; | 
 |  | 
 |   /// <summary> | 
 |   /// Get the next ID from a shared formula or an Array formula | 
 |   /// Sharedforumlas will have an id from 0-x. Array formula ids start from 0x4000001-. | 
 |   /// </summary> | 
 |   /// <param name="isArray">If the formula is an array formula</param> | 
 |   /// <returns></returns> | 
 |   internal int GetMaxShareFunctionIndex(bool isArray) { | 
 |     int i = _sharedFormulas.Count + 1; | 
 |     if (isArray) { | 
 |       i |= 0x40000000; | 
 |     } | 
 |  | 
 |     while (_sharedFormulas.ContainsKey(i)) { | 
 |       i++; | 
 |     } | 
 |     return i; | 
 |   } | 
 |  | 
 |   internal void UpdateCellsWithDate1904Setting() { | 
 |     var cse = new CellsStoreEnumerator<object>(_values); | 
 |     var offset = Workbook.Date1904 ? -ExcelWorkbook._date1904Offset : ExcelWorkbook._date1904Offset; | 
 |     while (cse.MoveNext()) { | 
 |       if (cse.Value is DateTime time) { | 
 |         try { | 
 |           double sdv = time.ToOADate(); | 
 |           sdv += offset; | 
 |  | 
 |           cse.Value = DateTime.FromOADate(sdv); | 
 |         } catch {} | 
 |       } | 
 |     } | 
 |   } | 
 |  | 
 |   public string GetFormula(int row, int col) { | 
 |     var v = _formulas.GetValue(row, col); | 
 |     if (v is int i) { | 
 |       return _sharedFormulas[i].GetFormula(row, col, Name); | 
 |     } | 
 |     if (v != null) { | 
 |       return v.ToString(); | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   public string GetFormulaR1C1(int row, int col) { | 
 |     var v = _formulas.GetValue(row, col); | 
 |     if (v is int i) { | 
 |       var sf = _sharedFormulas[i]; | 
 |       return ExcelCellBase.TranslateToR1C1( | 
 |           Formulas.RemoveDummyFunction(sf.Formula), | 
 |           sf.StartRow, | 
 |           sf.StartCol); | 
 |     } | 
 |     if (v != null) { | 
 |       return ExcelCellBase.TranslateToR1C1(Formulas.RemoveDummyFunction(v.ToString()), row, col); | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   public string GetFormulaR1C1_V1(int row, int col) { | 
 |     var v = _formulas.GetValue(row, col); | 
 |     if (v is int i) { | 
 |       var sf = _sharedFormulas[i]; | 
 |       return ExcelCellBase.TranslateToR1C1_V1( | 
 |           Formulas.RemoveDummyFunction(sf.Formula), | 
 |           sf.StartRow, | 
 |           sf.StartCol); | 
 |     } | 
 |     if (v != null) { | 
 |       return ExcelCellBase.TranslateToR1C1_V1(Formulas.RemoveDummyFunction(v.ToString()), row, col); | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   public bool IsArrayFormula(int row, int col) => | 
 |     _flags.GetFlagValue(row, col, CellFlags.ArrayFormula); | 
 |  | 
 |   public string GetArrayFormulaAddress(int row, int col) { | 
 |     var v = _formulas.GetValue(row, col); | 
 |     if ((v is int i) && (_sharedFormulas[i].IsArray)) { | 
 |       return _sharedFormulas[i].Address; | 
 |     } | 
 |     return ""; | 
 |   } | 
 |  | 
 |   public int GetStyleId(int row, int col) { | 
 |     int styleId = 0; | 
 |     if (!_styles.Exists(row, col, ref styleId) && !_styles.Exists(row, 0, ref styleId)) { | 
 |       styleId = _styles.GetValue(0, col); | 
 |     } | 
 |     return styleId; | 
 |   } | 
 |  | 
 |   /// <summary> | 
 |   /// Get the ExcelColumn for column (span ColumnMin and ColumnMax) | 
 |   /// </summary> | 
 |   /// <param name="column"></param> | 
 |   /// <returns></returns> | 
 |   internal ExcelColumn GetColumn(int column) { | 
 |     var c = _values.GetValue(0, column) as ExcelColumn; | 
 |     if (c == null) { | 
 |       int row = 0, | 
 |           col = column; | 
 |       if (_values.PrevCell(ref row, ref col)) { | 
 |         c = _values.GetValue(0, col) as ExcelColumn; | 
 |         if (c != null && c.ColumnMax >= column) { | 
 |           return c; | 
 |         } | 
 |         return null; | 
 |       } | 
 |     } | 
 |     return c; | 
 |   } | 
 |  | 
 |   public bool Equals(ExcelWorksheet x, ExcelWorksheet y) { | 
 |     return x.Name == y.Name | 
 |         && x.SheetID == y.SheetID | 
 |         && x.WorksheetXml.OuterXml == y.WorksheetXml.OuterXml; | 
 |   } | 
 |  | 
 |   public int GetHashCode(ExcelWorksheet obj) { | 
 |     return obj.WorksheetXml.OuterXml.GetHashCode(); | 
 |   } | 
 | } |