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

kwin pushed a commit to branch bugfix/no-escaping-in-code-spans
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git

commit 2756a0f566439cbb56173b41ce2f00f4de2269b9
Author: Konrad Windszus <[email protected]>
AuthorDate: Thu Jan 18 15:09:04 2024 +0100

    [DOXIA-727] Fix escaping inside code spans
    
    Code cleanup to keep track of surrounding element contexts
    Remove redundant flags
    Get rid of deprecated classes
---
 .../maven/doxia/sink/impl/AbstractSinkTest.java    |   4 +-
 .../doxia/module/markdown/MarkdownMarkup.java      |   2 +
 .../maven/doxia/module/markdown/MarkdownSink.java  | 479 ++++++++++-----------
 .../doxia/module/markdown/MarkdownSinkTest.java    |  16 +
 4 files changed, 258 insertions(+), 243 deletions(-)

diff --git 
a/doxia-core/src/test/java/org/apache/maven/doxia/sink/impl/AbstractSinkTest.java
 
b/doxia-core/src/test/java/org/apache/maven/doxia/sink/impl/AbstractSinkTest.java
index 90df6f9d..e109bad1 100644
--- 
a/doxia-core/src/test/java/org/apache/maven/doxia/sink/impl/AbstractSinkTest.java
+++ 
b/doxia-core/src/test/java/org/apache/maven/doxia/sink/impl/AbstractSinkTest.java
@@ -613,7 +613,7 @@ public abstract class AbstractSinkTest extends 
AbstractModuleTest {
         if (isXmlSink()) {
             assertThat(wrapXml(actual), isIdenticalTo(wrapXml(expected)));
         } else {
-            assertEquals(actual, expected);
+            assertEquals(expected, actual);
         }
     }
 
@@ -632,7 +632,7 @@ public abstract class AbstractSinkTest extends 
AbstractModuleTest {
         if (isXmlSink()) {
             assertThat(wrapXml(actual), isIdenticalTo(wrapXml(expected)));
         } else {
-            assertEquals(actual, expected);
+            assertEquals(expected, actual);
         }
     }
 
diff --git 
a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java
 
