"""Create generic figures with captions. SPDX-FileCopyrightText: © 2022 Brian S. Stephan SPDX-License-Identifier: AGPL-3.0-or-later """ import re from xml.etree.ElementTree import SubElement # nosec B405 - not parsing untrusted XML here import markdown class FigureExtension(markdown.Extension): """Wrap the markdown prepcoressor.""" def extendMarkdown(self, md): """Add FigureBlockProcessor to the Markdown instance.""" md.parser.blockprocessors.register(FigureBlockProcessor(md.parser), 'figure', 100) class FigureBlockProcessor(markdown.blockprocessors.BlockProcessor): """Process figures.""" # |> thing to put in the figure # |: optional caption for the figure # optional whatever else, like maybe an attr_list figure_regex = re.compile(r'^[ ]{0,3}\|>[ ]{0,3}(?P[^\n]*)') caption_regex = re.compile(r'^[ ]{0,3}\|:[ ]{0,3}(?P[^\n]*)') def test(self, parent, block): """Determine if we should process this block.""" lines = block.split('\n') return bool(self.figure_regex.search(lines[0])) def run(self, parent, blocks): """Replace the top block with HTML.""" block = blocks.pop(0) lines = block.split('\n') # consume line and create a figure figure_match = self.figure_regex.search(lines[0]) lines.pop(0) content = figure_match.group('content') figure = SubElement(parent, 'figure') figure.text = content if lines: if caption_match := self.caption_regex.search(lines[0]): # consume line and add the caption as a child of the figure lines.pop(0) caption = caption_match.group('caption') figcaption = SubElement(figure, 'figcaption') figcaption.text = caption if lines: # other lines are mysteries, might be attr_list, so re-append # make sure there's a child to hang the rest (which is maybe an attr_list?) off of # this is probably a bad hack if not len(list(figure)): SubElement(figure, 'span') rest = '\n'.join(lines) figure[-1].tail = f'\n{rest}' def makeExtension(*args, **kwargs): """Provide the extension to the markdown extension loader.""" return FigureExtension(*args, **kwargs)