You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by kw...@apache.org on 2023/01/24 18:56:22 UTC

[maven-doxia] branch bugfix/improve-metadata-parsing updated (effb97e3 -> 06d7210a)

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

kwin pushed a change to branch bugfix/improve-metadata-parsing
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git


 discard effb97e3 [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown)
     new 06d7210a [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown)

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (effb97e3)
            \
             N -- N -- N   refs/heads/bugfix/improve-metadata-parsing (06d7210a)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../doxia/module/markdown/MarkdownParser.java      | 88 +++++++++++++++-------
 .../maven/doxia/module/markdown/MarkdownSink.java  | 25 +++---
 .../module/markdown/YamlFrontMatterVisitor.java    | 36 +++++----
 .../doxia/module/markdown/MarkdownParserTest.java  | 84 ++++++++++++++++++---
 .../doxia/module/markdown/MarkdownSinkTest.java    | 24 ++++++
 .../src/test/resources/metadata-yaml.md            |  9 ++-
 .../src/test/resources/metadata.md                 |  5 +-
 7 files changed, 201 insertions(+), 70 deletions(-)
 copy doxia-sink-api/src/site/site.xml => doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/YamlFrontMatterVisitor.java (56%)


[maven-doxia] 01/01: [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown)

Posted by kw...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

kwin pushed a commit to branch bugfix/improve-metadata-parsing
in repository https://gitbox.apache.org/repos/asf/maven-doxia.git

commit 06d7210a31ca0ee6107dedf699576da8d50a1caa
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Sun Jan 22 11:11:46 2023 +0100

    [DOXIA-690] Improved support of metadata (both YAML front matter and
    MultiMarkdown)
    
    Properly support multiline values in sink and parser. Always emit with
    normalized separators.
---
 doxia-modules/doxia-module-markdown/pom.xml        |   5 +
 .../doxia/module/markdown/MarkdownParser.java      | 148 +++++++++++++++------
 .../maven/doxia/module/markdown/MarkdownSink.java  |  25 ++--
 .../module/markdown/YamlFrontMatterVisitor.java    |  40 ++++++
 .../doxia-module-markdown/src/site/apt/index.apt   |  11 +-
 .../doxia/module/markdown/MarkdownParserTest.java  |  84 ++++++++++--
 .../doxia/module/markdown/MarkdownSinkTest.java    |  24 ++++
 .../src/test/resources/metadata-yaml.md            |   9 +-
 .../src/test/resources/metadata.md                 |   5 +-
 9 files changed, 288 insertions(+), 63 deletions(-)

diff --git a/doxia-modules/doxia-module-markdown/pom.xml b/doxia-modules/doxia-module-markdown/pom.xml
index f6153534..5d094d7e 100644
--- a/doxia-modules/doxia-module-markdown/pom.xml
+++ b/doxia-modules/doxia-module-markdown/pom.xml
@@ -116,6 +116,11 @@ under the License.
       <artifactId>flexmark-ext-wikilink</artifactId>
       <version>${flexmarkVersion}</version>
     </dependency>
+    <dependency>
+      <groupId>com.vladsch.flexmark</groupId>
+      <artifactId>flexmark-ext-yaml-front-matter</artifactId>
+      <version>${flexmarkVersion}</version>
+    </dependency>
 
     <dependency>
       <groupId>org.jetbrains</groupId>
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
index c1cb3521..f5c9980e 100644
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/MarkdownParser.java
@@ -44,8 +44,14 @@ import javax.inject.Singleton;
 import java.io.IOException;
 import java.io.Reader;
 import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import com.vladsch.flexmark.ast.Heading;
 import com.vladsch.flexmark.ast.HtmlCommentBlock;
@@ -58,6 +64,7 @@ import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
 import com.vladsch.flexmark.ext.tables.TablesExtension;
 import com.vladsch.flexmark.ext.typographic.TypographicExtension;
 import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
+import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterExtension;
 import com.vladsch.flexmark.html.HtmlRenderer;
 import com.vladsch.flexmark.util.ast.Node;
 import com.vladsch.flexmark.util.data.MutableDataSet;
