Remove old html code
This commit is contained in:
parent
9246a53891
commit
20c92ddd5e
@ -3,7 +3,6 @@ package ru.noties.markwon.renderer;
|
|||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.text.Spanned;
|
import android.text.Spanned;
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
import org.commonmark.ext.gfm.strikethrough.Strikethrough;
|
||||||
import org.commonmark.ext.gfm.tables.TableBody;
|
import org.commonmark.ext.gfm.tables.TableBody;
|
||||||
@ -35,16 +34,13 @@ 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.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableBuilder;
|
import ru.noties.markwon.SpannableBuilder;
|
||||||
import ru.noties.markwon.SpannableConfiguration;
|
import ru.noties.markwon.SpannableConfiguration;
|
||||||
import ru.noties.markwon.SpannableFactory;
|
import ru.noties.markwon.SpannableFactory;
|
||||||
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
import ru.noties.markwon.html.api.MarkwonHtmlParser;
|
||||||
import ru.noties.markwon.renderer.html.SpannableHtmlParser;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
import ru.noties.markwon.spans.SpannableTheme;
|
||||||
import ru.noties.markwon.spans.TableRowSpan;
|
import ru.noties.markwon.spans.TableRowSpan;
|
||||||
import ru.noties.markwon.tasklist.TaskListBlock;
|
import ru.noties.markwon.tasklist.TaskListBlock;
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
|
|
||||||
class BoldProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
BoldProvider(@NonNull SpannableFactory factory) {
|
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.strongEmphasis();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,224 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.SpannableString;
|
|
||||||
import android.text.Spanned;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
import ru.noties.markwon.UrlProcessor;
|
|
||||||
import ru.noties.markwon.renderer.ImageSize;
|
|
||||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
class ImageProviderImpl implements SpannableHtmlParser.ImageProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
private final SpannableTheme theme;
|
|
||||||
private final AsyncDrawable.Loader loader;
|
|
||||||
private final UrlProcessor urlProcessor;
|
|
||||||
private final ImageSizeResolver imageSizeResolver;
|
|
||||||
|
|
||||||
ImageProviderImpl(
|
|
||||||
@NonNull SpannableFactory factory,
|
|
||||||
@NonNull SpannableTheme theme,
|
|
||||||
@NonNull AsyncDrawable.Loader loader,
|
|
||||||
@NonNull UrlProcessor urlProcessor,
|
|
||||||
@NonNull ImageSizeResolver imageSizeResolver
|
|
||||||
) {
|
|
||||||
this.factory = factory;
|
|
||||||
this.theme = theme;
|
|
||||||
this.loader = loader;
|
|
||||||
this.urlProcessor = urlProcessor;
|
|
||||||
this.imageSizeResolver = imageSizeResolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spanned provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
|
|
||||||
final Spanned spanned;
|
|
||||||
|
|
||||||
final Map<String, String> attributes = tag.attributes();
|
|
||||||
final String src = attributes.get("src");
|
|
||||||
final String alt = attributes.get("alt");
|
|
||||||
|
|
||||||
if (!TextUtils.isEmpty(src)) {
|
|
||||||
|
|
||||||
final String destination = urlProcessor.process(src);
|
|
||||||
|
|
||||||
final String replacement;
|
|
||||||
if (!TextUtils.isEmpty(alt)) {
|
|
||||||
replacement = alt;
|
|
||||||
} else {
|
|
||||||
replacement = "\uFFFC";
|
|
||||||
}
|
|
||||||
|
|
||||||
final Object span = factory.image(
|
|
||||||
theme,
|
|
||||||
destination,
|
|
||||||
loader,
|
|
||||||
imageSizeResolver,
|
|
||||||
parseImageSize(attributes),
|
|
||||||
false);
|
|
||||||
|
|
||||||
final SpannableString string = new SpannableString(replacement);
|
|
||||||
|
|
||||||
if (span != null) {
|
|
||||||
final int length = string.length();
|
|
||||||
if (span.getClass().isArray()) {
|
|
||||||
for (Object o : ((Object[]) span)) {
|
|
||||||
string.setSpan(o, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
string.setSpan(span, 0, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
spanned = string;
|
|
||||||
} else {
|
|
||||||
spanned = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return spanned;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static ImageSize parseImageSize(@NonNull Map<String, String> attributes) {
|
|
||||||
|
|
||||||
final ImageSize imageSize;
|
|
||||||
|
|
||||||
final StyleProvider styleProvider = new StyleProvider(attributes.get("style"));
|
|
||||||
|
|
||||||
final ImageSize.Dimension width = parseDimension(extractDimension("width", attributes, styleProvider));
|
|
||||||
final ImageSize.Dimension height = parseDimension(extractDimension("height", attributes, styleProvider));
|
|
||||||
|
|
||||||
if (width == null
|
|
||||||
&& height == null) {
|
|
||||||
imageSize = null;
|
|
||||||
} else {
|
|
||||||
imageSize = new ImageSize(width, height);
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String extractDimension(@NonNull String name, @NonNull Map<String, String> attributes, @NonNull StyleProvider styleProvider) {
|
|
||||||
|
|
||||||
final String out;
|
|
||||||
|
|
||||||
final String inline = attributes.get(name);
|
|
||||||
if (!TextUtils.isEmpty(inline)) {
|
|
||||||
out = inline;
|
|
||||||
} else {
|
|
||||||
out = extractDimensionFromStyle(name, styleProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static String extractDimensionFromStyle(@NonNull String name, @NonNull StyleProvider styleProvider) {
|
|
||||||
return styleProvider.attributes().get(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
private static ImageSize.Dimension parseDimension(@Nullable String raw) {
|
|
||||||
|
|
||||||
// a set of digits, then dimension unit (allow floating)
|
|
||||||
|
|
||||||
final ImageSize.Dimension dimension;
|
|
||||||
|
|
||||||
final int length = raw != null
|
|
||||||
? raw.length()
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
if (length == 0) {
|
|
||||||
dimension = null;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// first digit to find -> unit is finished (can be null)
|
|
||||||
|
|
||||||
int index = -1;
|
|
||||||
|
|
||||||
for (int i = length - 1; i >= 0; i--) {
|
|
||||||
if (Character.isDigit(raw.charAt(i))) {
|
|
||||||
index = i;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// no digits -> no dimension
|
|
||||||
if (index == -1) {
|
|
||||||
dimension = null;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
final String value;
|
|
||||||
final String unit;
|
|
||||||
|
|
||||||
// no unit is specified
|
|
||||||
if (index == length - 1) {
|
|
||||||
value = raw;
|
|
||||||
unit = null;
|
|
||||||
} else {
|
|
||||||
value = raw.substring(0, index + 1);
|
|
||||||
unit = raw.substring(index + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageSize.Dimension inner;
|
|
||||||
try {
|
|
||||||
final float floatValue = Float.parseFloat(value);
|
|
||||||
inner = new ImageSize.Dimension(floatValue, unit);
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
inner = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
dimension = inner;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return dimension;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class StyleProvider {
|
|
||||||
|
|
||||||
private final String style;
|
|
||||||
private Map<String, String> attributes;
|
|
||||||
|
|
||||||
StyleProvider(@Nullable String style) {
|
|
||||||
this.style = style;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Map<String, String> attributes() {
|
|
||||||
final Map<String, String> out;
|
|
||||||
if (attributes != null) {
|
|
||||||
out = attributes;
|
|
||||||
} else {
|
|
||||||
if (TextUtils.isEmpty(style)) {
|
|
||||||
out = attributes = Collections.emptyMap();
|
|
||||||
} else {
|
|
||||||
final String[] split = style.split(";");
|
|
||||||
final Map<String, String> map = new HashMap<>(split.length);
|
|
||||||
String[] parts;
|
|
||||||
for (String s : split) {
|
|
||||||
if (!TextUtils.isEmpty(s)) {
|
|
||||||
parts = s.split(":");
|
|
||||||
if (parts.length == 2) {
|
|
||||||
map.put(parts[0].trim(), parts[1].trim());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
out = attributes = map;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
|
|
||||||
class ItalicsProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
ItalicsProvider(@NonNull SpannableFactory factory) {
|
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.emphasis();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,49 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.text.TextUtils;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
import ru.noties.markwon.UrlProcessor;
|
|
||||||
import ru.noties.markwon.spans.LinkSpan;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
class LinkProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
private final SpannableTheme theme;
|
|
||||||
private final UrlProcessor urlProcessor;
|
|
||||||
private final LinkSpan.Resolver resolver;
|
|
||||||
|
|
||||||
LinkProvider(
|
|
||||||
@NonNull SpannableFactory factory,
|
|
||||||
@NonNull SpannableTheme theme,
|
|
||||||
@NonNull UrlProcessor urlProcessor,
|
|
||||||
@NonNull LinkSpan.Resolver resolver) {
|
|
||||||
this.factory = factory;
|
|
||||||
this.theme = theme;
|
|
||||||
this.urlProcessor = urlProcessor;
|
|
||||||
this.resolver = resolver;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
|
|
||||||
final Object span;
|
|
||||||
|
|
||||||
final Map<String, String> attributes = tag.attributes();
|
|
||||||
final String href = attributes.get("href");
|
|
||||||
if (!TextUtils.isEmpty(href)) {
|
|
||||||
|
|
||||||
final String destination = urlProcessor.process(href);
|
|
||||||
span = factory.link(theme, destination, resolver);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
span = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return span;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,332 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
import android.text.Html;
|
|
||||||
import android.text.Spanned;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import ru.noties.markwon.LinkResolverDef;
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
import ru.noties.markwon.UrlProcessor;
|
|
||||||
import ru.noties.markwon.UrlProcessorNoOp;
|
|
||||||
import ru.noties.markwon.renderer.ImageSizeResolver;
|
|
||||||
import ru.noties.markwon.renderer.ImageSizeResolverDef;
|
|
||||||
import ru.noties.markwon.spans.AsyncDrawable;
|
|
||||||
import ru.noties.markwon.spans.LinkSpan;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
@SuppressWarnings("WeakerAccess")
|
|
||||||
public class SpannableHtmlParser {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static SpannableHtmlParser create(
|
|
||||||
@NonNull SpannableFactory factory,
|
|
||||||
@NonNull SpannableTheme theme,
|
|
||||||
@NonNull AsyncDrawable.Loader loader,
|
|
||||||
@NonNull UrlProcessor urlProcessor,
|
|
||||||
@NonNull LinkSpan.Resolver resolver,
|
|
||||||
@NonNull ImageSizeResolver imageSizeResolver
|
|
||||||
) {
|
|
||||||
return builderWithDefaults(factory, theme, loader, urlProcessor, resolver, imageSizeResolver).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public static Builder builder() {
|
|
||||||
return new Builder();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static Builder builderWithDefaults(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) {
|
|
||||||
return builderWithDefaults(
|
|
||||||
factory,
|
|
||||||
theme,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null,
|
|
||||||
null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updated in 1.0.1: added imageSizeResolverArgument
|
|
||||||
* Updated in 1.1.0: add SpannableFactory
|
|
||||||
*/
|
|
||||||
@NonNull
|
|
||||||
public static Builder builderWithDefaults(
|
|
||||||
@NonNull SpannableFactory factory,
|
|
||||||
@NonNull SpannableTheme theme,
|
|
||||||
@Nullable AsyncDrawable.Loader asyncDrawableLoader,
|
|
||||||
@Nullable UrlProcessor urlProcessor,
|
|
||||||
@Nullable LinkSpan.Resolver resolver,
|
|
||||||
@Nullable ImageSizeResolver imageSizeResolver
|
|
||||||
) {
|
|
||||||
|
|
||||||
if (urlProcessor == null) {
|
|
||||||
urlProcessor = new UrlProcessorNoOp();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (resolver == null) {
|
|
||||||
resolver = new LinkResolverDef();
|
|
||||||
}
|
|
||||||
|
|
||||||
final BoldProvider boldProvider = new BoldProvider(factory);
|
|
||||||
final ItalicsProvider italicsProvider = new ItalicsProvider(factory);
|
|
||||||
final StrikeProvider strikeProvider = new StrikeProvider(factory);
|
|
||||||
|
|
||||||
final ImageProvider imageProvider;
|
|
||||||
if (asyncDrawableLoader != null) {
|
|
||||||
|
|
||||||
if (imageSizeResolver == null) {
|
|
||||||
imageSizeResolver = new ImageSizeResolverDef();
|
|
||||||
}
|
|
||||||
|
|
||||||
imageProvider = new ImageProviderImpl(
|
|
||||||
factory,
|
|
||||||
theme,
|
|
||||||
asyncDrawableLoader,
|
|
||||||
urlProcessor,
|
|
||||||
imageSizeResolver);
|
|
||||||
} else {
|
|
||||||
imageProvider = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new Builder()
|
|
||||||
.simpleTag("b", boldProvider)
|
|
||||||
.simpleTag("strong", boldProvider)
|
|
||||||
.simpleTag("i", italicsProvider)
|
|
||||||
.simpleTag("em", italicsProvider)
|
|
||||||
.simpleTag("cite", italicsProvider)
|
|
||||||
.simpleTag("dfn", italicsProvider)
|
|
||||||
.simpleTag("sup", new SuperScriptProvider(factory, theme))
|
|
||||||
.simpleTag("sub", new SubScriptProvider(factory, theme))
|
|
||||||
.simpleTag("u", new UnderlineProvider(factory))
|
|
||||||
.simpleTag("del", strikeProvider)
|
|
||||||
.simpleTag("s", strikeProvider)
|
|
||||||
.simpleTag("strike", strikeProvider)
|
|
||||||
.simpleTag("a", new LinkProvider(factory, theme, urlProcessor, resolver))
|
|
||||||
.imageProvider(imageProvider);
|
|
||||||
}
|
|
||||||
|
|
||||||
// for simple tags without arguments
|
|
||||||
// <b>, <i>, etc
|
|
||||||
public interface SpanProvider {
|
|
||||||
Object provide(@NonNull Tag tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface ImageProvider {
|
|
||||||
Spanned provide(@NonNull Tag tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface HtmlParser {
|
|
||||||
|
|
||||||
// returns span for a simple content
|
|
||||||
Object getSpan(@NonNull String html);
|
|
||||||
|
|
||||||
Spanned parse(@NonNull String html);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<String, SpanProvider> simpleTags;
|
|
||||||
private final ImageProvider imageProvider;
|
|
||||||
private final HtmlParser parser;
|
|
||||||
private final TagParser tagParser;
|
|
||||||
|
|
||||||
private SpannableHtmlParser(Builder builder) {
|
|
||||||
this.simpleTags = builder.simpleTags;
|
|
||||||
this.imageProvider = builder.imageProvider;
|
|
||||||
this.parser = builder.parser;
|
|
||||||
this.tagParser = new TagParser();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Tag parseTag(String html) {
|
|
||||||
return tagParser.parse(html);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public Object getSpanForTag(@NonNull Tag tag) {
|
|
||||||
|
|
||||||
// check if we have specific handler for tag.name
|
|
||||||
|
|
||||||
final Object out;
|
|
||||||
|
|
||||||
final SpanProvider provider = simpleTags.get(tag.name);
|
|
||||||
if (provider != null) {
|
|
||||||
out = provider.provide(tag);
|
|
||||||
} else {
|
|
||||||
// let's prepare mock content & extract spans from it
|
|
||||||
// actual content doesn't matter, here it's just `abc`
|
|
||||||
final String mock = tag.raw + "abc" + "</" + tag.name + ">";
|
|
||||||
out = parser.getSpan(mock);
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
// if tag is NULL, then it's HtmlBlock... else just a void tag
|
|
||||||
public Spanned getSpanned(@Nullable Tag tag, String html) {
|
|
||||||
final Spanned spanned;
|
|
||||||
if (tag != null && "img".equals(tag.name) && imageProvider != null) {
|
|
||||||
spanned = imageProvider.provide(tag);
|
|
||||||
} else {
|
|
||||||
spanned = parser.parse(html);
|
|
||||||
}
|
|
||||||
return spanned;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Builder {
|
|
||||||
|
|
||||||
private final Map<String, SpanProvider> simpleTags = new HashMap<>(3);
|
|
||||||
|
|
||||||
private ImageProvider imageProvider;
|
|
||||||
private HtmlParser parser;
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
Builder simpleTag(@NonNull String tag, @NonNull SpanProvider provider) {
|
|
||||||
simpleTags.put(tag, provider);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Builder imageProvider(@Nullable ImageProvider imageProvider) {
|
|
||||||
this.imageProvider = imageProvider;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Builder parser(@NonNull HtmlParser parser) {
|
|
||||||
this.parser = parser;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public SpannableHtmlParser build() {
|
|
||||||
if (parser == null) {
|
|
||||||
parser = DefaultHtmlParser.create();
|
|
||||||
}
|
|
||||||
return new SpannableHtmlParser(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static class Tag {
|
|
||||||
|
|
||||||
private final String raw;
|
|
||||||
private final String name;
|
|
||||||
private final Map<String, String> attributes;
|
|
||||||
|
|
||||||
private final boolean opening;
|
|
||||||
private final boolean voidTag;
|
|
||||||
|
|
||||||
public Tag(String raw, String name, @NonNull Map<String, String> attributes, boolean opening, boolean voidTag) {
|
|
||||||
this.raw = raw;
|
|
||||||
this.name = name;
|
|
||||||
this.attributes = attributes;
|
|
||||||
this.opening = opening;
|
|
||||||
this.voidTag = voidTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String raw() {
|
|
||||||
return raw;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String name() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public Map<String, String> attributes() {
|
|
||||||
return attributes;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean opening() {
|
|
||||||
return opening;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean voidTag() {
|
|
||||||
return voidTag;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "Tag{" +
|
|
||||||
"raw='" + raw + '\'' +
|
|
||||||
", name='" + name + '\'' +
|
|
||||||
", attributes=" + attributes +
|
|
||||||
", opening=" + opening +
|
|
||||||
", voidTag=" + voidTag +
|
|
||||||
'}';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static abstract class DefaultHtmlParser implements HtmlParser {
|
|
||||||
|
|
||||||
public static DefaultHtmlParser create() {
|
|
||||||
final DefaultHtmlParser parser;
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
parser = new Parser24();
|
|
||||||
} else {
|
|
||||||
parser = new ParserPre24();
|
|
||||||
}
|
|
||||||
return parser;
|
|
||||||
}
|
|
||||||
|
|
||||||
Object getSpan(Spanned spanned) {
|
|
||||||
|
|
||||||
final Object out;
|
|
||||||
|
|
||||||
final Object[] spans;
|
|
||||||
final int length = spanned != null ? spanned.length() : 0;
|
|
||||||
if (length == 0) {
|
|
||||||
spans = null;
|
|
||||||
} else {
|
|
||||||
spans = spanned.getSpans(0, length, Object.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (spans != null
|
|
||||||
&& spans.length > 0) {
|
|
||||||
out = spans[0];
|
|
||||||
} else {
|
|
||||||
out = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
private static class ParserPre24 extends DefaultHtmlParser {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getSpan(@NonNull String html) {
|
|
||||||
return getSpan(parse(html));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spanned parse(@NonNull String html) {
|
|
||||||
return Html.fromHtml(html, null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(Build.VERSION_CODES.N)
|
|
||||||
private static class Parser24 extends DefaultHtmlParser {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getSpan(@NonNull String html) {
|
|
||||||
return getSpan(parse(html));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Spanned parse(@NonNull String html) {
|
|
||||||
return Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT, null, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
|
|
||||||
class StrikeProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
StrikeProvider(@NonNull SpannableFactory factory) {
|
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.strikethrough();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
class SubScriptProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
private final SpannableTheme theme;
|
|
||||||
|
|
||||||
SubScriptProvider(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) {
|
|
||||||
this.factory = factory;
|
|
||||||
this.theme = theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.subScript(theme);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
import ru.noties.markwon.spans.SpannableTheme;
|
|
||||||
|
|
||||||
class SuperScriptProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
private final SpannableTheme theme;
|
|
||||||
|
|
||||||
SuperScriptProvider(@NonNull SpannableFactory factory, @NonNull SpannableTheme theme) {
|
|
||||||
this.factory = factory;
|
|
||||||
this.theme = theme;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.superScript(theme);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,155 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
class TagParser {
|
|
||||||
|
|
||||||
|
|
||||||
private static final Set<String> VOID_TAGS;
|
|
||||||
static {
|
|
||||||
final String[] tags = {
|
|
||||||
"area", "base", "br", "col", "embed", "hr", "img", "input",
|
|
||||||
"keygen", "link", "meta", "param", "source", "track", "wbr"
|
|
||||||
};
|
|
||||||
final Set<String> set = new HashSet<>(tags.length);
|
|
||||||
Collections.addAll(set, tags);
|
|
||||||
VOID_TAGS = Collections.unmodifiableSet(set);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
TagParser() {
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
SpannableHtmlParser.Tag parse(String html) {
|
|
||||||
|
|
||||||
final SpannableHtmlParser.Tag tag;
|
|
||||||
|
|
||||||
final int length = html != null
|
|
||||||
? html.length()
|
|
||||||
: 0;
|
|
||||||
|
|
||||||
// absolutely minimum (`<i>`)
|
|
||||||
if (length < 3) {
|
|
||||||
tag = null;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// // okay, we will consider a tag a void one if it's in our void list tag
|
|
||||||
|
|
||||||
final boolean closing = '<' == html.charAt(0) && '/' == html.charAt(1);
|
|
||||||
final boolean voidTag;
|
|
||||||
|
|
||||||
Map<String, String> attributes = null;
|
|
||||||
|
|
||||||
final StringBuilder builder = new StringBuilder();
|
|
||||||
|
|
||||||
String name = null;
|
|
||||||
String pendingAttribute = null;
|
|
||||||
|
|
||||||
char c;
|
|
||||||
char valueDelimiter = '\0';
|
|
||||||
|
|
||||||
for (int i = 0; i < length; i++) {
|
|
||||||
|
|
||||||
c = html.charAt(i);
|
|
||||||
|
|
||||||
// no more handling
|
|
||||||
if ('>' == c
|
|
||||||
|| '\\' == c) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (name == null) {
|
|
||||||
if (Character.isSpaceChar(c)) {
|
|
||||||
//noinspection StatementWithEmptyBody
|
|
||||||
if (builder.length() == 0) {
|
|
||||||
// ignore it, we must wait until we have tagName
|
|
||||||
} else {
|
|
||||||
|
|
||||||
name = builder.toString();
|
|
||||||
|
|
||||||
// clear buffer
|
|
||||||
builder.setLength(0);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (Character.isLetterOrDigit(c)) {
|
|
||||||
builder.append(c);
|
|
||||||
} /*else {
|
|
||||||
// we allow non-letter-digit only if builder.length == 0
|
|
||||||
// if we have already started
|
|
||||||
}*/
|
|
||||||
}
|
|
||||||
} else if (pendingAttribute == null) {
|
|
||||||
// we start checking for attribute
|
|
||||||
// ignore non-letter-digits before
|
|
||||||
if (Character.isLetterOrDigit(c)) {
|
|
||||||
builder.append(c);
|
|
||||||
} else /*if ('=' == c)*/ {
|
|
||||||
|
|
||||||
// attribute name is finished (only if we have already added something)
|
|
||||||
// else it's trailing chars that we are not interested in
|
|
||||||
if (builder.length() > 0) {
|
|
||||||
pendingAttribute = builder.toString();
|
|
||||||
builder.setLength(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// first char that we will meet will be the delimiter
|
|
||||||
if (valueDelimiter == '\0') {
|
|
||||||
valueDelimiter = c;
|
|
||||||
} else {
|
|
||||||
if (c == valueDelimiter) {
|
|
||||||
if (attributes == null) {
|
|
||||||
attributes = new HashMap<>(3);
|
|
||||||
}
|
|
||||||
attributes.put(pendingAttribute, builder.toString());
|
|
||||||
pendingAttribute = null;
|
|
||||||
valueDelimiter = '\0';
|
|
||||||
builder.setLength(0);
|
|
||||||
} else {
|
|
||||||
builder.append(c);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (builder.length() > 0) {
|
|
||||||
if (name == null) {
|
|
||||||
name = builder.toString();
|
|
||||||
} else if (pendingAttribute != null) {
|
|
||||||
if (attributes == null) {
|
|
||||||
attributes = new HashMap<>(3);
|
|
||||||
}
|
|
||||||
attributes.put(pendingAttribute, builder.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// in case of wrong parsing
|
|
||||||
if (name == null) {
|
|
||||||
tag = null;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
voidTag = !closing && VOID_TAGS.contains(name);
|
|
||||||
|
|
||||||
final Map<String, String> attributesMap;
|
|
||||||
if (attributes == null
|
|
||||||
|| attributes.size() == 0) {
|
|
||||||
//noinspection unchecked
|
|
||||||
attributesMap = Collections.EMPTY_MAP;
|
|
||||||
} else {
|
|
||||||
attributesMap = Collections.unmodifiableMap(attributes);
|
|
||||||
}
|
|
||||||
|
|
||||||
tag = new SpannableHtmlParser.Tag(html, name, attributesMap, !closing, voidTag);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tag;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,22 +0,0 @@
|
|||||||
package ru.noties.markwon.renderer.html;
|
|
||||||
|
|
||||||
import android.support.annotation.NonNull;
|
|
||||||
|
|
||||||
import ru.noties.markwon.SpannableFactory;
|
|
||||||
|
|
||||||
class UnderlineProvider implements SpannableHtmlParser.SpanProvider {
|
|
||||||
|
|
||||||
private final SpannableFactory factory;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @since 1.1.0
|
|
||||||
*/
|
|
||||||
UnderlineProvider(@NonNull SpannableFactory factory) {
|
|
||||||
this.factory = factory;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object provide(@NonNull SpannableHtmlParser.Tag tag) {
|
|
||||||
return factory.underline();
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user