Skip to content

Commit d483683

Browse files
jahavPankraty
andauthored
Update to 0.105.0-rc (#368)
* Update ClosedXML to 0.104.1, test fail --------- Co-authored-by: Aleksei <pankraty@gmail.com>
1 parent fd6e29a commit d483683

File tree

77 files changed

+227
-172
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+227
-172
lines changed

ClosedXML.Report.sln.DotSettings

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<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">
2+
<s:Boolean x:Key="/Default/UserDictionary/Words/=subrange/@EntryIndexedValue">True</s:Boolean>
3+
<s:Boolean x:Key="/Default/UserDictionary/Words/=subranges/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

ClosedXML.Report/ClosedXML.Report.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
</PropertyGroup>
3838

3939
<ItemGroup>
40-
<PackageReference Include="ClosedXML" Version="0.102.3" />
40+
<PackageReference Include="ClosedXML" Version="0.105.0-rc" />
4141
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
4242
<PackageReference Include="morelinq" Version="4.4.0" />
4343
<PackageReference Include="System.Linq.Dynamic.Core" Version="1.6.0.2" />

ClosedXML.Report/Excel/Subtotal.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -509,7 +509,7 @@ private void RecalculateGroups(MoveData[] extendedGroups, bool extendAtEnd)
509509

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

533533
private string ShiftFormula(string formula, int rowCount)
534534
{
535-
var pars = _range.Worksheet.GetRangeParameters(formula).Where(r => r.Key.Contains(":"));
535+
var pars = _range.Worksheet.GetRangeParameters(formula);
536536
foreach (var addr in pars)
537537
{
538538
var sheet = addr.Value.Worksheet;

ClosedXML.Report/Excel/TempSheetBuffer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,7 +204,7 @@ public void Clear()
204204

205205
public void Dispose()
206206
{
207-
var namedRanges = _wb.NamedRanges
207+
var namedRanges = _wb.DefinedNames
208208
.Where(nr => nr.Ranges.Any(r => r.Worksheet?.Name == SheetName))
209209
.ToList();
210210
namedRanges.ForEach(nr => nr.Delete());

ClosedXML.Report/Excel/XlExtensions.cs

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ public static class XlExtensions
3939
/// Find ranges within which contains the specified range (completely).
4040
/// </summary>
4141
/// <param name="range">range</param>
42-
public static IEnumerable<IXLNamedRange> GetContainerNames(this IXLRange range)
42+
public static IEnumerable<IXLDefinedName> GetContainerNames(this IXLRange range)
4343
{
44-
return range.Worksheet.Workbook.NamedRanges.Where(x => GetContainingRanges(x, range))
45-
.Union(range.Worksheet.NamedRanges.Where(x => GetContainingRanges(x, range)));
44+
return range.Worksheet.Workbook.DefinedNames.Where(x => GetContainingRanges(x, range))
45+
.Union(range.Worksheet.DefinedNames.Where(x => GetContainingRanges(x, range)));
4646
}
4747

4848
public static bool Contains(this IXLRangeAddress rangeAddress, IXLAddress address)
@@ -121,14 +121,14 @@ internal static KeyValuePair<string, IXLRangeAddress>[] GetRangeParameters(this
121121
/// Get the named ranges that contains the specified range (completely).
122122
/// </summary>
123123
/// <param name="range">range</param>
124-
public static IEnumerable<IXLNamedRange> GetContainingNames(this IXLRange range)
124+
public static IEnumerable<IXLDefinedName> GetContainingNames(this IXLRange range)
125125
{
126-
return range.Worksheet.NamedRanges
127-
.Union(range.Worksheet.Workbook.NamedRanges)
126+
return range.Worksheet.DefinedNames
127+
.Union(range.Worksheet.Workbook.DefinedNames)
128128
.Where(x => GetContainingRanges(x, range));
129129
}
130130

131-
private static bool GetContainingRanges(IXLNamedRange x, IXLRange xlRange)
131+
private static bool GetContainingRanges(IXLDefinedName x, IXLRange xlRange)
132132
{
133133
return x.Ranges.Select(GrowToMergedRanges)
134134
.Where(r => r.Worksheet.Position == xlRange.Worksheet.Position && !r.Equals(xlRange))
@@ -142,7 +142,8 @@ public static IXLRange GrowToMergedRanges(this IXLRange range)
142142
.ForEach(x =>
143143
{
144144
var xlCells = range.Union(x).Select(c => c.Address)
145-
.OrderBy(c => c.RowNumber).ThenBy(c => c.ColumnNumber);
145+
.OrderBy(c => c.RowNumber).ThenBy(c => c.ColumnNumber)
146+
.ToList();
146147
range = sheet.Range(xlCells.First().ToStringFixed(), xlCells.Last().ToStringFixed());
147148
});
148149
return range;

ClosedXML.Report/Options/PivotTag.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ private IXLPivotTable CreatePivot(PivotTag pivot, ProcessingContext context, XLP
195195
pt.MergeAndCenterWithLabels = pivot.HasParameter("MergeLabels");
196196
pt.ShowExpandCollapseButtons = pivot.HasParameter("ShowButtons");
197197
pt.ClassicPivotTableLayout = !pivot.HasParameter("TreeLayout");
198+
pt.Layout = XLPivotLayout.Tabular;
198199
pt.AutofitColumns = pivot.HasParameter("AutofitColumns");
199200
pt.SortFieldsAtoZ = !pivot.HasParameter("NoSort");
200201
pt.PreserveCellFormatting = !pivot.HasParameter("NoPreserveFormatting");

ClosedXML.Report/Options/SortTag.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ public override void Execute(ProcessingContext context)
2929
var fields = List.GetAll<SortTag>().ToArray();
3030
foreach (var tag in fields.OrderBy(x => x.Num).ThenBy(x => x.Column))
3131
{
32-
context.Range.SortColumns.Add(tag.Column, tag.Order);
32+
// Ignore blanks is a legacy option, but basically it means treat blanks as blanks
33+
// and blanks are always at the end (regardless of sorting order). The value `false`
34+
// would treat blanks as empty strings (i.e. sorted at the beginning instead of the end).
35+
context.Range.SortColumns.Add(tag.Column, tag.Order, ignoreBlanks: true);
3336
}
3437
context.Range.Sort();
3538

ClosedXML.Report/Options/TagsEvaluator.cs

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Generic;
23
using System.Linq;
34
using System.Text.RegularExpressions;
45
using ClosedXML.Excel;
@@ -16,10 +17,61 @@ private IEnumerable<string> GetAllTags(string cellValue)
1617
return from Match match in matches select match.Value;
1718
}
1819

19-
public OptionTag[] Parse(string value, IXLRange range, TemplateCell cell, out string newValue)
20+
/// <summary>
21+
/// Apply tags to the <paramref name="cell"/>, if it contains tags.
22+
/// </summary>
23+
/// <param name="cell">Template cell that *might* contain tags (doesn't have to).</param>
24+
/// <param name="range">Range each option will be associated with.</param>
25+
/// <returns>Created tags for the cell (may be empty).</returns>
26+
public OptionTag[] ApplyTagsTo(TemplateCell cell, IXLRange range)
27+
{
28+
OptionTag[] tags = Array.Empty<OptionTag>();
29+
if (cell.CellType == TemplateCellType.Formula)
30+
{
31+
tags = Parse(cell.Formula, range, cell, out var newValue);
32+
cell.Formula = newValue;
33+
}
34+
else if (cell.CellType == TemplateCellType.Value)
35+
{
36+
// Only text values can contain tags. Therefore skip all other types.
37+
if (cell.Value.TryGetText(out var text))
38+
{
39+
tags = Parse(text, range, cell, out var newValue);
40+
cell.Value = newValue == string.Empty ? Blank.Value : newValue;
41+
}
42+
}
43+
else
44+
{
45+
// Other template cell types shouldn't even get here
46+
cell.Value = Blank.Value;
47+
}
48+
49+
return tags;
50+
}
51+
52+
public OptionTag[] ApplyTagsTo(IXLCell cell, IXLRange range)
53+
{
54+
string value = cell.GetString();
55+
OptionTag[] tags;
56+
var templateCell = new TemplateCell(cell.Address.RowNumber, cell.Address.ColumnNumber, cell);
57+
if (value.StartsWith("&="))
58+
{
59+
tags = Parse(value.Substring(2), range, templateCell, out var newValue);
60+
cell.FormulaA1 = newValue;
61+
}
62+
else
63+
{
64+
tags = Parse(value, range, templateCell, out var newValue);
65+
cell.Value = newValue;
66+
}
67+
68+
return tags;
69+
}
70+
71+
private OptionTag[] Parse(string templateLiteral, IXLRange range, TemplateCell cell, out string newValue)
2072
{
2173
List<OptionTag> result = new List<OptionTag>();
22-
foreach (var expr in GetAllTags(value))
74+
foreach (var expr in GetAllTags(templateLiteral))
2375
{
2476
var optionTag = ParseTag(expr.Substring(2, expr.Length-4));
2577
if (optionTag == null)
@@ -31,9 +83,10 @@ public OptionTag[] Parse(string value, IXLRange range, TemplateCell cell, out st
3183
optionTag.RangeOptionsRow = range.LastRow().RangeAddress;
3284
}
3385
result.Add(optionTag);
34-
value = value.Replace(expr, "");
86+
templateLiteral = templateLiteral.Replace(expr, "");
3587
}
36-
newValue = value.Trim();
88+
89+
newValue = templateLiteral.Trim();
3790
return result.ToArray();
3891
}
3992

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

55108
return TagsRegister.CreateOption(name, dictionary);
56-
57109
}
58110
}
59111
}

ClosedXML.Report/Options/TagsList.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public void Execute(ProcessingContext context)
6969
{
7070
t.Execute(context);
7171
}
72-
catch(TemplateParseException ex)
72+
catch (TemplateParseException ex)
7373
{
7474
_errors.Add(new TemplateError(ex.Message, ex.Range));
7575
}

ClosedXML.Report/RangeInterpreter.cs

Lines changed: 61 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Linq;
56
using System.Reflection;
67
using ClosedXML.Excel;
@@ -44,29 +45,16 @@ public void ParseTags(IXLRange range, string rangeName)
4445
.Where(c => !c.HasFormula && !innerRanges.Any(nr => nr.Ranges.Contains(c.AsRange())))
4546
.ToArray();
4647
var cells = from c in cellsUsed
47-
let value = c.GetString()
48-
where TagExtensions.HasTag(value)
49-
select c;
48+
let value = c.GetString()
49+
where TagExtensions.HasTag(value)
50+
select c;
5051

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

5455
foreach (var cell in cells)
5556
{
56-
string value = cell.GetString();
57-
OptionTag[] tags;
58-
string newValue;
59-
var templateCell = new TemplateCell(cell.Address.RowNumber, cell.Address.ColumnNumber, cell);
60-
if (value.StartsWith("&="))
61-
{
62-
tags = _tagsEvaluator.Parse(value.Substring(2), range, templateCell, out newValue);
63-
cell.FormulaA1 = newValue;
64-
}
65-
else
66-
{
67-
tags = _tagsEvaluator.Parse(value, range, templateCell, out newValue);
68-
cell.Value = newValue;
69-
}
57+
OptionTag[] tags = _tagsEvaluator.ApplyTagsTo(cell, range);
7058
_tags[rangeName].AddRange(tags);
7159
}
7260
}
@@ -88,24 +76,36 @@ public void CopyTags(string srcRangeName, string destRangeName, IXLRange destRan
8876
_tags[destRangeName].AddRange(srcTags.CopyTo(destRange));
8977
}
9078

79+
/// <summary>
80+
/// <para>
81+
/// Apply variables to the template ranges and template cells in the <paramref name="range"/>.
82+
/// </para>
83+
/// </summary>
9184
public virtual void EvaluateValues(IXLRange range, params Parameter[] pars)
9285
{
9386
foreach (var parameter in pars)
9487
{
9588
AddParameter(parameter.Value);
9689
}
97-
var innerRanges = range.GetContainingNames()
98-
.Select(BindToVariable)
99-
.Where(nr => nr != null)
100-
.ToArray();
10190

102-
var cells = range.CellsUsed()
91+
// Get all defined names in the `range` that with the data from variables
92+
var boundRanges = new List<BoundRange>();
93+
foreach (var candidateName in range.GetContainingNames())
94+
{
95+
if (TryBindToVariable(candidateName, out var boundRange))
96+
boundRanges.Add(boundRange);
97+
}
98+
99+
// Get cells that should be templated, but aren't part of a bounded range.
100+
var cellsToTemplate = range.CellsUsed()
103101
.Where(c => !c.HasFormula
104102
&& c.GetString().Contains("{{")
105-
&& !innerRanges.Any(nr => nr.NamedRange.Ranges.Contains(c.AsRange())))
103+
&& !boundRanges.Any(nr => nr.DefinedName.Ranges.Contains(c.AsRange())))
106104
.ToArray();
107105

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

174-
foreach (var nr in innerRanges)
174+
// Render bound ranges
175+
foreach (var nr in boundRanges)
175176
{
176-
foreach (var rng in nr.NamedRange.Ranges)
177+
foreach (var rng in nr.DefinedName.Ranges)
177178
{
178-
var growedRange = rng.GrowToMergedRanges();
179+
var grownRange = rng.GrowToMergedRanges();
179180
var items = nr.RangeData as object[] ?? nr.RangeData.Cast<object>().ToArray();
180-
if (!items.Any())
181+
182+
if (!items.Any() && grownRange.IsOptionsRowEmpty())
181183
{
182-
if (growedRange.IsOptionsRowEmpty())
183-
{
184-
growedRange.Delete(XLShiftDeletedCells.ShiftCellsUp);
185-
}
186-
else
187-
{
188-
var rangeWithoutOptionsRow = growedRange.Worksheet
189-
.Range(growedRange.FirstCell(), growedRange.LastCell().CellAbove());
190-
if (growedRange.Worksheet.Tables.Any(t => t.Contains(rangeWithoutOptionsRow)))
191-
growedRange.Clear();
192-
else
193-
rangeWithoutOptionsRow.Delete(XLShiftDeletedCells.ShiftCellsUp);
194-
}
184+
// Related to #251. I am pretty sure this is wrong solution and dealing with empty items
185+
// should be done through RangeTemplate below. But if there are no items and empty option
186+
// row, the result is degenerated A1 rendered range in temp sheet. Deleting (empty) options
187+
// row would thus delete only first cell, not full (empty) options row.
188+
grownRange.Delete(XLShiftDeletedCells.ShiftCellsUp);
195189
continue;
196190
}
197-
var tplt = RangeTemplate.Parse(nr.NamedRange.Name, growedRange, _errors, _variables);
198-
using (var buff = tplt.Generate(items))
191+
192+
// Range template generates output into a new temporary sheet, as not to affect other things
193+
// and then copies it to the range in the original sheet.
194+
var rangeTemplate = RangeTemplate.Parse(nr.DefinedName.Name, grownRange, _errors, _variables);
195+
using (var renderedBuffer = rangeTemplate.Generate(items))
199196
{
200-
var ranges = nr.NamedRange.Ranges;
201-
var trgtRng = buff.CopyTo(growedRange);
197+
var ranges = nr.DefinedName.Ranges;
198+
var trgtRng = renderedBuffer.CopyTo(grownRange);
202199
ranges.Remove(rng);
203200
ranges.Add(trgtRng);
204-
nr.NamedRange.SetRefersTo(ranges);
201+
nr.DefinedName.SetRefersTo(ranges);
205202

206-
tplt.RangeTagsApply(trgtRng, items);
203+
rangeTemplate.RangeTagsApply(trgtRng, items);
207204
var isOptionsRowEmpty = trgtRng.IsOptionsRowEmpty();
208205
if (isOptionsRowEmpty)
209206
trgtRng.LastRow().Delete(XLShiftDeletedCells.ShiftCellsUp);
@@ -245,30 +242,38 @@ public void AddVariable(string alias, object value)
245242
_evaluator.AddVariable(alias, value);
246243
}
247244

248-
private BoundRange BindToVariable(IXLNamedRange namedRange)
245+
private bool TryBindToVariable(IXLDefinedName variableName, out BoundRange boundRange)
249246
{
250-
if (_variables.TryGetValue(namedRange.Name, out var variableValue) &&
247+
if (_variables.TryGetValue(variableName.Name, out var variableValue) &&
251248
variableValue is IEnumerable data1)
252-
return new BoundRange(namedRange, data1);
249+
{
250+
boundRange = new BoundRange(variableName, data1);
251+
return true;
252+
}
253253

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

256256
if (_evaluator.TryEvaluate(expression, out var res) &&
257257
res is IEnumerable data2)
258-
return new BoundRange(namedRange, data2);
258+
{
259+
boundRange = new BoundRange(variableName, data2);
260+
return true;
261+
}
259262

260-
return null;
263+
boundRange = null;
264+
return false;
261265
}
262266

267+
[DebuggerDisplay("Bound variable: {DefinedName.Name}")]
263268
private class BoundRange
264269
{
265-
public IXLNamedRange NamedRange { get; }
270+
public IXLDefinedName DefinedName { get; }
266271

267272
public IEnumerable RangeData { get; }
268273

269-
public BoundRange(IXLNamedRange namedRange, IEnumerable rangeData)
274+
public BoundRange(IXLDefinedName definedName, IEnumerable rangeData)
270275
{
271-
NamedRange = namedRange;
276+
DefinedName = definedName;
272277
RangeData = rangeData;
273278
}
274279
}

0 commit comments

Comments
 (0)