b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java
index 0ccefee8..331747f2 100644
--- 
a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java
+++ 
b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java
@@ -38,6 +38,8 @@ public interface MarkdownMarkup extends TextMarkup {
 
     String BLANK_LINE = EOL + EOL;
 
+    /** indentation e.g. for paragraphs inside lists */
+    String INDENT = StringUtils.repeat(String.valueOf(SPACE), 4);
     // ----------------------------------------------------------------------
     // Markup syntax
     // ----------------------------------------------------------------------
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 6977b72b..172d4cf2 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
@@ -23,9 +23,11 @@ import java.io.Writer;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
-import java.util.Stack;
+import java.util.Queue;
+import java.util.function.UnaryOperator;
 import java.util.stream.Collectors;
 
 import org.apache.commons.lang3.StringUtils;
@@ -50,7 +52,7 @@ public class MarkdownSink extends AbstractTextSink implements 
MarkdownMarkup {
     // ----------------------------------------------------------------------
 
     /**  A buffer that holds the current text when headerFlag or bufferFlag 
set to <code>true</code>. The content of this buffer is already escaped. */
-    private StringBuffer buffer;
+    private StringBuilder buffer;
 
     /**  author. */
     private Collection<String> authors;
@@ -64,56 +66,81 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     /**  linkName. */
     private String linkName;
 
-    /** startFlag. */
-    private boolean startFlag;
-
-    /**  tableCaptionFlag. */
-    private boolean tableCaptionFlag;
-
-    /** tableCellFlag, set to {@code true} inside table (header) cells */
-    private boolean tableCellFlag;
-
-    /** tableRowHeaderFlag, set to {@code true} for table rows containing at 
least one table header cell  */
+    /** tableHeaderCellFlag, set to {@code true} for table rows containing at 
least one table header cell  */
     private boolean tableHeaderCellFlag;
 
-    /**  headerFlag. */
-    private boolean headerFlag;
-
-    /**  bufferFlag, set to {@code true} in certain elements to prevent direct 
writing during {@link #text(String, SinkEventAttributes)} */
-    private boolean bufferFlag;
-
-    /**  verbatimFlag. */
-    private boolean verbatimFlag;
-
-    /** figure flag, set to {@code true} between {@link 
#figure(SinkEventAttributes)} and {@link #figure_()} events */
-    private boolean figureFlag;
-
     /**  number of cells in a table. */
     private int cellCount;
 
-    /**  The writer to use. */
-    private final PrintWriter writer;
-
-    /** {@code true} when last written character in {@link #writer} was a line 
separator, or writer is still at the beginning */
-    private boolean isWriterAtStartOfNewLine;
-
     /**  justification of table cells per column. */
     private List<Integer> cellJustif;
 
     /**  is header row */
     private boolean isFirstTableRow;
 
-    /**  listNestingLevel, 0 outside the list, 1 for the top-level list, 2 for 
a nested list, 3 for a list nested inside a nested list, .... */
-    private int listNestingLevel;
+    /**  The writer to use. */
+    private final PrintWriter writer;
+
+    /** {@code true} when last written character in {@link #writer} was a line 
separator, or writer is still at the beginning */
+    private boolean isWriterAtStartOfNewLine;
 
-    /**  listStyles. */
-    private final Stack<String> listStyles;
+    /** Keep track of end markup for inline events. */
+    protected Queue<Queue<String>> inlineStack = Collections.asLifoQueue(new 
LinkedList<>());
 
-    /** Keep track of the closing tags for inline events. */
-    protected Stack<List<String>> inlineStack = new Stack<>();
+    /** The context of the surrounding elements as stack (LIFO) */
+    protected Queue<ElementContext> elementContextStack = 
Collections.asLifoQueue(new LinkedList<>());
 
     private String figureSrc;
 
+    /** Most important contextual metadata (of the surrounding element) */
+    enum ElementContext {
+        HEAD("head", true, null, true),
+        BODY("body", true, MarkdownSink::escapeMarkdown),
+        // only the elements, which affect rendering of children and are 
different from BODY or HEAD are listed here
+        FIGURE("", false, MarkdownSink::escapeMarkdown, true),
+        CODE_BLOCK("code block", true, null),
+        CODE_SPAN("code span", false, null),
+        TABLE_CAPTION("table caption", false, MarkdownSink::escapeMarkdown),
+        TABLE_CELL("table cell", false, MarkdownSink::escapeForTableCell, 
true),
+        // same parameters as BODY but paragraphs inside lists are handled 
differently
+        LIST("list (regular)", true, MarkdownSink::escapeMarkdown),
+        NUMBERED_LIST("list (numbered)", true, MarkdownSink::escapeMarkdown);
+
+        final String name;
+        /**
+         * {@code true} if block element, otherwise {@code false} for inline 
elements
+         */
+        final boolean isBlock; 
+        /**
+         * The function to call to escape the given text. The function is 
supposed to return the escaped text or return just the given text if no 
escaping is necessary in this context
+         */
+        final UnaryOperator<String> escapeFunction;
+
+        /**
+         * if {@code true} requires buffering any text appearing inside this 
context
+         */
+        final boolean requiresBuffering;
+
+        ElementContext(String name, boolean isBlock, UnaryOperator<String> 
escapeFunction) {
+            this(name, isBlock, escapeFunction, false);
+        }
+
+        ElementContext(String name, boolean isBlock, UnaryOperator<String> 
escapeFunction, boolean requiresBuffering) {
+            this.name = name;
+            this.isBlock = isBlock;
+            this.escapeFunction = escapeFunction;
+            this.requiresBuffering = requiresBuffering;
+        }
+
+        private String escape(String text) {
+            // is escaping necessary at all?
+            if (escapeFunction == null) {
+                return text;
+            } else {
+                return escapeFunction.apply(text);
+            }
+        }
+    }
     // ----------------------------------------------------------------------
     // Public protected methods
     // ----------------------------------------------------------------------
@@ -126,94 +153,90 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     protected MarkdownSink(Writer writer) {
         this.writer = new PrintWriter(writer);
         isWriterAtStartOfNewLine = true;
-        this.listStyles = new Stack<>();
 
         init();
     }
 
+    private void leaveContext(ElementContext expectedContext) {
+        ElementContext removedContext = elementContextStack.remove();
+        if (removedContext != expectedContext) {
+            throw new IllegalStateException("Unexpected context " + 
removedContext);
+        }
+    }
     /**
      * Returns the buffer that holds the current text.
      *
      * @return A StringBuffer.
      */
-    protected StringBuffer getBuffer() {
+    protected StringBuilder getBuffer() {
         return buffer;
     }
 
-    /**
-     * Used to determine whether we are in head mode.
-     *
-     * @param headFlag True for head mode.
-     */
-    protected void setHeadFlag(boolean headFlag) {
-        this.headerFlag = headFlag;
-    }
-
     @Override
     protected void init() {
         super.init();
 
         resetBuffer();
 
-        this.listNestingLevel = 0;
-
         this.authors = new LinkedList<>();
         this.title = null;
         this.date = null;
         this.linkName = null;
-        this.startFlag = true;
-        this.tableCaptionFlag = false;
-        this.tableCellFlag = false;
         this.tableHeaderCellFlag = false;
-        this.headerFlag = false;
-        this.bufferFlag = false;
-        this.verbatimFlag = false;
-        this.figureFlag = false;
         this.cellCount = 0;
         this.cellJustif = null;
-        this.listStyles.clear();
+        this.elementContextStack.clear();
         this.inlineStack.clear();
+        // always set a default context (at least for tests not emitting a 
body)
+        elementContextStack.add(ElementContext.BODY);
     }
 
     /**
      * Reset the StringBuilder.
      */
     protected void resetBuffer() {
-        buffer = new StringBuffer();
+        buffer = new StringBuilder();
     }
 
     @Override
     public void head(SinkEventAttributes attributes) {
-        boolean startFlag = this.startFlag;
-
         init();
-
-        headerFlag = true;
-        this.startFlag = startFlag;
+        // remove default body context here
+        leaveContext(ElementContext.BODY);
+        elementContextStack.add(ElementContext.HEAD);
     }
 
     @Override
     public void head_() {
-        headerFlag = false;
-
+        leaveContext(ElementContext.HEAD);
         // only write head block if really necessary
         if (title == null && authors.isEmpty() && date == null) {
             return;
         }
-        write(METADATA_MARKUP + EOL);
+        writeUnescaped(METADATA_MARKUP + EOL);
         if (title != null) {
-            write("title: " + title + EOL);
+            writeUnescaped("title: " + title + EOL);
         }
         if (!authors.isEmpty()) {
-            write("author: " + EOL);
+            writeUnescaped("author: " + EOL);
             for (String author : authors) {
-                write("  - " + author + EOL);
+                writeUnescaped("  - " + author + EOL);
             }
         }
         if (date != null) {
-            write("date: " + date + EOL);
+            writeUnescaped("date: " + date + EOL);
         }
-        write(METADATA_MARKUP + BLANK_LINE);
+        writeUnescaped(METADATA_MARKUP + BLANK_LINE);
+    }
+
+    @Override
+    public void body(SinkEventAttributes attributes) {
+        elementContextStack.add(ElementContext.BODY);
+    }
+
+    @Override
+    public void body_() {
+        leaveContext(ElementContext.BODY);
     }
 
     @Override
@@ -243,31 +266,25 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     @Override
     public void sectionTitle(int level, SinkEventAttributes attributes) {
         if (level > 0) {
-            write(StringUtils.repeat(SECTION_TITLE_START_MARKUP, level) + 
SPACE);
+            writeUnescaped(StringUtils.repeat(SECTION_TITLE_START_MARKUP, 
level) + SPACE);
         }
     }
 
     @Override
     public void sectionTitle_(int level) {
         if (level > 0) {
-            write(BLANK_LINE);
+            writeUnescaped(BLANK_LINE);
         }
     }
 
     @Override
     public void list(SinkEventAttributes attributes) {
-        listNestingLevel++;
-        listStyles.push(LIST_UNORDERED_ITEM_START_MARKUP);
+        elementContextStack.add(ElementContext.LIST);
     }
 
     @Override
     public void list_() {
-        listNestingLevel--;
-        if (listNestingLevel == 0) {
-            write(EOL); // end add blank line (together with the preceding EOL 
of the item) only in case this was not
-            // nested
-        }
-        listStyles.pop();
+        endNumberedOrRegularList(false);
     }
 
     @Override
@@ -280,9 +297,9 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
         orderedOrUnorderedListItem_();
     }
 
