HTML

html

This artifact encapsulates HTML parsing from the core artifact and provides few predefined TagHandlers

final Markwon markwon = Markwon.builder(context)
        .usePlugin(HtmlPlugin.create())
        .build();

As this artifact brings modified jsoup library it was moved to a standalone module in order to minimize dependencies and unused code in applications that does not require HTML render capabilities.

Before 2.0.0 Markwon used android Html class for parsing and rendering. Unfortunately, according to markdown specification, markdown can contain HTML in unpredictable way if rendered outside of browser. For example:




 

<i>
Hello from italics tag

</i><b>bold></b>

This snippet could be represented as:

  • HtmlBlock (<i>\nHello from italics tag)
  • HtmlInline (<i>)
  • HtmlInline (<b>)
  • Text (bold)
  • HtmlInline (</b>)

A bit of background


This issue had brought attention to differences between HTML & commonmark implementations.

Unfortunately Android HTML class cannot parse a fragment of HTML to later be included in a bigger set of content. This is why the decision was made to bring HTML parsing in-markwon-house

Predefined TagHandlers

  • <img>
  • <a>
  • <blockquote>
  • <sub>
  • <sup>
  • <b>, <strong>
  • <s>, <del>
  • <u>, <ins>
  • <ul>, <ol>
  • <i>, <cite>, <em>, <dfn>
  • <h1>, <h2>, <h3>, <h4>, <h5>, <h6>

TIP

All predefined tag handlers will use styling spans for native markdown content. So, if your Markwon instance was configured to, for example, render Emphasis nodes as a red text then HTML tag handler will use the same span. This includes images, links, UrlResolver, LinkProcessor, etc


Staring with 4.0.0 you can exclude all default tag handlers:

.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
    @Override
    public void configureHtml(@NonNull HtmlPlugin plugin) {
        plugin.excludeDefaults(true);
    }
}))

or via plugin:

.usePlugin(new AbstractMarkwonPlugin() {
    @Override
    public void configure(@NonNull Registry registry) {
        registry.require(HtmlPlugin.class, new Action<HtmlPlugin>() {
            @Override
            public void apply(@NonNull HtmlPlugin htmlPlugin) {
                htmlPlugin.excludeDefaults(true);
            }
        });
    }
})

If you wish to exclude some of them TagHandlerNoOp can be used:

.usePlugin(HtmlPlugin.create(new HtmlPlugin.HtmlConfigure() {
    @Override
    public void configureHtml(@NonNull HtmlPlugin plugin) {
        plugin.addHandler(TagHandlerNoOp.create("h4", "h5", "h6", "img"));
    }
}))

TagHandler

To define a tag-handler that applies style for the whole tag content (from start to end), a SimpleTagHandler can be used. For example, let's define <align> tag, which can be used like this:

  • <align center>centered text</align>
  • <align end>this should be aligned at the end (right for LTR locales)</align>
  • <align>regular alignment</align>
public class AlignTagHandler extends SimpleTagHandler {

    @Nullable
    @Override
    public Object getSpans(
            @NonNull MarkwonConfiguration configuration,
            @NonNull RenderProps renderProps,
            @NonNull HtmlTag tag) {

        final Layout.Alignment alignment;

        // html attribute without value, <align center></align>
        if (tag.attributes().containsKey("center")) {
            alignment = Layout.Alignment.ALIGN_CENTER;
        } else if (tag.attributes().containsKey("end")) {
            alignment = Layout.Alignment.ALIGN_OPPOSITE;
        } else {
            // empty value or any other will make regular alignment
            alignment = Layout.Alignment.ALIGN_NORMAL;
        }

        return new AlignmentSpan.Standard(alignment);
    }

    @NonNull
    @Override
    public Collection<String> supportedTags() {
        return Collections.singleton("align");
    }
}

TIP

SimpleTagHandler can return an array of spans from getSpans method

Then register AlignTagHandler:

final Markwon markwon = Markwon.builder(this)
        .usePlugin(HtmlPlugin.create())
        .usePlugin(new AbstractMarkwonPlugin() {
            @Override
            public void configure(@NonNull Registry registry) {
                registry.require(HtmlPlugin.class, htmlPlugin -> htmlPlugin
                        .addHandler(new AlignTagHandler());
            }
        })
        .build();

or directly on HtmlPlugin:

final Markwon markwon = Markwon.builder(this)
        .usePlugin(HtmlPlugin.create(plugin -> plugin.addHandler(new AlignTagHandler())))
        .build();

If a tag requires special handling TagHandler can be used directly. For example let's define an <enhance> tag with start and end arguments, that will mark start and end positions of the text that needs to be enlarged:

<enhance start="5" end="12">This is text that must be enhanced, at least a part of it</enhance>
public class EnhanceTagHandler extends TagHandler {

    private final int enhanceTextSize;

    EnhanceTagHandler(@Px int enhanceTextSize) {
        this.enhanceTextSize = enhanceTextSize;
    }

    @Override
    public void handle(
            @NonNull MarkwonVisitor visitor,
            @NonNull MarkwonHtmlRenderer renderer,
            @NonNull HtmlTag tag) {

        // we require start and end to be present
        final int start = parsePosition(tag.attributes().get("start"));
        final int end = parsePosition(tag.attributes().get("end"));

        if (start > -1 && end > -1) {
            visitor.builder().setSpan(
                    new AbsoluteSizeSpan(enhanceTextSize),
                    tag.start() + start,
                    tag.start() + end
            );
        }
    }

    @NonNull
    @Override
    public Collection<String> supportedTags() {
        return Collections.singleton("enhance");
    }

    private static int parsePosition(@Nullable String value) {
        int position;
        if (!TextUtils.isEmpty(value)) {
            try {
                position = Integer.parseInt(value);
            } catch (NumberFormatException e) {
                e.printStackTrace();
                position = -1;
            }
        } else {
            position = -1;
        }
        return position;
    }
}
Last Updated: 6/24/2019, 1:47:44 PM