Skip to content

Commit 71a663d

Browse files
authored
Merge pull request #1533 from EPPlusSoftware/bug/pivotIssues
Fixes issue #1530, #1531 and #1532.
2 parents d3d5dd7 + 7174d50 commit 71a663d

13 files changed

+184
-36
lines changed

src/EPPlus/ExcelWorkbook.cs

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -881,10 +881,37 @@ public void CreateVBAProject()
881881
_vba = new ExcelVbaProject(this);
882882
_vba.Create();
883883
}
884-
/// <summary>
885-
/// URI to the workbook inside the package
886-
/// </summary>
887-
internal Uri WorkbookUri { get; private set; }
884+
/// <summary>
885+
/// Calculate all pivot tables in the workbook.
886+
/// Also see <seealso cref="ExcelPivotTable.Calculate(bool)"/> and <seealso cref="ExcelPivotTableCollection.Calculate(bool)"/>
887+
/// </summary>
888+
/// <param name="refresh">If the cache should be refreshed.</param>
889+
public void CalculateAllPivotTables(bool refresh = false)
890+
{
891+
var caches = new HashSet<PivotTableCacheInternal>();
892+
foreach (var ws in Worksheets)
893+
{
894+
if (ws.IsChartSheet) continue;
895+
foreach (var pt in ws.PivotTables)
896+
{
897+
var cache = pt.CacheDefinition._cacheReference;
898+
if (cache == null) continue;
899+
if (!caches.Contains(cache))
900+
{
901+
pt.Calculate(refresh);
902+
caches.Add(cache);
903+
}
904+
else
905+
{
906+
pt.Calculate(false);
907+
}
908+
}
909+
}
910+
}
911+
/// <summary>
912+
/// URI to the workbook inside the package
913+
/// </summary>
914+
internal Uri WorkbookUri { get; private set; }
888915
/// <summary>
889916
/// URI to the styles inside the package
890917
/// </summary>

