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
	 Dimitry Ivanov
						Dimitry Ivanov