Bring back html-api to core module
This commit is contained in:
parent
ec3d596d8a
commit
6f025b9a95
@ -8,41 +8,57 @@ import org.commonmark.node.HtmlInline;
|
|||||||
import org.commonmark.node.Node;
|
import org.commonmark.node.Node;
|
||||||
|
|
||||||
import ru.noties.markwon.AbstractMarkwonPlugin;
|
import ru.noties.markwon.AbstractMarkwonPlugin;
|
||||||
|
import ru.noties.markwon.MarkwonConfiguration;
|
||||||
import ru.noties.markwon.MarkwonVisitor;
|
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 {
|
public class HtmlPlugin extends AbstractMarkwonPlugin {
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static HtmlPlugin create() {
|
public static HtmlPlugin create() {
|
||||||
return create(MarkwonHtmlRendererImpl.create(), MarkwonHtmlParserImpl.create());
|
return new HtmlPlugin();
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
public static final float SCRIPT_DEF_TEXT_SIZE_RATIO = .75F;
|
||||||
public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer) {
|
|
||||||
return create(renderer, MarkwonHtmlParserImpl.create());
|
@Override
|
||||||
|
public void configureConfiguration(@NonNull MarkwonConfiguration.Builder builder) {
|
||||||
|
builder.htmlParser(MarkwonHtmlParserImpl.create());
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@Override
|
||||||
public static HtmlPlugin create(@NonNull MarkwonHtmlParser parser) {
|
public void configureHtmlRenderer(@NonNull MarkwonHtmlRenderer.Builder builder) {
|
||||||
return create(MarkwonHtmlRendererImpl.create(), parser);
|
builder
|
||||||
}
|
.addHandler(new EmphasisHandler(), "i", "em", "cite", "dfn")
|
||||||
|
.addHandler(new StrongEmphasisHandler(), "b", "strong")
|
||||||
@NonNull
|
.addHandler(new SuperScriptHandler(), "sup")
|
||||||
public static HtmlPlugin create(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) {
|
.addHandler(new SubScriptHandler(), "sub")
|
||||||
return new HtmlPlugin(renderer, parser);
|
.addHandler(new UnderlineHandler(), "u", "ins")
|
||||||
}
|
.addHandler(new StrikeHandler(), "s", "del")
|
||||||
|
.addHandler(new LinkHandler(), "a")
|
||||||
private final MarkwonHtmlRenderer renderer;
|
.addHandler(new ListHandler(), "ul", "ol")
|
||||||
private final MarkwonHtmlParser parser;
|
.addHandler(ImageHandler.create(), "img")
|
||||||
|
.addHandler(new BlockquoteHandler(), "blockquote")
|
||||||
public HtmlPlugin(@NonNull MarkwonHtmlRenderer renderer, @NonNull MarkwonHtmlParser parser) {
|
.addHandler(new HeadingHandler(), "h1", "h2", "h3", "h4", "h5", "h6");
|
||||||
this.renderer = renderer;
|
|
||||||
this.parser = parser;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterRender(@NonNull Node node, @NonNull MarkwonVisitor visitor) {
|
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
|
@Override
|
||||||
@ -64,7 +80,7 @@ public class HtmlPlugin extends AbstractMarkwonPlugin {
|
|||||||
|
|
||||||
private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) {
|
private void visitHtml(@NonNull MarkwonVisitor visitor, @Nullable String html) {
|
||||||
if (html != null) {
|
if (html != null) {
|
||||||
parser.processFragment(visitor.builder(), html);
|
visitor.configuration().htmlParser().processFragment(visitor.builder(), html);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
|
||||||
}
|
|
@ -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<String, TagHandler> tagHandlers;
|
|
||||||
|
|
||||||
private MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map<String, TagHandler> 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<HtmlTag.Inline>() {
|
|
||||||
@Override
|
|
||||||
public void apply(@NonNull List<HtmlTag.Inline> 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<HtmlTag.Block>() {
|
|
||||||
@Override
|
|
||||||
public void apply(@NonNull List<HtmlTag.Block> 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<String, TagHandler> 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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.style.MetricAffectingSpan;
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRendererImpl;
|
import ru.noties.markwon.html.HtmlPlugin;
|
||||||
|
|
||||||
public class SubScriptSpan extends MetricAffectingSpan {
|
public class SubScriptSpan extends MetricAffectingSpan {
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ public class SubScriptSpan extends MetricAffectingSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void apply(TextPaint paint) {
|
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);
|
paint.baselineShift -= (int) (paint.ascent() / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import android.support.annotation.NonNull;
|
|||||||
import android.text.TextPaint;
|
import android.text.TextPaint;
|
||||||
import android.text.style.MetricAffectingSpan;
|
import android.text.style.MetricAffectingSpan;
|
||||||
|
|
||||||
import ru.noties.markwon.html.MarkwonHtmlRendererImpl;
|
import ru.noties.markwon.html.HtmlPlugin;
|
||||||
|
|
||||||
public class SuperScriptSpan extends MetricAffectingSpan {
|
public class SuperScriptSpan extends MetricAffectingSpan {
|
||||||
|
|
||||||
@ -19,7 +19,7 @@ public class SuperScriptSpan extends MetricAffectingSpan {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void apply(TextPaint paint) {
|
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);
|
paint.baselineShift += (int) (paint.ascent() / 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,12 +13,6 @@ import ru.noties.markwon.html.HtmlTag;
|
|||||||
|
|
||||||
public class HeadingHandler extends SimpleTagHandler {
|
public class HeadingHandler extends SimpleTagHandler {
|
||||||
|
|
||||||
private final int level;
|
|
||||||
|
|
||||||
public HeadingHandler(int level) {
|
|
||||||
this.level = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Object getSpans(
|
public Object getSpans(
|
||||||
@ -31,6 +25,18 @@ public class HeadingHandler extends SimpleTagHandler {
|
|||||||
return null;
|
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);
|
CoreProps.HEADING_LEVEL.set(renderProps, level);
|
||||||
|
|
||||||
return factory.getSpans(configuration, renderProps);
|
return factory.getSpans(configuration, renderProps);
|
||||||
|
@ -9,6 +9,7 @@ import org.commonmark.parser.Parser;
|
|||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.priority.Priority;
|
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
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
@ -14,6 +14,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.priority.PriorityProcessor;
|
import ru.noties.markwon.priority.PriorityProcessor;
|
||||||
|
|
||||||
@ -102,7 +103,7 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
|
final MarkwonConfiguration.Builder configurationBuilder = new MarkwonConfiguration.Builder();
|
||||||
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
|
final MarkwonVisitor.Builder visitorBuilder = new MarkwonVisitorImpl.BuilderImpl();
|
||||||
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
final MarkwonSpansFactory.Builder spanFactoryBuilder = new MarkwonSpansFactoryImpl.BuilderImpl();
|
||||||
final RenderProps renderProps = new RenderPropsImpl();
|
final MarkwonHtmlRenderer.Builder htmlRendererBuilder = MarkwonHtmlRenderer.builder();
|
||||||
|
|
||||||
for (MarkwonPlugin plugin : plugins) {
|
for (MarkwonPlugin plugin : plugins) {
|
||||||
plugin.configureParser(parserBuilder);
|
plugin.configureParser(parserBuilder);
|
||||||
@ -111,13 +112,17 @@ class MarkwonBuilderImpl implements Markwon.Builder {
|
|||||||
plugin.configureConfiguration(configurationBuilder);
|
plugin.configureConfiguration(configurationBuilder);
|
||||||
plugin.configureVisitor(visitorBuilder);
|
plugin.configureVisitor(visitorBuilder);
|
||||||
plugin.configureSpansFactory(spanFactoryBuilder);
|
plugin.configureSpansFactory(spanFactoryBuilder);
|
||||||
|
plugin.configureHtmlRenderer(htmlRendererBuilder);
|
||||||
}
|
}
|
||||||
|
|
||||||
final MarkwonConfiguration configuration = configurationBuilder.build(
|
final MarkwonConfiguration configuration = configurationBuilder.build(
|
||||||
themeBuilder.build(),
|
themeBuilder.build(),
|
||||||
asyncDrawableLoaderBuilder.build(),
|
asyncDrawableLoaderBuilder.build(),
|
||||||
|
htmlRendererBuilder.build(),
|
||||||
spanFactoryBuilder.build());
|
spanFactoryBuilder.build());
|
||||||
|
|
||||||
|
final RenderProps renderProps = new RenderPropsImpl();
|
||||||
|
|
||||||
return new MarkwonImpl(
|
return new MarkwonImpl(
|
||||||
bufferType,
|
bufferType,
|
||||||
parserBuilder.build(),
|
parserBuilder.build(),
|
||||||
|
@ -4,6 +4,8 @@ import android.support.annotation.NonNull;
|
|||||||
|
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
import ru.noties.markwon.core.spans.LinkSpan;
|
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.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.image.ImageSizeResolver;
|
import ru.noties.markwon.image.ImageSizeResolver;
|
||||||
import ru.noties.markwon.image.ImageSizeResolverDef;
|
import ru.noties.markwon.image.ImageSizeResolverDef;
|
||||||
@ -29,6 +31,8 @@ public class MarkwonConfiguration {
|
|||||||
private final LinkSpan.Resolver linkResolver;
|
private final LinkSpan.Resolver linkResolver;
|
||||||
private final UrlProcessor urlProcessor;
|
private final UrlProcessor urlProcessor;
|
||||||
private final ImageSizeResolver imageSizeResolver;
|
private final ImageSizeResolver imageSizeResolver;
|
||||||
|
private final MarkwonHtmlParser htmlParser;
|
||||||
|
private final MarkwonHtmlRenderer htmlRenderer;
|
||||||
|
|
||||||
// @since 3.0.0
|
// @since 3.0.0
|
||||||
private final MarkwonSpansFactory spansFactory;
|
private final MarkwonSpansFactory spansFactory;
|
||||||
@ -41,6 +45,8 @@ public class MarkwonConfiguration {
|
|||||||
this.urlProcessor = builder.urlProcessor;
|
this.urlProcessor = builder.urlProcessor;
|
||||||
this.imageSizeResolver = builder.imageSizeResolver;
|
this.imageSizeResolver = builder.imageSizeResolver;
|
||||||
this.spansFactory = builder.spansFactory;
|
this.spansFactory = builder.spansFactory;
|
||||||
|
this.htmlParser = builder.htmlParser;
|
||||||
|
this.htmlRenderer = builder.htmlRenderer;
|
||||||
}
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -73,6 +79,16 @@ public class MarkwonConfiguration {
|
|||||||
return imageSizeResolver;
|
return imageSizeResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonHtmlParser htmlParser() {
|
||||||
|
return htmlParser;
|
||||||
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public MarkwonHtmlRenderer htmlRenderer() {
|
||||||
|
return htmlRenderer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 3.0.0
|
* @since 3.0.0
|
||||||
*/
|
*/
|
||||||
@ -90,6 +106,8 @@ public class MarkwonConfiguration {
|
|||||||
private LinkSpan.Resolver linkResolver;
|
private LinkSpan.Resolver linkResolver;
|
||||||
private UrlProcessor urlProcessor;
|
private UrlProcessor urlProcessor;
|
||||||
private ImageSizeResolver imageSizeResolver;
|
private ImageSizeResolver imageSizeResolver;
|
||||||
|
private MarkwonHtmlParser htmlParser;
|
||||||
|
private MarkwonHtmlRenderer htmlRenderer;
|
||||||
private MarkwonSpansFactory spansFactory;
|
private MarkwonSpansFactory spansFactory;
|
||||||
|
|
||||||
Builder() {
|
Builder() {
|
||||||
@ -113,6 +131,12 @@ public class MarkwonConfiguration {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public Builder htmlParser(@NonNull MarkwonHtmlParser htmlParser) {
|
||||||
|
this.htmlParser = htmlParser;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.0.1
|
* @since 1.0.1
|
||||||
*/
|
*/
|
||||||
@ -126,10 +150,12 @@ public class MarkwonConfiguration {
|
|||||||
public MarkwonConfiguration build(
|
public MarkwonConfiguration build(
|
||||||
@NonNull MarkwonTheme theme,
|
@NonNull MarkwonTheme theme,
|
||||||
@NonNull AsyncDrawableLoader asyncDrawableLoader,
|
@NonNull AsyncDrawableLoader asyncDrawableLoader,
|
||||||
|
@NonNull MarkwonHtmlRenderer htmlRenderer,
|
||||||
@NonNull MarkwonSpansFactory spansFactory) {
|
@NonNull MarkwonSpansFactory spansFactory) {
|
||||||
|
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.asyncDrawableLoader = asyncDrawableLoader;
|
this.asyncDrawableLoader = asyncDrawableLoader;
|
||||||
|
this.htmlRenderer = htmlRenderer;
|
||||||
this.spansFactory = spansFactory;
|
this.spansFactory = spansFactory;
|
||||||
|
|
||||||
if (syntaxHighlight == null) {
|
if (syntaxHighlight == null) {
|
||||||
@ -148,6 +174,10 @@ public class MarkwonConfiguration {
|
|||||||
imageSizeResolver = new ImageSizeResolverDef();
|
imageSizeResolver = new ImageSizeResolverDef();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (htmlParser == null) {
|
||||||
|
htmlParser = MarkwonHtmlParser.noOp();
|
||||||
|
}
|
||||||
|
|
||||||
return new MarkwonConfiguration(this);
|
return new MarkwonConfiguration(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import org.commonmark.node.Node;
|
|||||||
import org.commonmark.parser.Parser;
|
import org.commonmark.parser.Parser;
|
||||||
|
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.image.MediaDecoder;
|
import ru.noties.markwon.image.MediaDecoder;
|
||||||
import ru.noties.markwon.image.SchemeHandler;
|
import ru.noties.markwon.image.SchemeHandler;
|
||||||
@ -73,7 +74,13 @@ public interface MarkwonPlugin {
|
|||||||
*/
|
*/
|
||||||
void configureSpansFactory(@NonNull MarkwonSpansFactory.Builder builder);
|
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
|
* A method to store some arbitrary data in {@link RenderProps}. Although it won\'t make
|
||||||
|
@ -4,15 +4,10 @@ import android.support.annotation.NonNull;
|
|||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
/**
|
|
||||||
* @see MarkwonHtmlParser
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
class MarkwonHtmlParserNoOp extends MarkwonHtmlParser {
|
class MarkwonHtmlParserNoOp extends MarkwonHtmlParser {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public <T extends Appendable & CharSequence> void processFragment(@NonNull T output, @NonNull String htmlFragment) {
|
public <T extends Appendable & CharSequence> void processFragment(@NonNull T output, @NonNull String htmlFragment) {
|
||||||
|
// no op
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -27,6 +22,6 @@ class MarkwonHtmlParserNoOp extends MarkwonHtmlParser {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
|
// no op
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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<String, TagHandler> tagHandlers;
|
||||||
|
|
||||||
|
MarkwonHtmlRendererImpl(boolean allowNonClosedTags, @NonNull Map<String, TagHandler> 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<HtmlTag.Inline>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull List<HtmlTag.Inline> 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<HtmlTag.Block>() {
|
||||||
|
@Override
|
||||||
|
public void apply(@NonNull List<HtmlTag.Block> 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<String, TagHandler> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -21,6 +21,7 @@ import java.util.List;
|
|||||||
|
|
||||||
import ru.noties.markwon.core.CorePlugin;
|
import ru.noties.markwon.core.CorePlugin;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
import ru.noties.markwon.priority.Priority;
|
import ru.noties.markwon.priority.Priority;
|
||||||
import ru.noties.markwon.priority.PriorityProcessor;
|
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)).configureConfiguration(any(MarkwonConfiguration.Builder.class));
|
||||||
verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class));
|
verify(plugin, times(1)).configureVisitor(any(MarkwonVisitor.Builder.class));
|
||||||
verify(plugin, times(1)).configureSpansFactory(any(MarkwonSpansFactory.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
|
// we do not know how many times exactly, but at least once it must be called
|
||||||
verify(plugin, atLeast(1)).priority();
|
verify(plugin, atLeast(1)).priority();
|
||||||
|
@ -26,6 +26,7 @@ import ru.noties.markwon.SpanFactory;
|
|||||||
import ru.noties.markwon.SpannableBuilder;
|
import ru.noties.markwon.SpannableBuilder;
|
||||||
import ru.noties.markwon.core.CorePluginBridge;
|
import ru.noties.markwon.core.CorePluginBridge;
|
||||||
import ru.noties.markwon.core.MarkwonTheme;
|
import ru.noties.markwon.core.MarkwonTheme;
|
||||||
|
import ru.noties.markwon.html.MarkwonHtmlRenderer;
|
||||||
import ru.noties.markwon.image.AsyncDrawableLoader;
|
import ru.noties.markwon.image.AsyncDrawableLoader;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
@ -83,7 +84,7 @@ public class SyntaxHighlightTest {
|
|||||||
|
|
||||||
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
|
final MarkwonConfiguration configuration = MarkwonConfiguration.builder()
|
||||||
.syntaxHighlight(highlight)
|
.syntaxHighlight(highlight)
|
||||||
.build(mock(MarkwonTheme.class), mock(AsyncDrawableLoader.class), spansFactory);
|
.build(mock(MarkwonTheme.class), mock(AsyncDrawableLoader.class), mock(MarkwonHtmlRenderer.class), spansFactory);
|
||||||
|
|
||||||
final Map<Class<? extends Node>, MarkwonVisitor.NodeVisitor<? extends Node>> visitorMap = Collections.emptyMap();
|
final Map<Class<? extends Node>, MarkwonVisitor.NodeVisitor<? extends Node>> visitorMap = Collections.emptyMap();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user