@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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") |
@@ -26,6 +26,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; | |||
@@ -389,11 +393,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 boolean isFullUrl(String url) | |||
{ |
@@ -69,7 +69,7 @@ public class DocsPage extends RepositoryPage { | |||
final String commitId = getBestCommitId(head); | |||
List<String> extensions = processor.getAllExtensions(); | |||
List<PathModel> paths = JGitUtils.getDocuments(r, extensions); | |||
List<PathModel> paths = JGitUtils.getDocuments(r, extensions, objectId); | |||
List<MarkupDocument> roots = processor.getRootDocs(r, repositoryName, commitId); | |||
Fragment fragment = null; |