question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

How to implement custom markdown spec

See original GitHub issue
  • Markwon version: 4.1.0

First off I’d like to say thank you for this for the hard work, I’ve been trying to think of the best way to implement some custom Markdown spec features e.g.

img###(https://media.giphy.com/media/IcJ6n6VJNjRNS/giphy.gif)

I would create a standard markdown/html image tag from this using a simple regex patter like:

(img)(.*?)(([^)]+))

The above would allow me to extract the size, and url

Current Implementation Behaviour

I’ve tried using the AbstractMarkwonPlugin.processMarkdown() function but I’m sure this is not the best way to go about this.

class CustomImagePlugin private constructor(): IMarkdownPlugin, AbstractMarkwonPlugin() {

    /**
     * Regular expression that should be used for the implementing class
     */
    @VisibleForTesting(otherwise = Modifier.PRIVATE)
    override val regex = Regex(
        pattern = PATTERN_IMAGE,
        option = RegexOption.IGNORE_CASE
    )

    override fun processMarkdown(markdown: String): String {
        var buffer: String = markdown
        val matchers = regex.findAll(markdown)
        for (match in matchers) {
            val groups = match.groups
            val target = groups[GROUP_ORIGINAL_MATCH]?.value
            // ... impl replacing all custom markdown spec with standarad html/mardown
        }
        return buffer
    }

    companion object {

        @VisibleForTesting(otherwise = Modifier.PRIVATE)
        const val PATTERN_IMAGE = "(img)(.*?)(\\([^)]+\\))"

        private const val GROUP_ORIGINAL_MATCH = 0
        private const val GROUP_IMAGE_SIZE = 2
        private const val GROUP_IMAGE_SRC = 3

        fun create() = ImagePlugin()
    }
}

Could you please advise on how to go about this?

Alternative Implementation Behaviour

What I also tried was making use of AbstractMarkwonPlugin.beforeRender but this is documented to be:

This method will be called before rendering will occur thus making possible to post-process parsed node (make changes for example).

Thus getting an image span to pick it up at this point might not work?

class CustomImagePlugin private constructor(): IMarkdownPlugin, AbstractMarkwonPlugin() {

    /**
     * Regular expression that should be used for the implementing class
     */
    @VisibleForTesting(otherwise = Modifier.PRIVATE)
    override val regex = Regex(
        pattern = PATTERN_IMAGE,
        option = RegexOption.IGNORE_CASE
    )


    override fun beforeRender(node: Node) {
        node.accept(ImageVisitor())
    }

    inner class ImageVisitor : AbstractVisitor() {

       private fun replaceLiteral(node: String): String {
            var buffer: String = node
            val matchers = regex.findAll(node)
            for (match in matchers) {
                val groups = match.groups
                val target = groups[GROUP_ORIGINAL_MATCH]?.value
                // ... impl replacing all custom markdown spec with standarad html/mardown
            }
            return buffer
        }

        override fun visit(text: Text?) {
            val literal = text?.literal.orEmpty()
            if (literal.contains(regex))
                text?.literal = replaceLiteral(literal)
        }
    }

    companion object {

        @VisibleForTesting(otherwise = Modifier.PRIVATE)
        const val PATTERN_IMAGE = "(img)(.*?)(\\([^)]+\\))"

        private const val GROUP_ORIGINAL_MATCH = 0
        private const val GROUP_IMAGE_SIZE = 2
        private const val GROUP_IMAGE_SRC = 3

        fun create() = ImagePlugin()
    }
}

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (5 by maintainers)

github_iconTop GitHub Comments

2reactions
notiescommented, Aug 26, 2019

Hello @wax911 !

I think the the alternative approach is the best one at the moment. It is actually advised by commonmark creator and used by commonmark-ext-autolink artifact (although it uses commonmark post-processor, but it’s a minor difference).

The absolute best solution would be to write a custom inline-parser, but unfortunately it’s still not possible (#113).

UPD. A small update, there is another option available since 4.0.0:

final Markwon markwon = Markwon.builder(context)
        .usePlugin(ImagesPlugin.create(new ImagesPlugin.ImagesConfigure() {
            @Override
            public void configureImages(@NonNull ImagesPlugin plugin) {
                // data uri scheme handler is added automatically
                // SVG & GIF will be added if required dependencies are present in the classpath
                // default-media-decoder is also added automatically
                plugin
                        .addSchemeHandler(OkHttpNetworkSchemeHandler.create())
                        .addSchemeHandler(FileSchemeHandler.createWithAssets(context.getAssets()));
            }
        }))
        /* other plugins */
        .usePlugin(new AbstractMarkwonPlugin() {
            @Override
            public void configure(@NonNull Registry registry) {
                registry.require(CorePlugin.class, plugin -> plugin.addOnTextAddedListener(new CorePlugin.OnTextAddedListener() {
                    @Override
                    public void onTextAdded(@NonNull MarkwonVisitor visitor, @NonNull String text, int start) {
                        final Matcher matcher = /*obtain matcher*/;
                        while (matcher.find()) {

                            // set props
                            ImageProps.DESTINATION.set(visitor.renderProps(), matcher.group(2));
                            // image size can be set here also
                            ImageProps.IMAGE_SIZE.set(
                                    visitor.renderProps(),
                                    new ImageSize(
                                            new ImageSize.Dimension(100, "%"),
                                            new ImageSize.Dimension(480, null)
                                    ));
                            // it's important to use `start` parameter for correct spans placement
                            // oops, cannot use that here
//                            visitor.setSpansForNode(Image.class, start + matcher.start());

                            final SpanFactory spanFactory = visitor.configuration().spansFactory().require(Image.class);
                            SpannableBuilder.setSpans(
                                    visitor.builder(),
                                    spanFactory.getSpans(visitor.configuration(), visitor.renderProps()),
                                    start + matcher.start(),
                                    start + matcher.end()
                            );
                        }
                    }
                }));
            }
        })
        .build();
1reaction
notiescommented, Aug 26, 2019

You are welcome! Feel free to re-open if you will have more questions 🙌

Read more comments on GitHub >

github_iconTop Results From Across the Web

Extended Syntax - Markdown Guide
The basic Markdown syntax allows you to create code blocks by indenting lines by four spaces or one tab. If you find that...
Read more >
Spec Markdown
To convert your Markdown files into an HTML spec document, use the spec-md utility. npm install -g spec-md spec-md ./path/to/markdown.md > ./path/to/output.
Read more >
CommonMark: A Formal Specification For Markdown
Markdown is a powerful markup language that allows editing and formatting in plain text format that can then be parsed and rendered as...
Read more >
A formal spec for GitHub Flavored Markdown
We're releasing a formal specification of the syntax for GitHub Flavored Markdown, and its corresponding reference implementation.
Read more >
Customizing Markdown // Statamic 3 Docs
Using a custom parser in a modifier#. The markdown modifier accepts an optional argument to choose which parser to use. {{ text ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found