Skip to content
Vladimir Schneider edited this page Feb 8, 2022 · 96 revisions

For Maven, add flexmark-all as a dependency which includes core and all modules to the following sample:

<dependency>
    <groupId>com.vladsch.flexmark</groupId>
    <artifactId>flexmark-all</artifactId>
    <version>0.64.0</version>
</dependency>

Building via Gradle

compile 'com.vladsch.flexmark:flexmark-all:0.64.0'

Building with Android Studio

Additional settings due to duplicate files:

packagingOptions {
    exclude 'META-INF/LICENSE-LGPL-2.1.txt'
    exclude 'META-INF/LICENSE-LGPL-3.txt'
    exclude 'META-INF/LICENSE-W3C-TEST'
}

Please use the code listed here with the understanding that it is probably a bit out of date. Use the links to get the most current version.

Parse and render to HTML

Source: ProfileEmulationFamilySamples.java

package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.parser.ParserEmulationProfile;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;

public class BasicSample {
    void commonMark() {
        Parser parser = Parser.builder().build();
        Node document = parser.parse("This is *Sparta*");
        HtmlRenderer renderer = HtmlRenderer.builder().build();
        renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
    }

    void kramdown() {
        MutableDataSet options = new MutableDataSet();
        options.setFrom(ParserEmulationProfile.KRAMDOWN);
        options.set(Parser.EXTENSIONS, Arrays.asList(
                AbbreviationExtension.create(),
                DefinitionExtension.create(),
                FootnoteExtension.create(),
                TablesExtension.create(),
                TypographicExtension.create()
        ));

        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        Node document = parser.parse("This is *Sparta*");
        renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
    }

    void multiMarkdown() {
        MutableDataHolder options = new MutableDataSet();
        options.setFrom(ParserEmulationProfile.MULTI_MARKDOWN);

        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        Node document = parser.parse("This is *Sparta*");
        renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
    }

    void markdown() {
        MutableDataHolder options = new MutableDataSet();
        options.setFrom(ParserEmulationProfile.MARKDOWN);

        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        Node document = parser.parse("This is *Sparta*");
        renderer.render(document);  // "<p>This is <em>Sparta</em></p>\n"
    }
}

This uses the parser and renderer with default options. Both builders have methods for configuring their behavior, e.g. calling escapeHtml(true) on HtmlRenderer will escape raw HTML tags and blocks. For all available options, see methods on the builders.

Note that this library does not try to sanitize the resulting HTML; that is the responsibility of the caller.

Despite its name, commonmark is neither a superset nor a subset of other markdown flavors. Rather, it proposes a standard, unambiguous syntax specification for the original, "core" Markdown, thus effectively introducing yet another flavor. While flexmark is by default commonmark compliant, its parser can be tweaked in various ways. The sets of tweaks required to emulate the most commonly used markdown parsers around are available in flexmark as ParserEmulationProfiles.

As the name ParserEmulationProfile implies, it's only the parser that is adjusted to the specific markdown flavor. Applying the profile does not add features beyond those available in commonmark. If you want to use flexmark to fully emulate another markdown processor's behavior, you have to adjust the parser and configure the flexmark extensions that provide the additional features available in the parser that you want to emulate.

Use a visitor to process parsed nodes

Source: VisitorSample.java

package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.ast.Text;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.util.ast.NodeVisitor;
import com.vladsch.flexmark.util.ast.VisitHandler;

@SuppressWarnings({ "unchecked", "WeakerAccess" })
public class VisitorSample {
    int wordCount;

    // example of visitor for a node or nodes, just add VisitHandlers<> to the list
    // any node type not handled by the visitor will default to visiting its children
    NodeVisitor visitor = new NodeVisitor(
            new VisitHandler<>(Text.class, this::visit)
    );

