| // 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.vladsch.flexmark.ast.Heading; |
| import com.vladsch.flexmark.ext.anchorlink.AnchorLink; |
| import com.vladsch.flexmark.ext.anchorlink.internal.AnchorLinkNodeRenderer; |
| 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.ast.Node; |
| import com.vladsch.flexmark.util.data.DataHolder; |
| import com.vladsch.flexmark.util.data.MutableDataHolder; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| public class MarkdownFormatterHeader { |
| 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() {} |
| |
| @Override |
| public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { |
| return new HashSet<>( |
| Arrays.asList( |
| new NodeRenderingHandler<>( |
| AnchorLink.class, |
| (node, context, html) -> HeadingNodeRenderer.this.render(node, context)), |
| new NodeRenderingHandler<>(Heading.class, HeadingNodeRenderer.this::render))); |
| } |
| |
| void render(final AnchorLink node, final NodeRendererContext context) { |
| 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(), |
| () -> { |
| html.srcPos(node.getText()).withAttr().tag("span"); |
| context.renderChildren(node); |
| html.tag("/span"); |
| }); |
| } else { |
| html.srcPos(node.getText()) |
| .withAttr() |
| .tagLine("h" + node.getLevel(), () -> context.renderChildren(node)); |
| } |
| } else { |
| context.delegateRender(); |
| } |
| } |
| |
| public static class Factory implements DelegatingNodeRendererFactory { |
| @Override |
| public NodeRenderer apply(final DataHolder options) { |
| return new HeadingNodeRenderer(); |
| } |
| |
| @Override |
| public Set<Class<? extends NodeRendererFactory>> getDelegates() { |
| Set<Class<? extends NodeRendererFactory>> delegates = new HashSet<>(); |
| delegates.add(AnchorLinkNodeRenderer.Factory.class); |
| return delegates; |
| } |
| } |
| } |
| } |