From 093914fb1cb263cd6dfa9fdf2b13ea692acf4813 Mon Sep 17 00:00:00 2001 From: codejava Date: Mon, 13 Apr 2026 10:46:04 +0300 Subject: [PATCH] Upload files to "java/md2html" --- java/md2html/Md2Html.java | 82 ++++++ java/md2html/Md2HtmlTest.java | 117 +++++++++ java/md2html/Md2HtmlTester.java | 427 ++++++++++++++++++++++++++++++++ java/md2html/Pre.java | 11 + java/md2html/package-info.java | 8 + 5 files changed, 645 insertions(+) create mode 100644 java/md2html/Md2Html.java create mode 100644 java/md2html/Md2HtmlTest.java create mode 100644 java/md2html/Md2HtmlTester.java create mode 100644 java/md2html/Pre.java create mode 100644 java/md2html/package-info.java diff --git a/java/md2html/Md2Html.java b/java/md2html/Md2Html.java new file mode 100644 index 0000000..fd993c6 --- /dev/null +++ b/java/md2html/Md2Html.java @@ -0,0 +1,82 @@ +package md2html; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import markup.*; + +public class Md2Html { + + public static void main(String[] args) { + String text = readFile(args[0]); + BlockCreator creator = new BlockCreator(text); + List blocks = creator.divideByBlocks(); + + try ( + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(args[1]), + StandardCharsets.UTF_8 + ) + ) + ) { + for (int i = 0; i < blocks.size(); i++) { + PrimePartCreator creatorPrime = new PrimePartCreator( + blocks.get(i) + ); + PrimePart part = creatorPrime.createPart(); + StringBuilder sb = new StringBuilder(); + part.toHtml(sb); + writer.write(sb.toString()); + if (i < blocks.size() - 1) { + writer.write(System.lineSeparator()); + } + sb.setLength(0); // освобождаем память + } + } catch (IOException e) { + System.out.println("IOException: " + e.getMessage()); + } + } + + public static String readFile(String nameOfFile) { + try ( + BufferedReader reader = new BufferedReader( + new InputStreamReader( + new FileInputStream(nameOfFile), + StandardCharsets.UTF_8 + ) + ) + ) { + StringBuilder text = new StringBuilder(); + int read = reader.read(); + while (read != -1) { + text.append((char) read); + read = reader.read(); + } + return text.toString(); + } catch (FileNotFoundException e) { + System.out.println("Input file not found: " + e.getMessage()); + } catch (IOException e) { + System.out.println("IOException file not found: " + e.getMessage()); + } + return null; + } + + public static void writeToFile(String nameOfFile, String text) { + try ( + BufferedWriter writer = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(nameOfFile), + StandardCharsets.UTF_8 + ) + ) + ) { + writer.write(text); + } catch (FileNotFoundException e) { + System.out.println("Output file not found: " + e.getMessage()); + } catch (IOException e) { + System.out.println("IOException file not found: " + e.getMessage()); + } + } +} diff --git a/java/md2html/Md2HtmlTest.java b/java/md2html/Md2HtmlTest.java new file mode 100644 index 0000000..8a4240a --- /dev/null +++ b/java/md2html/Md2HtmlTest.java @@ -0,0 +1,117 @@ +package md2html; + +import base.Selector; +import java.util.function.Consumer; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public final class Md2HtmlTest { + + // === 3637 + private static final Consumer INS = tester -> + tester + .test("<<вставка>>", "

вставка

") + .test( + "Это <<вставка>>, вложенная в текст", + "

Это вставка, вложенная в текст

" + ) + .spoiled( + "Это не <<вставка>>", + "

Это не <<вставка>>

", + "<", + ">" + ) + .spoiled( + "Это не <<вставка>> 2", + "

Это не <<вставка>> 2

", + "<", + ">" + ) + .addElement("ins", "<<", ">>"); + private static final Consumer DEL = tester -> + tester + .test("}}удаление{{", "

удаление

") + .test( + "Это }}удаление{{, вложенное в текст", + "

Это удаление, вложенное в текст

" + ) + .spoiled("Это не }}удаление{{", "

Это не }}удаление{{

", "{") + .spoiled( + "Это не }}удаление{{ 2", + "

Это не }}удаление{{ 2

", + "{" + ) + .addElement("del", "}}", "{{"); + + // === 3839 + private static final Consumer PRE = tester -> + tester + .test( + "```код __без__ форматирования```", + "

код __без__ форматирования

" + ) + .test( + "Это не `\\``код __без__ форматирования``\\`", + "

Это не `код без форматирования`

" + ) + .addElement("pre", "```", (checker, markup, input, output) -> { + final String contentS = checker + .generateInput(markup) + .replace("`", ""); + + input.append("```").append(contentS).append("```"); + output + .append("
")
+                    .append(contentS.replace("<", "<").replace(">", ""))
+                    .append("
"); + }); + + // === 3435 + private static final Consumer SAMP = tester -> + tester + .test("!!пример!!", "

пример

") + .test( + "Это !!пример!!, вложенный в текст", + "

Это пример, вложенный в текст

" + ) + .spoiled("Это не !!пример!!", "

Это не !!пример!!

", "!") + .spoiled("Это не !!пример!! 2", "

Это не !!пример!! 2

", "!") + .addElement("samp", "!!"); + + // === 3233 + private static final Consumer VAR = tester -> + tester + .test("%переменная%", "

переменная

") + .test( + "Это %переменная%, вложенная в текст", + "

Это переменная, вложенная в текст

" + ) + .spoiled("Это не %переменная%", "

Это не %переменная%

", "%") + .spoiled( + "Это не %переменная% 2", + "

Это не %переменная% 2

", + "%" + ) + .addElement("var", "%"); + + // === Common + + public static final Selector SELECTOR = Selector.composite( + Md2HtmlTest.class, + c -> new Md2HtmlTester(), + Md2HtmlTester::test + ) + .variant("Base") + .variant("3637", INS, DEL) + .variant("3839", PRE) + .variant("3435", SAMP) + .variant("3233", VAR) + .selector(); + + private Md2HtmlTest() {} + + public static void main(final String... args) { + SELECTOR.main(args); + } +} diff --git a/java/md2html/Md2HtmlTester.java b/java/md2html/Md2HtmlTester.java new file mode 100644 index 0000000..362a0bf --- /dev/null +++ b/java/md2html/Md2HtmlTester.java @@ -0,0 +1,427 @@ +package md2html; + +import base.*; +import java.util.*; + +/** + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +public class Md2HtmlTester { + + private static final Map ESCAPES = Map.of( + "<", + "<", + ">", + ">" + ); + + private final Map elements = new HashMap<>(); + private final Map> tags = new LinkedHashMap<>(); + private final List> tests = new ArrayList<>(); + + public Md2HtmlTester() { + addElement("em", "*"); + addElement("em", "_"); + addElement("strong", "**"); + addElement("strong", "__"); + addElement("s", "--"); + addElement("code", "`"); + + test( + "# Заголовок первого уровня\n\n", + "

Заголовок первого уровня

" + ); + test("## Второго\n\n", "

Второго

"); + test("### Третьего ## уровня\n\n", "

Третьего ## уровня

"); + test( + "#### Четвертого\n# Все еще четвертого\n\n", + "

Четвертого\n# Все еще четвертого

" + ); + test( + "Этот абзац текста\nсодержит две строки.", + "

Этот абзац текста\nсодержит две строки.

" + ); + test( + " # Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с `#`.\n\n", + "

# Может показаться, что это заголовок.\nНо нет, это абзац, начинающийся с #.

" + ); + test("#И это не заголовок.\n\n", "

#И это не заголовок.

"); + test( + "###### Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)\n\n", + "
Заголовки могут быть многострочными\n(и с пропуском заголовков предыдущих уровней)
" + ); + test( + "Мы все любим *выделять* текст _разными_ способами.\n**Сильное выделение**, используется гораздо реже,\nно __почему бы и нет__?\nНемного --зачеркивания-- еще никому не вредило.\nКод представляется элементом `code`.\n\n", + "

Мы все любим выделять текст разными способами.\nСильное выделение, используется гораздо реже,\nно почему бы и нет?\nНемного зачеркивания еще никому не вредило.\nКод представляется элементом code.

" + ); + test( + "Обратите внимание, как экранируются специальные\nHTML-символы, такие как `<`, `>` и `&`.\n\n", + "

Обратите внимание, как экранируются специальные\nHTML-символы, такие как <, > и &.

" + ); + test( + "Экранирование должно работать во всех местах: <>&.\n\n", + "

Экранирование должно работать во всех местах: <>&.

" + ); + test( + "Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: \\*.", + "

Знаете ли вы, что в Markdown, одиночные * и _\nне означают выделение?\nОни так же могут быть заэкранированы\nпри помощи обратного слэша: *.

" + ); + test( + "\n\n\nЛишние пустые строки должны игнорироваться.\n\n\n\n", + "

Лишние пустые строки должны игнорироваться.

" + ); + test( + "Любите ли вы *вложенные __выделения__* так,\nкак __--люблю--__ их я?", + "

Любите ли вы вложенные выделения так,\nкак люблю их я?

" + ); + + test( + """ + # Заголовок первого уровня + + ## Второго + + ### Третьего ## уровня + + #### Четвертого + # Все еще четвертого + + Этот абзац текста + содержит две строки. + + # Может показаться, что это заголовок. + Но нет, это абзац, начинающийся с `#`. + + #И это не заголовок. + + ###### Заголовки могут быть многострочными + (и с пропуском заголовков предыдущих уровней) + + Мы все любим *выделять* текст _разными_ способами. + **Сильное выделение**, используется гораздо реже, + но __почему бы и нет__? + Немного --зачеркивания-- еще никому не вредило. + Код представляется элементом `code`. + + Обратите внимание, как экранируются специальные + HTML-символы, такие как `<`, `>` и `&`. + + Знаете ли вы, что в Markdown, одиночные * и _ + не означают выделение? + Они так же могут быть заэкранированы + при помощи обратного слэша: \\*. + + + + Лишние пустые строки должны игнорироваться. + + Любите ли вы *вложенные __выделения__* так, + как __--люблю--__ их я? + """, + """ +

Заголовок первого уровня

+

Второго

+

Третьего ## уровня

+

Четвертого + # Все еще четвертого

+

Этот абзац текста + содержит две строки.

+

# Может показаться, что это заголовок. + Но нет, это абзац, начинающийся с #.

+

#И это не заголовок.

+
Заголовки могут быть многострочными + (и с пропуском заголовков предыдущих уровней)
+

Мы все любим выделять текст разными способами. + Сильное выделение, используется гораздо реже, + но почему бы и нет? + Немного зачеркивания еще никому не вредило. + Код представляется элементом code.

+

Обратите внимание, как экранируются специальные + HTML-символы, такие как <, > и &.

+

Знаете ли вы, что в Markdown, одиночные * и _ + не означают выделение? + Они так же могут быть заэкранированы + при помощи обратного слэша: *.

+

Лишние пустые строки должны игнорироваться.

+

Любите ли вы вложенные выделения так, + как люблю их я?

+ """ + ); + + test( + "# Без перевода строки в конце", + "

Без перевода строки в конце

" + ); + test( + "# Один перевод строки в конце\n", + "

Один перевод строки в конце

" + ); + test( + "# Два перевода строки в конце\n\n", + "

Два перевода строки в конце

" + ); + test( + "Выделение может *начинаться на одной строке,\n а заканчиваться* на другой", + "

Выделение может начинаться на одной строке,\n а заканчиваться на другой

" + ); + test( + "# *Выделение* и `код` в заголовках", + "

Выделение и код в заголовках

" + ); + } + + protected void addElement(final String tag, final String markup) { + addElement(tag, markup, markup); + } + + protected void addElement( + final String tag, + final String begin, + final String end + ) { + addElement(tag, begin, (checker, markup, input, output) -> { + checker.space(input, output); + input.append(begin); + open(output, tag); + + checker.word(input, output); + checker.generate(markup, input, output); + checker.word(input, output); + + input.append(end); + close(output, tag); + checker.space(input, output); + }); + } + + public void addElement( + final String tag, + final String begin, + final Generator generator + ) { + Asserts.assertTrue( + "Duplicate element " + begin, + elements.put(begin, generator) == null + ); + tags.computeIfAbsent(tag, k -> new ArrayList<>()).add(begin); + } + + private final Runner runner = Runner.packages("md2html").files("Md2Html"); + + protected Md2HtmlTester test(final String input, final String output) { + tests.add(Pair.of(input, output)); + return this; + } + + protected Md2HtmlTester spoiled( + final String input, + final String output, + final String... spoilers + ) { + for (final String spoiler : spoilers) { + final Indexer in = new Indexer(input, spoiler); + final Indexer out = new Indexer( + output, + ESCAPES.getOrDefault(spoiler, spoiler) + ); + while (in.next() && out.next()) { + tests.add(Pair.of(in.cut(), out.cut())); + tests.add(Pair.of(in.escape(), output)); + } + } + return this; + } + + private static class Indexer { + + private final String string; + private final String spoiler; + private int index = -1; + + public Indexer(final String string, final String spoiler) { + this.string = string; + this.spoiler = spoiler; + } + + public boolean next() { + index = string.indexOf(spoiler, index + 1); + return index >= 0; + } + + public String cut() { + return ( + string.substring(0, index) + + string.substring(index + spoiler.length()) + ); + } + + public String escape() { + return string.substring(0, index) + "\\" + string.substring(index); + } + } + + private static void open(final StringBuilder output, final String tag) { + output.append("<").append(tag).append(">"); + } + + private static void close(final StringBuilder output, final String tag) { + output.append(""); + } + + public void test(final TestCounter counter) { + counter.scope("Testing " + String.join(", ", tags.keySet()), () -> + new Checker(counter).test() + ); + } + + public class Checker extends BaseChecker { + + public Checker(final TestCounter counter) { + super(counter); + } + + protected void test() { + for (final Pair test : tests) { + test(test); + } + + for (final String markup : elements.keySet()) { + randomTest(3, 10, List.of(markup)); + } + + final int d = TestCounter.DENOMINATOR; + for (int i = 0; i < 10; i++) { + randomTest(100, 1000, randomMarkup()); + } + randomTest(100, 100_000 / d, randomMarkup()); + } + + private void test(final Pair test) { + runner.testEquals( + counter, + Arrays.asList(test.first().split("\n")), + Arrays.asList(test.second().split("\n")) + ); + } + + private List randomMarkup() { + return Functional.map(tags.values(), random()::randomItem); + } + + private void randomTest( + final int paragraphs, + final int length, + final List markup + ) { + final StringBuilder input = new StringBuilder(); + final StringBuilder output = new StringBuilder(); + emptyLines(input); + final List markupList = new ArrayList<>(markup); + for (int i = 0; i < paragraphs; i++) { + final StringBuilder inputSB = new StringBuilder(); + paragraph(length, inputSB, output, markupList); + input.append(inputSB); + emptyLines(input); + } + test(Pair.of(input.toString(), output.toString())); + } + + private void paragraph( + final int length, + final StringBuilder input, + final StringBuilder output, + final List markup + ) { + final int h = random().nextInt(0, 6); + final String tag = h == 0 ? "p" : "h" + h; + if (h > 0) { + input + .append(new String(new char[h]).replace('\0', '#')) + .append(" "); + } + + open(output, tag); + while (input.length() < length) { + generate(markup, input, output); + final String middle = random().randomString( + ExtendedRandom.ENGLISH + ); + input.append(middle).append("\n"); + output.append(middle).append("\n"); + } + output.setLength(output.length() - 1); + close(output, tag); + + output.append("\n"); + input.append("\n"); + } + + private void space( + final StringBuilder input, + final StringBuilder output + ) { + if (random().nextBoolean()) { + final String space = random().nextBoolean() ? " " : "\n"; + input.append(space); + output.append(space); + } + } + + public void generate( + final List markup, + final StringBuilder input, + final StringBuilder output + ) { + word(input, output); + if (markup.isEmpty()) { + return; + } + final String type = random().randomItem(markup); + + markup.remove(type); + elements.get(type).generate(this, markup, input, output); + markup.add(type); + } + + protected void word( + final StringBuilder input, + final StringBuilder output + ) { + final String word = random().randomString( + random().randomItem( + ExtendedRandom.ENGLISH, + ExtendedRandom.GREEK, + ExtendedRandom.RUSSIAN + ) + ); + input.append(word); + output.append(word); + } + + private void emptyLines(final StringBuilder sb) { + while (random().nextBoolean()) { + sb.append('\n'); + } + } + + String generateInput(final List markup) { + final StringBuilder sb = new StringBuilder(); + generate(markup, sb, new StringBuilder()); + return sb + .toString() + .replace("<", "") + .replace(">", "") + .replace("]", ""); + } + } + + @FunctionalInterface + public interface Generator { + void generate( + Checker checker, + List markup, + StringBuilder input, + StringBuilder output + ); + } +} diff --git a/java/md2html/Pre.java b/java/md2html/Pre.java new file mode 100644 index 0000000..d191ee4 --- /dev/null +++ b/java/md2html/Pre.java @@ -0,0 +1,11 @@ +package md2html; + +import java.util.List; +import markup.*; + +public class Pre extends AbstractMarkup implements PartOfParagraph { + + public Pre(List items) { + super(items, "```", "pre", "", ""); + } +} diff --git a/java/md2html/package-info.java b/java/md2html/package-info.java new file mode 100644 index 0000000..ed6c2c3 --- /dev/null +++ b/java/md2html/package-info.java @@ -0,0 +1,8 @@ +/** + * Tests for Markdown to HTML homework + * of Introduction to Programming course. + * + * + * @author Georgiy Korneev (kgeorgiy@kgeorgiy.info) + */ +package md2html;