    public void visit(Text text) {
        // This is called for all Text nodes. Override other visit handlers for other node types.
        wordCount += text.getChars().unescape().split("\\W+").length;

        // Descending into children
        visitor.visitChildren(text);

        // Count words (this is just an example, don't actually do it this way for various reasons).
    }

    void countWords() {
        Parser parser = Parser.builder().build();
        Node document = parser.parse("Example\n=======\n\nSome more text");
        visitor.visit(document);

        System.out.println(wordCount);  // 4
    }
}

Customize HTML attributes via Attribute Provider

Source: AttributeProviderSample.java

package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.ast.AutoLink;
import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.html.AttributeProvider;
import com.vladsch.flexmark.html.AttributeProviderFactory;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.html.IndependentAttributeProviderFactory;
import com.vladsch.flexmark.html.renderer.AttributablePart;
import com.vladsch.flexmark.html.renderer.NodeRendererContext;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.html.Attributes;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;import org.jetbrains.annotations.NotNull;

import java.util.Arrays;import java.util.Collections;

public class AttributeProviderSample {
    static class SampleExtension implements HtmlRenderer.HtmlRendererExtension {
        @Override
        public void rendererOptions(@NotNull final MutableDataHolder options) {
            // add any configuration settings to options you want to apply to everything, here
        }

        @Override
        public void extend(final HtmlRenderer.Builder rendererBuilder, @NotNull final String rendererType) {
            rendererBuilder.attributeProviderFactory(SampleAttributeProvider.Factory());
        }

        static SampleExtension create() {
            return new SampleExtension();
        }
    }

    static class SampleAttributeProvider implements AttributeProvider {
        @Override
        public void setAttributes(@NotNull final Node node, @NotNull final AttributablePart part, @NotNull final Attributes attributes) {
            if (node instanceof AutoLink && part == AttributablePart.LINK) {
                // Put info in custom attribute instead
                attributes.replaceValue("class", "my-autolink-class");
            }
        }

        static AttributeProviderFactory Factory() {
            return new IndependentAttributeProviderFactory() {
                @Override
                public AttributeProvider create(NodeRendererContext context) {
                    //noinspection ReturnOfInnerClass
                    return new SampleAttributeProvider();
                }
            };
        }
    }

    static String commonMark(String markdown) {
        MutableDataHolder options = new MutableDataSet();
        options.set(Parser.EXTENSIONS, Collections.singletonList(new Extension[] { AutolinkExtension.create(), SampleExtension.create() }));

        // change soft break to hard break
        options.set(HtmlRenderer.SOFT_BREAK, "<br/>");

        Parser parser = Parser.builder(options).build();
        Node document = parser.parse(markdown);
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();
        final String html = renderer.render(document);
        return html;
    }

    public static void main(String[] args) {
        String html = commonMark("http://github.com/vsch/flexmark-java");
        System.out.println(html); // output: <p><a href="http://github.com/vsch/flexmark-java" class="my-autolink-class">http://github.com/vsch/flexmark-java</a></p>

        html = commonMark("hello\nworld");
        System.out.println(html); // output: <p>hello<br/>world</p>
    }
}

Include Markdown and HTML File Content

Source: JekyllIncludeFileSample.java

Jekyll tag extension can be used to include content using the {% include file %} syntax. Although the extension does not actually include the file since this operation is application dependent, it does provide everything necessary to allow your application to easily add this functionality.

In the code below, a hash map is used for file to file content mapping where you would include your own code to resolve the included file name to actual content and determine whether the file requires markdown conversion to HTML or should be included as is.

Markdown include sample is effectively:

  • Included File test.md

    ## Included Heading
    
    Included paragraph
    
    [ref]: http://example.com
  • Main Document

    http://github.com/vsch/flexmark-java
    
    [ref]
    
    {% include test.md %}
  • Rendered Html

    <p><a href="http://github.com/vsch/flexmark-java">http://github.com/vsch/flexmark-java</a></p>
    <p><a href="http://example.com">ref</a></p>
    <h2>Included Heading</h2>
    <p>Included paragraph</p>

