diff --git a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java index 4f6f3109..b2eb6846 100644 --- a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java +++ b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/HtmlTag.java @@ -9,9 +9,12 @@ import java.util.Map; /** * @see Inline * @see Block + * @since 2.0.0 */ public interface HtmlTag { + int NO_END = -1; + /** * @return normalized tag name (lower-case) */ @@ -33,6 +36,12 @@ public interface HtmlTag { */ boolean isEmpty(); + /** + * @return flag indicating if this tag is closed (has valid start and end) + * @see #NO_END + */ + boolean isClosed(); + @NonNull Map attributes(); @@ -59,5 +68,11 @@ public interface HtmlTag { */ @NonNull List children(); + + /** + * @return a flag indicating if this {@link Block} is at the root level (shortcut to calling: + * {@code parent() == null} + */ + boolean isRoot(); } } diff --git a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java index 33a041f5..8d168a72 100644 --- a/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java +++ b/markwon-html-parser-api/src/main/java/ru/noties/markwon/html/api/MarkwonHtmlParser.java @@ -4,8 +4,14 @@ import android.support.annotation.NonNull; import java.util.List; +/** + * @since 2.0.0 + */ public abstract class MarkwonHtmlParser { + /** + * Factory method to create a `no-op` implementation (no parsing) + */ @NonNull public static MarkwonHtmlParser noOp() { return new MarkwonHtmlParserNoOp(); @@ -19,14 +25,32 @@ public abstract class MarkwonHtmlParser { @NonNull T output, @NonNull String htmlFragment); - // clear all pending tags (if any) - // todo: we also can do this: if supplied value is -1 (for example) we ignore tags that are not closed + /** + * After this method exists a {@link MarkwonHtmlParser} will clear internal state for stored tags. + * If you wish to process them further after this method exists create own copy of supplied + * collection. + * + * @param documentLength known document length. This value is used to close all non-closed tags. + * If you wish to keep them open (do not force close at the end of a + * document pass here {@link HtmlTag#NO_END}. Later non-closed tags + * can be detected by calling {@link HtmlTag#isClosed()} + * @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Inline}) + */ public abstract void flushInlineTags( int documentLength, @NonNull FlushAction action); - // clear all pending blocks if any - // todo: we also can do this: if supplied value is -1 (for example) we ignore tags that are not closed + /** + * After this method exists a {@link MarkwonHtmlParser} will clear internal state for stored tags. + * If you wish to process them further after this method exists create own copy of supplied + * collection. + * + * @param documentLength known document length. This value is used to close all non-closed tags. + * If you wish to keep them open (do not force close at the end of a + * document pass here {@link HtmlTag#NO_END}. Later non-closed tags + * can be detected by calling {@link HtmlTag#isClosed()} + * @param action {@link FlushAction} to be called with resulting tags ({@link ru.noties.markwon.html.api.HtmlTag.Block}) + */ public abstract void flushBlockTags( int documentLength, @NonNull FlushAction action); diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java index 9f3e884b..6e852b1c 100644 --- a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacement.java @@ -11,6 +11,8 @@ import ru.noties.markwon.html.impl.jsoup.parser.Token; * _void_ tags and tags that are self-closed (even if HTML spec doesn\'t specify * a tag as self-closed). This is due to the fact that underlying parser does not * validate context and does not check if a tag is correctly used. + * + * @since 2.0.0 */ public class HtmlEmptyTagReplacement { diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java index 1b652ebe..57639e97 100644 --- a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/HtmlTagImpl.java @@ -11,12 +11,10 @@ import ru.noties.markwon.html.api.HtmlTag; abstract class HtmlTagImpl implements HtmlTag { - private static final int NO_VALUE = -1; - final String name; final int start; final Map attributes; - int end = NO_VALUE; + int end = NO_END; protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map attributes) { this.name = name; @@ -51,8 +49,9 @@ abstract class HtmlTagImpl implements HtmlTag { return attributes; } - boolean isClosed() { - return end > NO_VALUE; + @Override + public boolean isClosed() { + return end > NO_END; } abstract void closeAt(int end); @@ -86,8 +85,7 @@ abstract class HtmlTagImpl implements HtmlTag { @NonNull static BlockImpl root() { - //noinspection ConstantConditions - return new BlockImpl("", 0, null, null); + return new BlockImpl("", 0, Collections.emptyMap(), null); } @NonNull @@ -95,7 +93,7 @@ abstract class HtmlTagImpl implements HtmlTag { @NonNull String name, int start, @NonNull Map attributes, - @NonNull BlockImpl parent) { + @Nullable BlockImpl parent) { return new BlockImpl(name, start, attributes, parent); } @@ -107,7 +105,7 @@ abstract class HtmlTagImpl implements HtmlTag { @NonNull String name, int start, @NonNull Map attributes, - @NonNull BlockImpl parent) { + @Nullable BlockImpl parent) { super(name, start, attributes); this.parent = parent; } @@ -120,42 +118,36 @@ abstract class HtmlTagImpl implements HtmlTag { for (BlockImpl child : children) { child.closeAt(end); } - children = Collections.unmodifiableList(children); - } else { - children = Collections.emptyList(); } } } - boolean isRoot() { + @Override + public boolean isRoot() { return parent == null; } @Nullable @Override public Block parent() { - if (parent == null) { - throw new IllegalStateException("#parent() getter was called on the root node " + - "which should not be exposed outside internal usage"); - } return parent; } @NonNull @Override public List children() { - //noinspection unchecked - return (List) (List) children; + final List list; + if (children == null) { + list = Collections.emptyList(); + } else { + list = Collections.unmodifiableList((List) children); + } + return list; } @NonNull @Override public Map attributes() { - //noinspection ConstantConditions - if (attributes == null) { - throw new IllegalStateException("#attributes() getter was called on the root node " + - "which should not be exposed outside internal usage"); - } return attributes; } 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 f024c62e..abaacaff 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 @@ -14,6 +14,7 @@ import java.util.Locale; import java.util.Map; import java.util.Set; +import ru.noties.markwon.html.api.HtmlTag; import ru.noties.markwon.html.api.HtmlTag.Block; import ru.noties.markwon.html.api.HtmlTag.Inline; import ru.noties.markwon.html.api.MarkwonHtmlParser; @@ -24,6 +25,9 @@ import ru.noties.markwon.html.impl.jsoup.parser.ParseErrorList; import ru.noties.markwon.html.impl.jsoup.parser.Token; import ru.noties.markwon.html.impl.jsoup.parser.Tokeniser; +/** + * @since 2.0.0 + */ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { @NonNull @@ -173,12 +177,18 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { @Override public void flushInlineTags(int documentLength, @NonNull FlushAction action) { if (inlineTags.size() > 0) { - for (HtmlTagImpl.InlineImpl inline : inlineTags) { - inline.closeAt(documentLength); + + if (documentLength > HtmlTag.NO_END) { + for (HtmlTagImpl.InlineImpl inline : inlineTags) { + inline.closeAt(documentLength); + } } + //noinspection unchecked action.apply(Collections.unmodifiableList((List) inlineTags)); inlineTags.clear(); + } else { + action.apply(Collections.emptyList()); } } @@ -186,15 +196,19 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser { public void flushBlockTags(int documentLength, @NonNull FlushAction action) { HtmlTagImpl.BlockImpl block = currentBlock; - while (!block.isRoot()) { + while (block.parent != null) { block = block.parent; } - block.closeAt(documentLength); + if (documentLength > HtmlTag.NO_END) { + block.closeAt(documentLength); + } final List children = block.children(); if (children.size() > 0) { action.apply(children); + } else { + action.apply(Collections.emptyList()); } currentBlock = HtmlTagImpl.BlockImpl.root(); diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java index db321233..a70da0e7 100644 --- a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java +++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/jsoup/parser/Token.java @@ -222,7 +222,7 @@ public abstract class Token { } public final static class StartTag extends Tag { - StartTag() { + public StartTag() { super(TokenType.StartTag); attributes = new Attributes(); } diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java new file mode 100644 index 00000000..b4ca8996 --- /dev/null +++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/HtmlEmptyTagReplacementTest.java @@ -0,0 +1,42 @@ +package ru.noties.markwon.html.impl; + +import org.junit.Before; +import org.junit.Test; + +import ru.noties.markwon.html.impl.jsoup.nodes.Attributes; +import ru.noties.markwon.html.impl.jsoup.parser.Token; + +import static org.junit.Assert.assertEquals; + +public class HtmlEmptyTagReplacementTest { + + private HtmlEmptyTagReplacement replacement; + + @Before + public void before() { + replacement = HtmlEmptyTagReplacement.create(); + } + + @Test + public void imageReplacementNoAlt() { + final Token.StartTag startTag = new Token.StartTag(); + startTag.normalName = "img"; + assertEquals("\uFFFC", replacement.replace(startTag)); + } + + @Test + public void imageReplacementAlt() { + final Token.StartTag startTag = new Token.StartTag(); + startTag.normalName = "img"; + startTag.attributes = new Attributes().put("alt", "alternative27"); + assertEquals("alternative27", replacement.replace(startTag)); + } + + @Test + public void brAddsNewLine() { + final Token.StartTag startTag = new Token.StartTag(); + startTag.normalName = "br"; + startTag.selfClosing = true; + assertEquals("\n", replacement.replace(startTag)); + } +} \ No newline at end of file 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 1a150f1b..59cdc3eb 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 @@ -12,15 +12,15 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import ru.noties.markwon.html.api.HtmlTag; import ru.noties.markwon.html.api.MarkwonHtmlParser; -import ru.noties.markwon.html.impl.HtmlEmptyTagReplacement; -import ru.noties.markwon.html.impl.MarkwonHtmlParserImpl; import ru.noties.markwon.html.impl.jsoup.parser.Token; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @RunWith(RobolectricTestRunner.class) @@ -257,11 +257,11 @@ public class MarkwonHtmlParserImplTest { // tag names must be lower cased final Set set = new HashSet<>(tags.size()); - for (String tag: tags) { + for (String tag : tags) { set.add(tag.toLowerCase()); } - for (HtmlTag.Block block: blocks) { + for (HtmlTag.Block block : blocks) { assertTrue(block.name(), block.isEmpty()); assertTrue(set.remove(block.name())); } @@ -301,7 +301,7 @@ public class MarkwonHtmlParserImplTest { }); final StringBuilder html = new StringBuilder(); - for (String tag: tags) { + for (String tag : tags) { html.append('<') .append(tag) .append('>') @@ -327,7 +327,7 @@ public class MarkwonHtmlParserImplTest { final Set set = new HashSet<>(tags); boolean first = true; - for (HtmlTag.Block block: blocks) { + for (HtmlTag.Block block : blocks) { assertEquals(block.name(), block.name(), output.substring(block.start(), block.end())); if (first) { first = false; @@ -342,58 +342,455 @@ public class MarkwonHtmlParserImplTest { @Test public void multipleFragmentsContinuation() { - throw new RuntimeException(); + + final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement()); + + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, ""); + output.append("italic "); + impl.processFragment(output, ""); + + final CaptureInlineTagsAction action = new CaptureInlineTagsAction(); + impl.flushInlineTags(output.length(), action); + + assertTrue(action.called); + + final List inlines = action.tags; + assertEquals(inlines.toString(), 1, inlines.size()); + + final HtmlTag.Inline inline = inlines.get(0); + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + assertEquals("italic ", output.toString()); } @Test public void paragraphCannotContainAnythingButInlines() { - throw new RuntimeException(); - } - // move to htmlInlineTagreplacement test class - @Test - public void imageReplacementNoAlt() { - throw new RuntimeException(); - } + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); - @Test - public void brAddsNewLine() { - throw new RuntimeException(); - } + final StringBuilder output = new StringBuilder(); - @Test - public void imageReplacementAlt() { - throw new RuntimeException(); + impl.processFragment(output, "

italic bold italic

in-div
"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + final List inlines = inlineTagsAction.tags; + final List blocks = blockTagsAction.tags; + + assertEquals(2, inlines.size()); + assertEquals(2, blocks.size()); + + // inlines will be closed at the end of the document + // P will be closed right before
+ + with(inlines.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(inlines.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("b", inline.name()); + assertEquals("italic ".length(), inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(blocks.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("p", block.name()); + assertEquals(0, block.start()); + assertEquals(output.indexOf("in-div") - 1, block.end()); + } + }); + + with(blocks.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div", block.name()); + assertEquals(output.indexOf("in-div"), block.start()); + assertEquals(output.length(), block.end()); + } + }); } @Test public void blockCloseClosesChildren() { - throw new RuntimeException(); - } - @Test - public void allReturnedTagsAreClosed() { - throw new RuntimeException(); + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String html = "12hello!"; + impl.processFragment(output, html); + + assertEquals("12hello!", output.toString()); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + final int end = output.length(); + + assertEquals("div-1", block.name()); + assertEquals(0, block.start()); + assertEquals(end, block.end()); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div-2", block.name()); + assertEquals(1, block.start()); + assertEquals(end, block.end()); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div-3", block.name()); + assertEquals(2, block.start()); + assertEquals(end, block.end()); + assertEquals(0, block.children().size()); + } + }); + } + }); + } + }); } @Test public void allTagsAreLowerCase() { - throw new RuntimeException(); + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + impl.processFragment(output, "
italic emphasis italic
"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + + assertEquals(2, inlines.size()); + + with(inlines.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + assertEquals("i", inline.name()); + assertEquals(0, inline.start()); + assertEquals(output.length(), inline.end()); + } + }); + + with(inlines.get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Inline inline) { + + assertEquals("em", inline.name()); + + final int start = "italic ".length(); + assertEquals(start, inline.start()); + assertEquals(start + ("emphasis".length()), inline.end()); + } + }); + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("div", block.name()); + assertEquals(0, block.start()); + assertEquals(output.length(), block.end()); + } + }); } @Test public void previousListItemClosed() { - throw new RuntimeException(); - } - @Test - public void nestedBlocks() { - throw new RuntimeException(); + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + final String html = "
  • UL-First
  • UL-Second
    1. OL-First
    2. OL-Second
  • UL-Third"; + + impl.processFragment(output, html); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + assertEquals("ul", block.name()); + assertEquals(3, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("UL-First", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + + with(block.children().get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + + // this block will contain nested block text also + assertEquals("UL-Second\nOL-First\nOL-Second", output.substring(block.start(), block.end())); + assertEquals(1, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("ol", block.name()); + assertEquals(2, block.children().size()); + + with(block.children().get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("OL-First", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + + with(block.children().get(1), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("OL-Second", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + } + }); + } + }); + + with(block.children().get(2), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertEquals("li", block.name()); + assertEquals("UL-Third", output.substring(block.start(), block.end())); + assertEquals(0, block.children().size()); + } + }); + } + }); } @Test public void attributes() { - throw new RuntimeException(); + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "my-content"); + + final CaptureBlockTagsAction action = new CaptureBlockTagsAction(); + impl.flushBlockTags(output.length(), action); + + assertTrue(action.called); + assertEquals(1, action.tags.size()); + + with(action.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + + assertEquals("my-tag", block.name()); + + with(block.attributes(), new Action>() { + @Override + public void apply(@NonNull Map attributes) { + assertEquals(5, attributes.size()); + assertEquals("no-name", attributes.get("name")); + assertEquals("doSomething", attributes.get(":click")); + assertEquals("focus", attributes.get("@focus")); + assertEquals("blur", attributes.get("@blur.native")); + assertEquals("@id/id", attributes.get("android:id")); + } + }); + } + }); + } + + @Test + public void flushCloseTagsIfRequested() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "
    divibemstrong"); + + final int end = output.length(); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(end, inlineTagsAction); + impl.flushBlockTags(end, blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + assertEquals(4, inlines.size()); + for (HtmlTag.Inline inline : inlines) { + assertTrue(inline.isClosed()); + assertEquals(end, inline.end()); + } + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertTrue(block.isClosed()); + assertEquals(end, block.end()); + } + }); + } + + @Test + public void flushDoesNotCloseTagsIfNoEndRequested() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "
    divibemstrong"); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(HtmlTag.NO_END, inlineTagsAction); + impl.flushBlockTags(HtmlTag.NO_END, blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + with(inlineTagsAction.tags, new Action>() { + @Override + public void apply(@NonNull List inlines) { + assertEquals(4, inlines.size()); + for (HtmlTag.Inline inline : inlines) { + assertFalse(inline.isClosed()); + assertEquals(HtmlTag.NO_END, inline.end()); + } + } + }); + + assertEquals(1, blockTagsAction.tags.size()); + + with(blockTagsAction.tags.get(0), new Action() { + @Override + public void apply(@NonNull HtmlTag.Block block) { + assertFalse(block.isClosed()); + assertEquals(HtmlTag.NO_END, block.end()); + } + }); + } + + @Test + public void flushClearsInternalState() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + impl.processFragment(output, "

    italic bold italic

    paragraph

    and a div
    "); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + assertEquals(2, inlineTagsAction.tags.size()); + assertEquals(3, blockTagsAction.tags.size()); + + final CaptureInlineTagsAction captureInlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction captureBlockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), captureInlineTagsAction); + impl.flushBlockTags(output.length(), captureBlockTagsAction); + + assertTrue(captureInlineTagsAction.called); + assertTrue(captureBlockTagsAction.called); + + assertEquals(0, captureInlineTagsAction.tags.size()); + assertEquals(0, captureBlockTagsAction.tags.size()); + } + + @Test + public void resetClearsBothInlinesAndBlocks() { + + final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(); + final StringBuilder output = new StringBuilder(); + + impl.processFragment(output, "

    paragraph italic

    div
    "); + + impl.reset(); + + final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction(); + final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction(); + + impl.flushInlineTags(output.length(), inlineTagsAction); + impl.flushBlockTags(output.length(), blockTagsAction); + + assertTrue(inlineTagsAction.called); + assertTrue(blockTagsAction.called); + + assertEquals(0, inlineTagsAction.tags.size()); + assertEquals(0, blockTagsAction.tags.size()); } private static class CaptureTagsAction implements MarkwonHtmlParser.FlushAction { @@ -413,4 +810,12 @@ public class MarkwonHtmlParserImplTest { private static class CaptureBlockTagsAction extends CaptureTagsAction { } + + private interface Action { + void apply(@NonNull T t); + } + + private static void with(@NonNull T t, @NonNull Action action) { + action.apply(t); + } } \ No newline at end of file