import logging from functools import singledispatchmethod from docx.shared import Parented, RGBColor from .renderable import * from .renderable import Renderable from . import extended_markdown from .renderable.caption import CaptionInfo from .renderable.paragraph import Link from .renderable.table import Table from .renderable.equation import Equation from .renderable.heading import Heading from .renderable.list import List from .renderable.toc import ToC from .renderable.diagram import DiagramFigure from .label_pass import resolve_reference from .diagram_renderer import is_diagram_lang class RenderableFactory: def __init__(self, parent: Parented, hr_pagebreak: bool = False): self._parent = parent self._hr_pagebreak = hr_pagebreak @singledispatchmethod def create(self, marko_element: extended_markdown.BlockElement, caption_info: CaptionInfo) -> Renderable: paragraph = Paragraph(self._parent) paragraph.add_run(f"{marko_element.get_type()} is not supported", color=RGBColor.from_string('ff0000')) logging.warning(f"{marko_element.get_type()} is not supported") return paragraph @staticmethod def _create_runs(paragraph_or_link: Paragraph | Link, children, classes: list[type] = None): if not classes: classes = [] for child in children: if isinstance(child, (extended_markdown.RawText, extended_markdown.Literal)): text = child.children if isinstance(paragraph_or_link, Paragraph) and hasattr( paragraph_or_link, "add_run_with_citations" ): paragraph_or_link.add_run_with_citations( text, is_bold=extended_markdown.StrongEmphasis in classes or None, is_italic=extended_markdown.Emphasis in classes or None, strike_through=extended_markdown.Strikethrough in classes or None, ) else: paragraph_or_link.add_run( text, is_bold=extended_markdown.StrongEmphasis in classes or None, is_italic=extended_markdown.Emphasis in classes or None, strike_through=extended_markdown.Strikethrough in classes or None, ) elif isinstance(child, extended_markdown.CodeSpan): paragraph_or_link.add_run(child.children, is_italic=True) elif isinstance(child, extended_markdown.Image): caption = CaptionInfo( child.unique_name, child.title, getattr(child, "with_listing", False), getattr(child, "landscape", False), ) paragraph_or_link.add_image(child.dest, caption) elif isinstance(child, extended_markdown.LineBreak): pass elif isinstance(child, extended_markdown.InlineEquation): paragraph_or_link.add_inline_equation(child.latex_equation) elif isinstance(child, extended_markdown.Reference): if hasattr(paragraph_or_link, "add_reference"): paragraph_or_link.add_reference(child.type, child.name) else: paragraph_or_link.add_run(resolve_reference(child.type, child.name)) elif isinstance(child, (extended_markdown.Link, extended_markdown.Url)): RenderableFactory._create_runs(paragraph_or_link.add_link(child.dest), child.children, classes) elif isinstance(child, (extended_markdown.Emphasis, extended_markdown.StrongEmphasis, extended_markdown.Strikethrough)): RenderableFactory._create_runs(paragraph_or_link, child.children, classes + [type(child)]) else: paragraph_or_link.add_run(f" {child.get_type()} is not supported ", color=RGBColor.from_string("FF0000")) logging.warning(f"{child.get_type()} is not supported") @create.register def _(self, marko_paragraph: extended_markdown.Paragraph, caption_info: CaptionInfo): paragraph = Paragraph(self._parent) RenderableFactory._create_runs(paragraph, marko_paragraph.children) return paragraph @create.register def _(self, marko_heading: extended_markdown.Heading, caption_info: CaptionInfo): heading = Heading(self._parent, marko_heading.level, marko_heading.numbered) RenderableFactory._create_runs(heading, marko_heading.children) return heading @create.register def _(self, marko_code_block: extended_markdown.FencedCode, caption_info: CaptionInfo): lang = (marko_code_block.lang or "").strip().lower() source = marko_code_block.children[0].children if is_diagram_lang(lang): return DiagramFigure( self._parent, lang, source, caption_info, with_listing=bool(caption_info and caption_info.with_listing), ) listing = Listing(self._parent, marko_code_block.lang, caption_info) listing.set_text(source) return listing @create.register def _(self, marko_code_block: extended_markdown.CodeBlock, caption_info: CaptionInfo): lang = (getattr(marko_code_block, "lang", "") or "").strip().lower() source = marko_code_block.children[0].children if is_diagram_lang(lang): return DiagramFigure( self._parent, lang, source, caption_info, with_listing=bool(caption_info and caption_info.with_listing), ) listing = Listing(self._parent, getattr(marko_code_block, "lang", "") or "", caption_info) listing.set_text(source) return listing @create.register def _(self, marko_equation: extended_markdown.Equation, caption_info: CaptionInfo): unique = caption_info.unique_name if caption_info else None formula = Equation(self._parent, marko_equation.latex_equation, unique_name=unique, numbered=False) return formula @create.register def _(self, marko_list: extended_markdown.List, caption_info: CaptionInfo): list_ = List(self._parent, marko_list.ordered) def create_items_from_marko(marko_list_, level=1): for list_item in marko_list_.children: for child in list_item.children: if isinstance(child, extended_markdown.List): create_items_from_marko(child, level + 1) elif isinstance(child, extended_markdown.Paragraph): RenderableFactory._create_runs( list_.add_item(level), child.children ) create_items_from_marko(marko_list) return list_ @create.register def _(self, marko_table: extended_markdown.Table, caption_info: CaptionInfo): table = Table(self._parent, len(marko_table.children), len(marko_table.children[0].children), caption_info) for i, row in enumerate(marko_table.children): for j, cell in enumerate(row.children): merge_v = getattr(cell, "merge_v", "none") or "none" merge_h = getattr(cell, "merge_h", "none") or "none" table.set_cell_merge(i, j, merge_v, merge_h) if merge_v == "continue" or merge_h == "continue": # Placeholder paragraph so cell is still addressable; content unused on render table.add_paragraph_to_cell(i, j) continue RenderableFactory._create_runs( table.add_paragraph_to_cell(i, j), cell.children ) return table @create.register def _(self, marko_toc: extended_markdown.TOC, caption_info: CaptionInfo): toc = ToC(self._parent) return toc @create.register def _(self, marko_hr: extended_markdown.ThematicBreak, caption_info: CaptionInfo): if self._hr_pagebreak: from .renderable.page_break import PageBreak return PageBreak(self._parent) paragraph = Paragraph(self._parent) paragraph.add_run( "ThematicBreak is not supported", color=RGBColor.from_string("ff0000"), ) logging.warning("ThematicBreak is not supported") return paragraph