diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
index acf20c62..f69b2368 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
+++ b/markwon-html/src/main/java/ru/noties/markwon/html/HtmlPlugin.java
@@ -8,41 +8,57 @@ import org.commonmark.node.HtmlInline;
import org.commonmark.node.Node;
import ru.noties.markwon.AbstractMarkwonPlugin;
+import ru.noties.markwon.MarkwonConfiguration;
import ru.noties.markwon.MarkwonVisitor;
+import ru.noties.markwon.html.tag.BlockquoteHandler;
+import ru.noties.markwon.html.tag.EmphasisHandler;
+import ru.noties.markwon.html.tag.HeadingHandler;
+import ru.noties.markwon.html.tag.ImageHandler;
+import ru.noties.markwon.html.tag.LinkHandler;
+import ru.noties.markwon.html.tag.ListHandler;
+import ru.noties.markwon.html.tag.StrikeHandler;
+import ru.noties.markwon.html.tag.StrongEmphasisHandler;
+import ru.noties.markwon.html.tag.SubScriptHandler;
+import ru.noties.markwon.html.tag.SuperScriptHandler;
+import ru.noties.markwon.html.tag.UnderlineHandler;
+/**
+ * @since 3.0.0
+ */
public class HtmlPlugin extends AbstractMarkwonPlugin {
@NonNull
public static HtmlPlugin create() {
- return create(MarkwonHtmlRendererImpl.create(), MarkwonHtmlParserImpl.create());
+ return new HtmlPlugin();
}
- @NonNull
- public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer) {
- return create(renderer, MarkwonHtmlParserImpl.create());
+ public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
+
+ @Override
+ public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
+ builder.htmlParser(MarkwonHtmlParserImpl.create());
}
- @NonNull
- public static HtmlPlugin create(@NonNull MarkwonHtmlParser parser) {
- return create(MarkwonHtmlRendererImpl.create(), parser);
- }
-
- @NonNull
- public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) {
- return new HtmlPlugin(renderer, parser);
- }
-
- private final MarkwonHtmlRenderer renderer;
- private final MarkwonHtmlParser parser;
-
- public HtmlPlugin(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) {
- this.renderer = renderer;
- this.parser = parser;
+ @Override
+ public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
+ builder
+ .addHandler(new EmphasisHandler(), "i", "em", "cite", "dfn")
+ .addHandler(new StrongEmphasisHandler(), "b", "strong")
+ .addHandler(new SuperScriptHandler(), "sup")
+ .addHandler(new SubScriptHandler(), "sub")
+ .addHandler(new UnderlineHandler(), "u", "ins")
+ .addHandler(new StrikeHandler(), "s", "del")
+ .addHandler(new LinkHandler(), "a")
+ .addHandler(new ListHandler(), "ul", "ol")
+ .addHandler(ImageHandler.create(), "img")
+ .addHandler(new BlockquoteHandler(), "blockquote")
+ .addHandler(new HeadingHandler(), "h1", "h2", "h3", "h4", "h5", "h6");
}
@Override
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
- renderer.render(visitor, parser);
+ final MarkwonConfiguration configuration = visitor.configuration();
+ configuration.htmlRenderer().render(visitor, configuration.htmlParser());
}
@Override
@@ -64,7 +80,7 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) {
if (html != null) {
- parser.processFragment(visitor.builder(), html);
+ visitor.configuration().htmlParser().processFragment(visitor.builder(), html);
}
}
}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java b/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
deleted file mode 100644
index f9fed923..00000000
--- a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package ru.noties.markwon.html;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import ru.noties.markwon.MarkwonVisitor;
-
-/**
- * @since 2.0.0
- */
-public abstract class MarkwonHtmlRenderer {
-
- public abstract void render(
- @NonNull MarkwonVisitor visitor,
- @NonNull MarkwonHtmlParser parser
- );
-
- @Nullable
- public abstract TagHandler tagHandler(@NonNull String tagName);
-}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java b/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
deleted file mode 100644
index 33d292c9..00000000
--- a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
+++ /dev/null
@@ -1,195 +0,0 @@
-package ru.noties.markwon.html;
-
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Locale;
-import java.util.Map;
-
-import ru.noties.markwon.MarkwonVisitor;
-import ru.noties.markwon.html.tag.BlockquoteHandler;
-import ru.noties.markwon.html.tag.EmphasisHandler;
-import ru.noties.markwon.html.tag.HeadingHandler;
-import ru.noties.markwon.html.tag.ImageHandler;
-import ru.noties.markwon.html.tag.LinkHandler;
-import ru.noties.markwon.html.tag.ListHandler;
-import ru.noties.markwon.html.tag.StrikeHandler;
-import ru.noties.markwon.html.tag.StrongEmphasisHandler;
-import ru.noties.markwon.html.tag.SubScriptHandler;
-import ru.noties.markwon.html.tag.SuperScriptHandler;
-import ru.noties.markwon.html.tag.UnderlineHandler;
-
-public class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
-
- @NonNull
- public static MarkwonHtmlRendererImpl create() {
- return builderWithDefaults().build();
- }
-
- /**
- * @since 3.0.0
- */
- @NonNull
- public static MarkwonHtmlRendererImpl create(boolean allowNonClosedTags) {
- return builderWithDefaults(allowNonClosedTags).build();
- }
-
- @NonNull
- public static Builder builderWithDefaults() {
- return builderWithDefaults(false);
- }
-
- /**
- * @since 3.0.0
- */
- @NonNull
- public static Builder builderWithDefaults(boolean allowNonClosedTags) {
-
- final EmphasisHandler emphasisHandler = new EmphasisHandler();
- final StrongEmphasisHandler strongEmphasisHandler = new StrongEmphasisHandler();
- final StrikeHandler strikeHandler = new StrikeHandler();
- final UnderlineHandler underlineHandler = new UnderlineHandler();
- final ListHandler listHandler = new ListHandler();
-
- return builder()
- .allowNonClosedTags(allowNonClosedTags)
- .handler("i", emphasisHandler)
- .handler("em", emphasisHandler)
- .handler("cite", emphasisHandler)
- .handler("dfn", emphasisHandler)
- .handler("b", strongEmphasisHandler)
- .handler("strong", strongEmphasisHandler)
- .handler("sup", new SuperScriptHandler())
- .handler("sub", new SubScriptHandler())
- .handler("u", underlineHandler)
- .handler("ins", underlineHandler)
- .handler("del", strikeHandler)
- .handler("s", strikeHandler)
- .handler("strike", strikeHandler)
- .handler("a", new LinkHandler())
- .handler("ul", listHandler)
- .handler("ol", listHandler)
- .handler("img", ImageHandler.create())
- .handler("blockquote", new BlockquoteHandler())
- .handler("h1", new HeadingHandler(1))
- .handler("h2", new HeadingHandler(2))
- .handler("h3", new HeadingHandler(3))
- .handler("h4", new HeadingHandler(4))
- .handler("h5", new HeadingHandler(5))
- .handler("h6", new HeadingHandler(6));
- }
-
- @NonNull
- public static Builder builder() {
- return new Builder();
- }
-
- public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
-
- private final boolean allowNonClosedTags;
- private final Map tagHandlers;
-
- private MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map tagHandlers) {
- this.allowNonClosedTags = allowNonClosedTags;
- this.tagHandlers = tagHandlers;
- }
-
- @Override
- public void render(
- @NonNull final MarkwonVisitor visitor,
- @NonNull MarkwonHtmlParser parser) {
-
- final int end;
- if (!allowNonClosedTags) {
- end = HtmlTag.NO_END;
- } else {
- end = visitor.length();
- }
-
- parser.flushInlineTags(end, new MarkwonHtmlParser.FlushAction() {
- @Override
- public void apply(@NonNull List tags) {
-
- TagHandler handler;
-
- for (HtmlTag.Inline inline : tags) {
-
- // if tag is not closed -> do not render
- if (!inline.isClosed()) {
- continue;
- }
-
- handler = tagHandler(inline.name());
- if (handler != null) {
- handler.handle(visitor, MarkwonHtmlRendererImpl.this, inline);
- }
- }
- }
- });
-
- parser.flushBlockTags(end, new MarkwonHtmlParser.FlushAction() {
- @Override
- public void apply(@NonNull List tags) {
-
- TagHandler handler;
-
- for (HtmlTag.Block block : tags) {
-
- if (!block.isClosed()) {
- continue;
- }
-
- handler = tagHandler(block.name());
- if (handler != null) {
- handler.handle(visitor, MarkwonHtmlRendererImpl.this, block);
- } else {
- // see if any of children can be handled
- apply(block.children());
- }
- }
- }
- });
-
- parser.reset();
- }
-
- @Nullable
- @Override
- public TagHandler tagHandler(@NonNull String tagName) {
- return tagHandlers.get(tagName);
- }
-
- public static class Builder {
-
- private final Map tagHandlers = new HashMap<>(2);
- private boolean allowNonClosedTags;
-
- @NonNull
- public Builder handler(@NonNull String tagName, @NonNull TagHandler tagHandler) {
- tagHandlers.put(tagName.toLowerCase(Locale.US), tagHandler);
- return this;
- }
-
- /**
- * @param allowNonClosedTags that indicates if non-closed html tags should be rendered.
- * If this argument is true then all non-closed HTML tags
- * will be closed at the end of a document. Otherwise they will
- * be delivered non-closed {@code HtmlTag#isClosed()} and thus not
- * rendered at all
- * @since 3.0.0
- */
- @NonNull
- public Builder allowNonClosedTags(boolean allowNonClosedTags) {
- this.allowNonClosedTags = allowNonClosedTags;
- return this;
- }
-
- @NonNull
- public MarkwonHtmlRendererImpl build() {
- return new MarkwonHtmlRendererImpl(allowNonClosedTags, Collections.unmodifiableMap(tagHandlers));
- }
- }
-}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/span/SubScriptSpan.java b/markwon-html/src/main/java/ru/noties/markwon/html/span/SubScriptSpan.java
index d106c1a0..5ab51160 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/span/SubScriptSpan.java
+++ b/markwon-html/src/main/java/ru/noties/markwon/html/span/SubScriptSpan.java
@@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
-import ru.noties.markwon.html.MarkwonHtmlRendererImpl;
+import ru.noties.markwon.html.HtmlPlugin;
public class SubScriptSpan extends MetricAffectingSpan {
@@ -19,7 +19,7 @@ public class SubScriptSpan extends MetricAffectingSpan {
}
private void apply(TextPaint paint) {
- paint.setTextSize(paint.getTextSize() * MarkwonHtmlRendererImpl.SCRIPT_DEF_TEXT_SIZE_RATIO);
+ paint.setTextSize(paint.getTextSize() * HtmlPlugin.SCRIPT_DEF_TEXT_SIZE_RATIO);
paint.baselineShift -= (int) (paint.ascent() / 2);
}
}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/span/SuperScriptSpan.java b/markwon-html/src/main/java/ru/noties/markwon/html/span/SuperScriptSpan.java
index 9d43c563..7375309a 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/span/SuperScriptSpan.java
+++ b/markwon-html/src/main/java/ru/noties/markwon/html/span/SuperScriptSpan.java
@@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
import android.text.TextPaint;
import android.text.style.MetricAffectingSpan;
-import ru.noties.markwon.html.MarkwonHtmlRendererImpl;
+import ru.noties.markwon.html.HtmlPlugin;
public class SuperScriptSpan extends MetricAffectingSpan {
@@ -19,7 +19,7 @@ public class SuperScriptSpan extends MetricAffectingSpan {
}
private void apply(TextPaint paint) {
- paint.setTextSize(paint.getTextSize() * MarkwonHtmlRendererImpl.SCRIPT_DEF_TEXT_SIZE_RATIO);
+ paint.setTextSize(paint.getTextSize() * HtmlPlugin.SCRIPT_DEF_TEXT_SIZE_RATIO);
paint.baselineShift += (int) (paint.ascent() / 2);
}
}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/tag/HeadingHandler.java b/markwon-html/src/main/java/ru/noties/markwon/html/tag/HeadingHandler.java
index c3473483..a7de3a47 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/tag/HeadingHandler.java
+++ b/markwon-html/src/main/java/ru/noties/markwon/html/tag/HeadingHandler.java
@@ -13,12 +13,6 @@ import ru.noties.markwon.html.HtmlTag;
public class HeadingHandler extends SimpleTagHandler {
- private final int level;
-
- public HeadingHandler(int level) {
- this.level = level;
- }
-
@Nullable
@Override
public Object getSpans(
@@ -31,6 +25,18 @@ public class HeadingHandler extends SimpleTagHandler {
return null;
}
+ int level;
+ try {
+ level = Integer.parseInt(tag.name().substring(1));
+ } catch (NumberFormatException e) {
+ e.printStackTrace();
+ level = 0;
+ }
+
+ if (level < 1 || level > 6) {
+ return null;
+ }
+
CoreProps.HEADING_LEVEL.set(renderProps, level);
return factory.getSpans(configuration, renderProps);
diff --git a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
index c5f68fdc..81e16f2b 100644
--- a/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/AbstractMarkwonPlugin.java
@@ -9,6 +9,7 @@ import org.commonmark.parser.Parser;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.core.MarkwonTheme;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.priority.Priority;
@@ -69,6 +70,14 @@ public abstract class AbstractMarkwonPlugin implements MarkwonPlugin {
}
+ /**
+ * @inheritDoc
+ */
+ @Override
+ public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
+
+ }
+
/**
* @inheritDoc
*/
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
index e9d6aa2f..a390c46e 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonBuilderImpl.java
@@ -14,6 +14,7 @@ import java.util.List;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.core.MarkwonTheme;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.priority.PriorityProcessor;
@@ -102,7 +103,7 @@ class MarkwonBuilderImpl implements Markwon.Builder {
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
- final RenderProps renderProps = new RenderPropsImpl();
+ final MarkwonHtmlRenderer.Builder htmlRendererBuilder = MarkwonHtmlRenderer.builder();
for (MarkwonPlugin plugin : plugins) {
plugin.configureParser(parserBuilder);
@@ -111,13 +112,17 @@ class MarkwonBuilderImpl implements Markwon.Builder {
plugin.configureConfiguration(configurationBuilder);
plugin.configureVisitor(visitorBuilder);
plugin.configureSpansFactory(spanFactoryBuilder);
+ plugin.configureHtmlRenderer(htmlRendererBuilder);
}
final MarkwonConfiguration configuration = configurationBuilder.build(
themeBuilder.build(),
asyncDrawableLoaderBuilder.build(),
+ htmlRendererBuilder.build(),
spanFactoryBuilder.build());
+ final RenderProps renderProps = new RenderPropsImpl();
+
return new MarkwonImpl(
bufferType,
parserBuilder.build(),
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
index b5266c1d..af481ee8 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonConfiguration.java
@@ -4,6 +4,8 @@ import android.support.annotation.NonNull;
import ru.noties.markwon.core.MarkwonTheme;
import ru.noties.markwon.core.spans.LinkSpan;
+import ru.noties.markwon.html.MarkwonHtmlParser;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.ImageSizeResolver;
import ru.noties.markwon.image.ImageSizeResolverDef;
@@ -29,6 +31,8 @@ public class MarkwonConfiguration {
private final LinkSpan.Resolver linkResolver;
private final UrlProcessor urlProcessor;
private final ImageSizeResolver imageSizeResolver;
+ private final MarkwonHtmlParser htmlParser;
+ private final MarkwonHtmlRenderer htmlRenderer;
// @since 3.0.0
private final MarkwonSpansFactory spansFactory;
@@ -41,6 +45,8 @@ public class MarkwonConfiguration {
this.urlProcessor = builder.urlProcessor;
this.imageSizeResolver = builder.imageSizeResolver;
this.spansFactory = builder.spansFactory;
+ this.htmlParser = builder.htmlParser;
+ this.htmlRenderer = builder.htmlRenderer;
}
@NonNull
@@ -73,6 +79,16 @@ public class MarkwonConfiguration {
return imageSizeResolver;
}
+ @NonNull
+ public MarkwonHtmlParser htmlParser() {
+ return htmlParser;
+ }
+
+ @NonNull
+ public MarkwonHtmlRenderer htmlRenderer() {
+ return htmlRenderer;
+ }
+
/**
* @since 3.0.0
*/
@@ -90,6 +106,8 @@ public class MarkwonConfiguration {
private LinkSpan.Resolver linkResolver;
private UrlProcessor urlProcessor;
private ImageSizeResolver imageSizeResolver;
+ private MarkwonHtmlParser htmlParser;
+ private MarkwonHtmlRenderer htmlRenderer;
private MarkwonSpansFactory spansFactory;
Builder() {
@@ -113,6 +131,12 @@ public class MarkwonConfiguration {
return this;
}
+ @NonNull
+ public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
+ this.htmlParser = htmlParser;
+ return this;
+ }
+
/**
* @since 1.0.1
*/
@@ -126,10 +150,12 @@ public class MarkwonConfiguration {
public MarkwonConfiguration build(
@NonNull MarkwonTheme theme,
@NonNull AsyncDrawableLoader asyncDrawableLoader,
+ @NonNull MarkwonHtmlRenderer htmlRenderer,
@NonNull MarkwonSpansFactory spansFactory) {
this.theme = theme;
this.asyncDrawableLoader = asyncDrawableLoader;
+ this.htmlRenderer = htmlRenderer;
this.spansFactory = spansFactory;
if (syntaxHighlight == null) {
@@ -148,6 +174,10 @@ public class MarkwonConfiguration {
imageSizeResolver = new ImageSizeResolverDef();
}
+ if (htmlParser == null) {
+ htmlParser = MarkwonHtmlParser.noOp();
+ }
+
return new MarkwonConfiguration(this);
}
}
diff --git a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
index 2f9a6cb1..d887e619 100644
--- a/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
+++ b/markwon/src/main/java/ru/noties/markwon/MarkwonPlugin.java
@@ -8,6 +8,7 @@ import org.commonmark.node.Node;
import org.commonmark.parser.Parser;
import ru.noties.markwon.core.MarkwonTheme;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.image.MediaDecoder;
import ru.noties.markwon.image.SchemeHandler;
@@ -73,7 +74,13 @@ public interface MarkwonPlugin {
*/
void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder);
- // can be used to configure own properties and use between plugins
+ /**
+ * Configure {@link MarkwonHtmlRenderer} to add or remove HTML {@link ru.noties.markwon.html.TagHandler}s
+ *
+ * @see MarkwonHtmlRenderer
+ * @see MarkwonHtmlRenderer.Builder
+ */
+ void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder);
/**
* A method to store some arbitrary data in {@link RenderProps}. Although it won\'t make
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/HtmlTag.java b/markwon/src/main/java/ru/noties/markwon/html/HtmlTag.java
similarity index 100%
rename from markwon-html/src/main/java/ru/noties/markwon/html/HtmlTag.java
rename to markwon/src/main/java/ru/noties/markwon/html/HtmlTag.java
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlParser.java b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlParser.java
similarity index 100%
rename from markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlParser.java
rename to markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlParser.java
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java
similarity index 93%
rename from markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java
rename to markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java
index 3b49528a..ecf6e423 100644
--- a/markwon-html/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java
+++ b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlParserNoOp.java
@@ -4,15 +4,10 @@ import android.support.annotation.NonNull;
import java.util.Collections;
-/**
- * @see MarkwonHtmlParser
- * @since 2.0.0
- */
class MarkwonHtmlParserNoOp extends MarkwonHtmlParser {
-
@Override
public void processFragment(@NonNull T output, @NonNull String htmlFragment) {
-
+ // no op
}
@Override
@@ -27,6 +22,6 @@ class MarkwonHtmlParserNoOp extends MarkwonHtmlParser {
@Override
public void reset() {
-
+ // no op
}
}
diff --git a/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
new file mode 100644
index 00000000..9d641826
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRenderer.java
@@ -0,0 +1,65 @@
+package ru.noties.markwon.html;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import ru.noties.markwon.MarkwonVisitor;
+
+/**
+ * @since 2.0.0
+ */
+public abstract class MarkwonHtmlRenderer {
+
+ @NonNull
+ public static Builder builder() {
+ return new MarkwonHtmlRendererImpl.BuilderImpl();
+ }
+
+ public abstract void render(
+ @NonNull MarkwonVisitor visitor,
+ @NonNull MarkwonHtmlParser parser
+ );
+
+ @Nullable
+ public abstract TagHandler tagHandler(@NonNull String tagName);
+
+
+ /**
+ * @since 3.0.0
+ */
+ public interface Builder {
+
+ /**
+ * @param allowNonClosedTags parameter to indicate that all non-closed HTML tags should be
+ * closed at the end of a document. if {@code true} all non-closed
+ * tags will be force-closed at the end. Otherwise these tags will be
+ * ignored and thus not rendered.
+ * @return self
+ */
+ @NonNull
+ Builder allowNonClosedTags(boolean allowNonClosedTags);
+
+ /**
+ * Please note that if there is already a {@link TagHandler} registered with specified
+ * {@code tagName} it will be replaced with newly supplied one.
+ *
+ * @param tagHandler {@link TagHandler}
+ * @param tagName name of a tag
+ * @return self
+ */
+ @NonNull
+ Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName);
+
+ @NonNull
+ Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames);
+
+ @NonNull
+ Builder removeHandler(@NonNull String tagName);
+
+ @NonNull
+ Builder removeHandlers(@NonNull String... tagNames);
+
+ @NonNull
+ MarkwonHtmlRenderer build();
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
new file mode 100644
index 00000000..be78caec
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererImpl.java
@@ -0,0 +1,146 @@
+package ru.noties.markwon.html;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import ru.noties.markwon.MarkwonVisitor;
+
+class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
+
+ private final boolean allowNonClosedTags;
+ private final Map tagHandlers;
+
+ MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map tagHandlers) {
+ this.allowNonClosedTags = allowNonClosedTags;
+ this.tagHandlers = tagHandlers;
+ }
+
+ @Override
+ public void render(
+ @NonNull final MarkwonVisitor visitor,
+ @NonNull MarkwonHtmlParser parser) {
+
+ final int end;
+ if (!allowNonClosedTags) {
+ end = HtmlTag.NO_END;
+ } else {
+ end = visitor.length();
+ }
+
+ parser.flushInlineTags(end, new MarkwonHtmlParser.FlushAction() {
+ @Override
+ public void apply(@NonNull List tags) {
+
+ TagHandler handler;
+
+ for (HtmlTag.Inline inline : tags) {
+
+ // if tag is not closed -> do not render
+ if (!inline.isClosed()) {
+ continue;
+ }
+
+ handler = tagHandler(inline.name());
+ if (handler != null) {
+ handler.handle(visitor, MarkwonHtmlRendererImpl.this, inline);
+ }
+ }
+ }
+ });
+
+ parser.flushBlockTags(end, new MarkwonHtmlParser.FlushAction() {
+ @Override
+ public void apply(@NonNull List tags) {
+
+ TagHandler handler;
+
+ for (HtmlTag.Block block : tags) {
+
+ if (!block.isClosed()) {
+ continue;
+ }
+
+ handler = tagHandler(block.name());
+ if (handler != null) {
+ handler.handle(visitor, MarkwonHtmlRendererImpl.this, block);
+ } else {
+ // see if any of children can be handled
+ apply(block.children());
+ }
+ }
+ }
+ });
+
+ parser.reset();
+ }
+
+ @Nullable
+ @Override
+ public TagHandler tagHandler(@NonNull String tagName) {
+ return tagHandlers.get(tagName);
+ }
+
+ static class BuilderImpl implements Builder {
+
+ private final Map tagHandlers = new HashMap<>(2);
+ private boolean allowNonClosedTags;
+
+ @NonNull
+ @Override
+ public Builder allowNonClosedTags(boolean allowNonClosedTags) {
+ this.allowNonClosedTags = allowNonClosedTags;
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public Builder addHandler(@NonNull TagHandler tagHandler, @NonNull String tagName) {
+ tagHandlers.put(tagName, tagHandler);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public Builder addHandler(@NonNull TagHandler tagHandler, String... tagNames) {
+ for (String tagName : tagNames) {
+ if (tagName != null) {
+ tagHandlers.put(tagName, tagHandler);
+ }
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public Builder removeHandler(@NonNull String tagName) {
+ tagHandlers.remove(tagName);
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public Builder removeHandlers(@NonNull String... tagNames) {
+ for (String tagName : tagNames) {
+ if (tagName != null) {
+ tagHandlers.remove(tagName);
+ }
+ }
+ return this;
+ }
+
+ @NonNull
+ @Override
+ public MarkwonHtmlRenderer build() {
+ // okay, let's validate that we have at least one tagHandler registered
+ // if we have none -> return no-op implementation
+ return tagHandlers.size() > 0
+ ? new MarkwonHtmlRendererImpl(allowNonClosedTags, Collections.unmodifiableMap(tagHandlers))
+ : new MarkwonHtmlRendererNoOp();
+ }
+ }
+}
diff --git a/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererNoOp.java b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererNoOp.java
new file mode 100644
index 00000000..1a6ac51f
--- /dev/null
+++ b/markwon/src/main/java/ru/noties/markwon/html/MarkwonHtmlRendererNoOp.java
@@ -0,0 +1,20 @@
+package ru.noties.markwon.html;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+
+import ru.noties.markwon.MarkwonVisitor;
+
+class MarkwonHtmlRendererNoOp extends MarkwonHtmlRenderer {
+
+ @Override
+ public void render(@NonNull MarkwonVisitor visitor, @NonNull MarkwonHtmlParser parser) {
+ parser.reset();
+ }
+
+ @Nullable
+ @Override
+ public TagHandler tagHandler(@NonNull String tagName) {
+ return null;
+ }
+}
diff --git a/markwon-html/src/main/java/ru/noties/markwon/html/TagHandler.java b/markwon/src/main/java/ru/noties/markwon/html/TagHandler.java
similarity index 100%
rename from markwon-html/src/main/java/ru/noties/markwon/html/TagHandler.java
rename to markwon/src/main/java/ru/noties/markwon/html/TagHandler.java
diff --git a/markwon/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java b/markwon/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
index 2ce7c25e..e451f74e 100644
--- a/markwon/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
+++ b/markwon/src/test/java/ru/noties/markwon/MarkwonBuilderImplTest.java
@@ -21,6 +21,7 @@ import java.util.List;
import ru.noties.markwon.core.CorePlugin;
import ru.noties.markwon.core.MarkwonTheme;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import ru.noties.markwon.priority.Priority;
import ru.noties.markwon.priority.PriorityProcessor;
@@ -216,6 +217,7 @@ public class MarkwonBuilderImplTest {
verify(plugin, times(1)).configureConfiguration(any(MarkwonConfiguration.Builder.class));
verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class));
verify(plugin, times(1)).configureSpansFactory(any(MarkwonSpansFactory.Builder.class));
+ verify(plugin, times(1)).configureHtmlRenderer(any(MarkwonHtmlRenderer.Builder.class));
// we do not know how many times exactly, but at least once it must be called
verify(plugin, atLeast(1)).priority();
diff --git a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java
index a8f39248..9c0262bb 100644
--- a/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java
+++ b/markwon/src/test/java/ru/noties/markwon/syntax/SyntaxHighlightTest.java
@@ -26,6 +26,7 @@ import ru.noties.markwon.SpanFactory;
import ru.noties.markwon.SpannableBuilder;
import ru.noties.markwon.core.CorePluginBridge;
import ru.noties.markwon.core.MarkwonTheme;
+import ru.noties.markwon.html.MarkwonHtmlRenderer;
import ru.noties.markwon.image.AsyncDrawableLoader;
import static org.junit.Assert.assertEquals;
@@ -83,7 +84,7 @@ public class SyntaxHighlightTest {
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
.syntaxHighlight(highlight)
- .build(mock(MarkwonTheme.class), mock(AsyncDrawableLoader.class), spansFactory);
+ .build(mock(MarkwonTheme.class), mock(AsyncDrawableLoader.class), mock(MarkwonHtmlRenderer.class), spansFactory);
final Map, MarkwonVisitor.NodeVisitor extends Node>> visitorMap = Collections.emptyMap();