com.databasesandlife.util.wicket.MultilineLabelWithClickableLinks Maven / Gradle / Ivy
Show all versions of java-common Show documentation
package com.databasesandlife.util.wicket;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.model.IModel;
import org.apache.wicket.util.string.Strings;
import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static java.util.regex.Matcher.quoteReplacement;
/**
* Displays the text in the model over multiple HTML lines, and makes links clickable.
*
* @author This source is copyright Adrian Smith and licensed under the LGPL 3.
* @see Project on GitHub
*/
@SuppressWarnings("serial")
public class MultilineLabelWithClickableLinks extends Label {
public MultilineLabelWithClickableLinks(final String id, Serializable label) {
super(id, label);
setEscapeModelStrings(false);
}
public MultilineLabelWithClickableLinks(final String id, IModel> model) {
super(id, model);
setEscapeModelStrings(false);
}
/**
* Matches "foo://foo.foo/foo".
*
* - Protocol is optional
*
- Domain must contain at least one dot (TLD list is too long for whitelist)
*
- Last part is optional and can contain anything apart from space and trailing punctuation
* (= part of the sentence in which the link is embedded)
*
* Quotes are not allowed because we don't want <a href="foo"> to have foo containing quotes (XSS).
*/
protected static final Pattern linkPattern = Pattern.compile(
"(\\w{2,7}:/+)?([\\w-]+\\.)+[\\w-]+(/([^\\s'\"]*[\\w])?)?", Pattern.CASE_INSENSITIVE);
/**
* Takes some plain text and returns HTML with URLs marked up as clickable links.
*
*
Making links clickable is not as easy as it seems.
*
* - Conversion from plain text to HTML requires that entities
* such as "&" get replaced by "&".
*
- Links such as "foo.com/a&b" need to get replaced by
* "<a href='foo.com/a&b'>foo.com/a&b</a>".
*
*
* Therefore,
*
* - One cannot firstly replace entities and then markup links, as the links should contain
* unescaped "&" as opposed to "&".
*
- One cannot firstly encode links and then replace entities as the angle brackets in the link's
* "<a href.." would get replaced by "<a href..." which the
* browser would not understand.
*
*
* Therefore, the replacement of HTML entities, and the replacement of links, must be done
* in a single pass, not one after another.
*
* @param plainText Plain text, no HTML.
* @return Safe HTML with entities encoded and links marked up.
*/
public static CharSequence encodeLinksToHtml(CharSequence plainText) {
var m = linkPattern.matcher(plainText);
var result = new StringBuffer();
while (m.find()) {
var replacement = "" + Strings.escapeMarkup(m.group()) + "";
var backgroundStartInclIdx = result.length();
m.appendReplacement(result, quoteReplacement(replacement));
var backgroundEndExclIdx = result.length() - replacement.length();
var background = result.substring(backgroundStartInclIdx, backgroundEndExclIdx);
result.replace(backgroundStartInclIdx, backgroundEndExclIdx, Strings.escapeMarkup(background).toString());
}
var backgroundStartInclIdx = result.length();
m.appendTail(result);
var backgroundEndExclIdx = result.length();
var background = result.substring(backgroundStartInclIdx, backgroundEndExclIdx);
result.replace(backgroundStartInclIdx, backgroundEndExclIdx, Strings.escapeMarkup(background).toString());
return result;
}
@Override public void onComponentTagBody(MarkupStream markupStream, ComponentTag openTag) {
var plainText = getDefaultModelObjectAsString();
var htmlWithLinksMarkedup = encodeLinksToHtml(plainText);
var htmlWIthLinksAndNewlines = htmlWithLinksMarkedup.toString().replace("\n", "
");
replaceComponentTagBody(markupStream, openTag, htmlWIthLinksAndNewlines);
}
}