v0.5.3
Example document / build (push) Has been cancelled
Python application / build (push) Has been cancelled

- Мелкие исправления увеличивающие работу конвертера
This commit is contained in:
Igor20264
2026-09-10 10:50:15 +03:00
parent 510f7e7adf
commit e6b58efeb3
95 changed files with 7165 additions and 8771 deletions
+149
View File
@@ -0,0 +1,149 @@
"""Backend-agnostic traversal of the top level of a md2gost markdown document."""
from __future__ import annotations
import re
from abc import ABC, abstractmethod
#: `(unique_name, text, with_listing)` captured from a `%id Подпись` line.
PendingCaption = tuple[str | None, str | None, bool]
_BIBLIO_RE = re.compile(r"^\[([^\]]+)\]:\s*(.+)$")
_CONTENTS_TITLE = "СОДЕРЖАНИЕ"
def caption_tuple(element) -> PendingCaption:
return (element.unique_name, element.text, getattr(element, "with_listing", False))
def match_bibliography(children) -> tuple[str, str] | None:
"""Detect a `[key]: описание` source line, ignoring formatted spans."""
probe = ""
for child in children or []:
if type(child).__name__ in ("RawText", "Literal"):
probe += child.children or ""
m = _BIBLIO_RE.match(probe.strip())
if not m:
return None
return m.group(1), m.group(2).strip()
def is_contents_heading(text: str) -> bool:
upper = text.upper()
return upper == _CONTENTS_TITLE or upper.startswith(_CONTENTS_TITLE)
def parse_image_caption(image, default_id: str | None) -> tuple[str | None, str]:
"""Split an image title of the form `%id Подпись` into id and caption text."""
title = getattr(image, "title", None) or ""
if not title.startswith("%"):
return default_id, title
parts = title[1:].split(None, 1)
return parts[0], parts[1] if len(parts) > 1 else ""
class BlockVisitor(ABC):
"""
Emit hooks for :func:`walk_blocks` and :func:`walk_paragraph`.
Implementations own `pending_caption` and `biblio` because the concrete
emitters need to read them outside the walk as well.
"""
pending_caption: PendingCaption | None
biblio: list[tuple[str, str]]
def take_caption(self) -> PendingCaption:
pending = self.pending_caption
if pending is None:
return (None, None, False)
self.pending_caption = None
return pending
@abstractmethod
def on_heading(self, element) -> None:
...
@abstractmethod
def on_table(self, element, caption_id: str | None, caption_text: str | None) -> None:
...
@abstractmethod
def on_code(self, element, lang: str) -> None:
...
@abstractmethod
def on_list(self, element) -> None:
...
@abstractmethod
def on_equation(self, element) -> None:
...
@abstractmethod
def on_quote(self, element) -> None:
...
@abstractmethod
def on_other(self, element) -> None:
...
@abstractmethod
def on_image(self, image) -> None:
...
@abstractmethod
def on_body_text(self, children) -> None:
...
def on_biblio(self, key: str, text: str) -> None:
self.biblio.append((key, text))
def on_paragraph(self, element) -> None:
walk_paragraph(element, self)
def walk_paragraph(paragraph, visitor: BlockVisitor) -> None:
"""Route one paragraph to the bibliography, image or plain-text hook."""
biblio = match_bibliography(paragraph.children)
if biblio is not None:
visitor.on_biblio(*biblio)
return
children = paragraph.children or []
images = [c for c in children if type(c).__name__ == "Image"]
if images:
for image in images:
visitor.on_image(image)
visitor.on_body_text([c for c in children if type(c).__name__ != "Image"])
return
visitor.on_body_text(children)
def walk_blocks(children, visitor: BlockVisitor) -> None:
"""Dispatch every top-level element of a parsed document to `visitor`."""
for element in children:
name = type(element).__name__
if name in ("BlankLine", "TOC"):
continue
if name == "Caption":
visitor.pending_caption = caption_tuple(element)
elif name == "Heading":
visitor.on_heading(element)
elif name == "Paragraph":
visitor.on_paragraph(element)
elif name == "Table":
caption_id, caption_text, _ = visitor.take_caption()
visitor.on_table(element, caption_id, caption_text)
elif name in ("FencedCode", "CodeBlock"):
visitor.on_code(element, getattr(element, "lang", "") or "")
elif name == "List":
visitor.on_list(element)
elif name == "Equation":
visitor.on_equation(element)
elif name == "Quote":
visitor.on_quote(element)
elif hasattr(element, "children"):
visitor.on_other(element)