Wiki source code of Macro Transformation

Last modified by Eleni Cojocariu on 2026/10/13 18:18

Show last authors
1 The macro transformation is the [[rendering transformation>>doc:rendering:Main.Transformations]] that executes macros. It walks the XDOM a parser produced, looks up the component registered under each macro call, runs it, and puts the blocks it returned in the place of the call. [[Rendering Macros>>doc:documentation.xs.dev.rendering-macros.WebHome]] describes what a macro is made of; this page describes the engine that runs it.
2
3 == The Transformation That Runs First ==
4
5 Several transformations can run over the same XDOM, and ##rendering.transformations## decides which, as [[Rendering Transformations>>doc:documentation.xs.admin.configuration.rendering-transformations.WebHome]] describes. They run by priority, and the macro transformation declares ##100##, low enough to be among the first, so that the transformations after it see the content the macros produced rather than the calls.
6
7 == Finding the Next Macro to Execute ==
8
9 The transformation does not walk the tree in reading order. It collects the macro calls that have a macro registered for the syntax being rendered into a queue ordered by macro priority, and takes the lowest first. Each result goes back into the tree rather than into the output, so the queue can still grow while it is being drained.
10
11 What happens after each execution depends on the promise the macro made. A macro that declared its execution isolated cannot have inserted a call anywhere else, so only the blocks it returned are scanned and added to the queue. Any other macro forces a full rescan of the XDOM, because a call may now sit anywhere in it. [[Macro Execution Rules>>doc:documentation.xs.dev.rendering-macros.macro-execution-rules.WebHome]] covers that promise from the macro's side.
12
13 {{plantuml}}
14 @startuml
15 !theme bluegray
16 start
17 :Scan the XDOM for calls
18 that have a macro registered
19 for the current syntax;
20 :Order them in a queue
21 by macro priority;
22 repeat
23 :Take the first macro
24 off the queue and execute it;
25 :Replace the call by the blocks
26 it returned, wrapped in a
27 macro marker block;
28 if (execution declared isolated?) then (yes)
29 :Queue the calls the
30 returned blocks contain;
31 else (no)
32 :Scan the whole XDOM again;
33 endif
34 repeat while (a call left, under
35 the recursion limit?) is (yes)
36 stop
37 @enduml
38 {{/plantuml}}
39
40 The loop is bounded. The transformation counts the executions of macros that were themselves produced by another macro and stops after 1000 of them, so a macro generating itself leaves unexecuted content behind instead of hanging the request.
41
42 The component the transformation asks for each call is described in [[Macro Transformation API>>doc:documentation.xs.dev.rendering-macros.macro-transformation.api.WebHome]].
43
44 == When a Macro Does Not Run ==
45
46 Four things stop a macro, and each of them replaces the call by an error block in the page rather than failing the rendering.
47
48 |=What happened|=What the page shows
49 |No macro is registered under that name, for this syntax or for all syntaxes|An unknown macro error naming the id, so a misspelled macro name is visible where it was written
50 |The call sits inside a paragraph or a list item and ##supportsInlineMode## returns ##false##|A standalone macro error explaining that the macro needs a line of its own
51 |A parameter value cannot be converted into the parameters bean|An invalid parameters error carrying the conversion failure
52 |##execute## throws anything at all|A failed to execute error carrying the cause
53
54 One broken macro therefore costs its own block, never the page. The single exception is a macro that takes its own call out of the XDOM, as [[a deprecated macro handing over to its successor>>doc:documentation.xs.dev.rendering-macros.macro-execution-rules.WebHome]] does.
55
56 == The Context a Macro Receives ==
57
58 ##execute## is given a ##MacroTransformationContext##, which carries what the macro cannot read from its own parameters and content.
59
60 |=Member|=What a macro reads it for
61 |##getCurrentMacroBlock()##|Its own call, to read a raw parameter or to find where in the page it sits
62 |##getXDOM()##|The whole document being transformed
63 |##isInline()##|Whether the call sits inside a paragraph, so the macro can return inline blocks
64 |##getTransformation()##|The running transformation, for a macro that has to transform content of its own, as the ##include## macro does
65 |##getTransformationContext()##|The syntax being rendered and the restricted flag
66
67 == Reaching the Rendering in Progress from Anywhere ==
68
69 A component that is not a macro reads the same state through the ##RenderingContext## role: the syntax, the target syntax, the document being rendered and the macro call currently executing. It is the supported way for code outside a macro to know that a rendering is under way and in what state.
70
71 That context is set by the default ##TransformationManager## as it drives the transformations. Code that executes a ##Transformation## directly leaves it unset, and no public API sets it. Go through the ##TransformationManager## whenever a transformation has to be run.

Get Connected