Raw content include sample is effectively:

  • Included File test.html

    <p>some text</p>
  • Main Document

    http://github.com/vsch/flexmark-java
    
    [ref]
    
    {% include test.html %}
  • Rendered Html

    <p><a href="http://github.com/vsch/flexmark-java">http://github.com/vsch/flexmark-java</a></p>
    <p>[ref]</p>
    <p>some text</p>
package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.ext.autolink.AutolinkExtension;
import com.vladsch.flexmark.ext.jekyll.tag.JekyllTag;
import com.vladsch.flexmark.ext.jekyll.tag.JekyllTagExtension;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.util.ast.Document;
import com.vladsch.flexmark.util.data.MutableDataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class JekyllIncludeFileSample {
    static String commonMark(String markdown, Map<String, String> included) {
        MutableDataHolder options = new MutableDataSet();
        options.set(Parser.EXTENSIONS, Collections.singletonList(new Extension[] { AutolinkExtension.create(), JekyllTagExtension.create() }));

        // change soft break to hard break
        options.set(HtmlRenderer.SOFT_BREAK, "<br/>");

        Parser parser = Parser.builder(options).build();
        HtmlRenderer renderer = HtmlRenderer.builder(options).build();

        Document document = parser.parse(markdown);

        // see if document has includes
        Document doc = document;
        if (doc.contains(JekyllTagExtension.TAG_LIST)) {
            List<JekyllTag> tagList = JekyllTagExtension.TAG_LIST.get(doc);
            Map<String, String> includeHtmlMap = new HashMap<String, String>();

            for (JekyllTag tag : tagList) {
                String includeFile = tag.getParameters().toString();
                if (tag.getTag().equals("include") && !includeFile.isEmpty() && !includeHtmlMap.containsKey(includeFile)) {
                    // see if it exists
                    if (included.containsKey(includeFile)) {
                        // have the file
                        String text = included.get(includeFile);

                        if (includeFile.endsWith(".md")) {
                            Document includeDoc = parser.parse(text);
                            String includeHtml = renderer.render(includeDoc);
                            includeHtmlMap.put(includeFile, includeHtml);

                            // copy any definition of reference elements from included file to our document
                            parser.transferReferences(doc, includeDoc, null);
                        } else {
                            includeHtmlMap.put(includeFile, text);
                        }
                    }
                }

                if (!includeHtmlMap.isEmpty()) {
                    doc.set(JekyllTagExtension.INCLUDED_HTML, includeHtmlMap);
                }
            }
        }

        String html = renderer.render(document);
        return html;
    }

    public static void main(String[] args) {
        Map<String, String> included = new HashMap<>();
        included.put("test.md", "## Included Heading\n" +
                "\n" +
                "Included paragraph\n" +
                "\n" +
                "[ref]: http://example.com\n" +
                "");

        included.put("test.html", "<p>some text</p>\n" +
                "");

        String html = commonMark("http://github.com/vsch/flexmark-java\n" +
                "\n" +
                "[ref]\n" +
                "\n" +
                "{% include test.md %}\n" +
                "\n" +
                "", included);
        System.out.println(html);

        html = commonMark("http://github.com/vsch/flexmark-java\n" +
                "\n" +
                "[ref]\n" +
                "\n" +
                "{% include test.html %}\n" +
                "\n" +
                "", included);
        System.out.println(html);
    }
}

Render AST as Markdown with Formatting Options

Source: PegdownToCommonMark.java.

The flexmark-formatter module renders the AST as markdown with various formatting options to clean up and make the source consistent. This also comes with an API to allow extensions to provide formatting options and and handle rendering of markdown for custom nodes.

The Formatter class is a renderer that outputs markdown and formats it to specified options. Use it in place of HtmlRenderer to get formatted markdown. It can also be used to convert indentations from one ParserEmulationProfile to another:

