Skip to content

Instantly share code, notes, and snippets.

@attacco
Created April 28, 2016 23:09
Show Gist options
  • Save attacco/0d09d4aa1d4b0e98a87f69a8a37dbf25 to your computer and use it in GitHub Desktop.
Save attacco/0d09d4aa1d4b0e98a87f69a8a37dbf25 to your computer and use it in GitHub Desktop.
Method to wrap URL's in plain text to 'a href...' elements, like in html.
import java.util.Map;
import java.util.NavigableMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class URLWrapUtil {
private final static Pattern A_HREF_PATTERN = Pattern.compile("(?i)<a\\s+href\\s*=\\s*\".+\"\\s*>.+<\\s*/\\s*a\\s*>");
// Copied (and optimized) from android.util.Patterns#WEB_URL (SDK 23)
private final static Pattern URL_PATTERN = Pattern.compile("(?i)((?:(https?|rtsp):\\/\\/(?:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,64}(?:\\:(?:[a-zA-Z0-9\\$\\-\\_\\.\\+\\!\\*\\'\\(\\)\\,\\;\\?\\&\\=]|(?:\\%[a-fA-F0-9]{2})){1,25})?\\@)?)?(?:(([a-zA-Z0-9 -\uD7FF豈-\uFDCFﷰ-\uFFEF]([a-zA-Z0-9 -\uD7FF豈-\uFDCFﷰ-\uFFEF\\-]{0,61}[a-zA-Z0-9 -\uD7FF豈-\uFDCFﷰ-\uFFEF]){0,1}\\.)+[a-zA-Z -\uD7FF豈-\uFDCFﷰ-\uFFEF]{2,63}|((25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9])\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[1-9]|0)\\.(25[0-5]|2[0-4][0-9]|[0-1][0-9]{2}|[1-9][0-9]|[0-9]))))(?:\\:\\d{1,5})?)(\\/(?:(?:[a-zA-Z0-9 -\uD7FF豈-\uFDCFﷰ-\uFFEF\\;\\/\\?\\:\\@\\&\\=\\#\\~\\-\\.\\+\\!\\*\\'\\(\\)\\,\\_])|(?:\\%[a-fA-F0-9]{2}))*)?(?:\\b|$)");
public static String wrap(String source) {
if (source == null) {
return null;
}
// map of: start => end
final NavigableMap<Integer, Integer> entries = new TreeMap<>();
Matcher m = A_HREF_PATTERN.matcher(source);
while (m.find()) {
entries.put(m.start(), m.end());
}
m = URL_PATTERN.matcher(source);
final StringBuffer sb = new StringBuffer(); // appendReplacement method accepts only StringBuffer :(
while (m.find()) {
final int start = m.start();
final Map.Entry<Integer, Integer> entry = entries.floorEntry(start);
/*
* Из серии "Предварительная оптимизация".
* Возможно, данный блок поможет ускорить работу в случае, если source довольно большой, причем,
* "<a href.../>" встречается только в начале, а дальше идут просто URL.
* С одной стороны, entries будет каждый раз отсекать уже пройденные вхождения, с другой, это приводит к
* созданию объекта, оборачивающего entries - map view, см. описание метода java.util.NavigableMap.tailMap(K, boolean)).
*
* По-умолчанию, предлагаю не использовать эту оптимизацию.
* */
/*if (entry != null) {
entries = entries.tailMap(entry.getKey(), false);
}*/
// we're should wrap if there is no entry, or our start position is greater than the end of 'danger' region.
final boolean shouldWrap = entry == null || start > entry.getValue();
final String url = source.substring(start, m.end());
final String replacement = shouldWrap ? String.format("<a href=\"%1$s\">%1$s</a>", url) : url;
m.appendReplacement(sb, replacement);
}
m.appendTail(sb);
return sb.toString();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment