diff --git a/README.md b/README.md
index 7ab532d3..c9745960 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon%22)
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-image-loader%22)
-[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax%22)
+[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-syntax-highlight%22)
[](http://search.maven.org/#search|ga|1|g%3A%22ru.noties%22%20AND%20a%3A%22markwon-view%22)
**Markwon** is a library for Android that renders markdown as system-native Spannables. It gives ability to display markdown in all TextView widgets (**TextView**, **Button**, **Switch**, **CheckBox**, etc), **Notifications**, **Toasts**, etc. **No WebView is required**. Library provides reasonable defaults for display style of markdown but also gives all the means to tweak the appearance if desired. All markdown features are supported (including limited support for inlined HTML code, markdown tables and images).
diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java
new file mode 100644
index 00000000..39b6bf73
--- /dev/null
+++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/AppendableUtils.java
@@ -0,0 +1,35 @@
+package ru.noties.markwon.html.impl;
+
+import android.support.annotation.NonNull;
+
+import java.io.IOException;
+
+abstract class AppendableUtils {
+
+ static void appendQuietly(@NonNull Appendable appendable, char c) {
+ try {
+ appendable.append(c);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static void appendQuietly(@NonNull Appendable appendable, @NonNull CharSequence cs) {
+ try {
+ appendable.append(cs);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ static void appendQuietly(@NonNull Appendable appendable, @NonNull CharSequence cs, int start, int end) {
+ try {
+ appendable.append(cs, start, end);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private AppendableUtils() {
+ }
+}
diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java
index 4c335f03..7ac819f9 100644
--- a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java
+++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImpl.java
@@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
-import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@@ -26,6 +25,8 @@ 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;
+import static ru.noties.markwon.html.impl.AppendableUtils.appendQuietly;
+
/**
* @since 2.0.0
*/
@@ -38,7 +39,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
@NonNull
public static MarkwonHtmlParserImpl create(@NonNull HtmlEmptyTagReplacement inlineTagReplacement) {
- return new MarkwonHtmlParserImpl(inlineTagReplacement);
+ return new MarkwonHtmlParserImpl(inlineTagReplacement, TrimmingAppender.create());
}
// https://developer.mozilla.org/en-US/docs/Web/HTML/Inline_elements
@@ -57,9 +58,6 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
private static final String TAG_PARAGRAPH = "p";
private static final String TAG_LIST_ITEM = "li";
- // todo: make it configurable
-// private static final String IMG_REPLACEMENT = "\uFFFC";
-
static {
INLINE_TAGS = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(
"a", "abbr", "acronym",
@@ -113,12 +111,19 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
private final HtmlEmptyTagReplacement emptyTagReplacement;
+ private final TrimmingAppender trimmingAppender;
+
private final List inlineTags = new ArrayList<>(0);
private HtmlTagImpl.BlockImpl currentBlock = HtmlTagImpl.BlockImpl.root();
- MarkwonHtmlParserImpl(@NonNull HtmlEmptyTagReplacement replacement) {
+ private boolean isInsidePreTag;
+
+ MarkwonHtmlParserImpl(
+ @NonNull HtmlEmptyTagReplacement replacement,
+ @NonNull TrimmingAppender trimmingAppender) {
this.emptyTagReplacement = replacement;
+ this.trimmingAppender = trimmingAppender;
}
@@ -237,7 +242,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
final String replacement = emptyTagReplacement.replace(startTag);
if (replacement != null
&& replacement.length() > 0) {
- append(output, replacement);
+ appendQuietly(output, replacement);
}
// the thing is: we will keep this inline tag in the list,
@@ -276,7 +281,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
// it must be closed here not matter what we are as here we _assume_
// that it's a block tag
currentBlock.closeAt(output.length());
- append(output, "\n");
+ appendQuietly(output, '\n');
currentBlock = currentBlock.parent;
} else if (TAG_LIST_ITEM.equals(name)
&& TAG_LIST_ITEM.equals(currentBlock.name)) {
@@ -286,6 +291,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
}
if (isBlockTag(name)) {
+ isInsidePreTag = "pre".equals(name);
ensureNewLine(output);
}
@@ -298,7 +304,7 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
final String replacement = emptyTagReplacement.replace(startTag);
if (replacement != null
&& replacement.length() > 0) {
- append(output, replacement);
+ appendQuietly(output, replacement);
}
block.closeAt(output.length());
}
@@ -321,10 +327,14 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
final HtmlTagImpl.BlockImpl block = findOpenBlockTag(endTag.normalName);
if (block != null) {
+ if ("pre".equals(name)) {
+ isInsidePreTag = false;
+ }
+
block.closeAt(output.length());
if (TAG_PARAGRAPH.equals(name)) {
- append(output, "\n");
+ appendQuietly(output, '\n');
}
this.currentBlock = block.parent;
@@ -335,18 +345,14 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
@NonNull T output,
@NonNull Token.Character character) {
- // the thing here is: if it's a script tag that we are inside -> we must not treat this
- // as the text to append... should we even care about this? how many people are
- // going to include freaking script tags as html inline?
- //
- // so tags are: BUTTON, INPUT, SELECT, SCRIPT, TEXTAREA
- //
- // actually we must decide it here: should we append freaking characters for these _bad_
- // tags or not, as later we won't be able to change it and/or allow modification (as
- // all indexes will be affected with this)
+ // there are tags: BUTTON, INPUT, SELECT, SCRIPT, TEXTAREA, STYLE
+ // that might have character data that we do not want to display
- // for now: ignore the inline context
- append(output, character.getData());
+ if (isInsidePreTag) {
+ appendQuietly(output, character.getData());
+ } else {
+ trimmingAppender.append(output, character.getData());
+ }
}
protected void appendBlockChild(@NonNull HtmlTagImpl.BlockImpl parent, @NonNull HtmlTagImpl.BlockImpl child) {
@@ -400,20 +406,11 @@ public class MarkwonHtmlParserImpl extends MarkwonHtmlParser {
return BLOCK_TAGS.contains(name);
}
- protected static void append(@NonNull Appendable appendable, @NonNull CharSequence text) {
- try {
- appendable.append(text);
- } catch (IOException e) {
- // _must_ not happen
- throw new RuntimeException(e);
- }
- }
-
protected static void ensureNewLine(@NonNull T output) {
final int length = output.length();
if (length > 0
&& '\n' != output.charAt(length - 1)) {
- append(output, "\n");
+ appendQuietly(output, '\n');
}
}
diff --git a/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java
new file mode 100644
index 00000000..c29c93b9
--- /dev/null
+++ b/markwon-html-parser-impl/src/main/java/ru/noties/markwon/html/impl/TrimmingAppender.java
@@ -0,0 +1,68 @@
+package ru.noties.markwon.html.impl;
+
+import android.support.annotation.NonNull;
+
+import static ru.noties.markwon.html.impl.AppendableUtils.appendQuietly;
+
+abstract class TrimmingAppender {
+
+ abstract void append(
+ @NonNull T output,
+ @NonNull String data
+ );
+
+ @NonNull
+ static TrimmingAppender create() {
+ return new Impl();
+ }
+
+ static class Impl extends TrimmingAppender {
+
+ // if data is fully empty (consists of white spaces) -> do not add anything
+ // leading ws:
+ // - trim to one space (if at all present) append to output only if previous is ws
+ // trailing ws:
+ // - if present trim to single space
+
+ @Override
+ void append(
+ @NonNull T output,
+ @NonNull String data
+ ) {
+
+ final int startLength = output.length();
+
+ char c;
+
+ boolean previousIsWhiteSpace = false;
+
+ for (int i = 0, length = data.length(); i < length; i++) {
+
+ c = data.charAt(i);
+
+ if (Character.isWhitespace(c)) {
+ previousIsWhiteSpace = true;
+ continue;
+ }
+
+ if (previousIsWhiteSpace) {
+ // validate that output has ws as last char
+ final int outputLength = output.length();
+ if (outputLength > 0
+ && !Character.isWhitespace(output.charAt(outputLength - 1))) {
+ appendQuietly(output, ' ');
+ }
+ }
+
+ previousIsWhiteSpace = false;
+ appendQuietly(output, c);
+ }
+
+ // additionally check if previousIsWhiteSpace is true (if data ended with ws)
+ // BUT only if we have added something (otherwise the whole data is empty (white))
+ if (previousIsWhiteSpace && (startLength < output.length())) {
+ appendQuietly(output, ' ');
+ }
+ }
+ }
+}
diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java
index 14f33252..1dd20668 100644
--- a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java
+++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/MarkwonHtmlParserImplTest.java
@@ -33,7 +33,7 @@ public class MarkwonHtmlParserImplTest {
// all inline tags are correctly parsed
// a simple replacement that will return tag name as replacement (for this test purposes)
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement() {
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() {
@Nullable
@Override
public String replace(@NonNull Token.StartTag startTag) {
@@ -95,7 +95,7 @@ public class MarkwonHtmlParserImplTest {
"img", "input"
);
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement() {
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() {
@Nullable
@Override
public String replace(@NonNull Token.StartTag startTag) {
@@ -140,7 +140,7 @@ public class MarkwonHtmlParserImplTest {
@Test
public void blockVoidTags() {
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement() {
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() {
@Nullable
@Override
public String replace(@NonNull Token.StartTag startTag) {
@@ -209,7 +209,7 @@ public class MarkwonHtmlParserImplTest {
"FiveFiveFiveFiveFive"
);
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement() {
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() {
@Nullable
@Override
public String replace(@NonNull Token.StartTag startTag) {
@@ -277,7 +277,7 @@ public class MarkwonHtmlParserImplTest {
"video"
);
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement() {
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement() {
@Nullable
@Override
public String replace(@NonNull Token.StartTag startTag) {
@@ -328,7 +328,7 @@ public class MarkwonHtmlParserImplTest {
@Test
public void multipleFragmentsContinuation() {
- final MarkwonHtmlParserImpl impl = new MarkwonHtmlParserImpl(new HtmlEmptyTagReplacement());
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create(new HtmlEmptyTagReplacement());
final StringBuilder output = new StringBuilder();
@@ -424,7 +424,7 @@ public class MarkwonHtmlParserImplTest {
final String html = "12hello!";
impl.processFragment(output, html);
- assertEquals("12hello!", output.toString());
+ assertEquals(output.toString(), "12hello!", output.toString());
final CaptureBlockTagsAction action = new CaptureBlockTagsAction();
impl.flushBlockTags(output.length(), action);
@@ -473,6 +473,8 @@ public class MarkwonHtmlParserImplTest {
final StringBuilder output = new StringBuilder();
impl.processFragment(output, "italic emphasis italic
");
+ System.out.printf("output: `%s`%n", output);
+
final CaptureInlineTagsAction inlineTagsAction = new CaptureInlineTagsAction();
final CaptureBlockTagsAction blockTagsAction = new CaptureBlockTagsAction();
@@ -778,6 +780,30 @@ public class MarkwonHtmlParserImplTest {
assertEquals(0, blockTagsAction.tags.size());
}
+ @Test
+ public void blockTagNewLine() {
+
+ // we should make sure that a block tag will have a new line for it's
+ // content (white spaces before should be ignored)
+
+ final MarkwonHtmlParserImpl impl = MarkwonHtmlParserImpl.create();
+ final String html = "" +
+ " - ul-first" +
+ "
- ul-second" +
+ "
" +
+ " - ol-first" +
+ "
- ol-second" +
+ "
" +
+ " - ul-third" +
+ "
";
+
+ final StringBuilder output = new StringBuilder();
+ impl.processFragment(output, html);
+
+ final String[] split = output.toString().split("\n");
+ assertEquals(Arrays.toString(split), 5, split.length);
+ }
+
private static class CaptureTagsAction implements MarkwonHtmlParser.FlushAction {
boolean called;
diff --git a/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java
new file mode 100644
index 00000000..87923c33
--- /dev/null
+++ b/markwon-html-parser-impl/src/test/java/ru/noties/markwon/html/impl/TrimmingAppenderTest.java
@@ -0,0 +1,43 @@
+package ru.noties.markwon.html.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(manifest = Config.NONE)
+public class TrimmingAppenderTest {
+
+ private TrimmingAppender.Impl impl;
+
+ @Before
+ public void before() {
+ impl = new TrimmingAppender.Impl();
+ }
+
+ @Test
+ public void singlePart() {
+ final String input = " html body \n\ndiv hey ";
+ final StringBuilder builder = new StringBuilder();
+ impl.append(builder, input);
+ assertEquals("html body div hey ", builder.toString());
+ }
+
+ @Test
+ public void multiParts() {
+ final String[] inputs = {
+ "\n\n\n\n\nhtml\t body\n\ndiv ",
+ " span and go"
+ };
+ final StringBuilder builder = new StringBuilder();
+ for (String input : inputs) {
+ impl.append(builder, input);
+ }
+
+ assertEquals("html body div span and go", builder.toString());
+ }
+}
\ No newline at end of file
diff --git a/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java b/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java
index 650a0a53..86a96964 100644
--- a/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java
+++ b/markwon/src/main/java/ru/noties/markwon/SpannableBuilder.java
@@ -22,11 +22,20 @@ import java.util.Iterator;
@SuppressWarnings({"WeakerAccess", "unused"})
public class SpannableBuilder implements Appendable, CharSequence {
- // do not implement CharSequence (or any of Spanned interfaces)
- // we will be using SpannableStringBuilder anyway as a backing store
- // as it has tight connection with system (implements some hidden methods, etc)
-// private final SpannableStringBuilder builder;
+ public static void setSpans(@NonNull SpannableBuilder builder, @Nullable Object spans, int start, int end) {
+ if (spans != null) {
+ if (spans.getClass().isArray()) {
+ for (Object o : ((Object[]) spans)) {
+ builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ } else {
+ builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ }
+ }
+ }
+
+
private final StringBuilder builder;
// actually we might be just using ArrayList
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java
index 25b529fa..d2efe0d4 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRenderer.java
@@ -13,6 +13,7 @@ import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.MarkwonHtmlParser;
import ru.noties.markwon.renderer.html2.tag.EmphasisHandler;
import ru.noties.markwon.renderer.html2.tag.LinkHandler;
+import ru.noties.markwon.renderer.html2.tag.ListHandler;
import ru.noties.markwon.renderer.html2.tag.StrikeHandler;
import ru.noties.markwon.renderer.html2.tag.StrongEmphasisHandler;
import ru.noties.markwon.renderer.html2.tag.SubScriptHandler;
@@ -36,10 +37,16 @@ public abstract class MarkwonHtmlRenderer {
@NonNull
public static MarkwonHtmlRenderer create() {
+ return builderWithDefaults().build();
+ }
+
+ @NonNull
+ public static Builder builderWithDefaults() {
final EmphasisHandler emphasisHandler = new EmphasisHandler();
final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler();
final StrikeHandler strikeHandler = new StrikeHandler();
+ final ListHandler listHandler = new ListHandler();
return builder()
.handler("i", emphasisHandler)
@@ -55,7 +62,8 @@ public abstract class MarkwonHtmlRenderer {
.handler("s", strikeHandler)
.handler("strike", strikeHandler)
.handler("a", new LinkHandler())
- .build();
+ .handler("ul", listHandler)
+ .handler("ol", listHandler);
}
@NonNull
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java
index 991cb16c..0271732e 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/MarkwonHtmlRendererImpl.java
@@ -2,7 +2,6 @@ package ru.noties.markwon.renderer.html2;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.text.Spanned;
import java.util.List;
import java.util.Map;
@@ -36,7 +35,7 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
for (HtmlTag.Inline inline : tags) {
handler = tagHandler(inline.name());
if (handler != null) {
- setSpans(builder, handler.getSpans(configuration, inline), inline.start(), inline.end());
+ handler.handle(configuration, builder, inline);
}
}
}
@@ -49,7 +48,7 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
for (HtmlTag.Block block : tags) {
handler = tagHandler(block.name());
if (handler != null) {
- setSpans(builder, handler.getSpans(configuration, block), block.start(), block.end());
+ handler.handle(configuration, builder, block);
} else {
// see if any of children can be handled
apply(block.children());
@@ -66,16 +65,4 @@ class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
public TagHandler tagHandler(@NonNull String tagName) {
return tagHandlers.get(tagName);
}
-
- private static void setSpans(@NonNull SpannableBuilder builder, @Nullable Object spans, int start, int end) {
- if (spans != null) {
- if (spans.getClass().isArray()) {
- for (Object o : ((Object[]) spans)) {
- builder.setSpan(o, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- } else {
- builder.setSpan(spans, start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
- }
- }
}
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java
index a9ee5c70..d34218de 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/EmphasisHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class EmphasisHandler implements TagHandler {
+public class EmphasisHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java
index 8edfa020..faa952bc 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/LinkHandler.java
@@ -7,7 +7,7 @@ import android.text.TextUtils;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class LinkHandler implements TagHandler {
+public class LinkHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java
new file mode 100644
index 00000000..f7ddccd8
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/ListHandler.java
@@ -0,0 +1,83 @@
+package ru.noties.markwon.renderer.html2.tag;
+
+import android.support.annotation.NonNull;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.html.api.HtmlTag;
+
+public class ListHandler implements TagHandler {
+
+ @Override
+ public void handle(
+ @NonNull SpannableConfiguration configuration,
+ @NonNull SpannableBuilder builder,
+ @NonNull HtmlTag tag) {
+
+ if (!tag.isBlock()) {
+ return;
+ }
+
+ final HtmlTag.Block block = tag.getAsBlock();
+ final boolean ol = "ol".equals(block.name());
+ final boolean ul = "ul".equals(block.name());
+
+ if (!ol && !ul) {
+ return;
+ }
+
+ int number = 1;
+ final int bulletLevel = currentBulletListLevel(block);
+
+ Object spans;
+
+ for (HtmlTag.Block child : block.children()) {
+
+ visitChildren(configuration, builder, child);
+
+ if ("li".equals(child.name())) {
+ // insert list item here
+ if (ol) {
+ spans = configuration.factory().orderedListItem(
+ configuration.theme(),
+ number++
+ );
+ } else {
+ spans = configuration.factory().bulletListItem(
+ configuration.theme(),
+ bulletLevel
+ );
+ }
+ SpannableBuilder.setSpans(builder, spans, child.start(), child.end());
+ }
+ }
+ }
+
+ private void visitChildren(
+ @NonNull SpannableConfiguration configuration,
+ @NonNull SpannableBuilder builder,
+ @NonNull HtmlTag.Block block) {
+
+ TagHandler handler;
+
+ for (HtmlTag.Block child : block.children()) {
+ handler = configuration.htmlRenderer().tagHandler(child.name());
+ if (handler != null) {
+ handler.handle(configuration, builder, child);
+ } else {
+ visitChildren(configuration, builder, child);
+ }
+ }
+ }
+
+ private static int currentBulletListLevel(@NonNull HtmlTag.Block block) {
+ int level = 0;
+ while ((block = block.parent()) != null) {
+ if ("ul".equals(block.name())
+ || "ol".equals(block.name())) {
+ level += 1;
+ }
+ }
+ return level;
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java
new file mode 100644
index 00000000..b29bfe78
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SimpleTagHandler.java
@@ -0,0 +1,22 @@
+package ru.noties.markwon.renderer.html2.tag;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import ru.noties.markwon.SpannableBuilder;
+import ru.noties.markwon.SpannableConfiguration;
+import ru.noties.markwon.html.api.HtmlTag;
+
+public abstract class SimpleTagHandler implements TagHandler {
+
+ @Nullable
+ public abstract Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag);
+
+ @Override
+ public void handle(@NonNull SpannableConfiguration configuration, @NonNull SpannableBuilder builder, @NonNull HtmlTag tag) {
+ final Object spans = getSpans(configuration, tag);
+ if (spans != null) {
+ SpannableBuilder.setSpans(builder, spans, tag.start(), tag.end());
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java
index 574e20d1..ef8d0a71 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrikeHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class StrikeHandler implements TagHandler {
+public class StrikeHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java
index 5b390d1f..04d18a25 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/StrongEmphasisHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class StrongEmphasisHandler implements TagHandler {
+public class StrongEmphasisHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java
index 3e8ea546..a96f34bc 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SubScriptHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class SubScriptHandler implements TagHandler {
+public class SubScriptHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java
index a05ec567..c5eee815 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/SuperScriptHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class SuperScriptHandler implements TagHandler {
+public class SuperScriptHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java
index e8785616..39bdfc2f 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/TagHandler.java
@@ -1,13 +1,16 @@
package ru.noties.markwon.renderer.html2.tag;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
+import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
public interface TagHandler {
- @Nullable
- Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag);
+ void handle(
+ @NonNull SpannableConfiguration configuration,
+ @NonNull SpannableBuilder builder,
+ @NonNull HtmlTag tag
+ );
}
diff --git a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java
index 12d71d22..ee4bff01 100644
--- a/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java
+++ b/markwon/src/main/java/ru/noties/markwon/renderer/html2/tag/UnderlineHandler.java
@@ -6,7 +6,7 @@ import android.support.annotation.Nullable;
import ru.noties.markwon.SpannableConfiguration;
import ru.noties.markwon.html.api.HtmlTag;
-public class UnderlineHandler implements TagHandler {
+public class UnderlineHandler extends SimpleTagHandler {
@Nullable
@Override
public Object getSpans(@NonNull SpannableConfiguration configuration, @NonNull HtmlTag tag) {