diff --git a/src/EPPlus/Drawing/ExcelDrawing.cs b/src/EPPlus/Drawing/ExcelDrawing.cs index 1778afb660..6707a0d5c0 100644 --- a/src/EPPlus/Drawing/ExcelDrawing.cs +++ b/src/EPPlus/Drawing/ExcelDrawing.cs @@ -21,7 +21,6 @@ Date Author Change using OfficeOpenXml.Utils.FileUtils; using OfficeOpenXml.Utils.XML; using System; -using System.Drawing; using System.Globalization; using System.IO; using System.Linq; @@ -583,6 +582,9 @@ internal static ExcelDrawing GetDrawing(ExcelDrawings drawings, XmlNode node, Dr internal static ExcelDrawing GetDrawingFromNode(ExcelDrawings drawings, XmlNode node, XmlElement drawNode, ExcelGroupShape parent = null, DrawingsCollectionType DrawingsType = DrawingsCollectionType.Worksheet) { + string fallbackDrawingPath = ""; + string fallbackNvPrPath = ""; + switch (drawNode.LocalName) { case "sp": @@ -591,11 +593,19 @@ internal static ExcelDrawing GetDrawingFromNode(ExcelDrawings drawings, XmlNode var aPic = new ExcelPicture(drawings, node, parent, DrawingsType); return aPic; case "graphicFrame": - var c= ExcelChart.GetChart(drawings, node, parent); - if(c!=null) //If null, the drawing is not a chart. Might be a smart art, diagram or 3d model. We return a standard drawing to retain the drawing. + var c = ExcelChart.GetChart(drawings, node, parent); + if (c!=null) //If null, the drawing is not a chart. Might be a smart art, diagram or 3d model. We return a standard drawing to retain the drawing. { return c; } + else + { + //While we do not know the exact type. + //It's a standard drawing with a graphic frame + //We assume the object has its name etc. in the same nodes as a chart + fallbackDrawingPath = "xdr:graphicFrame"; + fallbackNvPrPath = "xdr:nvGraphicFramePr/xdr:cNvPr"; + } break; case "grpSp": return new ExcelGroupShape(drawings, node, parent, DrawingsType); @@ -644,7 +654,7 @@ internal static ExcelDrawing GetDrawingFromNode(ExcelDrawings drawings, XmlNode } break; } - return new ExcelDrawing(drawings, node, "", "",parent, DrawingsType); + return new ExcelDrawing(drawings, node, fallbackDrawingPath, fallbackNvPrPath, parent, DrawingsType); } private static ExcelDrawing GetShapeOrControl(ExcelDrawings drawings, XmlNode node, XmlElement drawNode, ExcelGroupShape parent, DrawingsCollectionType collectionType = DrawingsCollectionType.Worksheet) diff --git a/src/EPPlus/Drawing/ExcelGroupShape.cs b/src/EPPlus/Drawing/ExcelGroupShape.cs index c8a4644b26..2371c414d8 100644 --- a/src/EPPlus/Drawing/ExcelGroupShape.cs +++ b/src/EPPlus/Drawing/ExcelGroupShape.cs @@ -47,7 +47,6 @@ private void AddDrawings() _groupDrawings = new List(); foreach (XmlNode node in _topNode.ChildNodes) { - if (node.LocalName != "nvGrpSpPr" && node.LocalName != "grpSpPr") { var grpDraw = ExcelDrawing.GetDrawingFromNode(_parent._drawings, node, (XmlElement)node, _parent, _drawingsCollectionType); @@ -645,6 +644,9 @@ internal override void SaveDrawing(bool hasLoadedPivotTables) foreach (var d in Drawings) { + //Ensure position on underlying drawings are updated + d.AdjustPositionAndSize(); + d.UpdatePositionAndSizeXml(); d.SaveDrawing(hasLoadedPivotTables); } } diff --git a/src/EPPlus/ExcelRangeBase.cs b/src/EPPlus/ExcelRangeBase.cs index a681380e37..3fb4a4bf37 100644 --- a/src/EPPlus/ExcelRangeBase.cs +++ b/src/EPPlus/ExcelRangeBase.cs @@ -10,29 +10,31 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ -using System; -using System.Collections.Generic; -using OfficeOpenXml.FormulaParsing; -using OfficeOpenXml.Style; -using System.Globalization; -using System.Collections; -using OfficeOpenXml.Table; -using OfficeOpenXml.DataValidation; +using OfficeOpenXml.CellPictures; using OfficeOpenXml.ConditionalFormatting; -using OfficeOpenXml.FormulaParsing.LexicalAnalysis; using OfficeOpenXml.Core; using OfficeOpenXml.Core.CellStore; using OfficeOpenXml.Core.Worksheet; -using OfficeOpenXml.ThreadedComments; -using OfficeOpenXml.CellPictures; -using OfficeOpenXml.Sorting; +using OfficeOpenXml.DataValidation; using OfficeOpenXml.Export.HtmlExport.Interfaces; +using OfficeOpenXml.FormulaParsing; using OfficeOpenXml.FormulaParsing.Excel.Functions; -using OfficeOpenXml.Utils.TypeConversion; -using OfficeOpenXml.Utils.String; +using OfficeOpenXml.FormulaParsing.Excel.Functions.DateAndTime; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +using OfficeOpenXml.FormulaParsing.LexicalAnalysis; +using OfficeOpenXml.Sorting; +using OfficeOpenXml.Style; +using OfficeOpenXml.Table; +using OfficeOpenXml.ThreadedComments; using OfficeOpenXml.Utils.Cell; -using static OfficeOpenXml.ExcelWorksheet; +using OfficeOpenXml.Utils.String; +using OfficeOpenXml.Utils.TypeConversion; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; using System.Linq; +using static OfficeOpenXml.ExcelWorksheet; namespace OfficeOpenXml { @@ -1330,8 +1332,10 @@ public bool IsRichText var isRt = _worksheet._flags.GetFlagValue(_fromRow, _fromCol, CellFlags.RichText); if (isRt) { - _rtc = _worksheet.GetRichText(_fromRow, _fromCol, this); - return _rtc.Count > 0; + //Do not update _rtc. This is a boolean getter. + //It should not set any values even if they diff. + var couldBeEmptyRT = _worksheet.GetRichText(_fromRow, _fromCol, this); + return couldBeEmptyRT.Count > 0; } return isRt; } @@ -1457,6 +1461,16 @@ public ExcelRichTextCollection RichText } } + /// + /// Convert the contents of the top left cell of this range to a richtext string + /// And set the cell value as .RichText + /// + public ExcelRichTextCollection ConvertToRichText() + { + _rtc = _worksheet.ConvertCellValueToRichText(_fromRow, _fromCol, this); + return _rtc; + } + /// /// Returns the comment object of the first cell in the range /// diff --git a/src/EPPlus/ExcelWorksheet.cs b/src/EPPlus/ExcelWorksheet.cs index f0ebe3dec3..d45a7c1aa5 100644 --- a/src/EPPlus/ExcelWorksheet.cs +++ b/src/EPPlus/ExcelWorksheet.cs @@ -1515,10 +1515,12 @@ private void LoadHyperLinks(XmlReader xr) delRelIds.ToList().ForEach(x => Part.DeleteRelationship(x)); } - internal ExcelRichTextCollection GetRichText(int row, int col, ExcelRangeBase r = null) + internal ExcelRichTextCollection ConvertCellValueToRichText(int row, int col, ExcelRangeBase r = null) { var v = GetCoreValueInner(row, col); var isRt = _flags.GetFlagValue(row, col, CellFlags.RichText); + + //If it already is rt then no need to actually convert if (isRt && v._value is ExcelRichTextCollection rtc) { if (rtc._cells == null) rtc._cells = r; @@ -1526,19 +1528,42 @@ internal ExcelRichTextCollection GetRichText(int row, int col, ExcelRangeBase r } else { - var text = ValueToTextHandler.GetFormattedText(v._value, Workbook, v._styleId, false); - if (string.IsNullOrEmpty(text)) + object textValue = v._value; + if (textValue != null && typeof(ExcelRichTextCollection) == textValue.GetType()) { - var item = new ExcelRichTextCollection(Workbook, r); - SetValue(row, col, item); - return item; + textValue = ((ExcelRichTextCollection)textValue).Text; } - else + + var text = ValueToTextHandler.GetFormattedText(textValue, Workbook, v._styleId, false); + var item = new ExcelRichTextCollection(text, r); + SetValue(row, col, item); + + return item; + } + } + + internal ExcelRichTextCollection GetRichText(int row, int col, ExcelRangeBase r = null) + { + var v = GetCoreValueInner(row, col); + var isRt = _flags.GetFlagValue(row, col, CellFlags.RichText); + if (isRt && v._value is ExcelRichTextCollection rtc) + { + if (rtc._cells == null) rtc._cells = r; + return rtc; + } + else + { + object textValue = v._value; + if (textValue != null && typeof(ExcelRichTextCollection) == textValue.GetType()) { - var item = new ExcelRichTextCollection(text, r); - SetValue(row, col, item); - return item; + //This should only really happen if e.g. rich-text from a Note is Set to a cell value + textValue = ((ExcelRichTextCollection)textValue).Text; + var text = ValueToTextHandler.GetFormattedText(textValue, Workbook, v._styleId, false); + var itemRt = new ExcelRichTextCollection(text, r); + return itemRt; } + var item = new ExcelRichTextCollection(Workbook, r); + return item; } } @@ -2287,7 +2312,7 @@ public object GetValue(int Row, int Column) var v = GetValueInner(Row, Column); if (v != null) { - if (_flags.GetFlagValue(Row, Column, CellFlags.RichText)) + if (_flags.GetFlagValue(Row, Column, CellFlags.RichText) && Cells[Row, Column].IsRichText) { return (object)Cells[Row, Column].RichText.Text; } diff --git a/src/EPPlus/Style/RichText/ExcelRichTextCollection.cs b/src/EPPlus/Style/RichText/ExcelRichTextCollection.cs index de032100a6..78c0687103 100644 --- a/src/EPPlus/Style/RichText/ExcelRichTextCollection.cs +++ b/src/EPPlus/Style/RichText/ExcelRichTextCollection.cs @@ -10,19 +10,21 @@ Date Author Change ************************************************************************************************* 01/27/2020 EPPlus Software AB Initial release EPPlus 5 *************************************************************************************************/ +using OfficeOpenXml.Drawing.Style.Coloring; +using OfficeOpenXml.FormulaParsing.Excel.Functions.DateAndTime; +using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Statistical; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; +using OfficeOpenXml.Utils.String; +using OfficeOpenXml.Utils.TypeConversion; +using OfficeOpenXml.Utils.XML; using System; using System.Collections.Generic; +using System.Drawing; +using System.Globalization; using System.Linq; using System.Text; using System.Xml; -using System.Drawing; -using System.Globalization; -using OfficeOpenXml.Drawing.Style.Coloring; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Statistical; -using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; -using OfficeOpenXml.FormulaParsing.Excel.Functions.Text; -using OfficeOpenXml.Utils.XML; -using OfficeOpenXml.Utils.TypeConversion; namespace OfficeOpenXml.Style { @@ -40,7 +42,7 @@ internal ExcelRichTextCollection(ExcelWorkbook wb, ExcelRangeBase cells) { _wb = wb; _cells = cells; - _cells._worksheet._flags.SetFlagValue(_cells._fromRow, _cells._fromCol, true, CellFlags.RichText); + //_cells._worksheet._flags.SetFlagValue(_cells._fromRow, _cells._fromCol, true, CellFlags.RichText); } internal ExcelRichTextCollection(string s, ExcelRangeBase cells) @@ -162,6 +164,15 @@ public ExcelRichText Insert(int index, string text) { CheckDeleted(); if (text == null) throw new ArgumentException("Text can't be null", "text"); + + //If just a note we can't clear formulas on the cell itself. + if (_isComment == false) + { + //We MUST clear formulas before setting richtext + //To ensure calculate does not create missmatch between formula and richtext. + _cells.ClearFormulas(); + } + var rt = new ExcelRichText(text, this); rt.PreserveSpace = true; int prevIndex = 0; @@ -204,8 +215,19 @@ public ExcelRichText Insert(int index, string text) { rt.Color = Color.FromArgb(hex); } + + //If just a note we cannot set rich text flag on the cell itself. if (_isComment == false) { + //If the range value is not richtext it is now. + //Must set value first in order to not overwrite flags after. + if (_cells.Value != this) + { + //var flags = _cells._worksheet._flags; + _cells._worksheet._flags.GetFlagValue(_cells._fromRow, _cells._fromCol, CellFlags.RichText); + _cells.Value = this; + } + //If not a note then we are a cell and can set the flag. _cells._worksheet._flags.SetFlagValue(_cells._fromRow, _cells._fromCol, true, CellFlags.RichText); } } @@ -220,6 +242,8 @@ public void Clear() { CheckDeleted(); _list.Clear(); + _cells._worksheet._flags.SetFlagValue(_cells._fromRow, _cells._fromCol, false, CellFlags.RichText); + _cells.Value = string.Empty; } /// /// Removes an item at the specific index diff --git a/src/EPPlusTest/Core/Range/RangeRichTextTests.cs b/src/EPPlusTest/Core/Range/RangeRichTextTests.cs index 03aa3181d0..abe7f5d0e5 100644 --- a/src/EPPlusTest/Core/Range/RangeRichTextTests.cs +++ b/src/EPPlusTest/Core/Range/RangeRichTextTests.cs @@ -191,7 +191,7 @@ public void VerifyRichTextIsBlankIfAccess() var ws = p.Workbook.Worksheets.Add("Sheet1"); var t = ws.Cells["A1"].RichText.Text; - Assert.AreEqual(string.Empty, ws.Cells["A1"].Value); + Assert.AreEqual(null, ws.Cells["A1"].Value); } } [TestMethod] diff --git a/src/EPPlusTest/Issues/ChartIssues.cs b/src/EPPlusTest/Issues/ChartIssues.cs index 0a8ff0e91c..9bdf8d1f94 100644 --- a/src/EPPlusTest/Issues/ChartIssues.cs +++ b/src/EPPlusTest/Issues/ChartIssues.cs @@ -451,7 +451,13 @@ public void s1038() //Crashes when accessesing var drawingsWithinGroupShape = groupShape.Drawings; - SaveAndCleanup(package); + var firstDrawing = drawingsWithinGroupShape[0]; + + firstDrawing.SetPosition(500, 500); + //drawingsWithinGroupShape[0].Position.Y = 2000; + + + SaveAndCleanup(package); } } diff --git a/src/EPPlusTest/Issues/RichDataIssues.cs b/src/EPPlusTest/Issues/RichDataIssues.cs index d3e14bab44..3de76fa645 100644 --- a/src/EPPlusTest/Issues/RichDataIssues.cs +++ b/src/EPPlusTest/Issues/RichDataIssues.cs @@ -1,9 +1,11 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; +using OfficeOpenXml.Style; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Text; -using OfficeOpenXml; namespace EPPlusTest.Issues { @@ -85,5 +87,40 @@ public void VerifyCalcError() SaveAndCleanup(p); } } + + [TestMethod] + public void AddingRichTextToFormulasShouldOverwrite() + { + using (var pck = OpenPackage("dirtyRT.xlsx", true)) + { + var ws = pck.Workbook.Worksheets.Add("richText"); + + ws.Cells["A1"].Value = 1001.1d; + ws.Cells["C1"].Formula = "ROUND(A1, 1)"; + ws.Cells["B1"].Formula = "\"My favorite number is: \"&TEXT(ROUND(A1,1),\"#,##0.00;(#,##0.00)\")"; + + ws.Cells["B1"].RichText.Add("My favorite number is: 1001.1"); + + var myFormula = ws.Cells["B1"].Formula; + + Assert.IsTrue(string.IsNullOrEmpty(myFormula)); + } + } + + [TestMethod] + public void RtComment() + { + using(var p = OpenTemplatePackage("RtWithOldComment.xlsx")) + { + var ws = p.Workbook.Worksheets[0]; + + var isRichtext = ws.Cells["B3"]; + var text = ws.Cells["B3"].RichText; + var rtComment = ws.Cells["B3"].Comment; + var rtFromComment = rtComment.RichText; + + Assert.AreNotEqual(text.Text, rtFromComment.Text); + } + } } } diff --git a/src/EPPlusTest/Issues/StylingIssues.cs b/src/EPPlusTest/Issues/StylingIssues.cs index 1082c31630..737e22603a 100644 --- a/src/EPPlusTest/Issues/StylingIssues.cs +++ b/src/EPPlusTest/Issues/StylingIssues.cs @@ -468,7 +468,8 @@ public void s1005() Assert.IsTrue(origText.Contains("69928453.64000")); ws1.Cells["D8"].Calculate(); - var cellRich = ws1.Cells["D8"].RichText.Text; + + var cellRich = ws1.Cells["D8"].ConvertToRichText().Text; //Verify formatting has changed appropriately for calculated string Assert.IsTrue(cellRich.Contains("0,80")); diff --git a/src/EPPlusTest/Style/RichTextTest.cs b/src/EPPlusTest/Style/RichTextTest.cs index f5f35c8181..48a2ab3355 100644 --- a/src/EPPlusTest/Style/RichTextTest.cs +++ b/src/EPPlusTest/Style/RichTextTest.cs @@ -370,5 +370,46 @@ public void CheckRichTextProperties() Assert.AreEqual("Arial", C4.RichText[1].FontName); } + + [TestMethod] + public void issue2214() + { + var p = new ExcelPackage(); + + //Ensure that 'getting' any properties does not change type + var ws = p.Workbook.Worksheets.Add("Sheet1"); + ws.Cells["A1"].Value = 1D; + + Assert.IsInstanceOfType(ws.Cells["A1"].Value, typeof(double)); + var r = ws.Cells["A1"].RichText; + Assert.IsInstanceOfType(ws.Cells["A1"].Value, typeof(double)); + + var test = r.Text; + Assert.IsInstanceOfType(ws.Cells["A1"].Value, typeof(double)); + Assert.IsFalse(ws.Cells["A1"].IsRichText); + + //Assert that Setting the property does. + string text = "Hello"; + r.Text = text; + + Assert.IsTrue(ws.Cells["A1"].IsRichText); + Assert.IsInstanceOfType(ws.Cells["A1"].Value, typeof(string)); + Assert.AreEqual(text, (string)ws.Cells["A1"].Value); + } + + [TestMethod] + public void ConvertCellToRichText() + { + var p = new ExcelPackage(); + var ws = p.Workbook.Worksheets.Add("Sheet1"); + ws.Cells["A1"].Value = 1D; + + ws.Cells["A1"].ConvertToRichText(); + + Assert.IsTrue(ws.Cells["A1"].IsRichText); + Assert.IsInstanceOfType(ws.Cells["A1"].Value, typeof(string)); + Assert.AreEqual("1", (string)ws.Cells["A1"].Value); + Assert.AreEqual("1", ws.Cells["A1"].RichText.Text); + } } }