Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render back to markdown #30

Open
jryio opened this issue Aug 16, 2023 · 5 comments
Open

Render back to markdown #30

jryio opened this issue Aug 16, 2023 · 5 comments

Comments

@jryio
Copy link

jryio commented Aug 16, 2023

I have a use case where I wish to write a markdown parser that converts [[Wiki Style Links]] into valid common mark links E.g. [Wiki Style Links]({generated_source_url}].

Reading the docs and source code it does not seem possible to parse some Markdown source, do AST processing, and output a new Markdown source at a String/str.

Is my understanding correct? Also perhaps @chrisjsewell might know how to do this as they've written several plugins for this crate over at (https://github.com/chrisjsewell/markdown-it-plugins.rs)

@chrisjsewell
Copy link
Member

chrisjsewell commented Aug 16, 2023

Heya, so no I don't believe there is currently a "Markdown Renderer" for markdown-it.rs.

If someone is to create one, I would suggest having a look at https://github.com/executablebooks/mdformat, which is effectively the equivalent for https://github.com/executablebooks/markdown-it-py

This does bring up another issue I was going to open, which is what is the best way to implement renderers?

The HTML renderer (https://github.com/rlidwka/markdown-it.rs/blob/master/src/parser/renderer.rs) effectively holds a "priviledged" position, since NodeValue has a specific render method for it (https://github.com/rlidwka/markdown-it.rs/blob/47b48b8dd0b84f8d95761a7b2d2b530e328074c6/src/parser/node.rs#L223)

But for rendering to e.g. Markdown, JSON, ..., I'm not sure what the best practice way would be?
Any thoughts @rlidwka?

@chrisjsewell
Copy link
Member

chrisjsewell commented Aug 16, 2023

For example, this is something I was playing around with for rendering to https://github.com/syntax-tree/mdast

But obviously it means you have to make sure to add every possible node type that you want to render, and there is some boilerplate

use std::any::TypeId;
use markdown_it::*;
use serde_json::{json, Value};

pub struct Config;

pub trait RenderMdastNode {
    fn render_mdast(&self, config: &Config) -> Value;
}

impl RenderMdastNode for Node {
    fn render_mdast(&self, config: &Config) -> Value {
        macro_rules! render_node {
            ($node_type:ty) => {
                self.node_value
                    .downcast_ref::<$node_type>()
                    .unwrap()
                    .to_mdast(self, config)
            };
        }
        match self.node_type.id {
            id if id == TypeId::of::<Root>() => render_node!(Root),
            id if id == TypeId::of::<Paragraph>() => render_node!(Paragraph),
            id if id == TypeId::of::<Text>() => render_node!(Text),
            // ...
            _ => unimplemented!("node type not implemented: {:?}", self.node_type)
        }
    }
}

trait RenderMdast: NodeValue {
    fn to_mdast(&self, node: &Node, config: &Config) -> Value;
    fn create_children(&self, node: &Node, config: &Config) -> Vec<Value> {
        return node
            .children
            .iter()
            .map(|child| child.render_mdast(config))
            .collect::<Vec<Value>>();
    }
}

impl RenderMdast for Root {
    fn to_mdast(&self, node: &Node, config: &Config) -> Value {
        json!({
            "type": "root",
            "children": self.create_children(node, config),
        })
    }
}

impl RenderMdast for Paragraph {
    fn to_mdast(&self, node: &Node, config: &Config) -> Value {
        json!({
            "type": "paragraph",
            "children": self.create_children(node, config),
        })
    }
}

impl RenderMdast for Text {
    fn to_mdast(&self, _: &Node, _: &Config) -> Value {
        json!({
            "type": "text",
            "value": self.content,
        })
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_render() {
        let md = &mut markdown_it::MarkdownIt::new();
        markdown_it::plugins::cmark::add(md);
        md.parse("hallo").render_mdast(&Config);
    }
}

@rlidwka
Copy link
Collaborator

rlidwka commented Aug 17, 2023

This does bring up another issue I was going to open, which is what is the best way to implement renderers?

@chrisjsewell, your assessment above is correct.

As of now, you can override Renderer to render slightly differently (e.g. html vs xhtml). But there is no good way to implement rendering into something that's not html-like.

What are use-cases for this? Render back into markdown and... anything else?

@chrisjsewell
Copy link
Member

chrisjsewell commented Aug 18, 2023

Well in theory, anything that pandoc can output 😅

But I think at a "minimum"; a round-trip (i.e. Markdown) renderer, a programming language agnostic (e.g. JSON) AST renderer

@jryio
Copy link
Author

jryio commented Aug 18, 2023

This does bring up another issue I was going to open, which is what is the best way to implement renderers?

@chrisjsewell, your assessment above is correct.

As of now, you can override Renderer to render slightly differently (e.g. html vs xhtml). But there is no good way to implement rendering into something that's not html-like.

What are use-cases for this? Render back into markdown and... anything else?

The use case is to parse the source markdown syntax, transform the AST based on any number of rules/replacements/special markup syntax, then output valid common mark markdown.

Nothing other than that. There do not seem to be too many Rust crates which are capable of this.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants