Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update to 0.104.1 #368

Open
wants to merge 20 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions ClosedXML.Report.sln.DotSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=subrange/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=subranges/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
2 changes: 1 addition & 1 deletion ClosedXML.Report/ClosedXML.Report.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ClosedXML" Version="0.102.2" />
<PackageReference Include="ClosedXML" Version="0.105.0-rc" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="morelinq" Version="4.1.0" />
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.8" />
Expand Down
4 changes: 2 additions & 2 deletions ClosedXML.Report/Excel/Subtotal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -509,7 +509,7 @@ private void RecalculateGroups(MoveData[] extendedGroups, bool extendAtEnd)

private string ExpandFormula(MoveData[] groups, string formula)
{
var pars = _range.Worksheet.GetRangeParameters(formula).Where(r => r.Key.Contains(":"));
var pars = _range.Worksheet.GetRangeParameters(formula);
foreach (var addr in pars)
{
var firstGroup = groups.FirstOrDefault(x => x.SourceAddress.FirstAddress.RowNumber == addr.Value.FirstAddress.RowNumber);
Expand All @@ -532,7 +532,7 @@ private string ExpandFormula(MoveData[] groups, string formula)

private string ShiftFormula(string formula, int rowCount)
{
var pars = _range.Worksheet.GetRangeParameters(formula).Where(r => r.Key.Contains(":"));
var pars = _range.Worksheet.GetRangeParameters(formula);
foreach (var addr in pars)
{
var sheet = addr.Value.Worksheet;
Expand Down
13 changes: 7 additions & 6 deletions ClosedXML.Report/Excel/XlExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
/// Find ranges within which contains the specified range (completely).
/// </summary>
/// <param name="range">range</param>
public static IEnumerable<IXLNamedRange> GetContainerNames(this IXLRange range)
public static IEnumerable<IXLDefinedName> GetContainerNames(this IXLRange range)
{
return range.Worksheet.Workbook.NamedRanges.Where(x => GetContainingRanges(x, range))

Check warning on line 44 in ClosedXML.Report/Excel/XlExtensions.cs

View workflow job for this annotation

GitHub Actions / build

'XLWorkbook.NamedRanges' is obsolete: 'Use DefinedNames instead.'

Check warning on line 44 in ClosedXML.Report/Excel/XlExtensions.cs

View workflow job for this annotation

GitHub Actions / build

'XLWorkbook.NamedRanges' is obsolete: 'Use DefinedNames instead.'
.Union(range.Worksheet.NamedRanges.Where(x => GetContainingRanges(x, range)));

Check warning on line 45 in ClosedXML.Report/Excel/XlExtensions.cs

View workflow job for this annotation

GitHub Actions / build

'IXLWorksheet.NamedRanges' is obsolete: 'Use DefinedNames instead.'

Check warning on line 45 in ClosedXML.Report/Excel/XlExtensions.cs

View workflow job for this annotation

GitHub Actions / build

'IXLWorksheet.NamedRanges' is obsolete: 'Use DefinedNames instead.'
}

public static bool Contains(this IXLRangeAddress rangeAddress, IXLAddress address)
Expand Down Expand Up @@ -121,14 +121,14 @@
/// Get the named ranges that contains the specified range (completely).
/// </summary>
/// <param name="range">range</param>
public static IEnumerable<IXLNamedRange> GetContainingNames(this IXLRange range)
public static IEnumerable<IXLDefinedName> GetContainingNames(this IXLRange range)
{
return range.Worksheet.NamedRanges
.Union(range.Worksheet.Workbook.NamedRanges)
return range.Worksheet.DefinedNames
.Union(range.Worksheet.Workbook.DefinedNames)
.Where(x => GetContainingRanges(x, range));
}

private static bool GetContainingRanges(IXLNamedRange x, IXLRange xlRange)
private static bool GetContainingRanges(IXLDefinedName x, IXLRange xlRange)
{
return x.Ranges.Select(GrowToMergedRanges)
.Where(r => r.Worksheet.Position == xlRange.Worksheet.Position && !r.Equals(xlRange))
Expand All @@ -142,7 +142,8 @@
.ForEach(x =>
{
var xlCells = range.Union(x).Select(c => c.Address)
.OrderBy(c => c.RowNumber).ThenBy(c => c.ColumnNumber);
.OrderBy(c => c.RowNumber).ThenBy(c => c.ColumnNumber)
.ToList();
range = sheet.Range(xlCells.First().ToStringFixed(), xlCells.Last().ToStringFixed());
});
return range;
Expand Down
1 change: 1 addition & 0 deletions ClosedXML.Report/Options/PivotTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ private IXLPivotTable CreatePivot(PivotTag pivot, ProcessingContext context, XLP
pt.MergeAndCenterWithLabels = pivot.HasParameter("MergeLabels");
pt.ShowExpandCollapseButtons = pivot.HasParameter("ShowButtons");
pt.ClassicPivotTableLayout = !pivot.HasParameter("TreeLayout");
pt.Layout = XLPivotLayout.Tabular;
pt.AutofitColumns = pivot.HasParameter("AutofitColumns");
pt.SortFieldsAtoZ = !pivot.HasParameter("NoSort");
pt.PreserveCellFormatting = !pivot.HasParameter("NoPreserveFormatting");
Expand Down
5 changes: 4 additions & 1 deletion ClosedXML.Report/Options/SortTag.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ public override void Execute(ProcessingContext context)
var fields = List.GetAll<SortTag>().ToArray();
foreach (var tag in fields.OrderBy(x => x.Num).ThenBy(x => x.Column))
{
context.Range.SortColumns.Add(tag.Column, tag.Order);
// Ignore blanks is a legacy option, but basically it means treat blanks as blanks
// and blanks are always at the end (regardless of sorting order). The value `false`
// would treat blanks as empty strings (i.e. sorted at the beginning instead of the end).
context.Range.SortColumns.Add(tag.Column, tag.Order, ignoreBlanks: true);
}
context.Range.Sort();

Expand Down
64 changes: 58 additions & 6 deletions ClosedXML.Report/Options/TagsEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using ClosedXML.Excel;
Expand All @@ -16,10 +17,61 @@ private IEnumerable<string> GetAllTags(string cellValue)
return from Match match in matches select match.Value;
}

public OptionTag[] Parse(string value, IXLRange range, TemplateCell cell, out string newValue)
/// <summary>
/// Apply tags to the <paramref name="cell"/>, if it contains tags.
/// </summary>
/// <param name="cell">Template cell that *might* contain tags (doesn't have to).</param>
/// <param name="range">Range each option will be associated with.</param>
/// <returns>Created tags for the cell (may be empty).</returns>
public OptionTag[] ApplyTagsTo(TemplateCell cell, IXLRange range)
{
OptionTag[] tags = Array.Empty<OptionTag>();
if (cell.CellType == TemplateCellType.Formula)
{
tags = Parse(cell.Formula, range, cell, out var newValue);
cell.Formula = newValue;
}
else if (cell.CellType == TemplateCellType.Value)
{
// Only text values can contain tags. Therefore skip all other types.
if (cell.Value.TryGetText(out var text))
{
tags = Parse(text, range, cell, out var newValue);
cell.Value = newValue == string.Empty ? Blank.Value : newValue;
}
}
else
{
// Other template cell types shouldn't even get here
cell.Value = Blank.Value;
}

return tags;
}

public OptionTag[] ApplyTagsTo(IXLCell cell, IXLRange range)
{
string value = cell.GetString();
OptionTag[] tags;
var templateCell = new TemplateCell(cell.Address.RowNumber, cell.Address.ColumnNumber, cell);
if (value.StartsWith("&="))
{
tags = Parse(value.Substring(2), range, templateCell, out var newValue);
cell.FormulaA1 = newValue;
}
else
{
tags = Parse(value, range, templateCell, out var newValue);
cell.Value = newValue;
}

return tags;
}

private OptionTag[] Parse(string templateLiteral, IXLRange range, TemplateCell cell, out string newValue)
{
List<OptionTag> result = new List<OptionTag>();
foreach (var expr in GetAllTags(value))
foreach (var expr in GetAllTags(templateLiteral))
{
var optionTag = ParseTag(expr.Substring(2, expr.Length-4));
if (optionTag == null)
Expand All @@ -31,9 +83,10 @@ public OptionTag[] Parse(string value, IXLRange range, TemplateCell cell, out st
optionTag.RangeOptionsRow = range.LastRow().RangeAddress;
}
result.Add(optionTag);
value = value.Replace(expr, "");
templateLiteral = templateLiteral.Replace(expr, "");
}
newValue = value.Trim();

newValue = templateLiteral.Trim();
return result.ToArray();
}

Expand All @@ -53,7 +106,6 @@ private OptionTag ParseTag(string str)
}

return TagsRegister.CreateOption(name, dictionary);

}
}
}
2 changes: 1 addition & 1 deletion ClosedXML.Report/Options/TagsList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ public void Execute(ProcessingContext context)
{
t.Execute(context);
}
catch(TemplateParseException ex)
catch (TemplateParseException ex)
{
_errors.Add(new TemplateError(ex.Message, ex.Range));
}
Expand Down
117 changes: 61 additions & 56 deletions ClosedXML.Report/RangeInterpreter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using ClosedXML.Excel;
Expand Down Expand Up @@ -44,29 +45,16 @@ public void ParseTags(IXLRange range, string rangeName)
.Where(c => !c.HasFormula && !innerRanges.Any(nr => nr.Ranges.Contains(c.AsRange())))
.ToArray();
var cells = from c in cellsUsed
let value = c.GetString()
where TagExtensions.HasTag(value)
select c;
let value = c.GetString()
where TagExtensions.HasTag(value)
select c;

if (!_tags.ContainsKey(rangeName))
_tags.Add(rangeName, new TagsList(_errors));

foreach (var cell in cells)
{
string value = cell.GetString();
OptionTag[] tags;
string newValue;
var templateCell = new TemplateCell(cell.Address.RowNumber, cell.Address.ColumnNumber, cell);
if (value.StartsWith("&="))
{
tags = _tagsEvaluator.Parse(value.Substring(2), range, templateCell, out newValue);
cell.FormulaA1 = newValue;
}
else
{
tags = _tagsEvaluator.Parse(value, range, templateCell, out newValue);
cell.Value = newValue;
}
OptionTag[] tags = _tagsEvaluator.ApplyTagsTo(cell, range);
_tags[rangeName].AddRange(tags);
}
}
Expand All @@ -88,24 +76,36 @@ public void CopyTags(string srcRangeName, string destRangeName, IXLRange destRan
_tags[destRangeName].AddRange(srcTags.CopyTo(destRange));
}

