From e7230ed0450ff9985b4c8a159f74ca1c348d6e67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= <982881+JanKallman@users.noreply.github.com> Date: Fri, 29 Jun 2018 12:07:15 +0200 Subject: [PATCH 1/5] Fixed issue #220,#233,#234,#236. Added nuget config files --- .gitignore | 1 + .nuget/NuGet.Config | 6 + .nuget/NuGet.targets | 151 ++++++++++++++++++ EPPlus/ExcelCellBase.cs | 57 +++---- EPPlus/ExcelNamedRange.cs | 1 - EPPlus/ExcelNamedRangeCollection.cs | 5 + EPPlus/ExcelRangeBase.cs | 66 ++++---- EPPlus/ExcelWorksheet.cs | 6 +- .../ExcelUtilities/ExcelAddressUtil.cs | 43 +++++ EPPlus/Packaging/ZipPackage.cs | 6 +- EPPlus/Style/XmlAccess/ExcelFontXml.cs | 12 +- EPPlus/Table/ExcelTable.cs | 7 +- EPPlus/Table/ExcelTableCollection.cs | 18 ++- EPPlusTest/Address.cs | 16 +- EPPlusTest/Issues.cs | 120 +++++++++++++- EPPlusTest/TestBase.cs | 6 +- EPPlusTest/WorkSheet.cs | 12 +- 17 files changed, 433 insertions(+), 100 deletions(-) create mode 100644 .nuget/NuGet.Config create mode 100644 .nuget/NuGet.targets diff --git a/.gitignore b/.gitignore index 2b991b64..f0b42eb0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,6 +34,7 @@ obj/ Ankh.NoLoad *.[Pp]ublish.xml .vs/ +.nuget/nuget.exe #Tooling _ReSharper*/ diff --git a/.nuget/NuGet.Config b/.nuget/NuGet.Config new file mode 100644 index 00000000..67f8ea04 --- /dev/null +++ b/.nuget/NuGet.Config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.nuget/NuGet.targets b/.nuget/NuGet.targets new file mode 100644 index 00000000..02296949 --- /dev/null +++ b/.nuget/NuGet.targets @@ -0,0 +1,151 @@ + + + + $(MSBuildProjectDirectory)\..\ + + + false + + + false + + + true + + + true + + + + + + + + + + + $([System.IO.Path]::Combine($(SolutionDir), ".nuget")) + $([System.IO.Path]::Combine($(ProjectDir), "packages.config")) + + + + + $(SolutionDir).nuget + packages.config + + + + + $(NuGetToolsPath)\nuget.exe + @(PackageSource) + + "$(NuGetExePath)" + mono --runtime=v4.0.30319 $(NuGetExePath) + + $(TargetDir.Trim('\\')) + + -RequireConsent + + $(NuGetCommand) install "$(PackagesConfig)" -source "$(PackageSources)" $(RequireConsentSwitch) -solutionDir "$(SolutionDir) " + $(NuGetCommand) pack "$(ProjectPath)" -p Configuration=$(Configuration) -o "$(PackageOutputDir)" -symbols + + + + RestorePackages; + $(BuildDependsOn); + + + + + $(BuildDependsOn); + BuildPackage; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/EPPlus/ExcelCellBase.cs b/EPPlus/ExcelCellBase.cs index c3bb2e0d..476d95b6 100644 --- a/EPPlus/ExcelCellBase.cs +++ b/EPPlus/ExcelCellBase.cs @@ -758,34 +758,35 @@ public static string GetFullAddress(string worksheetName, string address) } internal static string GetFullAddress(string worksheetName, string address, bool fullRowCol) { - if (address.IndexOf("!") == -1 || address=="#REF!") - { - if (fullRowCol) - { - string[] cells = address.Split(':'); - if (cells.Length > 0) - { - address = string.Format("'{0}'!{1}", worksheetName, cells[0]); - if (cells.Length > 1) - { - address += string.Format(":{0}", cells[1]); - } - } - } - else - { - var a = new ExcelAddressBase(address); - if ((a._fromRow == 1 && a._toRow == ExcelPackage.MaxRows) || (a._fromCol == 1 && a._toCol == ExcelPackage.MaxColumns)) - { - address = string.Format("'{0}'!{1}{2}:{3}{4}", worksheetName, ExcelAddress.GetColumnLetter(a._fromCol), a._fromRow, ExcelAddress.GetColumnLetter(a._toCol), a._toRow); - } - else - { - address=GetFullAddress(worksheetName, address, true); - } - } - } - return address; + if(!string.IsNullOrEmpty(worksheetName)) worksheetName = worksheetName.Replace("'", "''"); //Makesure addresses handle single qoutes + if (address.IndexOf("!") == -1 || address=="#REF!") + { + if (fullRowCol) + { + string[] cells = address.Split(':'); + if (cells.Length > 0) + { + address = string.Format("'{0}'!{1}", worksheetName, cells[0]); + if (cells.Length > 1) + { + address += string.Format(":{0}", cells[1]); + } + } + } + else + { + var a = new ExcelAddressBase(address); + if ((a._fromRow == 1 && a._toRow == ExcelPackage.MaxRows) || (a._fromCol == 1 && a._toCol == ExcelPackage.MaxColumns)) + { + address = string.Format("'{0}'!{1}{2}:{3}{4}", worksheetName, ExcelAddress.GetColumnLetter(a._fromCol), a._fromRow, ExcelAddress.GetColumnLetter(a._toCol), a._toRow); + } + else + { + address=GetFullAddress(worksheetName, address, true); + } + } + } + return address; } #endregion #region IsValidCellAddress diff --git a/EPPlus/ExcelNamedRange.cs b/EPPlus/ExcelNamedRange.cs index ab1b1763..fc17999d 100644 --- a/EPPlus/ExcelNamedRange.cs +++ b/EPPlus/ExcelNamedRange.cs @@ -119,6 +119,5 @@ public override string ToString() { return Name; } - } } diff --git a/EPPlus/ExcelNamedRangeCollection.cs b/EPPlus/ExcelNamedRangeCollection.cs index 16e600c1..43a98549 100644 --- a/EPPlus/ExcelNamedRangeCollection.cs +++ b/EPPlus/ExcelNamedRangeCollection.cs @@ -34,6 +34,7 @@ using System.Text; using System.Collections; using System.Linq; +using OfficeOpenXml.FormulaParsing.ExcelUtilities; namespace OfficeOpenXml { @@ -65,6 +66,10 @@ internal ExcelNamedRangeCollection(ExcelWorkbook wb, ExcelWorksheet ws) public ExcelNamedRange Add(string Name, ExcelRangeBase Range) { ExcelNamedRange item; + if(!ExcelAddressUtil.IsValidName(Name)) + { + throw (new ArgumentException("Name contains invalid characters")); + } if (Range.IsName) { diff --git a/EPPlus/ExcelRangeBase.cs b/EPPlus/ExcelRangeBase.cs index 53d3c17e..046f41c9 100644 --- a/EPPlus/ExcelRangeBase.cs +++ b/EPPlus/ExcelRangeBase.cs @@ -2084,49 +2084,37 @@ public ExcelRangeBase LoadFromCollection(IEnumerable Collection, bool Prin return null; } - //if (Members.Length == 0) - //{ - // foreach (var item in Collection) - // { - // //_worksheet.Cells[row++, col].Value = item; - // values[row, col++] = item; - // } - //} - //else - //{ - foreach (var item in Collection) - { - col = 0; - if (item is string || item is decimal || item is DateTime || TypeCompat.IsPrimitive(item)) - { - //_worksheet.Cells[row, col++].Value = item; - values[row, col++] = item; - } - else + foreach (var item in Collection) + { + col = 0; + if (item is string || item is decimal || item is DateTime || TypeCompat.IsPrimitive(item)) + { + values[row, col++] = item; + } + else + { + foreach (var t in Members) { - foreach (var t in Members) + if (isSameType == false && item.GetType().GetMember(t.Name, memberFlags).Length == 0) { - if (isSameType == false && item.GetType().GetMember(t.Name, memberFlags).Length == 0) - { - col++; - continue; //Check if the property exists if and inherited class is used - } - else if (t is PropertyInfo) - { - values[row, col++] = ((PropertyInfo)t).GetValue(item, null); - } - else if (t is FieldInfo) - { - values[row, col++] = ((FieldInfo)t).GetValue(item); - } - else if (t is MethodInfo) - { - values[row, col++] = ((MethodInfo)t).Invoke(item, null); - } + col++; + continue; //Check if the property exists if and inherited class is used + } + else if (t is PropertyInfo) + { + values[row, col++] = ((PropertyInfo)t).GetValue(item, null); + } + else if (t is FieldInfo) + { + values[row, col++] = ((FieldInfo)t).GetValue(item); + } + else if (t is MethodInfo) + { + values[row, col++] = ((MethodInfo)t).Invoke(item, null); } } - row++; - //} + } + row++; } _worksheet.SetRangeValueInner(_fromRow, _fromCol, _fromRow + row - 1, _fromCol + col - 1, values); diff --git a/EPPlus/ExcelWorksheet.cs b/EPPlus/ExcelWorksheet.cs index 472f44bf..18a72327 100644 --- a/EPPlus/ExcelWorksheet.cs +++ b/EPPlus/ExcelWorksheet.cs @@ -3104,7 +3104,8 @@ private void SaveComments() { if (_comments.Uri == null) { - _comments.Uri=new Uri(string.Format(@"/xl/comments{0}.xml", SheetID), UriKind.Relative); + var id = SheetID; + _comments.Uri = XmlHelper.GetNewUri(_package.Package, @"/xl/comments{0}.xml", ref id); //Issue 236-Part already exists fix } if(_comments.Part==null) { @@ -3129,7 +3130,8 @@ private void SaveComments() { if (_vmlDrawings.Uri == null) { - _vmlDrawings.Uri = XmlHelper.GetNewUri(_package.Package, @"/xl/drawings/vmlDrawing{0}.vml"); + var id = SheetID; + _vmlDrawings.Uri = XmlHelper.GetNewUri(_package.Package, @"/xl/drawings/vmlDrawing{0}.vml", ref id); } if (_vmlDrawings.Part == null) { diff --git a/EPPlus/FormulaParsing/ExcelUtilities/ExcelAddressUtil.cs b/EPPlus/FormulaParsing/ExcelUtilities/ExcelAddressUtil.cs index 9dc51572..bbe3edd5 100644 --- a/EPPlus/FormulaParsing/ExcelUtilities/ExcelAddressUtil.cs +++ b/EPPlus/FormulaParsing/ExcelUtilities/ExcelAddressUtil.cs @@ -32,6 +32,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Text.RegularExpressions; namespace OfficeOpenXml.FormulaParsing.ExcelUtilities { @@ -67,5 +68,47 @@ public static bool IsValidAddress(string token) } return OfficeOpenXml.ExcelAddress.IsValidAddress(token); } + readonly static char[] NameInvalidChars = new char[] { '!', '@', '#', '$', '£', '%', '&', '/', '(', ')', '[', ']', '{', '}', '<', '>', '=', '+', '?', '\\', '*', '-', '~', '^', ':', ';', '|', ',', ' ' }; + public static bool IsValidName(string name) + { + if (string.IsNullOrEmpty(name)) + { + return false; + } + var fc = name[0]; + if (!(char.IsLetter(fc) || fc == '_' || (fc == '\\' && name.Length > 2))) + { + return false; + } + + if (name.IndexOfAny(NameInvalidChars, 1) > 0) + { + return false; + } + + if(ExcelCellBase.IsValidAddress(name)) + { + return false; + } + + //TODO:Add check for functionnames. + return true; + } + public static string GetValidName(string name) + { + if (string.IsNullOrEmpty(name)) + { + return name; + } + + var fc = name[0]; + if (!(char.IsLetter(fc) || fc == '_' || (fc == '\\' && name.Length > 2))) + { + name = "_" + name.Substring(1); + } + + name=NameInvalidChars.Aggregate(name, (c1, c2) => c1.Replace(c2, '_')); + return name; + } } } diff --git a/EPPlus/Packaging/ZipPackage.cs b/EPPlus/Packaging/ZipPackage.cs index fc0cb25b..38ce3035 100644 --- a/EPPlus/Packaging/ZipPackage.cs +++ b/EPPlus/Packaging/ZipPackage.cs @@ -98,7 +98,11 @@ internal ZipPackage(Stream stream) stream.Seek(0, SeekOrigin.Begin); using (ZipInputStream zip = new ZipInputStream(stream)) { - var e = zip.GetNextEntry(); + var e = zip.GetNextEntry(); + if(e==null) + { + throw (new InvalidDataException("The file is not an valid Package file. If the file is encrypted, please supply the password in the constructor.")); + } if (e.FileName.Contains("\\")) { _dirSeparator = '\\'; diff --git a/EPPlus/Style/XmlAccess/ExcelFontXml.cs b/EPPlus/Style/XmlAccess/ExcelFontXml.cs index 824a4a81..c3274398 100644 --- a/EPPlus/Style/XmlAccess/ExcelFontXml.cs +++ b/EPPlus/Style/XmlAccess/ExcelFontXml.cs @@ -300,7 +300,7 @@ private static float GetHeightByName(string name, float size) } else { - float min = -1, max = 500; + float min = -1, max = float.MaxValue; foreach (var h in FontSize.FontHeights[name]) { if (min < h.Key && h.Key < size) @@ -312,17 +312,20 @@ private static float GetHeightByName(string name, float size) max = h.Key; } } - if (min == max) + if (min == max || max==float.MaxValue) { return Convert.ToSingle(FontSize.FontHeights[name][min].Height); } + else if (min == -1) + { + return Convert.ToSingle(FontSize.FontHeights[name][max].Height); + } else { - return Convert.ToSingle(FontSize.FontHeights[name][min].Height + (FontSize.FontHeights[name][max].Height - FontSize.FontHeights[name][min].Height) * ((size - min) / (max - min))); + return Convert.ToSingle(FontSize.FontHeights[name][min].Height + (FontSize.FontHeights[name][max].Height - FontSize.FontHeights[name][min].Height) * ((size - min) / (max - min))); } } } - internal ExcelFontXml Copy() { ExcelFontXml newFont = new ExcelFontXml(NameSpaceManager); @@ -338,7 +341,6 @@ internal ExcelFontXml Copy() newFont.Color = Color.Copy(); return newFont; } - internal override XmlNode CreateXmlNode(XmlNode topElement) { TopNode = topElement; diff --git a/EPPlus/Table/ExcelTable.cs b/EPPlus/Table/ExcelTable.cs index 58b2541d..1cedfb67 100644 --- a/EPPlus/Table/ExcelTable.cs +++ b/EPPlus/Table/ExcelTable.cs @@ -37,6 +37,7 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Math; using OfficeOpenXml.Utils; using System.Security; +using OfficeOpenXml.FormulaParsing.ExcelUtilities; namespace OfficeOpenXml.Table { @@ -157,7 +158,7 @@ private string GetStartXml(string name, int tblId) xml += string.Format("", tblId, name, - cleanDisplayName(name), + ExcelAddressUtil.GetValidName(name), Address.Address); xml += string.Format("", Address.Address); @@ -191,7 +192,7 @@ private string GetStartXml(string name, int tblId) return xml; } - private string cleanDisplayName(string name) + internal static string CleanDisplayName(string name) { return Regex.Replace(name, @"[^\w\.-_]", "_"); } @@ -258,7 +259,7 @@ public string Name WorkSheet.Tables._tableNames.Add(value,ix); } SetXmlNodeString(NAME_PATH, value); - SetXmlNodeString(DISPLAY_NAME_PATH, cleanDisplayName(value)); + SetXmlNodeString(DISPLAY_NAME_PATH, ExcelAddressUtil.GetValidName(value)); } } /// diff --git a/EPPlus/Table/ExcelTableCollection.cs b/EPPlus/Table/ExcelTableCollection.cs index 1e0bff87..f4d7cee0 100644 --- a/EPPlus/Table/ExcelTableCollection.cs +++ b/EPPlus/Table/ExcelTableCollection.cs @@ -34,7 +34,7 @@ using System.Linq; using System.Text; using System.Xml; - +using OfficeOpenXml.FormulaParsing.ExcelUtilities; namespace OfficeOpenXml.Table { /// @@ -78,16 +78,19 @@ public ExcelTable Add(ExcelAddressBase Range, string Name) { if (Range.WorkSheet != null && Range.WorkSheet != _ws.Name) { - throw new ArgumentException("Range does not belong to worksheet", "Range"); + throw new ArgumentException("Range does not belong to a worksheet", "Range"); } - + if (string.IsNullOrEmpty(Name)) { Name = GetNewTableName(); } - else if (_ws.Workbook.ExistsTableName(Name)) + else { - throw (new ArgumentException("Tablename is not unique")); + if (_ws.Workbook.ExistsTableName(Name)) + { + throw (new ArgumentException("Tablename is not unique")); + } } ValidateTableName(Name); @@ -119,7 +122,10 @@ private void ValidateTableName(string Name) { throw new ArgumentException("Tablename has spaces"); } - + if (!ExcelAddressUtil.IsValidName(Name)) + { + throw (new ArgumentException("Tablename is not valid")); + } } public void Delete(int Index, bool ClearRange = false) diff --git a/EPPlusTest/Address.cs b/EPPlusTest/Address.cs index 7df746e9..188559cc 100644 --- a/EPPlusTest/Address.cs +++ b/EPPlusTest/Address.cs @@ -5,7 +5,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OfficeOpenXml; using OfficeOpenXml.Style; - +using OfficeOpenXml.FormulaParsing.ExcelUtilities; namespace EPPlusTest { /// @@ -134,7 +134,19 @@ public void IsValidCellAdress() Assert.IsFalse(ExcelCellBase.IsValidCellAddress("Table1!A1048576:XFD1048576")); Assert.IsFalse(ExcelCellBase.IsValidCellAddress("Table1!XFD1:XFD1048576")); } - + [TestMethod] + public void IsValidName() + { + Assert.IsFalse(ExcelAddressUtil.IsValidName("123sa")); //invalid start char + Assert.IsFalse(ExcelAddressUtil.IsValidName("*d")); //invalid start char + Assert.IsFalse(ExcelAddressUtil.IsValidName("\t")); //invalid start char + Assert.IsFalse(ExcelAddressUtil.IsValidName("\\t")); //Backslash at least three chars + Assert.IsFalse(ExcelAddressUtil.IsValidName("A+1")); //invalid char + Assert.IsFalse(ExcelAddressUtil.IsValidName("A%we")); //Address invalid + Assert.IsFalse(ExcelAddressUtil.IsValidName("BB73")); //Address invalid + Assert.IsTrue(ExcelAddressUtil.IsValidName("BBBB75")); //Valid + Assert.IsTrue(ExcelAddressUtil.IsValidName("BB1500005")); //Valid + } [TestMethod] public void NamedRangeMovesDownIfRowInsertedAbove() { diff --git a/EPPlusTest/Issues.cs b/EPPlusTest/Issues.cs index 07924889..ee54ae92 100644 --- a/EPPlusTest/Issues.cs +++ b/EPPlusTest/Issues.cs @@ -1859,8 +1859,8 @@ public void Issue66() ws.Cells["A1"].Value = 1; ws.Cells["B1"].Formula = "A1"; var wb = pck.Workbook; - wb.Names.Add("N1", ws.Cells["A1:A2"]); - ws.Names.Add("N2", ws.Cells["A1"]); + wb.Names.Add("Name1", ws.Cells["A1:A2"]); + ws.Names.Add("Name2", ws.Cells["A1"]); pck.Save(); using (var pck2 = new ExcelPackage(pck.Stream)) { @@ -2103,6 +2103,7 @@ public void Issue204() public void Issue170() { OpenTemplatePackage("print_titles_170.xlsx"); + _pck.Compatibility.IsWorksheets1Based = false; ExcelWorksheet sheet = _pck.Workbook.Worksheets[0]; sheet.PrinterSettings.RepeatColumns = new ExcelAddress("$A:$C"); @@ -2122,5 +2123,118 @@ public void Issue219() _pck.Dispose(); } + [TestMethod] + [ExpectedException(typeof(InvalidDataException))] + public void Issue234() + { + using (var s = new MemoryStream()) + { + var data = Encoding.UTF8.GetBytes("Bad data").ToArray(); + s.Write(data, 0, data.Length); + var package = new ExcelPackage(s); + } + } + + [TestMethod] + public void Issue220() + { + OpenPackage("sheetname_pbl.xlsx", true); + var ws=_pck.Workbook.Worksheets.Add("Deal's History"); + var a = ws.Cells["A:B"]; + ws.AutoFilterAddress = ws.Cells["A1:C3"]; + _pck.Workbook.Names.Add("Test", ws.Cells["B1:D2"]); + var name = a.WorkSheet; + + var a2 = new ExcelAddress("'Deal''s History'!a1:a3"); + Assert.AreEqual(a2.WorkSheet, "Deal's History"); + _pck.Save(); + _pck.Dispose(); + + } + [ExpectedException(typeof(ArgumentException))] + [TestMethod] + public void Issue233() + { + //get some test data + var cars = Car.GenerateList(); + + OpenPackage("issue233.xlsx",true); + + var sheetName = "Summary_GLEDHOWSUGARCO![]()PTY"; + + //Create the worksheet + var sheet = _pck.Workbook.Worksheets.Add(sheetName); + + //Read the data into a range + var range = sheet.Cells["A1"].LoadFromCollection(cars, true); + + //Make the range a table + var tbl = sheet.Tables.Add(range, $"data{sheetName}"); + tbl.ShowTotal = true; + tbl.Columns["ReleaseYear"].TotalsRowFunction = OfficeOpenXml.Table.RowFunctions.Sum; + + //save and dispose + _pck.Save(); + _pck.Dispose(); + } + public class Car + { + public int Id { get; set; } + public string Make { get; set; } + public string Model { get; set; } + public int ReleaseYear { get; set; } + + public Car(int id, string make, string model, int releaseYear) + { + Id = Id; + Make = make; + Model = model; + ReleaseYear = releaseYear; + } + + internal static List GenerateList() + { + return new List + { + //random data + new Car(1,"Toyota", "Carolla", 1950), + new Car(2,"Toyota", "Yaris", 2000), + new Car(3,"Toyota", "Hilux", 1990), + new Car(4,"Nissan", "Juke", 2010), + new Car(5,"Nissan", "Trail Blazer", 1995), + new Car(6,"Nissan", "Micra", 2018), + new Car(7,"BMW", "M3", 1980), + new Car(8,"BMW", "X5", 2008), + new Car(9,"BMW", "M6", 2003), + new Car(10,"Merc", "S Class", 2001) + }; + } + } + [TestMethod] + public void Issue236() + { + OpenTemplatePackage("Issue236.xlsx"); + _pck.Workbook.Worksheets["Sheet1"].Cells[7, 10].AddComment("test", "Author"); + SaveWorksheet("Issue236-Saved.xlsx"); + } + [TestMethod] + public void Issue228() + { + OpenTemplatePackage("Font55.xlsx"); + var ws = _pck.Workbook.Worksheets["Sheet1"]; + var d=ws.Drawings.AddShape("Shape1",eShapeStyle.Diamond); + ws.Cells["A1"].Value = "tasetraser"; + ws.Cells.AutoFitColumns(); + SaveWorksheet("Font55-Saved.xlsx"); + } + [TestMethod] + public void Issue241() + { + OpenPackage("issue241",true); + var wks = _pck.Workbook.Worksheets.Add("test"); + wks.DefaultRowHeight = 35; + _pck.Save(); + _pck.Dispose(); } - } + } +} diff --git a/EPPlusTest/TestBase.cs b/EPPlusTest/TestBase.cs index 8e2e38fd..3e062cfb 100644 --- a/EPPlusTest/TestBase.cs +++ b/EPPlusTest/TestBase.cs @@ -61,9 +61,13 @@ public void InitBase() _pck = new ExcelPackage(); } - protected ExcelPackage OpenPackage(string name) + protected ExcelPackage OpenPackage(string name, bool delete=false) { var fi = new FileInfo(_worksheetPath + name); + if(delete && fi.Exists) + { + fi.Delete(); + } _pck = new ExcelPackage(fi); return _pck; } diff --git a/EPPlusTest/WorkSheet.cs b/EPPlusTest/WorkSheet.cs index 8963879b..7710e49a 100644 --- a/EPPlusTest/WorkSheet.cs +++ b/EPPlusTest/WorkSheet.cs @@ -1095,7 +1095,7 @@ public void ValueError() Console.WriteLine(rt.Bold.ToString()); rt.Bold = true; Console.WriteLine(rt.Bold.ToString()); - } + } //[Ignore] //[TestMethod] public void FormulaError() @@ -1588,7 +1588,7 @@ public void DefinedName() ws.Names.AddValue("Value", 5); ws.Names.Add("FullRow", ws.Cells["2:2"]); ws.Names.Add("FullCol", ws.Cells["A:A"]); - //ws.Names["Value"].Style.Border.Bottom.Color.SetColor(Color.Black); + ws.Names.AddFormula("Formula", "Names!A2+Names!A3+Names!Value"); } [Ignore] @@ -1630,13 +1630,6 @@ public void LoadDataReader() dr[3] = 2.25; dt.Rows.Add(dr); - //dr = dt.NewRow(); - //dr[0] = "Row3"; - //dr[1] = 3; - //dr[2] = true; - //dr[3] = 3.125; - //dt.Rows.Add(dr); - using (var reader = dt.CreateDataReader()) { range = ws.Cells["A1"].LoadFromDataReader(reader, true, "My_Table", @@ -1652,6 +1645,7 @@ public void LoadDataReader() range = ws.Cells["A5"].LoadFromDataReader(reader, false, "My_Table2", OfficeOpenXml.Table.TableStyles.Medium5); } + Assert.AreEqual(1, range.Start.Column); Assert.AreEqual(4, range.End.Column); Assert.AreEqual(5, range.Start.Row); From d14c2d9b6bb12563dec024c9cdea5f55dd558d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20K=C3=A4llman?= <982881+JanKallman@users.noreply.github.com> Date: Fri, 29 Jun 2018 13:33:55 +0200 Subject: [PATCH 2/5] Enabled worksheetcharts to use a pivottable as source by adding a pivotTableSource parameter to the AddChart method of the Worksheets collection. #229 --- EPPlus/Drawing/ExcelDrawings.cs | 5 ++--- EPPlus/ExcelWorksheet.cs | 4 ++-- EPPlus/ExcelWorksheets.cs | 23 ++++++++++++++++++----- EPPlusTest/WorkSheet.cs | 9 +++++++-- 4 files changed, 29 insertions(+), 12 deletions(-) diff --git a/EPPlus/Drawing/ExcelDrawings.cs b/EPPlus/Drawing/ExcelDrawings.cs index f4ac8b5f..6bf9f45b 100644 --- a/EPPlus/Drawing/ExcelDrawings.cs +++ b/EPPlus/Drawing/ExcelDrawings.cs @@ -125,9 +125,8 @@ private void AddDrawings() ExcelDrawing dr; switch(node.LocalName) { - case "oneCellAnchor": - //dr = new ExcelDrawing(this, node, "xdr:sp/xdr:nvSpPr/xdr:cNvPr/@name"); - dr = ExcelDrawing.GetDrawing(this, node); //Issue 15373 + case "oneCellAnchor": + dr = ExcelDrawing.GetDrawing(this, node); break; case "twoCellAnchor": dr = ExcelDrawing.GetDrawing(this, node); diff --git a/EPPlus/ExcelWorksheet.cs b/EPPlus/ExcelWorksheet.cs index 18a72327..ac1ff800 100644 --- a/EPPlus/ExcelWorksheet.cs +++ b/EPPlus/ExcelWorksheet.cs @@ -99,10 +99,10 @@ public struct ExcelCoreValue public class ExcelChartsheet : ExcelWorksheet { //ExcelDrawings draws; - public ExcelChartsheet(XmlNamespaceManager ns, ExcelPackage pck, string relID, Uri uriWorksheet, string sheetName, int sheetID, int positionID, eWorkSheetHidden hidden, eChartType chartType) : + public ExcelChartsheet(XmlNamespaceManager ns, ExcelPackage pck, string relID, Uri uriWorksheet, string sheetName, int sheetID, int positionID, eWorkSheetHidden hidden, eChartType chartType, ExcelPivotTable pivotTableSource ) : base(ns, pck, relID, uriWorksheet, sheetName, sheetID, positionID, hidden) { - this.Drawings.AddChart("Chart 1", chartType); + this.Drawings.AddChart("Chart 1", chartType, pivotTableSource); } public ExcelChartsheet(XmlNamespaceManager ns, ExcelPackage pck, string relID, Uri uriWorksheet, string sheetName, int sheetID, int positionID, eWorkSheetHidden hidden) : base(ns, pck, relID, uriWorksheet, sheetName, sheetID, positionID, hidden) diff --git a/EPPlus/ExcelWorksheets.cs b/EPPlus/ExcelWorksheets.cs index 07cdd033..715d4693 100644 --- a/EPPlus/ExcelWorksheets.cs +++ b/EPPlus/ExcelWorksheets.cs @@ -46,6 +46,8 @@ using OfficeOpenXml.Packaging.Ionic.Zlib; using OfficeOpenXml.Utils; using OfficeOpenXml.VBA; +using OfficeOpenXml.Table.PivotTable; + namespace OfficeOpenXml { /// @@ -151,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) + private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType, ExcelPivotTable pivotTableSource = null) { int sheetID; Uri uriWorksheet; @@ -177,7 +179,7 @@ private ExcelWorksheet AddSheet(string Name, bool isChart, eChartType? chartType ExcelWorksheet worksheet; if (isChart) { - worksheet = new ExcelChartsheet(_namespaceManager, _pck, rel, uriWorksheet, Name, sheetID, positionID, eWorkSheetHidden.Visible, (eChartType)chartType); + worksheet = new ExcelChartsheet(_namespaceManager, _pck, rel, uriWorksheet, Name, sheetID, positionID, eWorkSheetHidden.Visible, (eChartType)chartType, pivotTableSource); } else { @@ -293,12 +295,23 @@ public ExcelWorksheet Add(string Name, ExcelWorksheet Copy) /// /// Adds a chartsheet to the workbook. /// - /// - /// + /// The name of the worksheet + /// The type of chart /// public ExcelChartsheet AddChart(string Name, eChartType chartType) { - return (ExcelChartsheet)AddSheet(Name, true, chartType); + return (ExcelChartsheet)AddSheet(Name, true, chartType, null); + } + /// + /// Adds a chartsheet to the workbook. + /// + /// The name of the worksheet + /// The type of chart + /// The pivottable source + /// + public ExcelChartsheet AddChart(string Name, eChartType chartType, ExcelPivotTable pivotTableSource) + { + return (ExcelChartsheet)AddSheet(Name, true, chartType, pivotTableSource); } private void CopySheetNames(ExcelWorksheet Copy, ExcelWorksheet added) { diff --git a/EPPlusTest/WorkSheet.cs b/EPPlusTest/WorkSheet.cs index 7710e49a..9b7bb6d5 100644 --- a/EPPlusTest/WorkSheet.cs +++ b/EPPlusTest/WorkSheet.cs @@ -1144,8 +1144,13 @@ public void PivotTableTest() new object [] { 1, 1, 2, 3, 5, 8, 13, 21, 34, 55} }); var table = ws.Tables.Add(ws.Cells["A1:D4"], "PivotData"); - ws.PivotTables.Add(ws.Cells["G1"], ws.Cells["A1:D4"], "PivotTable"); - Assert.AreEqual("PivotStyleMedium9", ws.PivotTables["PivotTable"].StyleName); + var pt=ws.PivotTables.Add(ws.Cells["G20"], ws.Cells["A1:D4"], "PivotTable1"); + pt.ColumnFields.Add(pt.Fields[1]); + pt.DataFields.Add(pt.Fields[3]); + Assert.AreEqual("PivotStyleMedium9", ws.PivotTables["PivotTable1"].StyleName); + + _pck.Workbook.Worksheets.AddChart("PivotChartWorksheet", eChartType.Line, pt); + SaveWorksheet("Pivot.xlsx"); } From 9d94735e9441bbc0c0ee2d226eda3044fadf8a32 Mon Sep 17 00:00:00 2001 From: Mats Alm Date: Tue, 17 Jul 2018 13:42:20 +0200 Subject: [PATCH 3/5] Value function did not handle blank values --- EPPlus/FormulaParsing/Excel/Functions/Text/Value.cs | 4 +++- .../ExcelRanges/TextExcelRangeTests.cs | 12 ++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/EPPlus/FormulaParsing/Excel/Functions/Text/Value.cs b/EPPlus/FormulaParsing/Excel/Functions/Text/Value.cs index c49d6a80..577e5f93 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/Text/Value.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/Text/Value.cs @@ -21,8 +21,10 @@ public class Value : ExcelFunction public override CompileResult Execute(IEnumerable arguments, ParsingContext context) { ValidateArguments(arguments, 1); - var val = ArgToString(arguments, 0).TrimEnd(' '); + var val = ArgToString(arguments, 0); double result = 0d; + if (string.IsNullOrEmpty(val)) return CreateResult(result, DataType.Integer); + val = val.TrimEnd(' '); if (Regex.IsMatch(val, $"^[\\d]*({Regex.Escape(_groupSeparator)}?[\\d]*)?({Regex.Escape(_decimalSeparator)}[\\d]*)?[ ?% ?]?$")) { if (val.EndsWith("%")) diff --git a/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/ExcelRanges/TextExcelRangeTests.cs b/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/ExcelRanges/TextExcelRangeTests.cs index 5d97b514..f367d3e3 100644 --- a/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/ExcelRanges/TextExcelRangeTests.cs +++ b/EPPlusTest/FormulaParsing/IntegrationTests/BuiltInFunctions/ExcelRanges/TextExcelRangeTests.cs @@ -149,5 +149,17 @@ public void ValueShouldHandleTime() var result = _worksheet.Cells["A4"].Value; Assert.AreEqual(0.5, result); } + + [TestMethod] + public void ValueShouldReturn0IfValueIsNull() + { + + _worksheet.Cells["A1"].Value = null; + _worksheet.Cells["A4"].Formula = "Value(A1)"; + _worksheet.Calculate(); + var result = _worksheet.Cells["A4"].Value; + Assert.AreEqual(0d, result); + } + } } From 26303b8baf28b680021345f79567f5acf53159f0 Mon Sep 17 00:00:00 2001 From: Mats Alm Date: Mon, 6 Aug 2018 09:15:07 +0200 Subject: [PATCH 4/5] Implemented Pmt function --- EPPlus/EPPlus.csproj | 1 + .../Excel/Functions/BuiltInFunctions.cs | 3 + .../Excel/Functions/Finance/Pmt.cs | 59 +++++++++++++++++++ EPPlusTest/EPPlusTest.csproj | 1 + .../Excel/Functions/FinanceFunctionTests.cs | 24 ++++++++ 5 files changed, 88 insertions(+) create mode 100644 EPPlus/FormulaParsing/Excel/Functions/Finance/Pmt.cs create mode 100644 EPPlusTest/FormulaParsing/Excel/Functions/FinanceFunctionTests.cs diff --git a/EPPlus/EPPlus.csproj b/EPPlus/EPPlus.csproj index 7f9cde7a..eeb1bdbc 100644 --- a/EPPlus/EPPlus.csproj +++ b/EPPlus/EPPlus.csproj @@ -400,6 +400,7 @@ + diff --git a/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs b/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs index d505cd19..42e59e9f 100644 --- a/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs +++ b/EPPlus/FormulaParsing/Excel/Functions/BuiltInFunctions.cs @@ -34,6 +34,7 @@ using OfficeOpenXml.FormulaParsing.Excel.Functions.Numeric; using OfficeOpenXml.FormulaParsing.Excel.Functions.RefAndLookup; using OfficeOpenXml.FormulaParsing.Excel.Functions.Information; +using OfficeOpenXml.FormulaParsing.Excel.Functions.Finance; namespace OfficeOpenXml.FormulaParsing.Excel.Functions { @@ -202,6 +203,8 @@ public BuiltInFunctions() Functions["daverage"] = new Daverage(); Functions["dvar"] = new Dvar(); Functions["dvarp"] = new Dvarp(); + //Finance + Functions["pmt"] = new Pmt(); } } } diff --git a/EPPlus/FormulaParsing/Excel/Functions/Finance/Pmt.cs b/EPPlus/FormulaParsing/Excel/Functions/Finance/Pmt.cs new file mode 100644 index 00000000..3f0424ba --- /dev/null +++ b/EPPlus/FormulaParsing/Excel/Functions/Finance/Pmt.cs @@ -0,0 +1,59 @@ +/* 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 + ******************************************************************************* + * Mats Alm Added 2018-07-17 + *******************************************************************************/ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using OfficeOpenXml.FormulaParsing.ExpressionGraph; + +namespace OfficeOpenXml.FormulaParsing.Excel.Functions.Finance +{ + public class Pmt : ExcelFunction + { + public override CompileResult Execute(IEnumerable arguments, ParsingContext context) + { + ValidateArguments(arguments, 3); + var rate = ArgToDecimal(arguments, 0); + var nPer = ArgToInt(arguments, 1); + var presentValue = ArgToDecimal(arguments, 2); + var payEndOfPeriod = false; + var futureValue = 0d; + if (arguments.Count() > 3) futureValue = ArgToDecimal(arguments, 3); + if (arguments.Count() > 4) payEndOfPeriod = ArgToBool(arguments, 4); + + var result = (futureValue + presentValue * System.Math.Pow(rate + 1, nPer)) * rate + / + ((payEndOfPeriod ? rate + 1 : 1) * (1 - System.Math.Pow(rate + 1, nPer))); + + + return CreateResult(result, DataType.Decimal); + } + + private static double GetInterest(double rate, double remainingAmount) + { + return remainingAmount * rate; + } + } +} diff --git a/EPPlusTest/EPPlusTest.csproj b/EPPlusTest/EPPlusTest.csproj index b1fac8e6..98430bf1 100644 --- a/EPPlusTest/EPPlusTest.csproj +++ b/EPPlusTest/EPPlusTest.csproj @@ -100,6 +100,7 @@ + diff --git a/EPPlusTest/FormulaParsing/Excel/Functions/FinanceFunctionTests.cs b/EPPlusTest/FormulaParsing/Excel/Functions/FinanceFunctionTests.cs new file mode 100644 index 00000000..3e913c99 --- /dev/null +++ b/EPPlusTest/FormulaParsing/Excel/Functions/FinanceFunctionTests.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using OfficeOpenXml; + +namespace EPPlusTest.FormulaParsing.Excel.Functions +{ + [TestClass] + public class FinanceFunctionTests + { + [TestMethod] + public void PmtTest1() + { + using (var package = new ExcelPackage()) + { + var sheet = package.Workbook.Worksheets.Add("test"); + sheet.Cells["A1"].Formula = "PMT( 5%/12, 60, 50000 )"; + sheet.Calculate(); + var value = sheet.Cells["A1"].Value; + var value2 = System.Math.Round(Convert.ToDouble(value), 2); + Assert.AreEqual(-943.56, value2); + } + } + } +} From 642e73bef00699b7d193f9b6305135b5c2ad87c0 Mon Sep 17 00:00:00 2001 From: Mats Alm Date: Mon, 6 Aug 2018 09:45:39 +0200 Subject: [PATCH 5/5] Fixed issue #276 --- EPPlus/FormulaParsing/CalculateExtentions.cs | 2 +- .../ExpressionGraph/ExpressionConverter.cs | 3 +++ .../Excel/Functions/DateTimeFunctionsTests.cs | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/EPPlus/FormulaParsing/CalculateExtentions.cs b/EPPlus/FormulaParsing/CalculateExtentions.cs index b2c5ee0c..d649ac22 100644 --- a/EPPlus/FormulaParsing/CalculateExtentions.cs +++ b/EPPlus/FormulaParsing/CalculateExtentions.cs @@ -157,7 +157,7 @@ private static void CalcChain(ExcelWorkbook wb, FormulaParser parser, Dependency { throw (fe); } - catch + catch(Exception e) { var error = ExcelErrorValue.Parse(ExcelErrorValue.Values.Value); SetValue(wb, item, error); diff --git a/EPPlus/FormulaParsing/ExpressionGraph/ExpressionConverter.cs b/EPPlus/FormulaParsing/ExpressionGraph/ExpressionConverter.cs index d7402840..8c75fc94 100644 --- a/EPPlus/FormulaParsing/ExpressionGraph/ExpressionConverter.cs +++ b/EPPlus/FormulaParsing/ExpressionGraph/ExpressionConverter.cs @@ -74,6 +74,9 @@ public Expression FromCompileResult(CompileResult compileResult) : new ExcelErrorExpression((ExcelErrorValue) compileResult.Result); case DataType.Empty: return new IntegerExpression(0); //Added JK + case DataType.Time: + case DataType.Date: + return new DecimalExpression((double)compileResult.Result); } return null; diff --git a/EPPlusTest/FormulaParsing/Excel/Functions/DateTimeFunctionsTests.cs b/EPPlusTest/FormulaParsing/Excel/Functions/DateTimeFunctionsTests.cs index 47507735..f56b8c57 100644 --- a/EPPlusTest/FormulaParsing/Excel/Functions/DateTimeFunctionsTests.cs +++ b/EPPlusTest/FormulaParsing/Excel/Functions/DateTimeFunctionsTests.cs @@ -581,5 +581,19 @@ public void NetworkdayIntlShouldReduceHoliday() Assert.AreEqual(13, ws.Cells["A1"].Value); } } + + [TestMethod] + public void TimeAddition() + { + using (var package = new ExcelPackage()) + { + var ws = package.Workbook.Worksheets.Add("test"); + ws.Cells["A1"].Formula = "1 + (Time(10,0,0))"; + ws.Calculate(); + var result = Convert.ToDouble(ws.Cells["A1"].Value); + result = Math.Round(result, 2); + Assert.AreEqual(1.42d, result); + } + } } }