diff --git a/.gitignore b/.gitignore index 4bcc6612..4bc5bde0 100644 --- a/.gitignore +++ b/.gitignore @@ -52,4 +52,12 @@ _ReSharper*/ ~$* #NuGet -packages/ \ No newline at end of file +packages/ +.idea/.idea.EPPlus/.idea/contentModel.xml +.idea/.idea.EPPlus/.idea/encodings.xml +.idea/.idea.EPPlus/.idea/indexLayout.xml +.idea/.idea.EPPlus/.idea/misc.xml +.idea/.idea.EPPlus/.idea/modules.xml +.idea/.idea.EPPlus/.idea/vcs.xml +.idea/.idea.EPPlus/.idea/workspace.xml +.idea/.idea.EPPlus/riderModule.iml diff --git a/EPPlus/Drawing/Vml/ExcelVmlDrawingCommentCollection.cs b/EPPlus/Drawing/Vml/ExcelVmlDrawingCommentCollection.cs index d10f0af7..9dd780d8 100644 --- a/EPPlus/Drawing/Vml/ExcelVmlDrawingCommentCollection.cs +++ b/EPPlus/Drawing/Vml/ExcelVmlDrawingCommentCollection.cs @@ -113,7 +113,15 @@ private XmlNode AddDrawing(ExcelRangeBase cell) { ix = ~ix; var prevDraw = _drawings[ix] as ExcelVmlDrawingBase; - prevDraw.TopNode.ParentNode.InsertBefore(node, prevDraw.TopNode); + var parentNode = prevDraw.TopNode.ParentNode; + if (parentNode!=null) + { + parentNode.InsertBefore(node, prevDraw.TopNode); + } + else + { + VmlDrawingXml.DocumentElement.AppendChild(node); + } } else { diff --git a/EPPlus/EPPlus.csproj b/EPPlus/EPPlus.csproj index 1f411ce0..00b7eafb 100644 --- a/EPPlus/EPPlus.csproj +++ b/EPPlus/EPPlus.csproj @@ -25,7 +25,7 @@ 3.5 - v3.5 + v4.0 publish\ true Disk diff --git a/EPPlus/ExcelCommentCollection.cs b/EPPlus/ExcelCommentCollection.cs index 9835142f..3b68d9d7 100644 --- a/EPPlus/ExcelCommentCollection.cs +++ b/EPPlus/ExcelCommentCollection.cs @@ -218,10 +218,15 @@ public void Remove(ExcelComment comment) } if (comment==c) { - comment.TopNode.ParentNode.RemoveChild(comment.TopNode); //Remove VML - comment._commentHelper.TopNode.ParentNode.RemoveChild(comment._commentHelper.TopNode); //Remove Comment + // + comment.TopNode.ParentNode?.RemoveChild(comment.TopNode); //Remove VML + comment._commentHelper.TopNode.ParentNode?.RemoveChild(comment._commentHelper.TopNode); //Remove Comment + + if (Worksheet.VmlDrawingsComments._drawings.ContainsKey(id)) + { + Worksheet.VmlDrawingsComments._drawings.Delete(id); + } - Worksheet.VmlDrawingsComments._drawings.Delete(id); _list.RemoveAt(i); Worksheet._commentsStore.Delete(comment.Range._fromRow, comment.Range._fromCol, 1, 1, false); //Issue 15549, Comments should not be shifted var ci = new CellsStoreEnumerator(Worksheet._commentsStore); diff --git a/EPPlus/ExcelRangeBase.cs b/EPPlus/ExcelRangeBase.cs index 0519de60..50921ec2 100644 --- a/EPPlus/ExcelRangeBase.cs +++ b/EPPlus/ExcelRangeBase.cs @@ -253,11 +253,11 @@ private static void Set_StyleName(ExcelRangeBase range, object value, int row, i private static void Set_Value(ExcelRangeBase range, object value, int row, int col) { var sfi = range._worksheet._formulas.GetValue(row, col); - if (sfi is int) + if (sfi is int && !range.IsSingleCell) { range.SplitFormulas(range._worksheet.Cells[row, col]); } - if (sfi != null) range._worksheet._formulas.SetValue(row, col, string.Empty); + // if (sfi != null) range._worksheet._formulas.SetValue(row, col, string.Empty); range._worksheet.SetValueInner(row, col, value); } private static void Set_Formula(ExcelRangeBase range, object value, int row, int col) @@ -1270,7 +1270,11 @@ public bool Merge set { IsRangeValid("merging"); - _worksheet.MergedCells.Clear(this); + if (!_worksheet.DisableMergeValidation) + { + _worksheet.MergedCells.Clear(this); + } + if (value) { _worksheet.MergedCells.Add(new ExcelAddressBase(FirstAddress), true); @@ -1608,7 +1612,7 @@ private void SplitFormula(ExcelAddressBase address, int ix) //The formula is inside the currenct range, remove it if (collide == eAddressCollition.Equal || collide == eAddressCollition.Inside) { - _worksheet._sharedFormulas.Remove(ix); + // _worksheet._sharedFormulas.Remove(ix); return; //fRange.SetSharedFormulaID(int.MinValue); } @@ -2609,29 +2613,35 @@ public void Copy(ExcelRangeBase Destination, ExcelRangeCopyOptionFlags? excelRan } var copiedMergedCells = new Dictionary(); //Merged cells - var csem = new CellsStoreEnumerator(_worksheet.MergedCells._cells, _fromRow, _fromCol, _toRow, _toCol); - while (csem.Next()) + + for (var row = _fromRow; row <= _toRow; row++) { - if (!copiedMergedCells.ContainsKey(csem.Value)) + for (var col = _fromCol; col <= _toCol; col++) { - var adr = new ExcelAddress(_worksheet.Name, _worksheet.MergedCells.List[csem.Value]); - var collideResult = Collide(adr); - if (collideResult == eAddressCollition.Inside || collideResult == eAddressCollition.Equal) - { - copiedMergedCells.Add(csem.Value, new ExcelAddress( - Destination._fromRow + (adr.Start.Row - _fromRow), - Destination._fromCol + (adr.Start.Column - _fromCol), - Destination._fromRow + (adr.End.Row - _fromRow), - Destination._fromCol + (adr.End.Column - _fromCol))); - } - else + var key = _worksheet.GetMergeCellId(row, col) - 1; + if (key >= 0 && !copiedMergedCells.ContainsKey(key)) { - //Partial merge of the address ignore. - copiedMergedCells.Add(csem.Value, null); + var adr = new ExcelAddress(_worksheet.Name, _worksheet.MergedCells.List[key]); + var collideResult = Collide(adr); + if (collideResult == eAddressCollition.Inside || collideResult == eAddressCollition.Equal) + { + copiedMergedCells.Add( + key, + new ExcelAddress( + Destination._fromRow + (adr.Start.Row - _fromRow), + Destination._fromCol + (adr.Start.Column - _fromCol), + Destination._fromRow + (adr.End.Row - _fromRow), + Destination._fromCol + (adr.End.Column - _fromCol))); + } + else + { + //Partial merge of the address ignore. + copiedMergedCells.Add(key, null); + } } } } - + Destination._worksheet.MergedCells.Clear(new ExcelAddressBase(Destination._fromRow, Destination._fromCol, Destination._fromRow + toRow - 1, Destination._fromCol + toCol - 1)); Destination._worksheet._values.Clear(Destination._fromRow, Destination._fromCol, toRow, toCol); diff --git a/EPPlus/ExcelStyles.cs b/EPPlus/ExcelStyles.cs index 79708310..cfdc8a20 100644 --- a/EPPlus/ExcelStyles.cs +++ b/EPPlus/ExcelStyles.cs @@ -953,6 +953,7 @@ internal int CloneStyle(ExcelStyles style, int styleID, bool isNamedStyle, bool xfs = style.CellXfs[styleID]; } ExcelXfs newXfs = xfs.Copy(this); + newXfs.XfId = 0; //Numberformat if (xfs.NumberFormatId > 0) { @@ -1026,7 +1027,7 @@ internal int CloneStyle(ExcelStyles style, int styleID, bool isNamedStyle, bool } //Named style reference - if (xfs.XfId > 0) + if (isNamedStyle && xfs.XfId > 0) { var id = style.CellStyleXfs[xfs.XfId].Id; var newId = CellStyleXfs.FindIndexByID(id); diff --git a/EPPlus/ExcelWorkbook.cs b/EPPlus/ExcelWorkbook.cs index 0887bd35..f7880b24 100644 --- a/EPPlus/ExcelWorkbook.cs +++ b/EPPlus/ExcelWorkbook.cs @@ -175,6 +175,11 @@ internal void GetDefinedNames() { string fullAddress = elem.InnerText; + if(fullAddress.EndsWith("!#REF!")) + { + continue; + } + int localSheetID; ExcelWorksheet nameWorksheet; diff --git a/EPPlus/ExcelWorksheet.cs b/EPPlus/ExcelWorksheet.cs index ac1ff800..2a6e7e3d 100644 --- a/EPPlus/ExcelWorksheet.cs +++ b/EPPlus/ExcelWorksheet.cs @@ -185,13 +185,69 @@ public Formulas Clone() /// public class MergeCellsCollection : IEnumerable { - internal MergeCellsCollection() + private struct AddressDimensions { + internal int StartRow; + internal int StartCol; + + internal int EndRow; + + internal int EndCol; + + internal AddressDimensions(int startRow, int startCol, int endRow, int endCol) + { + StartRow = startRow; + StartCol = startCol; + EndRow = endRow; + EndCol = endCol; + } + } + + private readonly ExcelWorksheet _ws; + internal MergeCellsCollection(ExcelWorksheet ws) + { + _ws = ws; } internal CellStore _cells = new CellStore(); List _list = new List(); internal List List { get { return _list; } } + + private IDictionary _mergedAreasDimsCache = new Dictionary(); + + /// + /// Get MergeCell Index No + /// + /// + /// + /// + public int GetId(int row, int column) + { + for (int i = 0; i < _list.Count; i++) + { + var mergedAddress = _list[i]; + if(!string.IsNullOrEmpty( mergedAddress)) + { + if (!_mergedAreasDimsCache.ContainsKey(mergedAddress)) + { + ExcelCellBase.GetRowColFromAddress(mergedAddress, out var startRow, out var startColumn, out int toRow, out int toColumn); + _mergedAreasDimsCache.Add(mergedAddress, new AddressDimensions(startRow, startColumn, toRow, toColumn)); + } + + var mergedDims = _mergedAreasDimsCache[mergedAddress]; + + if (mergedDims.StartRow <= row && row <= mergedDims.EndRow) + { + if (mergedDims.StartCol <= column && column <= mergedDims.EndCol) + { + return i + 1; + } + } + } + } + return 0; + } + public string this[int row, int column] { get @@ -226,6 +282,15 @@ internal void Add(ExcelAddressBase address, bool doValidate) lock (this) { ix = _list.Count; + var addressDimension = new AddressDimensions(address.Start.Row, address.Start.Column, address.End.Row, address.End.Column); + if (!_mergedAreasDimsCache.ContainsKey(address.Address)) + { + _mergedAreasDimsCache.Add(address.Address, addressDimension); + } + else + { + _mergedAreasDimsCache[address.Address] = addressDimension; + } _list.Add(address.Address); SetIndex(address, ix); } @@ -324,29 +389,33 @@ System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() #endregion internal void Clear(ExcelAddressBase Destination) { - var cse = new CellsStoreEnumerator(_cells, Destination._fromRow, Destination._fromCol, Destination._toRow, Destination._toCol); var used = new HashSet(); - while (cse.Next()) + _cells.Clear(Destination._fromRow, Destination._fromCol, Destination._toRow - Destination._fromRow + 1, Destination._toCol - Destination._fromCol + 1); + for (var row = Destination._fromRow; row <= Destination._toRow; row++) { - var v = cse.Value; - if (!used.Contains(v) && _list[v] != null) + for (var col = Destination._fromCol; col <= Destination._toCol; col++) { - var adr = new ExcelAddressBase(_list[v]); - if (!(Destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Inside || Destination.Collide(adr) == ExcelAddressBase.eAddressCollition.Equal)) + var mergedRangeId = GetId(row, col); + + if (mergedRangeId == 0 || used.Contains(mergedRangeId)) { - throw (new InvalidOperationException(string.Format("Can't delete/overwrite merged cells. A range is partly merged with the another merged range. {0}", adr._address))); + continue; } - 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; + used.Add(mergedRangeId); + // из метода GetId возвращается индекс в списке _list + 1, т.к. результат 0 означает, что значение в списке не найдено + // поэтому, чтобы восстановить исходный индекс, передаем mergedRangeId-1 + var mergedRangeAddress = new ExcelAddressBase(_list[mergedRangeId-1]); + if (Destination.Collide(mergedRangeAddress) == ExcelAddressBase.eAddressCollition.Partly) + { + throw (new InvalidOperationException(string.Format("Can't delete/overwrite merged cells. A range is partly merged with the another merged range. {0}", mergedRangeAddress._address))); + } + _list[mergedRangeId-1] = null; + } } } } + public bool DisableMergeValidation { get; set; } //internal CellStore _values; //internal CellStore _types; //internal CellStore _styles; @@ -396,6 +465,7 @@ public ExcelWorksheet(XmlNamespaceManager ns, ExcelPackage excelPackage, string _name = sheetName; _sheetID = sheetID; _positionID = positionID; + _mergedCells = new MergeCellsCollection(this); Hidden = hide; /**** Cellstore ****/ @@ -1664,7 +1734,7 @@ public ExcelRange SelectedRange return new ExcelRange(this, View.SelectedRange); } } - MergeCellsCollection _mergedCells = new MergeCellsCollection(); + MergeCellsCollection _mergedCells; /// /// Addresses to merged ranges /// @@ -2524,8 +2594,27 @@ public void DeleteRow(int rowFrom, int rows) { throw(new ArgumentException("Row out of range. Spans from 1 to " + ExcelPackage.MaxRows.ToString(CultureInfo.InvariantCulture))); } + lock (this) { + var commentsDictionary = new List(); + + foreach (ExcelComment excelComment in Comments) + { + var commentRow = Cells[excelComment.Reference].Start.Row; + if (commentRow < rowFrom) + { + continue; + } + + commentsDictionary.Add(excelComment); + } + + foreach (var c in commentsDictionary) + { + Comments.Remove(c); + } + _values.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); _formulas.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); _flags.Delete(rowFrom, 0, rows, ExcelPackage.MaxColumns); @@ -2543,6 +2632,7 @@ public void DeleteRow(int rowFrom, int rows) { tbl.Address = tbl.Address.DeleteRow(rowFrom, rows); } + foreach (var ptbl in PivotTables) { if (ptbl.Address.Start.Row > rowFrom + rows) @@ -2550,6 +2640,7 @@ public void DeleteRow(int rowFrom, int rows) ptbl.Address = ptbl.Address.DeleteRow(rowFrom, rows); } } + //Issue 15573 foreach (ExcelDataValidation dv in DataValidations) { @@ -2563,6 +2654,20 @@ public void DeleteRow(int rowFrom, int rows) } } } + + foreach (var commentToShift in commentsDictionary) + { + var commentTarget = Cells[commentToShift.Range.Start.Row - rows, commentToShift.Range.Start.Column]; + var existingComment = commentTarget.Comment; + if (existingComment != null) + { + existingComment.Text = commentToShift.Text; + } + else + { + commentTarget.AddComment(commentToShift.Text, commentToShift.Author); + } + } } } /// @@ -2863,22 +2968,7 @@ public void SetValue(string Address, object Value) /// 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; + return _mergedCells.GetId(row, column); } #endregion diff --git a/EPPlus/ExcelWorksheets.cs b/EPPlus/ExcelWorksheets.cs index 715d4693..82fbe0f6 100644 --- a/EPPlus/ExcelWorksheets.cs +++ b/EPPlus/ExcelWorksheets.cs @@ -153,7 +153,7 @@ public ExcelWorksheet Add(string Name) ExcelWorksheet worksheet = AddSheet(Name,false, null); return worksheet; } - private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType, ExcelPivotTable pivotTableSource = null) + private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType, ExcelPivotTable pivotTableSource = null, bool createVbaModule = false) { int sheetID; Uri uriWorksheet; @@ -187,7 +187,7 @@ private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType } _worksheets.Add(positionID, worksheet); - if (_pck.Workbook.VbaProject != null) + if (createVbaModule && _pck.Workbook.VbaProject != null) { var name = _pck.Workbook.VbaProject.GetModuleNameFromWorksheet(worksheet); _pck.Workbook.VbaProject.Modules.Add(new ExcelVBAModule(worksheet.CodeNameChange) { Name = name, Code = "", Attributes = _pck.Workbook.VbaProject.GetDocumentAttributes(Name, "0{00020820-0000-0000-C000-000000000046}"), Type = eModuleType.Document, HelpContext = 0 }); @@ -202,7 +202,7 @@ private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType /// /// The name of the workbook /// The worksheet to be copied - public ExcelWorksheet Add(string Name, ExcelWorksheet Copy) + public ExcelWorksheet Add(string Name, ExcelWorksheet Copy, bool copyVba= false) { lock (_worksheets) { @@ -268,7 +268,7 @@ public ExcelWorksheet Add(string Name, ExcelWorksheet Copy) CloneCells(Copy, added); //Copy the VBA code - if (_pck.Workbook.VbaProject != null) + if (copyVba && _pck.Workbook.VbaProject != null) { var name = _pck.Workbook.VbaProject.GetModuleNameFromWorksheet(added); _pck.Workbook.VbaProject.Modules.Add(new ExcelVBAModule(added.CodeNameChange) { Name = name, Code = Copy.CodeModule.Code, Attributes = _pck.Workbook.VbaProject.GetDocumentAttributes(Name, "0{00020820-0000-0000-C000-000000000046}"), Type = eModuleType.Document, HelpContext = 0 }); diff --git a/EPPlus/Properties/AssemblyInfo.cs b/EPPlus/Properties/AssemblyInfo.cs index b5c8c7fc..1c3c0588 100644 --- a/EPPlus/Properties/AssemblyInfo.cs +++ b/EPPlus/Properties/AssemblyInfo.cs @@ -46,7 +46,7 @@ // // Major Version // Minor Version -// Build Number +// Build NumberS // Revision // // You can specify all the values or you can default the Revision and Build Numbers @@ -62,7 +62,7 @@ //[assembly: AssemblyCulture("")] //[assembly: ComVisible(false)] - //[assembly: AssemblyVersion("4.5.0.0")] - //[assembly: AssemblyFileVersion("4.5.0.0")] + [assembly: AssemblyVersion("4.5.3.9")] + [assembly: AssemblyFileVersion("4.5.3.9")] #endif [assembly: AllowPartiallyTrustedCallers] \ No newline at end of file