/** * Yona, 21st Century Project Hosting SW *
* Copyright Yona & Yobi Authors & NAVER Corp. & NAVER LABS Corp. * https://yona.io **/ package utils; import controllers.UserApp; import models.Issue; import models.Organization; import models.Project; import models.User; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.jsoup.Jsoup; import org.jsoup.nodes.Document; import org.jsoup.nodes.Element; import org.jsoup.nodes.TextNode; import org.jsoup.select.Elements; import org.tmatesoft.svn.core.SVNException; import play.i18n.Messages; import playRepository.Commit; import playRepository.PlayRepository; import playRepository.RepositoryService; import javax.servlet.ServletException; import java.io.IOException; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** *
A renderer that makes auto-links from certain references extracted by HTML rendered by marked.js, using pre-defined patterns.
* *This renderer requires contents of specific objects(issues, comments, etc), and a project containing it.
* *There are examples of how certain references are changed.
** User/Project#Num: {@code User/Project#Num} * User#Num: {@code User#Num} * #Num: {@code #Num} * User/Project@SHA: {@code User/Project@The short id of this commit} * User{@literal @}SHA: {@code User@The short id of this commit} * {@literal @}SHA: {@code The short id of this commit} * {@literal @}User: {@code @User} * {@literal @}User/Project: {@code @User/Project} **/ public class AutoLinkRenderer { private static final String PATH_PATTERN_STR = "[a-zA-Z0-9-_.가-힣/]+"; private static final String ISSUE_PATTERN_STR = "\\d+"; private static final String SHA_PATTERN_STR = "[a-f0-9]{7,40}"; private static final Pattern PATH_WITH_ISSUE_PATTERN = Pattern.compile("@?(" + PATH_PATTERN_STR + ")#(" + ISSUE_PATTERN_STR + ")"); private static final Pattern ISSUE_PATTERN = Pattern.compile("#(" + ISSUE_PATTERN_STR + ")"); private static final Pattern PATH_WITH_SHA_PATTERN = Pattern.compile("(" + PATH_PATTERN_STR + ")@?(" + SHA_PATTERN_STR + ")"); private static final Pattern SHA_PATTERN = Pattern.compile("@?(" + SHA_PATTERN_STR + ")"); private static final Pattern LOGIN_ID_PATTERN_ALLOW_FORWARD_SLASH_PATTERN = Pattern.compile("@(" + PATH_PATTERN_STR + ")"); private static final String[] IGNORE_TAGNAME = {"CODE", "A"}; private static final Pattern WORD_PATTERN = Pattern.compile("\\w"); private static class Link { private static final String DEFAULT_LINK_FORMAT = "%s"; public static final Link EMPTY_LINK = new Link(); public String href; public String className; public String displayName; private Link() {} public Link(String href, String displayName) { this.href = href; this.displayName = displayName; } public Link(String href, String className, String displayName) { this.href = href; this.className = className; this.displayName = displayName; } public String toString() { return String.format(DEFAULT_LINK_FORMAT, StringUtils.defaultIfEmpty(href, StringUtils.EMPTY), StringUtils.defaultIfEmpty(className, StringUtils.EMPTY), StringUtils.defaultIfEmpty(displayName, StringUtils.EMPTY) ); } public boolean isValid() { return this != EMPTY_LINK; } } private static interface ToLink { public Link toLink(Matcher matcher); } public String body; public Project project; public AutoLinkRenderer(String body, Project project) { this.body = body; this.project = project; } public String render(String lang) { this.parse(PATH_WITH_ISSUE_PATTERN, new ToLink() { @Override public Link toLink(Matcher matcher) { String path = matcher.group(1); String issueNumber = matcher.group(2); Project project = getProjectFromPath(path); return toValidIssueLink(path, project, issueNumber); } }); parse(ISSUE_PATTERN, new ToLink() { @Override public Link toLink(Matcher matcher) { return toValidIssueLink(StringUtils.EMPTY, project, matcher.group(1)); } }); parse(PATH_WITH_SHA_PATTERN, new ToLink() { @Override public Link toLink(Matcher matcher) { String path = matcher.group(1); String SHA = matcher.group(2); Project project = getProjectFromPath(path); return toValidSHALink(path, project, SHA); } }); parse(SHA_PATTERN, new ToLink() { @Override public Link toLink(Matcher matcher) { return toValidSHALink(project, matcher.group(1)); } }); parse(LOGIN_ID_PATTERN_ALLOW_FORWARD_SLASH_PATTERN, new ToLink() { @Override public Link toLink(Matcher matcher) { String path = matcher.group(1); int slashIndex = path.indexOf("/"); if (slashIndex > -1) { return toValidProjectLink(path.substring(0, slashIndex), path.substring(slashIndex + 1)); } else { return toValidUserLink(path, lang); } } }); return this.body; } private AutoLinkRenderer parse(Pattern pattern, ToLink toLink) { Document doc = Jsoup.parse(body); Document.OutputSettings settings = doc.outputSettings(); settings.prettyPrint(false); Elements elements = doc.getElementsMatchingOwnText(pattern); for (Element el : elements) { if (isIgnoreElement(el)) { continue; } List