package marubinotto.piggydb.ui.wiki;
import static marubinotto.util.CollectionUtils.list;
import static marubinotto.util.RegexUtils.compile;
import java.util.List;
import marubinotto.piggydb.model.Fragment;
import marubinotto.piggydb.model.predicate.Preformatted;
import marubinotto.piggydb.ui.page.common.HtmlFragments;
import marubinotto.util.web.WebUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.UnhandledException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.oro.text.regex.Pattern;
import org.apache.oro.text.regex.PatternMatcher;
public class HtmlBuilder implements DocumentBuilder {
private static Log logger = LogFactory.getLog(HtmlBuilder.class);
public void breakBlocks(ParseContext context) {
closeAllBlocks(context);
}
public void startSection(ParseContext context, int level, String title) {
int headingLevel = level + 1; // NOTE top level is <h2>
context.print("<h" + headingLevel + ">");
context.print(title);
context.println("</h" + headingLevel + ">");
}
public void appendToParagraph(ParseContext context, String line) {
setBlock(context, HtmlBlock.PARAGRAPH, 1);
context.println(line);
}
public void appendToPreformattedText(ParseContext context, String line) {
setBlock(context, HtmlBlock.PREFORMATTED_TEXT, 1);
context.println(line);
}
public void putHorizontalRule(ParseContext context) {
closeAllBlocks(context);
context.println("<hr/>");
}
public void appendToBlockquote(ParseContext context, int level, String line) {
setBlock(context, HtmlBlock.BLOCKQUOTE, level);
context.println(line);
}
public void addUnorderedListElement(ParseContext context, int level, String content) {
setBlock(context, HtmlBlock.UNORDERED_LIST, level);
context.print(content);
}
public void addOrderedListElement(ParseContext context, int level, String content) {
setBlock(context, HtmlBlock.ORDERED_LIST, level);
context.print(content);
}
public void addDefinitionListEntry(ParseContext context, String term, String description) {
setBlock(context, HtmlBlock.DEFINITION_LIST, 1);
context.print("<dt>");
context.print(term);
context.println("</dt>");
if (description != null && !description.trim().equals("")) {
context.print("<dd>");
context.print(description);
context.println("</dd>");
}
}
public void addTableRow(ParseContext context, String[] values) {
setBlock(context, HtmlBlock.TABLE, 1);
context.print("<tr>");
for (String value : values) {
context.print("<td>");
context.print(value);
context.print("</td>");
}
context.println("</tr>");
}
public void finish(ParseContext context) {
closeAllBlocks(context);
}
public String escape(String chunk) {
return WebUtils.escapeHtml(chunk);
}
public String appendBreak(String line) {
return line + "<br/>";
}
public String processSpan(ParseContext context, String chunk, String classes) {
return "<span class=\"" + classes + "\">" + chunk + "</span>";
}
public String processItalic(ParseContext context, String chunk) {
return "<i>" + chunk + "</i>";
}
public String processBold(ParseContext context, String chunk) {
return "<b>" + chunk + "</b>";
}
public String processDelete(ParseContext context, String chunk) {
return "<del>" + chunk + "</del>";
}
private static final Pattern P_IMAGE_URL = compile("^(http|https):.*\\.(gif|png|jpeg|jpg)$");
public String processStandardUrl(ParseContext context, String url, boolean preformatted) {
if (!preformatted) {
for (UrlProcessor processor : urlProcessors) {
String result = processor.process(url, context.getMatcher());
if (result != null) return result;
}
}
return "<a class=\"url-link\" href=\"" + url + "\">" + url + "</a>";
}
public static List<UrlProcessor> urlProcessors =
list(new ImageUrlProcessor(), new YouTubeUrlProcessor());
public static interface UrlProcessor {
public String process(String url, PatternMatcher matcher);
}
public static class ImageUrlProcessor implements UrlProcessor {
public String process(String url, PatternMatcher matcher) {
if (!matcher.matches(url, P_IMAGE_URL)) return null;
return "<a class=\"img-link\" href=\"" + url + "\"><img src=\"" + url + "\" alt=\"" + url + "\"/></a>";
}
}
protected String fragmentUrl(Long id, ParseContext context) {
return context.getWebResources().fragmentPath(id);
}
private static String attrsForQuickView(Long id) {
return "class=\"" + HtmlFragments.CLASS_QUICK_VIEWABLE + "\" data-id=\"" + id + "\"";
}
public String makeLinkToFragment(ParseContext context, Long fragmentId, String label) {
return "<a " + attrsForQuickView(fragmentId) +
" href=\"" + fragmentUrl(fragmentId, context) + "\">" + label + "</a>";
}
public String makeLinkToFragmentWithDetail(ParseContext context, Fragment fragment) {
String url = fragmentUrl(fragment.getId(), context);
return "<a " + attrsForQuickView(fragment.getId()) +
" href=\"" + url + "\">#" + fragment.getId() + "</a> " +
(StringUtils.isNotBlank(fragment.getTitle()) ? fragment.getTitle() : "");
}
public String makeEmbeddedFragment(ParseContext context, Fragment fragment) {
logger.debug("Embed: " + fragment.getId() + " FragmentStack: " + context.getFragmentStack());
if (context.getFragmentStack().contains(fragment.getId())) {
return null; // avoid a loop
}
return makeEmbeddedFragmentRecursively(context, fragment);
}
private static String makeEmbeddedFragmentRecursively(ParseContext context, Fragment fragment) {
if (fragment.isFile()) {
if (fragment.isImageFile()) {
return context.getHtmlFragments().fragmentImage(fragment);
}
else {
return context.getHtmlFragments().fileIcon(fragment.getFileType()) + " " +
context.getHtmlFragments().linkToFragmentFileWithSize(fragment);
}
}
else if (Preformatted.INSTANCE.evaluate(fragment)) {
try {
return context.getHtmlFragments().preformattedContent(
fragment, context.getWikiParser(), context.getUser());
}
catch (Exception e) {
throw new UnhandledException(e);
}
}
else {
context.getFragmentStack().push(fragment.getId());
try {
return context.getWikiParser().parseNestedly(
fragment.getContent(),
context.getFragmentStack(),
context.getUser(),
context.getWebResources());
}
catch (Exception e) {
throw new UnhandledException(e);
}
finally {
context.getFragmentStack().pop();
}
}
}
public String processLabeledLink(ParseContext context, String label, String url) {
// Image label
if (context.getMatcher().matches(label, P_IMAGE_URL)) {
label = "<img src=\"" + label + "\" alt=\"\"/>";
}
// Fragment URN
if (url.startsWith(FragmentUrn.PREFIX)) {
Long id = new FragmentUrn(url).getId();
if (id != null) {
return "<a " + attrsForQuickView(id) + " href=\"" + fragmentUrl(id, context) + "\">" + label + "</a>";
}
}
return "<a href=\"" + url + "\">" + label + "</a>";
}
public String processFragmentRef(ParseContext context, String label, long id) {
return "<a " + attrsForQuickView(id) +
" href=\"" + fragmentUrl(id, context) + "\">" + label + "</a>";
}
public String processTagName(ParseContext context, String tagName) {
String url = context.getWebResources().tagPathByName(tagName);
return "<a class=\"tag\" href=\"" + url + "\">" + tagName + "</a>";
}
public String processErrorLine(String line) {
return "<span class=\"error-line\">" + line + "</span>";
}
// Internal
protected HtmlBlock getCurrentBlock(ParseContext context) {
if (context.getBlockStack().isEmpty()) {
return null;
}
return (HtmlBlock)context.getBlockStack().peek();
}
protected int getCurrentBlockLevel(ParseContext context) {
return context.getBlockStack().size();
}
protected void setBlock(ParseContext context, HtmlBlock block, int level) {
HtmlBlock currentBlock = getCurrentBlock(context);
if (currentBlock != null && !block.isSameTypeTo(currentBlock)) {
closeAllBlocks(context);
}
int currentBlockLevel = getCurrentBlockLevel(context);
if (level == currentBlockLevel) {
block.readyToAppend(context);
}
else if (level > currentBlockLevel) {
int down = level - currentBlockLevel;
for (int i = 1; i <= down; i++) {
block.open(context, currentBlockLevel + down);
context.getBlockStack().push(block);
}
}
else if (level < currentBlockLevel) {
int up = currentBlockLevel - level;
for (int i = 0; i < up; i++) {
closeCurrentBlock(context);
}
block.readyToAppend(context);
}
}
protected void closeCurrentBlock(ParseContext context) {
HtmlBlock currentBlock = (HtmlBlock)context.getBlockStack().pop();
currentBlock.close(context);
}
protected void closeAllBlocks(ParseContext context) {
while (!context.getBlockStack().isEmpty()) {
closeCurrentBlock(context);
}
}
}