package net.databinder.components;
import java.awt.font.TextAttribute;
import java.text.AttributedCharacterIterator;
import java.text.AttributedString;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.databinder.components.RenderedLabel.RenderedTextImageResource;
import org.apache.wicket.util.string.Strings;
/**
* Base class for rendered labels formated with a Markdown subset including **bold**
* __bold__ *italic* _italic_ and [link] appearance, as well as hard returns (space-space-newline)
* and paragraphs (newline-newline). Subclasses apply attributes to
* an AttributedString in the abstract attributeBold/Italic/Link methods.
* @see AttributedString
* @author Nathan Hamblen
*/
public abstract class FormattedRenderedTextImageResource extends RenderedTextImageResource {
//matches links patters like `[foo]: http://example.com/ "Optional Title Here"`
private static Pattern footnoteLinks = Pattern.compile("^ *\\[.+\\]\\:\\s.+\n", Pattern.MULTILINE);
// matches single newlines that do not have two spaces before them
private static Pattern strayNewlines = Pattern.compile("(?<!( )|\n)\n(?!\n)");
// group 1: either beginning of string or not a \
// group 2: beginning format element, to be expelled
// group 3: reluuctantly matched string inside formatters
// group 4: ending format element, to be expelled
private static Pattern boldFormat = Pattern.compile("(\\A|[^\\\\])(_{2}|\\*{2})(.+?)(\\2)", Pattern.DOTALL);
private static Pattern italicFormat = Pattern.compile("(\\A|[^\\\\])(\\*|_)(.+?)(\\2)", Pattern.DOTALL);
private static Pattern linkFormat = Pattern.compile("(\\A|[^\\\\])(\\[)(.+?)(\\](\\(|\\[).+?(\\)|\\]))", Pattern.DOTALL);
// matches a slash used for escaping, to be expelled
private static Pattern escapedCharacter = Pattern.compile("(\\\\)[^\\\\]");
private enum Style {BOLD, ITALIC, LINK};
private static class Range{
Style style;
int start;
int end;
}
private static class MutableRangeString {
List<Range> ranges = new ArrayList<Range>(10);
StringBuilder string;
public MutableRangeString(String str) {
string = new StringBuilder(str);
}
void expell(int start, int end) {
string.delete(start, end);
for(Range r : ranges) {
if (r.end > start) {
r.end = r.end + start - end;
if (r.start > start)
r.start = r.start + start - end;
}
}
}
}
/** Apply style markers to ranges matching the given format pattern. */
private static void process(MutableRangeString rangeStr, Pattern p, Style style) {
int delta = 0;
Matcher m = p.matcher(rangeStr.string.toString());
while (m.find()) {
Range r = new Range();
r.style = style;
r.start = m.start(3) - delta;
r.end = m.end(3) - delta;
rangeStr.ranges.add(r);
rangeStr.expell(m.start(2) - delta, m.end(2) - delta);
delta += m.end(2) - m.start(2);
rangeStr.expell(m.start(4) - delta, m.end(4) - delta);
delta += m.end(4) - m.start(4);
}
}
/** @return string formatted with markdown subset */
protected String getFormattedTextString() {
return text;
}
/** @return string with attributes derived from formatting in getFormattedTextString() */
@Override
protected List<AttributedCharacterIterator> getAttributedLines() {
String markedtext = getFormattedTextString();
if (Strings.isEmpty(markedtext))
return null;
markedtext = footnoteLinks.matcher(markedtext).replaceAll("");
markedtext = strayNewlines.matcher(markedtext.trim()).replaceAll("");
MutableRangeString rangeStr = new MutableRangeString(markedtext);
process(rangeStr, boldFormat, Style.BOLD);
process(rangeStr, italicFormat, Style.ITALIC);
process(rangeStr, linkFormat, Style.LINK);
int delta = 0;
Matcher m = escapedCharacter.matcher(rangeStr.string.toString());
while (m.find()) {
rangeStr.expell(m.start(1) - delta, m.end(1) - delta);
delta++;
}
String text = rangeStr.string.toString();
AttributedString attributedText = new AttributedString(text);
attributedText.addAttribute(TextAttribute.FONT, font);
for (Range r : rangeStr.ranges) {
if (r.style == Style.BOLD)
attributeBold(attributedText, r.start, r.end);
else if (r.style == Style.ITALIC)
attributeItalic(attributedText, r.start, r.end);
else if (r.style == Style.LINK)
attributeLink(attributedText, r.start, r.end);
}
return splitAtNewlines(attributedText, text);
}
abstract void attributeBold(AttributedString string, int start, int end);
abstract void attributeItalic(AttributedString string, int start, int end);
abstract void attributeLink(AttributedString string, int start, int end);
}