diff --git a/java/md2html/PrimePartCreator.java b/java/md2html/PrimePartCreator.java new file mode 100644 index 0000000..c87e48b --- /dev/null +++ b/java/md2html/PrimePartCreator.java @@ -0,0 +1,390 @@ +package md2html; + +import java.util.ArrayList; +import java.util.List; +import markup.*; + +public class PrimePartCreator { + + private final String block; + private int currentChar; + + public enum MarkdownToken { + WORD, + EMPHASIS_STAR, + STRONG_STAR, + EMPHASIS_UNDERLINE, + STRONG_UNDERLINE, + STRIKEOUT, + CODE, + SCREENING, + VAR, + SAMP, + INS, + DEL, + PRE, + NOTHING, + } + + public PrimePartCreator(String block) { + this.block = block; + currentChar = 0; + } + + public PrimePart createPart() { + PrimePart result; + if (isHeader()) { + int levelOfHeader = 0; + while ( + levelOfHeader < block.length() && + block.charAt(levelOfHeader) == '#' + ) { + levelOfHeader++; + } + currentChar = levelOfHeader + 1; + result = new Header(buildPart(MarkdownToken.WORD), levelOfHeader); + } else { + currentChar = 0; + result = new Paragraph(buildPart(MarkdownToken.WORD)); + } + return result; + } + + private List buildPart(MarkdownToken currentToken) { + List items = new ArrayList<>(); + MarkdownToken token = nextMarkdownToken(); + StringBuilder sb = new StringBuilder(); + while (token != MarkdownToken.NOTHING) { + if ( + (token == currentToken && currentToken != MarkdownToken.WORD) || + (token != MarkdownToken.SCREENING && isSuffix(currentToken)) + ) { + addToList(items, sb); + if ( + !(token == currentToken && + currentToken != MarkdownToken.WORD) + ) { + currentChar++; + } + break; + } + switch (token) { + case STRIKEOUT -> { + addToList(items, sb); + Strikeout strikeout = new Strikeout(buildPart(token)); + items.add(strikeout); + } + case STRONG_STAR, STRONG_UNDERLINE -> { + addToList(items, sb); + Strong strong = new Strong(buildPart(token)); + items.add(strong); + } + case EMPHASIS_STAR, EMPHASIS_UNDERLINE -> { + addToList(items, sb); + Emphasis emphasis = new Emphasis(buildPart(token)); + items.add(emphasis); + } + case SCREENING -> { + addToList(items, sb); + char ch = block.charAt(currentChar); + if (ch == '<') { + sb.append("<"); + } else if (ch == '>') { + sb.append(">"); + } else if (ch == '&') { + sb.append("&"); + } else { + sb.append(ch); + } + currentChar++; + } + case CODE -> { + addToList(items, sb); + Code code = new Code(buildPart(token)); + items.add(code); + } + case VAR -> { + addToList(items, sb); + Var var = new Var(buildPart(token)); + items.add(var); + } + case SAMP -> { + addToList(items, sb); + Samp samp = new Samp(buildPart(token)); + items.add(samp); + } + case INS -> { + addToList(items, sb); + Ins ins = new Ins(buildPart(token)); + items.add(ins); + } + case DEL -> { + addToList(items, sb); + Del del = new Del(buildPart(token)); + items.add(del); + } + case PRE -> { + addToList(items, sb); + + StringBuilder raw = new StringBuilder(); + while (currentChar < block.length()) { + if ( + isWordInBlock("```", currentChar, currentChar + 3) + ) { + currentChar += 3; + break; + } + char ch = block.charAt(currentChar); + if (ch == '<') { + raw.append("<"); + } else if (ch == '>') { + } else if (ch == '&') { + raw.append("&"); + } else { + raw.append(ch); + } + currentChar++; + } + items.add(new Pre(List.of(new Text(raw.toString())))); + } + default -> { + if (currentChar < block.length()) { + if (block.charAt(currentChar) == '<') { + sb.append("<"); + } else if (block.charAt(currentChar) == '>') { + sb.append(">"); + } else if (block.charAt(currentChar) == '&') { + sb.append("&"); + } else { + sb.append(block.charAt(currentChar)); + } + currentChar++; + } + } + } + token = nextMarkdownToken(); + } + if (token == MarkdownToken.NOTHING) { + addToList(items, sb); + } + return items; + } + + private MarkdownToken nextMarkdownToken() { + if (currentChar == block.length()) { + return MarkdownToken.NOTHING; + } + switch (block.charAt(currentChar)) { + case '*' -> { + if (isPrefix("**")) { + currentChar += 2; + return MarkdownToken.STRONG_STAR; + } else if (isPrefix("*")) { + currentChar += 1; + return MarkdownToken.EMPHASIS_STAR; + } + } + case '_' -> { + if (isPrefix("__")) { + currentChar += 2; + return MarkdownToken.STRONG_UNDERLINE; + } else if (isPrefix("_")) { + currentChar += 1; + return MarkdownToken.EMPHASIS_UNDERLINE; + } + } + case '-' -> { + if (isPrefix("--")) { + currentChar += 2; + return MarkdownToken.STRIKEOUT; + } + } + case '`' -> { + if ( + isWordInBlock("```", currentChar, currentChar + 3) && + hasClosing("```", currentChar + 3) + ) { + currentChar += 3; + return MarkdownToken.PRE; + } else if (hasClosing("`", currentChar + 1)) { + currentChar += 1; + return MarkdownToken.CODE; + } + } + case '\\' -> { + if (isScreening(block.charAt(currentChar + 1))) { + currentChar++; + return MarkdownToken.SCREENING; + } + } + case '%' -> { + if (isPrefix("%") && hasClosing("%", currentChar + 1)) { + currentChar += 1; + return MarkdownToken.VAR; + } + } + case '!' -> { + if (isPrefix("!!") && hasClosing("!!", currentChar + 2)) { + currentChar += 2; + return MarkdownToken.SAMP; + } + } + case '<' -> { + if ( + isWordInBlock("<<", currentChar, currentChar + 2) && + hasClosing(">>", currentChar + 2) + ) { + currentChar += 2; + return MarkdownToken.INS; + } + } + case '}' -> { + if ( + isWordInBlock("}}", currentChar, currentChar + 2) && + hasClosing("{{", currentChar + 2) + ) { + currentChar += 2; + return MarkdownToken.DEL; + } + } + default -> { + return MarkdownToken.WORD; + } + } + return MarkdownToken.WORD; + } + + private boolean hasClosing(String delimiter, int from) { + for (int j = from; j + delimiter.length() <= block.length(); j++) { + if (isWordInBlock(delimiter, j, j + delimiter.length())) { + if (j > 0 && block.charAt(j - 1) == '\\') { + continue; + } + if (delimiter.equals("`")) { + if (isWordInBlock("```", j, j + 3)) continue; + if (j >= 1 && isWordInBlock("```", j - 1, j + 2)) continue; + if (j >= 2 && isWordInBlock("```", j - 2, j + 1)) continue; + } + return true; + } + } + return false; + } + + private static boolean isScreening(char ch) { + return ( + ch == '*' || + ch == '_' || + ch == '%' || + ch == '!' || + ch == '<' || + ch == '>' || + ch == '}' || + ch == '{' || + ch == '`' + ); + } + + private boolean isSuffix(MarkdownToken token) { + if (token == MarkdownToken.WORD) { + return false; + } + String suffix = getHighlight(token); + if (isWordInBlock(suffix, currentChar, currentChar + suffix.length())) { + currentChar += suffix.length() - 1; + return true; + } + return false; + } + + private boolean isPrefix(String prefix) { + int i = currentChar + prefix.length(); + return ( + isValidIndexInBlock(i + prefix.length()) && + !isWordInBlock(prefix, i, i + prefix.length()) && + isWordInBlock(prefix, i - prefix.length(), i) && + !Character.isWhitespace(block.charAt(i)) + ); + } + + private boolean isWordInBlock(String word, int start, int end) { + if (end > block.length() || start < 0) { + return false; + } + return block.regionMatches(start, word, 0, word.length()); + } + + private boolean isValidIndexInBlock(int index) { + return index < block.length(); + } + + private boolean isHeader() { + int levelOfHeader = 0; + if (block.charAt(levelOfHeader) != '#') { + return false; + } + while ( + levelOfHeader < block.length() && block.charAt(levelOfHeader) == '#' + ) { + levelOfHeader++; + } + return ( + levelOfHeader < block.length() && + Character.isWhitespace(block.charAt(levelOfHeader)) + ); + } + + private static String getHighlight(MarkdownToken token) { + switch (token) { + case STRONG_STAR -> { + return "**"; + } + case STRONG_UNDERLINE -> { + return "__"; + } + case EMPHASIS_STAR -> { + return "*"; + } + case EMPHASIS_UNDERLINE -> { + return "_"; + } + case CODE -> { + return "`"; + } + case STRIKEOUT -> { + return "--"; + } + case SCREENING -> { + return "\\"; + } + case VAR -> { + return "%"; + } + case SAMP -> { + return "!!"; + } + case INS -> { + return ">>"; + } + case DEL -> { + return "{{"; + } + case PRE -> { + return "```"; + } + default -> { + return ""; + } + } + } + + private static void addToList( + List items, + StringBuilder sb + ) { + if (!sb.isEmpty()) { + items.add(new Text(sb.toString())); + sb.setLength(0); + } + } +} diff --git a/java/md2html/Samp.java b/java/md2html/Samp.java new file mode 100644 index 0000000..db1eccc --- /dev/null +++ b/java/md2html/Samp.java @@ -0,0 +1,11 @@ +package md2html; + +import java.util.List; +import markup.*; + +public class Samp extends AbstractMarkup implements PartOfParagraph { + + public Samp(List items) { + super(items, "!!", "samp", "", ""); + } +} diff --git a/java/md2html/Var.java b/java/md2html/Var.java new file mode 100644 index 0000000..9b8a2f7 --- /dev/null +++ b/java/md2html/Var.java @@ -0,0 +1,11 @@ +package md2html; + +import java.util.List; +import markup.*; + +public class Var extends AbstractMarkup implements PartOfParagraph { + + public Var(List items) { + super(items, "%", "var", "", ""); + } +}