149 lines
4.7 KiB
Python
149 lines
4.7 KiB
Python
"""DOCX core properties (File → Info) for generated reports."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
from datetime import datetime
|
|
from getpass import getuser
|
|
from typing import Any
|
|
|
|
CORE_PROP_MAX = 255
|
|
AUTHOR_SOURCES = ("os", "student", "custom")
|
|
DEFAULT_AUTHOR_SOURCE = "os"
|
|
DEFAULT_DOC_COMMENTS = "Создано при помощи md2gost (ТЗ МИРЭА)"
|
|
|
|
|
|
def os_user() -> str:
|
|
try:
|
|
return (getuser() or "").strip()
|
|
except Exception:
|
|
return ""
|
|
|
|
|
|
def clip_core(value: str | None) -> str:
|
|
text = "" if value is None else str(value)
|
|
if len(text) > CORE_PROP_MAX:
|
|
return text[:CORE_PROP_MAX]
|
|
return text
|
|
|
|
|
|
def _normalize_source(value: str | None) -> str:
|
|
src = (value or DEFAULT_AUTHOR_SOURCE).strip().lower()
|
|
return src if src in AUTHOR_SOURCES else DEFAULT_AUTHOR_SOURCE
|
|
|
|
|
|
@dataclass
|
|
class DocumentMetadata:
|
|
author_source: str = DEFAULT_AUTHOR_SOURCE
|
|
author: str = ""
|
|
last_modified_by: str = ""
|
|
title: str = ""
|
|
subject: str = ""
|
|
keywords: str = ""
|
|
comments: str = DEFAULT_DOC_COMMENTS
|
|
category: str = ""
|
|
|
|
def to_dict(self) -> dict[str, str]:
|
|
return {
|
|
"author_source": _normalize_source(self.author_source),
|
|
"author": (self.author or "").strip(),
|
|
"last_modified_by": (self.last_modified_by or "").strip(),
|
|
"title": (self.title or "").strip(),
|
|
"subject": (self.subject or "").strip(),
|
|
"keywords": (self.keywords or "").strip(),
|
|
"comments": "" if self.comments is None else str(self.comments).strip(),
|
|
"category": (self.category or "").strip(),
|
|
}
|
|
|
|
@classmethod
|
|
def from_dict(cls, raw: object) -> DocumentMetadata:
|
|
if not isinstance(raw, dict):
|
|
return cls()
|
|
comments = raw.get("comments")
|
|
if comments is None:
|
|
comments_s = DEFAULT_DOC_COMMENTS
|
|
else:
|
|
comments_s = str(comments).strip()
|
|
return cls(
|
|
author_source=_normalize_source(str(raw.get("author_source") or "")),
|
|
author=str(raw.get("author") or "").strip(),
|
|
last_modified_by=str(raw.get("last_modified_by") or "").strip(),
|
|
title=str(raw.get("title") or "").strip(),
|
|
subject=str(raw.get("subject") or "").strip(),
|
|
keywords=str(raw.get("keywords") or "").strip(),
|
|
comments=comments_s,
|
|
category=str(raw.get("category") or "").strip(),
|
|
)
|
|
|
|
|
|
def _pick(override: str | None, fallback: str) -> str:
|
|
if override is not None:
|
|
return str(override)
|
|
return fallback
|
|
|
|
|
|
def resolve_document_metadata(
|
|
*,
|
|
stored: DocumentMetadata | None = None,
|
|
student: str | None = None,
|
|
author_source: str | None = None,
|
|
author: str | None = None,
|
|
last_modified_by: str | None = None,
|
|
title: str | None = None,
|
|
subject: str | None = None,
|
|
keywords: str | None = None,
|
|
comments: str | None = None,
|
|
category: str | None = None,
|
|
) -> DocumentMetadata:
|
|
"""Merge CLI/request overrides over the saved profile into concrete core properties."""
|
|
base = stored or DocumentMetadata()
|
|
src = _normalize_source(author_source or base.author_source)
|
|
|
|
if author is not None:
|
|
resolved_author = str(author).strip()
|
|
elif src == "custom":
|
|
resolved_author = (base.author or "").strip()
|
|
elif src == "student":
|
|
resolved_author = (student or "").strip()
|
|
else:
|
|
resolved_author = ""
|
|
if not resolved_author:
|
|
resolved_author = os_user()
|
|
|
|
last_mod = _pick(last_modified_by, base.last_modified_by).strip()
|
|
if not last_mod:
|
|
last_mod = resolved_author
|
|
|
|
return DocumentMetadata(
|
|
author_source=src,
|
|
author=resolved_author,
|
|
last_modified_by=last_mod,
|
|
title=_pick(title, base.title).strip(),
|
|
subject=_pick(subject, base.subject).strip(),
|
|
keywords=_pick(keywords, base.keywords).strip(),
|
|
comments=_pick(comments, base.comments).strip(),
|
|
category=_pick(category, base.category).strip(),
|
|
)
|
|
|
|
|
|
def apply_document_metadata(
|
|
document: Any,
|
|
meta: DocumentMetadata,
|
|
*,
|
|
now: datetime | None = None,
|
|
) -> None:
|
|
stamp = now or datetime.utcnow()
|
|
if stamp.tzinfo is not None:
|
|
stamp = stamp.replace(tzinfo=None)
|
|
cp = document.core_properties
|
|
cp.author = clip_core(meta.author)
|
|
cp.last_modified_by = clip_core(meta.last_modified_by or meta.author)
|
|
cp.title = clip_core(meta.title)
|
|
cp.subject = clip_core(meta.subject)
|
|
cp.keywords = clip_core(meta.keywords)
|
|
cp.comments = clip_core(meta.comments)
|
|
cp.category = clip_core(meta.category)
|
|
cp.created = stamp
|
|
cp.modified = stamp
|
|
cp.revision = 1
|