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/02/02 18:59:20 UTC

[maven-doxia] branch master updated: [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown) (#141)

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

kwin 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 ab13591c [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown) (#141)
ab13591c is described below

commit ab13591cdd02f121e90c3a0332ec2c71a1cbe531
Author: Konrad Windszus <kw...@apache.org>
AuthorDate: Thu Feb 2 19:59:14 2023 +0100

    [DOXIA-690] Improved support of metadata (both YAML front matter and MultiMarkdown) (#141)
    
    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 b65f292a..e32ba64d 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 2b454ed1..eb9f25d9 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
@@ -25,8 +25,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;
@@ -39,6 +45,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;
@@ -76,19 +83,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
@@ -102,6 +113,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)
      */
@@ -136,6 +152,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())
@@ -156,6 +178,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) + EOL;
+                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 multi-value should just be concatenated and emitted in a single meta tag
+                final String separator;
+                if (key.equalsIgnoreCase("keywords")) {
+                    separator = ",";
+                } else {
+                    separator = EOL;
+                }
+                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.
      *
@@ -165,48 +267,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 740a2593..c5c7dcb1 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
@@ -21,6 +21,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;
 
@@ -50,7 +52,7 @@ public class MarkdownSink extends AbstractTextSink implements MarkdownMarkup {
     private StringBuilder tableCaptionBuffer;
 
     /**  author. */
-    private String author;
+    private Collection<String> authors;
 
     /**  title. */
     private String title;
@@ -154,7 +156,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;
@@ -205,21 +207,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);
     }
 
     /**
@@ -237,7 +242,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 c6362665..9d716de0 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
@@ -376,7 +376,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"));
     }
 
     /**
@@ -384,12 +446,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(
@@ -403,6 +463,9 @@ public class MarkdownParserTest extends AbstractParserTest {
                 "author",
                 "text",
                 "author_",
+                "author",
+                "text",
+                "author_",
                 "date",
                 "text",
                 "date_",
@@ -434,17 +497,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 4cd8b75b..cac89ac5 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
@@ -24,6 +24,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;
 
@@ -340,6 +341,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