Write a Macro in Java
Tutorial
This tutorial builds simple-example, a macro that displays the value it is called with, from the Maven command that generates the module to the macro running in a wiki page. Rendering Macros explains what a macro is made of and how the rendering engine runs it; this page writes one.
You need a Java version matching the XWiki runtime needs and Maven, and it helps to know the XWiki Rendering architecture the macro runs inside.
- Generate the module from the XWiki Rendering macro archetype:
mvn archetype:generate \ -DarchetypeGroupId=org.xwiki.rendering \ -DarchetypeArtifactId=xwiki-rendering-archetype-macroMaven asks which version of the archetype to use, and the newest is the default: the archetype is released with every XWiki version. It then asks for the groupId, artifactId, version and package of the module; this tutorial answers com.acme, example, 1.0-SNAPSHOT and com.acme.
- Look at what the archetype generated:
example/ pom.xml src/main/java/com/acme/ExampleMacroParameters.java src/main/java/com/acme/internal/ExampleMacro.java src/main/resources/META-INF/components.txt src/test/java/com/acme/IntegrationTests.java src/test/resources/example1.test src/test/resources/example2.testcomponents.txt holds one full class name per line, com.acme.internal.ExampleMacro here, and that line is what registers the macro.
- Read ExampleMacro.java, the macro itself:
package com.acme.internal; import javax.inject.Named; import java.util.List; import org.xwiki.component.annotation.Component; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.WordBlock; import org.xwiki.rendering.block.ParagraphBlock; import org.xwiki.rendering.macro.AbstractMacro; import org.xwiki.rendering.macro.MacroExecutionException; import com.acme.ExampleMacroParameters; import org.xwiki.rendering.transformation.MacroTransformationContext; /** * Example Macro. */ @Component @Named("simple-example") public class ExampleMacro extends AbstractMacro<ExampleMacroParameters> { /** * The description of the macro. */ private static final String DESCRIPTION = "Simple Example Macro"; /** * Create and initialize the descriptor of the macro. */ public ExampleMacro() { super("Simple Example", DESCRIPTION, ExampleMacroParameters.class); } @Override public List<Block> execute(ExampleMacroParameters parameters, String content, MacroTransformationContext context) throws MacroExecutionException { List<Block> result; List<Block> wordBlockAsList = List.of(new WordBlock(parameters.getParameter())); // Handle both inline mode and standalone mode. if (context.isInline()) { result = wordBlockAsList; } else { // Wrap the result in a Paragraph Block since a WordBlock is an inline element, and it needs to be // inside a standalone block. result = List.of(new ParagraphBlock(wordBlockAsList)); } return result; } @Override public boolean supportsInlineMode() { return true; } }AbstractMacro leaves two methods to implement. execute builds the blocks the macro call is replaced by. supportsInlineMode says whether the macro may be used inside existing content rather than on its own, as in a sentence or in a heading:
= hello {{velocity}}world{{/velocity}} =That is also why the result is built twice: called inline, the macro returns its WordBlock as it is, and called on its own it wraps it in a ParagraphBlock, because a word is an inline element and cannot stand alone in the page.
- Read ExampleMacroParameters.java, which declares what the macro can be called with:
package com.acme; import org.xwiki.properties.annotation.PropertyMandatory; import org.xwiki.properties.annotation.PropertyDescription; /** * Parameters for the {@link com.acme.internal.ExampleMacro} Macro. */ public class ExampleMacroParameters { /** * @see {@link #getParameter()} */ private String parameter; /** * @return the example parameter */ public String getParameter() { return this.parameter; } /** * @param parameter the example parameter */ @PropertyMandatory @PropertyDescription("Simple Example parameter") public void setParameter(String parameter) { this.parameter = parameter; } }Rendering Macros covers what this bean declares and how the editors read it; the Properties Module documents the annotations this example does not use.
- Read example1.test. The rendering system has a test framework that makes a macro easy to test, from a declarative file like this one, which checks the macro called on its own:
.runTransformations .#----------------------------------------------------- .input|xwiki/2.1 .# Test the macro in standalone mode .#----------------------------------------------------- {{simple-example parameter="hello"/}} .#----------------------------------------------------- .expect|xhtml/1.0 .#----------------------------------------------------- <p>hello</p> .#----------------------------------------------------- .expect|event/1.0 .#----------------------------------------------------- beginDocument beginMacroMarkerStandalone [simple-example] [parameter=hello] beginParagraph onWord [hello] endParagraph endMacroMarkerStandalone [simple-example] [parameter=hello] endDocumentThe framework parses the input, here XWiki Syntax 2.1, and compares the result against every expect block the file declares: XHTML 1.0, and the events the rendering system produces. Those events are its pivot format, the one every renderer consumes, so they state the result more precisely than any output syntax does. .runTransformations is what makes the macro transformation run on the parsed content, and so what makes the macro execute at all.
- Read example2.test, the same macro called inside a sentence:
.runTransformations .#----------------------------------------------------- .input|xwiki/2.1 .# Test the macro in inline mode .#----------------------------------------------------- This is inline {{simple-example parameter="hello"/}} .#----------------------------------------------------- .expect|xhtml/1.0 .#----------------------------------------------------- <p>This is inline hello</p> .#----------------------------------------------------- .expect|event/1.0 .#----------------------------------------------------- beginDocument beginParagraph onWord [This] onSpace onWord [is] onSpace onWord [inline] onSpace beginMacroMarkerInline [simple-example] [parameter=hello] onWord [hello] endMacroMarkerInline [simple-example] [parameter=hello] endParagraph endDocumentThe macro is called inside a paragraph this time, so the marker is beginMacroMarkerInline and the word it returns joins the sentence instead of starting a paragraph of its own.
- Build the module and run both tests:
mvn installIntegrationTests.java is what runs them. It executes every *.test file found on the classpath, so a new test file needs no new Java:
package com.acme; import org.xwiki.rendering.test.integration.junit5.RenderingTest; import org.xwiki.test.annotation.AllComponents; /** * Run all tests found in {@code *.test} files located in the classpath. These {@code *.test} files must follow the * conventions described in {@link org.xwiki.rendering.test.integration.TestDataParser}. */ @AllComponents class IntegrationTests extends RenderingTest { } - Deploy the built jar. A macro is an XWiki Component, and is deployed like any other component, as the "Deploying the Component" section of Writing Components describes.
- Call the macro from the content of a wiki page, on its own and inside a sentence:
{{simple-example parameter="hello"/}} This is inline {{simple-example parameter="hello"/}} - View that page. Each call is replaced by the value it was given, as a paragraph the first time and as a word in the sentence the second time:
hello This is inline hello
FAQ
How do I translate the name, description and parameters of the macro?
Ship a translation resource in the module, using the same keys as a wiki macro: Translate a Wiki Macro lists them.