Decompiling an Excel template into source - #1 C#
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
Amountcolumn 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
Buildmethod 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 real
OpenXmlValidatorover 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 the
Amountformula 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#.