Write a Log Highlighter for the Code Macro
Tutorial
This page builds a log language for the "code" Macro: a Java component that colours each line of a log according to its level. Building it shows how to highlight any language that the Pygments library bundled with XWiki doesn't know, which the Macro otherwise displays without highlighting. The Formatting page lists the other ways to extend the Macro.
- Add the Code Macro as a dependency of your Maven module:
<dependency> <groupId>org.xwiki.platform</groupId> <artifactId>xwiki-platform-rendering-macro-code</artifactId> <version>${platform.version}</version> <scope>provided</scope> </dependency> - Write a component that extends AbstractHighlightParser, with the name of the language as its hint. Its highlight method returns the code as a list of blocks: the plain/1.0 parser turns each line into words and spaces without interpreting any wiki markup, and a FormatBlock with a style parameter colours the lines that contain a level:
package com.acme.internal; import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import jakarta.inject.Inject; import jakarta.inject.Named; import jakarta.inject.Singleton; import org.xwiki.component.annotation.Component; import org.xwiki.rendering.block.Block; import org.xwiki.rendering.block.FormatBlock; import org.xwiki.rendering.block.NewLineBlock; import org.xwiki.rendering.listener.Format; import org.xwiki.rendering.parser.AbstractHighlightParser; import org.xwiki.rendering.parser.ParseException; import org.xwiki.rendering.parser.Parser; import org.xwiki.rendering.syntax.Syntax; import org.xwiki.rendering.syntax.SyntaxType; @Component @Singleton @Named("log") public class LogHighlightParser extends AbstractHighlightParser { private static final Syntax SYNTAX = new Syntax(new SyntaxType("log-highlight", "Log"), "1.0"); private static final Pattern LEVEL = Pattern.compile("\\b(ERROR|WARN|INFO|DEBUG)\\b"); private static final Map<String, String> STYLES = Map.of( "ERROR", "color: #C00000; font-weight: bold;", "WARN", "color: #A05A00;", "INFO", "color: #006000;", "DEBUG", "color: #707070;"); @Inject @Named("plain/1.0") private Parser plainTextParser; @Override public Syntax getSyntax() { return SYNTAX; } @Override public List<Block> highlight(String syntaxId, Reader source) throws ParseException { List<Block> blocks = new ArrayList<>(); try (BufferedReader reader = new BufferedReader(source)) { String line = reader.readLine(); while (line != null) { blocks.addAll(highlightLine(line)); line = reader.readLine(); if (line != null) { blocks.add(new NewLineBlock()); } } } catch (IOException e) { throw new ParseException("Failed to read the log", e); } return blocks; } private List<Block> highlightLine(String line) throws ParseException { if (line.isEmpty()) { return List.of(); } List<Block> lineBlocks = this.plainTextParser.parse(new StringReader(line)).getChildren().get(0).getChildren(); Matcher matcher = LEVEL.matcher(line); if (!matcher.find()) { return lineBlocks; } FormatBlock coloredLine = new FormatBlock(lineBlocks, Format.NONE); coloredLine.setParameter("style", STYLES.get(matcher.group(1))); return List.of(coloredLine); } } - Register the component by adding its class name to src/main/resources/META-INF/components.txt:
com.acme.internal.LogHighlightParser - Build the module and deploy its JAR like any other component, as the "Deploying the Component" section of Creating XWiki Components describes.
- Edit a Page and use the new language in the language parameter of the Macro:
{{code language="log"}} 2026-11-12 10:15:02 INFO Starting the indexing of 1250 pages 2026-11-12 10:15:07 DEBUG Indexed page Sandbox.WebHome 2026-11-12 10:15:31 WARN Page Main.Archive is larger than 10 MB, its content is truncated 2026-11-12 10:16:44 ERROR Failed to index page Blog.Release-18: the page is locked 2026-11-12 10:16:45 INFO Indexing done in 103 seconds {{/code}} - View the Page. Each line of the log is coloured according to its level:

FAQ
Can a component replace Pygments for a language that Pygments knows?
Yes. The Macro first looks for a component whose hint is the value of its language parameter, and uses Pygments only when there is none.
Does the colour style configured for the Macro apply to the new language?
No. The rendering.macro.code.pygments.style property only sets the colours of Pygments. A component chooses its own colours, as the STYLES map of the example does.
Do the other parameters of the Macro work with the new language?
Yes. The Macro adds the box and the line numbers of the layout parameter around the blocks that the component returns. The line numbers are counted from the code itself, so they stay aligned with the lines only when the component returns one NewLineBlock per line break, empty lines included, as the example does.