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) # Note that this could be parsed w/ fragment_fromstring # (https://lxml.de/3.1/api/private/lxml.html-module.html#fragment_fromstring) # But is would require using create_parent, and stripping that # instead, or fragments_fromstring, but iterating over those # to strings. Not one is a solution to the problem. 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'] res = etree.tostring(html, encoding='unicode', method='html') # lxml.HTML wraps the html w/ html/body tags, strip them # if present startstr = '' endstr = '' startpos = 0 endpos = None if res.startswith(startstr): startpos = len(startstr) if res.endswith(endstr): endpos = -len(endstr) res = res[startpos:endpos] return res # 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)