You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maven.apache.org by mi...@apache.org on 2021/01/02 21:35:17 UTC

[maven-doxia] branch master updated: [DOXIA-616] Markdown: Properly expose the language specified in fenced code blocks

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

michaelo 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 b55285c  [DOXIA-616] Markdown: Properly expose the language specified in fenced code blocks
b55285c is described below

commit b55285cada6195ca227a21da0168462addbac6de
Author: Bertrand Martin <be...@sentrysoftware.com>
AuthorDate: Tue Dec 29 01:15:20 2020 +0100

    [DOXIA-616] Markdown: Properly expose the language specified in fenced code blocks
    
    To properly implement this change the following task had to be performed:
    
    * Properly expose language class in fenced code blocks
    * Re-implemente metadata processing
    * Add unit tests
    * Add integration tests (with maven-site-plugin)
    * Fix *XhtmlBaseParser* and *XhtmlBaseSink* to properly expose attributes of the PRE and CODE elements
    * Fix *AbstractModuleTest* to use UTF-8 for input files
    * Remove Pegdown emulation profile and embrace CommonMark
    
    This closes #49
---
 .../apache/maven/doxia/parser/XhtmlBaseParser.java |   3 +-
 .../maven/doxia/sink/impl/Xhtml5BaseSink.java      |   4 +-
 .../maven/doxia/sink/impl/XhtmlBaseSink.java       |   4 +-
 .../org/apache/maven/doxia/AbstractModuleTest.java |   3 +-
 doxia-modules/doxia-module-markdown/pom.xml        |  75 +++++++
 .../src/it/DOXIA-616-fenced-code-block/pom.xml     |  69 ++++++
 .../src/site/markdown/fenced-code-block.md         |  26 +++
 .../DOXIA-616-fenced-code-block/src/site/site.xml  |  37 ++++
 .../it/DOXIA-616-fenced-code-block/verify.groovy   |  43 ++++
 .../doxia-module-markdown/src/it/general/pom.xml   |  69 ++++++
 .../src/it/general/src/site/markdown/index.md      |   5 +
 .../src/it/general/src/site/markdown/metadata.md   |  11 +
 .../src/it/general/src/site/site.xml               |  35 +++
 .../src/it/general/verify.groovy                   |  55 +++++
 .../doxia-module-markdown/src/it/settings.xml      |  55 +++++
 .../module/markdown/FlexmarkDoxiaExtension.java    |  58 -----
 .../module/markdown/FlexmarkDoxiaNodeRenderer.java | 146 -------------
 .../doxia/module/markdown/MarkdownParser.java      | 237 ++++++++++++---------
 .../doxia/module/markdown/MarkdownParserTest.java  |  70 +++++-
 .../src/test/resources/fenced-code-block.md        |   5 +
 .../src/test/resources/first-heading.md            |   2 +-
 .../src/test/resources/metadata.md                 |  13 +-
 .../maven/doxia/module/xhtml/XhtmlParser.java      |   2 +-
 pom.xml                                            |  16 ++
 24 files changed, 715 insertions(+), 328 deletions(-)

diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/parser/XhtmlBaseParser.java b/doxia-core/src/main/java/org/apache/maven/doxia/parser/XhtmlBaseParser.java
index 36b1024..57049e3 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/parser/XhtmlBaseParser.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/parser/XhtmlBaseParser.java
@@ -509,7 +509,8 @@ public class XhtmlBaseParser
                 || ( parser.getName().equals( HtmlMarkup.SAMP.toString() ) )
                 || ( parser.getName().equals( HtmlMarkup.TT.toString() ) ) )
         {
-            sink.inline( SinkEventAttributeSet.Semantics.CODE );
+            attribs.addAttributes( SinkEventAttributeSet.Semantics.CODE );
+            sink.inline( attribs );
         }
         else if ( parser.getName().equals( HtmlMarkup.A.toString() ) )
         {
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
index 96d86a0..2f0d9bc 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/Xhtml5BaseSink.java
@@ -1905,7 +1905,9 @@ public class Xhtml5BaseSink
     {
         if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, semantic ) )
         {
-            writeStartTag( tag );
+            SinkEventAttributes attributesNoSemantics = ( SinkEventAttributes ) attributes.copyAttributes();
+            attributesNoSemantics.removeAttribute( SinkEventAttributes.SEMANTICS );
+            writeStartTag( tag, attributesNoSemantics );
             tags.add( 0, tag );
         }
     }
diff --git a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/XhtmlBaseSink.java b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/XhtmlBaseSink.java
index c4a5aa5..6cb76e1 100644
--- a/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/XhtmlBaseSink.java
+++ b/doxia-core/src/main/java/org/apache/maven/doxia/sink/impl/XhtmlBaseSink.java
@@ -1782,7 +1782,9 @@ public class XhtmlBaseSink
     {
         if ( attributes.containsAttribute( SinkEventAttributes.SEMANTICS, semantic ) )
         {
-            writeStartTag( tag );
+            SinkEventAttributes attributesNoSemantics = ( SinkEventAttributes ) attributes.copyAttributes();
+            attributesNoSemantics.removeAttribute( SinkEventAttributes.SEMANTICS );
+            writeStartTag( tag, attributesNoSemantics );
             tags.add( 0, tag );
         }
     }
