Created
June 10, 2019 13:58
-
-
Save DarkSeraphim/30d7f6cd1bdd6efb0f38163ed99215bc to your computer and use it in GitHub Desktop.
Formatting Markdown to HTML
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.util.Deque; | |
import java.util.Objects; | |
import java.util.Queue; | |
import java.util.Set; | |
import java.util.HashSet; | |
import java.util.LinkedList; | |
import java.util.Map; | |
import java.util.HashMap; | |
class MarkdownFormatter { | |
static class FormatToken { | |
private final String tag; | |
private StringBuilder content = null; | |
FormatToken(String tag) { | |
this.tag = tag; | |
} | |
public String getTag() { | |
return this.tag; | |
} | |
public int hashCode() { | |
return this.tag.hashCode(); | |
} | |
public boolean equals(Object o) { | |
if (o == this) { | |
return true; | |
} | |
if (o == null || o.getClass() != this.getClass()) { | |
return false; | |
} | |
FormatToken that = (FormatToken) o; | |
return this.tag.equals(that.tag); | |
} | |
public void appendContent(StringBuilder content) { | |
if (this.content == null) { | |
this.content = content; | |
} else { | |
this.content.append(content); | |
} | |
} | |
public StringBuilder getContent() { | |
return this.content; | |
} | |
} | |
private static class HtmlTag { | |
private final String open, close; | |
public HtmlTag(String name) { | |
this(String.format("<%s>", name), String.format("</%s>", name)); | |
} | |
public HtmlTag(String open, String close) { | |
this.open = open; | |
this.close = close; | |
} | |
public String getOpenTag() { | |
return this.open; | |
} | |
public String getCloseTag() { | |
return this.close; | |
} | |
} | |
private final Set<Character> special = new HashSet<>(); | |
private final Map<FormatToken, HtmlTag> html = new HashMap<>(); | |
public MarkdownFormatter() { | |
this.registerTag("**", new HtmlTag("strong")); | |
this.registerTag("_", new HtmlTag("em")); | |
this.registerTag("~", new HtmlTag("s")); | |
this.registerTag("`", new HtmlTag("code")); | |
} | |
private void registerTag(String tag, HtmlTag html) { | |
this.special.add(tag.charAt(0)); | |
this.html.put(new FormatToken(tag), html); | |
} | |
public String toHTML(String input) { | |
Deque<FormatToken> formattingStack = new LinkedList<>(); | |
Map<FormatToken, Integer> present = new HashMap<>(); | |
FormatToken head = null; | |
StringBuilder result = new StringBuilder(); | |
int lm1 = input.length() - 1; | |
for (int i = 0; i < input.length(); i++) { | |
char c = input.charAt(i); | |
if (special.contains(c)) { | |
StringBuilder sb = new StringBuilder(3).append(c); | |
while (i < lm1 && input.charAt(i + 1) == c) { | |
sb.append(c); | |
i++; | |
} | |
FormatToken token = new FormatToken(sb.toString()); | |
if (present.containsKey(token)) { | |
sb = new StringBuilder(); | |
FormatToken open; | |
while ((open = formattingStack.pollLast()) != null) { | |
if (open.getContent() != null) { | |
sb.insert(0, open.getContent()); | |
} | |
present.computeIfPresent(open, ($, counter) -> counter != 1 ? counter - 1 : null); | |
if (open.equals(token)) { | |
// Matching but unknown tags should be treated as is | |
HtmlTag tag = this.html.computeIfAbsent(open, o -> new HtmlTag(o.getTag(), token.getTag())); | |
sb.insert(0, tag.getOpenTag()); | |
sb.append(tag.getCloseTag()); | |
break; | |
} else { | |
sb.insert(0, open.getTag()); | |
} | |
} | |
head = formattingStack.peekLast(); | |
if (head == null) { | |
result.append(sb); | |
} else { | |
head.appendContent(sb); | |
} | |
} else { | |
present.merge(token, 1, Integer::sum); | |
head = token; | |
formattingStack.offer(token); | |
} | |
} else { | |
StringBuilder sb = new StringBuilder().append(c); | |
while (i < lm1 && !special.contains(input.charAt(i + 1))) { | |
sb.append(input.charAt(i + 1)); | |
i++; | |
} | |
if (head == null) { | |
result.append(sb); | |
} else { | |
head.appendContent(sb); | |
} | |
} | |
} | |
return result.toString(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment