Inline html handling (no images)
This commit is contained in:
parent
e0c10c658b
commit
bf18b87420
@ -47,20 +47,6 @@ public class MainActivity extends Activity {
|
|||||||
|
|
||||||
final TextView textView = (TextView) findViewById(R.id.activity_main);
|
final TextView textView = (TextView) findViewById(R.id.activity_main);
|
||||||
|
|
||||||
// final Drawable drawable = getDrawable(R.mipmap.ic_launcher);
|
|
||||||
//// drawable.setBounds(0, 0, 16, 16);
|
|
||||||
// final SpannableStringBuilder builder = new SpannableStringBuilder();
|
|
||||||
// for (int i = 0; i < 10; i++) {
|
|
||||||
// builder.append("text here and icon: \u00a0");
|
|
||||||
// //noinspection WrongConstant
|
|
||||||
// builder.setSpan(new AsyncDrawableSpan(drawable, i % 3), builder.length() - 1, builder.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
// builder.append('\n');
|
|
||||||
// }
|
|
||||||
// textView.setText(builder);
|
|
||||||
//
|
|
||||||
// if (true) {
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
final Picasso picasso = new Picasso.Builder(this)
|
final Picasso picasso = new Picasso.Builder(this)
|
||||||
.listener(new Picasso.Listener() {
|
.listener(new Picasso.Listener() {
|
||||||
@ -78,7 +64,8 @@ public class MainActivity extends Activity {
|
|||||||
Scanner scanner = null;
|
Scanner scanner = null;
|
||||||
String md = null;
|
String md = null;
|
||||||
try {
|
try {
|
||||||
stream = getAssets().open("scrollable.md");
|
// stream = getAssets().open("scrollable.md");
|
||||||
|
stream = getAssets().open("test.md");
|
||||||
scanner = new Scanner(stream).useDelimiter("\\A");
|
scanner = new Scanner(stream).useDelimiter("\\A");
|
||||||
if (scanner.hasNext()) {
|
if (scanner.hasNext()) {
|
||||||
md = scanner.next();
|
md = scanner.next();
|
||||||
|
@ -3,6 +3,7 @@ package ru.noties.markwon.renderer;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
|
|
||||||
|
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.LinkSpan;
|
import ru.noties.markwon.spans.LinkSpan;
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
@ -22,12 +23,14 @@ public class SpannableConfiguration {
|
|||||||
private final AsyncDrawable.Loader asyncDrawableLoader;
|
private final AsyncDrawable.Loader asyncDrawableLoader;
|
||||||
private final SyntaxHighlight syntaxHighlight;
|
private final SyntaxHighlight syntaxHighlight;
|
||||||
private final LinkSpan.Resolver linkResolver;
|
private final LinkSpan.Resolver linkResolver;
|
||||||
|
private final SpannableHtmlParser htmlParser;
|
||||||
|
|
||||||
private SpannableConfiguration(Builder builder) {
|
private SpannableConfiguration(Builder builder) {
|
||||||
this.theme = builder.theme;
|
this.theme = builder.theme;
|
||||||
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
this.asyncDrawableLoader = builder.asyncDrawableLoader;
|
||||||
this.syntaxHighlight = builder.syntaxHighlight;
|
this.syntaxHighlight = builder.syntaxHighlight;
|
||||||
this.linkResolver = builder.linkResolver;
|
this.linkResolver = builder.linkResolver;
|
||||||
|
this.htmlParser = builder.htmlParser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpannableTheme theme() {
|
public SpannableTheme theme() {
|
||||||
@ -46,6 +49,10 @@ public class SpannableConfiguration {
|
|||||||
return linkResolver;
|
return linkResolver;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public SpannableHtmlParser htmlParser() {
|
||||||
|
return htmlParser;
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
@ -53,6 +60,7 @@ public class SpannableConfiguration {
|
|||||||
private AsyncDrawable.Loader asyncDrawableLoader;
|
private AsyncDrawable.Loader asyncDrawableLoader;
|
||||||
private SyntaxHighlight syntaxHighlight;
|
private SyntaxHighlight syntaxHighlight;
|
||||||
private LinkSpan.Resolver linkResolver;
|
private LinkSpan.Resolver linkResolver;
|
||||||
|
private SpannableHtmlParser htmlParser;
|
||||||
|
|
||||||
public Builder(Context context) {
|
public Builder(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
@ -78,6 +86,11 @@ public class SpannableConfiguration {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Builder htmlParser(SpannableHtmlParser htmlParser) {
|
||||||
|
this.htmlParser = htmlParser;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public SpannableConfiguration build() {
|
public SpannableConfiguration build() {
|
||||||
if (theme == null) {
|
if (theme == null) {
|
||||||
theme = SpannableTheme.create(context);
|
theme = SpannableTheme.create(context);
|
||||||
@ -91,6 +104,9 @@ public class SpannableConfiguration {
|
|||||||
if (linkResolver == null) {
|
if (linkResolver == null) {
|
||||||
linkResolver = new LinkResolverDef();
|
linkResolver = new LinkResolverDef();
|
||||||
}
|
}
|
||||||
|
if (htmlParser == null) {
|
||||||
|
htmlParser = SpannableHtmlParser.create(theme);
|
||||||
|
}
|
||||||
return new SpannableConfiguration(this);
|
return new SpannableConfiguration(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@ import android.support.annotation.NonNull;
|
|||||||
import android.text.SpannableStringBuilder;
|
import android.text.SpannableStringBuilder;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.style.StrikethroughSpan;
|
import android.text.style.StrikethroughSpan;
|
||||||
import android.text.style.URLSpan;
|
|
||||||
|
|
||||||
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
||||||
import org.commonmark.node.AbstractVisitor;
|
import org.commonmark.node.AbstractVisitor;
|
||||||
@ -17,6 +16,7 @@ import org.commonmark.node.FencedCodeBlock;
|
|||||||
import org.commonmark.node.HardLineBreak;
|
import org.commonmark.node.HardLineBreak;
|
||||||
import org.commonmark.node.Heading;
|
import org.commonmark.node.Heading;
|
||||||
import org.commonmark.node.HtmlBlock;
|
import org.commonmark.node.HtmlBlock;
|
||||||
|
import org.commonmark.node.HtmlInline;
|
||||||
import org.commonmark.node.Image;
|
import org.commonmark.node.Image;
|
||||||
import org.commonmark.node.Link;
|
import org.commonmark.node.Link;
|
||||||
import org.commonmark.node.ListBlock;
|
import org.commonmark.node.ListBlock;
|
||||||
@ -29,7 +29,11 @@ import org.commonmark.node.StrongEmphasis;
|
|||||||
import org.commonmark.node.Text;
|
import org.commonmark.node.Text;
|
||||||
import org.commonmark.node.ThematicBreak;
|
import org.commonmark.node.ThematicBreak;
|
||||||
|
|
||||||
|
import java.util.ArrayDeque;
|
||||||
|
import java.util.Deque;
|
||||||
|
|
||||||
import ru.noties.debug.Debug;
|
import ru.noties.debug.Debug;
|
||||||
|
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
import ru.noties.markwon.spans.AsyncDrawable;
|
||||||
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
import ru.noties.markwon.spans.AsyncDrawableSpan;
|
||||||
import ru.noties.markwon.spans.BlockQuoteSpan;
|
import ru.noties.markwon.spans.BlockQuoteSpan;
|
||||||
@ -42,10 +46,14 @@ import ru.noties.markwon.spans.OrderedListItemSpan;
|
|||||||
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||||
import ru.noties.markwon.spans.ThematicBreakSpan;
|
import ru.noties.markwon.spans.ThematicBreakSpan;
|
||||||
|
|
||||||
|
// please do not reuse between different texts (due to the html handling)
|
||||||
public class SpannableMarkdownVisitor extends AbstractVisitor {
|
public class SpannableMarkdownVisitor extends AbstractVisitor {
|
||||||
|
|
||||||
|
private static final String HTML_CONTENT = "<%1$s>%2$s</%1$s>";
|
||||||
|
|
||||||
private final SpannableConfiguration configuration;
|
private final SpannableConfiguration configuration;
|
||||||
private final SpannableStringBuilder builder;
|
private final SpannableStringBuilder builder;
|
||||||
|
private final Deque<HtmlInlineItem> htmlInlineItems;
|
||||||
|
|
||||||
private int blockQuoteIndent;
|
private int blockQuoteIndent;
|
||||||
private int listLevel;
|
private int listLevel;
|
||||||
@ -56,17 +64,16 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
) {
|
) {
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.builder = builder;
|
this.builder = builder;
|
||||||
|
this.htmlInlineItems = new ArrayDeque<>(2);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Text text) {
|
public void visit(Text text) {
|
||||||
// Debug.i(text);
|
|
||||||
builder.append(text.getLiteral());
|
builder.append(text.getLiteral());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(StrongEmphasis strongEmphasis) {
|
public void visit(StrongEmphasis strongEmphasis) {
|
||||||
// Debug.i(strongEmphasis);
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
visitChildren(strongEmphasis);
|
visitChildren(strongEmphasis);
|
||||||
setSpan(length, new StrongEmphasisSpan());
|
setSpan(length, new StrongEmphasisSpan());
|
||||||
@ -74,7 +81,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Emphasis emphasis) {
|
public void visit(Emphasis emphasis) {
|
||||||
// Debug.i(emphasis);
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
visitChildren(emphasis);
|
visitChildren(emphasis);
|
||||||
setSpan(length, new EmphasisSpan());
|
setSpan(length, new EmphasisSpan());
|
||||||
@ -83,8 +89,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(BlockQuote blockQuote) {
|
public void visit(BlockQuote blockQuote) {
|
||||||
|
|
||||||
// Debug.i(blockQuote);
|
|
||||||
|
|
||||||
newLine();
|
newLine();
|
||||||
if (blockQuoteIndent != 0) {
|
if (blockQuoteIndent != 0) {
|
||||||
builder.append('\n');
|
builder.append('\n');
|
||||||
@ -112,8 +116,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(Code code) {
|
public void visit(Code code) {
|
||||||
|
|
||||||
// Debug.i(code);
|
|
||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
|
|
||||||
// NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces
|
// NB, in order to provide a _padding_ feeling code is wrapped inside two unbreakable spaces
|
||||||
@ -163,7 +165,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void visitList(Node node) {
|
private void visitList(Node node) {
|
||||||
// Debug.i(node);
|
|
||||||
newLine();
|
newLine();
|
||||||
visitChildren(node);
|
visitChildren(node);
|
||||||
newLine();
|
newLine();
|
||||||
@ -175,15 +176,11 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(ListItem listItem) {
|
public void visit(ListItem listItem) {
|
||||||
|
|
||||||
// Debug.i(listItem);
|
|
||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
|
|
||||||
blockQuoteIndent += 1;
|
blockQuoteIndent += 1;
|
||||||
listLevel += 1;
|
listLevel += 1;
|
||||||
|
|
||||||
// todo, can be a bullet list & ordered list (with leading numbers... looks like we need to `draw` numbers...
|
|
||||||
|
|
||||||
final Node parent = listItem.getParent();
|
final Node parent = listItem.getParent();
|
||||||
if (parent instanceof OrderedList) {
|
if (parent instanceof OrderedList) {
|
||||||
|
|
||||||
@ -223,8 +220,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(ThematicBreak thematicBreak) {
|
public void visit(ThematicBreak thematicBreak) {
|
||||||
|
|
||||||
// Debug.i(thematicBreak);
|
|
||||||
|
|
||||||
newLine();
|
newLine();
|
||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
@ -238,8 +233,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
@Override
|
@Override
|
||||||
public void visit(Heading heading) {
|
public void visit(Heading heading) {
|
||||||
|
|
||||||
// Debug.i(heading);
|
|
||||||
|
|
||||||
newLine();
|
newLine();
|
||||||
|
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
@ -258,21 +251,16 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(SoftLineBreak softLineBreak) {
|
public void visit(SoftLineBreak softLineBreak) {
|
||||||
Debug.i(softLineBreak);
|
|
||||||
newLine();
|
newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(HardLineBreak hardLineBreak) {
|
public void visit(HardLineBreak hardLineBreak) {
|
||||||
Debug.i(hardLineBreak);
|
|
||||||
newLine();
|
newLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(CustomNode customNode) {
|
public void visit(CustomNode customNode) {
|
||||||
|
|
||||||
// Debug.i(customNode);
|
|
||||||
|
|
||||||
if (customNode instanceof Strikethrough) {
|
if (customNode instanceof Strikethrough) {
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
visitChildren(customNode);
|
visitChildren(customNode);
|
||||||
@ -287,8 +275,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
final boolean inTightList = isInTightList(paragraph);
|
final boolean inTightList = isInTightList(paragraph);
|
||||||
|
|
||||||
// Debug.i(paragraph, inTightList, listLevel);
|
|
||||||
|
|
||||||
if (!inTightList) {
|
if (!inTightList) {
|
||||||
newLine();
|
newLine();
|
||||||
}
|
}
|
||||||
@ -311,8 +297,6 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
|
|
||||||
visitChildren(image);
|
visitChildren(image);
|
||||||
|
|
||||||
// if image has no link, create it (to open in external app)
|
|
||||||
|
|
||||||
// we must check if anything _was_ added, as we need at least one char to render
|
// we must check if anything _was_ added, as we need at least one char to render
|
||||||
if (length == builder.length()) {
|
if (length == builder.length()) {
|
||||||
builder.append(' '); // breakable space
|
builder.append(' '); // breakable space
|
||||||
@ -321,14 +305,17 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
final Node parent = image.getParent();
|
final Node parent = image.getParent();
|
||||||
final boolean link = parent != null && parent instanceof Link;
|
final boolean link = parent != null && parent instanceof Link;
|
||||||
|
|
||||||
setSpan(length, new AsyncDrawableSpan(
|
setSpan(
|
||||||
|
length,
|
||||||
|
new AsyncDrawableSpan(
|
||||||
configuration.theme(),
|
configuration.theme(),
|
||||||
new AsyncDrawable(
|
new AsyncDrawable(
|
||||||
image.getDestination(),
|
image.getDestination(),
|
||||||
configuration.asyncDrawableLoader()
|
configuration.asyncDrawableLoader()
|
||||||
),
|
),
|
||||||
AsyncDrawableSpan.ALIGN_BOTTOM,
|
AsyncDrawableSpan.ALIGN_BOTTOM,
|
||||||
link)
|
link
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -339,6 +326,46 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
super.visit(htmlBlock);
|
super.visit(htmlBlock);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void visit(HtmlInline htmlInline) {
|
||||||
|
final SpannableHtmlParser htmlParser = configuration.htmlParser();
|
||||||
|
final SpannableHtmlParser.Tag tag = htmlParser.parseTag(htmlInline.getLiteral());
|
||||||
|
if (tag != null) {
|
||||||
|
if (tag.opening()) {
|
||||||
|
// push in stack
|
||||||
|
htmlInlineItems.push(new HtmlInlineItem(tag.name(), builder.length()));
|
||||||
|
visitChildren(htmlInline);
|
||||||
|
} else {
|
||||||
|
// pop last item
|
||||||
|
if (htmlInlineItems.size() > 0) {
|
||||||
|
final HtmlInlineItem item = htmlInlineItems.pop();
|
||||||
|
final int start = item.start;
|
||||||
|
final Object span = htmlParser.handleTag(item.tag);
|
||||||
|
if (span != null) {
|
||||||
|
setSpan(start, span);
|
||||||
|
} else {
|
||||||
|
final String content = builder.subSequence(start, builder.length()).toString();
|
||||||
|
final String html = String.format(HTML_CONTENT, item.tag, content);
|
||||||
|
final Object[] spans = htmlParser.htmlSpans(html);
|
||||||
|
final int length = spans != null
|
||||||
|
? spans.length
|
||||||
|
: 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
setSpan(start, spans[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new IllegalStateException("Unexpected closing html tag: " + tag.name()
|
||||||
|
+ ", at position: " + builder.length());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// let's add what we have
|
||||||
|
builder.append(htmlInline.getLiteral());
|
||||||
|
visitChildren(htmlInline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void visit(Link link) {
|
public void visit(Link link) {
|
||||||
final int length = builder.length();
|
final int length = builder.length();
|
||||||
@ -369,6 +396,15 @@ public class SpannableMarkdownVisitor extends AbstractVisitor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class HtmlInlineItem {
|
||||||
|
final String tag;
|
||||||
|
final int start;
|
||||||
|
HtmlInlineItem(String tag, int start) {
|
||||||
|
this.tag = tag;
|
||||||
|
this.start = start;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// private static String dump(Node node) {
|
// private static String dump(Node node) {
|
||||||
// final StringBuilder builder = new StringBuilder();
|
// final StringBuilder builder = new StringBuilder();
|
||||||
// node.accept(new DumpVisitor(builder));
|
// node.accept(new DumpVisitor(builder));
|
||||||
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.StrongEmphasisSpan;
|
||||||
|
|
||||||
|
class BoldProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new StrongEmphasisSpan();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.EmphasisSpan;
|
||||||
|
|
||||||
|
class ItalicsProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new EmphasisSpan();
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package ru.noties.markwon.renderer;
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@ -10,11 +10,44 @@ import android.text.Spanned;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
@SuppressWarnings("WeakerAccess")
|
||||||
public class SpannableHtmlParser {
|
public class SpannableHtmlParser {
|
||||||
|
|
||||||
// we need to handle images independently (in order to parse alt, width, height, etc)
|
// we need to handle images independently (in order to parse alt, width, height, etc)
|
||||||
|
|
||||||
|
// creates default parser
|
||||||
|
public static SpannableHtmlParser create(@NonNull SpannableTheme theme) {
|
||||||
|
return builderWithDefaults(theme)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builder() {
|
||||||
|
return new Builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Builder builderWithDefaults(@NonNull SpannableTheme theme) {
|
||||||
|
|
||||||
|
final BoldProvider boldProvider = new BoldProvider();
|
||||||
|
final ItalicsProvider italicsProvider = new ItalicsProvider();
|
||||||
|
final StrikeProvider strikeProvider = new StrikeProvider();
|
||||||
|
|
||||||
|
return new Builder()
|
||||||
|
.customTag("b", boldProvider)
|
||||||
|
.customTag("strong", boldProvider)
|
||||||
|
.customTag("i", italicsProvider)
|
||||||
|
.customTag("em", italicsProvider)
|
||||||
|
.customTag("cite", italicsProvider)
|
||||||
|
.customTag("dfn", italicsProvider)
|
||||||
|
.customTag("sup", new SuperScriptProvider(theme))
|
||||||
|
.customTag("sub", new SubScriptProvider(theme))
|
||||||
|
.customTag("u", new UnderlineProvider())
|
||||||
|
.customTag("del", strikeProvider)
|
||||||
|
.customTag("s", strikeProvider)
|
||||||
|
.customTag("strike", strikeProvider);
|
||||||
|
}
|
||||||
|
|
||||||
// for simple tags without arguments
|
// for simple tags without arguments
|
||||||
// <b>, <i>, etc
|
// <b>, <i>, etc
|
||||||
public interface SpanProvider {
|
public interface SpanProvider {
|
||||||
@ -25,15 +58,6 @@ public class SpannableHtmlParser {
|
|||||||
Object[] getSpans(@NonNull String html);
|
Object[] getSpans(@NonNull String html);
|
||||||
}
|
}
|
||||||
|
|
||||||
// creates default parser
|
|
||||||
public static SpannableHtmlParser create() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, SpanProvider> customTags;
|
private final Map<String, SpanProvider> customTags;
|
||||||
private final HtmlParser parser;
|
private final HtmlParser parser;
|
||||||
|
|
||||||
@ -42,6 +66,47 @@ public class SpannableHtmlParser {
|
|||||||
this.parser = builder.parser;
|
this.parser = builder.parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Tag parseTag(String html) {
|
||||||
|
|
||||||
|
final Tag tag;
|
||||||
|
|
||||||
|
final int length = html != null
|
||||||
|
? html.length()
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// absolutely minimum (`<i>`)
|
||||||
|
if (length < 3) {
|
||||||
|
tag = null;
|
||||||
|
} else {
|
||||||
|
final boolean closing = '<' == html.charAt(0) && '/' == html.charAt(1);
|
||||||
|
final String name = closing
|
||||||
|
? html.substring(2, length - 1)
|
||||||
|
: html.substring(1, length - 1);
|
||||||
|
tag = new Tag(name, !closing);
|
||||||
|
}
|
||||||
|
|
||||||
|
return tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Object handleTag(String tag) {
|
||||||
|
final Object out;
|
||||||
|
final SpanProvider provider = customTags.get(tag);
|
||||||
|
if (provider != null) {
|
||||||
|
out = provider.provide();
|
||||||
|
} else {
|
||||||
|
out = null;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Object[] htmlSpans(String html) {
|
||||||
|
// todo, additional handling of: image & link
|
||||||
|
return parser.getSpans(html);
|
||||||
|
}
|
||||||
|
|
||||||
public static class Builder {
|
public static class Builder {
|
||||||
|
|
||||||
private final Map<String, SpanProvider> customTags = new HashMap<>(3);
|
private final Map<String, SpanProvider> customTags = new HashMap<>(3);
|
||||||
@ -52,20 +117,46 @@ public class SpannableHtmlParser {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Builder setParser(@NonNull HtmlParser parser) {
|
public Builder parser(@NonNull HtmlParser parser) {
|
||||||
this.parser = parser;
|
this.parser = parser;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public SpannableHtmlParser build() {
|
public SpannableHtmlParser build() {
|
||||||
if (parser == null) {
|
if (parser == null) {
|
||||||
// todo, images....
|
|
||||||
parser = DefaultHtmlParser.create(null, null);
|
parser = DefaultHtmlParser.create(null, null);
|
||||||
}
|
}
|
||||||
return new SpannableHtmlParser(this);
|
return new SpannableHtmlParser(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class Tag {
|
||||||
|
|
||||||
|
private final String name;
|
||||||
|
private final boolean opening;
|
||||||
|
|
||||||
|
public Tag(String name, boolean opening) {
|
||||||
|
this.name = name;
|
||||||
|
this.opening = opening;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String name() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean opening() {
|
||||||
|
return opening;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Tag{" +
|
||||||
|
"name='" + name + '\'' +
|
||||||
|
", opening=" + opening +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public static abstract class DefaultHtmlParser implements HtmlParser {
|
public static abstract class DefaultHtmlParser implements HtmlParser {
|
||||||
|
|
||||||
public static DefaultHtmlParser create(@Nullable Html.ImageGetter imageGetter, @Nullable Html.TagHandler tagHandler) {
|
public static DefaultHtmlParser create(@Nullable Html.ImageGetter imageGetter, @Nullable Html.TagHandler tagHandler) {
|
@ -0,0 +1,10 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import android.text.style.StrikethroughSpan;
|
||||||
|
|
||||||
|
class StrikeProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new StrikethroughSpan();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
|
import ru.noties.markwon.spans.SubScriptSpan;
|
||||||
|
|
||||||
|
class SubScriptProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
|
||||||
|
public SubScriptProvider(SpannableTheme theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new SubScriptSpan(theme);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,18 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
|
import ru.noties.markwon.spans.SuperScriptSpan;
|
||||||
|
|
||||||
|
class SuperScriptProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
|
||||||
|
private final SpannableTheme theme;
|
||||||
|
|
||||||
|
SuperScriptProvider(SpannableTheme theme) {
|
||||||
|
this.theme = theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new SuperScriptSpan(theme);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,11 @@
|
|||||||
|
package ru.noties.markwon.renderer.html;
|
||||||
|
|
||||||
|
import android.text.style.UnderlineSpan;
|
||||||
|
|
||||||
|
class UnderlineProvider implements SpannableHtmlParser.SpanProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object provide() {
|
||||||
|
return new UnderlineSpan();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user