Files
md_to_gost/md2gost/renderable_factory.py
Igor20264 1a5b35eb54
Python application / build (push) Has been cancelled
BigUpdate 0.5.0
- add Local Render Mermaid
- add page-starе для указания смещения страниц
- add Гиперссылки в документе на списки литературы
2026-09-07 09:54:33 +03:00

196 lines
8.6 KiB
Python

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