From dc7c2f650de99c7f9ae8d6c049f419fcd00fb2a2 Mon Sep 17 00:00:00 2001
From: James Moger <james.moger@gitblit.com>
Date: Tue, 12 Nov 2013 16:12:59 -0500
Subject: [PATCH] Refactor markup processing in preparation for supporting other formats

---
 src/main/java/com/gitblit/wicket/pages/RepositoryPage.java |   45 ----
 src/main/java/com/gitblit/wicket/GitBlitWebApp.java        |    6 
 src/main/java/com/gitblit/PagesServlet.java                |   26 +-
 src/main/java/com/gitblit/wicket/pages/DocsPage.java       |   25 +-
 src/main/java/com/gitblit/wicket/pages/BlobPage.java       |    8 
 /dev/null                                                  |  105 ---------
 src/main/java/com/gitblit/wicket/pages/DocPage.java        |   92 ++++++++
 src/main/java/com/gitblit/wicket/pages/SummaryPage.java    |   59 +----
 src/main/resources/gitblit.css                             |   23 ++
 src/main/java/com/gitblit/wicket/WicketUtils.java          |   10 
 src/main/java/com/gitblit/wicket/pages/DocPage.html        |    6 
 src/main/java/com/gitblit/utils/StringUtils.java           |   14 +
 src/main/java/com/gitblit/wicket/MarkupProcessor.java      |  216 +++++++++++++++++++
 13 files changed, 398 insertions(+), 237 deletions(-)

diff --git a/src/main/java/com/gitblit/PagesServlet.java b/src/main/java/com/gitblit/PagesServlet.java
index ce4239c..e121f8a 100644
--- a/src/main/java/com/gitblit/PagesServlet.java
+++ b/src/main/java/com/gitblit/PagesServlet.java
@@ -38,6 +38,8 @@
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
 
 /**
  * Serves the content of a gh-pages branch.
@@ -142,21 +144,20 @@
 				return;
 			}
 
+			MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
 			String [] encodings = GitBlit.getEncodings();
 
 			RevTree tree = commit.getTree();
 			byte[] content = null;
 			if (StringUtils.isEmpty(resource)) {
 				// find resource
-				List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-				List<String> extensions = new ArrayList<String>(markdownExtensions.size() + 2);
+				List<String> extensions = new ArrayList<String>(processor.getMarkupExtensions());
 				extensions.add("html");
 				extensions.add("htm");
-				extensions.addAll(markdownExtensions);
-				for (String ext : extensions){
+				for (String ext : extensions) {
 					String file = "index." + ext;
 					String stringContent = JGitUtils.getStringContent(r, tree, file, encodings);
-					if(stringContent == null){
+					if (stringContent == null) {
 						continue;
 					}
 					content = stringContent.getBytes(Constants.ENCODING);
@@ -213,14 +214,13 @@
 				return;
 			}
 
-			// check to see if we should transform markdown files
-			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
-				if (resource.endsWith(ext)) {
-					String mkd = new String(content, Constants.ENCODING);
-					content = MarkdownUtils.transformMarkdown(mkd).getBytes(Constants.ENCODING);
-					response.setContentType("text/html; charset=" + Constants.ENCODING);
-					break;
-				}
+			// check to see if we should transform markup files
+			String ext = StringUtils.getFileExtension(resource);
+			if (processor.getMarkupExtensions().contains(ext)) {
+				String markup = new String(content, Constants.ENCODING);
+				MarkupDocument markupDoc = processor.parse(repository, commit.getName(), resource, markup);
+				content = markupDoc.html.getBytes("UTF-8");
+				response.setContentType("text/html; charset=" + Constants.ENCODING);
 			}
 
 			try {
diff --git a/src/main/java/com/gitblit/utils/StringUtils.java b/src/main/java/com/gitblit/utils/StringUtils.java
index 5e62778..e18bdc4 100644
--- a/src/main/java/com/gitblit/utils/StringUtils.java
+++ b/src/main/java/com/gitblit/utils/StringUtils.java
@@ -556,6 +556,20 @@
 	}
 
 	/**
+	 * Returns the file extension of a path.
+	 *
+	 * @param path
+	 * @return a blank string or a file extension
+	 */
+	public static String stripFileExtension(String path) {
+		int lastDot = path.lastIndexOf('.');
+		if (lastDot > -1) {
+			return path.substring(0, lastDot);
+		}
+		return path;
+	}
+
+	/**
 	 * Replace all occurences of a substring within a string with
 	 * another string.
 	 *
diff --git a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
index fa02c4f..9adbb94 100644
--- a/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
+++ b/src/main/java/com/gitblit/wicket/GitBlitWebApp.java
@@ -39,6 +39,7 @@
 import com.gitblit.wicket.pages.CommitDiffPage;
 import com.gitblit.wicket.pages.CommitPage;
 import com.gitblit.wicket.pages.ComparePage;
+import com.gitblit.wicket.pages.DocPage;
 import com.gitblit.wicket.pages.DocsPage;
 import com.gitblit.wicket.pages.FederationRegistrationPage;
 import com.gitblit.wicket.pages.ForkPage;
@@ -49,7 +50,6 @@
 import com.gitblit.wicket.pages.LogPage;
 import com.gitblit.wicket.pages.LogoutPage;
 import com.gitblit.wicket.pages.LuceneSearchPage;
-import com.gitblit.wicket.pages.MarkdownPage;
 import com.gitblit.wicket.pages.MetricsPage;
 import com.gitblit.wicket.pages.MyDashboardPage;
 import com.gitblit.wicket.pages.OverviewPage;
@@ -121,9 +121,9 @@
 		mount("/users", UsersPage.class);
 		mount("/logout", LogoutPage.class);
 
-		// setup the markdown urls
+		// setup the markup document urls
 		mount("/docs", DocsPage.class, "r");
-		mount("/markdown", MarkdownPage.class, "r", "h", "f");
+		mount("/doc", DocPage.class, "r", "h", "f");
 
 		// federation urls
 		mount("/proposal", ReviewProposalPage.class, "t");
diff --git a/src/main/java/com/gitblit/wicket/MarkupProcessor.java b/src/main/java/com/gitblit/wicket/MarkupProcessor.java
new file mode 100644
index 0000000..4b4bee6
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/MarkupProcessor.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright 2013 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket;
+
+import java.io.Serializable;
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.wicket.Page;
+import org.apache.wicket.RequestCycle;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.pegdown.LinkRenderer;
+import org.pegdown.ast.WikiLinkNode;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.gitblit.IStoredSettings;
+import com.gitblit.Keys;
+import com.gitblit.models.PathModel;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.MarkdownUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.pages.DocPage;
+
+/**
+ * Processes markup content and generates html with repository-relative page and
+ * image linking.
+ *
+ * @author James Moger
+ *
+ */
+public class MarkupProcessor {
+
+	public enum MarkupSyntax {
+		PLAIN, MARKDOWN
+	}
+
+	private Logger logger = LoggerFactory.getLogger(getClass());
+
+	private final IStoredSettings settings;
+
+	public MarkupProcessor(IStoredSettings settings) {
+		this.settings = settings;
+	}
+
+	public List<String> getMarkupExtensions() {
+		List<String> list = new ArrayList<String>();
+		list.addAll(settings.getStrings(Keys.web.markdownExtensions));
+		return list;
+	}
+
+	private MarkupSyntax determineSyntax(String documentPath) {
+		String ext = StringUtils.getFileExtension(documentPath).toLowerCase();
+		if (StringUtils.isEmpty(ext)) {
+			return MarkupSyntax.PLAIN;
+		}
+
+		if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {
+			return MarkupSyntax.MARKDOWN;
+		}
+
+		return MarkupSyntax.PLAIN;
+	}
+
+	public MarkupDocument parseReadme(Repository r, String repositoryName, String commitId) {
+		String readme = null;
+		RevCommit commit = JGitUtils.getCommit(r, commitId);
+		List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
+		for (PathModel path : paths) {
+			if (!path.isTree()) {
+				String name = path.name.toLowerCase();
+				if (name.equals("readme") || name.equals("readme.txt")) {
+					readme = path.name;
+					break;
+				} else if (name.startsWith("readme.")) {
+					String ext = StringUtils.getFileExtension(name).toLowerCase();
+					if (getMarkupExtensions().contains(ext)) {
+						readme = path.name;
+						break;
+					}
+				}
+			}
+		}
+
+		if (!StringUtils.isEmpty(readme)) {
+			String [] encodings = settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
+			String markup = JGitUtils.getStringContent(r, commit.getTree(), readme, encodings);
+			return parse(repositoryName, commitId, readme, markup);
+		}
+
+		return null;
+	}
+
+	public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {
+		final MarkupSyntax syntax = determineSyntax(documentPath);
+		final MarkupDocument doc = new MarkupDocument(documentPath, markupText, syntax);
+
+		if (markupText != null) {
+			try {
+				switch (syntax){
+				case MARKDOWN:
+					parse(doc, repositoryName, commitId);
+					break;
+				default:
+					doc.html = MarkdownUtils.transformPlainText(markupText);
+					break;
+				}
+			} catch (Exception e) {
+				logger.error("failed to transform " + syntax, e);
+			}
+		}
+
+		if (doc.html == null) {
+			// failed to transform markup
+			if (markupText == null) {
+				markupText = String.format("Document <b>%1$s</b> not found in <em>%2$s</em>", documentPath, repositoryName);
+			}
+			markupText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", "Error", "failed to parse markup", markupText);
+			doc.html = StringUtils.breakLinesForHtml(markupText);
+		}
+
+		return doc;
+	}
+
+	/**
+	 * Parses the document as Markdown using Pegdown.
+	 *
+	 * @param doc
+	 * @param repositoryName
+	 * @param commitId
+	 */
+	private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) {
+		LinkRenderer renderer = new LinkRenderer() {
+			@Override
+			public Rendering render(WikiLinkNode node) {
+				String path = doc.getRelativePath(node.getText());
+				String name = getDocumentName(path);
+				String url = getWicketUrl(DocPage.class, repositoryName, commitId, path);
+				return new Rendering(url, name);
+			}
+		};
+		doc.html = MarkdownUtils.transformMarkdown(doc.markup, renderer);
+	}
+
+	private String getWicketUrl(Class<? extends Page> pageClass, final String repositoryName, final String commitId, final String document) {
+		String fsc = settings.getString(Keys.web.forwardSlashCharacter, "/");
+		String encodedPath = document.replace(' ', '-');
+		try {
+			encodedPath = URLEncoder.encode(encodedPath, "UTF-8");
+		} catch (UnsupportedEncodingException e) {
+			logger.error(null, e);
+		}
+		encodedPath = encodedPath.replace("/", fsc).replace("%2F", fsc);
+
+		String url = RequestCycle.get().urlFor(pageClass, WicketUtils.newPathParameter(repositoryName, commitId, encodedPath)).toString();
+		return url;
+	}
+
+	private String getDocumentName(final String document) {
+		// extract document name
+		String name = StringUtils.stripFileExtension(document);
+		name = name.replace('_', ' ');
+		if (name.indexOf('/') > -1) {
+			name = name.substring(name.lastIndexOf('/') + 1);
+		}
+		return name;
+	}
+
+	public static class MarkupDocument implements Serializable {
+
+		private static final long serialVersionUID = 1L;
+
+		public final String documentPath;
+		public final String markup;
+		public final MarkupSyntax syntax;
+		public String html;
+
+		MarkupDocument(String documentPath, String markup, MarkupSyntax syntax) {
+			this.documentPath = documentPath;
+			this.markup = markup;
+			this.syntax = syntax;
+		}
+
+		String getCurrentPath() {
+			String basePath = "";
+			if (documentPath.indexOf('/') > -1) {
+				basePath = documentPath.substring(0, documentPath.lastIndexOf('/') + 1);
+				if (basePath.charAt(0) == '/') {
+					return basePath.substring(1);
+				}
+			}
+			return basePath;
+		}
+
+		String getRelativePath(String ref) {
+			return ref.charAt(0) == '/' ? ref.substring(1) : (getCurrentPath() + ref);
+		}
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/WicketUtils.java b/src/main/java/com/gitblit/wicket/WicketUtils.java
index a2522ef..6e3d932 100644
--- a/src/main/java/com/gitblit/wicket/WicketUtils.java
+++ b/src/main/java/com/gitblit/wicket/WicketUtils.java
@@ -21,7 +21,6 @@
 import java.util.Collection;
 import java.util.Date;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.TimeZone;
 
@@ -190,11 +189,10 @@
 			return newImage(wicketId, "file_settings_16x16.png");
 		}
 
-		List<String> mdExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-		for (String ext : mdExtensions) {
-			if (filename.endsWith('.' + ext.toLowerCase())) {
-				return newImage(wicketId, "file_world_16x16.png");
-			}
+		MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
+		String ext = StringUtils.getFileExtension(filename).toLowerCase();
+		if (processor.getMarkupExtensions().contains(ext)) {
+			return newImage(wicketId, "file_world_16x16.png");
 		}
 		return newImage(wicketId, "file_16x16.png");
 	}
diff --git a/src/main/java/com/gitblit/wicket/pages/BlobPage.java b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
index 0c93d48..56ad5b9 100644
--- a/src/main/java/com/gitblit/wicket/pages/BlobPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/BlobPage.java
@@ -35,6 +35,7 @@
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.ExternalImage;
+import com.gitblit.wicket.MarkupProcessor;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.CommitHeaderPanel;
 import com.gitblit.wicket.panels.PathBreadcrumbsPanel;
@@ -73,10 +74,11 @@
 				extension = blobPath.substring(blobPath.lastIndexOf('.') + 1).toLowerCase();
 			}
 
-			// see if we should redirect to the markdown page
-			for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
+			// see if we should redirect to the doc page
+			MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
+			for (String ext : processor.getMarkupExtensions()) {
 				if (ext.equals(extension)) {
-					setResponsePage(MarkdownPage.class, params);
+					setResponsePage(DocPage.class, params);
 					return;
 				}
 			}
diff --git a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.html b/src/main/java/com/gitblit/wicket/pages/DocPage.html
similarity index 80%
rename from src/main/java/com/gitblit/wicket/pages/MarkdownPage.html
rename to src/main/java/com/gitblit/wicket/pages/DocPage.html
index 7900625..beb8e4d 100644
--- a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.html
+++ b/src/main/java/com/gitblit/wicket/pages/DocPage.html
@@ -6,13 +6,13 @@
 
 <body>
 <wicket:extend>
-		<!-- markdown nav links -->	
+		<!-- doc nav links -->	
 		<div class="page_nav2">
 			<a wicket:id="blameLink"><wicket:message key="gb.blame"></wicket:message></a>  | <a wicket:id="historyLink"><wicket:message key="gb.history"></wicket:message></a> | <a wicket:id="rawLink"><wicket:message key="gb.raw"></wicket:message></a> | <a wicket:id="headLink"><wicket:message key="gb.head"></wicket:message></a>
 		</div>	
 	
-		<!--  markdown content -->
-		<div class="markdown" style="padding-bottom:5px;" wicket:id="markdownText">[markdown content]</div>
+		<!--  document content -->
+		<div class="markdown" style="padding-bottom:5px;" wicket:id="content">[content]</div>
 </wicket:extend>
 </body>
 </html>
\ No newline at end of file
diff --git a/src/main/java/com/gitblit/wicket/pages/DocPage.java b/src/main/java/com/gitblit/wicket/pages/DocPage.java
new file mode 100644
index 0000000..2c1308a
--- /dev/null
+++ b/src/main/java/com/gitblit/wicket/pages/DocPage.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2011 gitblit.com.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.gitblit.wicket.pages;
+
+import java.util.List;
+
+import org.apache.wicket.PageParameters;
+import org.apache.wicket.markup.html.basic.Label;
+import org.apache.wicket.markup.html.link.BookmarkablePageLink;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import com.gitblit.GitBlit;
+import com.gitblit.utils.JGitUtils;
+import com.gitblit.utils.StringUtils;
+import com.gitblit.wicket.CacheControl;
+import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
+import com.gitblit.wicket.WicketUtils;
+
+@CacheControl(LastModified.BOOT)
+public class DocPage extends RepositoryPage {
+
+	public DocPage(PageParameters params) {
+		super(params);
+
+		final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
+		MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
+
+		Repository r = getRepository();
+		RevCommit commit = JGitUtils.getCommit(r, objectId);
+		String [] encodings = GitBlit.getEncodings();
+
+		// Read raw markup content and transform it to html
+		String documentPath = path;
+		String markupText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);
+
+		// Hunt for document
+		if (StringUtils.isEmpty(markupText)) {
+			String name = StringUtils.stripFileExtension(path);
+
+			List<String> docExtensions = processor.getMarkupExtensions();
+			for (String ext : docExtensions) {
+				String checkName = name + "." + ext;
+				markupText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
+				if (!StringUtils.isEmpty(markupText)) {
+					// found it
+					documentPath = path;
+					break;
+				}
+			}
+		}
+
+		// document page links
+		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
+		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
+				WicketUtils.newPathParameter(repositoryName, objectId, documentPath)));
+		add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
+				repositoryName, objectId, documentPath)));
+		add(new BookmarkablePageLink<Void>("headLink", DocPage.class,
+				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, documentPath)));
+
+		MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(commit), documentPath, markupText);
+		add(new Label("content", markupDoc.html).setEscapeModelStrings(false));
+	}
+
+	@Override
+	protected String getPageName() {
+		return getString("gb.docs");
+	}
+
+	@Override
+	protected Class<? extends BasePage> getRepoNavPageClass() {
+		return DocsPage.class;
+	}
+}
diff --git a/src/main/java/com/gitblit/wicket/pages/DocsPage.java b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
index 2634ea8..37ecb35 100644
--- a/src/main/java/com/gitblit/wicket/pages/DocsPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/DocsPage.java
@@ -30,14 +30,14 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import com.gitblit.GitBlit;
-import com.gitblit.Keys;
 import com.gitblit.models.PathModel;
 import com.gitblit.utils.ByteFormat;
 import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.panels.LinkPanel;
 
@@ -47,13 +47,15 @@
 	public DocsPage(PageParameters params) {
 		super(params);
 
+		MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
+
 		Repository r = getRepository();
 		RevCommit head = JGitUtils.getCommit(r, null);
-		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
+		List<String> extensions = processor.getMarkupExtensions();
 		List<PathModel> paths = JGitUtils.getDocuments(r, extensions);
 
 		String doc = null;
-		String markdown = null;
+		String markup = null;
 		String html = null;
 
 		List<String> roots = Arrays.asList("home");
@@ -61,9 +63,7 @@
 		// try to find a custom index/root page
 		for (PathModel path : paths) {
 			String name = path.name.toLowerCase();
-			if (name.indexOf('.') > -1) {
-				name = name.substring(0, name.lastIndexOf('.'));
-			}
+			name = StringUtils.stripFileExtension(name);
 			if (roots.contains(name)) {
 				doc = path.name;
 				break;
@@ -73,8 +73,11 @@
 		if (!StringUtils.isEmpty(doc)) {
 			// load the document
 			String [] encodings = GitBlit.getEncodings();
-			markdown = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
-			html = MarkdownUtils.transformMarkdown(markdown, getMarkdownLinkRenderer());
+			markup = JGitUtils.getStringContent(r, head.getTree(), doc, encodings);
+
+			// parse document
+			MarkupDocument markupDoc = processor.parse(repositoryName, getBestCommitId(head), doc, markup);
+			html = markupDoc.html;
 		}
 
 		Fragment fragment = null;
@@ -103,11 +106,11 @@
 				PathModel entry = item.getModelObject();
 				item.add(WicketUtils.newImage("docIcon", "file_world_16x16.png"));
 				item.add(new Label("docSize", byteFormat.format(entry.size)));
-				item.add(new LinkPanel("docName", "list", entry.name, MarkdownPage.class, WicketUtils
+				item.add(new LinkPanel("docName", "list", entry.name, DocPage.class, WicketUtils
 						.newPathParameter(repositoryName, id, entry.path)));
 
 				// links
-				item.add(new BookmarkablePageLink<Void>("view", MarkdownPage.class, WicketUtils
+				item.add(new BookmarkablePageLink<Void>("view", DocPage.class, WicketUtils
 						.newPathParameter(repositoryName, id, entry.path)));
 				item.add(new BookmarkablePageLink<Void>("raw", RawPage.class, WicketUtils
 						.newPathParameter(repositoryName, id, entry.path)));
diff --git a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java b/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
deleted file mode 100644
index 0034984..0000000
--- a/src/main/java/com/gitblit/wicket/pages/MarkdownPage.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * Copyright 2011 gitblit.com.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *     http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.gitblit.wicket.pages;
-
-import java.text.MessageFormat;
-import java.util.List;
-
-import org.apache.wicket.PageParameters;
-import org.apache.wicket.markup.html.basic.Label;
-import org.apache.wicket.markup.html.link.BookmarkablePageLink;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-
-import com.gitblit.GitBlit;
-import com.gitblit.Keys;
-import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
-import com.gitblit.utils.StringUtils;
-import com.gitblit.wicket.CacheControl;
-import com.gitblit.wicket.CacheControl.LastModified;
-import com.gitblit.wicket.WicketUtils;
-
-@CacheControl(LastModified.BOOT)
-public class MarkdownPage extends RepositoryPage {
-
-	public MarkdownPage(PageParameters params) {
-		super(params);
-
-		final String path = WicketUtils.getPath(params).replace("%2f", "/").replace("%2F", "/");
-
-		Repository r = getRepository();
-		RevCommit commit = JGitUtils.getCommit(r, objectId);
-		String [] encodings = GitBlit.getEncodings();
-		List<String> extensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-
-		// Read raw markdown content and transform it to html
-		String markdownPath = path;
-		String markdownText = JGitUtils.getStringContent(r, commit.getTree(), path, encodings);
-		if (StringUtils.isEmpty(markdownText)) {
-			String name = path;
-			if (path.indexOf('.') > -1) {
-				name = path.substring(0, path.lastIndexOf('.'));
-			}
-
-			for (String ext : extensions) {
-				String checkName = name + "." + ext;
-				markdownText = JGitUtils.getStringContent(r, commit.getTree(), checkName, encodings);
-				if (!StringUtils.isEmpty(markdownText)) {
-					// found it
-					markdownPath = path;
-					break;
-				}
-			}
-		}
-
-		// markdown page links
-		add(new BookmarkablePageLink<Void>("blameLink", BlamePage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("historyLink", HistoryPage.class,
-				WicketUtils.newPathParameter(repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("rawLink", RawPage.class, WicketUtils.newPathParameter(
-				repositoryName, objectId, markdownPath)));
-		add(new BookmarkablePageLink<Void>("headLink", MarkdownPage.class,
-				WicketUtils.newPathParameter(repositoryName, Constants.HEAD, markdownPath)));
-
-		String htmlText;
-		try {
-			htmlText = MarkdownUtils.transformMarkdown(markdownText, getMarkdownLinkRenderer());
-		} catch (Exception e) {
-			logger.error("failed to transform markdown", e);
-			if (markdownText == null) {
-				markdownText = String.format("Markdown document <b>%1$s</b> not found in <em>%2$s</em>", markdownPath, repositoryName);
-			}
-			markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
-			htmlText = StringUtils.breakLinesForHtml(markdownText);
-		}
-
-		// Add the html to the page
-		add(new Label("markdownText", htmlText).setEscapeModelStrings(false));
-	}
-
-	@Override
-	protected String getPageName() {
-		return getString("gb.markdown");
-	}
-
-	@Override
-	protected Class<? extends BasePage> getRepoNavPageClass() {
-		return DocsPage.class;
-	}
-}
diff --git a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
index bc7dfa9..d0d801e 100644
--- a/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/RepositoryPage.java
@@ -16,8 +16,6 @@
 package com.gitblit.wicket.pages;
 
 import java.io.Serializable;
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -44,8 +42,6 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.pegdown.LinkRenderer;
-import org.pegdown.ast.WikiLinkNode;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -645,47 +641,6 @@
 
 	public boolean isOwner() {
 		return isOwner;
-	}
-
-	/**
-	 * Returns a Pegdown/Markdown link renderer which renders WikiLinks.
-	 *
-	 * @return a link renderer
-	 */
-	protected LinkRenderer getMarkdownLinkRenderer() {
-		RevCommit head = JGitUtils.getCommit(r, "HEAD");
-		final String id = getBestCommitId(head);
-		LinkRenderer renderer = new LinkRenderer() {
-			@Override
-			public Rendering render(WikiLinkNode node) {
-				try {
-					String fsc = GitBlit.getString(Keys.web.forwardSlashCharacter, "/");
-					// adjust the request path
-					String path = node.getText().charAt(0) == '/' ? node.getText().substring(1) : node.getText();
-					path = URLEncoder.encode(path.replace(' ', '-'), "UTF-8").replace("%2F", fsc);
-
-					// extract document name
-					String name = node.getText().replace('_', ' ');
-					if (name.indexOf('/') > -1) {
-						name = name.substring(name.lastIndexOf('/') + 1);
-					}
-
-					// strip Markdown extension
-					for (String ext : GitBlit.getStrings(Keys.web.markdownExtensions)) {
-						String x = "." + ext;
-						if (name.endsWith(x)) {
-							name = name.substring(0, name.length() - x.length());
-							break;
-						}
-					}
-					String url = urlFor(MarkdownPage.class, WicketUtils.newPathParameter(repositoryName, id, path)).toString();
-					return new Rendering(url, name);
-				} catch (UnsupportedEncodingException e) {
-					throw new IllegalStateException();
-				}
-			}
-		};
-		return renderer;
 	}
 
 	private class SearchForm extends SessionlessForm<Void> implements Serializable {
diff --git a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
index 3ea71d6..872f038 100644
--- a/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
+++ b/src/main/java/com/gitblit/wicket/pages/SummaryPage.java
@@ -43,15 +43,16 @@
 import com.gitblit.GitBlit;
 import com.gitblit.Keys;
 import com.gitblit.models.Metric;
-import com.gitblit.models.PathModel;
 import com.gitblit.models.RepositoryModel;
 import com.gitblit.models.UserModel;
 import com.gitblit.utils.JGitUtils;
-import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.CacheControl;
 import com.gitblit.wicket.CacheControl.LastModified;
 import com.gitblit.wicket.GitBlitWebSession;
+import com.gitblit.wicket.MarkupProcessor;
+import com.gitblit.wicket.MarkupProcessor.MarkupDocument;
+import com.gitblit.wicket.MarkupProcessor.MarkupSyntax;
 import com.gitblit.wicket.WicketUtils;
 import com.gitblit.wicket.charting.SecureChart;
 import com.gitblit.wicket.panels.BranchesPanel;
@@ -137,55 +138,17 @@
 		add(new TagsPanel("tagsPanel", repositoryName, r, numberRefs).hideIfEmpty());
 		add(new BranchesPanel("branchesPanel", getRepositoryModel(), r, numberRefs, false).hideIfEmpty());
 
-		String htmlText = null;
-		String markdownText = null;
-		String readme = null;
-		boolean isMarkdown = false;
-		try {
-			RevCommit head = JGitUtils.getCommit(r, null);
-			List<String> markdownExtensions = GitBlit.getStrings(Keys.web.markdownExtensions);
-			List<PathModel> paths = JGitUtils.getFilesInPath(r, null, head);
-			for (PathModel path : paths) {
-				if (!path.isTree()) {
-					String name = path.name.toLowerCase();
-					if (name.equals("readme") || name.equals("readme.txt")) {
-						readme = path.name;
-						isMarkdown = false;
-					} else if (name.startsWith("readme")) {
-						if (name.indexOf('.') > -1) {
-							String ext = name.substring(name.lastIndexOf('.') + 1);
-							if (markdownExtensions.contains(ext)) {
-								readme = path.name;
-								isMarkdown = true;
-								break;
-							}
-						}
-					}
-				}
-			}
-			if (!StringUtils.isEmpty(readme)) {
-				String [] encodings = GitBlit.getEncodings();
-				markdownText = JGitUtils.getStringContent(r, head.getTree(), readme, encodings);
-				if (isMarkdown) {
-					htmlText = MarkdownUtils.transformMarkdown(markdownText, getMarkdownLinkRenderer());
-				} else {
-					htmlText = MarkdownUtils.transformPlainText(markdownText);
-				}
-			}
-		} catch (Exception e) {
-			logger.error("failed to transform markdown", e);
-			markdownText = MessageFormat.format("<div class=\"alert alert-error\"><strong>{0}:</strong> {1}</div>{2}", getString("gb.error"), getString("gb.markdownFailure"), markdownText);
-			htmlText = MarkdownUtils.transformPlainText(markdownText);
-		}
-
-		if (StringUtils.isEmpty(htmlText)) {
+		RevCommit head = JGitUtils.getCommit(r, null);
+		MarkupProcessor processor = new MarkupProcessor(GitBlit.getSettings());
+		MarkupDocument markupDoc = processor.parseReadme(r, repositoryName, getBestCommitId(head));
+		if (markupDoc.markup == null) {
 			add(new Label("readme").setVisible(false));
 		} else {
-			Fragment fragment = new Fragment("readme", isMarkdown ? "markdownPanel" : "plaintextPanel", this);
-			fragment.add(new Label("readmeFile", readme));
+			Fragment fragment = new Fragment("readme", MarkupSyntax.PLAIN.equals(markupDoc.syntax) ? "plaintextPanel" : "markdownPanel", this);
+			fragment.add(new Label("readmeFile", markupDoc.documentPath));
 			// Add the html to the page
-			Component content = new Label("readmeContent", htmlText).setEscapeModelStrings(false);
-			fragment.add(content.setVisible(!StringUtils.isEmpty(htmlText)));
+			Component content = new Label("readmeContent", markupDoc.html).setEscapeModelStrings(false);
+			fragment.add(content.setVisible(!StringUtils.isEmpty(markupDoc.html)));
 			add(fragment);
 		}
 
diff --git a/src/main/resources/gitblit.css b/src/main/resources/gitblit.css
index 91bd4aa..9e75fa6 100644
--- a/src/main/resources/gitblit.css
+++ b/src/main/resources/gitblit.css
@@ -1540,6 +1540,29 @@
 	text-decoration: underline;	
 }
 
+div.markdown table {
+	max-width: 100%;
+	background-color: transparent;
+	border-collapse: collapse;
+	border-spacing: 0px;
+	font-size: inherit;
+	border-width: 0px 1px 1px 0px;
+    border-style: solid solid solid none;
+    border-color: rgb(221, 221, 221);
+    border-image: none;
+    border-collapse: separate;
+    margin: 10px 0px 20px;
+}
+
+div.markdown table td, div.markdown table th {
+	padding: 8px;
+	line-height: 20px;
+	text-align: left;
+	vertical-align: top;
+	border-top: 1px solid rgb(221, 221, 221);
+	border-left: 1px solid rgb(221, 221, 221);
+}
+
 div.markdown table.text th, div.markdown table.text td {
 	vertical-align: top;
 	border-top: 1px solid #ccc;

--
Gitblit v1.9.1