/// <summary>
/// <para>
/// Apply variables to the template ranges and template cells in the <paramref name="range"/>.
/// </para>
/// </summary>
public virtual void EvaluateValues(IXLRange range, params Parameter[] pars)
{
foreach (var parameter in pars)
{
AddParameter(parameter.Value);
}
var innerRanges = range.GetContainingNames()
.Select(BindToVariable)
.Where(nr => nr != null)
.ToArray();

var cells = range.CellsUsed()
// Get all defined names in the `range` that with the data from variables
var boundRanges = new List<BoundRange>();
foreach (var candidateName in range.GetContainingNames())
{
if (TryBindToVariable(candidateName, out var boundRange))
boundRanges.Add(boundRange);
}

// Get cells that should be templated, but aren't part of a bounded range.
var cellsToTemplate = range.CellsUsed()
.Where(c => !c.HasFormula
&& c.GetString().Contains("{{")
&& !innerRanges.Any(nr => nr.NamedRange.Ranges.Contains(c.AsRange())))
&& !boundRanges.Any(nr => nr.DefinedName.Ranges.Contains(c.AsRange())))
.ToArray();

foreach (var cell in cells)
// Apply template to the cell content, i.e. value, rich text, formula, comment or hyperlink.
// Unlike bound ranges, this doesn't change position of a cell, so it should be done first.
foreach (var cell in cellsToTemplate)
{
string value = cell.GetString();
try
Expand Down Expand Up @@ -171,39 +171,36 @@ string EvalString(string str)
}
}

