From 7881420cbd78b3ef65fc0a677e105d8bc117ef72 Mon Sep 17 00:00:00 2001 From: Dimitry Ivanov <mail@dimitryivanov.ru> Date: Fri, 24 Aug 2018 23:10:47 +0300 Subject: [PATCH] Add html file test --- .../html/impl/MarkwonHtmlParserImpl.java | 23 +++- .../html/impl/MarkwonHtmlParserImplTest.java | 24 ++++ .../renderer/html2/MarkwonHtmlRenderer.java | 9 +- .../renderer/html2/tag/HeadingHandler.java | 22 ++++ .../renderer/html2/tag/LinkHandler.java | 2 +- .../visitor/SpannableMarkdownVisitorTest.java | 65 ++++++++++- .../markwon/renderer/visitor/TestFactory.java | 2 +- markwon/src/test/resources/tests/html.yaml | 105 ++++++++++++++++++ .../src/test/resources/tests/single-img.yaml | 2 +- 9 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java create mode 100644 markwon/src/test/resources/tests/html.yaml diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java index 791147d2..86f985a2 100644 --- a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java @@ -119,9 +119,9 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { private boolean isInsidePreTag; - private Tokeniser tokeniser; - - private CharacterReader reader; + // the thing is: we ensure a new line BEFORE block tag + // but not after, so another tag will be placed on the same line (which is wrong) + private boolean previousIsBlock; MarkwonHtmlParserImpl( @@ -242,6 +242,8 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { final HtmlTagImpl.InlineImpl inline = new HtmlTagImpl.InlineImpl(name, output.length(), extractAttributes(startTag)); + ensureNewLineIfPreviousWasBlock(output); + if (isVoidTag(name) || startTag.selfClosing) { @@ -305,6 +307,8 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { if (isBlockTag(name)) { isInsidePreTag = "pre".equals(name); ensureNewLine(output); + } else { + ensureNewLineIfPreviousWasBlock(output); } final int start = output.length(); @@ -350,6 +354,11 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { block.closeAt(output.length()); + // if it's empty -> we do no care about if it's block or not + if (!block.isEmpty()) { + previousIsBlock = isBlockTag(block.name); + } + if (TAG_PARAGRAPH.equals(name)) { appendQuietly(output, '\n'); } @@ -368,6 +377,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { if (isInsidePreTag) { appendQuietly(output, character.getData()); } else { + ensureNewLineIfPreviousWasBlock(output); trimmingAppender.append(output, character.getData()); } } @@ -410,6 +420,13 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { return blockTag; } + protected <T extends Appendable & CharSequence> void ensureNewLineIfPreviousWasBlock(@NonNull T output) { + if (previousIsBlock) { + ensureNewLine(output); + previousIsBlock = false; + } + } + // name here must lower case protected static boolean isInlineTag(@NonNull String name) { return INLINE_TAGS.contains(name); diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java index 3edcfeb1..ad0669b3 100644 --- a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java @@ -833,6 +833,30 @@ public class MarkwonHtmlParserImplTest { }); } + @Test + public void newLineAfterBlockTag() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String[] fragments = { + "<h1>head #1</h1>just text", + "<h2>head #2</h2><span>in span tag</span>", + "<h3>head #3</h3><custom-tag>in custom-tag</custom-tag>" + }; + + for (String fragment: fragments) { + impl.processFragment(output, fragment); + } + + final String expected = "" + + "head #1\njust text\n" + + "head #2\nin span tag\n" + + "head #3\nin custom-tag"; + + assertEquals(expected, output.toString()); + } + private static class CaptureTagsAction<T> implements MarkwonHtmlParser.FlushAction<T> { boolean called; diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java index 338ab211..bd69445a 100644 --- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java @@ -13,6 +13,7 @@ import ru.noties.markwon.SpannableConfiguration; import ru.noties.markwon.html.api.MarkwonHtmlParser; import ru.noties.markwon.renderer.html2.tag.BlockquoteHandler; import ru.noties.markwon.renderer.html2.tag.EmphasisHandler; +import ru.noties.markwon.renderer.html2.tag.HeadingHandler; import ru.noties.markwon.renderer.html2.tag.ImageHandler; import ru.noties.markwon.renderer.html2.tag.LinkHandler; import ru.noties.markwon.renderer.html2.tag.ListHandler; @@ -69,7 +70,13 @@ public abstract class MarkwonHtmlRenderer { .handler("ul", listHandler) .handler("ol", listHandler) .handler("img", ImageHandler.create()) - .handler("blockquote", new BlockquoteHandler()); + .handler("blockquote", new BlockquoteHandler()) + .handler("h1", new HeadingHandler(1)) + .handler("h2", new HeadingHandler(2)) + .handler("h3", new HeadingHandler(3)) + .handler("h4", new HeadingHandler(4)) + .handler("h5", new HeadingHandler(5)) + .handler("h6", new HeadingHandler(6)); } @NonNull diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java new file mode 100644 index 00000000..e2138b05 --- /dev/null +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/HeadingHandler.java @@ -0,0 +1,22 @@ +package ru.noties.markwon.renderer.html2.tag; + +import android.support.annotation.NonNull; +import android.support.annotation.Nullable; + +import ru.noties.markwon.SpannableConfiguration; +import ru.noties.markwon.html.api.HtmlTag; + +public class HeadingHandler extends SimpleTagHandler { + + private final int level; + + public HeadingHandler(int level) { + this.level = level; + } + + @Nullable + @Override + public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { + return configuration.factory().heading(configuration.theme(), level); + } +} diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java index faa952bc..134874b9 100644 --- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java +++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java @@ -11,7 +11,7 @@ public class LinkHandler extends SimpleTagHandler { @Nullable @Override public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) { - final String destination = tag.attributes().get("src"); + final String destination = tag.attributes().get("href"); if (!TextUtils.isEmpty(destination)) { return configuration.factory().link( configuration.theme(), diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java index c47bccdf..ca9de641 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/SpannableMarkdownVisitorTest.java @@ -1,15 +1,20 @@ package ru.noties.markwon.renderer.visitor; import android.support.annotation.NonNull; +import android.support.annotation.Nullable; import android.text.SpannableStringBuilder; import org.commonmark.node.Node; +import org.junit.ComparisonFailure; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.ParameterizedRobolectricTestRunner; import org.robolectric.annotation.Config; +import java.util.Arrays; import java.util.Collection; +import java.util.Comparator; +import java.util.Map; import ix.Ix; import ix.IxPredicate; @@ -55,13 +60,16 @@ public class SpannableMarkdownVisitorTest { final SpannableStringBuilder stringBuilder = builder.spannableStringBuilder(); - System.out.printf("%n%s%n", stringBuilder); + System.out.printf("%s: %s%n", file, Arrays.toString(stringBuilder.getSpans(0, stringBuilder.length(), Object.class))); int index = 0; for (TestNode testNode : data.output()) { index = validate(stringBuilder, index, testNode); } + + // assert that the whole thing is processed + assertEquals(stringBuilder.length(), index); } private int validate(@NonNull SpannableStringBuilder builder, int index, @NonNull TestNode node) { @@ -103,6 +111,8 @@ public class SpannableMarkdownVisitorTest { final String info = node.toString(); + System.out.printf("%s: %s%n", file, builder.subSequence(index, out)); + // we can possibly have parent spans here, should filter them final Object[] spans = builder.getSpans(index, out, Object.class); assertTrue(info, spans != null); @@ -123,14 +133,18 @@ public class SpannableMarkdownVisitorTest { }) .first(null); - assertNotNull(info, testSpan); + assertNotNull( + format("info: %s, spans: %s", info, Arrays.toString(spans)), + testSpan + ); assertEquals(info, span.name(), testSpan.name()); - assertEquals(info, span.attributes(), testSpan.attributes()); + assertMapEquals(info, span.attributes(), testSpan.attributes()); return out; } + @SuppressWarnings("ConstantConditions") @NonNull private SpannableConfiguration configuration(@NonNull TestConfig config) { @@ -147,4 +161,49 @@ public class SpannableMarkdownVisitorTest { .factory(factory) .build(); } + + private static void assertMapEquals( + @NonNull String message, + @NonNull Map<String, String> expected, + @NonNull Map<String, String> actual) { + boolean result = true; + if (expected.size() == actual.size()) { + for (Map.Entry<String, String> entry : expected.entrySet()) { + if (!actual.containsKey(entry.getKey()) + || !equals(entry.getValue(), actual.get(entry.getKey()))) { + result = false; + break; + } + } + } + if (!result) { + final Comparator<Map.Entry<String, String>> comparator = new Comparator<Map.Entry<String, String>>() { + @Override + public int compare(Map.Entry<String, String> o1, Map.Entry<String, String> o2) { + return o1.getKey().compareTo(o2.getKey()); + } + }; + final String e = Ix.from(expected.entrySet()) + .orderBy(comparator) + .toList() + .toString(); + final String a = Ix.from(actual.entrySet()) + .orderBy(comparator) + .toList() + .toString(); + throw new ComparisonFailure(message, e, a); + } + } + + private static boolean equals(@Nullable Object o1, @Nullable Object o2) { + return o1 != null + ? o1.equals(o2) + : o2 == null; + + } + + @NonNull + private static String format(@NonNull String message, Object... args) { + return String.format(message, args); + } } \ No newline at end of file diff --git a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java index e87c6f34..89a0f646 100644 --- a/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java +++ b/markwon/src/test/java/ru/noties/markwon/renderer/visitor/TestFactory.java @@ -186,7 +186,7 @@ class TestFactory implements SpannableFactory { final int length = pairs.length; final Map<String, String> map = new HashMap<>(length); for (Pair pair : pairs) { - map.put(pair.key, String.valueOf(pair.value)); + map.put(pair.key, pair.value == null ? null : String.valueOf(pair.value)); } return map; } diff --git a/markwon/src/test/resources/tests/html.yaml b/markwon/src/test/resources/tests/html.yaml new file mode 100644 index 00000000..d9f966f1 --- /dev/null +++ b/markwon/src/test/resources/tests/html.yaml @@ -0,0 +1,105 @@ +input: |- + <h1>html</h1> + <h2>emphasis</h2> + <i>i</i><em>em</em><cite>cite</cite><dfn>dfn</dfn> + <h2>strong-emphasis</h2> + <b>b</b><strong>strong</strong> + <h2>super-script</h2> + <sup>sup</sup> + <h2>sub-script</h2> + <sub>sub</sub> + <h2>underline</h2> + <u>u</u><ins>ins</ins> + <h2>strike</h2> + <s>s</s><del>del</del> + <h2>link</h2> + <a href="a://href">a</a> + <h2>unordered-list</h2> + <ul><li>ul1<li>ul2</ul> + <h2>ordered-list</h2> + <ol><li>ol1<li>ol2</ol> + <h2>image</h2> + <img src="img://src" alt="img"> + <h2>blockquote</h2> + <blockquote>blockquote</blockquote> + <h3>3</h3> + <h4>4</h4> + <h5>5</h5> + <h6>6</h6> + +config: + use-html: true + +output: + - h1: "html" + - text: "\n" + - h2: "emphasis" + - text: "\n" + - i: "i" + - i: "em" + - i: "cite" + - i: "dfn" + - text: "\n" + - h2: "strong-emphasis" + - text: "\n" + - b: "b" + - b: "strong" + - text: "\n" + - h2: "super-script" + - text: "\n" + - sup: "sup" + - text: "\n" + - h2: "sub-script" + - text: "\n" + - sub: "sub" + - text: "\n" + - h2: "underline" + - text: "\n" + - u: "u" + - u: "ins" + - text: "\n" + - h2: "strike" + - text: "\n" + - s: "s" + - s: "del" + - text: "\n" + - h2: "link" + - text: "\n" + - a: "a" + attrs: + href: "a://href" + - text: "\n" + - h2: "unordered-list" + - text: "\n" + - ul: "ul1" + - text: "\n" + - ul: "ul2" + - text: "\n" + - h2: "ordered-list" + - text: "\n" + - ol: "ol1" + attrs: + start: 1 + - text: "\n" + - ol: "ol2" + attrs: + start: 2 + - text: "\n" + - h2: "image" + - text: "\n" + - img: "img" + attrs: + src: "img://src" + - text: "\n" + - h2: "blockquote" + - text: "\n" + - blockquote: "blockquote" + - text: "\n" + - h3: "3" + - text: "\n" + - h4: "4" + - text: "\n" + - h5: "5" + - text: "\n" + - h6: "6" + diff --git a/markwon/src/test/resources/tests/single-img.yaml b/markwon/src/test/resources/tests/single-img.yaml index 4ff5a02c..fd5a1343 100644 --- a/markwon/src/test/resources/tests/single-img.yaml +++ b/markwon/src/test/resources/tests/single-img.yaml @@ -3,6 +3,6 @@ input: "" output: - img: "image" attrs: - scr: "#href" + src: "#href" imageSize: null replacementTextIsLink: false \ No newline at end of file