-
Notifications
You must be signed in to change notification settings - Fork 275
Usage
- Building via Gradle
- Building with Android Studio
- Parse and render to HTML
- Use a visitor to process parsed nodes
- Customize HTML attributes via Attribute Provider
- Include Markdown and HTML File Content
- Render AST as Markdown with Formatting Options
- Render HTML to PDF
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>
compile 'com.vladsch.flexmark:flexmark-all:0.64.0'
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.
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.
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
}
}
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>
}
}
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);
}
}
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.
#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
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
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);
}
}
#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
See PDF Renderer Converter for details on rendering non-latin character sets.