miyuchina / mistletoe

A fast, extensible and spec-compliant Markdown parser in pure Python.
MIT License
831 stars 119 forks source link

use pylatex to render latex #78

Open Freakwill opened 5 years ago

Freakwill commented 5 years ago

Pylatex can produce more pretty latex codes.

class MyEnv(Environment):
    pass
​
env = MyEnv(options='hehe',data=['blahblah', 'blahblah'])
env.dumps()

# '\\begin{myenv}[hehe]%\nblahblah%\nblahblah%\n\\end{myenv}'

c = Command('mycmd', arguments=['bling', 'bling'])
c.dumps()
# '\\mycmd{bling}{bling}'
miyuchina commented 5 years ago

I'm interested in this. Will you be willing to write a renderer for this, preferably in pylatex_renderer, so that people who do not want to install an additional package have the default latex_renderer to fallback on?

If not, I can also look into this in a few. Thanks for the suggestion!

Freakwill commented 5 years ago

Following is just an attempt, not applying all features of pylatex, such as package propagating, there must be a better one.

pylatex_renderer.py:

# -*- coding: utf-8 -*-

"""
LaTeX renderer for mistletoe with pylatex.
"""

import mistletoe.latex_token as latex_token
from mistletoe.base_renderer import BaseRenderer

from pylatex import *
from pylatex.base_classes import *
from pylatex.utils import *

class PyLaTeXRenderer(BaseRenderer):
    def __init__(self, *extras):
        """
        Args:
            extras (list): allows subclasses to add even more custom tokens.
        """
        tokens = self._tokens_from_module(latex_token)
        self.packages = []
        super().__init__(*tokens, *extras)

    def render_strong(self, token):
        return bold(self.render_inner(token))

    def render_emphasis(self, token):
        return Command('textit', arguments=self.render_inner(token))

    def render_inline_code(self, token):
        return verbatim(self.render_inner(token))

    def render_strikethrough(self, token):
        self.packages.append(Package('ulem', options='normalem'))
        return Command('sout', data=self.render_inner(token))

    def render_image(self, token):
        self.packages.append(Package('graphicx'))
        return StandAloneGraphic(token.src)

    def render_link(self, token):
        self.packages.append(Package('hyperref'))
        inner = self.render_inner(token)
        return Command('href', arguments=[token.target, inner])

    def render_auto_link(self, token):
        self.packages.append(Package('hyperref'))
        return Command('url', token.target)

    @staticmethod
    def render_math(token):
        return token.content

    def render_escape_sequence(self, token):
        return self.render_inner(token)

    def render_raw_text(self, token, escape=True):
        return escape_latex(token.content) if escape else token.content

    def render_heading(self, token):
        inner = self.render_inner(token)
        if token.level == 1:
            return Section(inner)
        elif token.level == 2:
            return Subsection(inner)
        return Subsubsection(inner)

    def render_quote(self, token):
        self.packages.append(Package('csquotes'))
        class displayquote(Environment):
            packages = ['csquotes']
            data=self.render_inner(token)
        return displayquote.dumps()

    def render_paragraph(self, token):
        return '\n{}\n'.format(self.render_inner(token))

    def render_block_code(self, token):
        self.packages.append(Package('listings'))
        class lstlisting(Environment):
            packages = ['listings']
            options = Options(f'language={token.language}')
            data=[self.render_raw_text(token.children[0], False)]
        return lstlisting.dumps()

    def render_list(self, token):
        self.packages.append(Package('listings'))
        tag = Enumerate if token.start is not None else Itemize
        return tag(data=self.render_inner(token))

    def render_list_item(self, token):
        return self.render_inner(token)

    def render_table(self, token):
        def render_align(column_align):
            if column_align != [None]:
                return ''.join(map(get_align, token.column_align))
            else:
                return ''

        def get_align(col):
            if col is None:
                return 'l'
            elif col == 0:
                return 'c'
            elif col == 1:
                return 'r'
            raise RuntimeError('Unrecognized align option: ' + col)

        if hasattr(token, 'header'):
            head_inner = self.render_table_row(token.header)
            tab.add_row(head_inner)
            tab.add_hline()
        inner = self.render_inner(token)
        align = render_align(token.column_align)
        tab = Tabular(align)
        tab.add_row(inner)
        return tab.dumps()

    def render_table_row(self, token):
        return dumps_list(token.children, token='&', mapper=self.render)

    def render_table_cell(self, token):
        return self.render_inner(token)

    @staticmethod
    def render_thematic_break(token):
        return Command('hrulefill').dumps()

    @staticmethod
    def render_line_break(token):
        return '\n' if token.soft else Command('newline').dumps()+'\n'

    def render_document(self, token):
        self.footnotes.update(token.footnotes)
        with Document(packages=self.packages) as doc:
            doc.data=self.render_inner(token)
            return doc.dumps()