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); } } }