You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by sl...@apache.org on 2026/09/03 16:13:58 UTC

(maven-doxia) branch master updated: Render unknown Sink events as raw HTML in MarkdownSink

This is an automated email from the ASF dual-hosted git repository.

slachiewicz pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git


The following commit(s) were added to refs/heads/master by this push:
     new 571fab11 Render unknown Sink events as raw HTML in MarkdownSink
571fab11 is described below

commit 571fab113ab51ee85f747642bba6384ddf9a40d9
Author: Sylwester Lachiewicz <sl...@apache.org>
AuthorDate: Mon Aug 24 12:55:30 2026 +0200

    Render unknown Sink events as raw HTML in MarkdownSink
    
    MarkdownSink silently dropped any Sink event it had no dedicated
    handling for (e.g. <object>, image maps), taking the content that
    event carried with it. Emit them as raw HTML instead, mirroring
    Xhtml5BaseSink, but never self-close a non-void tag: HTML5 ignores
    a trailing slash on those, which would otherwise swallow all
    following content (see xdoc-selfclosing-raw-html for the matching
    parser-side fix).
---
 .../maven/doxia/module/markdown/MarkdownSink.java  | 39 ++++++++++++++++++-
 .../doxia/module/markdown/MarkdownSinkTest.java    | 45 ++++++++++++++++++++++
 2 files changed, 82 insertions(+), 2 deletions(-)

diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
index 690be2e5..ea534e74 100644
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
@@ -38,6 +38,7 @@ import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
 import org.apache.maven.doxia.util.DoxiaStringUtils;
 import org.apache.maven.doxia.util.DoxiaUtils;
 import org.apache.maven.doxia.util.HtmlTools;
+import org.jsoup.parser.Tag;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -1360,15 +1361,49 @@ public class MarkdownSink extends Xhtml5BaseSink implements MarkdownMarkup {
         write(text);
     }
 
+    /** Table row-grouping tags without their own content: {@link #unknown(String, Object[], SinkEventAttributes)}
+     * ignores these rather than rendering them as raw HTML. */
+    private static final Collection<String> IGNORED_TABLE_GROUPING_TAGS =
+            Collections.unmodifiableList(Arrays.asList("thead", "tbody", "tfoot"));
+
     /**
      * {@inheritDoc}
      *
-     * Unknown events just log a warning message but are ignored otherwise.
+     * Delegates tag reconstruction and attribute escaping to
+     * {@link Xhtml5BaseSink#unknown(String, Object[], SinkEventAttributes)}, so any tag recognized by
+     * {@link HtmlTools#getHtmlTag(String)} (e.g. {@code <object>}, {@code <map>}/{@code <area>}) is emitted as raw
+     * HTML instead of being silently dropped.
+     * <p>
+     * Unlike that XHTML sink, a {@link #TAG_TYPE_SIMPLE} event for a non-void element is never self-closed
+     * ({@code <name/>}): HTML5 has no self-closing syntax outside the fixed list of void elements, so a
+     * self-closed non-void tag such as {@code <object/>} would otherwise be re-parsed as an open tag that
+     * swallows all following content as its own (hidden) fallback children. Such events are instead written as
+     * an explicitly closed pair ({@code <name></name>}).
+     * <p>
+     * {@code <thead>}/{@code <tbody>}/{@code <tfoot>} are excluded: they carry no content of their own (Doxia's
+     * table sink API already fully captures the table structure via {@code table()}/{@code tableRows()}/
+     * {@code tableRow()}/{@code tableCell()}), and {@link MarkdownParser} fires these as unknown events for
+     * every table it parses (they have no dedicated sink method). Rendering them as raw HTML would inject a
+     * tag directly before the first cell of a row, which breaks GFM pipe-table syntax since a table row must
+     * start at the beginning of its line.
+     *
      * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
      */
     @Override
     public void unknown(String name, Object[] requiredParams, SinkEventAttributes attributes) {
-        LOGGER.warn("{}Unknown Sink event '" + name + "', ignoring!", getLocationLogPrefix());
+        if (IGNORED_TABLE_GROUPING_TAGS.contains(name)) {
+            return;
+        }
+        if (requiredParams != null
+                && requiredParams[0] instanceof Integer
+                && (Integer) requiredParams[0] == TAG_TYPE_SIMPLE
+                && HtmlTools.getHtmlTag(name) != null
+                && !Tag.valueOf(name).isSelfClosing()) {
+            super.unknown(name, new Object[] {TAG_TYPE_START}, attributes);
+            super.unknown(name, new Object[] {TAG_TYPE_END}, null);
+            return;
+        }
+        super.unknown(name, requiredParams, attributes);
     }
 
     @Override