foreach (var nr in innerRanges)
// Render bound ranges
foreach (var nr in boundRanges)
{
foreach (var rng in nr.NamedRange.Ranges)
foreach (var rng in nr.DefinedName.Ranges)
{
var growedRange = rng.GrowToMergedRanges();
var grownRange = rng.GrowToMergedRanges();
var items = nr.RangeData as object[] ?? nr.RangeData.Cast<object>().ToArray();
if (!items.Any())

if (!items.Any() && grownRange.IsOptionsRowEmpty())
{
if (growedRange.IsOptionsRowEmpty())
{
growedRange.Delete(XLShiftDeletedCells.ShiftCellsUp);
}
else
{
var rangeWithoutOptionsRow = growedRange.Worksheet
.Range(growedRange.FirstCell(), growedRange.LastCell().CellAbove());
if (growedRange.Worksheet.Tables.Any(t => t.Contains(rangeWithoutOptionsRow)))
growedRange.Clear();
else
rangeWithoutOptionsRow.Delete(XLShiftDeletedCells.ShiftCellsUp);
}
// Related to #251. I am pretty sure this is wrong solution and dealing with empty items
// should be done through RangeTemplate below. But if there are no items and empty option
// row, the result is degenerated A1 rendered range in temp sheet. Deleting (empty) options
// row would thus delete only first cell, not full (empty) options row.
grownRange.Delete(XLShiftDeletedCells.ShiftCellsUp);
continue;
}
var tplt = RangeTemplate.Parse(nr.NamedRange.Name, growedRange, _errors, _variables);
using (var buff = tplt.Generate(items))

// Range template generates output into a new temporary sheet, as not to affect other things
// and then copies it to the range in the original sheet.
var rangeTemplate = RangeTemplate.Parse(nr.DefinedName.Name, grownRange, _errors, _variables);
using (var renderedBuffer = rangeTemplate.Generate(items))
{
var ranges = nr.NamedRange.Ranges;
var trgtRng = buff.CopyTo(growedRange);
var ranges = nr.DefinedName.Ranges;
var trgtRng = renderedBuffer.CopyTo(grownRange);
ranges.Remove(rng);
ranges.Add(trgtRng);
nr.NamedRange.SetRefersTo(ranges);
nr.DefinedName.SetRefersTo(ranges);

tplt.RangeTagsApply(trgtRng, items);
rangeTemplate.RangeTagsApply(trgtRng, items);
var isOptionsRowEmpty = trgtRng.IsOptionsRowEmpty();
if (isOptionsRowEmpty)
trgtRng.LastRow().Delete(XLShiftDeletedCells.ShiftCellsUp);
Expand Down Expand Up @@ -245,30 +242,38 @@ public void AddVariable(string alias, object value)
_evaluator.AddVariable(alias, value);
}

private BoundRange BindToVariable(IXLNamedRange namedRange)
private bool TryBindToVariable(IXLDefinedName variableName, out BoundRange boundRange)
{
if (_variables.TryGetValue(namedRange.Name, out var variableValue) &&
if (_variables.TryGetValue(variableName.Name, out var variableValue) &&
variableValue is IEnumerable data1)
return new BoundRange(namedRange, data1);
{
boundRange = new BoundRange(variableName, data1);
return true;
}

var expression = "{{" + namedRange.Name.Replace("_", ".") +"}}";
var expression = "{{" + variableName.Name.Replace("_", ".") + "}}";

if (_evaluator.TryEvaluate(expression, out var res) &&
res is IEnumerable data2)
return new BoundRange(namedRange, data2);
{
boundRange = new BoundRange(variableName, data2);
return true;
}

return null;
boundRange = null;
return false;
}

[DebuggerDisplay("Bound variable: {DefinedName.Name}")]
private class BoundRange
{
public IXLNamedRange NamedRange { get; }
public IXLDefinedName DefinedName { get; }

public IEnumerable RangeData { get; }

public BoundRange(IXLNamedRange namedRange, IEnumerable rangeData)
public BoundRange(IXLDefinedName definedName, IEnumerable rangeData)
{
NamedRange = namedRange;
DefinedName = definedName;
RangeData = rangeData;
}
}
Expand Down
Loading
Loading