4 — Debugging an XSLT transformation
This is what Debug XML Tool exists for: stopping a transformation while it runs, watching what the processor really does, and moving forward instruction by instruction.
Where an xsl:message tells you that something happened, the debugger shows you where you
are, how you got there, and what your variables are worth at that precise moment.
This chapter assumes you know how to run a transformation (see Getting started).
4.1 Setting a breakpoint
Click in the gutter, to the left of the line number, opposite the instruction where you want execution to stop. A red dot appears. Click again to remove it.
Set your breakpoints on instructions — xsl:value-of, xsl:for-each, xsl:if,
xsl:call-template, xsl:apply-templates. A line carrying no executable instruction (blank line,
comment, closing tag) will never be reached.
Breakpoints are tied to their file. You can therefore set them in a main stylesheet and in
the modules it pulls in with xsl:include or xsl:import: each will be honoured in its own file.
Conditional breakpoints
On an xsl:for-each iterating five thousand times, stopping at every turn is of no use. A
conditional breakpoint fires only when an XPath expression is true.
- Right-click an existing breakpoint (on the dot itself, in the gutter).
- The dialogue « Condition XPath (vide = toujours) » opens.
- Type the expression —
@id = '3', orprice > 50, for example — then OK.
The dot goes from red to orange: it is now conditional.
⚠️ This dialogue is still in French, whatever the interface language. Its two texts — « Condition XPath (vide = toujours) » and « Supprimer condition » — live in the editor's web view and are not yet part of the translated resources. They read as "XPath condition (empty = always)" and "Remove condition".
The condition is evaluated in the context of the instruction, exactly as if you had written it at that point in the stylesheet. It is compiled once then reused: a condition on a ten-thousand iteration loop does not penalise execution.
To go back to an ordinary breakpoint, reopen the dialogue and click « Supprimer condition ».
An invalid condition does not silence the breakpoint — it stops at every pass. If the expression cannot be evaluated, the debugger falls back on stopping, and writes the reason to the error output. So the symptom of a wrong condition is a breakpoint that stops too often, never one that stays silent. Test your expression in the XPath evaluator.
4.2 Starting the debugger
Press F5, or click Run transformation (▶).
There is no separate "debug mode": the ordinary transformation is the debugging session. If no breakpoint is set, it runs straight through.
The state badge on the toolbar shows at all times where you stand:
| Badge | Meaning | Active buttons |
|---|---|---|
| IDLE | no run in progress | Run, Profiler |
| RUNNING | transformation running | Stop |
| PAUSED | stopped on a breakpoint | all stepping, Resume, Stop |
When execution stops, the line is highlighted in yellow in the editor, the file concerned is brought to the front, and the inspection panels fill up.
4.3 Stepping forward
Five commands, available from the keyboard and on the toolbar. None of them means anything except at a stop (PAUSED).
| Command | Key | Behaviour |
|---|---|---|
| Resume | F8 | resumes until the next breakpoint, or until the end |
| Step Over | F10 | runs the current line without entering the templates it calls |
| Step Into | F11 | enters the template or function called |
| Step Out | Shift+F11 | finishes the current template and stops at the caller |
| Run to cursor | Ctrl+F10 | resumes and stops at the line where your cursor is |
What you need to know
Your breakpoints stay active during stepping. A Step Over passing across a template holding a breakpoint will stop there. This is deliberate: a breakpoint you have set is never silently ignored.
"Run to cursor" does not set a permanent breakpoint. Place the cursor on the target line, press Ctrl+F10: execution resumes and stops there, once. Nothing is left in the gutter. It is the fastest way to reach a precise place without cluttering your breakpoints.
Step Over and Step Into differ by depth, not by line. On a
<xsl:call-template name="format-price">, Step Into takes you into format-price; Step Over
runs the whole call and brings you back to the next line of the current template.
4.4 Inspecting the state at a stop
Four panels, all in the bottom band, are refreshed at every stop.
Variables
Two sections:
- CURRENT NODE — the XML node the instruction applies to: its name, its type, its path and its value. This is the answer to "where am I in the source document?".
- VARIABLES — the XSLT variables and parameters visible at that point, with their values.
Outside a pause, the panel reads No variables — the debugger is not paused. At a stop, if there really is no variable in the current scope, it shows No variables in this scope — two distinct situations, deliberately distinguished.
Call Stack
The chain of templates and functions that led to the breakpoint, from the most recent to the oldest.
Each line shows its real execution position: for the top of the stack, the line where you are
stopped; for a caller, the line the call departs from — the xsl:call-template or
xsl:apply-templates concerned, and not the declaration line of the template. This is standard
debugger semantics: you see the path travelled, not the table of contents.
Double-click a line to open the corresponding file and highlight that position.
Outside a pause: No call stack — the debugger is not paused.
XPath evaluator
The XPath Evaluator panel evaluates an expression on your document. At a stop, it switches
automatically into the live context of the pause: a DEBUG badge shows the current node
(DEBUG — /catalog/book[1], for example), and your expressions evaluate exactly as if they were
written at that point in the stylesheet.
You can therefore test @id, ../title, count(following-sibling::book) — everything you need
to understand why your xsl:if does not fire.
- Type the expression, confirm with Enter.
- Results of node type are clickable: a double-click opens the source file and highlights the originating line in blue.
- ↑ / ↓ recall your previous expressions; Ctrl+Space opens the full history.
Outside debugging, the panel evaluates against the XML document of the current transformation. While no transformation has been run, it reads No XML file configured in transformation — run the transformation once to populate it.
Watch XPath
The evaluator answers a one-off question. The Watch XPath panel answers the same question at every stop, automatically.
- Type an expression in the « Add an XPath expression… » field and confirm.
- At every pause — breakpoint or step — all active watches are re-evaluated in the current context.
- The check box on each row switches its re-evaluation on or off.
- While execution runs, the values shown are greyed out: they date from the last stop.
- Clicking a value of node type navigates to its source.
- Right-clicking offers Remove, Copy expression, Copy value.
The list is kept per workspace and survives restarting the application: your usual watch expressions are there at the next launch.
4.5 Stopping
Shift+F9, or the Stop button. Execution is interrupted immediately, the badge returns to IDLE, the inspection panels empty and the yellow highlight disappears.
Your breakpoints, for their part, stay in place for the next session.
4.6 A complete example
Take a stylesheet formatting a catalogue of five books, with a named template for the price:
<xsl:template match="/">
<xsl:for-each select="catalog/book">
<tr>
<td><xsl:value-of select="@id"/></td>
<td>
<xsl:call-template name="format-price">
<xsl:with-param name="price" select="price"/>
</xsl:call-template>
</td>
</tr>
</xsl:for-each>
</xsl:template>
Goal: understand why the price of the third book comes out wrong.
- Set a breakpoint on the
<xsl:call-template name="format-price">line. - Right-click it, condition
position() = 3, OK. The dot turns orange. - F5. Execution stops straight at the third iteration — the first two go past without interruption.
- The Variables panel shows the current node
/catalog/book[3]. In the XPath Evaluator, typepriceto read the value actually passed. - F11 (Step Into) enters
format-price. The Call Stack now shows two levels:format-priceat the top, and below it the root template positioned on the line of the call. - Inspect the
$priceparameter in Variables. If it is not worth what you expected, the problem is in thexsl:with-param; otherwise it is informat-price. - Shift+F11 (Step Out) brings you back to the caller, F8 moves to the next book if there is one, Shift+F9 ends the session.
4.7 Limits worth knowing
- Highlighting targets the whole line, never a range of columns: the Saxon-HE engine supplies only a line point. On a line carrying several instructions, the highlight does not say which one.
- Debugging forces tracing on the XSLT processor and switches off its optimisations. A transformation run with breakpoints is therefore slower than a plain run — this is expected, and has no effect on the result produced.
- The engine is Saxon-HE: the schema-aware transformations and the streaming of the Professional/Enterprise editions are not available.
- A breakpoint on a non-executable line is never reached. If a breakpoint stays silent, check that it really sits on an instruction, and that the file concerned really takes part in the transformation being run.
What next
- Analysing — profiler, template coverage, and the jump back from the output to the instruction that produced it.
- Transforming & exporting — output formats, parameters, reusable scenarios.
- Validating & comparing — check that an output has not changed, test a stylesheet with XSpec.
- Reference — every shortcut, button, menu and gesture, in tables.