@@ -95,19 +102,23 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
      * In order to ensure that we have minimal risk of false positives when slurping metadata sections, the
      * first key in the metadata section must be one of these standard keys or else the entire metadata section is
      * ignored.
+     * @see <a href="https://fletcher.github.io/MultiMarkdown-5/metadata.html">Multimarkdown Metadata</a>
      */
     private static final Pattern METADATA_SECTION_PATTERN = Pattern.compile(
-            "\\A^\\s*"
+            "\\A^"
                     + "(?:title|author|date|address|affiliation|copyright|email|keywords|language|phone|subtitle)"
-                    + "[ \\t]*:[ \\t]*[^\\r\\n]*[ \\t]*$[\\r\\n]+"
-                    + "(?:^[ \\t]*[^:\\r\\n]+[ \\t]*:[ \\t]*[^\\r\\n]*[ \\t]*$[\\r\\n]+)*",
+                    + "[ \\t]*:[\\S\\s]+?^[ \\t]*$",
             Pattern.MULTILINE | Pattern.CASE_INSENSITIVE);
 
     /**
      * Regex that captures the key and value of a multimarkdown-style metadata entry.
+     * Group 1 captures the key, group 2 captures the value. Multivalues are not supported in the syntax!
+     * Multiline values need to be normalized
+     * @see <a href="https://fletcher.github.io/MultiMarkdown-5/metadata.html">Multimarkdown Metadata</a>
+     *
      */
-    private static final Pattern METADATA_ENTRY_PATTERN =
-            Pattern.compile("^[ \\t]*([^:\\r\\n]+?)[ \\t]*:[ \\t]*([^\\r\\n]*)[ \\t]*$", Pattern.MULTILINE);
+    private static final Pattern METADATA_ENTRY_PATTERN = Pattern.compile(
+            "^([^:\\r\\n]+?)[ \\t]*:([\\S\\s]+?)(?=(?:^(?:[^:\\r\\n]+?)[ \\t]*:)|^[ \\t]*$)", Pattern.MULTILINE);
 
     /**
      * The parser of the HTML produced by Flexmark, that we will
@@ -121,6 +132,11 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
      */
     private static final com.vladsch.flexmark.parser.Parser FLEXMARK_PARSER;
 
+    /**
+     * Flexmark's Markdown Metadata parser
+     */
+    private static final com.vladsch.flexmark.parser.Parser FLEXMARK_METADATA_PARSER;
+
     /**
      * Flexmark's HTML renderer (its output will be re-parsed and converted to Sink events)
      */
@@ -155,6 +171,12 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
         FLEXMARK_PARSER =
                 com.vladsch.flexmark.parser.Parser.builder(flexmarkOptions).build();
 
+        MutableDataSet flexmarkMetadataOptions = new MutableDataSet();
+        flexmarkMetadataOptions.set(
+                com.vladsch.flexmark.parser.Parser.EXTENSIONS, Arrays.asList(YamlFrontMatterExtension.create()));
+        FLEXMARK_METADATA_PARSER = com.vladsch.flexmark.parser.Parser.builder(flexmarkMetadataOptions)
+                .build();
+
         // Build the HTML renderer
         FLEXMARK_HTML_RENDERER = HtmlRenderer.builder(flexmarkOptions)
                 .linkResolverFactory(new FlexmarkDoxiaLinkResolver.Factory())
@@ -175,6 +197,86 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
         }
     }
 
