Switch from pegdown to flexmark-java
The pegdown project is no longer maintained and has been deprecated in favour of flexmark [1]. Furthermore, flexmark has more features than pegdown and has better performance [2]. [1] https://github.com/sirthias/pegdown [2] https://github.com/vsch/flexmark-java#feature-comparison Change-Id: Ie3c20a7fffb0f6917bd3de353a9d7b46eeaa818f
This commit is contained in:
@@ -14,30 +14,33 @@
|
||||
|
||||
package com.google.gerrit.server.documentation;
|
||||
|
||||
import static com.vladsch.flexmark.profiles.pegdown.Extensions.ALL;
|
||||
import static com.vladsch.flexmark.profiles.pegdown.Extensions.HARDWRAPS;
|
||||
import static com.vladsch.flexmark.profiles.pegdown.Extensions.SUPPRESS_ALL_HTML;
|
||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
||||
import static org.pegdown.Extensions.ALL;
|
||||
import static org.pegdown.Extensions.HARDWRAPS;
|
||||
import static org.pegdown.Extensions.SUPPRESS_ALL_HTML;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.vladsch.flexmark.Extension;
|
||||
import com.vladsch.flexmark.ast.Block;
|
||||
import com.vladsch.flexmark.ast.Heading;
|
||||
import com.vladsch.flexmark.ast.Node;
|
||||
import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer;
|
||||
import com.vladsch.flexmark.parser.Parser;
|
||||
import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
|
||||
import com.vladsch.flexmark.util.options.MutableDataHolder;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.apache.commons.lang.StringEscapeUtils;
|
||||
import org.eclipse.jgit.util.RawParseUtils;
|
||||
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||
import org.pegdown.LinkRenderer;
|
||||
import org.pegdown.PegDownProcessor;
|
||||
import org.pegdown.ToHtmlSerializer;
|
||||
import org.pegdown.ast.HeaderNode;
|
||||
import org.pegdown.ast.Node;
|
||||
import org.pegdown.ast.RootNode;
|
||||
import org.pegdown.ast.TextNode;
|
||||
|
||||
public class MarkdownFormatter {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
@@ -48,9 +51,9 @@ public class MarkdownFormatter {
|
||||
AtomicBoolean file = new AtomicBoolean();
|
||||
String src;
|
||||
try {
|
||||
src = readPegdownCss(file);
|
||||
src = readFlexMarkJavaCss(file);
|
||||
} catch (IOException err) {
|
||||
logger.atWarning().withCause(err).log("Cannot load pegdown.css");
|
||||
logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
|
||||
src = "";
|
||||
}
|
||||
defaultCss = file.get() ? null : src;
|
||||
@@ -61,9 +64,9 @@ public class MarkdownFormatter {
|
||||
return defaultCss;
|
||||
}
|
||||
try {
|
||||
return readPegdownCss(new AtomicBoolean());
|
||||
return readFlexMarkJavaCss(new AtomicBoolean());
|
||||
} catch (IOException err) {
|
||||
logger.atWarning().withCause(err).log("Cannot load pegdown.css");
|
||||
logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
@@ -81,8 +84,28 @@ public class MarkdownFormatter {
|
||||
return this;
|
||||
}
|
||||
|
||||
private MutableDataHolder markDownOptions() {
|
||||
int options = ALL & ~(HARDWRAPS);
|
||||
if (suppressHtml) {
|
||||
options |= SUPPRESS_ALL_HTML;
|
||||
}
|
||||
|
||||
MutableDataHolder optionsExt =
|
||||
PegdownOptionsAdapter.flexmarkOptions(
|
||||
options, MarkdownFormatterHeader.HeadingExtension.create())
|
||||
.toMutable();
|
||||
|
||||
ArrayList<Extension> extensions = new ArrayList<>();
|
||||
for (Extension extension : optionsExt.get(com.vladsch.flexmark.parser.Parser.EXTENSIONS)) {
|
||||
extensions.add(extension);
|
||||
}
|
||||
|
||||
return optionsExt;
|
||||
}
|
||||
|
||||
public byte[] markdownToDocHtml(String md, String charEnc) throws UnsupportedEncodingException {
|
||||
RootNode root = parseMarkdown(md);
|
||||
Node root = parseMarkdown(md);
|
||||
HtmlRenderer renderer = HtmlRenderer.builder(markDownOptions()).build();
|
||||
String title = findTitle(root);
|
||||
|
||||
StringBuilder html = new StringBuilder();
|
||||
@@ -100,7 +123,7 @@ public class MarkdownFormatter {
|
||||
html.append("\n</style>");
|
||||
html.append("</head>");
|
||||
html.append("<body>\n");
|
||||
html.append(new ToHtmlSerializer(new LinkRenderer()).toHtml(root));
|
||||
html.append(renderer.render(root));
|
||||
html.append("\n</body></html>");
|
||||
return html.toString().getBytes(charEnc);
|
||||
}
|
||||
@@ -111,38 +134,36 @@ public class MarkdownFormatter {
|
||||
}
|
||||
|
||||
private String findTitle(Node root) {
|
||||
if (root instanceof HeaderNode) {
|
||||
HeaderNode h = (HeaderNode) root;
|
||||
if (h.getLevel() == 1 && h.getChildren() != null && !h.getChildren().isEmpty()) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (Node n : root.getChildren()) {
|
||||
if (n instanceof TextNode) {
|
||||
b.append(((TextNode) n).getText());
|
||||
}
|
||||
}
|
||||
return b.toString();
|
||||
if (root instanceof Heading) {
|
||||
Heading h = (Heading) root;
|
||||
if (h.getLevel() == 1 && h.hasChildren()) {
|
||||
TextCollectingVisitor collectingVisitor = new TextCollectingVisitor();
|
||||
return collectingVisitor.collectAndGetText(h);
|
||||
}
|
||||
}
|
||||
|
||||
for (Node n : root.getChildren()) {
|
||||
String title = findTitle(n);
|
||||
if (title != null) {
|
||||
return title;
|
||||
if (root instanceof Block && root.hasChildren()) {
|
||||
Node child = root.getFirstChild();
|
||||
while (child != null) {
|
||||
String title = findTitle(child);
|
||||
if (title != null) {
|
||||
return title;
|
||||
}
|
||||
child = child.getNext();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private RootNode parseMarkdown(String md) {
|
||||
int options = ALL & ~(HARDWRAPS);
|
||||
if (suppressHtml) {
|
||||
options |= SUPPRESS_ALL_HTML;
|
||||
}
|
||||
return new PegDownProcessor(options).parseMarkdown(md.toCharArray());
|
||||
private Node parseMarkdown(String md) {
|
||||
Parser parser = Parser.builder(markDownOptions()).build();
|
||||
Node document = parser.parse(md);
|
||||
return document;
|
||||
}
|
||||
|
||||
private static String readPegdownCss(AtomicBoolean file) throws IOException {
|
||||
String name = "pegdown.css";
|
||||
private static String readFlexMarkJavaCss(AtomicBoolean file) throws IOException {
|
||||
String name = "flexmark-java.css";
|
||||
URL url = MarkdownFormatter.class.getResource(name);
|
||||
if (url == null) {
|
||||
throw new FileNotFoundException("Resource " + name);
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
// Copyright (C) 2018 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.documentation;
|
||||
|
||||
import com.google.common.flogger.FluentLogger;
|
||||
import com.vladsch.flexmark.ast.Heading;
|
||||
import com.vladsch.flexmark.ast.Node;
|
||||
import com.vladsch.flexmark.ext.anchorlink.AnchorLink;
|
||||
import com.vladsch.flexmark.ext.anchorlink.internal.AnchorLinkNodeRenderer;
|
||||
import com.vladsch.flexmark.html.CustomNodeRenderer;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer;
|
||||
import com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension;
|
||||
import com.vladsch.flexmark.html.HtmlWriter;
|
||||
import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory;
|
||||
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.profiles.pegdown.Extensions;
|
||||
import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
|
||||
import com.vladsch.flexmark.util.options.DataHolder;
|
||||
import com.vladsch.flexmark.util.options.MutableDataHolder;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class MarkdownFormatterHeader {
|
||||
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
|
||||
|
||||
static class HeadingExtension implements HtmlRendererExtension {
|
||||
@Override
|
||||
public void rendererOptions(final MutableDataHolder options) {
|
||||
// add any configuration settings to options you want to apply to everything, here
|
||||
}
|
||||
|
||||
@Override
|
||||
public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) {
|
||||
rendererBuilder.nodeRendererFactory(new HeadingNodeRenderer.Factory());
|
||||
}
|
||||
|
||||
static HeadingExtension create() {
|
||||
return new HeadingExtension();
|
||||
}
|
||||
}
|
||||
|
||||
static class HeadingNodeRenderer implements NodeRenderer {
|
||||
public HeadingNodeRenderer(DataHolder options) {}
|
||||
|
||||
@Override
|
||||
public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() {
|
||||
return new HashSet<NodeRenderingHandler<? extends Node>>(
|
||||
Arrays.asList(
|
||||
new NodeRenderingHandler<AnchorLink>(
|
||||
AnchorLink.class,
|
||||
new CustomNodeRenderer<AnchorLink>() {
|
||||
@Override
|
||||
public void render(
|
||||
AnchorLink node, NodeRendererContext context, HtmlWriter html) {
|
||||
HeadingNodeRenderer.this.render(node, context, html);
|
||||
}
|
||||
}),
|
||||
new NodeRenderingHandler<Heading>(
|
||||
Heading.class,
|
||||
new CustomNodeRenderer<Heading>() {
|
||||
@Override
|
||||
public void render(Heading node, NodeRendererContext context, HtmlWriter html) {
|
||||
HeadingNodeRenderer.this.render(node, context, html);
|
||||
}
|
||||
})));
|
||||
}
|
||||
|
||||
void render(final AnchorLink node, final NodeRendererContext context, final HtmlWriter html) {
|
||||
Node parent = node.getParent();
|
||||
|
||||
if (parent instanceof Heading && ((Heading) parent).getLevel() == 1) {
|
||||
// render without anchor link
|
||||
context.renderChildren(node);
|
||||
} else {
|
||||
context.delegateRender();
|
||||
}
|
||||
}
|
||||
|
||||
static boolean haveExtension(int extensions, int flags) {
|
||||
return (extensions & flags) != 0;
|
||||
}
|
||||
|
||||
static boolean haveAllExtensions(int extensions, int flags) {
|
||||
return (extensions & flags) == flags;
|
||||
}
|
||||
|
||||
void render(final Heading node, final NodeRendererContext context, final HtmlWriter html) {
|
||||
if (node.getLevel() == 1) {
|
||||
// render without anchor link
|
||||
final int extensions = context.getOptions().get(PegdownOptionsAdapter.PEGDOWN_EXTENSIONS);
|
||||
if (context.getHtmlOptions().renderHeaderId
|
||||
|| haveExtension(extensions, Extensions.ANCHORLINKS)
|
||||
|| haveAllExtensions(
|
||||
extensions, Extensions.EXTANCHORLINKS | Extensions.EXTANCHORLINKS_WRAP)) {
|
||||
String id = context.getNodeId(node);
|
||||
if (id != null) {
|
||||
html.attr("id", id);
|
||||
}
|
||||
}
|
||||
|
||||
if (context.getHtmlOptions().sourcePositionParagraphLines) {
|
||||
html.srcPos(node.getChars())
|
||||
.withAttr()
|
||||
.tagLine(
|
||||
"h" + node.getLevel(),
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
html.srcPos(node.getText()).withAttr().tag("span");
|
||||
context.renderChildren(node);
|
||||
html.tag("/span");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
html.srcPos(node.getText())
|
||||
.withAttr()
|
||||
.tagLine(
|
||||
"h" + node.getLevel(),
|
||||
new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
context.renderChildren(node);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
context.delegateRender();
|
||||
}
|
||||
}
|
||||
|
||||
public static class Factory implements DelegatingNodeRendererFactory {
|
||||
@Override
|
||||
public NodeRenderer create(final DataHolder options) {
|
||||
return new HeadingNodeRenderer(options);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<Class<? extends NodeRendererFactory>> getDelegates() {
|
||||
Set<Class<? extends NodeRendererFactory>> delegates =
|
||||
new HashSet<Class<? extends NodeRendererFactory>>();
|
||||
delegates.add(AnchorLinkNodeRenderer.Factory.class);
|
||||
return delegates;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user