From a1ad9b5bf11944e1a723b4a44298d2439f7201c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Boutemy?= Date: Sun, 4 Dec 2022 11:40:14 +0100 Subject: [PATCH] [DOXIA-569] add Markdown sink implementation --- doxia-core/pom.xml | 2 +- doxia-logging-api/pom.xml | 2 +- doxia-modules/doxia-module-apt/pom.xml | 2 +- doxia-modules/doxia-module-confluence/pom.xml | 2 +- .../doxia-module-docbook-simple/pom.xml | 2 +- doxia-modules/doxia-module-fml/pom.xml | 2 +- doxia-modules/doxia-module-fo/pom.xml | 2 +- doxia-modules/doxia-module-itext/pom.xml | 2 +- doxia-modules/doxia-module-latex/pom.xml | 2 +- doxia-modules/doxia-module-markdown/pom.xml | 2 +- .../doxia/module/markdown/MarkdownMarkup.java | 135 ++ .../doxia/module/markdown/MarkdownSink.java | 1149 +++++++++++++++++ .../module/markdown/MarkdownSinkFactory.java | 42 + .../module/markdown/MarkdownSinkTest.java | 470 +++++++ doxia-modules/doxia-module-rtf/pom.xml | 2 +- doxia-modules/doxia-module-twiki/pom.xml | 2 +- doxia-modules/doxia-module-xdoc/pom.xml | 2 +- doxia-modules/doxia-module-xhtml/pom.xml | 2 +- doxia-modules/doxia-module-xhtml5/pom.xml | 2 +- doxia-modules/pom.xml | 2 +- doxia-sink-api/pom.xml | 2 +- doxia-test-docs/pom.xml | 2 +- pom.xml | 4 +- 23 files changed, 1816 insertions(+), 20 deletions(-) create mode 100644 doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownMarkup.java create mode 100644 doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSink.java create mode 100644 doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownSinkFactory.java create mode 100644 doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java diff --git a/doxia-core/pom.xml b/doxia-core/pom.xml index 3e44da6ae..8ab1d24f1 100644 --- a/doxia-core/pom.xml +++ b/doxia-core/pom.xml @@ -25,7 +25,7 @@ under the License. org.apache.maven.doxia doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-logging-api/pom.xml b/doxia-logging-api/pom.xml index 3520e3f9c..2521af53e 100644 --- a/doxia-logging-api/pom.xml +++ b/doxia-logging-api/pom.xml @@ -25,7 +25,7 @@ under the License. doxia org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-apt/pom.xml b/doxia-modules/doxia-module-apt/pom.xml index c4e9e2f7c..eb347fdf0 100644 --- a/doxia-modules/doxia-module-apt/pom.xml +++ b/doxia-modules/doxia-module-apt/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-confluence/pom.xml b/doxia-modules/doxia-module-confluence/pom.xml index b0b07e7bc..14ffe848b 100644 --- a/doxia-modules/doxia-module-confluence/pom.xml +++ b/doxia-modules/doxia-module-confluence/pom.xml @@ -23,7 +23,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-docbook-simple/pom.xml b/doxia-modules/doxia-module-docbook-simple/pom.xml index 1f0ae7a2b..7ff8fd194 100644 --- a/doxia-modules/doxia-module-docbook-simple/pom.xml +++ b/doxia-modules/doxia-module-docbook-simple/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-fml/pom.xml b/doxia-modules/doxia-module-fml/pom.xml index bfa4e5f36..5ec14673d 100644 --- a/doxia-modules/doxia-module-fml/pom.xml +++ b/doxia-modules/doxia-module-fml/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-fo/pom.xml b/doxia-modules/doxia-module-fo/pom.xml index b090edadf..16a4156a0 100644 --- a/doxia-modules/doxia-module-fo/pom.xml +++ b/doxia-modules/doxia-module-fo/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-itext/pom.xml b/doxia-modules/doxia-module-itext/pom.xml index 7b817c34a..cc8df2598 100644 --- a/doxia-modules/doxia-module-itext/pom.xml +++ b/doxia-modules/doxia-module-itext/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-latex/pom.xml b/doxia-modules/doxia-module-latex/pom.xml index b4a0a8cfb..6c0f13688 100644 --- a/doxia-modules/doxia-module-latex/pom.xml +++ b/doxia-modules/doxia-module-latex/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-markdown/pom.xml b/doxia-modules/doxia-module-markdown/pom.xml index d379a3ad0..4905de269 100644 --- a/doxia-modules/doxia-module-markdown/pom.xml +++ b/doxia-modules/doxia-module-markdown/pom.xml @@ -25,7 +25,7 @@ under the License. org.apache.maven.doxia doxia-modules - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml 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 000000000..88f0a4e44 --- /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 Markdown format. + */ +@SuppressWarnings( "checkstyle:interfaceistype" ) +public interface MarkdownMarkup + extends TextMarkup +{ + // ---------------------------------------------------------------------- + // Markup separators + // ---------------------------------------------------------------------- + + /** backslash markup char: '\\' */ + char BACKSLASH = '\\'; + + String COMMENT_START = ""; + + /** 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: "\">" */ + String ANCHOR_END_MARKUP = "\">"; + + /** Syntax for the anchor start: " + * Note: The encoding used is UTF-8. + */ +public class MarkdownSink + extends AbstractTextSink + implements MarkdownMarkup +{ + // ---------------------------------------------------------------------- + // Instance fields + // ---------------------------------------------------------------------- + + /** A buffer that holds the current text when headerFlag or bufferFlag set to true. */ + 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 listStyles; + + /** Keep track of the closing tags for inline events. */ + protected Stack> inlineStack = new Stack<>(); + + // ---------------------------------------------------------------------- + // Public protected methods + // ---------------------------------------------------------------------- + + /** + * Constructor, initialize the Writer and the variables. + * + * @param writer not null writer to write the result. Should 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( "" ); + } + + /** {@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 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 ) + { + getLog().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: + * + *
+     * \~, \=, \-, \+, \*, \[, \], \<, \>, \{, \}, \\
+     * 
+ * + * @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 000000000..5a92a6bb1 --- /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 000000000..0cb58b4eb --- /dev/null +++ b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownSinkTest.java @@ -0,0 +1,470 @@ +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; + +/** + * Test the MarkdownSink 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 = ""; + 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 ""; + } + + /** + * 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" ); + } +} diff --git a/doxia-modules/doxia-module-rtf/pom.xml b/doxia-modules/doxia-module-rtf/pom.xml index 2344ce5b6..69f0f8b27 100644 --- a/doxia-modules/doxia-module-rtf/pom.xml +++ b/doxia-modules/doxia-module-rtf/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-twiki/pom.xml b/doxia-modules/doxia-module-twiki/pom.xml index c7e4b352d..ff503274b 100644 --- a/doxia-modules/doxia-module-twiki/pom.xml +++ b/doxia-modules/doxia-module-twiki/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-xdoc/pom.xml b/doxia-modules/doxia-module-xdoc/pom.xml index a4ffb74dc..39d7e87f3 100644 --- a/doxia-modules/doxia-module-xdoc/pom.xml +++ b/doxia-modules/doxia-module-xdoc/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-xhtml/pom.xml b/doxia-modules/doxia-module-xhtml/pom.xml index 91f4c4af6..71f6b2bd0 100644 --- a/doxia-modules/doxia-module-xhtml/pom.xml +++ b/doxia-modules/doxia-module-xhtml/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/doxia-module-xhtml5/pom.xml b/doxia-modules/doxia-module-xhtml5/pom.xml index 1066d9182..30b6b706c 100644 --- a/doxia-modules/doxia-module-xhtml5/pom.xml +++ b/doxia-modules/doxia-module-xhtml5/pom.xml @@ -25,7 +25,7 @@ under the License. doxia-modules org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-modules/pom.xml b/doxia-modules/pom.xml index d71340bbd..d20d5156b 100644 --- a/doxia-modules/pom.xml +++ b/doxia-modules/pom.xml @@ -22,7 +22,7 @@ under the License. doxia org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-sink-api/pom.xml b/doxia-sink-api/pom.xml index 92c4b5cef..6777b69ae 100644 --- a/doxia-sink-api/pom.xml +++ b/doxia-sink-api/pom.xml @@ -25,7 +25,7 @@ under the License. doxia org.apache.maven.doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/doxia-test-docs/pom.xml b/doxia-test-docs/pom.xml index b4ca0d6e8..9734f7997 100644 --- a/doxia-test-docs/pom.xml +++ b/doxia-test-docs/pom.xml @@ -25,7 +25,7 @@ under the License. org.apache.maven.doxia doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index ba66df1c0..1a188e753 100644 --- a/pom.xml +++ b/pom.xml @@ -31,7 +31,7 @@ under the License. org.apache.maven.doxia doxia - 1.11.2-SNAPSHOT + 1.12.0-SNAPSHOT pom Doxia @@ -87,7 +87,7 @@ under the License. 7 doxia-archives/doxia-LATEST RedundantThrows,NewlineAtEndOfFile,ParameterNumber,MethodLength,FileLength,MethodName,InnerAssignment,MagicNumber - 2021-11-28T20:51:17Z + 2022-12-04T11:30:28Z