This is the XML counterpart to the C# and F# walkthroughs, but the pitch is different: there’s no C# or F# code in this one at all. If your data already comes out of a pipeline as XML — an XSLT transform, a database export, whatever — you can go straight from that data to a real Excel file, using the same template-driven idea as the other demos, but with a declarative transform instead of a programming language. The full project is on GitHub: Kookerella.Demo.DecompileToSource.

What we’re starting from

The same plain Excel file used in the other demos: 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 xml -o decompiled/InvoiceTemplate.xml

This prints out plain XML — not source code in any programming language, just the workbook’s own data and styling, validated against the tool’s own Xml.xsd schema. A trimmed excerpt, the “Widgets” row:

<cell ref="A4">
  <text>Widgets</text>
</cell>
<cell ref="B4">
  <number>3</number>
</cell>
<cell ref="C4">
  <number>9.99</number>
  <style>
    <numberFormat kind="currency" />
  </style>
</cell>
<cell ref="D4">
  <formula cachedValue="29.97">B4*C4</formula>
  <style>
    <numberFormat kind="currency" />
  </style>
</cell>

Text, numbers, a formula with its cached value, and styling — all as plain elements and attributes, no code to read.

Step 3: Turning the decompiled snippet into a transform

The decompiled XML is disposable — it’s the starting point, not the deliverable. The maintained version is split across two files: orders.xml (the data that varies invoice to invoice, in whatever shape your own pipeline already produces it), and invoice.xslt (the styling, copied verbatim from the decompiled XML, with the two hardcoded item rows replaced by an xsl:for-each).

The diff from the decompiled file is deliberately small — the title cell and the four header cells are copied byte-for-byte. Only the two hardcoded “Widgets”/“Gadgets” cell blocks became this:

<xsl:for-each select="order">
  <xsl:variable name="row" select="3 + position()" />
  <xsl:variable name="amount" select="xs:double(@quantity) * xs:double(@unitPrice)" />

  <cell ref="{concat('A', $row)}">
    <text><xsl:value-of select="@item" /></text>
  </cell>
  <cell ref="{concat('B', $row)}">
    <number><xsl:value-of select="@quantity" /></number>
  </cell>
  <cell ref="{concat('C', $row)}">
    <number><xsl:value-of select="@unitPrice" /></number>
    <style>
      <numberFormat kind="currency" />
    </style>
  </cell>
  <cell ref="{concat('D', $row)}">
    <formula cachedValue="{$amount}"><xsl:value-of select="concat('B', $row, '*C', $row)" /></formula>
    <style>
      <numberFormat kind="currency" />
    </style>
  </cell>
</xsl:for-each>

This is the XSLT mirror of the C# demo’s collection-expression spread and the F# demo’s yield! — same idea (splice a computed sequence of rows into a template), a third different syntax for it.

Step 4: Running the transform

XSLT needs an XSLT processor — .NET doesn’t ship one built in for XSLT 3.0, so this demo uses Saxon HE, distributed as a dotnet local tool:

dotnet tool restore
dotnet SaxonHE12NetXslt -s:orders.xml -xsl:invoice.xslt -o:invoice-workbook.xml

That produces invoice-workbook.xml — the same shape as the decompiled XML, except with three real data rows instead of two hardcoded ones.

Step 5: Building the Excel file

fsopenxmldsl-mcp build invoice-workbook.xml invoice.xlsx

build is the inverse of convert --lang xml: it reads XML matching the tool’s Xml.xsd schema and writes a real .xlsx. No C# or F# was compiled or run anywhere in this pipeline. The only “code” is the .xslt stylesheet itself — a declarative transform, not a program.

Step 6: Prove it stays correct

Because the schema and fsopenxmldsl-mcp build are real, checkable things, this pipeline is tested by running the actual commands and inspecting the result, rather than trusting that the XSLT “looks right”:

  • Schema validity — runs a real OpenXmlValidator over the output.
  • Correctness — feeds in 0, 1, and 3 orders and checks the row count and the Amount formula for each one.
  • Styling preservation — pins the exact colors, bold, font size, and border style that came from the original template.

Recap

StepCommandOutput
Install the tooldotnet tool install -g Kookerella.FsOpenXmlDsl.Mcpfsopenxmldsl-mcp on your PATH
Reverse-engineerfsopenxmldsl-mcp convert InvoiceTemplate.xlsx --lang xml -o InvoiceTemplate.xmlPlain XML that reproduces the original .xlsx exactly
Make it data-drivenReplace hardcoded <cell> blocks with xsl:for-each over your own datainvoice.xslt
Transformdotnet SaxonHE12NetXslt -s:orders.xml -xsl:invoice.xslt -o:invoice-workbook.xmlWorkbook XML with real data rows
Buildfsopenxmldsl-mcp build invoice-workbook.xml invoice.xlsxA real, schema-valid .xlsx
Verifydotnet testSchema validity, formula correctness, and styling all pinned by real tests

Last up: the same demo again, this time from plain JSON.