Html parser impl tests
This commit is contained in:
parent
76fdbabad0
commit
203f5fae52
@ -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<String, String> attributes();
|
||||
|
||||
@ -59,5 +68,11 @@ public interface HtmlTag {
|
||||
*/
|
||||
@NonNull
|
||||
List<Block> children();
|
||||
|
||||
/**
|
||||
* @return a flag indicating if this {@link Block} is at the root level (shortcut to calling:
|
||||
* {@code parent() == null}
|
||||
*/
|
||||
boolean isRoot();
|
||||
}
|
||||
}
|
||||
|
@ -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<HtmlTag.Inline> 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<HtmlTag.Block> action);
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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<String, String> attributes;
|
||||
int end = NO_VALUE;
|
||||
int end = NO_END;
|
||||
|
||||
protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map<String, String> 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.<String, String>emptyMap(), null);
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@ -95,7 +93,7 @@ abstract class HtmlTagImpl implements HtmlTag {
|
||||
@NonNull String name,
|
||||
int start,
|
||||
@NonNull Map<String, String> 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<String, String> 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<Block> children() {
|
||||
//noinspection unchecked
|
||||
return (List<Block>) (List<? extends Block>) children;
|
||||
final List<Block> list;
|
||||
if (children == null) {
|
||||
list = Collections.emptyList();
|
||||
} else {
|
||||
list = Collections.unmodifiableList((List<? extends Block>) children);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Map<String, String> 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;
|
||||
}
|
||||
|
||||
|
@ -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<Inline> action) {
|
||||
if (inlineTags.size() > 0) {
|
||||
|
||||
if (documentLength > HtmlTag.NO_END) {
|
||||
for (HtmlTagImpl.InlineImpl inline : inlineTags) {
|
||||
inline.closeAt(documentLength);
|
||||
}
|
||||
}
|
||||
|
||||
//noinspection unchecked
|
||||
action.apply(Collections.unmodifiableList((List<? extends Inline>) inlineTags));
|
||||
inlineTags.clear();
|
||||
} else {
|
||||
action.apply(Collections.<Inline>emptyList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -186,15 +196,19 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
||||
public void flushBlockTags(int documentLength, @NonNull FlushAction<Block> action) {
|
||||
|
||||
HtmlTagImpl.BlockImpl block = currentBlock;
|
||||
while (!block.isRoot()) {
|
||||
while (block.parent != null) {
|
||||
block = block.parent;
|
||||
}
|
||||
|
||||
if (documentLength > HtmlTag.NO_END) {
|
||||
block.closeAt(documentLength);
|
||||
}
|
||||
|
||||
final List<Block> children = block.children();
|
||||
if (children.size() > 0) {
|
||||
action.apply(children);
|
||||
} else {
|
||||
action.apply(Collections.<Block>emptyList());
|
||||
}
|
||||
|
||||
currentBlock = HtmlTagImpl.BlockImpl.root();
|
||||
|
@ -222,7 +222,7 @@ public abstract class Token {
|
||||
}
|
||||
|
||||
public final static class StartTag extends Tag {
|
||||
StartTag() {
|
||||
public StartTag() {
|
||||
super(TokenType.StartTag);
|
||||
attributes = new Attributes();
|
||||
}
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
@ -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<String> 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<String> 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, "<i>");
|
||||
output.append("italic ");
|
||||
impl.processFragment(output, "</i>");
|
||||
|
||||
final CaptureInlineTagsAction action = new CaptureInlineTagsAction();
|
||||
impl.flushInlineTags(output.length(), action);
|
||||
|
||||
assertTrue(action.called);
|
||||
|
||||
final List<HtmlTag.Inline> 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, "<p><i>italic <b>bold italic <div>in-div</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<HtmlTag.Inline> inlines = inlineTagsAction.tags;
|
||||
final List<HtmlTag.Block> 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 <div>
|
||||
|
||||
with(inlines.get(0), new Action<HtmlTag.Inline>() {
|
||||
@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<HtmlTag.Inline>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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 = "<div-1>1<div-2>2<div-3>hello!</div-1>";
|
||||
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<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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, "<DiV><I>italic <eM>emphasis</Em> italic</i></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);
|
||||
|
||||
with(inlineTagsAction.tags, new Action<List<HtmlTag.Inline>>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Inline> inlines) {
|
||||
|
||||
assertEquals(2, inlines.size());
|
||||
|
||||
with(inlines.get(0), new Action<HtmlTag.Inline>() {
|
||||
@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<HtmlTag.Inline>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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><li>UL-First<li>UL-Second<ol><li>OL-First<li>OL-Second</ol><li>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<HtmlTag.Block>() {
|
||||
@Override
|
||||
public void apply(@NonNull HtmlTag.Block block) {
|
||||
|
||||
assertEquals("ul", block.name());
|
||||
assertEquals(3, block.children().size());
|
||||
|
||||
with(block.children().get(0), new Action<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@Override
|
||||
public void apply(@NonNull HtmlTag.Block block) {
|
||||
assertEquals("ol", block.name());
|
||||
assertEquals(2, block.children().size());
|
||||
|
||||
with(block.children().get(0), new Action<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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<HtmlTag.Block>() {
|
||||
@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-tag " +
|
||||
"name=no-name " +
|
||||
":click='doSomething' " +
|
||||
"@focus=\"focus\" " +
|
||||
"@blur.native=\"blur\" " +
|
||||
"android:id=\"@id/id\">my-content</my-tag>");
|
||||
|
||||
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<HtmlTag.Block>() {
|
||||
@Override
|
||||
public void apply(@NonNull HtmlTag.Block block) {
|
||||
|
||||
assertEquals("my-tag", block.name());
|
||||
|
||||
with(block.attributes(), new Action<Map<String, String>>() {
|
||||
@Override
|
||||
public void apply(@NonNull Map<String, String> 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, "<div><i><b><em><strong>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<List<HtmlTag.Inline>>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Inline> 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<HtmlTag.Block>() {
|
||||
@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, "<div><i><b><em><strong>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<List<HtmlTag.Inline>>() {
|
||||
@Override
|
||||
public void apply(@NonNull List<HtmlTag.Inline> 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<HtmlTag.Block>() {
|
||||
@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, "<p><i>italic <b>bold italic</b></i></p><p>paragraph</p><div>and a div</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, "<p>paragraph <i>italic</i></p><div>div</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<T> implements MarkwonHtmlParser.FlushAction<T> {
|
||||
@ -413,4 +810,12 @@ public class MarkwonHtmlParserImplTest {
|
||||
|
||||
private static class CaptureBlockTagsAction extends CaptureTagsAction<HtmlTag.Block> {
|
||||
}
|
||||
|
||||
private interface Action<T> {
|
||||
void apply(@NonNull T t);
|
||||
}
|
||||
|
||||
private static <T> void with(@NonNull T t, @NonNull Action<T> action) {
|
||||
action.apply(t);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user