-    /** {@inheritDoc} */
+    @Override
     public void numberedList(int numbering, SinkEventAttributes attributes) {
-        listNestingLevel++;
+        elementContextStack.add(ElementContext.NUMBERED_LIST);
         // markdown only supports decimal numbering
         if (numbering != NUMBERING_DECIMAL) {
             LOGGER.warn(
@@ -290,18 +307,20 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
                     NUMBERING_DECIMAL,
                     numbering);
         }
-        String style = LIST_ORDERED_ITEM_START_MARKUP;
-        listStyles.push(style);
     }
 
     @Override
     public void numberedList_() {
-        listNestingLevel--;
-        if (listNestingLevel == 0) {
-            write(EOL); // end add blank line (together with the preceding EOL 
of the item) only in case this was not
+        endNumberedOrRegularList(true);
+    }
+
+    private void endNumberedOrRegularList(boolean isNumberedList) {
+        ElementContext context = isNumberedList ? ElementContext.NUMBERED_LIST 
: ElementContext.LIST;
+        leaveContext(context);
+        if (getListNestingLevel() < 0) {
+            writeUnescaped(EOL); // end add blank line (together with the 
preceding EOL of the item) only in case this was not
             // nested
         }
-        listStyles.pop();
     }
 
     @Override
@@ -315,19 +334,41 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     }
 
     private void orderedOrUnorderedListItem() {
-        write(getListPrefix());
+        writeUnescaped(getListItemPrefix());
     }
 
     private void orderedOrUnorderedListItem_() {
         ensureBeginningOfLine();
     }
 
