Unrect 0.3.0-alpha.2
dotnet add package Unrect --version 0.3.0-alpha.2
NuGet\Install-Package Unrect -Version 0.3.0-alpha.2
<PackageReference Include="Unrect" Version="0.3.0-alpha.2" />
<PackageVersion Include="Unrect" Version="0.3.0-alpha.2" />
<PackageReference Include="Unrect" />
paket add Unrect --version 0.3.0-alpha.2
#r "nuget: Unrect, 0.3.0-alpha.2"
#:package Unrect@0.3.0-alpha.2
#addin nuget:?package=Unrect&version=0.3.0-alpha.2&prerelease
#tool nuget:?package=Unrect&version=0.3.0-alpha.2&prerelease
Unrect
Financial and operational reports live in spreadsheets, and spreadsheets are flat 2D grids carrying hierarchical, heterogeneous data: a header, a summary table, N repeating client blocks each with its own sub-sections. Row-oriented parsers handle this badly — they devolve into stateful cursor logic and index arithmetic that breaks the moment a row shifts. Unrect takes a different approach: you declare the shape of the data — a header, a table bound to a record type, a repeating series bounded by a caption — and the framework decomposes the grid and projects it into typed objects. You never write a loop that walks the grid deciding what comes next.
Install
dotnet add package Unrect
dotnet add package Unrect.Spreadsheets
Unrect is the engine — the projection vocabulary, the layout composites, the strategies
that decide boundaries — and works directly over any 2D grid you can adapt to ISpace.
Unrect.Spreadsheets adds the adapters that read spreadsheet files — .xls/.xlsx today — straight into that grid;
add it when your data lives in a workbook rather than an array you built yourself.
The same package also has a streaming door, Workbook, for files too large to read
whole — see Large files below.
GridSpace ships in the Unrect package for exactly that case:
GridSpace.Create(values, isBlank: ...) turns a plain 2D array into a space, deciding
there and then what counts as empty. It needs nothing else installed, which makes it the
way to build test fixtures and scripted data without a workbook in sight.
Show me the code
A report with a typed header, a summary table, and the same repeating per-investor block appearing twice under two different captions:
Fund IRR Report
Example Fund I 2026-06-30
--------------------------------------------------
Investors | Contribution ITD | ... | Irr
--------------------------------------------------
Cash Flows Using Transfer Date
[investor block] [investor block] ...
Cash Flows using inception date
[investor block] [investor block] ...
using Unrect.Spreadsheets;
using static Unrect.Projections.Projection;
var header = VerticalFlow(v => new
{
Title = v.Next(Text()),
Fund = v.Next(Text()),
ReportDate = v.Next(Date()),
});
// Captions bind to record properties by name (case- and whitespace-insensitive).
var summary = Table<SummaryRow>(bind => bind.Column(r => r.Investor, "Investors"));
var investorBlock = Table<CashFlow>();
// Declared once, placed twice — .Until bounds the first series so it stops at the
// second caption instead of trying to parse it as another investor block.
var series = VerticalRepeat(investorBlock, separatedBy: BlankRows());
const string Inception = "Cash Flows using inception date";
var byTransferDate = series
.Under(Caption("Cash Flows Using Transfer Date"))
.Until(RowContaining(Inception));
var byInception = series.Under(Caption(Inception));
var report = VerticalFlow(v => new
{
Header = v.Next(header),
Summary = v.Next(summary),
ByTransferDate = v.Next(byTransferDate),
ByInception = v.Next(byInception),
});
var result = report.Map(SpreadsheetSpace.Create("irr-report.xlsx", "IRR"));
record SummaryRow(string Investor, decimal ContributionItd, decimal DistributionItd,
decimal ManagementFeeItd, decimal EndBalance, double Irr);
record CashFlow(string InvestorName, DateTime Date, string Transaction, double Irr);
The ideas
- Projections are reusable, immutable values. Declare
reportonce, apply it to as many workbooks as you have —workbooks.Select(report.Map). - Diagnostics carry a declaration path and an A1 cell location — a failure tells you which projection it came from and exactly where on the sheet it happened.
- Names are inferred from your own identifiers. The local you assign a projection to
(
series,byTransferDate) is what shows up in its diagnostics — no separate naming step. - Tolerance is declared per projection, never ambient.
.Optional()and.Else()mark exactly where a missing or malformed region is acceptable; nothing is silently lenient everywhere. - Content anchors survive layout drift.
.On,.Below,.RightOf,Caption, and.Untilfind their place by what a row or column says, not by a hard-coded offset that breaks the next time someone inserts a row.
Large files
SpreadsheetSpace.Create reads a sheet whole, which is the simple default and the right
choice for anything that fits comfortably in memory. For a file too big for that, or the
same declaration applied to many files in sequence, Workbook reads a window at a time
instead of the whole grid:
var report = VerticalFlow(v => ...); // one declaration, reused
foreach (var path in monthlyCloseOfFunds)
{
using var book = Workbook.Open(path);
Publish(report.Map(book.Sheet("Detail"))); // bounded memory per iteration
}
Same projections, same results — the two paths differ only in the shape of their cost. A
monotone read through Workbook costs about 35% more wall time for about 2.7× less live
memory than SpreadsheetSpace.Create; a declaration that reaches backwards or sweeps a
band wider than its window can cost more than that, which book.Statistics("Detail")
will tell you. Projections are immutable and workbooks are independent, so
Parallel.ForEach(monthlyCloseOfFunds, path => { using var book = ...; }) needs nothing
added. The full guide, including the sizing law and the statistics to act on: docs/streaming.md.
Learn more
docs/vocabulary.md— the full operator survey, grouped by role.docs/streaming.md— theWorkbookguide: when to reach for it, the lifecycle rules, the sizing law, and the statistics vocabulary.docs/design/— the specs behind the vocabulary (layout, matching, tables, diagnostics, streaming).linqpad/— worked examples against the workbooks inexamples/, including the report above (linqpad/investor-irr.linq).
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 was computed. net5.0-windows was computed. net6.0 was computed. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 was computed. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp2.0 was computed. netcoreapp2.1 was computed. netcoreapp2.2 was computed. netcoreapp3.0 was computed. netcoreapp3.1 was computed. |
| .NET Standard | netstandard2.0 is compatible. netstandard2.1 is compatible. |
| .NET Framework | net461 was computed. net462 was computed. net463 was computed. net47 was computed. net471 was computed. net472 was computed. net48 was computed. net481 was computed. |
| MonoAndroid | monoandroid was computed. |
| MonoMac | monomac was computed. |
| MonoTouch | monotouch was computed. |
| Tizen | tizen40 was computed. tizen60 was computed. |
| Xamarin.iOS | xamarinios was computed. |
| Xamarin.Mac | xamarinmac was computed. |
| Xamarin.TVOS | xamarintvos was computed. |
| Xamarin.WatchOS | xamarinwatchos was computed. |
-
.NETStandard 2.0
- No dependencies.
-
.NETStandard 2.1
- No dependencies.
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Unrect:
| Package | Downloads |
|---|---|
|
Unrect.Spreadsheets
The file-format adapters for Unrect. Reads spreadsheet files — .xls and .xlsx today — and presents each sheet as an Unrect space, so a declared projection can be applied to a real workbook. Blankness is decided here, at the point data enters the system. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.3.0-alpha.2 | 49 | 9/9/2026 |
| 0.3.0-alpha.1 | 71 | 9/7/2026 |
| 0.2.0-alpha.1 | 62 | 9/6/2026 |
| 0.1.0-alpha.4 | 64 | 9/5/2026 |
| 0.1.0-alpha.3 | 63 | 9/5/2026 |
| 0.1.0-alpha.2 | 73 | 9/4/2026 |
| 0.1.0-alpha.1 | 72 | 9/2/2026 |