From 5e144d303d75d72dff710ae60cbc7f737dff4316 Mon Sep 17 00:00:00 2001 From: Amy Gale Ruth Bowersox Date: Wed, 13 Mar 2024 22:02:01 -0600 Subject: [PATCH] added support for Obsidian style image references, including embedded dimensions --- src/dragonglass/mparse.py | 95 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 94 insertions(+), 1 deletion(-) diff --git a/src/dragonglass/mparse.py b/src/dragonglass/mparse.py index 5c726f1..ec05d3b 100644 --- a/src/dragonglass/mparse.py +++ b/src/dragonglass/mparse.py @@ -1,5 +1,6 @@ #/usr/bin/env python3 +import re import markdown import xml.etree.ElementTree as etree from urllib.parse import urlparse @@ -60,6 +61,98 @@ class MetaStripper(Extension): md.preprocessors.register(MetaStripper.MetaStripperProc(md), 'metastripper', PRIO_BASE) +class ObsidianImages(Extension): + """An extension that supports image tags the way Obsidian handles them.""" + DIMS = re.compile(r'(.*)\|(\d+)(?:x(\d+))?') + + def __init__(self, context, **kwargs): + super(ObsidianImages, self).__init__(**kwargs) + self._context = context + + @property + def invalid_reference_classname(self): + return 'invalid-reference' + + def _parse_dimensions(self, s): + m = self.DIMS.match(s) + if m: + width = int(m.group(2)) + height = int(m.group(3)) if m.group(3) else -1 + return m.group(1), width, height + else: + return s, -1, -1 + + def _lookup_image_reference(self, name): + node, _ = self._context.src_index.lookup(name) + if node: + return node.link_target(self._context.url_prefix) + return None + + class ObsidianImageProc(InlineProcessor): + def __init__(self, pattern, md, extref): + super(ObsidianImages.ObsidianImageProc, self).__init__(pattern, md) + self._extref = extref + + def handleMatch(self, m, data): + name, width, height = self._extref._parse_dimensions(m.group(1)) + link = self._extref._lookup_image_reference(name) + if link is None: + el = etree.Element('span') + el.set('class', self._extref.invalid_reference_classname) + el.text = name + else: + el = etree.Element('img') + el.set('src', link) + el.set('alt', name) + if width > 0: + el.set('width', str(width)) + if height > 0: + el.set('height', str(height)) + return el, m.start(0), m.end(0) + + class GenericImageProc(InlineProcessor): + def __init__(self, pattern, md, extref): + super(ObsidianImages.GenericImageProc, self).__init__(pattern, md) + self._extref = extref + + def handleMatch(self, m, data): + name, width, height = self._extref._parse_dimensions(m.group(1)) + link = m.group(2) + if is_proper_url(link): + el = etree.Element('img') + el.set('src', link) + if len(name) > 0: + el.set('alt', name) + if width > 0: + el.set('width', str(width)) + if height > 0: + el.set('height', str(height)) + else: + newlink = self._extref._lookup_image_reference(link) + if newlink: + el = etree.Element('img') + el.set('src', newlink) + if len(name) > 0: + el.set('alt', name) + if width > 0: + el.set('width', str(width)) + if height > 0: + el.set('height', str(height)) + else: + el = etree.Element('span') + el.set('class', self._extref.invalid_reference_classname) + el.text = link + return el, m.start(0), m.end(0) + + def extendMarkdown(self, md): + OBSIMAGE_PATTERN = r'!\[\[(.*?)\]\]' + GENERICIMAGE_PATTERN = r'!\[(.*?)\]\((.*?)\)' + md.inlinePatterns.register(ObsidianImages.ObsidianImageProc(OBSIMAGE_PATTERN, md, self), + 'obsidian_images', PRIO_BASE + 1010) + md.inlinePatterns.register(ObsidianImages.GenericImageProc(GENERICIMAGE_PATTERN, md, self), + 'obsidian_generic_images', PRIO_BASE + 1000) + + class ObsidianLinks(Extension): """ An extension that processes Obsidian internal links in the [[page name]] format, as well as overrides the standard @@ -151,4 +244,4 @@ class ObsidianInlines(Extension): def create_markdown_parser(context): - return markdown.Markdown(extensions=[MetaStripper(), ObsidianLinks(context), ObsidianInlines()]) + return markdown.Markdown(extensions=[MetaStripper(), ObsidianImages(context), ObsidianLinks(context), ObsidianInlines()])