from jinja2 import contextfilter, environmentfilter from jinja2.ext import Extension from io import StringIO from lxml import etree @contextfilter def rellinktoabs(context, value): env = context.environment # get the path for this context rel_path = context['resource'].relative_path content_url = env.globals['content_url'](context, rel_path) html = etree.HTML(value) # get all the fragment urls r = html.xpath("//a[@href[starts-with(.,'#')]]") for i in r: # prefix them w/ the content_url i.attrib['href'] = content_url + i.attrib['href'] return etree.tostring(html, encoding='unicode', method='html') # mostly copied from hyde.ext.templates.jinja.py Markdown # and using docs from: # https://jinja.palletsprojects.com/en/2.10.x/extensions/#example-extension # to get the filter installed class RelLinktoAbs(Extension): """ A wrapper around the rellinktoabs filter for syntactic sugar. """ tags = { 'rellinktoabs' } def __init__(self, env): super(RelLinktoAbs, self).__init__(env) env.filters['rellinktoabs'] = rellinktoabs def parse(self, parser): """ Parses the statements and defers to the callback for rellinktoabs processing. """ lineno = next(parser.stream).lineno body = parser.parse_statements(['name:endrellinktoabs'], drop_needle=True) return nodes.CallBlock( self.call_method('_render_rellinktoabs'), [], [], body).set_lineno(lineno) def _render_rellinktoabs(self, caller=None): """ Calls the rellinktoabs filter to transform the output. """ if not caller: return '' output = caller().strip() return rellinktoabs(self.environment, output)