You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by hb...@apache.org on 2022/12/29 18:09:23 UTC

[maven-doxia] branch master updated: [DOXIA-569] add Markdown sink implementation

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

hboutemy 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 df935386 [DOXIA-569] add Markdown sink implementation
df935386 is described below

commit df9353869e6031987533ec2f288eede1f945728a
Author: Hervé Boutemy <hb...@apache.org>
AuthorDate: Sun Dec 4 11:40:14 2022 +0100

    [DOXIA-569] add Markdown sink implementation
---
 .../doxia/module/markdown/MarkdownMarkup.java      |  135 +++
 .../maven/doxia/module/markdown/MarkdownSink.java  | 1153 ++++++++++++++++++++
 .../doxia/module/markdown/MarkdownSinkFactory.java |   42 +
 .../doxia/module/markdown/MarkdownSinkTest.java    |  473 ++++++++
 4 files changed, 1803 insertions(+)

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
new file mode 100644
index 00000000..13142d20
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java
@@ -0,0 +1,135 @@
+package org.apache.maven.doxia.module.markdown;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import org.apache.maven.doxia.markup.TextMarkup;
+import org.codehaus.plexus.util.StringUtils;
+
+/**
+ * This interface defines all markups and syntaxes used by the <b>Markdown</b> format.
+ */
+@SuppressWarnings( "checkstyle:interfaceistype" )
+public interface MarkdownMarkup
+    extends TextMarkup
+{
+    // ----------------------------------------------------------------------
+    // Markup separators
+    // ----------------------------------------------------------------------
+
+    /** backslash markup char: '\\' */
+    char BACKSLASH = '\\';
+
+    String COMMENT_START = "<!-- ";
+    String COMMENT_END = " -->";
+
+    /** numbering decimal markup char: '1' */
+    char NUMBERING = '1';
+
+    /** numbering lower alpha markup char: 'a' */
+    char NUMBERING_LOWER_ALPHA_CHAR = 'a';
+
+    /** numbering lower roman markup char: 'i' */
+    char NUMBERING_LOWER_ROMAN_CHAR = 'i';
+
+    /** numbering upper alpha markup char: 'A' */
+    char NUMBERING_UPPER_ALPHA_CHAR = 'A';
+
+    /** numbering upper roman markup char: 'I' */
+    char NUMBERING_UPPER_ROMAN_CHAR = 'I';
+
+    /** page break markup char: '\f' */
+    char PAGE_BREAK = '\f';
+
+    // ----------------------------------------------------------------------
+    // Markup syntax
+    // ----------------------------------------------------------------------
+
+    /** Syntax for the anchor end: "\"></a>" */
+    String ANCHOR_END_MARKUP = "\"></a>";
+
+    /** Syntax for the anchor start: "<a id=\"" */
+    String ANCHOR_START_MARKUP = "<a id=\"";
+
+    /** Syntax for the bold style end: "**" */
+    String BOLD_END_MARKUP = "**";
+
+    /** Syntax for the bold style start: "**" */
+    String BOLD_START_MARKUP = "**";
+
+    /** Syntax for the header start: "---" */
+    String METADATA_MARKUP = StringUtils.repeat( String.valueOf( MINUS ), 3 );
+
+    /** Syntax for the horizontal rule: "========" */
+    String HORIZONTAL_RULE_MARKUP = StringUtils.repeat( String.valueOf( EQUAL ), 8 );
+
+    /** Syntax for the italic style end: "_" */
+    String ITALIC_END_MARKUP = "_";
+
+    /** Syntax for the italic style start: "_" */
+    String ITALIC_START_MARKUP = "_";
+
+    /** Syntax for the link end: ")" */
+    String LINK_END_MARKUP = ")";
+
+    /** Syntax for the link start: "[" */
+    String LINK_START_1_MARKUP = "[";
+
+    /** Syntax for the link start: "](" */
+    String LINK_START_2_MARKUP = "](";
+
+    /** Syntax for the list start: "-" */
+    String LIST_START_MARKUP = "-";
+
+    /** Syntax for the mono-spaced style end: "`" */
+    String MONOSPACED_END_MARKUP = "`";
+
+    /** Syntax for the mono-spaced style start: "`" */
+    String MONOSPACED_START_MARKUP = "`";
+
+    /** Syntax for the non boxed verbatim start: "```" */
+    String NON_BOXED_VERBATIM_START_MARKUP = "```";
+
+    /** Syntax for the non breaking space: "\ " */
+    String NON_BREAKING_SPACE_MARKUP = String.valueOf( BACKSLASH ) + SPACE;
+
+    /** Syntax for the page break: "\f" */
+    String PAGE_BREAK_MARKUP = String.valueOf( PAGE_BREAK );
+
+    /** Syntax for the section title start: "#" */
+    String SECTION_TITLE_START_MARKUP = "#";
+
+    /** Syntax for the table cell start: "|" */
+    String TABLE_CELL_SEPARATOR_MARKUP = String.valueOf( PIPE );
+
+    /** Syntax for the table column, centered style: "---|" */
+    String TABLE_COL_DEFAULT_ALIGNED_MARKUP = StringUtils.repeat( String.valueOf( MINUS ), 3 ) + PIPE;
+
+    /** Syntax for the table column, left style: "---+" */
+    String TABLE_COL_LEFT_ALIGNED_MARKUP = StringUtils.repeat( String.valueOf( MINUS ), 3 ) + PLUS;
+
+    /** Syntax for the table column, right style: "---:" */
+    String TABLE_COL_RIGHT_ALIGNED_MARKUP = StringUtils.repeat( String.valueOf( MINUS ), 3 ) + COLON;
+
+    /** Syntax for the table row end: "|" */
+    String TABLE_ROW_SEPARATOR_MARKUP = String.valueOf( PIPE );
+
+    /** Syntax for the non boxed verbatim end: "```" */
+    String NON_BOXED_VERBATIM_END_MARKUP = "```";
+}
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
new file mode 100644
index 00000000..8637af59
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java
@@ -0,0 +1,1153 @@
+package org.apache.maven.doxia.module.markdown;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+import org.apache.maven.doxia.sink.SinkEventAttributes;
+import org.apache.maven.doxia.sink.impl.AbstractTextSink;
+import org.apache.maven.doxia.sink.impl.SinkEventAttributeSet;
+import org.codehaus.plexus.util.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Markdown generator implementation.
+ * <br>
+ * <b>Note</b>: The encoding used is UTF-8.
+ */
+public class MarkdownSink
+    extends AbstractTextSink
+    implements MarkdownMarkup
+{
+    private static final Logger LOGGER = LoggerFactory.getLogger( MarkdownSink.class );
+
+    // ----------------------------------------------------------------------
+    // Instance fields
+    // ----------------------------------------------------------------------
+
+    /**  A buffer that holds the current text when headerFlag or bufferFlag set to <code>true</code>. */
+    private StringBuffer buffer;
+
+    /**  A buffer that holds the table caption. */
+    private StringBuilder tableCaptionBuffer;
+
+    /**  author. */
+    private String author;
+
+    /**  title. */
+    private String title;
+
+    /**  date. */
+    private String date;
+
+    /**  linkName. */
+    private String linkName;
+
+    /** startFlag. */
+    private boolean startFlag;
+
+    /**  tableCaptionFlag. */
+    private boolean tableCaptionFlag;
+
+    /**  tableCellFlag. */
+    private boolean tableCellFlag;
+
+    /**  headerFlag. */
+    private boolean headerFlag;
+
+    /**  bufferFlag. */
+    private boolean bufferFlag;
+
+    /**  itemFlag. */
+    private boolean itemFlag;
+
+    /**  verbatimFlag. */
+    private boolean verbatimFlag;
+
+    /**  gridFlag for tables. */
+    private boolean gridFlag;
+
+    /**  number of cells in a table. */
+    private int cellCount;
+
+    /**  The writer to use. */
+    private final PrintWriter writer;
+
+    /**  justification of table cells. */
+    private int[] cellJustif;
+
+    /**  a line of a row in a table. */
+    private String rowLine;
+
+    /**  is header row */
+    private boolean headerRow;
+
+    /**  listNestingIndent. */
+    private String listNestingIndent;
+
+    /**  listStyles. */
+    private final Stack<String> listStyles;
+
+    /** Keep track of the closing tags for inline events. */
+    protected Stack<List<String>> inlineStack = new Stack<>();
+
+    // ----------------------------------------------------------------------
+    // Public protected methods
+    // ----------------------------------------------------------------------
+
+    /**
+     * Constructor, initialize the Writer and the variables.
+     *
+     * @param writer not null writer to write the result. <b>Should</b> be an UTF-8 Writer.
+     */
+    protected MarkdownSink( Writer writer )
+    {
+        this.writer = new PrintWriter( writer );
+        this.listStyles = new Stack<>();
+
+        init();
+    }
+
+    /**
+     * Returns the buffer that holds the current text.
+     *
+     * @return A StringBuffer.
+     */
+    protected StringBuffer 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;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    protected void init()
+    {
+        super.init();
+
+        resetBuffer();
+
+        this.tableCaptionBuffer = new StringBuilder();
+        this.listNestingIndent = "";
+
+        this.author = null;
+        this.title = null;
+        this.date = null;
+        this.linkName = null;
+        this.startFlag = true;
+        this.tableCaptionFlag = false;
+        this.tableCellFlag = false;
+        this.headerFlag = false;
+        this.bufferFlag = false;
+        this.itemFlag = false;
+        this.verbatimFlag = false;
+        this.gridFlag = false;
+        this.cellCount = 0;
+        this.cellJustif = null;
+        this.rowLine = null;
+        this.listStyles.clear();
+        this.inlineStack.clear();
+    }
+
+    /**
+     * Reset the StringBuilder.
+     */
+    protected void resetBuffer()
+    {
+        buffer = new StringBuffer();
+    }
+
+    /**
+     * Reset the TableCaptionBuffer.
+     */
+    protected void resetTableCaptionBuffer()
+    {
+        tableCaptionBuffer = new StringBuilder();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void head()
+    {
+        boolean startFlag = this.startFlag;
+
+        init();
+
+        headerFlag = true;
+        this.startFlag = startFlag;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void head_()
+    {
+        headerFlag = false;
+
+        if ( ! startFlag )
+        {
+            write( EOL );
+        }
+        // TODO add --- once DOXIA-617 implemented
+        // write( METADATA_MARKUP + EOL );
+        if ( title != null )
+        {
+            write( "title: " + title + EOL );
+        }
+        if ( author != null )
+        {
+            write( "author: " + author + EOL );
+        }
+        if ( date != null )
+        {
+            write( "date: " + date + EOL );
+        }
+        // write( METADATA_MARKUP + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void title_()
+    {
+        if ( buffer.length() > 0 )
+        {
+            title = buffer.toString();
+            resetBuffer();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void author_()
+    {
+        if ( buffer.length() > 0 )
+        {
+            author = buffer.toString();
+            resetBuffer();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void date_()
+    {
+        if ( buffer.length() > 0 )
+        {
+            date = buffer.toString();
+            resetBuffer();
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void section1_()
+    {
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void section2_()
+    {
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void section3_()
+    {
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void section4_()
+    {
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void section5_()
+    {
+        write( EOL );
+    }
+
+    private void sectionTitle( int level )
+    {
+        write( EOL + StringUtils.repeat( SECTION_TITLE_START_MARKUP, level + 1 ) + SPACE );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle1()
+    {
+        sectionTitle( 1 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle1_()
+    {
+        write( EOL + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle2()
+    {
+        sectionTitle( 2 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle2_()
+    {
+        write( EOL + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle3()
+    {
+        sectionTitle( 3 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle3_()
+    {
+        write( EOL + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle4()
+    {
+        sectionTitle( 4 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle4_()
+    {
+        write( EOL + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle5()
+    {
+        sectionTitle( 5 );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void sectionTitle5_()
+    {
+        write( EOL + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void list()
+    {
+        listNestingIndent += " ";
+        listStyles.push( LIST_START_MARKUP );
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void list_()
+    {
+        write( EOL );
+        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
+        listStyles.pop();
+        itemFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void listItem()
+    {
+        //if ( !numberedList )
+        //write( EOL + listNestingIndent + "*" );
+        //else
+        numberedListItem();
+        itemFlag = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void listItem_()
+    {
+        write( EOL );
+        itemFlag = false;
+    }
+
+    /** {@inheritDoc} */
+    public void numberedList( int numbering )
+    {
+        listNestingIndent += " ";
+        write( EOL );
+
+        String style;
+        switch ( numbering )
+        {
+            case NUMBERING_UPPER_ALPHA:
+                style = String.valueOf( NUMBERING_UPPER_ALPHA_CHAR );
+                break;
+            case NUMBERING_LOWER_ALPHA:
+                style = String.valueOf( NUMBERING_LOWER_ALPHA_CHAR );
+                break;
+            case NUMBERING_UPPER_ROMAN:
+                style = String.valueOf( NUMBERING_UPPER_ROMAN_CHAR );
+                break;
+            case NUMBERING_LOWER_ROMAN:
+                style = String.valueOf( NUMBERING_LOWER_ROMAN_CHAR );
+                break;
+            case NUMBERING_DECIMAL:
+            default:
+                style = String.valueOf( NUMBERING );
+        }
+
+        listStyles.push( style );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void numberedList_()
+    {
+        write( EOL );
+        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
+        listStyles.pop();
+        itemFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void numberedListItem()
+    {
+        String style = listStyles.peek();
+        write( EOL + listNestingIndent + style + SPACE );
+        itemFlag = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void numberedListItem_()
+    {
+        write( EOL );
+        itemFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definitionList()
+    {
+        listNestingIndent += " ";
+        listStyles.push( "" );
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definitionList_()
+    {
+        write( EOL );
+        listNestingIndent = StringUtils.chomp( listNestingIndent, " " );
+        listStyles.pop();
+        itemFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definedTerm()
+    {
+        write( EOL + " [" );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definedTerm_()
+    {
+        write( "] " );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definition()
+    {
+        itemFlag = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void definition_()
+    {
+        write( EOL );
+        itemFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void pageBreak()
+    {
+        write( EOL + PAGE_BREAK + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void paragraph()
+    {
+        if ( tableCellFlag )
+        {
+            // ignore paragraphs in table cells
+        }
+        else if ( itemFlag )
+        {
+            write( EOL + EOL + "  " + listNestingIndent );
+        }
+        else
+        {
+            write( EOL + " " );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void paragraph_()
+    {
+        if ( tableCellFlag )
+        {
+            // ignore paragraphs in table cells
+        }
+        else
+        {
+            write( EOL + EOL );
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void verbatim( SinkEventAttributes attributes )
+    {
+        write( EOL );
+        verbatimFlag = true;
+        write( EOL + NON_BOXED_VERBATIM_START_MARKUP + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void verbatim_()
+    {
+        write( EOL + NON_BOXED_VERBATIM_END_MARKUP + EOL );
+        verbatimFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void horizontalRule()
+    {
+        write( EOL + HORIZONTAL_RULE_MARKUP + EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void table()
+    {
+        write( EOL );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void table_()
+    {
+        if ( tableCaptionBuffer.length() > 0 )
+        {
+            text( tableCaptionBuffer.toString() + EOL );
+        }
+
+        resetTableCaptionBuffer();
+    }
+
+    /** {@inheritDoc} */
+    public void tableRows( int[] justification, boolean grid )
+    {
+        cellJustif = null; //justification;
+        gridFlag = grid;
+        headerRow = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableRows_()
+    {
+        cellJustif = null;
+        gridFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableRow()
+    {
+        bufferFlag = true;
+        cellCount = 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableRow_()
+    {
+        bufferFlag = false;
+
+        // write out the header row first, then the data in the buffer
+        buildRowLine();
+
+        write( TABLE_ROW_SEPARATOR_MARKUP );
+
+        write( buffer.toString() );
+
+        resetBuffer();
+
+        write( EOL );
+
+        if ( headerRow )
+        {
+           write( rowLine );
+           headerRow = false;
+        }
+
+        // only reset cell count if this is the last row
+        cellCount = 0;
+    }
+
+    /** Construct a table row. */
+    private void buildRowLine()
+    {
+        StringBuilder rLine = new StringBuilder( TABLE_ROW_SEPARATOR_MARKUP );
+
+        for ( int i = 0; i < cellCount; i++ )
+        {
+            if ( cellJustif != null )
+            {
+                switch ( cellJustif[i] )
+                {
+                case 1:
+                    rLine.append( TABLE_COL_LEFT_ALIGNED_MARKUP );
+                    break;
+                case 2:
+                    rLine.append( TABLE_COL_RIGHT_ALIGNED_MARKUP );
+                    break;
+                default:
+                    rLine.append( TABLE_COL_DEFAULT_ALIGNED_MARKUP );
+                }
+            }
+            else
+            {
+                rLine.append( TABLE_COL_DEFAULT_ALIGNED_MARKUP );
+            }
+        }
+        rLine.append( EOL );
+
+        this.rowLine = rLine.toString();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableCell()
+    {
+        tableCell( false );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableHeaderCell()
+    {
+        tableCell( true );
+    }
+
+    /**
+     * Starts a table cell.
+     *
+     * @param headerRow If this cell is part of a header row.
+     */
+    public void tableCell( boolean headerRow )
+    {
+        tableCellFlag = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableCell_()
+    {
+        endTableCell();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableHeaderCell_()
+    {
+        endTableCell();
+    }
+
+    /**
+     * Ends a table cell.
+     */
+    private void endTableCell()
+    {
+        tableCellFlag = false;
+        buffer.append( TABLE_CELL_SEPARATOR_MARKUP );
+        cellCount++;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableCaption()
+    {
+        tableCaptionFlag = true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void tableCaption_()
+    {
+        tableCaptionFlag = false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void figureCaption_()
+    {
+        write( EOL );
+    }
+
+    /** {@inheritDoc} */
+    public void figureGraphics( String name )
+    {
+        write( "<img src=\"" + name + "\" />" );
+    }
+
+    /** {@inheritDoc} */
+    public void anchor( String name )
+    {
+        //write( ANCHOR_START_MARKUP + name );
+        // TODO get implementation from Xhtml5 base sink
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void anchor_()
+    {
+        //write( ANCHOR_END_MARKUP );
+    }
+
+    /** {@inheritDoc} */
+    public void link( String name )
+    {
+        if ( !headerFlag )
+        {
+            write( LINK_START_1_MARKUP );
+            linkName = name;
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    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.
+     */
+    public void link( String name, String target )
+    {
+        if ( !headerFlag )
+        {
+            write( LINK_START_1_MARKUP );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void inline()
+    {
+        inline( null );
+    }
+
+    /** {@inheritDoc} */
+    public void inline( SinkEventAttributes attributes )
+    {
+        if ( !headerFlag )
+        {
+            List<String> tags = new ArrayList<>();
+
+            if ( attributes != null )
+            {
+
+                if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, "italic" ) )
+                {
+                    write( ITALIC_START_MARKUP );
+                    tags.add( 0, ITALIC_END_MARKUP );
+                }
+
+                if ( 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 );
+                }
+
+            }
+
+            inlineStack.push( tags );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void inline_()
+    {
+        if ( !headerFlag )
+        {
+            for ( String tag: inlineStack.pop() )
+            {
+                write( tag );
+            }
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void italic()
+    {
+        inline( SinkEventAttributeSet.Semantics.ITALIC );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void italic_()
+    {
+        inline_();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void bold()
+    {
+        inline( SinkEventAttributeSet.Semantics.BOLD );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void bold_()
+    {
+        inline_();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void monospaced()
+    {
+        inline( SinkEventAttributeSet.Semantics.CODE );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void monospaced_()
+    {
+        inline_();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void lineBreak()
+    {
+        if ( headerFlag || bufferFlag )
+        {
+            buffer.append( EOL );
+        }
+        else if ( verbatimFlag )
+        {
+            write( EOL );
+        }
+        else
+        {
+            write( BACKSLASH + EOL );
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void nonBreakingSpace()
+    {
+        if ( headerFlag || bufferFlag )
+        {
+            buffer.append( NON_BREAKING_SPACE_MARKUP );
+        }
+        else
+        {
+            write( NON_BREAKING_SPACE_MARKUP );
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void text( String text )
+    {
+        if ( tableCaptionFlag )
+        {
+            tableCaptionBuffer.append( text );
+        }
+        else if ( headerFlag || bufferFlag )
+        {
+            buffer.append( text );
+        }
+        else if ( verbatimFlag )
+        {
+            verbatimContent( text );
+        }
+        else
+        {
+            content( text );
+        }
+    }
+
+    /** {@inheritDoc} */
+    public void rawText( String text )
+    {
+        write( text );
+    }
+
+    /** {@inheritDoc} */
+    public void comment( String comment )
+    {
+        rawText( ( startFlag ? "" : EOL ) + COMMENT_START + comment + COMMENT_END );
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * Unkown events just log a warning message but are ignored otherwise.
+     * @see org.apache.maven.doxia.sink.Sink#unknown(String,Object[],SinkEventAttributes)
+     */
+    public void unknown( String name, Object[] requiredParams, SinkEventAttributes attributes )
+    {
+        LOGGER.warn( "Unknown Sink event '" + name + "', ignoring!" );
+    }
+
+    /**
+     * Write text to output.
+     *
+     * @param text The text to write.
+     */
+    protected void write( String text )
+    {
+        startFlag = false;
+        if ( tableCellFlag )
+        {
+            buffer.append( text );
+        }
+        else
+        {
+            writer.write( unifyEOLs( text ) );
+        }
+    }
+
+    /**
+     * Write Apt escaped text to output.
+     *
+     * @param text The text to write.
+     */
+    protected void content( String text )
+    {
+        write( escapeMarkdown( text ) );
+    }
+
+    /**
+     * Write verbatim text to output.
+     *
+     * @param text The text to write.
+     */
+    protected void verbatimContent( String text )
+    {
+        write( text );
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void flush()
+    {
+        writer.flush();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close()
+    {
+        writer.close();
+
+        init();
+    }
+
+    // ----------------------------------------------------------------------
+    // Private methods
+    // ----------------------------------------------------------------------
+
+    /**
+     * Escape special characters in a text in Markdown:
+     *
+     * <pre>
+     * \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\
+     * </pre>
+     *
+     * @param text the String to escape, may be null
+     * @return the text escaped, "" if null String input
+     */
+    private static String escapeMarkdown( String text )
+    {
+        if ( text == null )
+        {
+            return "";
+        }
+
+        int length = text.length();
+        StringBuilder buffer = new StringBuilder( length );
+
+        for ( int i = 0; i < length; ++i )
+        {
+            char c = text.charAt( i );
+            switch ( c )
+            { // 0080
+                case '\\':
+                case '~':
+                case '=':
+                case '+':
+                case '*':
+                case '[':
+                case ']':
+                case '<':
+                case '>':
+                case '{':
+                case '}':
+                    buffer.append( '\\' );
+                    buffer.append( c );
+                    break;
+                default:
+                    buffer.append( c );
+            }
+        }
+
+        return buffer.toString();
+    }
+}
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSinkFactory.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSinkFactory.java
new file mode 100644
index 00000000..5a92a6bb
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSinkFactory.java
@@ -0,0 +1,42 @@
+package org.apache.maven.doxia.module.markdown;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Writer;
+
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.SinkFactory;
+import org.apache.maven.doxia.sink.impl.AbstractTextSinkFactory;
+import org.codehaus.plexus.component.annotations.Component;
+
+/**
+ * Markdown implementation of the Sink factory.
+ */
+@Component( role = SinkFactory.class, hint = "markdown" )
+public class MarkdownSinkFactory
+    extends AbstractTextSinkFactory
+{
+    /** {@inheritDoc} */
+    protected Sink createSink( Writer writer, String encoding )
+    {
+        // encoding can safely be ignored since it isn't written into the generated Markdown source
+        return new MarkdownSink( writer );
+    }
+}
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
new file mode 100644
index 00000000..f5592f88
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java
@@ -0,0 +1,473 @@
+package org.apache.maven.doxia.module.markdown;
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import java.io.Writer;
+
+import org.apache.maven.doxia.markup.Markup;
+import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.sink.impl.AbstractSinkTest;
+import org.codehaus.plexus.util.StringUtils;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Test the <code>MarkdownSink</code> class
+ */
+public class MarkdownSinkTest extends AbstractSinkTest
+{
+    /** {@inheritDoc} */
+    protected String outputExtension()
+    {
+        return "md";
+    }
+
+    /** {@inheritDoc} */
+    protected Sink createSink( Writer writer )
+    {
+        return new MarkdownSink( writer );
+    }
+
+    /** {@inheritDoc} */
+    protected boolean isXmlSink()
+    {
+        return false;
+    }
+
+    /** {@inheritDoc} */
+    protected String getTitleBlock( String title )
+    {
+        return title;
+    }
+
+    /** {@inheritDoc} */
+    protected String getAuthorBlock( String author )
+    {
+        return author;
+    }
+
+    /** {@inheritDoc} */
+    protected String getDateBlock( String date )
+    {
+        return date;
+    }
+
+    /** {@inheritDoc} */
+    protected String getHeadBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getBodyBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getArticleBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getNavigationBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getSidebarBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getSectionTitleBlock( String title )
+    {
+        return title;
+    }
+
+    protected String getSectionBlock( String title, int level )
+    {
+        return EOL + StringUtils.repeat( MarkdownMarkup.SECTION_TITLE_START_MARKUP, level + 1 ) + SPACE + title + EOL + EOL + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getSection1Block( String title )
+    {
+        return getSectionBlock( title, 1 );
+    }
+
+    /** {@inheritDoc} */
+    protected String getSection2Block( String title )
+    {
+        return getSectionBlock( title, 2 );
+    }
+
+    /** {@inheritDoc} */
+    protected String getSection3Block( String title )
+    {
+        return getSectionBlock( title, 3 );
+    }
+
+    /** {@inheritDoc} */
+    protected String getSection4Block( String title )
+    {
+        return getSectionBlock( title, 4 );
+    }
+
+    /** {@inheritDoc} */
+    protected String getSection5Block( String title )
+    {
+        return getSectionBlock( title, 5 );
+    }
+
+    /** {@inheritDoc} */
+    protected String getHeaderBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getContentBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getFooterBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getListBlock( String item )
+    {
+        return EOL + EOL + Markup.SPACE + "" + MarkdownMarkup.LIST_START_MARKUP + "" + Markup.SPACE + item + EOL + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getNumberedListBlock( String item )
+    {
+        return EOL + EOL + Markup.SPACE + ""
+            + MarkdownMarkup.NUMBERING_LOWER_ROMAN_CHAR + ""
+            + Markup.SPACE + item + EOL + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getDefinitionListBlock( String definum, String definition )
+    {
+        return EOL + EOL + Markup.SPACE + "" + Markup.LEFT_SQUARE_BRACKET + definum
+            + Markup.RIGHT_SQUARE_BRACKET + "" + Markup.SPACE + definition + EOL + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getFigureBlock( String source, String caption )
+    {
+       String figureBlock = "<img src=\"" + source + "\" />";
+       if( caption != null )
+       {
+           figureBlock += caption + EOL;
+       }
+       return figureBlock;
+    }
+
+    /** {@inheritDoc} */
+    protected String getTableBlock( String cell, String caption )
+    {
+        return EOL + cell
+            + MarkdownMarkup.TABLE_ROW_SEPARATOR_MARKUP + EOL
+            + caption + EOL;
+    }
+
+    @Override
+    public void testTable()
+    {
+        // TODO re-enable test from parent
+    }
+
+    /** {@inheritDoc} */
+    protected String getParagraphBlock( String text )
+    {
+        return EOL + Markup.SPACE + text + EOL + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getDataBlock( String value, String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getTimeBlock( String datetime, String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getAddressBlock( String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getBlockquoteBlock( String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getDivisionBlock( String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getVerbatimBlock( String text )
+    {
+        return EOL + EOL + MarkdownMarkup.NON_BOXED_VERBATIM_START_MARKUP + EOL + text + EOL
+            + MarkdownMarkup.NON_BOXED_VERBATIM_START_MARKUP + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getHorizontalRuleBlock()
+    {
+        return EOL + MarkdownMarkup.HORIZONTAL_RULE_MARKUP + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getPageBreakBlock()
+    {
+        return EOL + MarkdownMarkup.PAGE_BREAK_MARKUP + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getAnchorBlock( String anchor )
+    {
+        return anchor;
+    }
+
+    /** {@inheritDoc} */
+    protected String getLinkBlock( String link, String text )
+    {
+        String lnk = link.startsWith( "#" ) ? link.substring( 1 ) : link;
+        return MarkdownMarkup.LINK_START_1_MARKUP + text + MarkdownMarkup.LINK_START_2_MARKUP + lnk + MarkdownMarkup.LINK_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getInlineBlock( String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getInlineItalicBlock( String text )
+    {
+        return MarkdownMarkup.ITALIC_START_MARKUP + text + MarkdownMarkup.ITALIC_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getInlineBoldBlock( String text )
+    {
+        return MarkdownMarkup.BOLD_START_MARKUP + text + MarkdownMarkup.BOLD_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getInlineCodeBlock( String text )
+    {
+        return MarkdownMarkup.MONOSPACED_START_MARKUP + text + MarkdownMarkup.MONOSPACED_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getItalicBlock( String text )
+    {
+        return MarkdownMarkup.ITALIC_START_MARKUP + text + MarkdownMarkup.ITALIC_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getBoldBlock( String text )
+    {
+        return MarkdownMarkup.BOLD_START_MARKUP + text + MarkdownMarkup.BOLD_END_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getMonospacedBlock( String text )
+    {
+        return text;
+    }
+
+    /** {@inheritDoc} */
+    protected String getLineBreakBlock()
+    {
+        return MarkdownMarkup.BACKSLASH + EOL;
+    }
+
+    /** {@inheritDoc} */
+    protected String getLineBreakOpportunityBlock()
+    {
+        return "";
+    }
+
+    /** {@inheritDoc} */
+    protected String getNonBreakingSpaceBlock()
+    {
+        return MarkdownMarkup.NON_BREAKING_SPACE_MARKUP;
+    }
+
+    /** {@inheritDoc} */
+    protected String getTextBlock( String text )
+    {
+        // "\\~, \\=, \\-, \\+, \\*, \\[, \\], \\<, \\>, \\{, \\}, \\\\"
+        StringBuilder sb = new StringBuilder();
+        sb.append( getSpecialCharacters( '~' ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.EQUAL ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.MINUS ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.PLUS ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.STAR ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.LEFT_SQUARE_BRACKET ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.RIGHT_SQUARE_BRACKET ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.LESS_THAN ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.GREATER_THAN ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.LEFT_CURLY_BRACKET ) ).append( ",_" );
+        sb.append( getSpecialCharacters( Markup.RIGHT_CURLY_BRACKET ) ).append( ",_" );
+        sb.append( getSpecialCharacters( MarkdownMarkup.BACKSLASH ) );
+
+        return sb.toString();
+    }
+
+    @Override
+    public void testText()
+    {
+        // TODO re-enable this test as inherited from parent
+    }
+
+    /** {@inheritDoc} */
+    protected String getRawTextBlock( String text )
+    {
+        return text;
+    }
+
+    /**
+     * Add a backslash for a special markup character
+     *
+     * @param c
+     * @return the character with a backslash before
+     */
+    private static String getSpecialCharacters( char c )
+    {
+        return MarkdownMarkup.BACKSLASH + "" + c;
+    }
+
+    /** {@inheritDoc} */
+    protected String getCommentBlock( String text )
+    {
+        return "<!-- " + text + " -->";
+    }
+
+    /**
+     * test for DOXIA-497.
+     */
+    public void _testLinksAndParagraphsInTableCells()
+    {
+        final String linkTarget = "target";
+        final String linkText = "link";
+        final String paragraphText = "paragraph text";
+        final Sink sink = getSink();
+        sink.table();
+        sink.tableRow();
+        sink.tableCell();
+        sink.link( linkTarget );
+        sink.text( linkText );
+        sink.link_();
+        sink.tableCell_();
+        sink.tableCell();
+        sink.paragraph();
+        sink.text( paragraphText );
+        sink.paragraph_();
+        sink.tableCell_();
+        sink.tableRow_();
+        sink.table_();
+        sink.flush();
+        sink.close();
+
+        String expected = EOL +
+                MarkdownMarkup.LEFT_CURLY_BRACKET +
+                MarkdownMarkup.LEFT_CURLY_BRACKET +
+                MarkdownMarkup.LEFT_CURLY_BRACKET +
+                linkTarget +
+                MarkdownMarkup.RIGHT_CURLY_BRACKET +
+                linkText +
+                MarkdownMarkup.RIGHT_CURLY_BRACKET +
+                MarkdownMarkup.RIGHT_CURLY_BRACKET +
+                MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP +
+                paragraphText +
+                MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP +
+                EOL;
+
+        assertEquals( expected, getSinkContent(), "Wrong link or paragraph markup in table cell" );
+    }
+
+    public void _testTableCellsWithJustification()
+    {
+        final String linkTarget = "target";
+        final String linkText = "link";
+        final String paragraphText = "paragraph text";
+        final Sink sink = getSink();
+        sink.table();
+        sink.tableRows( new int[] { Sink.JUSTIFY_RIGHT, Sink.JUSTIFY_LEFT }, false );
+        sink.tableRow();
+        sink.tableCell();
+        sink.link( linkTarget );
+        sink.text( linkText );
+        sink.link_();
+        sink.tableCell_();
+        sink.tableCell();
+        sink.paragraph();
+        sink.text( paragraphText );
+        sink.paragraph_();
+        sink.tableCell_();
+        sink.tableRow_();
+        sink.tableRows_();
+        sink.table_();
+        sink.flush();
+        sink.close();
+
+        String expected = EOL +
+                MarkdownMarkup.TABLE_COL_RIGHT_ALIGNED_MARKUP + 
+                MarkdownMarkup.TABLE_COL_LEFT_ALIGNED_MARKUP + 
+                EOL +
+                MarkdownMarkup.LINK_START_1_MARKUP+
+                linkTarget +
+                MarkdownMarkup.LINK_START_2_MARKUP+
+                linkText +
+                MarkdownMarkup.LINK_END_MARKUP+
+                MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP +
+                paragraphText +
+                MarkdownMarkup.TABLE_CELL_SEPARATOR_MARKUP +
+                EOL +
+                MarkdownMarkup.TABLE_COL_RIGHT_ALIGNED_MARKUP +
+                MarkdownMarkup.TABLE_COL_LEFT_ALIGNED_MARKUP +
+                EOL;
+
+        assertEquals( expected, getSinkContent(), "Wrong justification in table cells" );
+    }
+}