package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.formatter.Formatter;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.profile.pegdown.Extensions;
import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter;
import com.vladsch.flexmark.util.data.DataHolder;
import com.vladsch.flexmark.util.data.MutableDataSet;

public class PegdownToCommonMark {
     final private static DataHolder OPTIONS = PegdownOptionsAdapter.flexmarkOptions(
            Extensions.ALL
    );

    static final MutableDataSet FORMAT_OPTIONS = new MutableDataSet();
    static {
        // copy extensions from Pegdown compatible to Formatting
        FORMAT_OPTIONS.set(Parser.EXTENSIONS, Parser.EXTENSIONS.get(OPTIONS));
    }

    static final Parser PARSER = Parser.builder(OPTIONS).build();
    static final Formatter RENDERER = Formatter.builder(FORMAT_OPTIONS).build();

    // use the PARSER to parse and RENDERER to parse pegdown indentation rules and render CommonMark

    public static void main(String[] args) {
        final String pegdown = "#Heading\n" +
                "-----\n" +
                "paragraph text \n" +
                "lazy continuation\n" +
                "\n" +
                "* list item\n" +
                "    > block quote\n" +
                "    lazy continuation\n" +
                "\n" +
                "~~~info\n" +
                "        with uneven indent\n" +
                "           with uneven indent\n" +
                "     indented code\n" +
                "~~~ \n" +
                "\n" +
                "        with uneven indent\n" +
                "           with uneven indent\n" +
                "     indented code\n" +
                "1. numbered item 1   \n" +
                "1. numbered item 2   \n" +
                "1. numbered item 3   \n" +
                "    - bullet item 1   \n" +
                "    - bullet item 2   \n" +
                "    - bullet item 3   \n" +
                "        1. numbered sub-item 1   \n" +
                "        1. numbered sub-item 2   \n" +
                "        1. numbered sub-item 3   \n" +
                "    \n" +
                "    ~~~info\n" +
                "            with uneven indent\n" +
                "               with uneven indent\n" +
                "         indented code\n" +
                "    ~~~ \n" +
                "    \n" +
                "            with uneven indent\n" +
                "               with uneven indent\n" +
                "         indented code\n" +
                "";
        System.out.println("pegdown\n");
        System.out.println(pegdown);

        Node document = PARSER.parse(pegdown);
        String commonmark = RENDERER.render(document);

        System.out.println("\n\nCommonMark\n");
        System.out.println(commonmark);
    }
}

will convert pegdown 4 space indent to CommonMark list item text column indent.

Pegdown Input
#Heading
-----
paragraph text
lazy continuation

* list item
    > block quote
    lazy continuation

~~~info
        with uneven indent
           with uneven indent
     indented code
~~~

        with uneven indent
           with uneven indent
     indented code
1. numbered item 1
1. numbered item 2
1. numbered item 3
    - bullet item 1
    - bullet item 2
    - bullet item 3
        1. numbered sub-item 1
        1. numbered sub-item 2
        1. numbered sub-item 3

    ~~~info
            with uneven indent
               with uneven indent
         indented code
    ~~~

            with uneven indent
               with uneven indent
         indented code
CommonMark Output

Converted to CommonMark indents, ATX heading spaces added, blank lines added, fenced and indented code indents minimized:

# Heading

-----

paragraph text
lazy continuation

* list item

  > block quote
  > lazy continuation

~~~info
   with uneven indent
      with uneven indent
indented code
~~~

       with uneven indent
          with uneven indent
    indented code

1. numbered item 1
2. numbered item 2
3. numbered item 3
   - bullet item 1
   - bullet item 2
   - bullet item 3
     1. numbered sub-item 1
     2. numbered sub-item 2
     3. numbered sub-item 3

   ~~~info
      with uneven indent
         with uneven indent
   indented code
   ~~~

          with uneven indent
             with uneven indent
       indented code

Render HTML to PDF

Source: PdfConverter.java.

The flexmark-pdf-converter module renders HTML to PDF using Open HTML To PDF