-    private String getListPrefix() {
-        StringBuilder prefix = new StringBuilder();
-        for (int indent = 1; indent < listNestingLevel; indent++) {
-            prefix.append("    "); // 4 spaces per indentation level
+    /**
+     * 
+     * @return the nesting level of the list. -1 for outside a list, 0 for 
non-nested list, 1 for list inside a list, ....
+     */
+    private int getListNestingLevel() {
+        return (int) (elementContextStack.stream().filter(e -> e == 
ElementContext.LIST).count() - 1);
+    }
+
+    /**
+     * 
+     * @return {@code true} if in numbered list, {@code false} if inside 
regular list
+     * @throws IllegalStateException if not inside a list at all
+     */
+    private boolean isInNumberedList() {
+        ElementContext context = elementContextStack.element();
+        if (context == ElementContext.LIST) {
+            return false;
+        } else if (context == ElementContext.NUMBERED_LIST) {
+            return true;
+        } else {
+            throw new IllegalStateException("Not inside a list but inside " + 
context);
         }
-        prefix.append(listStyles.peek());
+    }
+
+    private String getListItemPrefix() {
+        StringBuilder prefix = new StringBuilder();
+        prefix.append(StringUtils.repeat(INDENT, getListNestingLevel())); // 4 
spaces per list nesting level
+        prefix.append(isInNumberedList() ? LIST_ORDERED_ITEM_START_MARKUP : 
LIST_UNORDERED_ITEM_START_MARKUP);
         prefix.append(SPACE);
         return prefix.toString();
     }
@@ -335,34 +376,32 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     @Override
     public void definitionList(SinkEventAttributes attributes) {
         LOGGER.warn("Definition list not natively supported in Markdown, 
rendering HTML instead");
-        write("<dl>" + EOL);
+        writeUnescaped("<dl>" + EOL);
     }
 
     @Override
     public void definitionList_() {
-        verbatimFlag = true;
-        write("</dl>" + BLANK_LINE);
+        writeUnescaped("</dl>" + BLANK_LINE);
     }
 
     @Override
     public void definedTerm(SinkEventAttributes attributes) {
-        write("<dt>");
-        verbatimFlag = false;
+        writeUnescaped("<dt>");
     }
 
     @Override
     public void definedTerm_() {
-        write("</dt>" + EOL);
+        writeUnescaped("</dt>" + EOL);
     }
 
     @Override
     public void definition(SinkEventAttributes attributes) {
-        write("<dd>");
+        writeUnescaped("<dd>");
     }
 
     @Override
     public void definition_() {
-        write("</dd>" + EOL);
+        writeUnescaped("</dd>" + EOL);
     }
 
     @Override
@@ -372,36 +411,43 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
 
     @Override
     public void paragraph(SinkEventAttributes attributes) {
-        ensureBeginningOfLine();
+        // ignore paragraphs in inline elements
+        if (elementContextStack.element().isBlock) {
+            ensureBeginningOfLine();
+            if (elementContextStack.element() == ElementContext.LIST) {
+                // indentation is mandatory inside lists (only for first line)
+                writeUnescaped(INDENT);
+            }
+        }
     }
 
     @Override
     public void paragraph_() {
-        if (tableCellFlag || listNestingLevel > 0) {
-            // ignore paragraphs in table cells or lists
-        } else {
-            write(BLANK_LINE);
+        // ignore paragraphs in inline elements
+        if (elementContextStack.element().isBlock) {
+            writeUnescaped(BLANK_LINE);
         }
     }
 
     @Override
     public void verbatim(SinkEventAttributes attributes) {
+        // always assume is supposed to be monospaced (i.e. emitted inside a 
<pre><code>...</code></pre>)
         ensureBeginningOfLine();
-        verbatimFlag = true;
-        write(VERBATIM_START_MARKUP + EOL);
+        elementContextStack.add(ElementContext.CODE_BLOCK);
+        writeUnescaped(VERBATIM_START_MARKUP + EOL);
     }
 
     @Override
     public void verbatim_() {
         ensureBeginningOfLine();
-        write(VERBATIM_END_MARKUP + BLANK_LINE);
-        verbatimFlag = false;
+        writeUnescaped(VERBATIM_END_MARKUP + BLANK_LINE);
+        leaveContext(ElementContext.CODE_BLOCK);
     }
 
     @Override
     public void horizontalRule(SinkEventAttributes attributes) {
         ensureBeginningOfLine();
-        write(HORIZONTAL_RULE_MARKUP + BLANK_LINE);
+        writeUnescaped(HORIZONTAL_RULE_MARKUP + BLANK_LINE);
     }
 
     @Override
@@ -442,13 +488,13 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
             // afterwards emit the first row
         }
 
-        write(TABLE_ROW_PREFIX);
+        writeUnescaped(TABLE_ROW_PREFIX);
 
-        write(buffer.toString());
+        writeUnescaped(buffer.toString());
 
         resetBuffer();
 
-        write(EOL);
+        writeUnescaped(EOL);
 
         if (isFirstTableRow) {
             // emit delimiter row
@@ -461,16 +507,16 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     }
 
     private void writeEmptyTableHeader() {
-        write(TABLE_ROW_PREFIX);
+        writeUnescaped(TABLE_ROW_PREFIX);
         for (int i = 0; i < cellCount; i++) {
-            write(StringUtils.repeat(String.valueOf(SPACE), 3) + 
TABLE_CELL_SEPARATOR_MARKUP);
+            writeUnescaped(StringUtils.repeat(String.valueOf(SPACE), 3) + 
TABLE_CELL_SEPARATOR_MARKUP);
         }
-        write(EOL);
+        writeUnescaped(EOL);
     }
 
     /** Emit the delimiter row which determines the alignment */
     private void writeTableDelimiterRow() {
-        write(TABLE_ROW_PREFIX);
+        writeUnescaped(TABLE_ROW_PREFIX);
         int justification = Sink.JUSTIFY_LEFT;
         for (int i = 0; i < cellCount; i++) {
             // keep previous column's alignment in case too few are specified
@@ -479,18 +525,18 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
             }
             switch (justification) {
                 case Sink.JUSTIFY_RIGHT:
-                    write(TABLE_COL_RIGHT_ALIGNED_MARKUP);
+                    writeUnescaped(TABLE_COL_RIGHT_ALIGNED_MARKUP);
                     break;
                 case Sink.JUSTIFY_CENTER:
-                    write(TABLE_COL_CENTER_ALIGNED_MARKUP);
+                    writeUnescaped(TABLE_COL_CENTER_ALIGNED_MARKUP);
                     break;
                 default:
-                    write(TABLE_COL_LEFT_ALIGNED_MARKUP);
+                    writeUnescaped(TABLE_COL_LEFT_ALIGNED_MARKUP);
                     break;
             }
-            write(TABLE_CELL_SEPARATOR_MARKUP);
+            writeUnescaped(TABLE_CELL_SEPARATOR_MARKUP);
         }
-        write(EOL);
+        writeUnescaped(EOL);
     }
 
     @Override
@@ -521,7 +567,7 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
                 }
             }
         }