diff --git a/doxia-core/src/test/java/org/apache/maven/doxia/AbstractModuleTest.java b/doxia-core/src/test/java/org/apache/maven/doxia/AbstractModuleTest.java
index 3a1ec80..79249a5 100644
--- a/doxia-core/src/test/java/org/apache/maven/doxia/AbstractModuleTest.java
+++ b/doxia-core/src/test/java/org/apache/maven/doxia/AbstractModuleTest.java
@@ -30,6 +30,7 @@ import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.io.Reader;
 import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 
 /**
  * Provide some common convenience methods to test Doxia modules (parsers and sinks).
@@ -172,7 +173,7 @@ public abstract class AbstractModuleTest
 
         assertNotNull( "Could not find resource: " + baseName + "." + extension, is );
 
-        return new InputStreamReader( is );
+        return new InputStreamReader( is, StandardCharsets.UTF_8 );
     }
 
     /**
diff --git a/doxia-modules/doxia-module-markdown/pom.xml b/doxia-modules/doxia-module-markdown/pom.xml
index 29bbac4..c72083b 100644
--- a/doxia-modules/doxia-module-markdown/pom.xml
+++ b/doxia-modules/doxia-module-markdown/pom.xml
@@ -72,5 +72,80 @@ under the License.
       <groupId>org.codehaus.plexus</groupId>
       <artifactId>plexus-utils</artifactId>
     </dependency>
+    <dependency>
+      <groupId>commons-io</groupId>
+      <artifactId>commons-io</artifactId>
+    </dependency>
   </dependencies>
+  <build>
+
+    <plugins>
+
+      <!-- install -->
+      <plugin>
+        <artifactId>maven-install-plugin</artifactId>
+        <executions>
+
+          <!-- integration-test -->
+          <!-- Copy the Doxia XHTML module from our working directory to the local-repo -->
+          <!-- We do that manually because the invoker:install goal (below) doesn't do it, as -->
+          <!-- it's not the artifact we're working on, but the ones that are produced by our "siblings" -->
+          <execution>
+            <id>copy-doxia-module-xhtml-to-local-repo</id>
+            <phase>integration-test</phase>
+            <goals>
+              <goal>install-file</goal>
+            </goals>
+            <configuration>
+              <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <file>${project.basedir}/../doxia-module-xhtml/target/doxia-module-xhtml-${project.version}.jar</file>
+            </configuration>
+          </execution>
+          <execution>
+            <id>copy-doxia-to-local-repo</id>
+            <phase>integration-test</phase>
+            <goals>
+              <goal>install-file</goal>
+            </goals>
+            <configuration>
+              <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+              <file>${project.basedir}/../../doxia-core/target/doxia-core-${project.version}.jar</file>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+
+      <!--  maven-invoker-plugin -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-invoker-plugin</artifactId>
+        <!-- <version>2.0.0</version> -->
+        <configuration>
+          <debug>false</debug>
+          <cloneProjectsTo>${project.build.directory}/it</cloneProjectsTo>
+          <pomIncludes>
+            <pomInclude>**/pom.xml</pomInclude>
+          </pomIncludes>
+          <postBuildHookScript>verify</postBuildHookScript>
+          <localRepositoryPath>${project.build.directory}/local-repo</localRepositoryPath>
+          <extraArtifacts></extraArtifacts>
+          <settingsFile>src/it/settings.xml</settingsFile>
+          <goals>
+            <goal>clean</goal>
+            <goal>site</goal>
+          </goals>
+        </configuration>
+        <executions>
+          <execution>
+            <id>integration-test</id>
+            <goals>
+              <goal>install</goal>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
 </project>
diff --git a/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/pom.xml b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/pom.xml
new file mode 100644
index 0000000..12afb4f
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.doxia</groupId>
+  <artifactId>it</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <description>Project Description</description>
+
+  <build>
+    <plugins>
+
+      <!-- site -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>3.9.1</version>
+        <configuration>
+          <generateReports>false</generateReports>
+          <generateProjectInfo>false</generateProjectInfo>
+          <inputEncoding>UTF-8</inputEncoding>
+          <outputEncoding>UTF-8</outputEncoding>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.doxia</groupId>
+            <artifactId>doxia-core</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+          <dependency>
+            <groupId>org.apache.maven.doxia</groupId>
+            <artifactId>doxia-module-xhtml</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+          <dependency>
+            <groupId>@project.groupId@</groupId>
+            <artifactId>@project.artifactId@</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+        </dependencies>
+
+      </plugin>
+
+    </plugins>
+  </build>
+
+</project>
diff --git a/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/markdown/fenced-code-block.md b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/markdown/fenced-code-block.md
new file mode 100644
index 0000000..3f4da06
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/markdown/fenced-code-block.md
@@ -0,0 +1,26 @@
+author: Bertrand Martin
+
+# Fenced Code Block
+
+This is Java code and must be tagged so such.
+
+```java
+// Fenced Code Block 1
+System.out.println("Hello, Fenced Code Block!");
+```
+
+This is non-specified code:
+
+```
+# Fenced Code Block 2
+ch = fopen("/tmp/test.log", "r");
+```
+
+This is similar to indented block:
+
+    // Indented Code Block
+    System.out.println("Hello, Indented Block");
+
+And it is also different from the inline code: `System.out.println("Hello, Inline Code");`, which remains simple.
+
+And what about ```System.out.println("Inline fenced code");```?
\ No newline at end of file
diff --git a/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/site.xml b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/site.xml
new file mode 100644
index 0000000..ebc6ae2
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/src/site/site.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project name="${project.name} from site.xml">
+
+  <skin>
+    <groupId>org.apache.maven.skins</groupId>
+    <artifactId>maven-fluido-skin</artifactId>
+    <version>1.9</version>
+  </skin>
+
+  <body>
+
+    <menu name="Testing">
+      <item name="Fenced Code Block" href="fenced-code-block.html" />
+    </menu>
+
+  </body>
+</project>
diff --git a/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/verify.groovy b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/verify.groovy
new file mode 100644
index 0000000..b52ff05
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/DOXIA-616-fenced-code-block/verify.groovy
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+ // Verify fenced-code-block.html
+
+// File was produced
+File resultFile = new File(basedir, "target/site/fenced-code-block.html")
+assert resultFile.isFile()
+
+// Check the content
+String content = resultFile.text;
+
+// Our first fenced code block is <div class="source"><pre><code class="language-java">...</code></pre></div>
+assert content =~ '<div class="source">.*<pre.*>.*<code.*class=".*language-java.*">.*// Fenced Code Block 1'
+
+// Our second fenced code block doesn't specify a language
+assert content =~ '<div class="source">.*<pre.*>.*<code.*># Fenced Code Block 2'
+assert !(content =~ '<div class="source">.*<pre.*>.*<code.*language-.*># Fenced Code Block 2')
+
+// Our third code block is indented, and it shows the same way
+assert content =~ '<div class="source">.*<pre.*>.*<code.*>// Indented Code Block'
+
+// Then we have inline code, which must be in simple <code>
+assert content =~ 'inline code: <code>System.out.println'
+
+// The last one is inline "fenced" code block which must be in simple <code>
+assert content =~ 'And what about <code>System.out.println'
diff --git a/doxia-modules/doxia-module-markdown/src/it/general/pom.xml b/doxia-modules/doxia-module-markdown/src/it/general/pom.xml
new file mode 100644
index 0000000..12afb4f
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/general/pom.xml
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.maven.doxia</groupId>
+  <artifactId>it</artifactId>
+  <version>1.0-SNAPSHOT</version>
+  <packaging>pom</packaging>
+
+  <description>Project Description</description>
+
+  <build>
+    <plugins>
+
+      <!-- site -->
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-site-plugin</artifactId>
+        <version>3.9.1</version>
+        <configuration>
+          <generateReports>false</generateReports>
+          <generateProjectInfo>false</generateProjectInfo>
+          <inputEncoding>UTF-8</inputEncoding>
+          <outputEncoding>UTF-8</outputEncoding>
+        </configuration>
+        <dependencies>
+          <dependency>
+            <groupId>org.apache.maven.doxia</groupId>
+            <artifactId>doxia-core</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+          <dependency>
+            <groupId>org.apache.maven.doxia</groupId>
+            <artifactId>doxia-module-xhtml</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+          <dependency>
+            <groupId>@project.groupId@</groupId>
+            <artifactId>@project.artifactId@</artifactId>
+            <version>@project.version@</version>
+          </dependency>
+        </dependencies>
+
+      </plugin>
+
+    </plugins>
+  </build>
+
+</project>
diff --git a/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/index.md b/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/index.md
new file mode 100644
index 0000000..f509994
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/index.md
@@ -0,0 +1,5 @@
+# General Integration Tests for Doxia Markdown Module
+
+Each page is dedicated to one specific feature of the Doxia Markdown Module.
+
+Everything is tested in **verify.groovy**.
\ No newline at end of file
diff --git a/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/metadata.md b/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/metadata.md
new file mode 100644
index 0000000..de13a27
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/general/src/site/markdown/metadata.md
@@ -0,0 +1,11 @@
+Author: Bertrand 'Yours, Truly' Martin
+Title: Title from Header
+Keywords: smile,😉,utf-8
+Empty:
+
+   Weird   :      Spacing
+
+
+# Title from Title
+
+description: This description must not be included in the header
diff --git a/doxia-modules/doxia-module-markdown/src/it/general/src/site/site.xml b/doxia-modules/doxia-module-markdown/src/it/general/src/site/site.xml
new file mode 100644
index 0000000..02c04e7
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/general/src/site/site.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<project name="${project.name} from site.xml">
+
+  <body>
+
+    <menu name="Getting Started">
+      <item name="Overview" href="index.html" />
+    </menu>
+
+    <menu name="Testing">
+      <item name="Metadata" href="metadata.html" />
+    </menu>
+
+  </body>
+</project>
diff --git a/doxia-modules/doxia-module-markdown/src/it/general/verify.groovy b/doxia-modules/doxia-module-markdown/src/it/general/verify.groovy
new file mode 100644
index 0000000..6c7d71b
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/general/verify.groovy
@@ -0,0 +1,55 @@
+/*
+ * 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.
+ */
+
+
+// Verify index.html
+
+// File was produced
+File resultFile = new File(basedir, "target/site/index.html")
+assert resultFile.isFile()
+
+
+// Verify metadata.html
+
+// File was produced
+resultFile = new File(basedir, "target/site/metadata.html")
+assert resultFile.isFile()
+
+// Check the content
+String content = resultFile.text;
+
+// <title> must contain the specified title in the metadata, not in the first heading
+assert content =~ '<title>.*Title from Header.*</title>'
+
+// Author is Bertrand, yours truly
+// Apostrophe must have been interpreted properly
+assert content =~ '<meta name="Author" content="Bertrand \'Yours, Truly\' Martin" />'
+
+// Keywords do support utf-8 smileys
+assert content =~ '<meta name="Keywords" content="smile,&#x1f609;,utf-8" />'
+
+// Meta are properly trimmed
+assert content =~ '<meta name="Weird" content="Spacing" />'
+
+// Empty is empty
+assert content =~ '<meta name="Empty" content="" />'
+
+// No description is provided, as it was not part of the metadata at the beginning of the doc
+assert !(content =~ '<meta name="description"')
+
diff --git a/doxia-modules/doxia-module-markdown/src/it/settings.xml b/doxia-modules/doxia-module-markdown/src/it/settings.xml
new file mode 100644
index 0000000..a163718
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/it/settings.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!--
+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.
+-->
+
+<settings>
+  <profiles>
+    <profile>
+      <id>it-repo</id>
+      <activation>
+        <activeByDefault>true</activeByDefault>
+      </activation>
+      <repositories>
+        <repository>
+          <id>1local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </repository>
+      </repositories>
+      <pluginRepositories>
+        <pluginRepository>
+          <id>1local.central</id>
+          <url>@localRepositoryUrl@</url>
+          <releases>
+            <enabled>true</enabled>
+          </releases>
+          <snapshots>
+            <enabled>true</enabled>
+          </snapshots>
+        </pluginRepository>
+      </pluginRepositories>
+    </profile>
+  </profiles>
+</settings>
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaExtension.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaExtension.java
deleted file mode 100644
index f020d45..0000000
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaExtension.java
+++ /dev/null
@@ -1,58 +0,0 @@
-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 com.vladsch.flexmark.html.HtmlRenderer;
-import com.vladsch.flexmark.util.builder.Extension;
-import com.vladsch.flexmark.util.options.DataKey;
-import com.vladsch.flexmark.util.options.MutableDataHolder;
-
-/**
- * Implements flexmark-java extension to render fenced code and indented code using doxia format
- */
-class FlexmarkDoxiaExtension implements HtmlRenderer.HtmlRendererExtension
-{
-    /** Constant <code>INPUT_FILE_EXTENSION</code> */
-    public static final DataKey<String> INPUT_FILE_EXTENSION = new DataKey<>( "INPUT_FILE_EXTENSION", "md" );
-
-    /** {@inheritDoc} */
-    @Override
-    public void rendererOptions( final MutableDataHolder options )
-    {
-
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public void extend( HtmlRenderer.Builder rendererBuilder, String rendererType )
-    {
-        rendererBuilder.nodeRendererFactory( new FlexmarkDoxiaNodeRenderer.Factory() );
-    }
-
-    /**
-     * <p>create.</p>
-     *
-     * @return a {@link com.vladsch.flexmark.util.builder.Extension} object.
-     */
-    public static Extension create()
-    {
-        return new FlexmarkDoxiaExtension();
-    }
-}
diff --git a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaNodeRenderer.java b/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaNodeRenderer.java
deleted file mode 100644
index dadf24b..0000000
--- a/doxia-modules/doxia-module-markdown/src/main/java/org/apache/maven/doxia/module/markdown/FlexmarkDoxiaNodeRenderer.java
+++ /dev/null
@@ -1,146 +0,0 @@
-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 com.vladsch.flexmark.ast.FencedCodeBlock;
-import com.vladsch.flexmark.ast.IndentedCodeBlock;
-import com.vladsch.flexmark.html.CustomNodeRenderer;
-import com.vladsch.flexmark.html.HtmlWriter;
-import com.vladsch.flexmark.html.renderer.NodeRenderer;
-import com.vladsch.flexmark.html.renderer.NodeRendererContext;
-import com.vladsch.flexmark.html.renderer.NodeRendererFactory;
-import com.vladsch.flexmark.html.renderer.NodeRenderingHandler;
-import com.vladsch.flexmark.util.options.DataHolder;
-
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * The node renderer that renders all the core nodes (comes last in the order of node renderers).
- */
-@SuppressWarnings( "WeakerAccess" )
-class FlexmarkDoxiaNodeRenderer implements NodeRenderer
-{
-    FlexmarkDoxiaNodeRenderer( DataHolder options )
-    {
-    }
-
-    /** {@inheritDoc} */
-    @Override
-    public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers()
-    {
-        return new HashSet<NodeRenderingHandler<?>>( Arrays.asList(
-                new NodeRenderingHandler<>( IndentedCodeBlock.class, new CustomNodeRenderer<IndentedCodeBlock>()
-                {
-                    @Override
-                    public void render( IndentedCodeBlock node, NodeRendererContext context, HtmlWriter html )
-                    {
-                        FlexmarkDoxiaNodeRenderer.this.render( node, context, html );
-                    }
-                } ),
-                new NodeRenderingHandler<>( FencedCodeBlock.class, new CustomNodeRenderer<FencedCodeBlock>()
-                {
-                    @Override
-                    public void render( FencedCodeBlock node, NodeRendererContext context, HtmlWriter html )
-                    {
-                        FlexmarkDoxiaNodeRenderer.this.render( node, context, html );
-                    }
-                } )
-        ) );
-    }
-
-    private void render( IndentedCodeBlock node, NodeRendererContext context, HtmlWriter html )
-    {
-        html.line();
-        html.attr( "class", "source" ).withAttr().tag( "div" );
-        html.srcPosWithEOL( node.getChars() ).tag( "pre" ).openPre();
-
-        String noLanguageClass = context.getHtmlOptions().noLanguageClass.trim();
-        if ( !noLanguageClass.isEmpty() )
-        {
-            html.attr( "class", noLanguageClass );
-        }
-
-        //html.srcPosWithEOL(node.getContentChars()).withAttr(CoreNodeRenderer.CODE_CONTENT).tag("code");
-        String s = node.getContentChars().trimTailBlankLines().normalizeEndWithEOL();
-        while ( !s.isEmpty() && s.charAt( 0 ) == '\n' )
-        {
-            html.raw( "<br/>" );
-            s = s.substring( 1 );
-        }
-        html.text( s );
-
-        //html.tag("/code");
-        html.tag( "/pre" ).closePre();
-        html.tag( "/div" );
-        html.line();
-    }
-
-    private void render( FencedCodeBlock node, NodeRendererContext context, HtmlWriter html )
-    {
-        html.line();
-        html.attr( "class", "source" ).withAttr().tag( "div" );
-        html.srcPosWithTrailingEOL( node.getChars() ).tag( "pre" ).openPre();
-
-        //BasedSequence info = node.getInfo();
-        //if (info.isNotNull() && !info.isBlank()) {
-        //    int space = info.indexOf(' ');
-        //    BasedSequence language;
-        //    if (space == -1) {
-        //        language = info;
-        //    } else {
-        //        language = info.subSequence(0, space);
-        //    }
-        //    html.attr("class", context.getHtmlOptions().languageClassPrefix + language.unescape());
-        //} else  {
-        //    String noLanguageClass = context.getHtmlOptions().noLanguageClass.trim();
-        //    if (!noLanguageClass.isEmpty()) {
-        //        html.attr("class", noLanguageClass);
-        //    }
-        //}
-
-        //html.srcPosWithEOL(node.getContentChars()).withAttr(CoreNodeRenderer.CODE_CONTENT).tag("code");
-        String s = node.getContentChars().normalizeEOL();
-        while ( !s.isEmpty() && s.charAt( 0 ) == '\n' )
-        {
-            html.raw( "<br/>" );
-            s = s.substring( 1 );
-        }
-        html.text( s );
-
-        //html.tag("/code");
-        html.tag( "/pre" ).closePre();
-        html.tag( "/div" );
-        html.line();
-    }
-
-    /**
-     * Factory for doxia node renderer
-     */
-    public static class Factory implements NodeRendererFactory
-    {
-        @Override
-        public NodeRenderer create( final DataHolder options )
-        {
-            return new FlexmarkDoxiaNodeRenderer( options );
-        }
-    }
-}
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 f40e123..1728d3e 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
@@ -24,18 +24,24 @@ import com.vladsch.flexmark.ast.HtmlCommentBlock;
 import com.vladsch.flexmark.util.ast.Node;
 import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
 import com.vladsch.flexmark.html.HtmlRenderer;
-import com.vladsch.flexmark.profiles.pegdown.Extensions;
-import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
-import com.vladsch.flexmark.util.builder.Extension;
-import com.vladsch.flexmark.util.options.MutableDataHolder;
-import org.apache.commons.lang3.StringEscapeUtils;
-import org.apache.commons.lang3.StringUtils;
+import com.vladsch.flexmark.util.options.MutableDataSet;
+import com.vladsch.flexmark.ext.escaped.character.EscapedCharacterExtension;
+import com.vladsch.flexmark.ext.abbreviation.AbbreviationExtension;
+import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
+import com.vladsch.flexmark.ext.definition.DefinitionExtension;
+import com.vladsch.flexmark.ext.typographic.TypographicExtension;
+import com.vladsch.flexmark.ext.tables.TablesExtension;
+import com.vladsch.flexmark.ext.wikilink.WikiLinkExtension;
+import com.vladsch.flexmark.ext.gfm.strikethrough.StrikethroughExtension;
+
+import org.apache.commons.io.input.CharSequenceReader;
 import org.apache.maven.doxia.markup.HtmlMarkup;
 import org.apache.maven.doxia.module.xhtml.XhtmlParser;
 import org.apache.maven.doxia.parser.AbstractParser;
 import org.apache.maven.doxia.parser.ParseException;
 import org.apache.maven.doxia.parser.Parser;
 import org.apache.maven.doxia.sink.Sink;
+import org.apache.maven.doxia.util.HtmlTools;
 import org.codehaus.plexus.component.annotations.Component;
 import org.codehaus.plexus.component.annotations.Requirement;
 import org.codehaus.plexus.util.IOUtil;
@@ -43,8 +49,7 @@ import org.codehaus.plexus.util.xml.pull.XmlPullParser;
 
 import java.io.IOException;
 import java.io.Reader;
-import java.io.StringReader;
-import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -74,48 +79,97 @@ public class MarkdownParser
 
     /**
      * Regex that identifies a multimarkdown-style metadata section at the start of the document
+     *
+     * 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.
      */
-    private static final String MULTI_MARKDOWN_METADATA_SECTION =
-        "^(((?:[^\\s:][^:]*):(?:.*(?:\r?\n\\p{Blank}+[^\\s].*)*\r?\n))+)(?:\\s*\r?\n)";
+    private static final Pattern METADATA_SECTION_PATTERN = Pattern.compile(
+            "\\A^\\s*"
+            + "(?:title|author|date|address|affiliation|copyright|email|keywords|language|phone|subtitle)"
+            + "\\h*:\\h*\\V*\\h*$\\v+"
+            + "(?:^\\h*[^:\\v]+\\h*:\\h*\\V*\\h*$\\v+)*",
+            Pattern.MULTILINE | Pattern.CASE_INSENSITIVE );
 
     /**
      * Regex that captures the key and value of a multimarkdown-style metadata entry.
      */
-    private static final String MULTI_MARKDOWN_METADATA_ENTRY =
-        "([^\\s:][^:]*):(.*(?:\r?\n\\p{Blank}+[^\\s].*)*)\r?\n";
-
-    /**
-     * 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.
-     */
-    private static final String[] STANDARD_METADATA_KEYS =
-        { "title", "author", "date", "address", "affiliation", "copyright", "email", "keywords", "language", "phone",
-            "subtitle" };
+    private static final Pattern METADATA_ENTRY_PATTERN = Pattern.compile(
+            "^\\h*([^:\\v]+?)\\h*:\\h*(\\V*)\\h*$",
+            Pattern.MULTILINE );
 
     /**
      * <p>getType.</p>
      *
      * @return a int.
      */
+    @Override
     public int getType()
     {
         return TXT_TYPE;
     }
 
+    /**
+     * The parser of the HTML produced by Flexmark, that we will
+     * use to convert this HTML to Sink events
+     */
     @Requirement
     private MarkdownHtmlParser parser;
 
+    /**
+     * Flexmark's Markdown parser (one static instance fits all)
+     */
+    private static final com.vladsch.flexmark.parser.Parser FLEXMARK_PARSER;
+
+    /**
+     * Flexmark's HTML renderer (its output will be re-parsed and converted to Sink events)
+     */
+    private static final HtmlRenderer FLEXMARK_HTML_RENDERER;
+
+    // Initialize the Flexmark parser and renderer, once and for all
+    static
+    {
+        MutableDataSet flexmarkOptions = new MutableDataSet();
+
+        // Enable the extensions that we used to have in Pegdown
+        flexmarkOptions.set( com.vladsch.flexmark.parser.Parser.EXTENSIONS, Arrays.asList(
+                EscapedCharacterExtension.create(),
+                AbbreviationExtension.create(),
+                AutolinkExtension.create(),
+                DefinitionExtension.create(),
+                TypographicExtension.create(),
+                TablesExtension.create(),
+                WikiLinkExtension.create(),
+                StrikethroughExtension.create()
+        ) );
+
+        // Additional options on the HTML rendering
+        flexmarkOptions.set( HtmlRenderer.HTML_BLOCK_OPEN_TAG_EOL, false );
+        flexmarkOptions.set( HtmlRenderer.HTML_BLOCK_CLOSE_TAG_EOL, false );
+        flexmarkOptions.set( HtmlRenderer.MAX_TRAILING_BLANK_LINES, -1 );
+
+        // Build the Markdown parser
+        FLEXMARK_PARSER = com.vladsch.flexmark.parser.Parser.builder( flexmarkOptions ).build();
+
+        // Build the HTML renderer
+        FLEXMARK_HTML_RENDERER = HtmlRenderer.builder( flexmarkOptions )
+                .linkResolverFactory( new FlexmarkDoxiaLinkResolver.Factory() )
+                .build();
+
+    }
+
     /** {@inheritDoc} */
+    @Override
     public void parse( Reader source, Sink sink )
         throws ParseException
     {
         try
         {
             // Markdown to HTML (using flexmark-java library)
-            String html = toHtml( source );
+            CharSequence html = toHtml( source );
+
             // then HTML to Sink API
-            parser.parse( new StringReader( html ), sink );
+            parser.parse( new CharSequenceReader( html ), sink );
         }
         catch ( IOException e )
         {
@@ -130,133 +184,98 @@ public class MarkdownParser
      * @return HTML content generated by flexmark-java
      * @throws IOException passed through
      */
-    String toHtml( Reader source )
+    CharSequence toHtml( Reader source )
         throws IOException
     {
+        // Read the source
         String text = IOUtil.toString( source );
-        MutableDataHolder flexmarkOptions = PegdownOptionsAdapter.flexmarkOptions(
-                Extensions.ALL & ~( Extensions.HARDWRAPS | Extensions.ANCHORLINKS ) ).toMutable();
-        ArrayList<Extension> extensions = new ArrayList<>();
-        for ( Extension extension : flexmarkOptions.get( com.vladsch.flexmark.parser.Parser.EXTENSIONS ) )
-        {
-            extensions.add( extension );
-        }
-
-        extensions.add( FlexmarkDoxiaExtension.create() );
-        flexmarkOptions.set( com.vladsch.flexmark.parser.Parser.EXTENSIONS, extensions );
-        flexmarkOptions.set( HtmlRenderer.HTML_BLOCK_OPEN_TAG_EOL, false );
-        flexmarkOptions.set( HtmlRenderer.HTML_BLOCK_CLOSE_TAG_EOL, false );
-        flexmarkOptions.set( HtmlRenderer.MAX_TRAILING_BLANK_LINES, -1 );
-
-        com.vladsch.flexmark.parser.Parser parser = com.vladsch.flexmark.parser.Parser.builder( flexmarkOptions )
-                .build();
-        HtmlRenderer renderer = HtmlRenderer.builder( flexmarkOptions )
-                                    .linkResolverFactory( new FlexmarkDoxiaLinkResolver.Factory() )
-                                    .build();
-
 
+        // Now, build the HTML document
         StringBuilder html = new StringBuilder( 1000 );
         html.append( "<html>" );
         html.append( "<head>" );
-        Pattern metadataPattern = Pattern.compile( MULTI_MARKDOWN_METADATA_SECTION, Pattern.MULTILINE );
-        Matcher metadataMatcher = metadataPattern.matcher( text );
+
+        // 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() )
         {
-            metadataPattern = Pattern.compile( MULTI_MARKDOWN_METADATA_ENTRY, Pattern.MULTILINE );
-            Matcher lineMatcher = metadataPattern.matcher( metadataMatcher.group( 1 ) );
-            boolean first = true;
-            while ( lineMatcher.find() )
+            Matcher entryMatcher = METADATA_ENTRY_PATTERN.matcher( metadataMatcher.group( 0 ) );
+            while ( entryMatcher.find() )
             {
-                String key = StringUtils.trimToEmpty( lineMatcher.group( 1 ) );
-                if ( first )
-                {
-                    boolean found = false;
-                    for ( String k : STANDARD_METADATA_KEYS )
-                    {
-                        if ( k.equalsIgnoreCase( key ) )
-                        {
-                            found = true;
-                            break;
-                        }
-                    }
-                    if ( !found )
-                    {
-                        break;
-                    }
-                    first = false;
-                }
-                String value = StringUtils.trimToEmpty( lineMatcher.group( 2 ) );
+                String key = entryMatcher.group( 1 );
+                String value = entryMatcher.group( 2 );
                 if ( "title".equalsIgnoreCase( key ) )
                 {
                     haveTitle = true;
                     html.append( "<title>" );
-                    html.append( StringEscapeUtils.escapeXml( value ) );
+                    html.append( HtmlTools.escapeHTML( value, false ) );
                     html.append( "</title>" );
                 }
-                else if ( "author".equalsIgnoreCase( key ) )
-                {
-                    html.append( "<meta name=\'author\' content=\'" );
-                    html.append( StringEscapeUtils.escapeXml( value ) );
-                    html.append( "\' />" );
-                }
-                else if ( "date".equalsIgnoreCase( key ) )
-                {
-                    html.append( "<meta name=\'date\' content=\'" );
-                    html.append( StringEscapeUtils.escapeXml( value ) );
-                    html.append( "\' />" );
-                }
                 else
                 {
-                    html.append( "<meta name=\'" );
-                    html.append( StringEscapeUtils.escapeXml( key ) );
-                    html.append( "\' content=\'" );
-                    html.append( StringEscapeUtils.escapeXml( value ) );
-                    html.append( "\' />" );
+                    html.append( "<meta name='" );
+                    html.append( HtmlTools.escapeHTML( key ) );
+                    html.append( "' content='" );
+                    html.append( HtmlTools.escapeHTML( value ) );
+                    html.append( "' />" );
                 }
             }
-            if ( !first )
-            {
-                text = text.substring( metadataMatcher.end() );
-            }
+
+            // Trim the metadata from the source
+            text = text.substring( metadataMatcher.end( 0 ) );
+
         }
 
-        Node rootNode = parser.parse( text );
-        String markdownHtml = renderer.render( rootNode );
+        // 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 );
 
-        if ( !haveTitle && rootNode.hasChildren() )
+        // Special trick: if there is no title specified as a metadata in the header, we will use the first
+        // heading as the document title
+        if ( !haveTitle && documentRoot.hasChildren() )
         {
-            // use the first (non-comment) node only if it is a heading
-            Node firstNode = rootNode.getFirstChild();
-            while ( firstNode != null && !( firstNode instanceof Heading ) )
+            // Skip the comment nodes
+            Node firstNode = documentRoot.getFirstChild();
+            while ( firstNode != null && firstNode instanceof HtmlCommentBlock )
             {
-                if ( !( firstNode instanceof HtmlCommentBlock ) )
-                {
-                    break;
-                }
                 firstNode = firstNode.getNext();
             }
 
-            if ( firstNode instanceof Heading )
+            // If this first non-comment node is a heading, we use it as the document title
+            if ( firstNode != null && firstNode instanceof Heading )
             {
                 html.append( "<title>" );
                 TextCollectingVisitor collectingVisitor = new TextCollectingVisitor();
                 String headingText = collectingVisitor.collectAndGetText( firstNode );
-                html.append( StringEscapeUtils.escapeXml( headingText ) );
+                html.append( HtmlTools.escapeHTML( headingText, false ) );
                 html.append( "</title>" );
             }
         }
         html.append( "</head>" );
         html.append( "<body>" );
-        html.append( markdownHtml );
+
+        // Convert our Markdown document to HTML and append it to our HTML
+        FLEXMARK_HTML_RENDERER.render( documentRoot, html );
+
         html.append( "</body>" );
         html.append( "</html>" );
 
-        return html.toString();
+        return html;
     }
 
     /**
      * Internal parser for HTML generated by the Markdown library.
+     *
+     * 2 special things:
+     * <ul>
+     * <li> DIV elements are translated as Unknown Sink events
+     * <li> PRE elements are all considered as boxed
+     * </ul>
+     * PRE elements need to be "boxed" because the XhtmlSink will surround the
+     * corresponding verbatim() Sink event with a DIV element with class="source",
+     * which is how most Maven Skin (incl. Fluido) recognize a block of code, which
+     * needs to be highlighted accordingly.
      */
     @Component( role = MarkdownHtmlParser.class )
     public static class MarkdownHtmlParser
@@ -268,6 +287,13 @@ public class MarkdownParser
         }
 
         @Override
+        protected void init()
+        {
+            super.init();
+            super.boxed = true;
+        }
+
+        @Override
         protected boolean baseEndTag( XmlPullParser parser, Sink sink )
         {
             boolean visited = super.baseEndTag( parser, sink );
@@ -291,6 +317,7 @@ public class MarkdownParser
                 if ( parser.getName().equals( HtmlMarkup.DIV.toString() ) )
                 {
                     handleUnknown( parser, sink, TAG_TYPE_START );
+                    super.boxed = true;
                     visited = true;
                 }
             }
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 8110378..fbdd547 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
@@ -165,12 +165,43 @@ public class MarkdownParserTest
     {
         Iterator<SinkEventElement> it = parseFileToEventTestingSink( "code" ).getEventList().iterator();
 
-        assertEquals( it, "head", "head_", "body", "paragraph", "text", "paragraph_", "text", "unknown", "verbatim", "text", "verbatim_", "unknown", "body_" );
+        assertEquals( it, "head", "head_", "body", "paragraph", "text", "paragraph_", "text", "verbatim", "inline", "text", "inline_", "verbatim_", "body_" );
 
         assertFalse( it.hasNext() );
     }
 
     /**
+     * Assert the verbatim sink event is fired when parsing "fenced-code-block.md".
+     *
+     * @throws Exception if the event list is not correct when parsing the document
+     */
+    public void testFencedCodeBlockSinkEvent()
+        throws Exception
+    {
+        List<SinkEventElement> eventList = parseFileToEventTestingSink( "fenced-code-block" ).getEventList();
+        Iterator<SinkEventElement> it = eventList.iterator();
+
+        assertEquals( it, "head", "head_", "body", "paragraph", "text", "paragraph_", "text", "verbatim", "inline", "text", "inline_", "verbatim_", "body_" );
+
+        assertFalse( it.hasNext() );
+
+        // PRE element must be a "verbatim" Sink event that specifies
+        // BOXED = true
+        SinkEventElement pre = eventList.get( 7 );
+        assertEquals( "verbatim", pre.getName() );
+        SinkEventAttributeSet preAtts = (SinkEventAttributeSet) pre.getArgs()[0];
+        assertTrue( preAtts.containsAttribute( SinkEventAttributes.DECORATION, "boxed" ) );
+
+        // * CODE element must be an "inline" Sink event that specifies:
+        // * SEMANTICS = "code" and CLASS = "language-java"
+        SinkEventElement code = eventList.get( 8 );
+        assertEquals( "inline", code.getName() );
+        SinkEventAttributeSet codeAtts = (SinkEventAttributeSet) code.getArgs()[0];
+        assertTrue( codeAtts.containsAttribute( SinkEventAttributes.SEMANTICS, "code" ) );
+        assertTrue( codeAtts.containsAttribute( SinkEventAttributes.CLASS, "language-java" ) );
+    }
+
+    /**
      * Assert the figureGraphics sink event is fired when parsing "image.md".
      *
      * @throws Exception if the event list is not correct when parsing the document
@@ -223,7 +254,7 @@ public class MarkdownParserTest
     public void testLinkWithAnchorAndQuery() throws Exception
     {
         Iterator<SinkEventElement> it = parseFileToEventTestingSink( "link_anchor_query" ).getEventList().iterator();
-        
+
         assertEquals( it, "head", "head_", "body", "paragraph", "link", "text", "link_", "paragraph_", "body_" );
 
         assertFalse( it.hasNext() );
@@ -269,14 +300,36 @@ public class MarkdownParserTest
     public void testMetadataSinkEvent()
         throws Exception
     {
-        Iterator<SinkEventElement> it = parseFileToEventTestingSink( "metadata" ).getEventList().iterator();
+        List<SinkEventElement> eventList = parseFileToEventTestingSink( "metadata" ).getEventList();
+        Iterator<SinkEventElement> it = eventList.iterator();
 
-        assertEquals( it, "head", "title", "text", "title_", "author", "text", "author_", "date", "text", "date_",
-                      "head_", "body", "unknown", "text", "unknown", "paragraph", "text", "paragraph_", "section1",
+        assertEquals( it, "head", "title", "text", "text", "text", "title_", "author", "text", "author_", "date", "text", "date_",
+                      "unknown", "head_", "body", "unknown", "text", "unknown", "paragraph", "text", "paragraph_", "section1",
                       "sectionTitle1", "text", "sectionTitle1_", "paragraph", "text", "paragraph_", "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]);
+
+        // Author must be "Somebody <so...@somewhere.org>"
+        assertEquals( "Somebody 'Nickname' Great <so...@somewhere.org>", 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" ) );
+
     }
 
     /**
@@ -290,8 +343,9 @@ public class MarkdownParserTest
         Iterator<SinkEventElement> it = parseFileToEventTestingSink( "first-heading" ).getEventList().iterator();
 
         // NOTE: H1 is rendered as "unknown" and H2 is "section1" (see DOXIA-203)
-        assertEquals( it, "head", "title", "text", "title_", "head_", "body", "section1", "sectionTitle1", "text",
-                      "sectionTitle1_", "paragraph", "text", "paragraph_", "section1_", "body_" );
+        assertEquals( it, "head", "title", "text", "title_", "head_", "body", "comment", "text",
+                "section1", "sectionTitle1", "text", "sectionTitle1_", "paragraph", "text",
+                "paragraph_", "section1_", "body_" );
 
         assertFalse( it.hasNext() );
     }
@@ -358,7 +412,7 @@ public class MarkdownParserTest
     {
         try ( Reader reader = getTestReader( file ) )
         {
-            return parser.toHtml( reader );
+            return parser.toHtml( reader ).toString();
         }
     }
 
diff --git a/doxia-modules/doxia-module-markdown/src/test/resources/fenced-code-block.md b/doxia-modules/doxia-module-markdown/src/test/resources/fenced-code-block.md
new file mode 100644
index 0000000..d8df735
--- /dev/null
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/fenced-code-block.md
@@ -0,0 +1,5 @@
+Below code is Java:
+
+```java
+System.out.println(helloWorld);
+```
diff --git a/doxia-modules/doxia-module-markdown/src/test/resources/first-heading.md b/doxia-modules/doxia-module-markdown/src/test/resources/first-heading.md
index 622fffe..108f199 100644
--- a/doxia-modules/doxia-module-markdown/src/test/resources/first-heading.md
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/first-heading.md
@@ -1,7 +1,7 @@
 
 
 
-
+<!-- To be discarded -->
 
 
 First heading
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 dac8009..97ce7f1 100644
--- a/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md
+++ b/doxia-modules/doxia-module-markdown/src/test/resources/metadata.md
@@ -1,10 +1,13 @@
-title: A title
-author: Somebody
-date: 2013
 
-# The document
+title: A Title & a 'Test'
+author: Somebody 'Nickname' Great <so...@somewhere.org>
 
-Some text
+date: 2013 © Copyleft
+keywords: maven,doxia,markdown
+
+# The document with look-alike header
+
+copyright: none
 
 ## A subheading
 
diff --git a/doxia-modules/doxia-module-xhtml/src/main/java/org/apache/maven/doxia/module/xhtml/XhtmlParser.java b/doxia-modules/doxia-module-xhtml/src/main/java/org/apache/maven/doxia/module/xhtml/XhtmlParser.java
index 00cfc8d..470fc2f 100644
--- a/doxia-modules/doxia-module-xhtml/src/main/java/org/apache/maven/doxia/module/xhtml/XhtmlParser.java
+++ b/doxia-modules/doxia-module-xhtml/src/main/java/org/apache/maven/doxia/module/xhtml/XhtmlParser.java
@@ -53,7 +53,7 @@ public class XhtmlParser
     implements XhtmlMarkup
 {
     /** For boxed verbatim. */
-    private boolean boxed;
+    protected boolean boxed;
 
     /** Empty elements don't write a closing tag. */
     private boolean isEmptyElement;
diff --git a/pom.xml b/pom.xml
index b634aa6..da0dd4c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -207,6 +207,14 @@ under the License.
         <artifactId>plexus-utils</artifactId>
         <version>3.3.0</version>
       </dependency>
+
+      <!-- Apache Commons IO -->
+      <dependency>
+        <groupId>commons-io</groupId>
+        <artifactId>commons-io</artifactId>
+        <version>2.6</version>
+      </dependency>
+
     </dependencies>
   </dependencyManagement>
 
@@ -262,6 +270,7 @@ under the License.
               <exclude>src/test/site/**/*.confluence</exclude>
               <exclude>src/test/resources/**/*.twiki</exclude>
               <exclude>src/test/resources/**/*.md</exclude>
+              <exclude>src/it/**/site/**/*.md</exclude>
             </excludes>
           </configuration>
         </plugin>
@@ -317,6 +326,13 @@ under the License.
                 <exclude>org/apache/maven/doxia/module/fml/FmlContentParser</exclude>
                 <exclude>org/apache/maven/doxia/module/xdoc/XdocParser</exclude>
               </excludes>
+              <ignored>
+                <!-- DOXIA-616 -->
+                <difference>
+                  <differenceType>8001</differenceType>
+                  <className>org/apache/maven/doxia/module/markdown/FlexmarkDoxiaNodeRenderer$Factory</className>
+                </difference>
+              </ignored>
             </configuration>
           </execution>
         </executions>