From 2ba6fe2673f39c8549921a837cc146c7a0724c09 Mon Sep 17 00:00:00 2001 From: Igor20264 Date: Fri, 4 Sep 2026 22:48:33 +0300 Subject: [PATCH] 0.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - fix Не собиралось само список всех приложений --- md2gost/help_content.py | 4 ++- md2gost/renderer.py | 74 +++++++++++++++++++++++++++++++++++++++-- tests/test_mirea_tz.py | 38 +++++++++++++++++++++ 3 files changed, 113 insertions(+), 3 deletions(-) diff --git a/md2gost/help_content.py b/md2gost/help_content.py index 8cfc18d..8f7ef72 100644 --- a/md2gost/help_content.py +++ b/md2gost/help_content.py @@ -42,7 +42,9 @@ CLI (тот же движок) # *ЗАКЛЮЧЕНИЕ # *СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ # *ПРИЛОЖЕНИЯ - (после ПРИЛОЖЕНИЯ — перечень основным текстом, затем ## Приложение А Название) + (перечень «Приложение А — …» собирается сам из ## Приложение А Название; + можно написать вручную — тогда автосписок не дублируется) + ## Приложение А Название Нумерованные разделы: # 1 Название ## 1.1 Подраздел Точку в конце заголовка не ставить. diff --git a/md2gost/renderer.py b/md2gost/renderer.py index 98ac382..06df6ca 100644 --- a/md2gost/renderer.py +++ b/md2gost/renderer.py @@ -121,8 +121,9 @@ class Renderer: self._layout_tracker.set_page_size(max_height, max_width) def process(self, renderables: list[Renderable]): - for i in range(len(renderables)): - self.render(renderables[i]) + items = self._ensure_appendix_index(list(renderables)) + for i in range(len(items)): + self.render(items[i]) self._flush_to_new_screen() # If document had no TOC, still add page numbers to the only section @@ -132,6 +133,75 @@ class Renderer: if self._debugger: self._debugger.after_rendered() + def _ensure_appendix_index(self, renderables: list[Renderable]) -> list[Renderable]: + """Insert «Приложение X — Title» list after # *ПРИЛОЖЕНИЯ if missing (method guide §6).""" + sec_i = None + first_app_i = None + entries: list[tuple[str, str]] = [] + for i, r in enumerate(renderables): + if not isinstance(r, Heading): + continue + text = (r.text or "").strip() + upper = text.upper().replace("Ё", "Е") + if upper in ("ПРИЛОЖЕНИЯ", "ПРИЛОЖЕНИЕ") and sec_i is None: + sec_i = i + continue + m = re.match(r"(?i)^Приложение\s+([А-ЯA-ZЁ])(?:\s+(.+))?$", text) + if not m: + continue + letter = m.group(1).upper().replace("Ё", "Е") + title = (m.group(2) or "").strip() + if first_app_i is None: + first_app_i = i + entries.append((letter, title)) + + if sec_i is None or first_app_i is None or not entries: + return renderables + if self._has_appendix_list_between(renderables, sec_i, first_app_i): + return renderables + + from .profiles import dash_separator + dash = dash_separator() + index_paras: list[Renderable] = [] + for letter, title in entries: + line = f"Приложение {letter}" + if title: + line = f"{line} {dash} {title}" + index_paras.append(self._make_appendix_index_paragraph(line)) + + return renderables[: sec_i + 1] + index_paras + renderables[sec_i + 1 :] + + @staticmethod + def _has_appendix_list_between( + renderables: list[Renderable], sec_i: int, first_app_i: int + ) -> bool: + """True if author already put a list/prose between ПРИЛОЖЕНИЯ and first appendix.""" + for r in renderables[sec_i + 1 : first_app_i]: + if isinstance(r, Heading): + continue + text = "" + if isinstance(r, Paragraph): + text = (r._docx_paragraph.text or "").strip() + elif hasattr(r, "text"): + text = (getattr(r, "text", None) or "").strip() + if not text: + continue + if re.search(r"(?i)приложение\s+[А-ЯA-ZЁ]", text): + return True + if re.search(r"[А-Яа-яA-Za-z]{3,}", text): + return True + return False + + def _make_appendix_index_paragraph(self, text: str) -> Paragraph: + """Body-text line in the appendix list under ПРИЛОЖЕНИЯ (no first-line indent).""" + p = Paragraph(self._document._body) + p.add_run(text) + p.style = "Normal" + p.first_line_indent = Cm(0) + pf = p._docx_paragraph.paragraph_format + pf.left_indent = Cm(0) + return p + @staticmethod def _rewrite_heading_text(heading: Heading, text: str) -> None: runs = heading._docx_paragraph.runs diff --git a/tests/test_mirea_tz.py b/tests/test_mirea_tz.py index ca82765..185d9bf 100644 --- a/tests/test_mirea_tz.py +++ b/tests/test_mirea_tz.py @@ -428,6 +428,44 @@ def test_appendix_item_becomes_h3_plus_title(): assert title_p._docx_paragraph.alignment == WD_ALIGN_PARAGRAPH.CENTER +def test_appendix_index_auto_inserted(): + import docx + from md2gost.renderable.heading import Heading + from md2gost.renderer import Renderer + from md2gost.styles import apply_mirea_styles + + doc = docx.Document(r"md2gost/Template.docx") + doc._body.clear_content() + apply_mirea_styles(doc) + renderer = Renderer(doc, skip_numbering=True) + + sec = Heading(doc._body, 1, False) + sec.add_run("ПРИЛОЖЕНИЯ") + a = Heading(doc._body, 2, True) + a.add_run("Приложение А Графический материал") + b = Heading(doc._body, 2, True) + b.add_run("Приложение Б Листинг") + + out = renderer._ensure_appendix_index([sec, a, b]) + assert len(out) == 5 # sec + 2 index lines + a + b + assert out[0] is sec + t0 = out[1]._docx_paragraph.text + t1 = out[2]._docx_paragraph.text + assert t0.startswith("Приложение А") + assert "Графический материал" in t0 + assert t1.startswith("Приложение Б") + assert "Листинг" in t1 + assert out[3] is a + assert out[4] is b + + # If list already present — do not duplicate + from md2gost.renderable.paragraph import Paragraph + manual = Paragraph(doc._body) + manual.add_run("Приложение А — вручную") + out2 = renderer._ensure_appendix_index([sec, manual, a, b]) + assert out2 == [sec, manual, a, b] + + def test_caption_table_keep_with_next_and_toc_styles(): import docx from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING