From f76fee63ed9cb3a30d3c0c092d860b1cb93a481b Mon Sep 17 00:00:00 2001
From: Gerard Smyth <gerard.smyth@gmail.com>
Date: Thu, 08 May 2014 13:09:30 -0400
Subject: [PATCH] Updated the SyndicationServlet to provide an additional option to return details of the tags in the repository instead of the commits. This uses a new 'ot' request parameter to indicate the object type of the content to return, which can be ither TAG or COMMIT. If this is not provided, then COMMIT is assumed to maintain backwards compatability. If tags are returned, then the paging parameters, 'l' and 'pg' are still supported, but searching options are currently ignored.

---
 src/main/java/com/gitblit/wicket/MarkupProcessor.java |  283 ++++++++++++++++++++++++++++++++++++++++++++++++++++----
 1 files changed, 262 insertions(+), 21 deletions(-)

diff --git a/src/main/java/com/gitblit/wicket/MarkupProcessor.java b/src/main/java/com/gitblit/wicket/MarkupProcessor.java
index 4b4bee6..e7681f2 100644
--- a/src/main/java/com/gitblit/wicket/MarkupProcessor.java
+++ b/src/main/java/com/gitblit/wicket/MarkupProcessor.java
@@ -15,29 +15,53 @@
  */
 package com.gitblit.wicket;
 
+import static org.pegdown.FastEncoder.encode;
+
 import java.io.Serializable;
+import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.text.MessageFormat;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.wicket.Page;
 import org.apache.wicket.RequestCycle;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.mylyn.wikitext.confluence.core.ConfluenceLanguage;
+import org.eclipse.mylyn.wikitext.core.parser.Attributes;
+import org.eclipse.mylyn.wikitext.core.parser.MarkupParser;
+import org.eclipse.mylyn.wikitext.core.parser.builder.HtmlDocumentBuilder;
+import org.eclipse.mylyn.wikitext.core.parser.markup.MarkupLanguage;
+import org.eclipse.mylyn.wikitext.mediawiki.core.MediaWikiLanguage;
+import org.eclipse.mylyn.wikitext.textile.core.TextileLanguage;
+import org.eclipse.mylyn.wikitext.tracwiki.core.TracWikiLanguage;
+import org.eclipse.mylyn.wikitext.twiki.core.TWikiLanguage;
+import org.pegdown.DefaultVerbatimSerializer;
 import org.pegdown.LinkRenderer;
+import org.pegdown.ToHtmlSerializer;
+import org.pegdown.VerbatimSerializer;
+import org.pegdown.ast.ExpImageNode;
+import org.pegdown.ast.RefImageNode;
 import org.pegdown.ast.WikiLinkNode;
+import org.pegdown.plugins.ToHtmlSerializerPlugin;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.gitblit.IStoredSettings;
 import com.gitblit.Keys;
 import com.gitblit.models.PathModel;
+import com.gitblit.servlet.RawServlet;
 import com.gitblit.utils.JGitUtils;
 import com.gitblit.utils.MarkdownUtils;
 import com.gitblit.utils.StringUtils;
 import com.gitblit.wicket.pages.DocPage;
+import com.google.common.base.Joiner;
 
 /**
  * Processes markup content and generates html with repository-relative page and
@@ -49,7 +73,7 @@
 public class MarkupProcessor {
 
 	public enum MarkupSyntax {
-		PLAIN, MARKDOWN
+		PLAIN, MARKDOWN, TWIKI, TRACWIKI, TEXTILE, MEDIAWIKI, CONFLUENCE
 	}
 
 	private Logger logger = LoggerFactory.getLogger(getClass());
@@ -62,8 +86,28 @@
 
 	public List<String> getMarkupExtensions() {
 		List<String> list = new ArrayList<String>();
+		list.addAll(settings.getStrings(Keys.web.confluenceExtensions));
 		list.addAll(settings.getStrings(Keys.web.markdownExtensions));
+		list.addAll(settings.getStrings(Keys.web.mediawikiExtensions));
+		list.addAll(settings.getStrings(Keys.web.textileExtensions));
+		list.addAll(settings.getStrings(Keys.web.tracwikiExtensions));
+		list.addAll(settings.getStrings(Keys.web.twikiExtensions));
 		return list;
+	}
+
+	public List<String> getAllExtensions() {
+		List<String> list = getMarkupExtensions();
+		list.add("txt");
+		list.add("TXT");
+		return list;
+	}
+
+	private List<String> getRoots() {
+		return settings.getStrings(Keys.web.documents);
+	}
+
+	private String [] getEncodings() {
+		return settings.getStrings(Keys.web.blobEncodings).toArray(new String[0]);
 	}
 
 	private MarkupSyntax determineSyntax(String documentPath) {
@@ -72,40 +116,84 @@
 			return MarkupSyntax.PLAIN;
 		}
 
-		if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {
+		if (settings.getStrings(Keys.web.confluenceExtensions).contains(ext)) {
+			return MarkupSyntax.CONFLUENCE;
+		} else if (settings.getStrings(Keys.web.markdownExtensions).contains(ext)) {
 			return MarkupSyntax.MARKDOWN;
+		} else if (settings.getStrings(Keys.web.mediawikiExtensions).contains(ext)) {
+			return MarkupSyntax.MEDIAWIKI;
+		} else if (settings.getStrings(Keys.web.textileExtensions).contains(ext)) {
+			return MarkupSyntax.TEXTILE;
+		} else if (settings.getStrings(Keys.web.tracwikiExtensions).contains(ext)) {
+			return MarkupSyntax.TRACWIKI;
+		} else if (settings.getStrings(Keys.web.twikiExtensions).contains(ext)) {
+			return MarkupSyntax.TWIKI;
 		}
 
 		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);
+	public boolean hasRootDocs(Repository r) {
+		List<String> roots = getRoots();
+		List<String> extensions = getAllExtensions();
+		List<PathModel> paths = JGitUtils.getFilesInPath(r, null, null);
 		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;
+				String ext = StringUtils.getFileExtension(path.name).toLowerCase();
+				String name = StringUtils.stripFileExtension(path.name).toLowerCase();
+
+				if (roots.contains(name)) {
+					if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
+						return true;
 					}
 				}
 			}
 		}
+		return false;
+	}
 
-		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);
+	public List<MarkupDocument> getRootDocs(Repository r, String repositoryName, String commitId) {
+		List<String> roots = getRoots();
+		List<MarkupDocument> list = getDocs(r, repositoryName, commitId, roots);
+		return list;
+	}
+
+	public MarkupDocument getReadme(Repository r, String repositoryName, String commitId) {
+		List<MarkupDocument> list = getDocs(r, repositoryName, commitId, Arrays.asList("readme"));
+		if (list.isEmpty()) {
+			return null;
 		}
+		return list.get(0);
+	}
 
-		return null;
+	private List<MarkupDocument> getDocs(Repository r, String repositoryName, String commitId, List<String> names) {
+		List<String> extensions = getAllExtensions();
+		String [] encodings = getEncodings();
+		Map<String, MarkupDocument> map = new HashMap<String, MarkupDocument>();
+		RevCommit commit = JGitUtils.getCommit(r, commitId);
+		List<PathModel> paths = JGitUtils.getFilesInPath(r, null, commit);
+		for (PathModel path : paths) {
+			if (!path.isTree()) {
+				String ext = StringUtils.getFileExtension(path.name).toLowerCase();
+				String name = StringUtils.stripFileExtension(path.name).toLowerCase();
+
+				if (names.contains(name)) {
+					if (StringUtils.isEmpty(ext) || extensions.contains(ext)) {
+						String markup = JGitUtils.getStringContent(r, commit.getTree(), path.name, encodings);
+						MarkupDocument doc = parse(repositoryName, commitId, path.name, markup);
+						map.put(name, doc);
+					}
+				}
+			}
+		}
+		// return document list in requested order
+		List<MarkupDocument> list = new ArrayList<MarkupDocument>();
+		for (String name : names) {
+			if (map.containsKey(name)) {
+				list.add(map.get(name));
+			}
+		}
+		return list;
 	}
 
 	public MarkupDocument parse(String repositoryName, String commitId, String documentPath, String markupText) {
@@ -115,8 +203,23 @@
 		if (markupText != null) {
 			try {
 				switch (syntax){
+				case CONFLUENCE:
+					parse(doc, repositoryName, commitId, new ConfluenceLanguage());
+					break;
 				case MARKDOWN:
 					parse(doc, repositoryName, commitId);
+					break;
+				case MEDIAWIKI:
+					parse(doc, repositoryName, commitId, new MediaWikiLanguage());
+					break;
+				case TEXTILE:
+					parse(doc, repositoryName, commitId, new TextileLanguage());
+					break;
+				case TRACWIKI:
+					parse(doc, repositoryName, commitId, new TracWikiLanguage());
+					break;
+				case TWIKI:
+					parse(doc, repositoryName, commitId, new TWikiLanguage());
 					break;
 				default:
 					doc.html = MarkdownUtils.transformPlainText(markupText);
@@ -140,6 +243,62 @@
 	}
 
 	/**
+	 * Parses the markup using the specified markup language
+	 *
+	 * @param doc
+	 * @param repositoryName
+	 * @param commitId
+	 * @param lang
+	 */
+	private void parse(final MarkupDocument doc, final String repositoryName, final String commitId, MarkupLanguage lang) {
+		StringWriter writer = new StringWriter();
+		HtmlDocumentBuilder builder = new HtmlDocumentBuilder(writer) {
+
+			@Override
+			public void image(Attributes attributes, String imagePath) {
+				String url;
+				if (imagePath.indexOf("://") == -1) {
+					// relative image
+					String path = doc.getRelativePath(imagePath);
+					String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+					url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+				} else {
+					// absolute image
+					url = imagePath;
+				}
+				super.image(attributes, url);
+			}
+
+			@Override
+			public void link(Attributes attributes, String hrefOrHashName, String text) {
+				String url;
+				if (hrefOrHashName.charAt(0) != '#') {
+					if (hrefOrHashName.indexOf("://") == -1) {
+						// relative link
+						String path = doc.getRelativePath(hrefOrHashName);
+						url = getWicketUrl(DocPage.class, repositoryName, commitId, path);
+					} else {
+						// absolute link
+						url = hrefOrHashName;
+					}
+				} else {
+					// page-relative hash link
+					url = hrefOrHashName;
+				}
+				super.link(attributes, url, text);
+			}
+		};
+
+		// avoid the <html> and <body> tags
+		builder.setEmitAsDocument(false);
+
+		MarkupParser parser = new MarkupParser(lang);
+		parser.setBuilder(builder);
+		parser.parse(doc.markup);
+		doc.html = writer.toString();
+	}
+
+	/**
 	 * Parses the document as Markdown using Pegdown.
 	 *
 	 * @param doc
@@ -148,6 +307,36 @@
 	 */
 	private void parse(final MarkupDocument doc, final String repositoryName, final String commitId) {
 		LinkRenderer renderer = new LinkRenderer() {
+
+			@Override
+			public Rendering render(ExpImageNode node, String text) {
+				if (node.url.indexOf("://") == -1) {
+					// repository-relative image link
+					String path = doc.getRelativePath(node.url);
+					String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+					String url = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+					return new Rendering(url, text);
+				}
+				// absolute image link
+				return new Rendering(node.url, text);
+			}
+
+			@Override
+			public Rendering render(RefImageNode node, String url, String title, String alt) {
+				Rendering rendering;
+				if (url.indexOf("://") == -1) {
+					// repository-relative image link
+					String path = doc.getRelativePath(url);
+					String contextUrl = RequestCycle.get().getRequest().getRelativePathPrefixToContextRoot();
+					String wurl = RawServlet.asLink(contextUrl, repositoryName, commitId, path);
+					rendering = new Rendering(wurl, alt);
+				} else {
+					// absolute image link
+					rendering = new Rendering(url, alt);
+				}
+				return StringUtils.isEmpty(title) ? rendering : rendering.withAttribute("title", encode(title));
+			}
+
 			@Override
 			public Rendering render(WikiLinkNode node) {
 				String path = doc.getRelativePath(node.getText());
@@ -210,7 +399,59 @@
 		}
 
 		String getRelativePath(String ref) {
-			return ref.charAt(0) == '/' ? ref.substring(1) : (getCurrentPath() + ref);
+			if (ref.charAt(0) == '/') {
+				// absolute path in repository
+				return ref.substring(1);
+			} else {
+				// resolve relative repository path
+				String cp = getCurrentPath();
+				if (StringUtils.isEmpty(cp)) {
+					return ref;
+				}
+				// this is a simple relative path resolver
+				List<String> currPathStrings = new ArrayList<String>(Arrays.asList(cp.split("/")));
+				String file = ref;
+				while (file.startsWith("../")) {
+					// strip ../ from the file reference
+					// drop the last path element
+					file = file.substring(3);
+					currPathStrings.remove(currPathStrings.size() - 1);
+				}
+				currPathStrings.add(file);
+				String path = Joiner.on("/").join(currPathStrings);
+				return path;
+			}
 		}
 	}
+
+	/**
+	 * This class implements a workaround for a bug reported in issue-379.
+	 * The bug was introduced by my own pegdown pull request #115.
+	 *
+	 * @author James Moger
+	 *
+	 */
+	public static class WorkaroundHtmlSerializer extends ToHtmlSerializer {
+
+		 public WorkaroundHtmlSerializer(final LinkRenderer linkRenderer) {
+			 super(linkRenderer,
+					 Collections.<String, VerbatimSerializer>singletonMap(VerbatimSerializer.DEFAULT, DefaultVerbatimSerializer.INSTANCE),
+					 Collections.<ToHtmlSerializerPlugin>emptyList());
+		    }
+	    private void printAttribute(String name, String value) {
+	        printer.print(' ').print(name).print('=').print('"').print(value).print('"');
+	    }
+
+	    /* Reimplement print image tag to eliminate a trailing double-quote */
+		@Override
+	    protected void printImageTag(LinkRenderer.Rendering rendering) {
+	        printer.print("<img");
+	        printAttribute("src", rendering.href);
+	        printAttribute("alt", rendering.text);
+	        for (LinkRenderer.Attribute attr : rendering.attributes) {
+	            printAttribute(attr.name, attr.value);
+	        }
+	        printer.print("/>");
+	    }
+	}
 }

--
Gitblit v1.9.1