package org.rascalmpl.uri;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class LinkifiedString {
private List<Integer> linkOffsets;
private List<Integer> linkLengths;
private List<String> linkTargets;
private String linkedString;
public String getString() {
return linkedString;
}
public boolean containsLinks() {
return linkOffsets != null && linkOffsets.size() > 0;
}
public int linkCount() {
return linkOffsets == null ? 0 : linkOffsets.size();
}
public int linkOffset(int index) {
if (linkOffsets == null) {
throw new IllegalStateException("There are no links");
}
return linkOffsets.get(index);
}
public int linkLength(int index) {
if (linkLengths == null) {
throw new IllegalStateException("There are no links");
}
return linkLengths.get(index);
}
public String linkTarget(int index) {
if (linkTargets == null) {
throw new IllegalStateException("There are no links");
}
return linkTargets.get(index);
}
// scan for both \uE007 + [Title](url) and all |loc|'s
private static final Pattern findLinks = Pattern.compile(
"(?:\\uE007" // first alternative, Markdown
+ "\\[([^\\]]*)\\]" // [Title]
+ "\\(([^\\)]*)\\)" // (link)
+ ")"
+ "|"
+ "(" // or the other alternative, any rascal location
+ "\\|[^\\t-\\n\\r\\s\\|]*://[^\\t-\\n\\r\\s\\|]*\\|" // |location|
+ "(?:\\([^\\)]*\\))?" // (optional offset)
+ ")");
public LinkifiedString(String input) {
linkedString = input;
StringBuffer sb = null;
Matcher m = findLinks.matcher(input);
while (m.find()) {
if (linkOffsets == null) {
linkOffsets = new ArrayList<Integer>();
linkLengths = new ArrayList<Integer>();
linkTargets = new ArrayList<String>();
}
if (m.group(3) == null) {
if (sb == null) {
// first link found, so we have to recut the string
sb = new StringBuffer(input.length());
}
// markdown link
String name = "\u261E " + m.group(1) ;
String url = m.group(2);
linkTargets.add(url);
m.appendReplacement(sb, escapeReplacement(name));
linkOffsets.add((sb.length() - name.length()) + 1);
linkLengths.add(name.length());
}
else {
// source location
String loc = m.group(3);
linkTargets.add(loc);
if (sb != null) {
// we are re-appending
m.appendReplacement(sb, escapeReplacement(loc));
linkOffsets.add((sb.length() - loc.length()) + 1);
}
else {
linkOffsets.add(m.start() + 1);
}
linkLengths.add(loc.length());
}
}
if (sb != null) {
// we have a new string
m.appendTail(sb);
linkedString = sb.toString();
}
}
private static String escapeReplacement(String replacement) {
// the $ char should be escaped when we append it as a replacement,
// since it is used by the regular expression engine to reference a group
// the \ also needs to be escape, since it is used for escaping in the replacement string
if (replacement.length() < 20) {
return replacement.replace("\\", "\\\\").replace("$", "\\$");
}
else {
// okay, "larger" string, lets avoid double loop
int stringLength = replacement.length();
StringBuilder sb = new StringBuilder(stringLength);
for (int currentChar = 0; currentChar < stringLength; currentChar++) {
char c = replacement.charAt(currentChar);
if (c == '\\')
sb.append('\\');
else if (c == '$' )
sb.append('\\');
sb.append(c);
}
return sb.toString();
}
}
}