Skip to content

Commit 4f9798d

Browse files
authored
Bugfix/#136 nested horiz rng (#221)
* #136 fix nested horizontal ranges
1 parent b165f8a commit 4f9798d

File tree

9 files changed

+199
-69
lines changed

9 files changed

+199
-69
lines changed

ClosedXML.Report/Excel/TempSheetBuffer.cs

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,11 +81,19 @@ public void NewRow()
8181
_minRow = _row;
8282
}
8383

84-
public void NewColumn()
84+
public void NewRow(IXLAddress startAddr)
8585
{
8686
if (_clmn > 1)
8787
_clmn--;
88-
ChangeAddress(1, _clmn + 1);
88+
ChangeAddress(_row + 1, startAddr.ColumnNumber);
89+
_minRow = _row;
90+
}
91+
92+
public void NewColumn(IXLAddress startAddr)
93+
{
94+
if (_clmn > 1)
95+
_clmn--;
96+
ChangeAddress(startAddr.RowNumber, _clmn + 1);
8997
_minClmn = _clmn;
9098
}
9199

@@ -110,8 +118,7 @@ private void ChangeAddress(int row, int clmn)
110118
public IXLRange CopyTo(IXLRange range)
111119
{
112120
var firstCell = _sheet.Cell(1, 1);
113-
var lastCell = _sheet.Cell(_prevrow, _prevclmn);
114-
var tempRng = _sheet.Range(firstCell, lastCell);
121+
var tempRng = _sheet.Range(firstCell, LastCell);
115122

116123
var rowDiff = tempRng.RowCount() - range.RowCount();
117124
if (rowDiff > 0)
@@ -163,6 +170,17 @@ public IXLRange CopyTo(IXLRange range)
163170
return range;
164171
}
165172

173+
public IXLCell LastCell
174+
{
175+
get
176+
{
177+
var rowNumber = Math.Max(_prevrow, _sheet.RowsUsed().LastOrDefault()?.RowNumber() ?? 1);
178+
var columnNumber = Math.Max(_prevclmn, _sheet.ColumnsUsed().LastOrDefault()?.ColumnNumber() ?? 1);
179+
var lastCell = GetCell(rowNumber, columnNumber); //_sheet.Cell(_prevrow, _prevclmn);
180+
return lastCell;
181+
}
182+
}
183+
166184
public void SetPrevCellToLastUsed()
167185
{
168186
var lastUsed = _sheet.LastCellUsed();

ClosedXML.Report/RangeTemplate.cs

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,11 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
171171
var rangeStart = _buff.NextAddress;
172172
for (int i = 0; i < items.Length; i++)
173173
{
174-
var rowStart = _buff.NextAddress;
174+
var startAddr = _buff.NextAddress;
175175
IXLAddress rowEnd = null;
176176
int row = 1;
177177
var tags = _tags.CopyTo(_rowRange);
178+
var renderedSubranges = new List<string>();
178179

179180
// render row cells
180181
for (var iCell = 0; iCell < _cells.Count; iCell++)
@@ -185,13 +186,19 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
185186

186187
if (cell.CellType == TemplateCellType.None)
187188
{
188-
RenderSubrange(items[i], evaluator, cell, tags, ref iCell, ref row);
189+
var xlCell = _rowRange.Cell(cell.Row, cell.Column);
190+
var ownRng = _subranges.First(r => r._cells.Any(c => c.CellType != TemplateCellType.None && c.XLCell != null && Equals(c.XLCell.Address, xlCell.Address)));
191+
if (!renderedSubranges.Contains(ownRng.Name))
192+
{
193+
RenderSubrange(ownRng, items[i], evaluator, cell, tags, ref iCell, ref row);
194+
renderedSubranges.Add(ownRng.Name);
195+
}
189196
}
190197
else if (cell.CellType == TemplateCellType.NewRow)
191198
{
192199
row++;
193200
rowEnd = _buff.PrevAddress;
194-
_buff.NewRow();
201+
_buff.NewRow(startAddr);
195202
if (row > _rowCnt)
196203
break;
197204
}
@@ -201,7 +208,7 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
201208
}
202209
}
203210

204-
var newRowRng = _buff.GetRange(rowStart, rowEnd);
211+
var newRowRng = _buff.GetRange(startAddr, rowEnd);
205212
foreach (var mrg in _mergedRanges.Where(r=>!_optionsRow.Contains(r)))
206213
{
207214
var newMrg = mrg.Relative(_rowRange, newRowRng);
@@ -218,7 +225,7 @@ private void VerticalTable(object[] items, FormulaEvaluator evaluator)
218225
{
219226
RenderCell(evaluator, cell);
220227
}
221-
_buff.NewRow();
228+
_buff.NewRow(rangeStart);
222229
}
223230

224231
// Execute range options tags
@@ -318,12 +325,11 @@ private void RenderCell(object[] items, int i, FormulaEvaluator evaluator, Templ
318325
RenderCell(evaluator, cell, new Parameter("item", items[i]), new Parameter("index", i));
319326
}
320327

