Decompiling an Excel template into source - #2 F#
This is the F# counterpart to part one’s C# walkthrough — same demo, same template, same idea: you don’t have to hand-write Excel styling code. If someone already built the spreadsheet you need to reproduce, you can reverse-engineer it into real source code, then wire in your own data. The full project is on GitHub: Kookerella.Demo.DecompileToSource.
What we’re starting from
The same plain Excel file used in the C# demo: a bold, 16pt “INVOICE” title, a
white-on-navy header row with a bottom border, and two sample line-item rows with an
Amount column computed as Qty * Unit Price.
Step 1: Install the tool
dotnet tool install -g Kookerella.FsOpenXmlDsl.Mcp
Step 2: Reverse-engineer the template
fsopenxmldsl-mcp convert templates/InvoiceTemplate.xlsx --lang fsharp -o decompiled/InvoiceTemplate.g.fsx
This prints out F# code that reproduces the workbook exactly — an F# script, meant to be
run with dotnet fsi, not a .csproj-based project. Its first line is:
#r "nuget: Kookerella.FsOpenXmlDsl, 0.3.1"
#r "nuget: <package>, <version>" is F# Interactive’s own directive for pulling a NuGet
package directly into a script — the closest F# equivalent to the C# demo’s #:package
line, and just as portable: it pins the exact version the tool itself was built against,
so it works on any machine with the .NET 10 SDK, not just the one that generated it. You
can run it immediately:
cd decompiled
dotnet fsi InvoiceTemplate.g.fsx
That produces output.xlsx, pixel-for-pixel the same as the original template — title,
header styling, borders, currency formatting, and the Qty * Unit Price formulas all
expressed as plain F# values (cell (Text "...", style = ...), Formula(...), and so
on).
Step 3: Adding the package to a real project
The script above pulls the package in via #r "nuget: ...". In a compiled project you
add it the usual way:
dotnet add package Kookerella.FsOpenXmlDsl
Step 4: Turning the decompiled snippet into real code
The decompiled script is disposable — it’s the starting point, not the deliverable. The diff to a maintained version is deliberately small:
Wrap the expression in a
buildfunction that takesOrderLine listinstead of running standalone.Replace the two hardcoded item rows with a projection over that data:
let dataRows = orders |> List.mapi (fun i order -> let r = 4 + i let amount = order.Quantity * order.UnitPrice row [ cell (Text order.Item) cell (Number order.Quantity) cell (Number order.UnitPrice, style = { CellStyle.Default with NumberFormat = Some Currency }) cell (Formula(sprintf "B%d*C%d" r r, Some amount), style = { CellStyle.Default with NumberFormat = Some Currency }) ])Splice the generated rows into the sheet with
yield!, instead of listing two hardcoded rows:sheet "Invoice" [ yield row [ (* title row, exactly as decompiled *) ] yield row ([ (* header row, exactly as decompiled *) ], index = 2) yield! dataRows ]This is the F# mirror of the C# demo’s collection-expression spread (
.. dataRows) — same idea, splice a computed sequence into a literal, different syntax.
Everything else — every style value, every color, every border — is copied verbatim from the decompiled script. 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
The test suite checks the same three things the C# demo’s tests do:
- Schema validity — runs a real
OpenXmlValidatorover the output. - Correctness — feeds in 0, 1, and 3 orders and checks the row count and the
Amountformula for each one. - Styling preservation — pins the exact colors, bold, font size, and border style that came from the original template.
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 fsharp -o InvoiceTemplate.g.fsx | A portable F# script that reproduces the original .xlsx exactly |
| Add the package | dotnet add package Kookerella.FsOpenXmlDsl | Used directly — there’s no separate wrapper package in F# |
| Make it data-driven | Replace hardcoded rows with a projection over your own data model, spliced in with yield! | A maintained generator |
| Verify | dotnet test | Schema validity, formula correctness, and styling all pinned by real tests |
Next up: the same demo with no code at all, just an XSLT transform.