Files
Igor20264 510f7e7adf
Python application / build (push) Waiting to run
v0.5.2
Что то сделал
2026-09-08 19:37:54 +03:00

168 lines
4.9 KiB
Python
Raw Permalink 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.
"""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