added TipTap
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
from typing import Any
|
||||
|
||||
from app.services.espd.html_util import html_to_plain
|
||||
@@ -34,14 +35,42 @@ def resolve_doc_html_map(
|
||||
return out
|
||||
|
||||
|
||||
def _section_plain(html: str, *title_parts: str) -> str:
|
||||
"""Extract plain text of first h2 whose title contains any of title_parts."""
|
||||
if not html:
|
||||
return ""
|
||||
parts = re.split(r"(?i)(<h2\b[^>]*>.*?</h2>)", html)
|
||||
i = 1
|
||||
while i < len(parts):
|
||||
heading = re.sub(r"<[^>]+>", "", parts[i])
|
||||
body = parts[i + 1] if i + 1 < len(parts) else ""
|
||||
lower = heading.lower()
|
||||
if any(p.lower() in lower for p in title_parts):
|
||||
return html_to_plain(body)
|
||||
i += 2
|
||||
return ""
|
||||
|
||||
|
||||
def content_extra_context(doc_type: DocumentType, html_or_plain: str) -> dict[str, str]:
|
||||
plain = html_or_plain or ""
|
||||
if "<" in plain:
|
||||
plain = html_to_plain(plain)
|
||||
extra = {"DOC_CONTENT": plain}
|
||||
html = html_or_plain or ""
|
||||
plain = html_to_plain(html) if "<" in html else html
|
||||
extra: dict[str, str] = {"DOC_CONTENT": plain}
|
||||
key = CONTENT_CONTEXT_KEYS.get(doc_type)
|
||||
if key:
|
||||
extra[key] = plain
|
||||
|
||||
# Sectional placeholders used by existing rp.docx / pmi.docx templates
|
||||
if doc_type == DocumentType.RP:
|
||||
purpose = _section_plain(html, "назначение")
|
||||
conditions = _section_plain(html, "условия выполнения", "условия")
|
||||
extra["PURPOSE"] = purpose or plain
|
||||
extra["EXECUTION_CONDITIONS"] = conditions or ""
|
||||
elif doc_type == DocumentType.PMI:
|
||||
obj = _section_plain(html, "объект")
|
||||
methods = _section_plain(html, "методы")
|
||||
extra["TEST_OBJECT"] = obj or plain
|
||||
extra["TEST_METHODS"] = methods or ""
|
||||
|
||||
return extra
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
"""Default HTML skeletons for ESPD rich-text editors (visible outlines)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from app.services.espd.models import DocumentType
|
||||
|
||||
# Placeholder paragraph shown under each heading so the outline is obvious in TipTap.
|
||||
_HINT = "<p><em>Заполните раздел…</em></p>"
|
||||
|
||||
|
||||
def _h2(title: str) -> str:
|
||||
return f"<h2>{title}</h2>{_HINT}"
|
||||
|
||||
|
||||
def _h3(title: str) -> str:
|
||||
return f"<h3>{title}</h3>{_HINT}"
|
||||
|
||||
|
||||
SKELETONS: dict[str, str] = {
|
||||
DocumentType.RO.value: "\n".join(
|
||||
[
|
||||
_h2("1. Назначение программы"),
|
||||
_h3("1.1. Функциональное назначение"),
|
||||
_h3("1.2. Эксплуатационное назначение"),
|
||||
_h2("2. Условия выполнения программы"),
|
||||
_h3("2.1. Требования к техническим средствам"),
|
||||
_h3("2.2. Требования к программным средствам"),
|
||||
_h3("2.3. Требования к персоналу"),
|
||||
_h2("3. Установка программы"),
|
||||
_h3("3.1. Состав дистрибутива"),
|
||||
_h3("3.2. Порядок установки"),
|
||||
_h2("4. Работа с программой"),
|
||||
_h3("4.1. Запуск программы"),
|
||||
_h3("4.2. Основные операции"),
|
||||
_h3("4.3. Завершение работы"),
|
||||
_h2("5. Сообщения оператору"),
|
||||
]
|
||||
),
|
||||
DocumentType.RP.value: "\n".join(
|
||||
[
|
||||
_h2("1. Назначение программы"),
|
||||
_h2("2. Условия выполнения программы"),
|
||||
_h2("3. Характеристики программы"),
|
||||
_h2("4. Обращение к программе"),
|
||||
_h2("5. Входные и выходные данные"),
|
||||
_h2("6. Сообщения"),
|
||||
]
|
||||
),
|
||||
DocumentType.RSP.value: "\n".join(
|
||||
[
|
||||
_h2("1. Общие сведения о программе"),
|
||||
_h2("2. Структура программы"),
|
||||
_h2("3. Настройка программы"),
|
||||
_h2("4. Проверка программы"),
|
||||
_h2("5. Дополнительные возможности"),
|
||||
_h2("6. Сообщения системному программисту"),
|
||||
]
|
||||
),
|
||||
DocumentType.PMI.value: "\n".join(
|
||||
[
|
||||
_h2("1. Объект испытаний"),
|
||||
_h2("2. Цель испытаний"),
|
||||
_h2("3. Требования к программе"),
|
||||
_h2("4. Требования к программной документации"),
|
||||
_h2("5. Средства и порядок испытаний"),
|
||||
_h2("6. Методы испытаний"),
|
||||
]
|
||||
),
|
||||
DocumentType.TEXT.value: "\n".join(
|
||||
[
|
||||
_h2("1. Общие сведения"),
|
||||
"<p><em>Краткое описание состава текста программы.</em></p>",
|
||||
_h2("2. Текст программы"),
|
||||
"<pre><code>// Вставьте листинг или фрагменты исходного кода…\n</code></pre>",
|
||||
_h2("3. Примечания"),
|
||||
]
|
||||
),
|
||||
DocumentType.IMP.value: "\n".join(
|
||||
[
|
||||
_h2("1. Перечень исполняемых модулей"),
|
||||
"<p><em>Таблица файлов формируется из загруженного ZIP при генерации DOCX. "
|
||||
"Здесь можно указать пояснения к составу поставки.</em></p>",
|
||||
_h2("2. Инструкция по установке / размещению"),
|
||||
_h2("3. Проверка контрольных сумм"),
|
||||
"<p><em>Контрольная сумма ZIP подставляется автоматически при генерации.</em></p>",
|
||||
]
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def get_skeleton(doc_type: str | DocumentType) -> str:
|
||||
key = doc_type.value if isinstance(doc_type, DocumentType) else str(doc_type)
|
||||
return SKELETONS.get(key, "")
|
||||
|
||||
|
||||
def all_skeletons() -> dict[str, str]:
|
||||
return dict(SKELETONS)
|
||||
|
||||
|
||||
def is_blank_html(html: str | None) -> bool:
|
||||
"""True if editor content is empty / only whitespace or empty tags."""
|
||||
if not html or not str(html).strip():
|
||||
return True
|
||||
import re
|
||||
from html import unescape
|
||||
|
||||
text = re.sub(r"<[^>]+>", " ", str(html))
|
||||
text = unescape(text)
|
||||
text = re.sub(r"\s+", " ", text).strip()
|
||||
return text == ""
|
||||
Reference in New Issue
Block a user