Wiki source code of Write a Log Highlighter for the Code Macro
Last modified by Vincent Massol on 2026/11/12 20:27
Show last authors
| author | version | line-number | content |
|---|---|---|---|
| 1 | This page builds a ##log## language for the [["code" Macro>>doc:documentation.xs.user.base.page.edit.formatting.code-macro.WebHome]]: 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>>doc:documentation.xs.dev.edit.formatting.WebHome]] page lists the other ways to extend the Macro. | ||
| 2 | |||
| 3 | 1. Add the Code Macro as a dependency of your Maven module: ((( | ||
| 4 | {{code language="xml"}} | ||
| 5 | <dependency> | ||
| 6 | <groupId>org.xwiki.platform</groupId> | ||
| 7 | <artifactId>xwiki-platform-rendering-macro-code</artifactId> | ||
| 8 | <version>${platform.version}</version> | ||
| 9 | <scope>provided</scope> | ||
| 10 | </dependency> | ||
| 11 | {{/code}} | ||
| 12 | ))) | ||
| 13 | 1. 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: ((( | ||
| 14 | {{code language="java"}} | ||
| 15 | package com.acme.internal; | ||
| 16 | |||
| 17 | import java.io.BufferedReader; | ||
| 18 | import java.io.IOException; | ||
| 19 | import java.io.Reader; | ||
| 20 | import java.io.StringReader; | ||
| 21 | import java.util.ArrayList; | ||
| 22 | import java.util.List; | ||
| 23 | import java.util.Map; | ||
| 24 | import java.util.regex.Matcher; | ||
| 25 | import java.util.regex.Pattern; | ||
| 26 | |||
| 27 | import jakarta.inject.Inject; | ||
| 28 | import jakarta.inject.Named; | ||
| 29 | import jakarta.inject.Singleton; | ||
| 30 | |||
| 31 | import org.xwiki.component.annotation.Component; | ||
| 32 | import org.xwiki.rendering.block.Block; | ||
| 33 | import org.xwiki.rendering.block.FormatBlock; | ||
| 34 | import org.xwiki.rendering.block.NewLineBlock; | ||
| 35 | import org.xwiki.rendering.listener.Format; | ||
| 36 | import org.xwiki.rendering.parser.AbstractHighlightParser; | ||
| 37 | import org.xwiki.rendering.parser.ParseException; | ||
| 38 | import org.xwiki.rendering.parser.Parser; | ||
| 39 | import org.xwiki.rendering.syntax.Syntax; | ||
| 40 | import org.xwiki.rendering.syntax.SyntaxType; | ||
| 41 | |||
| 42 | @Component | ||
| 43 | @Singleton | ||
| 44 | @Named("log") | ||
| 45 | public class LogHighlightParser extends AbstractHighlightParser | ||
| 46 | { | ||
| 47 | private static final Syntax SYNTAX = new Syntax(new SyntaxType("log-highlight", "Log"), "1.0"); | ||
| 48 | |||
| 49 | private static final Pattern LEVEL = Pattern.compile("\\b(ERROR|WARN|INFO|DEBUG)\\b"); | ||
| 50 | |||
| 51 | private static final Map<String, String> STYLES = Map.of( | ||
| 52 | "ERROR", "color: #C00000; font-weight: bold;", | ||
| 53 | "WARN", "color: #A05A00;", | ||
| 54 | "INFO", "color: #006000;", | ||
| 55 | "DEBUG", "color: #707070;"); | ||
| 56 | |||
| 57 | @Inject | ||
| 58 | @Named("plain/1.0") | ||
| 59 | private Parser plainTextParser; | ||
| 60 | |||
| 61 | @Override | ||
| 62 | public Syntax getSyntax() | ||
| 63 | { | ||
| 64 | return SYNTAX; | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public List<Block> highlight(String syntaxId, Reader source) throws ParseException | ||
| 69 | { | ||
| 70 | List<Block> blocks = new ArrayList<>(); | ||
| 71 | try (BufferedReader reader = new BufferedReader(source)) { | ||
| 72 | String line = reader.readLine(); | ||
| 73 | while (line != null) { | ||
| 74 | blocks.addAll(highlightLine(line)); | ||
| 75 | line = reader.readLine(); | ||
| 76 | if (line != null) { | ||
| 77 | blocks.add(new NewLineBlock()); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } catch (IOException e) { | ||
| 81 | throw new ParseException("Failed to read the log", e); | ||
| 82 | } | ||
| 83 | return blocks; | ||
| 84 | } | ||
| 85 | |||
| 86 | private List<Block> highlightLine(String line) throws ParseException | ||
| 87 | { | ||
| 88 | if (line.isEmpty()) { | ||
| 89 | return List.of(); | ||
| 90 | } | ||
| 91 | |||
| 92 | List<Block> lineBlocks = this.plainTextParser.parse(new StringReader(line)).getChildren().get(0).getChildren(); | ||
| 93 | |||
| 94 | Matcher matcher = LEVEL.matcher(line); | ||
| 95 | if (!matcher.find()) { | ||
| 96 | return lineBlocks; | ||
| 97 | } | ||
| 98 | |||
| 99 | FormatBlock coloredLine = new FormatBlock(lineBlocks, Format.NONE); | ||
| 100 | coloredLine.setParameter("style", STYLES.get(matcher.group(1))); | ||
| 101 | return List.of(coloredLine); | ||
| 102 | } | ||
| 103 | } | ||
| 104 | {{/code}} | ||
| 105 | ))) | ||
| 106 | 1. Register the component by adding its class name to ##src/main/resources/META-INF/components.txt##: ((( | ||
| 107 | {{code language="none"}} | ||
| 108 | com.acme.internal.LogHighlightParser | ||
| 109 | {{/code}} | ||
| 110 | ))) | ||
| 111 | 1. Build the module and deploy its JAR like any other component, as the "Deploying the Component" section of [[Creating XWiki Components>>doc:xwiki:Documentation.DevGuide.Tutorials.WritingComponents.WebHome]] describes. | ||
| 112 | 1. Edit a Page and use the new language in the ##language## parameter of the Macro: ((( | ||
| 113 | {{code language="xwiki"}} | ||
| 114 | {{code language="log"}} | ||
| 115 | 2026-11-12 10:15:02 INFO Starting the indexing of 1250 pages | ||
| 116 | 2026-11-12 10:15:07 DEBUG Indexed page Sandbox.WebHome | ||
| 117 | 2026-11-12 10:15:31 WARN Page Main.Archive is larger than 10 MB, its content is truncated | ||
| 118 | 2026-11-12 10:16:44 ERROR Failed to index page Blog.Release-18: the page is locked | ||
| 119 | 2026-11-12 10:16:45 INFO Indexing done in 103 seconds | ||
| 120 | {{/code}} | ||
| 121 | {{/code}} | ||
| 122 | ))) | ||
| 123 | 1. View the Page. Each line of the log is coloured according to its level:((( | ||
| 124 | {{image reference="code-macro-log-language.png" size="extra" alt="A log displayed by the code Macro, with the INFO lines in green, the DEBUG line in grey, the WARN line in orange and the ERROR line in bold red"/}} | ||
| 125 | ))) |