321-
private void RenderSubrange(object item, FormulaEvaluator evaluator, TemplateCell cell, TagsList tags, ref int iCell, ref int row)
328+
private void RenderSubrange(RangeTemplate ownRng, object item, FormulaEvaluator evaluator, TemplateCell cell,
329+
TagsList tags, ref int iCell, ref int row)
322330
{
323331
var start = _buff.NextAddress;
324332
// the child template to which the cell belongs
325-
var xlCell = _rowRange.Cell(cell.Row, cell.Column);
326-
var ownRng = _subranges.First(r => r._cells.Any(c => c.CellType != TemplateCellType.None && c.XLCell != null && Equals(c.XLCell.Address, xlCell.Address)));
327333
var formula = ownRng.Source.ReplaceLast("_", ".");
328334

329335
if (evaluator.Evaluate(formula, new Parameter(Name, item)) is IEnumerable value)
@@ -333,7 +339,6 @@ private void RenderSubrange(object item, FormulaEvaluator evaluator, TemplateCel
333339

334340
if (ownRng.IsHorizontal)
335341
{
336-
iCell += ownRng._colCnt - 1;
337342
int shiftLen = ownRng._colCnt * (valArr.Length - 1);
338343
tags.Where(tag => tag.Cell.Row == cell.Row && tag.Cell.Column > cell.Column)
339344
.ForEach(t =>
@@ -373,18 +378,18 @@ private void HorizontalTable(object[] items, FormulaEvaluator evaluator)
373378
var tags = _tags.CopyTo(_rowRange);
374379
for (int i = 0; i < items.Length; i++)
375380
{
376-
var clmnStart = _buff.NextAddress;
381+
var startAddr = _buff.NextAddress;
377382
foreach (var cell in _cells)
378383
{
379384
if (cell.CellType == TemplateCellType.None)
380385
throw new NotSupportedException("Horizontal range does not support subranges.");
381386
else if (cell.CellType != TemplateCellType.NewRow)
382387
RenderCell(items, i, evaluator, cell);
383388
else
384-
_buff.NewRow();
389+
_buff.NewRow(startAddr);
385390
}
386391

387-
var newClmnRng = _buff.GetRange(clmnStart, _buff.PrevAddress);
392+
var newClmnRng = _buff.GetRange(startAddr, _buff.PrevAddress);
388393
foreach (var mrg in _mergedRanges.Where(r => _optionsRow == null || !_optionsRow.Contains(r)))
389394
{
390395
var newMrg = mrg.Relative(_rowRange, newClmnRng);
@@ -394,7 +399,7 @@ private void HorizontalTable(object[] items, FormulaEvaluator evaluator)
394399
tags.Execute(new ProcessingContext(newClmnRng, items[i], evaluator));
395400

396401
if (_rowCnt > 1)
397-
_buff.NewColumn();
402+
_buff.NewColumn(startAddr);
398403
}
399404

400405
var worksheet = _rowRange.Worksheet;

tests/ClosedXML.Report.Tests/ClosedXML.Report.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
</ItemGroup>
3030

3131
<ItemGroup>
32+
<PackageReference Include="Bogus" Version="33.0.2" />
3233
<PackageReference Include="DocumentFormat.OpenXml" Version="2.7.2" />
3334
<PackageReference Include="FluentAssertions" Version="4.18.0" />
3435
<PackageReference Include="linq2db" Version="1.0.7.1" />

tests/ClosedXML.Report.Tests/ReportOptionsTests.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,19 @@ public void ColsFit_option_should_FitWidth()
5959
[Fact]
6060
public void Sort_option_should_sort_range()
6161
{
62+
var testEntities = TestEntity.GetTestData(6).ToArray();
6263
XlTemplateTest("8_sort.xlsx",
6364
tpl => tpl.AddVariable(new
6465
{
65-
data = TestEntity.GetTestData(6),
66+
data = testEntities,
6667
dates = new[] { DateTime.Parse("2013-01-01"), DateTime.Parse("2013-01-02"), DateTime.Parse("2013-01-03") }
6768
}),
6869
wb =>
6970
{
7071
var worksheet = wb.Worksheet(1);
71-
worksheet.Range("D5:D10").Cells().Select(x=>x.GetValue<int>()).ToArray().Should().ContainInOrder(new [] { 37, 31, 71, 24, 63, 76});
72-
worksheet.Range("E5:E10").Cells().Select(x=>x.GetString()).ToArray().Should().ContainInOrder("Dallas", "Miami", "Montana", "NY", "NY", "Oklahoma");
72+
var expectedOrder = testEntities.OrderBy(x=>x.Address.City).ThenBy(x=>x.Age).ToArray();
73+
worksheet.Range("D5:D10").Cells().Select(x=>x.GetValue<int>()).ToArray().Should().ContainInOrder(expectedOrder.Select(x => x.Age));
74+
worksheet.Range("E5:E10").Cells().Select(x=>x.GetString()).ToArray().Should().ContainInOrder(expectedOrder.Select(x => x.Address.City));
7375
});
7476
}
7577

tests/ClosedXML.Report.Tests/TestModels/Address.cs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@ namespace ClosedXML.Report.Tests.TestModels
22
{
33
public class Address
44
{
5-
/// <inheritdoc />
5+
public Address()
6+
{
7+
}
8+
69
public Address(string country, string city, string street)
710
{
811
City = city;
@@ -14,4 +17,4 @@ public Address(string country, string city, string street)
1417
public string City { get; set; }
1518
public string Street { get; set; }
1619
}
17-
}
20+
}
Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,58 @@
11
using System.Collections.Generic;
2-
using System.Linq;
2+
using Bogus;
33

44
namespace ClosedXML.Report.Tests.TestModels
55
{
6+
public class TestOrder
7+
{
8+
public string OrderNumber { get; set; }
9+
public List<OrderItem> ProductsWithQuantities { get; set; } = new List<OrderItem>();
10+
11+
public TestOrder()
12+
{
13+
}
14+
15+
public TestOrder(string orderNumber)
16+
{
17+
OrderNumber = orderNumber;
18+
}
19+
20+
public static IEnumerable<TestOrder> GetTestData(int rowCount)
21+
{
22+
var products = new[]
23+
{
24+
"Brioche", "Tart taten", "Apple pie", "Creamy croissants", "Toast with cream", "Scramble croissant",
25+
"Beunier donuts", "Profiteroles with Mascarpone", "Creme de parisien", "Chocolate Fondant", "Quiche with bacon",
26+
};
27+
28+
var orderItemFaker = new Faker<OrderItem>()
29+
.RuleFor(x => x.ProductName, f => f.PickRandom(products))
30+
.RuleFor(x => x.Quantity, f => f.Random.Number(1, 50));
31+
32+
var orderFaker = new Faker<TestOrder>()
33+
.RuleFor(x => x.OrderNumber, f => f.Random.Number(100000, 999999).ToString())
34+
.RuleFor(x => x.ProductsWithQuantities, f => orderItemFaker.Generate(f.Random.Number(2, 10)));
35+
36+
return orderFaker.Generate(rowCount);
37+
}
38+
}
39+
40+
public class OrderItem
41+
{
42+
public OrderItem()
43+
{
44+
}
45+
46+
public OrderItem(string productName, decimal quantity)
47+
{
48+
ProductName = productName;
49+
Quantity = quantity;
50+
}
51+
52+
public string ProductName { get; set; }
53+
public decimal Quantity { get; set; }
54+
}
55+
656
public class TestEntity
757
{
858
public string Name { get; set; }
@@ -11,6 +61,10 @@ public class TestEntity
1161
public int[] Hours { get; set; }
1262
public Address Address { get; set; }
1363

64+
public TestEntity()
65+
{
66+
}
67+
1468
public TestEntity(string name, string role, int age, int[] hours)
1569
{
1670
Hours = hours;
@@ -21,15 +75,21 @@ public TestEntity(string name, string role, int age, int[] hours)
2175

2276
public static IEnumerable<TestEntity> GetTestData(int rowCount)
2377
{
24-
return new[]
25-
{
26-
new TestEntity("John Smith", "Developer", 24, new [] { 6, 8, 4 }) {Address = new Address("USA", "NY", "94, Reade St")},
27-
new TestEntity("James Smith", "Analyst", 37, new [] { 3, 5, 7 }) {Address = new Address("USA", "Dallas", "5, Ross ave")},
28-
new TestEntity("Jim Smith", "Manager", 31, new[] { 2, 9, 1 }) {Address = new Address("USA", "Miami", "16, Indian Creek Dr")},
29-
new TestEntity("Chuck Norris", "Actor", 76, new [] { 7, 14, 2 }) {Address = new Address("USA", "Oklahoma", "9, Reade Rd")},
30-
new TestEntity("Dirk Benedict", "Actor", 71, new [] { 4, 9, 1 }) {Address = new Address("USA", "Montana", "7, Ross St, Helena")},
31-
new TestEntity("Kenneth Lauren Burns", "Producer", 63, new[] { 9, 1, 2 }) {Address = new Address("USA", "NY", "13, Indian Creek Dr, Brooklyn")},
32-
}.Take(rowCount);
78+
//var roles = new[] { "Developer", "Analyst", "Manager", "Actor", "Producer" };
79+
var addressFaker = new Faker<Address>()
80+
.RuleFor(o => o.Country, f => f.Address.Country())
81+
.RuleFor(o => o.City, f => f.Address.City())
82+
.RuleFor(o => o.Street, f => f.Address.StreetAddress());
83+
84+
var testEntity = new Faker<TestEntity>()
85+
.StrictMode(true)
86+
.RuleFor(o => o.Name, f => f.Name.FullName())
87+
.RuleFor(o => o.Role, f => f.Name.JobTitle())
88+
.RuleFor(o => o.Age, f => f.Random.Number(20, 70))
89+
.RuleFor(o => o.Address, () => addressFaker)
90+
.RuleFor(o => o.Hours, f => new []{ f.Random.Number(2, 14), f.Random.Number(2, 14), f.Random.Number(2, 14) });
91+
92+
return testEntity.Generate(rowCount);
3393
}
3494
}
35-
}
95+
}

0 commit comments

Comments
 (0)