@@ -0,0 +1,159 @@
|
||||
"""Pre-assign section-scoped numbers and resolve @Type:label references."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
from .numberer import Numberer, APPENDIX_LETTERS
|
||||
from .renderable import Renderable
|
||||
from .renderable.heading import Heading
|
||||
from .renderable.requires_numbering import RequiresNumbering
|
||||
from .renderable.equation import Equation
|
||||
from .renderable.paragraph import Paragraph
|
||||
from .renderable.caption import CaptionInfo
|
||||
|
||||
|
||||
APPENDIX_RE = re.compile(r"^ПРИЛОЖЕНИЕ\s+([А-ЯA-Z])\b", re.IGNORECASE)
|
||||
|
||||
|
||||
class LabelRegistry:
|
||||
def __init__(self):
|
||||
self._map: dict[tuple[str, str], str] = {}
|
||||
|
||||
def put(self, category: str, name: str, number: str):
|
||||
self._map[(category.lower(), name)] = number
|
||||
# also store without category for loose lookup
|
||||
self._map[("*", name)] = f"{category} {number}"
|
||||
|
||||
def get(self, category: str, name: str) -> str | None:
|
||||
return self._map.get((category.lower(), name))
|
||||
|
||||
def get_display(self, category: str, name: str) -> str | None:
|
||||
n = self.get(category, name)
|
||||
if n:
|
||||
return f"{category} {n}"
|
||||
# try Russian category aliases
|
||||
aliases = {
|
||||
"рисунок": "Рисунок",
|
||||
"figure": "Рисунок",
|
||||
"таблица": "Таблица",
|
||||
"table": "Таблица",
|
||||
"листинг": "Листинг",
|
||||
"listing": "Листинг",
|
||||
"формула": "Формула",
|
||||
"formula": "Формула",
|
||||
"equation": "Формула",
|
||||
}
|
||||
cat = aliases.get(category.lower(), category)
|
||||
n = self.get(cat, name)
|
||||
if n:
|
||||
return f"{cat} {n}"
|
||||
return None
|
||||
|
||||
|
||||
def assign_numbers(renderables: list[Renderable],
|
||||
numbered_equations: set[str] | None = None,
|
||||
numbering_scope: str = "section") -> LabelRegistry:
|
||||
"""Walk renderables once, set_number on objects, fill LabelRegistry."""
|
||||
numbered_equations = numbered_equations or set()
|
||||
numberer = Numberer(mode=numbering_scope)
|
||||
registry = LabelRegistry()
|
||||
section_count = 0
|
||||
appendix_index = 0
|
||||
|
||||
for r in renderables:
|
||||
if isinstance(r, Heading):
|
||||
text = (r.text or "").strip()
|
||||
upper = text.upper()
|
||||
if r.level == 1 and r.is_numbered:
|
||||
section_count += 1
|
||||
numberer.enter_section(section_count)
|
||||
m = APPENDIX_RE.match(upper)
|
||||
if m:
|
||||
numberer.enter_appendix(m.group(1).upper())
|
||||
elif upper.startswith("ПРИЛОЖЕНИЕ"):
|
||||
parts = upper.split()
|
||||
if len(parts) >= 2 and parts[1] in APPENDIX_LETTERS:
|
||||
numberer.enter_appendix(parts[1])
|
||||
elif appendix_index < len(APPENDIX_LETTERS):
|
||||
numberer.enter_appendix(APPENDIX_LETTERS[appendix_index])
|
||||
appendix_index += 1
|
||||
|
||||
if isinstance(r, Equation):
|
||||
label = r.unique_name
|
||||
if r.needs_numbering or (label and label in numbered_equations):
|
||||
r.enable_numbering()
|
||||
num = numberer.next_number("Формула", label)
|
||||
r.set_number(num)
|
||||
if label:
|
||||
registry.put("Формула", label, num)
|
||||
continue
|
||||
|
||||
if isinstance(r, RequiresNumbering):
|
||||
label = getattr(r, "unique_name", None)
|
||||
num = numberer.next_number(r.numbering_category, label)
|
||||
r.set_number(num)
|
||||
if label:
|
||||
registry.put(r.numbering_category, label, num)
|
||||
# Diagram +listing → also number Листинг with same label
|
||||
from .renderable.diagram import DiagramFigure
|
||||
if isinstance(r, DiagramFigure) and r.listing is not None:
|
||||
lnum = numberer.next_number("Листинг", label)
|
||||
r.set_listing_number(lnum)
|
||||
if label:
|
||||
registry.put("Листинг", label, lnum)
|
||||
# Images nested in paragraphs
|
||||
if isinstance(r, Paragraph):
|
||||
pass
|
||||
|
||||
# Images attached to paragraphs
|
||||
if isinstance(r, Paragraph):
|
||||
for img in getattr(r, "_images", []):
|
||||
label = getattr(img, "unique_name", None)
|
||||
num = numberer.next_number(img.numbering_category, label)
|
||||
img.set_number(num)
|
||||
if label:
|
||||
registry.put(img.numbering_category, label, num)
|
||||
|
||||
return registry
|
||||
|
||||
|
||||
def resolve_pending_in_renderables(renderables: list[Renderable], resolve_fn) -> None:
|
||||
"""Resolve @Type:label placeholders in paragraphs, lists, headings and table cells."""
|
||||
from .renderable.list import List as RList
|
||||
from .renderable.table import Table
|
||||
from .renderable.heading import Heading
|
||||
|
||||
def resolve_para(p):
|
||||
if hasattr(p, "resolve_pending_refs"):
|
||||
p.resolve_pending_refs(resolve_fn)
|
||||
|
||||
for r in renderables:
|
||||
if isinstance(r, Paragraph):
|
||||
resolve_para(r)
|
||||
elif isinstance(r, Heading):
|
||||
resolve_para(r)
|
||||
elif isinstance(r, RList):
|
||||
for p in getattr(r, "_paragraphs", []):
|
||||
resolve_para(p)
|
||||
elif isinstance(r, Table):
|
||||
for row in getattr(r, "_rows", []):
|
||||
for cell in row:
|
||||
for p in cell:
|
||||
resolve_para(p)
|
||||
|
||||
|
||||
# Patch Paragraph to resolve references at run-creation time via module-level registry
|
||||
_ACTIVE_REGISTRY: LabelRegistry | None = None
|
||||
|
||||
|
||||
def set_active_registry(registry: LabelRegistry | None):
|
||||
global _ACTIVE_REGISTRY
|
||||
_ACTIVE_REGISTRY = registry
|
||||
|
||||
|
||||
def resolve_reference(type_: str, name: str) -> str:
|
||||
if _ACTIVE_REGISTRY is None:
|
||||
return f"{type_}?"
|
||||
display = _ACTIVE_REGISTRY.get_display(type_, name)
|
||||
return display or f"{type_}?"
|
||||
Reference in New Issue
Block a user