LCOV - code coverage report
Current view: top level - server/documentation - MarkdownFormatter.java (source / functions) Hit Total Coverage
Test: _coverage_report.dat Lines: 0 74 0.0 %
Date: 2022-11-19 15:00:39 Functions: 0 11 0.0 %

          Line data    Source code
       1             : // Copyright (C) 2012 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 static com.vladsch.flexmark.profiles.pegdown.Extensions.ALL;
      18             : import static com.vladsch.flexmark.profiles.pegdown.Extensions.HARDWRAPS;
      19             : import static com.vladsch.flexmark.profiles.pegdown.Extensions.SUPPRESS_ALL_HTML;
      20             : import static java.nio.charset.StandardCharsets.UTF_8;
      21             : 
      22             : import com.google.common.base.Strings;
      23             : import com.google.common.flogger.FluentLogger;
      24             : import com.google.gerrit.common.Nullable;
      25             : import com.vladsch.flexmark.ast.Heading;
      26             : import com.vladsch.flexmark.ast.util.TextCollectingVisitor;
      27             : import com.vladsch.flexmark.html.HtmlRenderer;
      28             : import com.vladsch.flexmark.parser.Parser;
      29             : import com.vladsch.flexmark.profiles.pegdown.PegdownOptionsAdapter;
      30             : import com.vladsch.flexmark.util.ast.Block;
      31             : import com.vladsch.flexmark.util.ast.Node;
      32             : import com.vladsch.flexmark.util.data.MutableDataHolder;
      33             : import java.io.FileNotFoundException;
      34             : import java.io.IOException;
      35             : import java.io.InputStream;
      36             : import java.io.UnsupportedEncodingException;
      37             : import java.net.URL;
      38             : import java.nio.charset.Charset;
      39             : import java.util.concurrent.atomic.AtomicBoolean;
      40             : import org.apache.commons.text.StringEscapeUtils;
      41             : import org.eclipse.jgit.util.RawParseUtils;
      42             : import org.eclipse.jgit.util.TemporaryBuffer;
      43             : 
      44           0 : public class MarkdownFormatter {
      45           0 :   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
      46             : 
      47             :   private static final String defaultCss;
      48             : 
      49             :   static {
      50           0 :     AtomicBoolean file = new AtomicBoolean();
      51             :     String src;
      52             :     try {
      53           0 :       src = readFlexMarkJavaCss(file);
      54           0 :     } catch (IOException err) {
      55           0 :       logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
      56           0 :       src = "";
      57           0 :     }
      58           0 :     defaultCss = file.get() ? null : src;
      59           0 :   }
      60             : 
      61             :   private static String readCSS() {
      62           0 :     if (defaultCss != null) {
      63           0 :       return defaultCss;
      64             :     }
      65             :     try {
      66           0 :       return readFlexMarkJavaCss(new AtomicBoolean());
      67           0 :     } catch (IOException err) {
      68           0 :       logger.atWarning().withCause(err).log("Cannot load flexmark-java.css");
      69           0 :       return "";
      70             :     }
      71             :   }
      72             : 
      73             :   private boolean suppressHtml;
      74             :   private String css;
      75             : 
      76             :   public MarkdownFormatter suppressHtml() {
      77           0 :     suppressHtml = true;
      78           0 :     return this;
      79             :   }
      80             : 
      81             :   public MarkdownFormatter setCss(String css) {
      82           0 :     this.css = StringEscapeUtils.escapeHtml4(css);
      83           0 :     return this;
      84             :   }
      85             : 
      86             :   private MutableDataHolder markDownOptions() {
      87           0 :     int options = ALL & ~HARDWRAPS;
      88           0 :     if (suppressHtml) {
      89           0 :       options |= SUPPRESS_ALL_HTML;
      90             :     }
      91             : 
      92           0 :     MutableDataHolder optionsExt =
      93           0 :         PegdownOptionsAdapter.flexmarkOptions(
      94           0 :                 options, MarkdownFormatterHeader.HeadingExtension.create())
      95           0 :             .toMutable();
      96             : 
      97           0 :     return optionsExt;
      98             :   }
      99             : 
     100             :   public byte[] markdownToDocHtml(String md, String charEnc) throws UnsupportedEncodingException {
     101           0 :     Node root = parseMarkdown(md);
     102           0 :     HtmlRenderer renderer = HtmlRenderer.builder(markDownOptions()).build();
     103           0 :     String title = findTitle(root);
     104             : 
     105           0 :     StringBuilder html = new StringBuilder();
     106           0 :     html.append("<html>");
     107           0 :     html.append("<head>");
     108           0 :     if (!Strings.isNullOrEmpty(title)) {
     109           0 :       html.append("<title>").append(title).append("</title>");
     110             :     }
     111           0 :     html.append("<style type=\"text/css\">\n");
     112           0 :     if (css != null) {
     113           0 :       html.append(css);
     114             :     } else {
     115           0 :       html.append(readCSS());
     116             :     }
     117           0 :     html.append("\n</style>");
     118           0 :     html.append("</head>");
     119           0 :     html.append("<body>\n");
     120           0 :     html.append(renderer.render(root));
     121           0 :     html.append("\n</body></html>");
     122           0 :     return html.toString().getBytes(charEnc);
     123             :   }
     124             : 
     125             :   public String extractTitleFromMarkdown(byte[] data, String charEnc) {
     126           0 :     String md = RawParseUtils.decode(Charset.forName(charEnc), data);
     127           0 :     return findTitle(parseMarkdown(md));
     128             :   }
     129             : 
     130             :   @Nullable
     131             :   private String findTitle(Node root) {
     132           0 :     if (root instanceof Heading) {
     133           0 :       Heading h = (Heading) root;
     134           0 :       if (h.getLevel() == 1 && h.hasChildren()) {
     135           0 :         TextCollectingVisitor collectingVisitor = new TextCollectingVisitor();
     136           0 :         return collectingVisitor.collectAndGetText(h);
     137             :       }
     138             :     }
     139             : 
     140           0 :     if (root instanceof Block && root.hasChildren()) {
     141           0 :       Node child = root.getFirstChild();
     142           0 :       while (child != null) {
     143           0 :         String title = findTitle(child);
     144           0 :         if (title != null) {
     145           0 :           return title;
     146             :         }
     147           0 :         child = child.getNext();
     148           0 :       }
     149             :     }
     150             : 
     151           0 :     return null;
     152             :   }
     153             : 
     154             :   private Node parseMarkdown(String md) {
     155           0 :     Parser parser = Parser.builder(markDownOptions()).build();
     156           0 :     Node document = parser.parse(md);
     157           0 :     return document;
     158             :   }
     159             : 
     160             :   private static String readFlexMarkJavaCss(AtomicBoolean file) throws IOException {
     161           0 :     String name = "flexmark-java.css";
     162           0 :     URL url = MarkdownFormatter.class.getResource(name);
     163           0 :     if (url == null) {
     164           0 :       throw new FileNotFoundException("Resource " + name);
     165             :     }
     166           0 :     file.set("file".equals(url.getProtocol()));
     167           0 :     try (InputStream in = url.openStream();
     168           0 :         TemporaryBuffer.Heap tmp = new TemporaryBuffer.Heap(128 * 1024)) {
     169           0 :       tmp.copy(in);
     170           0 :       return new String(tmp.toByteArray(), UTF_8);
     171             :     }
     172             :   }
     173             : }

Generated by: LCOV version 1.16+git.20220603.dfeb750