hot-fix 0.4.3
Python application / build (push) Has been cancelled

- fix Не появляется пустая странициа перед названием таблицы
- fix Не появляется пустая страница перед альбомной ориентацией
- fix При отказе от изменений создаётся новый файл
- fix Замена "..." на «...» больше не затрагивает uml и mermaid блоки
This commit is contained in:
Igor20264
2026-09-05 10:24:13 +03:00
parent 8508d04bbc
commit febf62c257
18 changed files with 267 additions and 49 deletions
+80
View File
@@ -156,6 +156,86 @@ def test_landscape_places_figure_inside_landscape_section(tmp_path, monkeypatch)
assert image_section == 1
W_NS = "{http://schemas.openxmlformats.org/wordprocessingml/2006/main}"
def _is_break_paragraph(el) -> bool:
"""Empty paragraph that is a page break and/or hosts a section break."""
if el.tag != f"{W_NS}p":
return False
texts = [t.text or "" for t in el.findall(f".//{W_NS}t")]
if any(t.strip() for t in texts):
return False
pPr = el.find(f"{W_NS}pPr")
has_sect = pPr is not None and pPr.find(f"{W_NS}sectPr") is not None
has_br = any(br.get(f"{W_NS}type") == "page" for br in el.findall(f".//{W_NS}br"))
return has_sect or has_br
def _breaks_between_last_text_and_table(doc) -> int:
"""Empty page/section-break paras between the last body sentence and the table."""
children = list(doc.element.body)
table_i = next(i for i, el in enumerate(children) if el.tag == f"{W_NS}tbl")
j = table_i - 1
breaks = 0
while j >= 0:
el = children[j]
if _is_break_paragraph(el):
breaks += 1
j -= 1
continue
if el.tag == f"{W_NS}p":
text = "".join(t.text or "" for t in el.findall(f".//{W_NS}t")).strip()
if text.startswith("Таблица") or text.startswith("Продолжение"):
j -= 1
continue
break
return breaks
def test_portrait_to_landscape_is_single_section_break():
"""Text then +landscape: one next-page section, no extra blank portrait page."""
doc = Document(os.path.join(package_dir(), "Template.docx"))
body = doc._body._element
for child in list(body):
if child.tag.endswith("}sectPr"):
continue
body.remove(child)
from md2gost.renderable.heading import Heading
from md2gost.renderable.paragraph import Paragraph
from md2gost.renderable.toc import ToC
h = Heading(doc._body, 1, False)
h.add_run("СОДЕРЖАНИЕ")
toc = ToC(doc._body, toc_mode="native")
para = Paragraph(doc._body)
para.add_run("Текст перед широкой таблицей.")
table = Table(doc._body, 2, 3, CaptionInfo("wide", "Карта", landscape=True))
for r in range(2):
for c in range(3):
table.add_paragraph_to_cell(r, c).add_run(f"{r}{c}")
Renderer(doc, skip_numbering=True).process([h, toc, para, table])
assert _breaks_between_last_text_and_table(doc) == 1
# Same without TOC — still a single break, not page-break + section-break.
doc2 = Document(os.path.join(package_dir(), "Template.docx"))
body2 = doc2._body._element
for child in list(body2):
if child.tag.endswith("}sectPr"):
continue
body2.remove(child)
para2 = Paragraph(doc2._body)
para2.add_run("Текст перед широкой таблицей.")
table2 = Table(doc2._body, 2, 3, CaptionInfo("wide2", "Карта", landscape=True))
for r in range(2):
for c in range(3):
table2.add_paragraph_to_cell(r, c).add_run(f"{r}{c}")
Renderer(doc2, skip_numbering=True).process([para2, table2])
assert _breaks_between_last_text_and_table(doc2) == 1
def test_landscape_listing_deferred_after_section(tmp_path, monkeypatch):
"""+landscape +listing → listing must not sit in the landscape section."""
from unittest.mock import patch
+43
View File
@@ -190,6 +190,27 @@ def test_preprocess_quotes():
assert "«тест»" in preprocess_markdown('Он сказал "тест" вслух.')
def test_preprocess_skips_uml_and_mermaid_fences():
md = (
'В тексте "кавычки".\n\n'
"```uml\n"
'rectangle "Акционеры" as own\n'
"```\n\n"
"```mermaid\n"
'title "Выручка"\n'
"```\n\n"
"```uml-c4context\n"
'Person(user, "Заявитель")\n'
"```\n"
)
out = preprocess_markdown(md)
assert "«кавычки»" in out
assert 'rectangle "Акционеры" as own' in out
assert 'title "Выручка"' in out
assert 'Person(user, "Заявитель")' in out
assert "«Акционеры»" not in out
def test_formula_refs():
assert find_formula_refs("см. @Формула:eq1 и далее") == {"eq1"}
@@ -466,6 +487,28 @@ def test_appendix_index_auto_inserted():
assert out2 == [sec, manual, a, b]
def test_table_caption_does_not_set_page_break_before():
import os
import docx
from docx.shared import Mm
from md2gost import package_dir
from md2gost.styles import apply_mirea_styles
from md2gost.layout_tracker import LayoutState
from md2gost.renderable.caption import Caption, CaptionInfo
doc = docx.Document(os.path.join(package_dir(), "Template.docx"))
apply_mirea_styles(doc)
cap = Caption(doc._body, "Таблица", CaptionInfo("t1", "Реквизиты"), "1", True)
state = LayoutState(Mm(250), Mm(160))
state.add_height(Mm(248))
leftover = state.remaining_page_height
infos = list(cap.render(None, state))
assert len(infos) == 1
assert cap._docx_paragraph.style.name == "Название таблицы"
assert cap._docx_paragraph.paragraph_format.page_break_before in (None, False)
assert infos[0].height > leftover
def test_caption_table_keep_with_next_and_toc_styles():
import docx
from docx.enum.text import WD_ALIGN_PARAGRAPH, WD_LINE_SPACING
+23 -1
View File
@@ -4,7 +4,13 @@ import os
import sys
from md2gost.dnd import first_markdown, normalize_drop_paths, parse_tkdnd_files
from md2gost.pipeline import ConvertRequest, convert, default_output_path, should_launch_gui
from md2gost.pipeline import (
ConvertRequest,
convert,
default_output_path,
should_launch_gui,
timestamped_output_path,
)
from md2gost.__main__ import build_parser, request_from_args
@@ -20,6 +26,22 @@ def test_default_output_path():
assert path.endswith("report.docx")
def test_timestamped_output_path(tmp_path):
import re
src = tmp_path / "ПР1.docx"
src.write_bytes(b"x")
out = timestamped_output_path(str(src))
name = os.path.basename(out)
assert re.fullmatch(r"ПР1_\d{4}-\d{2}-\d{2}-\d{2}-\d{2}\.docx", name)
assert not os.path.exists(out)
# collision → seconds (or seconds_2)
open(out, "wb").close()
out2 = timestamped_output_path(str(src))
assert out2 != out
assert os.path.basename(out2).startswith("ПР1_")
assert out2.endswith(".docx")
def test_parse_tkdnd_files():
raw = r"{C:\My Files\a.md} C:\tmp\b.md"
assert parse_tkdnd_files(raw) == [r"C:\My Files\a.md", r"C:\tmp\b.md"]