Html parser impl tests
This commit is contained in:
parent
76fdbabad0
commit
203f5fae52
@ -9,9 +9,12 @@ import java.util.Map;
|
|||||||
/**
|
/**
|
||||||
* @see Inline
|
* @see Inline
|
||||||
* @see Block
|
* @see Block
|
||||||
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public interface HtmlTag {
|
public interface HtmlTag {
|
||||||
|
|
||||||
|
int NO_END = -1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return normalized tag name (lower-case)
|
* @return normalized tag name (lower-case)
|
||||||
*/
|
*/
|
||||||
@ -33,6 +36,12 @@ public interface HtmlTag {
|
|||||||
*/
|
*/
|
||||||
boolean isEmpty();
|
boolean isEmpty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return flag indicating if this tag is closed (has valid start and end)
|
||||||
|
* @see #NO_END
|
||||||
|
*/
|
||||||
|
boolean isClosed();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
Map<String, String> attributes();
|
Map<String, String> attributes();
|
||||||
|
|
||||||
@ -59,5 +68,11 @@ public interface HtmlTag {
|
|||||||
*/
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
List<Block> children();
|
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;
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
public abstract class MarkwonHtmlParser {
|
public abstract class MarkwonHtmlParser {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory method to create a `no-op` implementation (no parsing)
|
||||||
|
*/
|
||||||
@NonNull
|
@NonNull
|
||||||
public static MarkwonHtmlParser noOp() {
|
public static MarkwonHtmlParser noOp() {
|
||||||
return new MarkwonHtmlParserNoOp();
|
return new MarkwonHtmlParserNoOp();
|
||||||
@ -19,14 +25,32 @@ public abstract class MarkwonHtmlParser {
|
|||||||
@NonNull T output,
|
@NonNull T output,
|
||||||
@NonNull String htmlFragment);
|
@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(
|
public abstract void flushInlineTags(
|
||||||
int documentLength,
|
int documentLength,
|
||||||
@NonNull FlushAction<HtmlTag.Inline> action);
|
@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(
|
public abstract void flushBlockTags(
|
||||||
int documentLength,
|
int documentLength,
|
||||||
@NonNull FlushAction<HtmlTag.Block> action);
|
@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
|
* _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
|
* 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.
|
* validate context and does not check if a tag is correctly used.
|
||||||
|
*
|
||||||
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class HtmlEmptyTagReplacement {
|
public class HtmlEmptyTagReplacement {
|
||||||
|
|
||||||
|
@ -11,12 +11,10 @@ import ru.noties.markwon.html.api.HtmlTag;
|
|||||||
|
|
||||||
abstract class HtmlTagImpl implements HtmlTag {
|
abstract class HtmlTagImpl implements HtmlTag {
|
||||||
|
|
||||||
private static final int NO_VALUE = -1;
|
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final int start;
|
final int start;
|
||||||
final Map<String, String> attributes;
|
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) {
|
protected HtmlTagImpl(@NonNull String name, int start, @NonNull Map<String, String> attributes) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -51,8 +49,9 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isClosed() {
|
@Override
|
||||||
return end > NO_VALUE;
|
public boolean isClosed() {
|
||||||
|
return end > NO_END;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract void closeAt(int end);
|
abstract void closeAt(int end);
|
||||||
@ -86,8 +85,7 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
static BlockImpl root() {
|
static BlockImpl root() {
|
||||||
//noinspection ConstantConditions
|
return new BlockImpl("", 0, Collections.<String, String>emptyMap(), null);
|
||||||
return new BlockImpl("", 0, null, null);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -95,7 +93,7 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
@NonNull String name,
|
@NonNull String name,
|
||||||
int start,
|
int start,
|
||||||
@NonNull Map<String, String> attributes,
|
@NonNull Map<String, String> attributes,
|
||||||
@NonNull BlockImpl parent) {
|
@Nullable BlockImpl parent) {
|
||||||
return new BlockImpl(name, start, attributes, parent);
|
return new BlockImpl(name, start, attributes, parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,7 +105,7 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
@NonNull String name,
|
@NonNull String name,
|
||||||
int start,
|
int start,
|
||||||
@NonNull Map<String, String> attributes,
|
@NonNull Map<String, String> attributes,
|
||||||
@NonNull BlockImpl parent) {
|
@Nullable BlockImpl parent) {
|
||||||
super(name, start, attributes);
|
super(name, start, attributes);
|
||||||
this.parent = parent;
|
this.parent = parent;
|
||||||
}
|
}
|
||||||
@ -120,42 +118,36 @@ abstract class HtmlTagImpl implements HtmlTag {
|
|||||||
for (BlockImpl child : children) {
|
for (BlockImpl child : children) {
|
||||||
child.closeAt(end);
|
child.closeAt(end);
|
||||||
}
|
}
|
||||||
children = Collections.unmodifiableList(children);
|
|
||||||
} else {
|
|
||||||
children = Collections.emptyList();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isRoot() {
|
@Override
|
||||||
|
public boolean isRoot() {
|
||||||
return parent == null;
|
return parent == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Block parent() {
|
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;
|
return parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public List<Block> children() {
|
public List<Block> children() {
|
||||||
//noinspection unchecked
|
final List<Block> list;
|
||||||
return (List<Block>) (List<? extends Block>) children;
|
if (children == null) {
|
||||||
|
list = Collections.emptyList();
|
||||||
|
} else {
|
||||||
|
list = Collections.unmodifiableList((List<? extends Block>) children);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> attributes() {
|
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;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,6 +14,7 @@ import java.util.Locale;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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.Block;
|
||||||
import ru.noties.markwon.html.api.HtmlTag.Inline;
|
import ru.noties.markwon.html.api.HtmlTag.Inline;
|
||||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
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.Token;
|
||||||
import ru.noties.markwon.html.impl.jsoup.parser.Tokeniser;
|
import ru.noties.markwon.html.impl.jsoup.parser.Tokeniser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -173,12 +177,18 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
|
|||||||
@Override
|
@Override
|
||||||
public void flushInlineTags(int documentLength, @NonNull FlushAction<Inline> action) {
|
public void flushInlineTags(int documentLength, @NonNull FlushAction<Inline> action) {
|
||||||
if (inlineTags.size() > 0) {
|
if (inlineTags.size() > 0) {
|
||||||
|
|
||||||
|
if (documentLength > HtmlTag.NO_END) {
|
||||||
for (HtmlTagImpl.InlineImpl inline : inlineTags) {
|
for (HtmlTagImpl.InlineImpl inline : inlineTags) {
|
||||||
inline.closeAt(documentLength);
|
inline.closeAt(documentLength);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//noinspection unchecked
|
//noinspection unchecked
|
||||||
action.apply(Collections.unmodifiableList((List<? extends Inline>) inlineTags));
|
action.apply(Collections.unmodifiableList((List<? extends Inline>) inlineTags));
|
||||||
inlineTags.clear();
|
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) {
|
public void flushBlockTags(int documentLength, @NonNull FlushAction<Block> action) {
|
||||||
|
|
||||||
HtmlTagImpl.BlockImpl block = currentBlock;
|
HtmlTagImpl.BlockImpl block = currentBlock;
|
||||||
while (!block.isRoot()) {
|
while (block.parent != null) {
|
||||||
block = block.parent;
|
block = block.parent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (documentLength > HtmlTag.NO_END) {
|
||||||
block.closeAt(documentLength);
|
block.closeAt(documentLength);
|
||||||
|
}
|
||||||
|
|
||||||
final List<Block> children = block.children();
|
final List<Block> children = block.children();
|
||||||
if (children.size() > 0) {
|
if (children.size() > 0) {
|
||||||
action.apply(children);
|
action.apply(children);
|
||||||
|
} else {
|
||||||
|
action.apply(Collections.<Block>emptyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBlock = HtmlTagImpl.BlockImpl.root();
|
currentBlock = HtmlTagImpl.BlockImpl.root();
|
||||||
|
@ -222,7 +222,7 @@ public abstract class Token {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final static class StartTag extends Tag {
|
public final static class StartTag extends Tag {
|
||||||
StartTag() {
|
public StartTag() {
|
||||||
super(TokenType.StartTag);
|
super(TokenType.StartTag);
|
||||||
attributes = new Attributes();
|
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.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import ru.noties.markwon.html.api.HtmlTag;
|
import ru.noties.markwon.html.api.HtmlTag;
|
||||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
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 ru.noties.markwon.html.impl.jsoup.parser.Token;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
@ -342,58 +342,455 @@ public class MarkwonHtmlParserImplTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void multipleFragmentsContinuation() {
|
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
|
@Test
|
||||||
public void paragraphCannotContainAnythingButInlines() {
|
public void paragraphCannotContainAnythingButInlines() {
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
// move to htmlInlineTagreplacement test class
|
final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create();
|
||||||
@Test
|
|
||||||
public void imageReplacementNoAlt() {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
final StringBuilder output = new StringBuilder();
|
||||||
public void brAddsNewLine() {
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
impl.processFragment(output, "<p><i>italic <b>bold italic <div>in-div</div>");
|
||||||
public void imageReplacementAlt() {
|
|
||||||
throw new RuntimeException();
|
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
|
@Test
|
||||||
public void blockCloseClosesChildren() {
|
public void blockCloseClosesChildren() {
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create();
|
||||||
public void allReturnedTagsAreClosed() {
|
final StringBuilder output = new StringBuilder();
|
||||||
throw new RuntimeException();
|
|
||||||
|
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
|
@Test
|
||||||
public void allTagsAreLowerCase() {
|
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
|
@Test
|
||||||
public void previousListItemClosed() {
|
public void previousListItemClosed() {
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create();
|
||||||
public void nestedBlocks() {
|
final StringBuilder output = new StringBuilder();
|
||||||
throw new RuntimeException();
|
|
||||||
|
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
|
@Test
|
||||||
public void attributes() {
|
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> {
|
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 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