diff --git a/ClosedXML.Report/ClosedXML.Report.csproj b/ClosedXML.Report/ClosedXML.Report.csproj index 3d22006..ed54fa6 100644 --- a/ClosedXML.Report/ClosedXML.Report.csproj +++ b/ClosedXML.Report/ClosedXML.Report.csproj @@ -1,23 +1,24 @@ - + netstandard2.0;netstandard2.1 10 ClosedXML.Report - ClosedXML.Report + CustomEa.ClosedXML.Report Debug;Release https://github.com/ClosedXML/ClosedXML.Report - https://github.com/ClosedXML/ClosedXML.Report - Alexey Rozhkov, Alexey Pankratev + https://github.com/eagleoriginal/ClosedXML.Report + IOrlov, Alexey Rozhkov, Alexey Pankratev MIT - ClosedXML.Report - See https://github.com/ClosedXML/ClosedXML.Report/releases/tag/$(productVersion) - ClosedXML.Report is a tool for report generation and data analysis in .NET applications through the use of Microsoft Excel. ClosedXML.Report is a .NET-library for report generation Microsoft Excel without requiring Excel to be installed on the machine that's running the code. + CustomEa.ClosedXML.Report + See https://github.com/eagleoriginal/ClosedXML.Report/releases/tag/$(productVersion) + Not Intended for Common Use!!! +Pacakge with Custom Changes in original ClosedXML.Report. +ClosedXML.Report is a tool for report generation and data analysis in .NET applications through the use of Microsoft Excel. ClosedXML.Report is a .NET-library for report generation Microsoft Excel without requiring Excel to be installed on the machine that's running the code. ClosedXML Reporting Excel - ClosedXML - https://github.com/ClosedXML/ClosedXML.Report/raw/develop/Resources/favicon-01.png + CustomEa.ClosedXML MIT - false + True true @@ -30,12 +31,21 @@ snupkg true ClosedXML.Report.snk + 0.2.10-Beta5 + favicon-01.png true + + + True + \ + + + diff --git a/ClosedXML.Report/Excel/Subtotal.cs b/ClosedXML.Report/Excel/Subtotal.cs index 37949f2..77169c9 100644 --- a/ClosedXML.Report/Excel/Subtotal.cs +++ b/ClosedXML.Report/Excel/Subtotal.cs @@ -351,8 +351,15 @@ private MoveData[] ScanRange(int groupBy) var val = row.Cell(groupBy).GetString(); var isSummaryRow = row.IsSummary(); + var isLeftGroupEndLine = false; + if (false == isSummaryRow) + { + isLeftGroupEndLine = _groups.SingleOrDefault(gr => gr.Column == groupBy - 1 && + gr.SummaryRow == null && + gr.Range.RangeAddress.LastAddress.RowNumber == row.RowNumber()) != null; + } - if (string.IsNullOrEmpty(val) && !isSummaryRow) + if (string.IsNullOrEmpty(val) && !isSummaryRow) { if (groupStart > 0) { @@ -364,6 +371,18 @@ private MoveData[] ScanRange(int groupBy) continue; } + if (isLeftGroupEndLine) + { + var localGroupStart = groupStart == 0 + ? row.RangeAddress.Relative(_range.RangeAddress).FirstAddress.RowNumber + : groupStart; + + groups.Add(CreateMoveTask(groupBy, prevVal, _range.Cell(localGroupStart, 1), row.LastCell(), RangeType.DataRange)); + prevVal = null; + groupStart = 0; + continue; + } + if (val != prevVal) { if (groupStart > 0) diff --git a/ClosedXML.Report/Excel/SubtotalSummaryFunc.cs b/ClosedXML.Report/Excel/SubtotalSummaryFunc.cs index 8d1bd51..1487754 100644 --- a/ClosedXML.Report/Excel/SubtotalSummaryFunc.cs +++ b/ClosedXML.Report/Excel/SubtotalSummaryFunc.cs @@ -56,6 +56,8 @@ public virtual int FuncNum } } + public object DefaultValueForEmptySource { get; set; } + public Func GetCalculateDelegate; internal object Calculate(IDataSource dataSource) diff --git a/ClosedXML.Report/Excel/XlExtensions.cs b/ClosedXML.Report/Excel/XlExtensions.cs index 90e7115..f98e1ae 100644 --- a/ClosedXML.Report/Excel/XlExtensions.cs +++ b/ClosedXML.Report/Excel/XlExtensions.cs @@ -349,7 +349,13 @@ public static void ReplaceCFFormulaeToA1(this IXLWorksheet worksheet) { foreach (var format in worksheet.ConditionalFormats) { - var target = format.Ranges.OrderBy(x=>x.RangeAddress.FirstAddress.RowNumber) + format.Ranges.RemoveAll(range => false == range.RangeAddress.IsValid); + var validRanges = format.Ranges.Where(range => range.RangeAddress.IsValid).ToList(); + if (false == validRanges.Any()) + continue; + + var target = + validRanges.OrderBy(x=>x.RangeAddress.FirstAddress.RowNumber) .ThenBy(x=> x.RangeAddress.FirstAddress.ColumnNumber) .First().FirstCell(); foreach (var v in format.Values.Where(v => v.Value.Value.StartsWith("&=")).ToList()) diff --git a/ClosedXML.Report/Options/GroupTag.cs b/ClosedXML.Report/Options/GroupTag.cs index 5eb8bb6..8b61f89 100644 --- a/ClosedXML.Report/Options/GroupTag.cs +++ b/ClosedXML.Report/Options/GroupTag.cs @@ -12,6 +12,7 @@ OPTION PARAMS OBJECTS RNG Priority "\PageBreaks" "\TotalLabel" "\GrandLabel" + "\DisableSubtotalLine" "SummaryAbove" Range rD Normal @@ -30,6 +31,8 @@ OPTION PARAMS OBJECTS RNG Priority using ClosedXML.Excel; using ClosedXML.Report.Excel; using ClosedXML.Report.Utils; +using DocumentFormat.OpenXml.Drawing; +using DocumentFormat.OpenXml.Spreadsheet; using MoreLinq; namespace ClosedXML.Report.Options @@ -40,6 +43,7 @@ public class GroupTag : SortTag public bool PageBreaks => Parameters.ContainsKey("pagebreaks"); public bool DisableSubtotals => Parameters.ContainsKey("disablesubtotals"); + public bool DisableSubtotalLine => Parameters.ContainsKey("disablesubtotalline"); public bool Collapse => Parameters.ContainsKey("collapse"); public bool DisableOutLine => Parameters.ContainsKey("disableoutline"); public bool OutLine => !Parameters.ContainsKey("disableoutline"); @@ -110,11 +114,48 @@ private void Process(ProcessingContext context, GroupTag[] groups, bool summaryA var level = 0; var rows = root.RowCount() - 1; var columns = root.ColumnCount(); - if (rows <= 0 || columns <= 0) + if (columns <= 0) + { return; + } - var r = root.Offset(0, 0, rows, columns); + // Empty Total grand for report + if (rows <= 0) + { + if (disableGrandTotal) + return; + + var r2= root.Offset(0, 0, 1, columns); + using (var subtotal = new Subtotal(r2, summaryAbove, groups, context.Evaluator)) + { + if (TotalLabel != null) subtotal.TotalLabel = TotalLabel; + if (GrandLabel != null) subtotal.GrandLabel = GrandLabel; + if (!disableGrandTotal) + { + var total = subtotal.AddGrandTotal(summaries); + total.SummaryRow.Cell(2).Value = total.SummaryRow.Cell(1).Value; + total.SummaryRow.Cell(1).Value = Blank.Value; + level++; + } + + foreach (var subGroup in subtotal.Groups.OrderBy(x => x.Column).Reverse()) + { + FormatHeaderFooter(subGroup, groupRow); + + GroupRender(subGroup, new GroupTag { Column = 1, Level = 1 }); + } + + r2.Rows().ForEach(r => r.WorksheetRow().OutlineLevel = 0); + } + + // Rem DoDeleteSpecialRow + root.LastRow().Delete(XLShiftDeletedCells.ShiftCellsUp); + return; + } + + var r = root.Offset(0, 0, rows, columns); + using (var subtotal = new Subtotal(r, summaryAbove, groups, context.Evaluator)) { if (TotalLabel != null) subtotal.TotalLabel = TotalLabel; @@ -129,9 +170,18 @@ private void Process(ProcessingContext context, GroupTag[] groups, bool summaryA foreach (var g in groups.OrderBy(x => x.Column)) { + // Todo: New Feature Group Without Subtotal. Only Merge. + if (g.DisableSubtotalLine) + { + subtotal.ScanForGroups(g.Column); + g.Level = ++level; + + continue; + } + Func labFormat = null; if (!string.IsNullOrEmpty(g.LabelFormat)) - labFormat = title => string.Format(LabelFormat, title); + labFormat = title => string.Format(g.LabelFormat, title); if (g.MergeLabels == MergeMode.Merge2 && summaries.Length == 0) subtotal.ScanForGroups(g.Column); @@ -218,7 +268,12 @@ protected virtual void GroupRender(SubtotalGroup subGroup, GroupTag grData) var rng = subGroup.Range.Column(subGroup.Column); if (subGroup.Range.RowCount() > 1) { - int cellIdx = _maxLevel - subGroup.Level + 1; + // TODO: Wrong Style apply for merged cells if on right has grouped total + // But in first cell i expect already cell with value and style + // Plus with DisableSubtotalLine feature this became totally wrong + int cellIdx = 1; + //int cellIdx = _maxLevel - subGroup.Level + 1; // TODO: Comment for future investigation + var style = rng.Cell(cellIdx).Style; rng.Merge(); rng.Style = style; diff --git a/ClosedXML.Report/Options/SummaryFuncTag.cs b/ClosedXML.Report/Options/SummaryFuncTag.cs index 73e64ef..e28f2f6 100644 --- a/ClosedXML.Report/Options/SummaryFuncTag.cs +++ b/ClosedXML.Report/Options/SummaryFuncTag.cs @@ -1,8 +1,11 @@ using System; +using System.Linq; using System.Linq.Expressions; using ClosedXML.Excel; using ClosedXML.Report.Excel; using ClosedXML.Report.Utils; +using DocumentFormat.OpenXml.Math; +using DocumentFormat.OpenXml.Spreadsheet; namespace ClosedXML.Report.Options { @@ -32,8 +35,16 @@ public override void Execute(ProcessingContext context) summRow = context.Range.LastRow(); calculatedRange = context.Range.Offset(0, summ.Column - 1, context.Range.RowCount() - 1, 1); } + var items = summ.DataSource.GetAll(); - if (summ.FuncNum == 0) + if (items == null || items.Length == 0) + { + if (summ.DefaultValueForEmptySource != null) + { + summRow.Cell(summ.Column).Value = XLCellValueConverter.FromObject(summ.DefaultValueForEmptySource); + } + } + else if (summ.FuncNum == 0) { var value = summ.Calculate((IDataSource)context.Value); summRow.Cell(summ.Column).Value = XLCellValueConverter.FromObject(value); @@ -62,6 +73,14 @@ private SubtotalSummaryFunc GetFunc(ProcessingContext context) //return XLDynamicExpressionParser.ParseLambda(new[] {par}, null, GetParameter("Over")); }; func.DataSource = DataSource; + + if (HasParameter("Default")) + { + var dlg = context.Evaluator.ParseExpression(GetParameter("Default"), new ParameterExpression[] {}); + + func.DefaultValueForEmptySource = dlg.DynamicInvoke(); + } + return func; } } diff --git a/ClosedXML.Report/RangeInterpreter.cs b/ClosedXML.Report/RangeInterpreter.cs index 0f5d657..c979ecd 100644 --- a/ClosedXML.Report/RangeInterpreter.cs +++ b/ClosedXML.Report/RangeInterpreter.cs @@ -177,7 +177,13 @@ string EvalString(string str) { var growedRange = rng.GrowToMergedRanges(); var items = nr.RangeData as object[] ?? nr.RangeData.Cast().ToArray(); - if (!items.Any()) + // Comment this section + // Revision: a38eda3a3a7092812c8358a949d4e489635501bf + // Author: Aleksei + // Date: 27.07.2023 23:54:54 + // Message: + // Forcibly remove a range representing a table if its data source is empty (#251) (#323) + /*if (!items.Any()) { if (growedRange.IsOptionsRowEmpty()) { @@ -190,7 +196,7 @@ string EvalString(string str) rangeWithoutOptionsRow.Delete(XLShiftDeletedCells.ShiftCellsUp); } continue; - } + }*/ var tplt = RangeTemplate.Parse(nr.NamedRange.Name, growedRange, _errors, _variables); using (var buff = tplt.Generate(items)) { diff --git a/tests/ClosedXML.Report.Tests/GroupTagTests.cs b/tests/ClosedXML.Report.Tests/GroupTagTests.cs index 1737856..a873499 100644 --- a/tests/ClosedXML.Report.Tests/GroupTagTests.cs +++ b/tests/ClosedXML.Report.Tests/GroupTagTests.cs @@ -1,5 +1,7 @@ using System; +using System.Collections.Generic; using System.Drawing; +using System.IO; using System.Linq; using ClosedXML.Report.Tests.TestModels; using LinqToDB; @@ -38,7 +40,7 @@ public void Simple(string templateFile) { using (var db = new DbDemos()) { - var cust = db.customers.LoadWith(x=>x.Orders.First().Items).OrderBy(c => c.CustNo).First(x=>x.CustNo == 1356); + var cust = db.customers.LoadWith(x => x.Orders.First().Items).OrderBy(c => c.CustNo).First(x => x.CustNo == 1356); cust.Logo = Resource.toms_diving_center; tpl.AddVariable("MoreOrders", cust.Orders.Take(5)); tpl.AddVariable(cust); @@ -52,6 +54,29 @@ public void Simple(string templateFile) }); } + [Xunit.Fact] + public void Simple_EmptyResult() + { + string templateFile = "GroupTagTests_Simple_Empty.xlsx"; + XlTemplateTest(templateFile, + tpl => + { + using (var db = new DbDemos()) + { + var cust = db.customers.LoadWith(x => x.Orders.First().Items).OrderBy(c => c.CustNo).First(x => x.CustNo == 1356); + cust.Orders.Clear(); + cust.Logo = Resource.toms_diving_center; + tpl.AddVariable("MoreOrders", cust.Orders.Take(0)); + tpl.AddVariable(cust); + } + tpl.AddVariable("Tax", 13); + }, + wb => + { + CompareWithGauge(wb, templateFile); + }); + } + [Theory, InlineData("GroupTagTests_SummaryAbove.xlsx"), InlineData("GroupTagTests_MergeLabels.xlsx"), @@ -79,6 +104,7 @@ public void EmptyDataSource(string templateFile) InlineData("GroupTagTests_MultiRanges.xlsx"), //InlineData("GroupTagTests_FormulasWithTagsInGroupRow.xlsx"), // Formulas with tags got broken after upgrading to ClosedXML 0.100 InlineData("GroupTagTests_TotalLabel.xlsx"), + InlineData("GroupTagTests_DisableSubTotals_MergeLabels.xlsx") ] public void Customers(string templateFile) { diff --git a/tests/Gauges/GroupTagTests_DisableSubTotals_MergeLabels.xlsx b/tests/Gauges/GroupTagTests_DisableSubTotals_MergeLabels.xlsx new file mode 100644 index 0000000..d495687 Binary files /dev/null and b/tests/Gauges/GroupTagTests_DisableSubTotals_MergeLabels.xlsx differ diff --git a/tests/Gauges/GroupTagTests_Simple_Empty.xlsx b/tests/Gauges/GroupTagTests_Simple_Empty.xlsx new file mode 100644 index 0000000..875b8da Binary files /dev/null and b/tests/Gauges/GroupTagTests_Simple_Empty.xlsx differ diff --git a/tests/Templates/GroupTagTests_DisableSubTotals_MergeLabels.xlsx b/tests/Templates/GroupTagTests_DisableSubTotals_MergeLabels.xlsx new file mode 100644 index 0000000..6caeab1 Binary files /dev/null and b/tests/Templates/GroupTagTests_DisableSubTotals_MergeLabels.xlsx differ diff --git a/tests/Templates/GroupTagTests_Simple_Empty.xlsx b/tests/Templates/GroupTagTests_Simple_Empty.xlsx new file mode 100644 index 0000000..e15328e Binary files /dev/null and b/tests/Templates/GroupTagTests_Simple_Empty.xlsx differ