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", "", "");
+ }
+}