Browse Source

Automatic Table Of Contents Generator

- Generate a Table of contentes automatically
- just put the tag '__TOC__' in your markdown file and gitblit will automatically generate a table of contents based in your headings
- Make the anchor between the table of contents and the heading using HTML anchor mechanism
pull/1281/head
Yuri Campolongo 6 years ago
parent
commit
dc921ce46e

+ 55
- 0
src/main/java/com/gitblit/markdown/ModifyModel.java View File

@@ -0,0 +1,55 @@
package com.gitblit.markdown;

/**
* Created by Yuriy Aizenberg
*/
public class ModifyModel {

private static final String PATTERN = "%s- [%s](#%s)";
private static final String AFFIX = "   ";

private int currentDeepLevel = 1;
private String headerName;
private String headerLink;


public ModifyModel(int currentDeepLevel, String headerName, String headerLink) {
this.currentDeepLevel = currentDeepLevel;
this.headerName = headerName;
this.headerLink = headerLink;
}

public int getCurrentDeepLevel() {
return currentDeepLevel;
}

public void setCurrentDeepLevel(int currentDeepLevel) {
this.currentDeepLevel = currentDeepLevel;
}

public String getHeaderName() {
return headerName;
}

public void setHeaderName(String headerName) {
this.headerName = headerName;
}

public String getHeaderLink() {
return headerLink;
}

public void setHeaderLink(String headerLink) {
this.headerLink = headerLink;
}

public String create() {
String affixs = "";
if (currentDeepLevel > 1) {
for (int i = 0; i < currentDeepLevel - 1; i++) {
affixs += AFFIX;
}
}
return String.format(PATTERN, affixs, headerName, headerLink);
}
}

+ 31
- 0
src/main/java/com/gitblit/markdown/ParameterUtils.java View File

@@ -0,0 +1,31 @@
package com.gitblit.markdown;

/**
* Created by Yuriy Aizenberg
*/
public class ParameterUtils {

public static Integer extractInteger(String key, Integer defValue) {
if (Utils.isEmpty(key)) return defValue;
try {
return Integer.valueOf(key);
} catch (NumberFormatException e) {
return defValue;
}
}

public static Integer extractInteger(String key) {
return extractInteger(key, null);
}

public static Boolean extractBoolean(String key, Boolean defValue) {
if (Utils.isEmpty(key)) return defValue;
return Boolean.valueOf(key);
}

public static Boolean extractBoolean(String key) {
return extractBoolean(key, null);
}


}

+ 97
- 0
src/main/java/com/gitblit/markdown/TableOfContentsGenerator.java View File

@@ -0,0 +1,97 @@
package com.gitblit.markdown;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class TableOfContentsGenerator {

public static final String DEFAULT_HEADER = "#";
public static final char DEFAULT_HEADER_CHAR = '#';
public static final String ALT_1_HEADER = "=";
public static final String ALT_2_HEADER = "-";

public static final String TOC_HEADER = "## Table of contents";

private String markdown;
private List<ModifyModel> rootModels = new ArrayList<ModifyModel>();

public TableOfContentsGenerator(String markdown) {
this.markdown = markdown;
}

public String start() {

if (!this.markdown.contains("__TOC__")) {
return this.markdown;
}

int patternLineNumber = -1;
int currentLine = 0;
String[] items = markdown.split("\n");
List<String> fileContent = new ArrayList<String>(Arrays.asList(items));

String previousLine = null;

for (String line : fileContent) {
++currentLine;
if (line.startsWith(DEFAULT_HEADER)) {
String trim = line.trim();

int count = getCount(trim);
if (count < 1 || count > 6) {
previousLine = line;
continue;
}
String headerName = line.substring(count);
rootModels.add(new ModifyModel(count, headerName, Utils.normalize(headerName)));
} else if (line.startsWith(ALT_1_HEADER) && !Utils.isEmpty(previousLine)) {
if (line.replaceAll(ALT_1_HEADER, "").isEmpty()) {
rootModels.add(new ModifyModel(1, previousLine, Utils.normalize(previousLine)));
}
} else if (line.startsWith(ALT_2_HEADER) && !Utils.isEmpty(previousLine)) {
if (line.replaceAll(ALT_2_HEADER, "").isEmpty()) {
rootModels.add(new ModifyModel(2, previousLine, Utils.normalize(previousLine)));
}
} else if (line.trim().equals("__TOC__")) {
patternLineNumber = currentLine;
}
previousLine = line;
}

return getMarkdown(fileContent, patternLineNumber).replaceAll("__TOC__", "");

}

private String getMarkdown(List<String> fileContent, int patternLineNumber) {
StringBuilder writer = new StringBuilder();
if (patternLineNumber == -1) {
System.out.println("Pattern for replace not found!");
return "";
}
fileContent.add(patternLineNumber - 1, TOC_HEADER);
for (ModifyModel modifyModel : rootModels) {
fileContent.add(patternLineNumber, modifyModel.create());
patternLineNumber++;
}

for (String line : fileContent) {
writer.append(line).append("\n");
}

return writer.toString();
}

private int getCount(String string) {
int count = 0;
for (int i = 0; i < string.length(); i++) {
if (string.charAt(i) == DEFAULT_HEADER_CHAR) {
++count;
} else {
break;
}
}
return count;
}

}

