You don’t have to hand-write Excel styling code. If someone already built the spreadsheet you need to reproduce — a finance person’s invoice template, a report layout a designer signed off on — you can reverse-engineer it into real source code, then wire in your own data. This is the first of four posts walking through the same demo in C#, F#, XSLT, and plain JSON. The full project is on GitHub: Kookerella.Demo.DecompileToSource.
What we’re starting from #
A plain Excel file, built by hand in Excel. It has:
- A bold, 16pt “INVOICE” title
- A header row styled white-on-navy with a bottom border
- Two sample line-item rows, with an
Amount
column computed asQty * Unit Price
Nobody wrote any code to produce this file — it’s just a spreadsheet.
Step 1: Install the tool #
The reverse-engineering happens via a CLI tool, fsopenxmldsl-mcp
, published as a
dotnet
global tool:
dotnet tool install -g Kookerella.FsOpenXmlDsl.Mcp
Step 2: Reverse-engineer the template #
fsopenxmldsl-mcp convert templates/InvoiceTemplate.xlsx --lang csharp -o decompiled/InvoiceTemplate.g.cs
This reads the real .xlsx
file and prints out C# code that reproduces it exactly. The top line of the result is:
#:package Kookerella.CsOpenXmlDsl@0.3.2
That #:package
line is a .NET 10 file-based app
directive — this single .cs
file is a complete, runnable program with no .csproj
needed. You can run it immediately, on any machine with the .NET 10 SDK:
cd decompiled
dotnet run InvoiceTemplate.g.cs
That produces output.xlsx
— pixel-for-pixel the same as the original template. Nothing
was lost in translation: title, header styling, borders, currency formatting, and the
Qty * Unit Price
formulas are all there, expressed as plain C# method calls
(Cell.Text(...)
, .WithStyle(...)
, Cell.Formula(...)
, and so on).
This is the point where “decompiling a spreadsheet” stops being a metaphor. You now have the template as source code you can read, diff, and edit.
Step 3: Adding the package to a real project #
The file-based app above pinned the package inline for convenience. In a normal project you add it the usual way:
dotnet add package Kookerella.CsOpenXmlDsl
Kookerella.CsOpenXmlDsl
is a C# wrapper around a separate F# core library,
Kookerella.FsOpenXmlDsl
, which does the actual OOXML reading/writing.
Step 4: Turning the decompiled snippet into real code #
The decompiled file is disposable — it’s the starting point, not the deliverable. Nobody wants to hardcode “Widgets” and “Gadgets” forever. The diff from the decompiled file to a maintained version is deliberately small:
Wrap the top-level statements in a class and a
Build
method that takes real data (IEnumerable<OrderLine>
) instead of running standalone.Replace the two hardcoded item rows with a LINQ projection over that data:
var dataRows = orders.Select((order, i) =>
{
var row = 4 + i;
return Row.Of(
Cell.Text(order.Item),
Cell.Number(order.Quantity),
Cell.Number(order.UnitPrice).WithStyle(CellStyle.Default.WithNumberFormat(NumberFormatKind.Currency)),
Cell.Formula($"B{row}*C{row}", order.Quantity * order.UnitPrice).WithStyle(CellStyle.Default.WithNumberFormat(NumberFormatKind.Currency)));
});
Splice the generated rows into the sheet with a C# collection expression, instead of listing two hardcoded rows:
var sheet0 = Sheet.Create(
"Invoice",
[
/* title row, exactly as decompiled */,
/* header row, exactly as decompiled */,
.. dataRows,
]);
Everything else — every style call, every color, every border — is copied verbatim from the decompiled file. The styling was never hand-written by a developer in the first place, so there’s nothing to “port” — only the two data rows needed to become data-driven.
Step 5: Prove it stays correct #
Because this is now real code, it can be tested like real code — something you can’t do
to a .xlsx
file sitting on a file share. The test suite checks three things a spreadsheet can’t check about itself:
Schema validity— runs a realOpenXmlValidator
over the output. If a future change ever produces invalid OOXML, this catches it immediately instead of someone discovering a “repair this file?” dialog in Excel weeks later.Correctness— feeds in 0, 1, and 3 orders and checks the row count and theAmount
formula text/value are correct for each one.Styling preservation— pins the exact colors, bold, font size, and border style that came from the original template, so a future edit can’t silently drift the invoice’s look away from what the original designer signed off on.
Recap #
| Step | Command | Output |
|---|---|---|
| Install the tool | dotnet tool install -g Kookerella.FsOpenXmlDsl.Mcp |
fsopenxmldsl-mcp on your PATH |
| Reverse-engineer | fsopenxmldsl-mcp convert InvoiceTemplate.xlsx --lang csharp -o InvoiceTemplate.g.cs |
A runnable C# file that reproduces the original .xlsx exactly |
| Add the package | dotnet add package Kookerella.CsOpenXmlDsl |
The C# wrapper over the F# core |
| Make it data-driven | Replace hardcoded rows with a LINQ projection over your own data model | A maintained generator |
| Verify | dotnet test |
Schema validity, formula correctness, and styling all pinned by real tests |
The template never had to be redesigned in code, and the developer never had to reverse-engineer OOXML XML by hand — the tool did that translation, once, and everything after that is ordinary, testable C#. Next up: the same demo in F#.