DEV Community

Daniel Jonathan
Daniel Jonathan

Posted on

Debugging XSLT vs Liquid in VS Code

Both the XSLT Debugger and DotLiquid Debugger let you step through a template and inspect variables. But they work differently under the hood — and those differences affect what you can do while debugging.


The Fundamental Difference

XSLT debugging is live. The XSLT Debugger supports two engines, each with its own instrumentation strategy:

  • Saxon (XSLT 2.0/3.0) — exposes a TraceListener interface with Enter and Leave callbacks that fire as each instruction executes. The engine cooperates natively.
  • .NET XslCompiledTransform (XSLT 1.0) — has no TraceListener, so the debugger rewrites the stylesheet at load time, injecting <dbg:probe> extension calls into every template, if, for-each, and when block. A registered extension object handles each probe and pauses execution on a TaskCompletionSource until you click Step.

Both approaches genuinely pause execution. You're inspecting a running process.

Liquid debugging is replay. DotLiquid has no such API — Template.Render() runs the whole template and returns. The extension records a trace during that render, then lets you step through the recording. By the time you click Step, the template has already finished.


Capability Comparison

XSLT Debugger DotLiquid Debugger
Step model Live pause/resume Trace replay
Breakpoints Yes — set and hit mid-execution No
Backward stepping No Always — it's just a recording
Variable state Live, from the running engine Recorded snapshot at each step
Modify and continue No No — edit and re-render
Conditional breakpoints No No
Filter chain tracing N/A Yes — each filter is a step
Branch visibility Taken branch only Taken branch only
Re-render cost Steps are free (engine is paused) One render upfront, steps are free after

What This Looks Like in Practice

Setting a breakpoint:
In the XSLT debugger you can set a breakpoint on a specific <xsl:template> or <xsl:for-each>, hit F5, and the debugger stops there — even if that template fires 50 iterations in. You never see the first 49.

In the DotLiquid debugger there are no breakpoints. You start at step 1 and click forward. For a template with many iterations you can drag the step slider to jump ahead quickly, but you can't say "stop when item.qty > 10".

Backward stepping:
The DotLiquid debugger supports backward stepping — you're just moving a cursor through a recording. The XSLT Debugger does not support step back.

Modifying state:
Neither debugger supports modify-and-continue. In both cases you edit the template or input and re-render from scratch.

Filter chain tracing:
This is where the DotLiquid debugger has an advantage. Because every filter application is recorded as a separate step, you can step through name | Upcase | Truncate: 10 | Append: "…" and see the value after each filter. XSLT doesn't have filter chains — XPath functions are composed inline and there's no equivalent granularity.


Why the Difference Exists

Both XSLT engines provide a path to genuine pause/resume — either through a native TraceListener (Saxon) or through stylesheet rewriting at load time (.NET XSLT 1.0). The key is that XSLT execution is structured: templates fire, instructions execute in sequence, and there are clear entry/exit points to hook into.

DotLiquid was designed as a simple, safe rendering library. It has no extension points for interrupting execution, and its render loop is a single synchronous call with no observable mid-execution state. The replay approach is the only option available without forking the engine.


Which Should You Use?

If you're debugging XSLT maps — especially complex structural transformations, apply-templates logic, or recursive templates — the live debugger is significantly more powerful. Breakpoints and live state make it practical to debug templates that are hundreds of lines long. Use the compiled engine for XSLT 1.0 (including inline C#); use Saxon for XSLT 2.0/3.0. The full series is covered in XSLT Debugging in Logic Apps.

If you're debugging Liquid maps — filter results, conditional branches, loop variable values — the replay model covers the common cases well. The main limitation is the absence of breakpoints; for most Liquid templates this is a minor inconvenience rather than a real blocker. For a deeper look at Liquid templates and the debugger extension, see DotLiquid Debugging in Logic Apps.

Top comments (0)