Wiki source code of Rendering Macros
Last modified by superadmin on 2026/10/14 18:26
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | A [[rendering macro>>doc:extensions:Extension.RenderingMacroTransformation.WebHome]] is a component implementing the ##Macro## role, registered under the name it is called by in wiki syntax. [[Rendering Macros>>doc:documentation.xs.user.rendering-macros.WebHome]] describes what a macro is to the person reading the page; this one describes what a macro is in the code. | ||
| 2 | |||
| 3 | == A Macro Is a Component == | ||
| 4 | |||
| 5 | The name a macro is called by in the syntax is its component hint, and nothing registers it separately. The class below is what makes {{code language="none"}}{{useravatar/}}{{/code}} work, declared in its module's ##META-INF/components.txt## like any other component: | ||
| 6 | |||
| 7 | {{code language="java"}} | ||
| 8 | @Component | ||
| 9 | @Named("useravatar") | ||
| 10 | @Singleton | ||
| 11 | public class UserAvatarMacro extends AbstractMacro<UserAvatarMacroParameters> | ||
| 12 | { | ||
| 13 | public UserAvatarMacro() | ||
| 14 | { | ||
| 15 | super("User Avatar", DESCRIPTION, UserAvatarMacroParameters.class); | ||
| 16 | setDefaultCategories(Set.of(DEFAULT_CATEGORY_CONTENT)); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public List<Block> execute(UserAvatarMacroParameters parameters, String content, | ||
| 21 | MacroTransformationContext context) throws MacroExecutionException | ||
| 22 | { | ||
| 23 | // Build and return the blocks the macro call is replaced by. | ||
| 24 | } | ||
| 25 | |||
| 26 | @Override | ||
| 27 | public boolean supportsInlineMode() | ||
| 28 | { | ||
| 29 | return true; | ||
| 30 | } | ||
| 31 | } | ||
| 32 | {{/code}} | ||
| 33 | |||
| 34 | Changing the ##@Named## value renames the macro. A hint of the form ##macroname/syntaxid##, ##mymacro/xwiki/2.0## for instance, registers the macro for that one syntax instead of for all of them. Two components declared on the same hint are resolved by component priority, which is how an extension replaces a bundled macro without touching it. | ||
| 35 | |||
| 36 | == A Macro Returns Blocks, Not Text == | ||
| 37 | |||
| 38 | ##execute## returns a list of ##Block##s, and the macro transformation substitutes them for the macro call in the XDOM, the tree the parser produced from the page. A renderer only ever sees that tree, once every macro in it has run. The components a macro injects to build those blocks are in [[Java Macro Helpers>>doc:documentation.xs.dev.rendering-macros.java-macro-helpers.WebHome]]. | ||
| 39 | |||
| 40 | {{plantuml}} | ||
| 41 | @startuml | ||
| 42 | !theme bluegray | ||
| 43 | skinparam nodesep 30 | ||
| 44 | skinparam ranksep 40 | ||
| 45 | |||
| 46 | file "Stored page content:\nwiki syntax holding\na macro call" as SRC | ||
| 47 | rectangle "Parser" as P | ||
| 48 | card "XDOM:\na MacroBlock among\nthe other blocks" as X1 | ||
| 49 | rectangle "Macro transformation\nlooks the component up by\nthe macro name, calls execute()" as MT | ||
| 50 | card "XDOM:\nthe blocks the macro\nreturned, in its place" as X2 | ||
| 51 | rectangle "Renderer" as R | ||
| 52 | file "HTML, LaTeX,\nplain text, ..." as OUT | ||
| 53 | |||
| 54 | SRC --> P | ||
| 55 | P --> X1 | ||
| 56 | X1 --> MT | ||
| 57 | MT --> X2 | ||
| 58 | X2 --> R | ||
| 59 | R --> OUT | ||
| 60 | @enduml | ||
| 61 | {{/plantuml}} | ||
| 62 | |||
| 63 | Two things follow, and they are what a first macro usually gets wrong: | ||
| 64 | |||
| 65 | * **The output is syntax-independent.** A macro returning an ##ImageBlock## renders in HTML, in a PDF export and in LaTeX alike, while one returning raw HTML renders in none of the others. | ||
| 66 | * **A macro may emit another macro call**, and that call is executed in turn, because what the macro returned went back into the tree rather than into the output. | ||
| 67 | |||
| 68 | Macros are also not run in the order they are written. The transformation runs them by priority, lowest first, and ##AbstractMacro## defaults to ##1000##. A macro that has to act before the ones its own content holds lowers it, as the ##container## macro does: | ||
| 69 | |||
| 70 | {{code language="java"}} | ||
| 71 | @Override | ||
| 72 | public int getPriority() | ||
| 73 | { | ||
| 74 | return 750; | ||
| 75 | } | ||
| 76 | {{/code}} | ||
| 77 | |||
| 78 | Priority is one of several things the transformation decides rather than the macro. [[Macro Transformation>>doc:documentation.xs.dev.rendering-macros.macro-transformation.WebHome]] describes how it runs the macros of a page, and [[Macro Execution Rules>>doc:documentation.xs.dev.rendering-macros.macro-execution-rules.WebHome]] covers the rest, including when a macro is told not to execute at all. | ||
| 79 | |||
| 80 | == The Parameters Bean Is What the Editors Show == | ||
| 81 | |||
| 82 | A macro declares its parameters as a bean, and the descriptor the editors read is built from it. The annotations on each setter are the whole of the parameter metadata, so a parameter is described once, in the code: | ||
| 83 | |||
| 84 | {{code language="java"}} | ||
| 85 | public class UserAvatarMacroParameters | ||
| 86 | { | ||
| 87 | private String username; | ||
| 88 | |||
| 89 | @PropertyMandatory | ||
| 90 | @PropertyDescription("the name of the user whose avatar is to be displayed") | ||
| 91 | @PropertyDisplayType(UserReference.class) | ||
| 92 | public void setUsername(String username) | ||
| 93 | { | ||
| 94 | this.username = username; | ||
| 95 | } | ||
| 96 | } | ||
| 97 | {{/code}} | ||
| 98 | |||
| 99 | ##@PropertyDescription## is the help text beside the field, ##@PropertyMandatory## makes the editor refuse an empty value, and ##@PropertyDisplayType## replaces the plain text field by a [[picker>>doc:documentation.xs.dev.rendering-macros.macro-parameter-pickers.WebHome]], here the user picker. The [[Properties Module>>doc:extensions:Extension.Properties Module||anchor="HPopulateaJavaBean"]] documents every annotation a parameter bean can carry. The categories the macro is listed under come from ##setDefaultCategories##; ##AbstractMacro## defines the bundled ones, listed in [[Macro Categories>>doc:documentation.xs.dev.rendering-macros.macro-categories.WebHome]]: ##Formatting##, ##Content##, ##Navigation##, ##Development##, ##Layout##, ##Internal## and ##Deprecated##. | ||
| 100 | |||
| 101 | == The Content Holds the Main Data == | ||
| 102 | |||
| 103 | The main data of a macro goes in its content, between the opening and closing tags, whether it is rich text like a message or raw text like a code snippet. A parameter is for data that is always short, such as a title. The content is declared by a content descriptor, which the editors read like the parameters bean: | ||
| 104 | |||
| 105 | {{code language="java"}} | ||
| 106 | super("Info", DESCRIPTION, | ||
| 107 | new DefaultContentDescriptor("Content of the message", true, Block.LIST_BLOCK_TYPE), | ||
| 108 | MessageMacroParameters.class); | ||
| 109 | {{/code}} | ||
| 110 | |||
| 111 | The description is the help text of the content field and the boolean makes the content mandatory. The type decides how the WYSIWYG editor edits it: ##Block.LIST_BLOCK_TYPE## declares wiki content, edited in place like the rest of the page, while the default ##String## declares raw text, edited in a text area of the "Edit Macro" dialog. When the content accepts less than the rest of the page, the description says what it accepts, so the restriction is visible before the macro fails to render it. Declaring the type is only half of it: the macro must also mark which of the blocks it returns are editable, as [[Make a Macro Editable Inline>>doc:documentation.xs.dev.rendering-macros.make-macro-editable-inline.WebHome]] describes. | ||
| 112 | |||
| 113 | == Java Macro or Wiki Macro == | ||
| 114 | |||
| 115 | A macro can also be written as a wiki page carrying an ##XWiki.WikiMacroClass## object, with no Java and no build, see [[Wiki Macros>>doc:documentation.xs.dev.rendering-macros.wiki-macros.WebHome]]. That object holds the same metadata the Java descriptor does, plus the macro code itself and the visibility that decides which component manager the macro is registered in. | ||
| 116 | |||
| 117 | |=Characteristic|=Java macro|=Wiki macro | ||
| 118 | |Easy to debug, and to write an automated test for|(/)|(x) | ||
| 119 | |Optimized for performance|(/)|(x) | ||
| 120 | |[[Advanced parameter metadata>>doc:documentation.xs.dev.rendering-macros.wiki-macros.wiki-macro-fields.WebHome]], such as [[pickers>>doc:documentation.xs.dev.rendering-macros.macro-parameter-pickers.WebHome]] and deprecation|(/)|(/) | ||
| 121 | |Needs no development skills and no build|(x)|(/) | ||
| 122 | |Fast to write, and easy for a user to customize afterwards|(x)|(/) | ||
| 123 | |||
| 124 | [[Write a Macro in Java>>doc:documentation.xs.dev.rendering-macros.write-java-macro.WebHome]] is the Java tutorial, and [[Write a Macro in a Wiki Page>>doc:documentation.xs.dev.rendering-macros.wiki-macros.write-wiki-macro.WebHome]] the wiki one. | ||
| 125 | |||
| 126 | == Executing Content Is a Rights Decision == | ||
| 127 | |||
| 128 | A macro that renders or executes the content it is handed runs what a page author wrote, so it also needs a required-rights analyzer. The analyzer does not enforce anything; it reports what the macro's content will need, and without one nothing tells the author to declare those rights or shows a reviewer what the page will run. | ||
| 129 | |||
| 130 | A macro whose result may be reused across requests extends ##AbstractExecutedContentMacro## rather than driving the asynchronous renderer by hand; it then takes the standard ##async##, ##cached## and ##context## parameters. |