-        tableCellFlag = true;
+        elementContextStack.add(ElementContext.TABLE_CELL);
     }
 
     @Override
@@ -544,41 +590,32 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
      * Ends a table cell.
      */
     private void endTableCell() {
-        tableCellFlag = false;
+        leaveContext(ElementContext.TABLE_CELL);
         buffer.append(TABLE_CELL_SEPARATOR_MARKUP);
         cellCount++;
     }
 
     @Override
     public void tableCaption(SinkEventAttributes attributes) {
-        tableCaptionFlag = true;
+        elementContextStack.add(ElementContext.TABLE_CAPTION);
     }
 
     @Override
     public void tableCaption_() {
-        tableCaptionFlag = false;
+        leaveContext(ElementContext.TABLE_CAPTION);
     }
 
     @Override
     public void figure(SinkEventAttributes attributes) {
         figureSrc = null;
-        figureFlag = true;
-    }
-
-    @Override
-    public void figureCaption(SinkEventAttributes attributes) {
-        bufferFlag = true;
-    }
-
-    @Override
-    public void figureCaption_() {
-        bufferFlag = false;
+        elementContextStack.add(ElementContext.FIGURE);
     }
 
     @Override
     public void figureGraphics(String name, SinkEventAttributes attributes) {
         figureSrc = escapeMarkdown(name);
-        if (!figureFlag) {
+        // is it a standalone image (outside a figure)?
+        if (elementContextStack.peek() != ElementContext.FIGURE) {
             Object alt = attributes.getAttribute(SinkEventAttributes.ALT);
             if (alt == null) {
                 alt = "";
@@ -589,14 +626,14 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
 
     @Override
     public void figure_() {
+        leaveContext(ElementContext.FIGURE);
         writeImage(buffer.toString(), figureSrc);
-        figureFlag = false;
     }
 
     private void writeImage(String alt, String src) {
-        write("![");
-        write(alt);
-        write("](" + src + ")");
+        writeUnescaped("![");
+        writeUnescaped(alt);
+        writeUnescaped("](" + src + ")");
     }
 
     /** {@inheritDoc} */
@@ -612,69 +649,57 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
 
     /** {@inheritDoc} */
     public void link(String name, SinkEventAttributes attributes) {
-        if (!headerFlag) {
-            write(LINK_START_1_MARKUP);
-            linkName = name;
-        }
+        writeUnescaped(LINK_START_1_MARKUP);
+        linkName = name;
     }
 
     @Override
     public void link_() {
-        if (!headerFlag) {
-            write(LINK_START_2_MARKUP);
-            text(linkName.startsWith("#") ? linkName.substring(1) : linkName);
-            write(LINK_END_MARKUP);
-            linkName = null;
-        }
-    }
-
-    /**
-     * A link with a target.
-     *
-     * @param name The name of the link.
-     * @param target The link target.
-     */
-    void link(String name, String target) {
-        if (!headerFlag) {
-            write(LINK_START_1_MARKUP);
-        }
+        writeUnescaped(LINK_START_2_MARKUP);
+        text(linkName.startsWith("#") ? linkName.substring(1) : linkName);
+        writeUnescaped(LINK_END_MARKUP);
+        linkName = null;
     }
 
     @Override
     public void inline(SinkEventAttributes attributes) {
-        if (!headerFlag && !verbatimFlag) {
-            List<String> tags = new ArrayList<>();
-
-            if (attributes != null) {
+        Queue<String> endMarkups = Collections.asLifoQueue(new LinkedList<>());
+
+        if (attributes != null && elementContextStack.element() != 
ElementContext.CODE_BLOCK && elementContextStack.element() != 
ElementContext.CODE_SPAN) {
+            // code excludes other styles in markdown
+            if (attributes.containsAttribute(SinkEventAttributes.SEMANTICS, 
"code")
+                    || 
attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "monospaced")
+                    || attributes.containsAttribute(SinkEventAttributes.STYLE, 
"monospaced")) {
+                writeUnescaped(MONOSPACED_START_MARKUP);
+                endMarkups.add(MONOSPACED_END_MARKUP);
+                elementContextStack.add(ElementContext.CODE_SPAN);
+            } else {
                 // in XHTML "<em>" is used, but some tests still rely on the 
outdated "<italic>"
                 if 
(attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "em")
-                        || 
attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "italic")) {
-                    write(ITALIC_START_MARKUP);
-                    tags.add(0, ITALIC_END_MARKUP);
+                        || 
attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "italic")
+                        || 
attributes.containsAttribute(SinkEventAttributes.STYLE, "italic")) {
+                    writeUnescaped(ITALIC_START_MARKUP);
+                    endMarkups.add(ITALIC_END_MARKUP);
                 }
                 // in XHTML "<strong>" is used, but some tests still rely on 
the outdated "<bold>"
                 if 
(attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "strong")
-                        || 
attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "bold")) {
-                    write(BOLD_START_MARKUP);
-                    tags.add(0, BOLD_END_MARKUP);
-                }
-
-                if 
(attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "code")) {
-                    write(MONOSPACED_START_MARKUP);
-                    tags.add(0, MONOSPACED_END_MARKUP);
+                        || 
attributes.containsAttribute(SinkEventAttributes.SEMANTICS, "bold")
+                        || 
attributes.containsAttribute(SinkEventAttributes.STYLE, "bold")) {
+                    writeUnescaped(BOLD_START_MARKUP);
+                    endMarkups.add(BOLD_END_MARKUP);
                 }
             }
-
-            inlineStack.push(tags);
         }
