71 lines
2.4 KiB
Python
71 lines
2.4 KiB
Python
"""Section-scoped and continuous object numbering."""
|
||
|
||
from collections import defaultdict
|
||
|
||
|
||
APPENDIX_LETTERS = "АБВГДЕЖИКЛМНПРСТУФХЦШЩЭЮЯ" # without Ё З Й О Ч Ь Ы Ъ
|
||
|
||
|
||
class Numberer:
|
||
"""Assigns object numbers.
|
||
|
||
* ``section`` (MIREA): ``1.1``, ``2.3`` or ``Б.1`` — counters reset per section/appendix.
|
||
* ``continuous`` (PIS_custom): ``1``, ``2``, ``3`` — сквозная нумерация по всему документу.
|
||
"""
|
||
|
||
def __init__(self, mode: str = "section"):
|
||
if mode not in ("section", "continuous"):
|
||
raise ValueError(f"Unknown numbering mode: {mode!r}")
|
||
self._mode = mode
|
||
self._section: int = 0
|
||
self._appendix: str | None = None
|
||
self._counters: dict[str, int] = defaultdict(int)
|
||
self._labels: dict[tuple[str, str], str] = {} # (category, unique_name) -> number string
|
||
|
||
@property
|
||
def mode(self) -> str:
|
||
return self._mode
|
||
|
||
@property
|
||
def section(self) -> int:
|
||
return self._section
|
||
|
||
@property
|
||
def appendix(self) -> str | None:
|
||
return self._appendix
|
||
|
||
def enter_section(self, section_number: int) -> None:
|
||
"""Start a numbered chapter (Heading 1 numbered)."""
|
||
self._section = section_number
|
||
self._appendix = None
|
||
if self._mode != "continuous":
|
||
self._counters.clear()
|
||
|
||
def enter_appendix(self, letter: str) -> None:
|
||
"""Start an appendix (letter А, Б, …)."""
|
||
self._appendix = letter
|
||
if self._mode != "continuous":
|
||
self._counters.clear()
|
||
|
||
def next_number(self, category: str, unique_name: str | None = None) -> str:
|
||
"""Allocate next number for category; optionally bind a label."""
|
||
self._counters[category] += 1
|
||
n = self._counters[category]
|
||
if self._mode == "continuous":
|
||
number = str(n)
|
||
elif self._appendix:
|
||
number = f"{self._appendix}.{n}"
|
||
elif self._section:
|
||
number = f"{self._section}.{n}"
|
||
else:
|
||
number = str(n)
|
||
if unique_name:
|
||
self._labels[(category, unique_name)] = number
|
||
return number
|
||
|
||
def resolve(self, category: str, unique_name: str) -> str | None:
|
||
return self._labels.get((category, unique_name))
|
||
|
||
def register_label(self, category: str, unique_name: str, number: str) -> None:
|
||
self._labels[(category, unique_name)] = number
|