From ab299a044b188842ba2109dabb5eb61fa625b48d Mon Sep 17 00:00:00 2001 From: Guillaume Nodet Date: Fri, 23 Oct 2026 15:48:56 +0000 Subject: [PATCH] Fix xdoc generator to HTML-escape content of {@code} and {@literal} tags The chhorz javadoc-parser wraps {@code X} content verbatim in X without HTML-escaping X. When X contains angle brackets (e.g. {@code }), Jsoup subsequently parses as an unknown HTML tag and silently drops it from the XML output, causing the tag name to disappear from the generated site. This is visible in Maven's settings reference where descriptions such as {@code } and {@code } rendered with the tag name missing on the published site. Fix: pre-process {@code} and {@literal} inline tags in XdocGenerator.rewrite() by HTML-escaping their content before the javadoc-parser and Jsoup pipeline see it. The escapeHtml() helper normalises pre-existing </> entities first (which appear in CDATA-wrapped .mdo descriptions) so both forms are handled correctly and produce identical output. Add a test case to html4.mdo / html4.expected.xml that exercises the fix with {@code <name>value</name>} (decoded to {@code value} by MXParser) and verifies <name>value</name> in output. --- .../modello/plugin/xdoc/XdocGenerator.java | 45 ++++++++++++++++++- .../src/test/resources/html4.expected.xml | 13 +++++- .../src/test/resources/html4.mdo | 8 ++++ 3 files changed, 64 insertions(+), 2 deletions(-) diff --git a/modello-plugins/modello-plugin-xdoc/src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java b/modello-plugins/modello-plugin-xdoc/src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java index 9fceef8d6..289b6c9a6 100644 --- a/modello-plugins/modello-plugin-xdoc/src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java +++ b/modello-plugins/modello-plugin-xdoc/src/main/java/org/codehaus/modello/plugin/xdoc/XdocGenerator.java @@ -29,6 +29,8 @@ import java.io.Writer; import java.nio.charset.StandardCharsets; import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; import com.github.chhorz.javadoc.JavaDoc; @@ -685,6 +687,43 @@ private static void writeMarkupElement(XMLWriter w, String name, String markup) w.endElement(); } + /** + * Pattern matching {@code {@code ...}} and {@code {@literal ...}} inline tags, + * including nested braces. + */ + private static final Pattern INLINE_CODE_TAG_PATTERN = + Pattern.compile("\\{@(?:code|literal)\\s+((?:[^{}]|\\{[^{}]*\\})*)\\}"); + + /** + * HTML-escapes {@code <}, {@code >} and {@code &} in {@code s}. + * Handles both raw characters and pre-existing HTML entities (e.g. {@code <} from + * CDATA-wrapped .mdo descriptions) so that the result is always a clean, once-escaped + * HTML string. + */ + private static String escapeHtml(String s) { + // Normalise any pre-escaped entities first so we never double-escape + String unescaped = s.replace("<", "<").replace(">", ">").replace("&", "&"); + return unescaped.replace("&", "&").replace("<", "<").replace(">", ">"); + } + + /** + * Pre-processes Javadoc {@code {@code}} and {@code {@literal}} inline tags so that + * their content is HTML-escaped before being fed into the HTML/Jsoup pipeline. + * + *

Without this step, content such as {@code {@code }} would be passed + * verbatim to the Javadoc parser which wraps it in a {@code } HTML element + * without escaping; Jsoup would then silently drop the unknown {@code } tag.

+ */ + private static String preprocessInlineCodeTags(String text) { + StringBuffer sb = new StringBuffer(); + Matcher m = INLINE_CODE_TAG_PATTERN.matcher(text); + while (m.find()) { + m.appendReplacement(sb, Matcher.quoteReplacement("" + escapeHtml(m.group(1)) + "")); + } + m.appendTail(sb); + return sb.toString(); + } + /** * Ensures that text will have balanced tags * @@ -692,10 +731,14 @@ private static void writeMarkupElement(XMLWriter w, String name, String markup) * @return valid XML string */ private static String rewrite(String text) { + // Pre-process {@code} / {@literal} tags to HTML-escape their content before the + // Javadoc parser and Jsoup pipeline see it; otherwise angle brackets inside those + // tags are treated as HTML elements and silently dropped by Jsoup. + String preprocessed = preprocessInlineCodeTags(text); JavaDoc javaDoc = JavaDocParserBuilder.withStandardJavadocTags() .withOutputType(OutputType.HTML) .build() - .parse(text); + .parse(preprocessed); String html = javaDoc.getDescription() + javaDoc.getTags().stream() .map(XdocGenerator::renderJavaDocTag) diff --git a/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.expected.xml b/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.expected.xml index a629a5bf3..15410f53c 100644 --- a/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.expected.xml +++ b/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.expected.xml @@ -13,6 +13,7 @@ <model> <extend/> + <properties/> </model> @@ -41,8 +42,18 @@ This is a description in HTML4 1.0.0 No description. + + + properties + + + String + + 1.0.0 + The format is <name>value</name>. + - + \ No newline at end of file diff --git a/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.mdo b/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.mdo index cb85dc092..b9f1d8d57 100644 --- a/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.mdo +++ b/modello-plugins/modello-plugin-xdoc/src/test/resources/html4.mdo @@ -31,6 +31,14 @@ 1.0.0+ String + + properties + 1.0.0+ + String + + The format is {@code <name>value</name>}. + +