+        inlineStack.add(endMarkups);
     }
 
     @Override
     public void inline_() {
-        if (!headerFlag && !verbatimFlag) {
-            for (String tag : inlineStack.pop()) {
-                write(tag);
+        for (String endMarkup : inlineStack.remove()) {
+            if (endMarkup.equals(MONOSPACED_END_MARKUP)) {
+                leaveContext(ElementContext.CODE_SPAN);
             }
+            writeUnescaped(endMarkup);
         }
     }
 
@@ -710,22 +735,12 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
 
     @Override
     public void lineBreak(SinkEventAttributes attributes) {
-        if (headerFlag || bufferFlag) {
-            buffer.append(EOL);
-        } else if (verbatimFlag) {
-            write(EOL);
-        } else {
-            write("" + SPACE + SPACE + EOL);
-        }
+        writeUnescaped("" + SPACE + SPACE + EOL);
     }
 
     @Override
     public void nonBreakingSpace() {
-        if (headerFlag || bufferFlag) {
-            buffer.append(NON_BREAKING_SPACE_MARKUP);
-        } else {
-            write(NON_BREAKING_SPACE_MARKUP);
-        }
+        writeUnescaped(NON_BREAKING_SPACE_MARKUP);
     }
 
     @Override
@@ -733,15 +748,13 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
         if (attributes != null) {
             inline(attributes);
         }
-        if (tableCaptionFlag) {
+        ElementContext currentContext = elementContextStack.element();
+        if (currentContext == ElementContext.TABLE_CAPTION) {
             // table caption cannot even be emitted via XHTML in markdown as 
there is no suitable location
             LOGGER.warn("Ignoring unsupported table caption in Markdown");
-        } else if (headerFlag || bufferFlag) {
-            buffer.append(escapeMarkdown(text));
-        } else if (verbatimFlag) {
-            verbatimContent(text);
         } else {
-            content(text);
+            String unifiedText = currentContext.escape(unifyEOLs(text));
+            writeUnescaped(unifiedText);
         }
         if (attributes != null) {
             inline_();
@@ -750,12 +763,12 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
 
     @Override
     public void rawText(String text) {
-        write(text);
+        writeUnescaped(text);
     }
 
     @Override
     public void comment(String comment) {
-        rawText((startFlag ? "" : EOL) + COMMENT_START + comment + 
COMMENT_END);
+        rawText(COMMENT_START + comment + COMMENT_END);
     }
 
     /**
@@ -770,37 +783,20 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     }
 
     /**
-     * Write text to output.
-     *
-     * @param text The text to write.
-     */
-    protected void write(String text) {
-        startFlag = false;
-        String unifiedText = unifyEOLs(text);
-        if (tableCellFlag) {
-            buffer.append(escapeForTableCell(unifiedText));
-        } else {
-            isWriterAtStartOfNewLine = unifiedText.endsWith(EOL);
-            writer.write(unifiedText);
-        }
-    }
-
-    /**
-     * Write Markdown escaped text to output.
-     *
-     * @param text The text to write.
+     * 
+     * @return {@code true} if any of the parent contexts require buffering
      */
-    protected void content(String text) {
-        write(escapeMarkdown(text));
+    private boolean requiresBuffering() {
+        return elementContextStack.stream().anyMatch(c -> c.requiresBuffering);
     }
 
-    /**
-     * Write verbatim text to output.
-     *
-     * @param text The text to write.
-     */
-    protected void verbatimContent(String text) {
-        write(text);
+    protected void writeUnescaped(String text) {
+        if (requiresBuffering()) {
+            buffer.append(text);
+        } else {
+            isWriterAtStartOfNewLine = text.endsWith(EOL);
+            writer.write(text);
+        }
     }
 
     @Override
@@ -869,14 +865,15 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     }
 
     /**
-     * Escapes the pipe character according to <a 
href="https://github.github.com/gfm/#tables-extension-";>GFM Table Extension</a>.
+     * Escapes the pipe character according to <a 
href="https://github.github.com/gfm/#tables-extension-";>GFM Table Extension</a> 
in addition
+     * to the regular markdown escaping.
      * @param text
-     * @return
-     *
+     * @return the escaped text
+     * @see {@link #escapeMarkdown(String)
      */
     private static String escapeForTableCell(String text) {
-        // assume already contains the regular markdown escape sequences
-        return text.replace("|", "\\|");
+
+        return escapeMarkdown(text).replace("|", "\\|");
     }
 
     /**
@@ -886,7 +883,7 @@ public class MarkdownSink extends AbstractTextSink 
implements MarkdownMarkup {
     private void ensureBeginningOfLine() {
         // make sure that we are at the start of a line without adding 
unnecessary blank lines
         if (!isWriterAtStartOfNewLine) {
-            write(EOL);
+            writeUnescaped(EOL);
         }
     }
 }
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 b32d33e3..31aa1d87 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
@@ -31,6 +31,7 @@ import org.apache.maven.doxia.parser.ParseException;
 import org.apache.maven.doxia.parser.Parser;
 import org.apache.maven.doxia.sink.Sink;
 import org.apache.maven.doxia.sink.impl.AbstractSinkTest;
+import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
 import org.apache.maven.doxia.sink.impl.SinkEventTestingSink;
 import org.apache.maven.doxia.util.HtmlTools;
 import org.hamcrest.MatcherAssert;
@@ -429,4 +430,19 @@ public class MarkdownSinkTest extends AbstractSinkTest {
 
         assertEquals(expected, getSinkContent(), "Wrong link or paragraph 
markup in table cell");
     }
+
+    @Test
+    public void testInlineCodeWithSpecialCharacters() {
+        String text = "Test&<>*_";
+        final Sink sink = getSink();
+        sink.inline(SinkEventAttributeSet.Semantics.CODE);
+        sink.text(text);
+        sink.inline_();
+        sink.flush();
+        sink.close();
+
+        String expected =  "`" + text + "`";
+
+        assertEquals(expected, getSinkContent(), "Wrong inline code!");
+    }
 }


Reply via email to