package com.vladsch.flexmark.samples;

import com.vladsch.flexmark.util.ast.Node;
import com.vladsch.flexmark.html.HtmlRenderer;
import com.vladsch.flexmark.parser.Parser;
import com.vladsch.flexmark.parent.PdfConverterExtension;
import com.vladsch.flexmark.profile.pegdown.Extensions;
import com.vladsch.flexmark.profile.pegdown.PegdownOptionsAdapter;
import com.vladsch.flexmark.util.data.DataHolder;
import org.apache.commons.io.IOUtils;

import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;

public class PdfConverter {
    static final DataHolder OPTIONS = PegdownOptionsAdapter.flexmarkOptions(
            Extensions.ALL & ~(Extensions.ANCHORLINKS | Extensions.EXTANCHORLINKS_WRAP)
    ).toImmutable();

    static String getResourceFileContent(String resourcePath) {
        StringWriter writer = new StringWriter();
        getResourceFileContent(writer, resourcePath);
        return writer.toString();
    }

    private static void getResourceFileContent(final StringWriter writer, final String resourcePath) {
        InputStream inputStream = com.vladsch.flexmark.java.samples.PdfConverter.class.getResourceAsStream(resourcePath);
        try {
            IOUtils.copy(inputStream, writer, "UTF-8");
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        final String pegdown = "#Heading\n" +
                "-----\n" +
                "paragraph text \n" +
                "lazy continuation\n" +
                "\n" +
                "* list item\n" +
                "    > block quote\n" +
                "    lazy continuation\n" +
                "\n" +
                "~~~info\n" +
                "   with uneven indent\n" +
                "      with uneven indent\n" +
                "indented code\n" +
                "~~~ \n" +
                "\n" +
                "       with uneven indent\n" +
                "          with uneven indent\n" +
                "    indented code\n" +
                "1. numbered item 1   \n" +
                "1. numbered item 2   \n" +
                "1. numbered item 3   \n" +
                "    - bullet item 1   \n" +
                "    - bullet item 2   \n" +
                "    - bullet item 3   \n" +
                "        1. numbered sub-item 1   \n" +
                "        1. numbered sub-item 2   \n" +
                "        1. numbered sub-item 3   \n" +
                "    \n" +
                "    ~~~info\n" +
                "       with uneven indent\n" +
                "          with uneven indent\n" +
                "    indented code\n" +
                "    ~~~ \n" +
                "    \n" +
                "           with uneven indent\n" +
                "              with uneven indent\n" +
                "        indented code\n" +
                "";
        System.out.println("pegdown\n");
        System.out.println(pegdown);

        final Parser PARSER = Parser.builder(OPTIONS).build();
        final HtmlRenderer RENDERER = HtmlRenderer.builder(OPTIONS).build();

        Node document = PARSER.parse(pegdown);
        String html = RENDERER.render(document);

        html = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\">\n" +
         "\n" +  // add your stylesheets, scripts styles etc.
         "</head><body>" + html + "\n" +
         "</body></html>";

        PdfConverterExtension.exportToPdf("~/flexmark-java.pdf", html,"", OPTIONS);
    }
}
Pegdown Markdown Input
#Heading
-----
paragraph text
lazy continuation

* list item
    > block quote
    lazy continuation

~~~info
   with uneven indent
      with uneven indent
indented code
~~~

       with uneven indent
          with uneven indent
    indented code
1. numbered item 1
1. numbered item 2
1. numbered item 3
    - bullet item 1
    - bullet item 2
    - bullet item 3
        1. numbered sub-item 1
        1. numbered sub-item 2
        1. numbered sub-item 3

    ~~~info
       with uneven indent
          with uneven indent
    indented code
    ~~~

           with uneven indent
              with uneven indent
        indented code
PDF output

flexmark-java.pdf

flexmark-java-pdf image

See PDF Renderer Converter for details on rendering non-latin character sets.