vdom
is Python data object for composing HTML. mistletoe
is a markdown converter; in this notebook we write a mistletoe
to vdom
renderer.
There are some outstanding issues about how to handle inline span html.
Personal Opinion:
mistletoe
is easier to extend thanmistune
andcommonmark
.
vdom
, like altair
, embed extra data into the notebook that can be consumed later.
This post builds on previous work that converts html
to vdom
.
from deathbeds import __vdom_from_raw_html as vdom_raw
import mistletoe, vdom, functools, html
vdomRenderer
replaces all of the mistletoe.html_renderer.HTMLRenderer
methods to return vdom
objects. It was possible to simplify
most of the original mistletoe.html_renderer.HTMLRenderer
by returning vdom
.
class vdomRenderer(mistletoe.html_renderer.HTMLRenderer):
def __call__(self, str):
return self.render(mistletoe.Document(str.splitlines()))
def render_inner(self, token, tag=None, **data):
render = lambda:tag(*(self.render(child) for child in token.children), **data)
if self._dom and self._dom.buf[-1]:
self._dom.buf[-1][-1] = functools.partial(self._dom.buf[-1][-1], render())
return ''
else: return render()
render_document = functools.partialmethod(render_inner, tag=vdom.div)
render_strong = functools.partialmethod(render_inner, tag=vdom.strong)
render_emphasis = functools.partialmethod(render_inner, tag=vdom.em)
render_strikethrough = functools.partialmethod(render_inner, tag=vdom.create_component('del'))
render_escape_sequence = functools.partialmethod(render_inner, tag=vdom.span)
render_paragraph = functools.partialmethod(render_inner, tag=vdom.p)
render_quote = functools.partialmethod(render_inner, tag=vdom.blockquote)
render_list_item = functools.partialmethod(render_inner, tag=vdom.li)
def render_image(self, token):
return vdom.img(src=token.src, alt=self.render_to_plain(token), title=token.title and self.escape_html(token.title) or '')
def render_to_plain(self, token):
if hasattr(token, 'children'):
return ''.join(self.render_to_plain(child) for child in token.children)
return self.escape_html(token.content)
def render_link(self, token): return self.render_inner(
token, vdom.a, href=self.escape_url(token.target),
title=token.title and self.escape_html(token.title) or '')
def render_auto_link(self, token): return self.render_inner(
token, vdom.a, href=token.mailto and 'mailto:{}'.format(token.target) or self.escape_url(token.target))
def render_raw_text(self, token): return self.escape_html(token.content)
def render_heading(self, token): return self.render_inner(token, getattr(vdom, F'h{token.level}'))
def render_block_code(self, token): return vdom.pre(
vdom.code(html.escape(token.children[0].content), **({
'class': F"language-{self.escape_html(token.language)}"
} if token.language else {})))
def render_list(self, token, tag=vdom.ul, **data):
if token.start is not None:
tag = vdom.ol
if token.start != 1: tag = functools.partial(tag, start=str(token.start))
data.update(start=token.start if token.start != 1 else '')
return self.render_inner(token, tag, **data)
def render_table(self, token, tag=vdom.table):
if hasattr(token, 'header'): tag = functools.partial(tag, vdom.thead(self.render_table_row(token.header, is_header=True)))
return tag(self.render_inner(token, vdom.tbody))
def render_table_row(self, token, is_header=False): return vdom.tr(
*(self.render_table_cell(child, is_header) for child in token.children))
def render_table_cell(self, token, in_header=False): return self.render_inner(
token, vdom.th if in_header else vdom.td, align={None: 'left', 0: 'center', 1: 'right'}[token.align])
@staticmethod
def render_thematic_break(token): return vdom.hr()
@staticmethod
def render_line_break(token): return '\n' if token.soft else vdom.br()
@staticmethod
def render_html_block(token): return vdom.div(vdom_raw.vdomParser()(token.content))
def render_inline_code(self, token):
return self.render_inner(html.escape(token.children[0].content), vdom.code)
_dom = None
def render_html_span(self, token):
self._dom = (self._dom or vdom_raw.vdomParser())
self._dom.feed(token.content)
return self._dom.buf.pop(0) if len(self._dom.buf) == 2 else ''
class FlexRenderer(vdomRenderer):
def render_list(self, token, tag=vdom.ul, **data):
setattr(self, 'prepends', getattr(self, 'prepends') or [])
if token.start is not None:
tag = vdom.ol
if token.start != 1: tag = functools.partial(tag, start=str(token.start))
data.update(start=token.start if token.start != 1 else '')
elif self.flex: tag = data.update(style={
'width': '100%', 'display': 'flex', 'flex-direction': ['row', 'column'][len(self.prepends)%2]
}) or vdom.div
try: return self.prepends.append(None) or self.render_inner(token, tag, **data)
finally: self.prepends.pop()
def render_list_item(self, token, tag=vdom.li, **data):
if self.prepends[-1] is None: self.prepends[-1] = token.prepend
if not token.leader[0].isnumeric() and self.flex:
tag = vdom.div
data.update(style={'flex': '1', 'max-width': '100%'})
return self.render_inner(token, tag, **data)
class FlexRenderer(vdomRenderer):
prepend = None
def render_list(self, token, tag=vdom.ul, **data):
setattr(self, 'prepend', getattr(self, 'prepend') or [])
self.prepend.append(token.children[0].leader if token.children and token.children[0].leader == '-' else None)
try:
if self.prepend[-1]: return self.render_inner(token, vdom.div, **{**data, 'style':{
'width': '100%', 'display': 'flex', 'flex-direction': ['column', 'row'][sum(map(bool, self.prepend))%2]}})
else: return super().render_list(token, tag, **data)
finally: self.prepend.pop()
def render_list_item(self, token, tag=vdom.li, **data):
if self.prepend[-1]:
tag = vdom.div
data.update(style={'flex': '1', 'max-width': '100%'})
return self.render_inner(token, tag, **data)
if __name__ == '__main__':
!ipython -m pytest -- 2018-11-03-Markdown-to-vdom-with-mistletoe.ipynb
============================= test session starts ============================= platform win32 -- Python 3.6.6, pytest-3.5.1, py-1.5.3, pluggy-0.6.0 Matplotlib: 2.2.2 Freetype: 2.8.1 rootdir: C:\Users\deathbeds\deathbeds.github.io\deathbeds, inifile: tox.ini plugins: xonsh-0.8.1, xdist-1.22.5, testmon-0.9.12, remotedata-0.2.1, parallel-0.0.2, openfiles-0.3.0, mpl-0.9, localserver-0.4.1, forked-0.2, doctestplus-0.1.3, arraydiff-0.2, hypothesis-3.66.16, importnb-0.5.0 collected 4 items 2018-11-03-Markdown-to-vdom-with-mistletoe.ipynb .... [100%] ========================== 4 passed in 0.86 seconds ===========================
renderers = __import__('pytest').mark.parametrize('renderer', (FlexRenderer, vdomRenderer))
@renderers
def test_renderer(renderer):
r = renderer()
r.render(mistletoe.Document("""---
# A table
| Tables | Are | Cool |
| ------------- |:-------------:| -----:|
| col 3 is | right-aligned | $1600 |
| col 2 is | centered | $12 |
| zebra stripes | are neat | $1 |
* Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu Now we are talaking about some business and that bu
* > Does a block quote work in line item
* asdklfjaskdfj
* Fuck
* A
* B
1. C
2. D
* Q
* ### B
---
> Testing things
### Howdy\n\n~~Good~~ look buh <span><code>__crap__</code></span>.\n\n<h1>Things</h1>""".splitlines()))
@renderers
def test_graphviz_in_markdown_in_vdom(renderer):
import graphviz
g = graphviz.Source("graph {A}")
renderer().render(mistletoe.Document(
F"""# bUST
{''.join(g._repr_svg_().splitlines())}""".splitlines()
))