diff --git a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java
index 713adaa7..5ca6c819 100644
--- a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java
+++ b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java
@@ -34,6 +34,7 @@ import org.apache.maven.doxia.sink.SinkEventAttributeSet;
 import org.apache.maven.doxia.sink.SinkEventAttributeSet.Semantics;
 import org.apache.maven.doxia.sink.impl.AbstractSinkTest;
 import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;
+import org.apache.maven.doxia.sink.impl.Xhtml5BaseSink;
 import org.apache.maven.doxia.util.DoxiaStringUtils;
 import org.junit.jupiter.api.Test;
 
@@ -741,4 +742,48 @@ class MarkdownSinkTest extends AbstractSinkTest {
                 + "|<pre><code>code with | and ` inside</code></pre>|" + EOL + EOL;
         assertEquals(expected, getSinkContent());
     }
+
+    /**
+     * A self-closing {@code <object/>} (as xdoc's parser emits it, i.e. a single {@code unknown} event with
+     * {@link Xhtml5BaseSink#TAG_TYPE_SIMPLE}) is not a void HTML element, so it must never be rendered
+     * self-closed: HTML5 ignores the trailing slash on a non-void tag and would otherwise treat everything that
+     * follows as {@code <object>} fallback content. See MDOXIA-159.
+     */
+    @Test
+    void unknownObjectEventIsRenderedAsExplicitlyClosedRawHtml() throws IOException {
+        SinkEventAttributeSet attributes = new SinkEventAttributeSet();
+        attributes.addAttribute("type", "image/svg+xml");
+        attributes.addAttribute("data", "x.svg");
+
+        try (Sink sink = getSink()) {
+            sink.unknown("object", new Object[] {Xhtml5BaseSink.TAG_TYPE_SIMPLE}, attributes);
+        }
+
+        assertEquals("<object type=\"image/svg+xml\" data=\"x.svg\"></object>", getSinkContent());
+    }
+
+    /**
+     * An image map ({@code <map>} wrapping a void {@code <area/>}) must survive conversion instead of being
+     * silently dropped like any other unknown event previously was. Unlike {@code <object>}, {@code <area>} is a
+     * void element, so it is still fine to render it self-closed. See MDOXIA-159.
+     */
+    @Test
+    void unknownMapAndAreaEventsAreRenderedAsRawHtml() throws IOException {
+        SinkEventAttributeSet mapAttributes = new SinkEventAttributeSet();
+        mapAttributes.addAttribute("name", "imgmap");
+        SinkEventAttributeSet areaAttributes = new SinkEventAttributeSet();
+        areaAttributes.addAttribute("shape", "rect");
+        areaAttributes.addAttribute("coords", "0,0,10,10");
+        areaAttributes.addAttribute("href", "a.html");
+
+        try (Sink sink = getSink()) {
+            sink.unknown("map", new Object[] {Xhtml5BaseSink.TAG_TYPE_START}, mapAttributes);
+            sink.unknown("area", new Object[] {Xhtml5BaseSink.TAG_TYPE_SIMPLE}, areaAttributes);
+            sink.unknown("map", new Object[] {Xhtml5BaseSink.TAG_TYPE_END}, null);
+        }
+
+        assertEquals(
+                "<map name=\"imgmap\"><area shape=\"rect\" coords=\"0,0,10,10\" href=\"a.html\" /></map>",
+                getSinkContent());
+    }
 }