v0.5.2
Python application / build (push) Waiting to run

Что то сделал
This commit is contained in:
Igor20264
2026-09-08 19:37:54 +03:00
parent 1a5b35eb54
commit 510f7e7adf
90 changed files with 11720 additions and 5547 deletions
+167
View File
@@ -0,0 +1,167 @@
"""Classify DOCX paragraphs/tables into IR kinds (style first, then heuristics)."""
from __future__ import annotations
import re
from md2gost.renderable.toc import SPECIAL_TITLES
from .captions import parse_any_caption
CAPTION_STYLE_KINDS = {
"Caption Figure": "figure",
"Название таблицы": "table",
"Caption Table": "table",
"Caption Listing": "listing",
"Caption": None, # need text heuristic
}
HEADING_STYLE_RE = re.compile(r"^(?:Heading|Заголовок)\s*(\d+)$", re.IGNORECASE)
TOC_STYLE_RE = re.compile(r"^toc\s*\d+$", re.IGNORECASE)
SPECIAL_UPPER = {t.upper() for t in SPECIAL_TITLES}
APPENDIX_RE = re.compile(
r"^ПРИЛОЖЕНИЕ\s+([А-ЯA-ZЁ])(?:\s+(.+))?$",
re.IGNORECASE,
)
NUMBERED_HEADING_RE = re.compile(r"^(\d+(?:\.\d+)*)\.?\s+(.+)$")
PRACTICE_RE = re.compile(r"^Практическая\s+работа\b", re.IGNORECASE)
LIST_MARKER_RE = re.compile(r"^(?:[–—\-•▪]|(\d+)\.)\s*\t?\s*(.*)$")
BIBLIO_LINE_RE = re.compile(r"^\[(\d+(?:\.\d+)?)\]\s*[:.]?\s*(.+)$")
BODY_START_TITLES = SPECIAL_UPPER | {"СОДЕРЖАНИЕ"}
def style_name(paragraph) -> str:
try:
if paragraph.style is None:
return ""
return paragraph.style.name or ""
except Exception:
return ""
def is_toc_style(name: str) -> bool:
return bool(TOC_STYLE_RE.match((name or "").strip()))
def heading_level_from_style(name: str) -> int | None:
m = HEADING_STYLE_RE.match((name or "").strip())
if not m:
return None
try:
level = int(m.group(1))
except ValueError:
return None
return level if 1 <= level <= 9 else None
def is_code_style(name: str) -> bool:
n = (name or "").strip().lower()
return n == "code" or n.startswith("code ")
def is_bibliography_style(name: str) -> bool:
n = (name or "").strip().lower()
return n in ("bibliography", "bibliography heading")
def is_caption_style(name: str) -> bool:
return (name or "").strip() in CAPTION_STYLE_KINDS
def caption_kind_from_style(name: str) -> str | None:
return CAPTION_STYLE_KINDS.get((name or "").strip())
def is_special_title(text: str) -> bool:
t = (text or "").strip().upper()
if t in SPECIAL_UPPER:
return True
# allow «СПИСОК ИСПОЛЬЗОВАННЫХ ИСТОЧНИКОВ» variants already in set
if t.startswith("СПИСОК ИСПОЛЬЗ"):
return True
return False
def is_body_start_heading(text: str, style: str = "") -> bool:
"""True if this heading marks the start of report body (skip title pages before it)."""
t = (text or "").strip()
if not t:
return False
if heading_level_from_style(style) or style.lower().startswith("heading"):
upper = t.upper()
if upper.lstrip("* ").upper() in BODY_START_TITLES or is_special_title(t):
return True
if PRACTICE_RE.match(t):
return True
if NUMBERED_HEADING_RE.match(t):
return True
if APPENDIX_RE.match(t):
return True
# Any Heading 1 after front matter
if heading_level_from_style(style) == 1:
return True
# Heuristic without style: special titles alone
if is_special_title(t) or PRACTICE_RE.match(t):
return True
return False
def classify_heading_text(text: str, style_level: int | None) -> tuple[int, str, bool]:
"""
Return (level, title_text, numbered).
numbered=False → emit as # *TITLE
"""
raw = (text or "").strip()
level = style_level or 1
app = APPENDIX_RE.match(raw)
if app:
letter = app.group(1).upper()
name = (app.group(2) or "").strip()
title = f"Приложение {letter}" + (f" {name}" if name else "")
return 2, title, True
if is_special_title(raw):
return 1, raw.upper(), False
m = NUMBERED_HEADING_RE.match(raw)
if m:
# keep digits in text for manual numbering dialect
return level, raw, True
if PRACTICE_RE.match(raw):
return 1, raw, True
return level, raw, True
def look_like_list_item(text: str) -> tuple[bool, bool, str] | None:
"""
Detect md2gost-style list lines (\\t / 1.\\t) or plain markers.
Returns (ordered, marker_ok, body) or None.
"""
t = (text or "").strip()
if not t:
return None
if t[0] in ("", "", "-", "", "") and (len(t) == 1 or t[1] in "\t "):
body = t[1:].lstrip("\t ").strip()
return False, True, body
m = re.match(r"^(\d+)\.\s*\t?\s*(.+)$", t)
if m:
return True, True, m.group(2).strip()
return None
def parse_biblio_line(text: str) -> tuple[str, str] | None:
m = BIBLIO_LINE_RE.match((text or "").strip())
if not m:
return None
return m.group(1), m.group(2).strip()
def paragraph_has_numpr(paragraph) -> bool:
pPr = paragraph._element.pPr
if pPr is None:
return False
return pPr.numPr is not None