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;
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;
class MarkwonHtmlRendererImpl extends MarkwonHtmlRenderer {
private final boolean allowNonClosedTags;
private final Map tagHandlers;
@SuppressWarnings("WeakerAccess")
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 Builder {
private final Map tagHandlers = new HashMap<>(2);
private boolean allowNonClosedTags;
private boolean excludeDefaults;
private boolean isBuilt;
void allowNonClosedTags(boolean allowNonClosedTags) {
checkState();
this.allowNonClosedTags = allowNonClosedTags;
}
void addHandler(@NonNull TagHandler tagHandler) {
checkState();
for (String tag : tagHandler.supportedTags()) {
tagHandlers.put(tag, tagHandler);
}
}
@Nullable
TagHandler getHandler(@NonNull String tagName) {
checkState();
return tagHandlers.get(tagName);
}
public void excludeDefaults(boolean excludeDefaults) {
checkState();
this.excludeDefaults = excludeDefaults;
}
@NonNull
public MarkwonHtmlRenderer build() {
checkState();
isBuilt = true;
if (!excludeDefaults) {
// register default handlers, check if a handler is present already for specified tag
registerDefaultHandlers();
}
// 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();
}
private void checkState() {
if (isBuilt) {
throw new IllegalStateException("Builder has been already built");
}
}
private void registerDefaultHandlers() {
add(ImageHandler.create());
add(new LinkHandler());
add(new BlockquoteHandler());
add(new SubScriptHandler());
add(new SuperScriptHandler());
add(new StrongEmphasisHandler());
add(new StrikeHandler());
add(new UnderlineHandler());
add(new ListHandler());
add(new EmphasisHandler());
add(new HeadingHandler());
}
private void add(@NonNull TagHandler tagHandler) {
for (String tag : tagHandler.supportedTags()) {
if (!tagHandlers.containsKey(tag)) {
tagHandlers.put(tag, tagHandler);
}
}
}
}
}