Files
md_to_gost/md2gost/renderable/toc.py
T
Igor20264 b38661f588
Python application / build (push) Has been cancelled
Update 0.4.0
- Add\Rework UI
- Add Split Table and Listing
- Add Support Customazeble schems
2026-09-04 22:28:39 +03:00

146 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Table of contents: manual (layout-based) or native Word TOC field."""
from copy import copy
from typing import Generator
from docx.enum.text import WD_TAB_LEADER, WD_TAB_ALIGNMENT, WD_PARAGRAPH_ALIGNMENT
from docx.shared import Parented
from . import Paragraph
from .page_break import PageBreak
from .renderable import Renderable
from ..layout_tracker import LayoutState
from ..rendered_info import RenderedInfo
from ..sub_renderable import SubRenderable
from ..util import create_element
SPECIAL_TITLES = {
"СОДЕРЖАНИЕ", "ВВЕДЕНИЕ", "ЗАКЛЮЧЕНИЕ",
"СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ",
"СПИСОК ИСПОЛЬЗУЕМЫХ ИСТОЧНИКОВ",
"ПРИЛОЖЕНИЕ", "ПРИЛОЖЕНИЯ",
}
# Word field: outline levels 13, hyperlinks, hide tab/page in web view
_NATIVE_TOC_INSTR = r'TOC \o "1-3" \h \z \u'
class ToC(Renderable):
def __init__(self, parent: Parented, heading_numbering: str = "manual",
toc_mode: str = "native"):
self._parent = parent
self._heading_numbering = heading_numbering # auto | manual
self._toc_mode = toc_mode # manual | native
self._paragraph = Paragraph(parent)
self._paragraph._docx_paragraph.paragraph_format.alignment = WD_PARAGRAPH_ALIGNMENT.LEFT
self._paragraph.first_line_indent = 0
self._items: list[tuple[int, str, int, bool]] = []
self._native_ready = False
def set_heading_numbering(self, mode: str):
self._heading_numbering = mode
def set_toc_mode(self, mode: str):
self._toc_mode = mode
@property
def toc_mode(self) -> str:
return self._toc_mode
def add_item(self, level: int, title: str, page: int, numbered: bool):
self._items.append((level, title, page, numbered))
def fill(self):
if self._toc_mode == "native":
self._fill_native()
else:
self._fill_manual()
def _fill_native(self):
"""Insert a Word TOC field. Pages appear after update in Word."""
if self._native_ready:
return
p = self._paragraph._docx_paragraph
# Clear any leftover runs
for child in list(p._p):
if child.tag.endswith("}r"):
p._p.remove(child)
def add_fld_char(fld_char_type: str, dirty: bool = False):
attrs = {"w:fldCharType": fld_char_type}
if dirty:
attrs["w:dirty"] = "true"
run = create_element("w:r", [
create_element("w:fldChar", attrs)
])
p._p.append(run)
def add_instr(text: str):
instr = create_element("w:instrText", {"xml:space": "preserve"}, f" {text} ")
run = create_element("w:r", [instr])
p._p.append(run)
def add_text(text: str):
run = create_element("w:r", [
create_element("w:t", {"xml:space": "preserve"}, text)
])
p._p.append(run)
add_fld_char("begin", dirty=True)
add_instr(_NATIVE_TOC_INSTR)
add_fld_char("separate")
add_text(
"Обновите содержание в Word: ПКМ по полю → Обновить поле → целиком."
)
add_fld_char("end")
self._native_ready = True
def _fill_manual(self):
p = self._paragraph._docx_paragraph
usable = (p.part.document.sections[0].page_width
- p.part.document.sections[0].left_margin
- p.part.document.sections[0].right_margin)
p.paragraph_format.tab_stops.add_tab_stop(
usable, alignment=WD_TAB_ALIGNMENT.RIGHT, leader=WD_TAB_LEADER.DOTS)
p.paragraph_format.tab_stops.add_tab_stop(
0, alignment=WD_TAB_ALIGNMENT.LEFT, leader=WD_TAB_LEADER.SPACES)
numbering = [0 for _ in range(10)]
for level, title, page, numbered in self._items:
numbering[level - 1] += 1
for i in range(level, len(numbering)):
numbering[i] = 0
if title.strip().upper() == "СОДЕРЖАНИЕ":
continue
display = title.strip()
# Special sections and numbered H1 → uppercase in TOC (method guide)
if (
display.upper() in SPECIAL_TITLES
or display.upper().startswith("ПРИЛОЖЕНИЕ")
or (level == 1 and numbered)
):
display = display.upper()
p.add_run(" " * (level - 1))
if numbered and self._heading_numbering == "auto":
num = ".".join(str(x) for x in numbering[:level])
p.add_run(f"{num} ")
run = p.add_run(display)
run.bold = False
p.add_run(f"\t{page}")
p.add_run("\n")
def render(self, previous_rendered: RenderedInfo, layout_state: LayoutState)\
-> Generator[RenderedInfo | SubRenderable, None, None]:
# Native TOC: insert field shell early so it lands in the body before page break.
# Final instr is ensured again in fill() after render.
if self._toc_mode == "native" and not self._native_ready:
self._fill_native()
for rendered_info in self._paragraph.render(previous_rendered, copy(layout_state)):
yield RenderedInfo(rendered_info.docx_element, 0)
yield from PageBreak(self._parent).render(None, copy(layout_state))