@@ -821,6 +1361,30 @@ body.show-hidden .field.checkbox.is-hidden {{ display: flex !important; }}
root.querySelectorAll('details.group').forEach(d => d.open = false);
}});
+ root.addEventListener('click', (e) => {{
+ const btn = e.target.closest('.btn-copy');
+ if (!btn) return;
+ e.preventDefault();
+ const text = btn.getAttribute('data-copy') || '';
+ const done = () => {{
+ btn.classList.add('copied');
+ btn.setAttribute('title', 'Скопировано');
+ setTimeout(() => {{
+ btn.classList.remove('copied');
+ btn.setAttribute('title', 'Копировать DataPath · тип');
+ }}, 1200);
+ }};
+ if (navigator.clipboard && navigator.clipboard.writeText) {{
+ navigator.clipboard.writeText(text).then(done).catch(() => {{
+ const inp = btn.previousElementSibling;
+ if (inp && inp.select) {{ inp.select(); document.execCommand('copy'); done(); }}
+ }});
+ }} else {{
+ const inp = btn.previousElementSibling;
+ if (inp && inp.select) {{ inp.select(); document.execCommand('copy'); done(); }}
+ }}
+ }});
+
const filter = document.getElementById('filter');
const stats = document.getElementById('stats');
function applyFilter() {{
@@ -868,14 +1432,22 @@ body.show-hidden .field.checkbox.is-hidden {{ display: flex !important; }}
def export_form_prototype(
*,
- form_xml: Path,
+ form_xml: Path | None = None,
+ form: dict[str, Any] | None = None,
output_html: Path,
display_title: str | None = None,
+ include_elements_tree: bool = False,
+ include_text_maket: bool = False,
) -> Path:
- form = parse_form(form_xml)
+ if form is None:
+ if form_xml is None:
+ raise ValueError("нужен form_xml или form")
+ form = parse_form(form_xml)
if display_title:
form["title"] = display_title
- html_text = build_html(form)
+ tree = format_elements_tree(form) if include_elements_tree else None
+ maket = format_text_maket(form) if include_text_maket else None
+ html_text = build_html(form, elements_tree=tree, text_maket=maket)
output_html = Path(output_html)
output_html.parent.mkdir(parents=True, exist_ok=True)
output_html.write_text(html_text, encoding="utf-8")
diff --git a/form_xml_to_html.py b/form_xml_to_html.py
index cd8f1b6..943fc32 100644
--- a/form_xml_to_html.py
+++ b/form_xml_to_html.py
@@ -1,13 +1,14 @@
#!/usr/bin/env python3
-"""CLI: HTML-прототип управляемой формы 1С из Form.xml.
+"""CLI: HTML/текстовый прототип управляемой формы 1С из Form.xml.
-Версия: 0.1.0
+Версия: 0.2.3
Автор: Michael BAG
Примеры:
- python3 form_xml_to_html.py -b crm3-26 -o Справочник.Номенклатура -f ФормаЭлемента
- python3 form_xml_to_html.py -c configs/form_prototype.crm3-26.json -f ФормаЭлемента
- python3 form_xml_to_html.py -b crm3-26.rhana -o Общие -f РК_ФормаНастроекРана
+ python3 form_xml_to_html.py -b crm3-26 -o Справочник.Номенклатура -f ФормаЭлемента -p out/form-prototypes
+ python3 form_xml_to_html.py -b crm3-26.rhana -o Общие -f РК_ФормаНастроекРана -O /tmp/form.html -T
+ python3 form_xml_to_html.py -c configs/form_prototype.crm3-26.rhana.json -f РК_ФормаНастроекРана -T -M
+ python3 form_xml_to_html.py -b crm3-26.rhana -o Общие -f РК_ФормаНастроекРана -T -M -t -O /tmp/form.txt
"""
from __future__ import annotations
@@ -22,9 +23,11 @@ from export1c_help.form_prototype import (
__email__,
__version__,
build_output_path,
+ compose_text_report,
default_search_roots,
export_form_prototype,
load_settings_file,
+ parse_form,
resolve_form_xml,
)
from export1c_help.resolve import ResolveError
@@ -34,7 +37,7 @@ def _build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="form_xml_to_html",
description=(
- "Собрать HTML-прототип формы 1С по -b/-o/-f "
+ "HTML/текстовый прототип формы 1С по -b/-o/-f "
f"(v{__version__}, {__author__})"
),
)
@@ -58,14 +61,36 @@ def _build_parser() -> argparse.ArgumentParser:
"-p",
"--out-path",
help=(
- "Каталог вывода: /<конфа>/<тип>/<объект>/<форма>.html "
- "(по умолчанию out/form-prototypes)"
+ "Каталог вывода HTML: /<конфа>/<тип>/<объект>/<форма>.html"
),
)
+ parser.add_argument(
+ "-O",
+ "--out-file",
+ help="Файл вывода (если нет -p): HTML или текст при -t",
+ )
+ parser.add_argument(
+ "-T",
+ "--elements-tree",
+ action="store_true",
+ help="Дерево элементов (консоль / в HTML / в текстовый файл)",
+ )
+ parser.add_argument(
+ "-M",
+ "--text-maket",
+ action="store_true",
+ help="Текстовый макет «как в окне» (консоль / в HTML / в текстовый файл)",
+ )
+ parser.add_argument(
+ "-t",
+ "--text-only",
+ action="store_true",
+ help="Только текст в -O (без HTML); нужны -T и/или -M",
+ )
parser.add_argument(
"-c",
"--config",
- help="JSON с параметрами (base, object, out_path, search_roots), без формы",
+ help="JSON с параметрами (base, object, out_path, out_file, search_roots), без формы",
)
parser.add_argument(
"--version",
@@ -75,19 +100,16 @@ def _build_parser() -> argparse.ArgumentParser:
return parser
-def _resolve_out_path(raw: str | None, *, config_path: Path | None) -> Path:
- tool_root = Path(__file__).resolve().parent
- if not raw:
- return (tool_root / "out" / "form-prototypes").resolve()
+def _resolve_path(raw: str, *, config_path: Path | None) -> Path:
path = Path(raw).expanduser()
if path.is_absolute():
return path.resolve()
- # Относительный путь: от cwd; если запускали с -c — также пробуем от каталога config
cwd_path = (Path.cwd() / path).resolve()
if path.parts[0] in (".", "..") or cwd_path.parent.exists():
return cwd_path
if config_path is not None:
return (config_path.parent / path).resolve()
+ tool_root = Path(__file__).resolve().parent
return (tool_root / path).resolve()
@@ -107,12 +129,30 @@ def main(argv: list[str] | None = None) -> int:
base = args.base or settings.get("base")
object_ref = args.object or settings.get("object")
- out_raw = args.out_path or settings.get("out_path") or settings.get("out")
+ out_path_raw = args.out_path or settings.get("out_path")
+ out_file_raw = args.out_file or settings.get("out_file") or settings.get("out")
+
+ want_tree = bool(args.elements_tree)
+ want_maket = bool(args.text_maket)
+ text_only = bool(args.text_only)
if not base:
parser.error("нужен -b/--base или base в -c/--config")
if not object_ref:
parser.error("нужен -o/--object или object в -c/--config")
+ if text_only and not out_file_raw:
+ parser.error("при -t/--text-only нужен -O/--out-file")
+ if text_only and not (want_tree or want_maket):
+ parser.error("при -t/--text-only укажите -T и/или -M")
+ if text_only and out_path_raw:
+ parser.error("при -t/--text-only используйте -O, не -p")
+
+ want_html = not text_only and bool(out_path_raw or out_file_raw)
+ if not want_html and not (want_tree or want_maket):
+ parser.error(
+ "укажите вывод: -p/--out-path или -O/--out-file для HTML, "
+ "либо -T/-M для текста в консоль (или -t -O для текстового файла)"
+ )
search_roots = default_search_roots(config_path)
extra_roots = settings.get("search_roots") or []
@@ -131,28 +171,74 @@ def main(argv: list[str] | None = None) -> int:
form_name=args.form,
search_roots=search_roots,
)
- out_root = _resolve_out_path(
- str(out_raw) if out_raw else None, config_path=config_path
- )
- output_html = build_output_path(
- out_root,
- base_name=base_name,
- type_dir=type_dir,
- object_name=object_name,
- form_name=args.form.strip(),
- )
- export_form_prototype(
- form_xml=form_xml,
- output_html=output_html,
- display_title=f"{object_name} · {args.form.strip()}",
+ form = parse_form(form_xml)
+ display_title = f"{object_name} · {args.form.strip()}"
+ form["title"] = display_title
+
+ text_report = compose_text_report(
+ form, include_tree=want_tree, include_maket=want_maket
)
+
+ # Текст в консоль, если -O не задан (и есть что показывать)
+ if not out_file_raw and (want_tree or want_maket) and text_report:
+ sys.stdout.write(text_report)
+ if not text_report.endswith("\n"):
+ sys.stdout.write("\n")
+
+ written: Path | None = None
+
+ if text_only:
+ out_file = _resolve_path(str(out_file_raw), config_path=config_path)
+ out_file.parent.mkdir(parents=True, exist_ok=True)
+ out_file.write_text(text_report, encoding="utf-8")
+ written = out_file
+ elif want_html:
+ if out_path_raw:
+ out_root = _resolve_path(str(out_path_raw), config_path=config_path)
+ output_html = build_output_path(
+ out_root,
+ base_name=base_name,
+ type_dir=type_dir,
+ object_name=object_name,
+ form_name=args.form.strip(),
+ )
+ else:
+ output_html = _resolve_path(str(out_file_raw), config_path=config_path)
+ # В HTML дерево/макет добавляются при -O (и при -p тоже, если флаги заданы)
+ # По ТЗ: при заданном -O — в HTML; без -O текст в консоль уже выше.
+ embed_tree = want_tree and bool(out_file_raw or out_path_raw)
+ embed_maket = want_maket and bool(out_file_raw or out_path_raw)
+ # Уточнение ТЗ: «Если -O не задан → консоль; иначе → html с деревом».
+ # При только -p без -O: HTML без обязательного встраивания; дерево уже в консоли.
+ if out_file_raw and not out_path_raw:
+ embed_tree = want_tree
+ embed_maket = want_maket
+ elif out_path_raw and not out_file_raw:
+ # -p без -O: HTML базовый; -T/-M уже ушли в консоль
+ embed_tree = False
+ embed_maket = False
+ else:
+ # и -p, и -O: HTML в структуру -p, в файл встраиваем при наличии флагов
+ embed_tree = want_tree
+ embed_maket = want_maket
+
+ export_form_prototype(
+ form=form,
+ output_html=output_html,
+ display_title=display_title,
+ include_elements_tree=embed_tree,
+ include_text_maket=embed_maket,
+ )
+ written = output_html
+
except ResolveError as exc:
print(f"error: {exc}", file=sys.stderr)
return 1
- print(f"OK: {output_html}")
- print(f"src: {form_xml}")
- print(f"form_xml_to_html {__version__} — {__author__}")
+ if written is not None:
+ print(f"OK: {written}", file=sys.stderr)
+ print(f"src: {form_xml}", file=sys.stderr)
+ print(f"form_xml_to_html {__version__} — {__author__}", file=sys.stderr)
return 0