package com.boardgamegeek.util; import android.annotation.SuppressLint; import android.text.TextUtils; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Converts XML returned from the BGG API into HTML. */ public class XmlConverter { private static final String BASE_URL = "https://boardgamegeek.com"; private static final String STATIC_IMAGES_URL = "https://cf.geekdo-static.com/images/"; private static final String IMAGES_URL = "https://cf.geekdo-images.com/images/"; private final List<Replaceable> replacers; public XmlConverter() { replacers = new ArrayList<>(); replacers.add(new SimpleReplacer("\\[hr\\]", "<hr/>")); replacers.add(new SimpleReplacer("\\[clear\\]", "<div style=\"clear:both\"></div>")); createPair("b"); createPair("i"); createPair("u"); createPair("-", "strike"); replacers.add(new SimpleReplacer("\\[floatleft\\]", "<div style=\"float:left\">")); replacers.add(new SimpleReplacer("\\[/floatleft\\]", "</div>")); createPair("center"); replacers.add(new SimpleReplacer("\\[floatright\\]", "<div style=\"float:right\">")); replacers.add(new SimpleReplacer("\\[/floatright\\]", "</div>")); replacers.add(new Replacer("\\[COLOR=([^#].*?)\\]", "<span style=\"color:", "\">")); replacers.add(new Replacer("\\[COLOR=#(.*?)\\]", "<span style=\"color:#", "\">")); replacers.add(new SimpleReplacer("\\[/COLOR\\]", "</span>")); replacers.add(new Replacer("\\[BGCOLOR=([^#].*?)\\]", "<span style=\"background-color:", "\">")); replacers.add(new Replacer("\\[BGCOLOR=#(.*?)\\]", "<span style=\"background-color#:", "\">")); replacers.add(new SimpleReplacer("\\[/BGCOLOR\\]", "</span>")); // TODO: determine when image is a PNG replacers.add(new Replacer("\\[ImageID=(\\d+).*?\\]", "<div style=\"display:inline\"><img src=\"" + IMAGES_URL + "pic", "_t.jpg\"/></div>")); replacers.add(new Replacer("\\[IMG\\](.*?)\\[/IMG\\]", "<div style=\"display:inline\"><img src=\"", "\"/></div>")); // TODO: YouTube, Vimeo, tweet, mp3 replacers.add(new GeekUrlReplacer()); replacers.add(GeekLinkReplacer.createNumeric("thing")); replacers.add(GeekLinkReplacer.createNumeric("thread")); replacers.add(GeekLinkReplacer.createNumeric("article", "reply"));// TODO: add #id replacers.add(GeekLinkReplacer.createNumeric("geeklist", "GeekList")); replacers.add(GeekLinkReplacer.createNumeric("filepage", "file")); replacers.add(GeekLinkReplacer.createNumeric("person")); replacers.add(GeekLinkReplacer.createNumeric("company")); replacers.add(GeekLinkReplacer.createNumeric("property")); replacers.add(GeekLinkReplacer.createNumeric("family")); replacers.add(GeekLinkReplacer.createNumeric("guild")); replacers.add(GeekLinkReplacer.createAlpha("user")); replacers.add(GeekLinkReplacer.createNumeric("question", "GeekQuestion")); replacers.add(GeekLinkReplacer.createNumeric("media", "podcast episode")); replacers.add(GeekLinkReplacer.createNumeric("blog")); replacers.add(GeekLinkReplacer.createNumeric("blogpost", "blog post")); replacers.add(new SimpleReplacer("\\[q\\]", "Quote:<blockquote>")); replacers.add(new Replacer("\\[q=\"(.*?)\"\\]", "", " wrote:<blockquote>")); replacers.add(new SimpleReplacer("\\[/q\\]", "</blockquote>")); replacers.add(new SimpleReplacer("\\[o\\]", "Spoiler: <span style=\"color:white\">")); replacers.add(new SimpleReplacer("\\[/o\\]", "</span>")); createPair("c", "tt"); replacers.add(new UrlReplacer()); replacers.add(new UrlReplacer2()); // TODO: roll // TODO: size isn't working replacers.add(new Replacer("\\[size=(.*?)\\]", "<span font-size=\"", "px\">")); replacers.add(new SimpleReplacer("\\[/size\\]", "</span>")); replacers.add(SimpleReplacer.createCustomImage("\\:\\)\\s", "smile.gif")); replacers.add(SimpleReplacer.createCustomImage("\\:\\(\\s", "sad.gif")); replacers.add(SimpleReplacer.createCustomImage("\\:D\\s", "biggrin.gif")); replacers.add(SimpleReplacer.createCustomImage("\\:p\\s", "tongue.gif")); replacers.add(SimpleReplacer.createCustomImage("\\;\\)\\s", "wink.gif")); replacers.add(SimpleReplacer.createImage("what", "rock.gif")); replacers.add(SimpleReplacer.createGif("wow")); replacers.add(SimpleReplacer.createGif("angry")); replacers.add(SimpleReplacer.createGif("cool")); replacers.add(SimpleReplacer.createGif("laugh")); replacers.add(SimpleReplacer.createGif("meeple")); replacers.add(SimpleReplacer.createGif("surprise")); replacers.add(SimpleReplacer.createGif("blush")); replacers.add(SimpleReplacer.createGif("snore")); replacers.add(SimpleReplacer.createGif("cry")); replacers.add(SimpleReplacer.createGif("kiss")); replacers.add(SimpleReplacer.createGif("modest")); replacers.add(SimpleReplacer.createGif("whistle")); replacers.add(SimpleReplacer.createGif("devil")); replacers.add(SimpleReplacer.createGif("soblue")); replacers.add(SimpleReplacer.createGif("yuk")); replacers.add(SimpleReplacer.createGif("gulp")); replacers.add(SimpleReplacer.createGif("shake")); replacers.add(SimpleReplacer.createGif("arrrh")); replacers.add(SimpleReplacer.createGif("zombie")); replacers.add(SimpleReplacer.createGif("robot")); replacers.add(SimpleReplacer.createGif("ninja")); replacers.add(SimpleReplacer.createGif("sauron")); replacers.add(SimpleReplacer.createGif("goo")); replacers.add(SimpleReplacer.createImage("star", "star_yellow.gif")); replacers.add(SimpleReplacer.createImage("halfstar", "star_yellowhalf.gif")); replacers.add(SimpleReplacer.createImage("nostar", "star_white.gif")); replacers.add(SimpleReplacer.createImage("gg", "geekgold.gif")); replacers.add(SimpleReplacer.createGif("bag")); replacers.add(SimpleReplacer.createGif("bacon")); replacers.add(SimpleReplacer.createGif("caravan")); createCamelImage("mint"); createCamelImage("lime"); createCamelImage("grape"); createCamelImage("lemon"); createCamelImage("orange"); replacers.add(SimpleReplacer.createGif("goldencamel")); createTajImage("blue"); createTajImage("brown"); createTajImage("gray"); createTajImage("maroon"); createTajImage("tan"); createTajImage("white"); replacers.add(SimpleReplacer.createImage("thumbsup", "thumbs-up.gif")); replacers.add(SimpleReplacer.createImage("thumbsdown", "thumbs-down.gif")); replacers.add(SimpleReplacer.createGif("coffee")); replacers.add(SimpleReplacer.createGif("tobacco")); replacers.add(SimpleReplacer.createGif("indigo")); replacers.add(SimpleReplacer.createGif("sugar")); replacers.add(SimpleReplacer.createGif("corn")); replacers.add(SimpleReplacer.createGif("colonist")); replacers.add(SimpleReplacer.createGif("1vp")); replacers.add(SimpleReplacer.createGif("5vp")); replacers.add(SimpleReplacer.createGif("1db")); replacers.add(SimpleReplacer.createGif("5db")); for (int i = 0; i <= 9; i++) { replacers.add(SimpleReplacer.createImage("d10-" + i, "d10-" + i + ".gif")); } replacers.add(SimpleReplacer.createImage("city", "ttr_city.gif")); createTrainImage("red"); createTrainImage("green"); createTrainImage("blue"); createTrainImage("yellow"); createTrainImage("black"); createTrainImage("purple"); createTrainImage("white"); replacers.add(SimpleReplacer.createGif("wood")); replacers.add(SimpleReplacer.createGif("wheat")); replacers.add(SimpleReplacer.createGif("sheep")); replacers.add(SimpleReplacer.createGif("ore")); replacers.add(SimpleReplacer.createGif("brick")); replacers.add(SimpleReplacer.createGif("cinnamon")); replacers.add(SimpleReplacer.createGif("clove")); replacers.add(SimpleReplacer.createGif("ginger")); replacers.add(SimpleReplacer.createGif("nutmeg")); replacers.add(SimpleReplacer.createGif("pepper")); replacers.add(SimpleReplacer.createGif("coal")); replacers.add(SimpleReplacer.createGif("oil")); replacers.add(SimpleReplacer.createGif("trash")); replacers.add(SimpleReplacer.createGif("nuclear")); for (int i = 1; i <= 6; i++) { replacers.add(SimpleReplacer.createImage("d6-" + i, "die-white-" + i + ".gif")); replacers.add(SimpleReplacer.createImage("bd6-" + i, "die-black-" + i + ".gif")); } replacers.add(SimpleReplacer.createPng("tankard")); replacers.add(SimpleReplacer.createPng("jug")); replacers.add(SimpleReplacer.createPng("chalice")); replacers.add(SimpleReplacer.createGif("worker")); replacers.add(SimpleReplacer.createGif("building")); replacers.add(SimpleReplacer.createGif("aristocrat")); replacers.add(SimpleReplacer.createGif("trade")); replacers.add(SimpleReplacer.createPng("arrowN")); replacers.add(SimpleReplacer.createPng("arrowNE")); replacers.add(SimpleReplacer.createPng("arrowE")); replacers.add(SimpleReplacer.createPng("arrowSE")); replacers.add(SimpleReplacer.createPng("arrowS")); replacers.add(SimpleReplacer.createPng("arrowSW")); replacers.add(SimpleReplacer.createPng("arrowW")); replacers.add(SimpleReplacer.createPng("arrowNW")); replacers.add(SimpleReplacer.createPng("power")); replacers.add(SimpleReplacer.createPng("XBA")); replacers.add(SimpleReplacer.createPng("XBB")); replacers.add(SimpleReplacer.createPng("XBX")); replacers.add(SimpleReplacer.createPng("XBY")); replacers.add(SimpleReplacer.createPng("PSC")); replacers.add(SimpleReplacer.createPng("PSS")); replacers.add(SimpleReplacer.createPng("PST")); replacers.add(SimpleReplacer.createPng("PSX")); replacers.add(SimpleReplacer.createPng("WiiH")); replacers.add(SimpleReplacer.createPng("Wii1")); replacers.add(SimpleReplacer.createPng("Wii2")); replacers.add(SimpleReplacer.createPng("WiiA")); replacers.add(SimpleReplacer.createPng("WiiB")); replacers.add(SimpleReplacer.createPng("WiiC")); replacers.add(SimpleReplacer.createPng("WiiX")); replacers.add(SimpleReplacer.createPng("WiiY")); replacers.add(SimpleReplacer.createPng("WiiZ")); replacers.add(SimpleReplacer.createPng("Wii\\+")); replacers.add(SimpleReplacer.createPng("Wii-")); replacers.add(SimpleReplacer.createImage("!block", "bang_block.png")); replacers.add(SimpleReplacer.createImage("\\?block", "question_block.png")); replacers.add(SimpleReplacer.createImage("blank", "tiles/BLANK.gif")); replacers.add(new UpperCaseReplacer("\\:([A-Za-z])\\:", "<img src=\"" + STATIC_IMAGES_URL + "tiles/", ".gif\"/>")); replacers.add(new Replacer("\\:k([A-Za-z])\\:", "<img src=\"" + STATIC_IMAGES_URL + "k", ".png\"/>")); } private void createPair(String tag) { replacers.add(SimpleReplacer.createStart(tag)); replacers.add(SimpleReplacer.createEnd(tag)); } private void createPair(String tag, String replacementTag) { replacers.add(SimpleReplacer.createStart(tag, replacementTag)); replacers.add(SimpleReplacer.createEnd(tag, replacementTag)); } private void createCamelImage(String color) { replacers.add(SimpleReplacer.createImage(color + "camel", "camel_" + color + ".gif")); } private void createTajImage(String color) { replacers.add(SimpleReplacer.createImage(color + "taj", "taj_" + color + ".gif")); } private void createTrainImage(String color) { replacers.add(SimpleReplacer.createImage(color + "train", "ttr_" + color + ".gif")); } public String toHtml(String text) { if (TextUtils.isEmpty(text)) { return ""; } for (Replaceable replacer : replacers) { text = replacer.replace(text); } text = "<div style=\"white-space: pre-wrap\">" + text + "</div>"; return text; } public String strip(String text) { if (TextUtils.isEmpty(text)) { return ""; } for (Replaceable replacer : replacers) { text = replacer.strip(text); } return text; } interface Replaceable { String replace(String text); String strip(String text); } private static class SimpleReplacer implements Replaceable { final Pattern pattern; final String replacement; public SimpleReplacer(String pattern, String replacement) { this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); this.replacement = replacement; } public static SimpleReplacer createStart(String tag) { return new SimpleReplacer("\\[" + tag + "\\]", "<" + tag + ">"); } public static SimpleReplacer createStart(String tag, String replacementTag) { return new SimpleReplacer("\\[" + tag + "\\]", "<" + replacementTag + ">"); } public static SimpleReplacer createEnd(String tag) { return new SimpleReplacer("\\[/" + tag + "\\]", "</" + tag + ">"); } public static SimpleReplacer createEnd(String tag, String replacementTag) { return new SimpleReplacer("\\[/" + tag + "\\]", "</" + replacementTag + ">"); } public static SimpleReplacer createGif(String image) { return new SimpleReplacer("\\:" + image + "\\:", "<img src=\"" + STATIC_IMAGES_URL + image + ".gif\"/>"); } public static SimpleReplacer createPng(String image) { return new SimpleReplacer("\\:" + image + "\\:", "<img src=\"" + STATIC_IMAGES_URL + image + ".png\"/>"); } public static SimpleReplacer createImage(String image, String imageFile) { return new SimpleReplacer("\\:" + image + "\\:", "<img src=\"" + STATIC_IMAGES_URL + imageFile + "\"/>"); } public static SimpleReplacer createCustomImage(String image, String imageFile) { return new SimpleReplacer(image, "<img src=\"" + STATIC_IMAGES_URL + imageFile + "\"/>"); } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); if (matcher.find()) { return matcher.replaceAll(replacement); } return text; } @Override public String strip(String text) { return pattern.matcher(text).replaceAll(""); } } private static class Replacer implements Replaceable { private final Pattern pattern; private final String prefix; private final String suffix; public Replacer(String pattern, String prefix, String suffix) { this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); this.prefix = prefix; this.suffix = suffix; } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(buffer, prefix + matcher.group(1) + suffix); } matcher.appendTail(buffer); return buffer.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(buffer, matcher.group(1)); } matcher.appendTail(buffer); return buffer.toString(); } } private static class UpperCaseReplacer implements Replaceable { private final Pattern pattern; private final String prefix; private final String suffix; public UpperCaseReplacer(String pattern, String prefix, String suffix) { this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); this.prefix = prefix; this.suffix = suffix; } @SuppressLint("DefaultLocale") @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(buffer, prefix + matcher.group(1).toUpperCase() + suffix); } matcher.appendTail(buffer); return buffer.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer buffer = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(buffer, matcher.group(1).toUpperCase()); } matcher.appendTail(buffer); return buffer.toString(); } } public static class UrlReplacer implements Replaceable { private final Pattern pattern; public UrlReplacer() { pattern = Pattern.compile("\\[url\\](.*?)\\[/url\\]", Pattern.CASE_INSENSITIVE); } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String url = HttpUtils.ensureScheme(matcher.group(1)); matcher.appendReplacement(result, "<a href=\"" + url + "\">" + url + "</a>"); } matcher.appendTail(result); return result.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { matcher.appendReplacement(result, HttpUtils.ensureScheme(matcher.group(1))); } matcher.appendTail(result); return result.toString(); } } public static class UrlReplacer2 implements Replaceable { private final Pattern pattern; public UrlReplacer2() { pattern = Pattern.compile("\\[url=(.*?)\\](.*?)\\[/url\\]", Pattern.CASE_INSENSITIVE); } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String url = HttpUtils.ensureScheme(matcher.group(1)); String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, "<a href=\"" + url + "\">" + url + "</a>"); } else { matcher.appendReplacement(result, "<a href=\"" + url + "\">" + displayText + "</a>"); } } matcher.appendTail(result); return result.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, HttpUtils.ensureScheme(matcher.group(1))); } else { matcher.appendReplacement(result, displayText); } } matcher.appendTail(result); return result.toString(); } } public static class GeekUrlReplacer implements Replaceable { private final Pattern pattern; public GeekUrlReplacer() { pattern = Pattern.compile("\\[geekurl=(.*?)\\](.*?)\\[/geekurl\\]", Pattern.CASE_INSENSITIVE); } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, "<a href=\"" + BASE_URL + matcher.group(1) + "\">" + matcher.group(1) + "</a>"); } else { matcher.appendReplacement(result, "<a href=\"" + BASE_URL + matcher.group(1) + "\">" + displayText + "</a>"); } } matcher.appendTail(result); return result.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, matcher.group(1)); } else { matcher.appendReplacement(result, displayText); } } matcher.appendTail(result); return result.toString(); } } /*** * Replaces a GeekLink with an HREF tag. * [thing=13]Catan[/thing] becomes <a href="https://boardgamegeek.com/thing/13">Catan</a> * [thing=13][/thing] becomes <a href="https://boardgamegeek.com/thing/13">thing 13</a> */ public static class GeekLinkReplacer implements Replaceable { private final Pattern pattern; private final String url; private final String displayPrefix; private GeekLinkReplacer(String pattern, String url, String displayPrefix) { this.pattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); this.url = url; this.displayPrefix = displayPrefix; } static GeekLinkReplacer createAlpha(String path) { return new GeekLinkReplacer("\\[" + path + "=(.*?)\\](.*?)\\[/" + path + "\\]", BASE_URL + "/" + path + "/", path); } static GeekLinkReplacer createNumeric(String path) { return createNumeric(path, path); } static GeekLinkReplacer createNumeric(String path, String display) { return new GeekLinkReplacer("\\[" + path + "=(\\d+)\\](.*?)\\[/" + path + "\\]", BASE_URL + "/" + path + "/", display); } @Override public String replace(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, "<a href=\"" + url + matcher.group(1) + "\">" + displayPrefix + " " + matcher.group(1) + "</a>"); } else { matcher.appendReplacement(result, "<a href=\"" + url + matcher.group(1) + "\">" + displayText + "</a>"); } } matcher.appendTail(result); return result.toString(); } @Override public String strip(String text) { Matcher matcher = pattern.matcher(text); StringBuffer result = new StringBuffer(); while (matcher.find()) { String displayText = matcher.group(2); if (TextUtils.isEmpty(displayText)) { matcher.appendReplacement(result, displayPrefix + " " + matcher.group(1)); } else { matcher.appendReplacement(result, displayText); } } matcher.appendTail(result); return result.toString(); } } }