+    private boolean processMetadataForHtml(StringBuilder html, StringBuilder source) {
+        final Map<String, List<String>> metaData;
+        final int endOffset; // end of metadata within source
+        // support two types of metadata:
+        if (source.toString().startsWith("---")) {
+            // 1. YAML front matter (https://github.com/vsch/flexmark-java/wiki/Extensions#yaml-front-matter)
+            Node documentRoot = FLEXMARK_METADATA_PARSER.parse(source.toString());
+            YamlFrontMatterVisitor visitor = new YamlFrontMatterVisitor();
+            visitor.visit(documentRoot);
+            metaData = visitor.getData();
+            endOffset = visitor.getEndOffset();
+        } else {
+            // 2. Multimarkdown metadata (https://fletcher.github.io/MultiMarkdown-5/metadata.html), not yet supported
+            // by Flexmark (https://github.com/vsch/flexmark-java/issues/550)
+            metaData = new LinkedHashMap<>();
+            Matcher metadataMatcher = METADATA_SECTION_PATTERN.matcher(source);
+            if (metadataMatcher.find()) {
+                String entry = metadataMatcher.group(0) + '\n';
+                Matcher entryMatcher = METADATA_ENTRY_PATTERN.matcher(entry);
+                while (entryMatcher.find()) {
+                    String key = entryMatcher.group(1);
+                    String value = normalizeMultilineValue(entryMatcher.group(2));
+                    metaData.put(key, Collections.singletonList(value));
+                }
+                endOffset = metadataMatcher.end(0);
+            } else {
+                endOffset = 0;
+            }
+        }
+        if (endOffset > 0) {
+            // Trim the metadata from the source
+            source.delete(0, endOffset);
+        }
+        return writeHtmlMetadata(html, metaData);
+    }
+
+    static String normalizeMultilineValue(String value) {
+        return value.trim().replaceAll("[ \\t]*[\\r\\n]+[ \\t]*", " ");
+    }
+
+    private boolean writeHtmlMetadata(StringBuilder html, Map<String, List<String>> data) {
+        boolean containsTitle = false;
+        for (Entry<String, List<String>> entry : data.entrySet()) {
+            if (writeHtmlMetadata(html, entry.getKey(), entry.getValue())) {
+                containsTitle = true;
+            }
+        }
+        return containsTitle;
+    }
+
+    private boolean writeHtmlMetadata(StringBuilder html, String key, List<String> values) {
+        if ("title".equalsIgnoreCase(key)) {
+            html.append("<title>");
+            html.append(HtmlTools.escapeHTML(values.stream().collect(Collectors.joining(", ")), false));
+            html.append("</title>");
+            return true;
+        } else {
+            if (key.equalsIgnoreCase("author") && values.size() > 1) {
+                // for multiple authors emit multiple meta tags
+                for (String value : values) {
+                    writeHtmlMetadata(html, key, Collections.singletonList(value));
+                }
+            } else {
+                // every other multivalue should just be concatenated and emitted in a single meta tag
+                final String separator;
+                if (key.equalsIgnoreCase("keywords")) {
+                    separator = ",";
+                } else {
+                    separator = "\n";
+                }
+                html.append("<meta name='");
+                html.append(HtmlTools.escapeHTML(key));
+                html.append("' content='");
+                html.append(HtmlTools.escapeHTML(values.stream().collect(Collectors.joining(separator))));
+                html.append("' />");
+            }
+            return false;
+        }
+    }
+
     /**
      * uses flexmark-java library to parse content and generate HTML output.
      *
@@ -184,48 +286,18 @@ public class MarkdownParser extends AbstractTextParser implements TextMarkup {
      */
     String toHtml(Reader source) throws IOException {
         // Read the source
-        String text = IOUtil.toString(source);
+        StringBuilder markdownText = new StringBuilder(IOUtil.toString(source));
 
         // Now, build the HTML document
         StringBuilder html = new StringBuilder(1000);
         html.append("<html>");
         html.append("<head>");
 
-        // detect yaml style metadata
-        if (text.startsWith("---")) {
-            // remove the enclosing --- to get back to classical metadata
-            text = text.replaceFirst("---", "").replaceFirst("---", "");
-        }
-
-        // First, we interpret the "metadata" section of the document and add the corresponding HTML headers
-        Matcher metadataMatcher = METADATA_SECTION_PATTERN.matcher(text);
-        boolean haveTitle = false;
-        if (metadataMatcher.find()) {
-            Matcher entryMatcher = METADATA_ENTRY_PATTERN.matcher(metadataMatcher.group(0));
-            while (entryMatcher.find()) {
-                String key = entryMatcher.group(1);
-                String value = entryMatcher.group(2);
-                if ("title".equalsIgnoreCase(key)) {
-                    haveTitle = true;
-                    html.append("<title>");
-                    html.append(HtmlTools.escapeHTML(value, false));
-                    html.append("</title>");
-                } else {
-                    html.append("<meta name='");
-                    html.append(HtmlTools.escapeHTML(key));
-                    html.append("' content='");
-                    html.append(HtmlTools.escapeHTML(value));
-                    html.append("' />");
-                }
-            }
-
-            // Trim the metadata from the source
-            text = text.substring(metadataMatcher.end(0));
-        }
+        boolean haveTitle = processMetadataForHtml(html, markdownText);
 
         // Now is the time to parse the Markdown document
         // (after we've trimmed out the metadatas, and before we check for its headings)
-        Node documentRoot = FLEXMARK_PARSER.parse(text);
+        Node documentRoot = FLEXMARK_PARSER.parse(markdownText.toString());
 
         // Special trick: if there is no title specified as a metadata in the header, we will use the first
         // heading as the document title
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 5229bc52..aee94439 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
@@ -40,6 +40,8 @@ package org.apache.maven.doxia.module.markdown;
 import java.io.PrintWriter;
 import java.io.Writer;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Stack;
 
@@ -69,7 +71,7 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
     private StringBuilder tableCaptionBuffer;
 
     /**  author. */
-    private String author;
+    private Collection<String> authors;
 
     /**  title. */
     private String title;
@@ -173,7 +175,7 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
         this.tableCaptionBuffer = new StringBuilder();
         this.listNestingIndent = "";
 
-        this.author = null;
+        this.authors = new LinkedList<>();
         this.title = null;
         this.date = null;
         this.linkName = null;
@@ -224,21 +226,24 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
     public void head_() {
         headerFlag = false;
 
-        if (!startFlag) {
-            write(EOL);
+        // only write head block if really necessary
+        if (title == null && authors.isEmpty() && date == null) {
+            return;
         }
-        // TODO add --- once DOXIA-617 implemented
-        // write( METADATA_MARKUP + EOL );
+        write(METADATA_MARKUP + EOL);
         if (title != null) {
             write("title: " + title + EOL);
         }
-        if (author != null) {
-            write("author: " + author + EOL);
+        if (!authors.isEmpty()) {
+            write("author: " + EOL);
+            for (String author : authors) {
+                write("  - " + author + EOL);
+            }
         }
         if (date != null) {
             write("date: " + date + EOL);
         }
-        // write( METADATA_MARKUP + EOL );
+        write(METADATA_MARKUP + EOL);
     }
 
     /**
@@ -256,7 +261,7 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
      */
     public void author_() {
         if (buffer.length() > 0) {
-            author = buffer.toString();
+            authors.add(buffer.toString());
             resetBuffer();
         }
     }
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/YamlFrontMatterVisitor.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/YamlFrontMatterVisitor.java
new file mode 100644
index 00000000..b02c156e
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/YamlFrontMatterVisitor.java
@@ -0,0 +1,40 @@
+/*
+ * 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.
+ */
+package org.apache.maven.doxia.module.markdown;
+
+import com.vladsch.flexmark.ext.yaml.front.matter.AbstractYamlFrontMatterVisitor;
+import com.vladsch.flexmark.ext.yaml.front.matter.YamlFrontMatterBlock;
+
+public class YamlFrontMatterVisitor extends AbstractYamlFrontMatterVisitor {
+
+    int endOffset = 0;
+
+    @Override
+    public void visit(YamlFrontMatterBlock node) {
+        endOffset = node.getContentChars().getEndOffset();
+        super.visit(node);
+    }
+
+    /**
+     * @return the end of the YAML front matter metadata in the input source
+     */
+    public int getEndOffset() {
+        return endOffset;
+    }
+}
diff --git a/doxia-modules/doxia-module-markdown/src/site/apt/index.apt b/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
index b896ffc9..b83bf1ca 100644
--- a/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
+++ b/doxia-modules/doxia-module-markdown/src/site/apt/index.apt
@@ -30,7 +30,16 @@ doxia-module-markdown
 
   Markdown is a popular lightweight markup language, easy to read and easy to write.
   It is supported by a large panel of websites, text editors/IDEs and converter tools.
-  Markdown format is only supported as Doxia source format.
+  Markdown format is supported both as source (parser) and destination (sink), the latter only since version 1.12.0.
+
+* Metadata
+
+  Although metadata was not part of the original Markdown format it is now widely supported through multiple extensions.
+  This modules supports the following two metadata formats:
+
+  * {{{http://fletcher.github.io/MultiMarkdown-5/metadata.html}MultiMarkdown Metadata}}
+
+  * {{{https://github.com/vsch/flexmark-java/wiki/Extensions#yaml-front-matter}YAML front matter}}
 
 * References
 
diff --git a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
index 92e8be94..92fe8fcb 100644
--- a/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
+++ b/doxia-modules/doxia-module-markdown/src/test/java/org/apache/maven/doxia/module/markdown/MarkdownParserTest.java
@@ -395,7 +395,69 @@ public class MarkdownParserTest extends AbstractParserTest {
      */
     @Test
     public void testMetadataSinkEvent() throws Exception {
-        testMetadataSinkEvent("metadata");
+        List<SinkEventElement> eventList =
+                parseFileToEventTestingSink("metadata").getEventList();
+        Iterator<SinkEventElement> it = eventList.iterator();
+
+        assertSinkEquals(
+                it,
+                "head",
+                "title",
+                "text",
+                "text",
+                "text",
+                "title_",
+                "author",
+                "text",
+                "author_",
+                "date",
+                "text",
+                "date_",
+                "unknown",
+                "head_",
+                "body",
+                "section1",
+                "sectionTitle1",
+                "text",
+                "sectionTitle1_",
+                "paragraph",
+                "text",
+                "paragraph_",
+                "section2",
+                "sectionTitle2",
+                "text",
+                "sectionTitle2_",
+                "paragraph",
+                "text",
+                "paragraph_",
+                "section2_",
+                "section1_",
+                "body_");
+
+        assertFalse(it.hasNext());
+
+        // Title must be "A Title & a Test"
+        assertEquals("A Title ", eventList.get(2).getArgs()[0]);
+        assertEquals("&", eventList.get(3).getArgs()[0]);
+        assertEquals(" a 'Test'", eventList.get(4).getArgs()[0]);
+
+        // Authors on multiple lines are just normalized in terms of whitespaces (i.e. space newline space is replaced
+        // by space)
+        assertEquals(
+                "Somebody 'Nickname' Great <so...@somewhere.org>, another author",
+                eventList.get(7).getArgs()[0]);
+
+        // Date must be "2013 © Copyleft"
+        assertEquals("2013 \u00A9 Copyleft", eventList.get(10).getArgs()[0]);
+
+        // * META element must be an "unknown" Sink event that specifies:
+        // * name = "keywords" and content = "maven,doxia,markdown"
+        SinkEventElement meta = eventList.get(12);
+        assertEquals("unknown", meta.getName());
+        assertEquals("meta", meta.getArgs()[0]);
+        SinkEventAttributeSet metaAtts = (SinkEventAttributeSet) meta.getArgs()[2];
+        assertTrue(metaAtts.containsAttribute(SinkEventAttributes.NAME, "keywords"));
+        assertTrue(metaAtts.containsAttribute("content", "maven,doxia,markdown"));
     }
 
     /**
@@ -403,12 +465,10 @@ public class MarkdownParserTest extends AbstractParserTest {
      *
      * @throws Exception if the event list is not correct when parsing the document
      */
+    @Test
     public void testMetadataYamlSinkEvent() throws Exception {
-        testMetadataSinkEvent("metadata-yaml");
-    }
-
-    private void testMetadataSinkEvent(String doc) throws Exception {
-        List<SinkEventElement> eventList = parseFileToEventTestingSink(doc).getEventList();
+        List<SinkEventElement> eventList =
+                parseFileToEventTestingSink("metadata-yaml").getEventList();
         Iterator<SinkEventElement> it = eventList.iterator();
 
         assertSinkEquals(
@@ -422,6 +482,9 @@ public class MarkdownParserTest extends AbstractParserTest {
                 "author",
                 "text",
                 "author_",
+                "author",
+                "text",
+                "author_",
                 "date",
                 "text",
                 "date_",
@@ -453,17 +516,20 @@ public class MarkdownParserTest extends AbstractParserTest {
         assertEquals("&", eventList.get(3).getArgs()[0]);
         assertEquals(" a 'Test'", eventList.get(4).getArgs()[0]);
 
-        // Author must be "Somebody <so...@somewhere.org>"
+        // first author must be "Somebody <so...@somewhere.org>"
         assertEquals(
                 "Somebody 'Nickname' Great <so...@somewhere.org>",
                 eventList.get(7).getArgs()[0]);
 
+        // second author must be "another author"
+        assertEquals("another author", eventList.get(10).getArgs()[0]);
+
         // Date must be "2013 © Copyleft"
-        assertEquals("2013 \u00A9 Copyleft", eventList.get(10).getArgs()[0]);
+        assertEquals("2013 \u00A9 Copyleft", eventList.get(13).getArgs()[0]);
 
         // * META element must be an "unknown" Sink event that specifies:
         // * name = "keywords" and content = "maven,doxia,markdown"
-        SinkEventElement meta = eventList.get(12);
+        SinkEventElement meta = eventList.get(15);
         assertEquals("unknown", meta.getName());
         assertEquals("meta", meta.getArgs()[0]);
         SinkEventAttributeSet metaAtts = (SinkEventAttributeSet) meta.getArgs()[2];
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 8381ac77..e57a12ab 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
@@ -43,6 +43,7 @@ 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;
 
@@ -359,6 +360,29 @@ public class MarkdownSinkTest extends AbstractSinkTest {
         return "<!-- " + text + " -->";
     }
 
+    @Test
+    public void testMultipleAuthors() {
+        final Sink sink = getSink();
+        sink.head();
+        sink.author();
+        sink.text("first author");
+        sink.author_();
+        sink.author();
+        sink.text("second author");
+        sink.author_();
+        sink.head_();
+        sink.flush();
+        sink.close();
+
+        String expected = MarkdownMarkup.METADATA_MARKUP + EOL
+                + "author: " + EOL
+                + "  - first author" + EOL
+                + "  - second author" + EOL
+                + MarkdownMarkup.METADATA_MARKUP + EOL;
+
+        assertEquals(expected, getSinkContent(), "Wrong metadata section");
+    }
+
     /**
      * test for DOXIA-497.
      */
diff --git a/doxia-modules/doxia-module-markdown/src/test/resources/metadata-yaml.md b/doxia-modules/doxia-module-markdown/src/test/resources/metadata-yaml.md
index 406c74ff..2f7679c6 100644
--- a/doxia-modules/doxia-module-markdown/src/test/resources/metadata-yaml.md
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/metadata-yaml.md
@@ -1,8 +1,13 @@
 ---
 title: A Title & a 'Test'
-author: Somebody 'Nickname' Great <so...@somewhere.org>
+author:
+  - Somebody 'Nickname' Great <so...@somewhere.org>
+  - another author
 date: 2013 © Copyleft
-keywords: maven,doxia,markdown
+keywords: 
+  - maven
+  - doxia
+  - markdown
 ---
 
 # The document with look-alike header
diff --git a/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md b/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md
index 97ce7f11..f3ed4542 100644
--- a/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md
@@ -1,7 +1,6 @@
-
 title: A Title & a 'Test'
-author: Somebody 'Nickname' Great <so...@somewhere.org>
-
+author: Somebody 'Nickname' Great <so...@somewhere.org>,
+another author
 date: 2013 © Copyleft
 keywords: maven,doxia,markdown