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> visitorMap = Collections.emptyMap();