src/EPPlus/Style/RichText/ExcelRichText.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,7 @@ internal void WriteHtmlText(StringBuilder sb)
327327
/// <param name="xr"></param>
328328
internal void ReadrPr(XmlReader xr)
329329
{
330+
if (xr.IsEmptyElement == true) return;
330331
while (xr.Read())
331332
{
332333
if (xr.LocalName == "rPr") break;

src/EPPlus/Table/PivotTable/Calculation/PivotTableCalculation.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ private static void CalculateSourceFields(ExcelPivotTable pivotTable)
191191
{
192192
var keys = new List<Dictionary<int[], HashSet<int[]>>>();
193193
var calcFields = new Dictionary<string, PivotCalculationStore>(StringComparer.InvariantCultureIgnoreCase);
194-
foreach(var field in pivotTable.DataFields.Where(x=>string.IsNullOrEmpty(x.Field.Cache.Formula)==false).Select(x=>x.Field.Cache))
194+
foreach(var field in pivotTable.Fields.Where(x=>string.IsNullOrEmpty(x.Cache.Formula)==false).Select(x=>x.Cache))
195195
{
196196
foreach(var token in field.FormulaTokens)
197197
{

src/EPPlus/Table/PivotTable/ExcelPivotTable.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -346,9 +346,10 @@ public bool IsCalculated
346346
internal HashSet<int[]> _rowItems = null;
347347
internal HashSet<int[]> _colItems = null;
348348
/// <summary>
349-
/// Calculates the pivot table
349+
/// Calculates the pivot table.
350+
/// Also see <seealso cref="ExcelPivotTableCollection.Calculate(bool)"/> and <seealso cref="ExcelWorkbook.CalculateAllPivotTables(bool)"/>
350351
/// </summary>
351-
/// <param name="refreshCache"></param>
352+
/// <param name="refreshCache">If the pivot cache should be refreshed from the source data, before calculating the pivot table.</param>
352353
public void Calculate(bool refreshCache = false)
353354
{
354355
if (refreshCache || CacheDefinition._cacheReference.Records.RecordCount == 0)

src/EPPlus/Table/PivotTable/ExcelPivotTableCacheField.cs

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1052,37 +1052,48 @@ private List<eItemType> GetItemTypeFromFunctionList(eSubTotalFunctions subTotalF
10521052

10531053
internal static object AddSharedItemToHashSet(HashSet<object> hs, object o)
10541054
{
1055-
if (o == null)
1055+
o = GetShareItemValue(o);
1056+
if (!hs.Contains(o))
10561057
{
1057-
o = ExcelPivotTable.PivotNullValue;
1058+
hs.Add(o);
1059+
}
1060+
1061+
return o;
1062+
}
1063+
/// <summary>
1064+
/// Removes milliseconds from TimeSpan and DateTime values and set the value to <see cref="ExcelPivotTable.PivotNullValue" /> if the value is null
1065+
/// </summary>
1066+
/// <param name="v">The value</param>
1067+
/// <returns>The new value</returns>
1068+
internal static object GetShareItemValue(object v)
1069+
{
1070+
if (v == null)
1071+
{
1072+
v = ExcelPivotTable.PivotNullValue;
10581073
}
10591074
else
10601075
{
1061-
var t = o.GetType();
1076+
var t = v.GetType();
10621077
if (t == typeof(TimeSpan))
10631078
{
1064-
var ticks = ((TimeSpan)o).Ticks + (TimeSpan.TicksPerSecond) / 2;
1065-
o = new DateTime(ticks - (ticks % TimeSpan.TicksPerSecond));
1079+
var ticks = ((TimeSpan)v).Ticks + (TimeSpan.TicksPerSecond) / 2;
1080+
v = new DateTime(ticks - (ticks % TimeSpan.TicksPerSecond));
10661081
}
10671082
if (t == typeof(DateTime))
10681083
{
1069-
var ticks = ((DateTime)o).Ticks;
1084+
var ticks = ((DateTime)v).Ticks;
10701085
if ((ticks % TimeSpan.TicksPerSecond) != 0)
10711086
{
10721087
ticks += TimeSpan.TicksPerSecond / 2;
1073-
o = new DateTime(ticks - (ticks % TimeSpan.TicksPerSecond));
1088+
v = new DateTime(ticks - (ticks % TimeSpan.TicksPerSecond));
10741089
}
10751090
}
10761091
}
1077-
if (!hs.Contains(o))
1078-
{
1079-
hs.Add(o);
1080-
}
10811092

1082-
return o;
1093+
return v;
10831094
}
10841095

1085-
internal Dictionary<object, int> GetCacheLookup()
1096+
internal Dictionary<object, int> GetCacheLookup()
10861097
{
10871098
if(Grouping == null)
10881099
{

src/EPPlus/Table/PivotTable/ExcelPivotTableCollection.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,5 +263,28 @@ public void Delete(ExcelPivotTable PivotTable, bool ClearRange = false)
263263
_pivotTables.Remove(PivotTable);
264264
_pivotTableNames.Remove(PivotTable.Name);
265265
}
266+
/// <summary>
267+
/// Calculate all pivot tables in the collection.
268+
/// Also see <seealso cref="ExcelPivotTable.Calculate(bool)"/> and <seealso cref="ExcelWorkbook.CalculateAllPivotTables(bool)"/>
269+
/// </summary>
270+
/// <param name="refresh">If the cache should be refreshed.</param>
271+
public void Calculate(bool refresh = false)
272+
{
273+
var caches = new HashSet<PivotTableCacheInternal>();
274+
foreach (var pt in _pivotTables)
275+
{
276+
var cache = pt.CacheDefinition._cacheReference;
277+
if (cache == null) continue;
278+
if (!caches.Contains(cache))
279+
{
280+
pt.Calculate(refresh);
281+
caches.Add(cache);
282+
}
283+
else
284+
{
285+
pt.Calculate(false);
286+
}
287+
}
288+
}
266289
}
267290
}

src/EPPlus/Table/PivotTable/PivotTableCacheRecords.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,8 @@ internal void CreateRecords()
144144
f._fieldRecordIndex = new Dictionary<int, List<int>>();
145145
for (int r = sr._fromRow + 1; r <= toRow; r++) //Skip first row as it contains the headers.
146146
{
147-
var v = ws.GetValue(r, c) ?? ExcelPivotTable.PivotNullValue;
148-
var ix = lookup[v];
147+
var v = ExcelPivotTableCacheField.GetShareItemValue(ws.GetValue(r, c));
148+
var ix = lookup[v];
149149
l.Add(ix);
150150
var ciIx = r - (sr._fromRow + 1);
151151
if (f._fieldRecordIndex.ContainsKey(ix))

src/EPPlusTest/ExcelRangeBaseTests.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ Date Author Change
2828
*******************************************************************************/
2929
using Microsoft.VisualStudio.TestTools.UnitTesting;
3030
using OfficeOpenXml;
31+
using OfficeOpenXml.FormulaParsing.Excel.Functions.MathFunctions;
3132

3233
namespace EPPlusTest
3334
{
@@ -158,5 +159,22 @@ public void ClearFormulaValuesTest()
158159
Assert.AreEqual(3d, worksheet.Cells["A4"].Value);
159160
}
160161
}
162+
[TestMethod]
163+
public void CheckNaNOnSave()
164+
{
165+
using (var package = new ExcelPackage())
166+
{
167+
var worksheet = package.Workbook.Worksheets.Add("Sheet1");
168+
worksheet.Cells["A1"].Value = double.NaN;
169+
worksheet.Cells["A2"].Value = 0;
170+
worksheet.Cells["B1:B2"].Formula = "A1+1";
171+
object yourNanVariable = double.NaN;
172+
worksheet.Cells["A1"].Value = yourNanVariable is double d && double.IsNaN(d) ? 0 : yourNanVariable;
173+
174+
worksheet.Calculate();
175+
SaveWorkbook("Nan.xlsx", package);
176+
}
177+
}
178+
161179
}
162180
}

src/EPPlusTest/Issues/PackageIssues.cs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
2-
using System;
3-
using System.Collections.Generic;
4-
using System.Linq;
5-
using System.Text;
6-
using System.Threading.Tasks;
72
using OfficeOpenXml;
8-
using OfficeOpenXml.Drawing;
9-
using OfficeOpenXml.Drawing.Chart;
10-
using System.IO;
113
namespace EPPlusTest.Issues
124
{
135
[TestClass]
@@ -50,6 +42,31 @@ public void i1440()
5042
var ws = package.Workbook.Worksheets[0];
5143
}
5244
}
45+
[TestMethod]
46+
public void s703()
47+
{
48+
var fi = GetTemplateFile("errorfile.xlsx");
49+
if (fi != null)
50+
{
51+
var formFile = fi.OpenRead();
5352

53+
using (ExcelPackage package = new ExcelPackage(formFile))
54+
{
55+
var ws = package.Workbook.Worksheets[0];
56+
SaveWorkbook("s703.xlsx", package);
57+
}
58+
formFile.Close();
59+
formFile.Dispose();
60+
}
61+
}
62+
[TestMethod]
63+
public void i1530()
64+
{
65+
using (ExcelPackage package = OpenTemplatePackage("i1530.xlsx"))
66+
{
67+
var ws = package.Workbook.Worksheets[0];
68+
Assert.AreEqual("a", ws.Cells["A2"].Value);
69+
}
70+
}
5471
}
5572
}

src/EPPlusTest/Issues/PivotTableIssues.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,5 @@
11
using Microsoft.VisualStudio.TestTools.UnitTesting;
22
using OfficeOpenXml;
3-
using System.IO;
4-
using OfficeOpenXml.FormulaParsing;
5-
63
namespace EPPlusTest.Issues
74
{
85
[TestClass]

src/EPPlusTest/Table/PivotTable/Calculation/VerifyPivotCalculationTests.cs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,6 @@ public void VerifyCalculationMD()
2727
var ptWs = p.Workbook.Worksheets["PivotTables"];
2828
var ws = p.Workbook.Worksheets[3];
2929
var pt = ws.PivotTables[0];
30-
31-
Assert.AreEqual(4335.69, GetPtData(pt, 0, "Good Samaritan Hospital", "Tissue Sealer", "2023", "mar"));
32-
Assert.AreEqual(34454.62, GetPtData(pt, 0, "Palm Beach Garden Comm Hospital", null, 2022, "Nov"));
3330
}
3431
}
3532
private object GetPtData(ExcelPivotTable pt, int datafield, params object[] values)

