Line data Source code
1 : // Copyright (C) 2018 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.documentation; 16 : 17 : import com.vladsch.flexmark.ast.Heading; 18 : import com.vladsch.flexmark.ext.anchorlink.AnchorLink; 19 : import com.vladsch.flexmark.ext.anchorlink.internal.AnchorLinkNodeRenderer; 20 : import com.vladsch.flexmark.html.HtmlRenderer; 21 : import com.vladsch.flexmark.html.HtmlRenderer.HtmlRendererExtension; 22 : import com.vladsch.flexmark.html.HtmlWriter; 23 : import com.vladsch.flexmark.html.renderer.DelegatingNodeRendererFactory; 24 : import com.vladsch.flexmark.html.renderer.NodeRenderer; 25 : import com.vladsch.flexmark.html.renderer.NodeRendererContext; 26 : import com.vladsch.flexmark.html.renderer.NodeRendererFactory; 27 : import com.vladsch.flexmark.html.renderer.NodeRenderingHandler; 28 : import com.vladsch.flexmark.profiles.pegdown.Extensions; 29 : import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter; 30 : import com.vladsch.flexmark.util.ast.Node; 31 : import com.vladsch.flexmark.util.data.DataHolder; 32 : import com.vladsch.flexmark.util.data.MutableDataHolder; 33 : import java.util.Arrays; 34 : import java.util.HashSet; 35 : import java.util.Set; 36 : 37 0 : public class MarkdownFormatterHeader { 38 0 : static class HeadingExtension implements HtmlRendererExtension { 39 : @Override 40 : public void rendererOptions(final MutableDataHolder options) { 41 : // add any configuration settings to options you want to apply to everything, here 42 0 : } 43 : 44 : @Override 45 : public void extend(final HtmlRenderer.Builder rendererBuilder, final String rendererType) { 46 0 : rendererBuilder.nodeRendererFactory(new HeadingNodeRenderer.Factory()); 47 0 : } 48 : 49 : static HeadingExtension create() { 50 0 : return new HeadingExtension(); 51 : } 52 : } 53 : 54 : static class HeadingNodeRenderer implements NodeRenderer { 55 0 : public HeadingNodeRenderer() {} 56 : 57 : @Override 58 : public Set<NodeRenderingHandler<?>> getNodeRenderingHandlers() { 59 0 : return new HashSet<>( 60 0 : Arrays.asList( 61 : new NodeRenderingHandler<>( 62 : AnchorLink.class, 63 0 : (node, context, html) -> HeadingNodeRenderer.this.render(node, context)), 64 : new NodeRenderingHandler<>(Heading.class, HeadingNodeRenderer.this::render))); 65 : } 66 : 67 : void render(final AnchorLink node, final NodeRendererContext context) { 68 0 : Node parent = node.getParent(); 69 : 70 0 : if (parent instanceof Heading && ((Heading) parent).getLevel() == 1) { 71 : // render without anchor link 72 0 : context.renderChildren(node); 73 : } else { 74 0 : context.delegateRender(); 75 : } 76 0 : } 77 : 78 : static boolean haveExtension(int extensions, int flags) { 79 0 : return (extensions & flags) != 0; 80 : } 81 : 82 : static boolean haveAllExtensions(int extensions, int flags) { 83 0 : return (extensions & flags) == flags; 84 : } 85 : 86 : void render(final Heading node, final NodeRendererContext context, final HtmlWriter html) { 87 0 : if (node.getLevel() == 1) { 88 : // render without anchor link 89 0 : final int extensions = context.getOptions().get(PegdownOptionsAdapter.PEGDOWN_EXTENSIONS); 90 0 : if (context.getHtmlOptions().renderHeaderId 91 0 : || haveExtension(extensions, Extensions.ANCHORLINKS) 92 0 : || haveAllExtensions( 93 : extensions, Extensions.EXTANCHORLINKS | Extensions.EXTANCHORLINKS_WRAP)) { 94 0 : String id = context.getNodeId(node); 95 0 : if (id != null) { 96 0 : html.attr("id", id); 97 : } 98 : } 99 : 100 0 : if (context.getHtmlOptions().sourcePositionParagraphLines) { 101 0 : html.srcPos(node.getChars()) 102 0 : .withAttr() 103 0 : .tagLine( 104 0 : "h" + node.getLevel(), 105 : () -> { 106 0 : html.srcPos(node.getText()).withAttr().tag("span"); 107 0 : context.renderChildren(node); 108 0 : html.tag("/span"); 109 0 : }); 110 : } else { 111 0 : html.srcPos(node.getText()) 112 0 : .withAttr() 113 0 : .tagLine("h" + node.getLevel(), () -> context.renderChildren(node)); 114 : } 115 0 : } else { 116 0 : context.delegateRender(); 117 : } 118 0 : } 119 : 120 0 : public static class Factory implements DelegatingNodeRendererFactory { 121 : @Override 122 : public NodeRenderer apply(final DataHolder options) { 123 0 : return new HeadingNodeRenderer(); 124 : } 125 : 126 : @Override 127 : public Set<Class<? extends NodeRendererFactory>> getDelegates() { 128 0 : Set<Class<? extends NodeRendererFactory>> delegates = new HashSet<>(); 129 0 : delegates.add(AnchorLinkNodeRenderer.Factory.class); 130 0 : return delegates; 131 : } 132 : } 133 : } 134 : }