+ 63
- 0
src/main/java/com/gitblit/markdown/Utils.java View File

@@ -0,0 +1,63 @@
package com.gitblit.markdown;

import java.io.*;

/**
* Created by Yuriy Aizenberg
*/
public class Utils {

private static final String SPACES = " ";
private static final String CODES = "%([abcdef]|\\d){2,2}";
private static final String SPECIAL_CHARS = "[\\/?!:\\[\\]`.,()*\"';{}+=<>~\\$|#]";
private static final String DASH = "-";
private static final String EMPTY = "";


public static boolean checkSourceFile(String fileName) {
if (isEmpty(fileName)) return false;
File sourceFile = new File(fileName);
return sourceFile.exists() && sourceFile.canRead() && sourceFile.isFile();
}

public static boolean isEmpty(String string) {
return string == null || string.isEmpty();
}

public static int getFileLines(String filePath, int defFaultValue) {
LineNumberReader lineNumberReader = null;
FileReader fileReader = null;
try {
fileReader = new FileReader(filePath);
lineNumberReader = new LineNumberReader(fileReader);
lineNumberReader.skip(Long.MAX_VALUE);
return lineNumberReader.getLineNumber() + 1;
} catch (IOException ignored) {
} finally {
closeStream(lineNumberReader, fileReader);
}
return defFaultValue;
}

public static void closeStream(Closeable... closeable) {
for (Closeable c : closeable) {
if (c != null) {
try {
c.close();
} catch (IOException ignored) {
}
}
}
}

public static String normalize(final String taintedURL) {
return taintedURL
.trim()

.replaceAll(SPACES, DASH)

.replaceAll(CODES, EMPTY)

.replaceAll(SPECIAL_CHARS, EMPTY).toLowerCase();
}
}

+ 1
- 0
src/main/java/com/gitblit/utils/JSoupXssFilter.java View File

@@ -81,6 +81,7 @@ public class JSoupXssFilter implements XssFilter {
.addAttributes("img", "align", "alt", "height", "src", "title", "width")
.addAttributes("ol", "start", "type")
.addAttributes("q", "cite")
.addAttributes(":all", "id")
.addAttributes("span", "class", "style")
.addAttributes("table", "class", "style", "summary", "width")
.addAttributes("td", "abbr", "axis", "class", "colspan", "rowspan", "style", "width")

+ 28
- 1
src/main/java/com/gitblit/wicket/MarkupProcessor.java View File

@@ -28,6 +28,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.wicket.Page;
import org.apache.wicket.RequestCycle;
@@ -55,6 +57,8 @@ import org.slf4j.LoggerFactory;

import com.gitblit.IStoredSettings;
import com.gitblit.Keys;
import com.gitblit.markdown.TableOfContentsGenerator;
import com.gitblit.markdown.Utils;
import com.gitblit.models.PathModel;
import com.gitblit.servlet.RawServlet;
import com.gitblit.utils.JGitUtils;
@@ -358,11 +362,34 @@ public class MarkupProcessor {
}
};

final String content = MarkdownUtils.transformMarkdown(doc.markup, renderer);
String markdown = new TableOfContentsGenerator(doc.markup).start();
String content = MarkdownUtils.transformMarkdown(markdown, renderer);

content = generateHeaderIds(content);
final String safeContent = xssFilter.relaxed(content);

doc.html = safeContent;
}
private String generateHeaderIds(String content) {
Pattern insideHeader = Pattern.compile("<h\\d>(.*?)<\\/h\\d>");
Matcher m = insideHeader.matcher(content);

while (m.find()) {
String id = Utils.normalize(m.group().replaceAll("<h\\d>", "").replaceAll("<\\/h\\d>", ""));
Pattern iniTag = Pattern.compile("<h\\d");
Matcher lookTag = iniTag.matcher(m.group());
String tag = "";
if (lookTag.find()) {
tag = lookTag.group();
}
String finalTag = m.group().replace(tag, tag + " id=\"" + id + "\"");

content = content.replace(m.group(), finalTag);
}
return content;
}

private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {
String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/");

Loading…
Cancel
Save