- 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 mechanismpull/1281/head
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); | |||||
} | |||||
} |
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); | |||||
} | |||||
} |
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; | |||||
} | |||||
} |
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(); | |||||
} | |||||
} |
.addAttributes("img", "align", "alt", "height", "src", "title", "width") | .addAttributes("img", "align", "alt", "height", "src", "title", "width") | ||||
.addAttributes("ol", "start", "type") | .addAttributes("ol", "start", "type") | ||||
.addAttributes("q", "cite") | .addAttributes("q", "cite") | ||||
.addAttributes(":all", "id") | |||||
.addAttributes("span", "class", "style") | .addAttributes("span", "class", "style") | ||||
.addAttributes("table", "class", "style", "summary", "width") | .addAttributes("table", "class", "style", "summary", "width") | ||||
.addAttributes("td", "abbr", "axis", "class", "colspan", "rowspan", "style", "width") | .addAttributes("td", "abbr", "axis", "class", "colspan", "rowspan", "style", "width") |
import java.util.HashMap; | import java.util.HashMap; | ||||
import java.util.List; | import java.util.List; | ||||
import java.util.Map; | import java.util.Map; | ||||
import java.util.regex.Matcher; | |||||
import java.util.regex.Pattern; | |||||
import org.apache.wicket.Page; | import org.apache.wicket.Page; | ||||
import org.apache.wicket.RequestCycle; | import org.apache.wicket.RequestCycle; | ||||
import com.gitblit.IStoredSettings; | import com.gitblit.IStoredSettings; | ||||
import com.gitblit.Keys; | import com.gitblit.Keys; | ||||
import com.gitblit.markdown.TableOfContentsGenerator; | |||||
import com.gitblit.markdown.Utils; | |||||
import com.gitblit.models.PathModel; | import com.gitblit.models.PathModel; | ||||
import com.gitblit.servlet.RawServlet; | import com.gitblit.servlet.RawServlet; | ||||
import com.gitblit.utils.JGitUtils; | import com.gitblit.utils.JGitUtils; | ||||
} | } | ||||
}; | }; | ||||
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); | final String safeContent = xssFilter.relaxed(content); | ||||
doc.html = safeContent; | 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) { | private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) { | ||||
String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/"); | String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/"); |