src/EPPlusTest/Table/PivotTable/Calculation/VerifyPivotCalculationWorkbookTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,25 @@ public void VerifyCalculationPivotTable7()
124124
Assert.AreEqual(ErrorValues.RefError, ws.Cells["A6"].Value);
125125
}
126126

127+
[TestMethod]
128+
public void VerifyCalculationAllPivotTables()
129+
{
130+
using (var p = OpenTemplatePackage("PivotTableCalculation.xlsx"))
131+
{
132+
p.Workbook.CalculateAllPivotTables(true);
133+
}
134+
}
135+
[TestMethod]
136+
public void VerifyCalculationPivotTablesCollectionCalculate()
137+
{
138+
using (var p = OpenTemplatePackage("PivotTableCalculation.xlsx"))
139+
{
140+
foreach (var ws in p.Workbook.Worksheets)
141+
{
142+
ws.PivotTables.Calculate(true);
143+
}
144+
}
145+
}
127146
private object GetPtData(ExcelPivotTable pt, int datafield, params object[] values)
128147
{
129148
var l = new List<PivotDataFieldItemSelection>();

src/EPPlusTest/Table/PivotTable/PivotTableTests.cs

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,5 +1095,42 @@ public void s690()
10951095
SaveAndCleanup(pck);
10961096
}
10971097
}
1098+
[TestMethod]
1099+
public void VerifyPivotTableSaveWithDateGroupAndMilliseconds()
1100+
{
1101+
1102+
using (var pck = OpenPackage("PivotDateGrpMS.xlsx", true))
1103+
{
1104+
// get the handle to the existing worksheet
1105+
var ws = pck.Workbook.Worksheets.Add("Sheet1");
1106+
1107+
ws.Cells["A1"].Value = "Date";
1108+
ws.Cells["B1"].Value = "Value";
1109+
1110+
ws.Cells["A2"].Value = new DateTime(2024, 5, 1, 13, 30, 15, 999);
1111+
ws.Cells["B2"].Value = 150.95;
1112+
1113+
ws.Cells["A3"].Value = new DateTime(2024, 3, 1, 8, 30, 15, 1);
1114+
ws.Cells["B3"].Value = 300.5;
1115+
1116+
1117+
ws.Cells[2, 1, 3, 1].Style.Numberformat.Format = "mm-dd-yy";
1118+
ws.Cells[2, 2, 3, 2].Style.Numberformat.Format = "#,##0";
1119+
1120+
ws.Cells.AutoFitColumns();
1121+
1122+
var wsPivot = pck.Workbook.Worksheets.Add("PivotDateGrp");
1123+
var pt = wsPivot.PivotTables.Add(wsPivot.Cells["A3"], ws.Cells["A1:B3"], "PivotTable1");
1124+
var rowField = pt.RowFields.Add(pt.Fields["Date"]);
1125+
1126+
rowField.AddDateGrouping(eDateGroupBy.Years | eDateGroupBy.Quarters);
1127+
rowField.Name = "Quarters"; //We rename the field OrderDate to Quarters.
1128+
var dataField = pt.DataFields.Add(pt.Fields["Value"]);
1129+
dataField.Format = "#,##0";
1130+
pt.DataOnRows = false;
1131+
pt.Calculate(true);
1132+
SaveAndCleanup(pck); //Make sure no exception happens when saving as Milliseconds should be stripped from any DateTime or TimeSpan value before comparing the shared item.
1133+
}
1134+
}
10981135
}
10991136
}

0 commit comments

Comments
 (0)