mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-30 08:26:55 +03:00
feat(form-compile): add --from-object mode for auto form generation from metadata
Read 1C object XML (Document/Catalog), apply ERP preset, generate Form.xml automatically. Supports Item/List/Choice/Folder purposes with auto-resolve of object path and purpose from OutputPath convention. Also extends DSL with DynamicList Settings, Table choiceMode/initialTreeView/enableDrag. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3c48451704
commit
076bbcb9d5
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
name: form-compile
|
name: form-compile
|
||||||
description: Компиляция управляемой формы 1С из компактного JSON-определения. Используй когда нужно создать форму с нуля по описанию элементов
|
description: Компиляция управляемой формы 1С из JSON-определения или из метаданных объекта. Используй когда нужно создать форму с нуля по описанию элементов или сгенерировать типовую форму
|
||||||
argument-hint: <JsonPath> <OutputPath>
|
argument-hint: <JsonPath> <OutputPath> | -FromObject <OutputPath>
|
||||||
allowed-tools:
|
allowed-tools:
|
||||||
- Bash
|
- Bash
|
||||||
- Read
|
- Read
|
||||||
@@ -9,29 +9,30 @@ allowed-tools:
|
|||||||
- Glob
|
- Glob
|
||||||
---
|
---
|
||||||
|
|
||||||
# /form-compile — Генерация Form.xml из JSON DSL
|
# /form-compile — Генерация Form.xml
|
||||||
|
|
||||||
Принимает компактное JSON-определение формы (20–50 строк) и генерирует полный корректный Form.xml (100–500+ строк) с namespace-декларациями, автогенерированными companion-элементами, последовательными ID.
|
Два режима:
|
||||||
|
1. **JSON DSL** — из JSON-определения формы
|
||||||
|
2. **From object** (`-FromObject`) — автоматически из метаданных объекта 1С по пресету ERP
|
||||||
|
|
||||||
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника: архетипы, конвенции именования, продвинутые паттерны. Для простых форм (1–3 поля, пользователь описал что нужно) — не нужно.
|
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника. Для простых форм (1–3 поля) — не нужно.
|
||||||
|
|
||||||
## Использование
|
|
||||||
|
|
||||||
```
|
|
||||||
/form-compile <JsonPath> <OutputPath>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Параметры
|
## Параметры
|
||||||
|
|
||||||
| Параметр | Обязательный | Описание |
|
| Параметр | Обязательный | Описание |
|
||||||
|------------|:------------:|-----------------------------------|
|
|------------|:------------:|---------------------------------|
|
||||||
| JsonPath | да | Путь к JSON-определению формы |
|
| JsonPath | режим 1 | Путь к JSON-определению формы |
|
||||||
| OutputPath | да | Путь к выходному файлу Form.xml |
|
| OutputPath | да | Путь к выходному Form.xml |
|
||||||
|
| FromObject | режим 2 | Флаг (без значения) — генерация по метаданным объекта |
|
||||||
|
|
||||||
## Команда
|
## Команда
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -JsonPath "<json>" -OutputPath "<xml>"
|
# Режим JSON DSL
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -JsonPath "<json>" -OutputPath "<Form.xml>"
|
||||||
|
|
||||||
|
# Режим from-object (объект и purpose выводятся из OutputPath; Document и Catalog)
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -FromObject -OutputPath "<.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml>"
|
||||||
```
|
```
|
||||||
|
|
||||||
## JSON DSL — справка
|
## JSON DSL — справка
|
||||||
@@ -167,6 +168,12 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
|||||||
| `footer: true` | Показать подвал |
|
| `footer: true` | Показать подвал |
|
||||||
| `commandBarLocation` | `"None"`, `"Top"`, `"Auto"` |
|
| `commandBarLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||||
| `searchStringLocation` | `"None"`, `"Top"`, `"Auto"` |
|
| `searchStringLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||||
|
| `choiceMode: true` | Режим выбора (для форм выбора) |
|
||||||
|
| `initialTreeView` | `"ExpandTopLevel"` и др. (иерархические списки) |
|
||||||
|
| `enableDrag: true` | Разрешить перетаскивание |
|
||||||
|
| `enableStartDrag: true` | Разрешить начало перетаскивания |
|
||||||
|
| `rowPictureDataPath` | Путь к картинке строки (напр. `"Список.DefaultPicture"`) |
|
||||||
|
| `tableAutofill: false` | Управление Autofill внутреннего AutoCommandBar |
|
||||||
|
|
||||||
### Страницы (pages + page)
|
### Страницы (pages + page)
|
||||||
|
|
||||||
@@ -221,6 +228,9 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{ "name": "Объект", "type": "DataProcessorObject.Загрузка", "main": true }
|
{ "name": "Объект", "type": "DataProcessorObject.Загрузка", "main": true }
|
||||||
|
{ "name": "Список", "type": "DynamicList", "main": true, "settings": {
|
||||||
|
"mainTable": "Catalog.Номенклатура", "dynamicDataRead": true
|
||||||
|
}}
|
||||||
{ "name": "Итого", "type": "decimal(15,2)" }
|
{ "name": "Итого", "type": "decimal(15,2)" }
|
||||||
{ "name": "Таблица", "type": "ValueTable", "columns": [
|
{ "name": "Таблица", "type": "ValueTable", "columns": [
|
||||||
{ "name": "Номенклатура", "type": "CatalogRef.Номенклатура" },
|
{ "name": "Номенклатура", "type": "CatalogRef.Номенклатура" },
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"name": "erp-standard",
|
||||||
|
"description": "ERP 8.3.24 standard form layout",
|
||||||
|
|
||||||
|
"document.item": {
|
||||||
|
"header": {
|
||||||
|
"position": "insidePage",
|
||||||
|
"layout": "2col",
|
||||||
|
"distribute": "even",
|
||||||
|
"dateTitle": "от"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"fields": ["Комментарий"],
|
||||||
|
"position": "insidePage"
|
||||||
|
},
|
||||||
|
"tabularSections": {
|
||||||
|
"container": "pages",
|
||||||
|
"exclude": ["ДополнительныеРеквизиты"],
|
||||||
|
"lineNumber": true
|
||||||
|
},
|
||||||
|
"additional": {
|
||||||
|
"position": "page",
|
||||||
|
"layout": "2col",
|
||||||
|
"bspGroup": true
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"autoTitle": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
"catalog.item": {
|
||||||
|
"codeDescription": {
|
||||||
|
"layout": "horizontal",
|
||||||
|
"order": "descriptionFirst"
|
||||||
|
},
|
||||||
|
"parent": {
|
||||||
|
"title": "Входит в группу",
|
||||||
|
"position": "afterCodeDescription"
|
||||||
|
},
|
||||||
|
"tabularSections": {
|
||||||
|
"exclude": ["ДополнительныеРеквизиты", "Представления"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,764 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# form-compile v1.4 — Compile 1C managed form from JSON
|
# form-compile v1.5 — Compile 1C managed form from JSON or object metadata
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
|
import copy
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# FROM-OBJECT MODE: functions for metadata parsing, presets, DSL generation
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
NS = {
|
||||||
|
'md': 'http://v8.1c.ru/8.3/MDClasses',
|
||||||
|
'xr': 'http://v8.1c.ru/8.3/xcf/readable',
|
||||||
|
'v8': 'http://v8.1c.ru/8.1/data/core',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _et_find(node, path):
|
||||||
|
"""Find with namespace map."""
|
||||||
|
return node.find(path, NS)
|
||||||
|
|
||||||
|
|
||||||
|
def _et_findall(node, path):
|
||||||
|
"""Findall with namespace map."""
|
||||||
|
return node.findall(path, NS)
|
||||||
|
|
||||||
|
|
||||||
|
def _et_text(node, path, default=''):
|
||||||
|
"""Get text of a sub-element, or default."""
|
||||||
|
el = node.find(path, NS)
|
||||||
|
return el.text if el is not None and el.text else default
|
||||||
|
|
||||||
|
|
||||||
|
def parse_object_meta(object_path):
|
||||||
|
"""Parse 1C metadata XML and return dict with Type, Name, Synonym, Attributes, TabularSections, etc."""
|
||||||
|
tree = ET.parse(object_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
# Detect object type from root child
|
||||||
|
meta_root = _et_find(root, '.')
|
||||||
|
# Root is MetaDataObject; first child is the type node
|
||||||
|
type_node = None
|
||||||
|
for child in root:
|
||||||
|
type_node = child
|
||||||
|
break
|
||||||
|
if type_node is None:
|
||||||
|
print("Not a 1C metadata XML: " + object_path, file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Extract local name (strip namespace)
|
||||||
|
obj_type = type_node.tag.split('}')[-1] if '}' in type_node.tag else type_node.tag
|
||||||
|
|
||||||
|
props_node = _et_find(type_node, 'md:Properties')
|
||||||
|
child_objs = _et_find(type_node, 'md:ChildObjects')
|
||||||
|
|
||||||
|
# Name
|
||||||
|
obj_name = _et_text(props_node, 'md:Name')
|
||||||
|
|
||||||
|
# Synonym (Russian)
|
||||||
|
synonym = obj_name
|
||||||
|
syn_node = _et_find(props_node, "md:Synonym/v8:item[v8:lang='ru']/v8:content")
|
||||||
|
if syn_node is not None and syn_node.text:
|
||||||
|
synonym = syn_node.text
|
||||||
|
|
||||||
|
def extract_type(type_parent):
|
||||||
|
"""Extract type string from md:Type element."""
|
||||||
|
if type_parent is None:
|
||||||
|
return 'string'
|
||||||
|
types = []
|
||||||
|
for t in _et_findall(type_parent, 'v8:Type'):
|
||||||
|
if t.text:
|
||||||
|
types.append(t.text)
|
||||||
|
if not types:
|
||||||
|
return 'string'
|
||||||
|
return ' | '.join(types)
|
||||||
|
|
||||||
|
def is_ref_type(t):
|
||||||
|
return bool(re.search(r'Ref\.', t) or re.search(r'\u0441\u0441\u044b\u043b\u043a\u0430\.', t))
|
||||||
|
|
||||||
|
def extract_attrs(parent_node):
|
||||||
|
"""Extract attribute list from ChildObjects."""
|
||||||
|
result = []
|
||||||
|
if parent_node is None:
|
||||||
|
return result
|
||||||
|
for attr_node in _et_findall(parent_node, 'md:Attribute'):
|
||||||
|
ap = _et_find(attr_node, 'md:Properties')
|
||||||
|
a_name = _et_text(ap, 'md:Name')
|
||||||
|
a_syn_node = _et_find(ap, "md:Synonym/v8:item[v8:lang='ru']/v8:content")
|
||||||
|
a_syn = a_syn_node.text if a_syn_node is not None and a_syn_node.text else a_name
|
||||||
|
a_type_node = _et_find(ap, 'md:Type')
|
||||||
|
a_type = extract_type(a_type_node)
|
||||||
|
result.append({
|
||||||
|
'Name': a_name,
|
||||||
|
'Synonym': a_syn,
|
||||||
|
'Type': a_type,
|
||||||
|
'IsRef': is_ref_type(a_type),
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Attributes
|
||||||
|
attributes = extract_attrs(child_objs)
|
||||||
|
|
||||||
|
# Tabular sections
|
||||||
|
tabular_sections = []
|
||||||
|
if child_objs is not None:
|
||||||
|
for ts_node in _et_findall(child_objs, 'md:TabularSection'):
|
||||||
|
tsp = _et_find(ts_node, 'md:Properties')
|
||||||
|
ts_name = _et_text(tsp, 'md:Name')
|
||||||
|
ts_syn_node = _et_find(tsp, "md:Synonym/v8:item[v8:lang='ru']/v8:content")
|
||||||
|
ts_syn = ts_syn_node.text if ts_syn_node is not None and ts_syn_node.text else ts_name
|
||||||
|
ts_co = _et_find(ts_node, 'md:ChildObjects')
|
||||||
|
ts_cols = extract_attrs(ts_co)
|
||||||
|
tabular_sections.append({
|
||||||
|
'Name': ts_name,
|
||||||
|
'Synonym': ts_syn,
|
||||||
|
'Columns': ts_cols,
|
||||||
|
})
|
||||||
|
|
||||||
|
meta = {
|
||||||
|
'Type': obj_type,
|
||||||
|
'Name': obj_name,
|
||||||
|
'Synonym': synonym,
|
||||||
|
'Attributes': attributes,
|
||||||
|
'TabularSections': tabular_sections,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Type-specific properties
|
||||||
|
if obj_type == 'Document':
|
||||||
|
nt_node = _et_find(props_node, 'md:NumberType')
|
||||||
|
meta['NumberType'] = nt_node.text if nt_node is not None and nt_node.text else 'String'
|
||||||
|
elif obj_type == 'Catalog':
|
||||||
|
cl_node = _et_find(props_node, 'md:CodeLength')
|
||||||
|
meta['CodeLength'] = int(cl_node.text) if cl_node is not None and cl_node.text else 0
|
||||||
|
dl_node = _et_find(props_node, 'md:DescriptionLength')
|
||||||
|
meta['DescriptionLength'] = int(dl_node.text) if dl_node is not None and dl_node.text else 0
|
||||||
|
hi_node = _et_find(props_node, 'md:Hierarchical')
|
||||||
|
meta['Hierarchical'] = (hi_node is not None and hi_node.text == 'true')
|
||||||
|
ht_node = _et_find(props_node, 'md:HierarchyType')
|
||||||
|
meta['HierarchyType'] = ht_node.text if ht_node is not None and ht_node.text else 'HierarchyFoldersAndItems'
|
||||||
|
owners = []
|
||||||
|
for ow in _et_findall(props_node, 'md:Owners/xr:Item'):
|
||||||
|
if ow.text:
|
||||||
|
owners.append(ow.text)
|
||||||
|
meta['Owners'] = owners
|
||||||
|
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
|
def _deep_merge(base, overlay):
|
||||||
|
"""Deep merge two dicts. overlay wins on conflicts."""
|
||||||
|
if not overlay:
|
||||||
|
return base
|
||||||
|
if not base:
|
||||||
|
return overlay
|
||||||
|
result = {}
|
||||||
|
for k in base:
|
||||||
|
result[k] = base[k]
|
||||||
|
for k in overlay:
|
||||||
|
if k in result and isinstance(result[k], dict) and isinstance(overlay[k], dict):
|
||||||
|
result[k] = _deep_merge(result[k], overlay[k])
|
||||||
|
else:
|
||||||
|
result[k] = overlay[k]
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def load_preset(preset_name, script_dir, out_path_resolved):
|
||||||
|
"""Load preset: hardcoded defaults -> built-in JSON -> project-level JSON, with deep merge."""
|
||||||
|
defaults = {
|
||||||
|
'document.item': {
|
||||||
|
'header': {'position': 'insidePage', 'layout': '2col', 'distribute': 'even', 'dateTitle': '\u043e\u0442'},
|
||||||
|
'footer': {'fields': ['\u041a\u043e\u043c\u043c\u0435\u043d\u0442\u0430\u0440\u0438\u0439'], 'position': 'insidePage'},
|
||||||
|
'tabularSections': {'container': 'pages', 'exclude': ['\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b'], 'lineNumber': True},
|
||||||
|
'additional': {'position': 'page', 'layout': '2col', 'bspGroup': True},
|
||||||
|
'fieldDefaults': {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}},
|
||||||
|
'commandBar': 'auto',
|
||||||
|
'properties': {'autoTitle': False},
|
||||||
|
},
|
||||||
|
'document.list': {
|
||||||
|
'columns': 'all', 'columnType': 'labelField', 'hiddenRef': True,
|
||||||
|
'tableCommandBar': 'none', 'commandBar': 'auto',
|
||||||
|
'properties': {},
|
||||||
|
},
|
||||||
|
'document.choice': {
|
||||||
|
'basedOn': 'document.list',
|
||||||
|
'properties': {'windowOpeningMode': 'LockOwnerWindow'},
|
||||||
|
},
|
||||||
|
'catalog.item': {
|
||||||
|
'header': {'layout': '1col', 'distribute': 'left'},
|
||||||
|
'codeDescription': {'layout': 'horizontal', 'order': 'descriptionFirst'},
|
||||||
|
'parent': {'title': '\u0412\u0445\u043e\u0434\u0438\u0442 \u0432 \u0433\u0440\u0443\u043f\u043f\u0443', 'position': 'afterCodeDescription'},
|
||||||
|
'owner': {'readOnly': True, 'position': 'first'},
|
||||||
|
'tabularSections': {'container': 'inline', 'exclude': ['\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b', '\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f'], 'lineNumber': True},
|
||||||
|
'footer': {'fields': [], 'position': 'none'},
|
||||||
|
'additional': {'position': 'none', 'bspGroup': True},
|
||||||
|
'fieldDefaults': {'ref': {'choiceButton': True}, 'boolean': {'element': 'check'}},
|
||||||
|
'commandBar': 'auto',
|
||||||
|
'properties': {},
|
||||||
|
},
|
||||||
|
'catalog.folder': {
|
||||||
|
'parent': {'title': '\u0412\u0445\u043e\u0434\u0438\u0442 \u0432 \u0433\u0440\u0443\u043f\u043f\u0443'},
|
||||||
|
'properties': {'windowOpeningMode': 'LockOwnerWindow'},
|
||||||
|
},
|
||||||
|
'catalog.list': {
|
||||||
|
'columns': 'all', 'columnType': 'labelField', 'hiddenRef': True,
|
||||||
|
'tableCommandBar': 'none', 'commandBar': 'auto',
|
||||||
|
'properties': {},
|
||||||
|
},
|
||||||
|
'catalog.choice': {
|
||||||
|
'basedOn': 'catalog.list', 'choiceMode': True,
|
||||||
|
'properties': {'windowOpeningMode': 'LockOwnerWindow'},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
# Try built-in preset
|
||||||
|
preset_dir = os.path.join(os.path.dirname(script_dir), 'presets')
|
||||||
|
built_in_path = os.path.join(preset_dir, f'{preset_name}.json')
|
||||||
|
if os.path.isfile(built_in_path):
|
||||||
|
with open(built_in_path, 'r', encoding='utf-8-sig') as f:
|
||||||
|
preset_data = json.load(f)
|
||||||
|
for k in list(preset_data.keys()):
|
||||||
|
defaults[k] = _deep_merge(defaults.get(k), preset_data[k])
|
||||||
|
|
||||||
|
# Try project-level preset (scan up from output path)
|
||||||
|
scan_dir = os.path.dirname(out_path_resolved)
|
||||||
|
while scan_dir:
|
||||||
|
proj_preset = os.path.join(scan_dir, 'presets', 'skills', 'form', f'{preset_name}.json')
|
||||||
|
if os.path.isfile(proj_preset):
|
||||||
|
with open(proj_preset, 'r', encoding='utf-8-sig') as f:
|
||||||
|
proj_data = json.load(f)
|
||||||
|
for k in list(proj_data.keys()):
|
||||||
|
defaults[k] = _deep_merge(defaults.get(k), proj_data[k])
|
||||||
|
break
|
||||||
|
parent_dir = os.path.dirname(scan_dir)
|
||||||
|
if parent_dir == scan_dir:
|
||||||
|
break
|
||||||
|
scan_dir = parent_dir
|
||||||
|
|
||||||
|
# Resolve basedOn references
|
||||||
|
for k in list(defaults.keys()):
|
||||||
|
sect = defaults[k]
|
||||||
|
if isinstance(sect, dict) and 'basedOn' in sect:
|
||||||
|
base_name = sect['basedOn']
|
||||||
|
if base_name in defaults:
|
||||||
|
merged = _deep_merge(defaults[base_name], sect)
|
||||||
|
merged.pop('basedOn', None)
|
||||||
|
defaults[k] = merged
|
||||||
|
|
||||||
|
return defaults
|
||||||
|
|
||||||
|
|
||||||
|
def new_field_element(attr_name, data_path, attr_type, field_defaults, extra_props=None):
|
||||||
|
"""Build a field element DSL entry."""
|
||||||
|
is_ref = bool(re.search(r'Ref\.', attr_type))
|
||||||
|
is_bool = bool(re.match(r'^\s*xs:boolean\s*$', attr_type) or attr_type == 'boolean' or re.search(r'Boolean', attr_type))
|
||||||
|
|
||||||
|
el_type = 'input'
|
||||||
|
if is_bool and field_defaults and field_defaults.get('boolean') and field_defaults['boolean'].get('element') == 'check':
|
||||||
|
el_type = 'check'
|
||||||
|
|
||||||
|
el = OrderedDict()
|
||||||
|
el[el_type] = attr_name
|
||||||
|
el['path'] = data_path
|
||||||
|
|
||||||
|
# Apply ref defaults
|
||||||
|
if is_ref and field_defaults and field_defaults.get('ref'):
|
||||||
|
if field_defaults['ref'].get('choiceButton') is True:
|
||||||
|
el['choiceButton'] = True
|
||||||
|
|
||||||
|
# Extra props
|
||||||
|
if extra_props:
|
||||||
|
for k in extra_props:
|
||||||
|
el[k] = extra_props[k]
|
||||||
|
|
||||||
|
return el
|
||||||
|
|
||||||
|
|
||||||
|
# --- Catalog DSL generators ---
|
||||||
|
|
||||||
|
def generate_catalog_dsl(meta, preset_data, purpose):
|
||||||
|
purpose_key = f"catalog.{purpose.lower()}"
|
||||||
|
p = preset_data.get(purpose_key, {})
|
||||||
|
fd = p.get('fieldDefaults', {})
|
||||||
|
|
||||||
|
dispatch = {
|
||||||
|
'Folder': lambda: generate_catalog_folder_dsl(meta, p),
|
||||||
|
'List': lambda: generate_catalog_list_dsl(meta, p),
|
||||||
|
'Choice': lambda: generate_catalog_choice_dsl(meta, p, preset_data),
|
||||||
|
'Item': lambda: generate_catalog_item_dsl(meta, p, fd),
|
||||||
|
}
|
||||||
|
return dispatch[purpose]()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_catalog_folder_dsl(meta, p):
|
||||||
|
elements = []
|
||||||
|
# Code (if CodeLength > 0)
|
||||||
|
if meta.get('CodeLength', 0) > 0:
|
||||||
|
elements.append(OrderedDict([('input', '\u041a\u043e\u0434'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Code')]))
|
||||||
|
# Description
|
||||||
|
elements.append(OrderedDict([('input', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Description')]))
|
||||||
|
# Parent
|
||||||
|
parent_title = p.get('parent', {}).get('title')
|
||||||
|
parent_el = OrderedDict([('input', '\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Parent')])
|
||||||
|
if parent_title:
|
||||||
|
parent_el['title'] = parent_title
|
||||||
|
elements.append(parent_el)
|
||||||
|
|
||||||
|
props = OrderedDict([('windowOpeningMode', 'LockOwnerWindow')])
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
props[k] = p['properties'][k]
|
||||||
|
|
||||||
|
form_props = OrderedDict([('useForFoldersAndItems', 'Folders')])
|
||||||
|
for k in props:
|
||||||
|
form_props[k] = props[k]
|
||||||
|
|
||||||
|
return OrderedDict([
|
||||||
|
('title', meta['Synonym']),
|
||||||
|
('properties', form_props),
|
||||||
|
('elements', elements),
|
||||||
|
('attributes', [
|
||||||
|
OrderedDict([('name', '\u041e\u0431\u044a\u0435\u043a\u0442'), ('type', f"CatalogObject.{meta['Name']}"), ('main', True)])
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_catalog_list_dsl(meta, p):
|
||||||
|
columns = []
|
||||||
|
# Description always first
|
||||||
|
columns.append(OrderedDict([('labelField', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a.Description')]))
|
||||||
|
# Code if present
|
||||||
|
if meta.get('CodeLength', 0) > 0:
|
||||||
|
columns.append(OrderedDict([('labelField', '\u041a\u043e\u0434'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a.Code')]))
|
||||||
|
# Custom attributes
|
||||||
|
for attr in meta['Attributes']:
|
||||||
|
columns.append(OrderedDict([('labelField', attr['Name']), ('path', f"\u0421\u043f\u0438\u0441\u043e\u043a.{attr['Name']}")]))
|
||||||
|
# Hidden ref
|
||||||
|
if p.get('hiddenRef', True) is not False:
|
||||||
|
columns.append(OrderedDict([('labelField', '\u0421\u0441\u044b\u043b\u043a\u0430'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a.Ref'), ('visible', False)]))
|
||||||
|
|
||||||
|
table_el = OrderedDict([
|
||||||
|
('table', '\u0421\u043f\u0438\u0441\u043e\u043a'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a'),
|
||||||
|
('rowPictureDataPath', '\u0421\u043f\u0438\u0441\u043e\u043a.DefaultPicture'),
|
||||||
|
('commandBarLocation', 'None'),
|
||||||
|
('tableAutofill', False),
|
||||||
|
('columns', columns),
|
||||||
|
])
|
||||||
|
# Hierarchical properties
|
||||||
|
if meta.get('Hierarchical'):
|
||||||
|
table_el['initialTreeView'] = 'ExpandTopLevel'
|
||||||
|
table_el['enableStartDrag'] = True
|
||||||
|
table_el['enableDrag'] = True
|
||||||
|
|
||||||
|
form_props = OrderedDict()
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
form_props[k] = p['properties'][k]
|
||||||
|
|
||||||
|
return OrderedDict([
|
||||||
|
('title', meta['Synonym']),
|
||||||
|
('properties', form_props),
|
||||||
|
('elements', [table_el]),
|
||||||
|
('attributes', [
|
||||||
|
OrderedDict([
|
||||||
|
('name', '\u0421\u043f\u0438\u0441\u043e\u043a'), ('type', 'DynamicList'), ('main', True),
|
||||||
|
('settings', OrderedDict([('mainTable', f"Catalog.{meta['Name']}"), ('dynamicDataRead', True)])),
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_catalog_choice_dsl(meta, p, preset_data):
|
||||||
|
# Start from list
|
||||||
|
list_key = 'catalog.list'
|
||||||
|
lp = preset_data.get(list_key, {})
|
||||||
|
dsl = generate_catalog_list_dsl(meta, lp)
|
||||||
|
|
||||||
|
# Add choice-specific properties
|
||||||
|
dsl['properties']['windowOpeningMode'] = 'LockOwnerWindow'
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
dsl['properties'][k] = p['properties'][k]
|
||||||
|
|
||||||
|
# Set ChoiceMode on table
|
||||||
|
dsl['elements'][0]['choiceMode'] = True
|
||||||
|
|
||||||
|
return dsl
|
||||||
|
|
||||||
|
|
||||||
|
def generate_catalog_item_dsl(meta, p, fd):
|
||||||
|
header_children = []
|
||||||
|
|
||||||
|
# Owner (if subordinate)
|
||||||
|
if meta.get('Owners') and len(meta['Owners']) > 0:
|
||||||
|
owner_el = OrderedDict([('input', '\u0412\u043b\u0430\u0434\u0435\u043b\u0435\u0446'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Owner'), ('readOnly', True)])
|
||||||
|
header_children.append(owner_el)
|
||||||
|
|
||||||
|
# Code + Description
|
||||||
|
cd_layout = (p.get('codeDescription') or {}).get('layout', 'horizontal')
|
||||||
|
cd_order = (p.get('codeDescription') or {}).get('order', 'descriptionFirst')
|
||||||
|
has_code = meta.get('CodeLength', 0) > 0
|
||||||
|
|
||||||
|
if cd_layout == 'horizontal' and has_code:
|
||||||
|
cd_children = []
|
||||||
|
desc_el = OrderedDict([('input', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Description')])
|
||||||
|
code_el = OrderedDict([('input', '\u041a\u043e\u0434'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Code')])
|
||||||
|
if cd_order == 'descriptionFirst':
|
||||||
|
cd_children = [desc_el, code_el]
|
||||||
|
else:
|
||||||
|
cd_children = [code_el, desc_el]
|
||||||
|
header_children.append(OrderedDict([
|
||||||
|
('group', 'horizontal'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u041a\u043e\u0434\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'), ('showTitle', False),
|
||||||
|
('representation', 'none'), ('children', cd_children),
|
||||||
|
]))
|
||||||
|
else:
|
||||||
|
# Vertical or no code
|
||||||
|
header_children.append(OrderedDict([('input', '\u041d\u0430\u0438\u043c\u0435\u043d\u043e\u0432\u0430\u043d\u0438\u0435'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Description')]))
|
||||||
|
if has_code:
|
||||||
|
header_children.append(OrderedDict([('input', '\u041a\u043e\u0434'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Code')]))
|
||||||
|
|
||||||
|
# Parent (for hierarchical catalogs)
|
||||||
|
parent_pos = (p.get('parent') or {}).get('position', 'afterCodeDescription')
|
||||||
|
parent_title = (p.get('parent') or {}).get('title')
|
||||||
|
if meta.get('Hierarchical'):
|
||||||
|
parent_el = OrderedDict([('input', '\u0420\u043e\u0434\u0438\u0442\u0435\u043b\u044c'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Parent')])
|
||||||
|
if parent_title:
|
||||||
|
parent_el['title'] = parent_title
|
||||||
|
if parent_pos == 'beforeCodeDescription':
|
||||||
|
insert_idx = 1 if (meta.get('Owners') and len(meta['Owners']) > 0) else 0
|
||||||
|
header_children.insert(insert_idx, parent_el)
|
||||||
|
else:
|
||||||
|
# afterCodeDescription (default)
|
||||||
|
header_children.append(parent_el)
|
||||||
|
|
||||||
|
# Custom attributes -> header
|
||||||
|
footer_field_names = (p.get('footer') or {}).get('fields', [])
|
||||||
|
|
||||||
|
for attr in meta['Attributes']:
|
||||||
|
if attr['Name'] in footer_field_names:
|
||||||
|
continue
|
||||||
|
header_children.append(new_field_element(attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{attr['Name']}", attr['Type'], fd))
|
||||||
|
|
||||||
|
# Build root elements
|
||||||
|
root_elements = []
|
||||||
|
|
||||||
|
# ГруппаШапка
|
||||||
|
root_elements.append(OrderedDict([
|
||||||
|
('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430'), ('showTitle', False),
|
||||||
|
('representation', 'none'), ('children', header_children),
|
||||||
|
]))
|
||||||
|
|
||||||
|
# Tabular sections
|
||||||
|
ts_exclude = ['\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b', '\u041f\u0440\u0435\u0434\u0441\u0442\u0430\u0432\u043b\u0435\u043d\u0438\u044f']
|
||||||
|
if (p.get('tabularSections') or {}).get('exclude'):
|
||||||
|
ts_exclude = p['tabularSections']['exclude']
|
||||||
|
ts_line_number = (p.get('tabularSections') or {}).get('lineNumber', True)
|
||||||
|
|
||||||
|
visible_ts = [ts for ts in meta['TabularSections'] if ts['Name'] not in ts_exclude]
|
||||||
|
|
||||||
|
for ts in visible_ts:
|
||||||
|
ts_cols = []
|
||||||
|
if ts_line_number:
|
||||||
|
ts_cols.append(OrderedDict([('labelField', f"{ts['Name']}\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438"), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}.LineNumber")]))
|
||||||
|
for col in ts['Columns']:
|
||||||
|
ts_cols.append(new_field_element(f"{ts['Name']}{col['Name']}", f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}.{col['Name']}", col['Type'], fd))
|
||||||
|
root_elements.append(OrderedDict([('table', ts['Name']), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}"), ('columns', ts_cols)]))
|
||||||
|
|
||||||
|
# Footer fields
|
||||||
|
for fn in footer_field_names:
|
||||||
|
f_attr = next((a for a in meta['Attributes'] if a['Name'] == fn), None)
|
||||||
|
if f_attr:
|
||||||
|
root_elements.append(new_field_element(f_attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{f_attr['Name']}", f_attr['Type'], fd))
|
||||||
|
|
||||||
|
# BSP group
|
||||||
|
bsp_group = (p.get('additional') or {}).get('bspGroup', True)
|
||||||
|
if bsp_group:
|
||||||
|
root_elements.append(OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b')]))
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
form_props = OrderedDict()
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
form_props[k] = p['properties'][k]
|
||||||
|
# UseForFoldersAndItems
|
||||||
|
if meta.get('Hierarchical') and meta.get('HierarchyType') == 'HierarchyFoldersAndItems':
|
||||||
|
form_props['useForFoldersAndItems'] = 'Items'
|
||||||
|
|
||||||
|
return OrderedDict([
|
||||||
|
('title', meta['Synonym']),
|
||||||
|
('properties', form_props),
|
||||||
|
('elements', root_elements),
|
||||||
|
('attributes', [
|
||||||
|
OrderedDict([('name', '\u041e\u0431\u044a\u0435\u043a\u0442'), ('type', f"CatalogObject.{meta['Name']}"), ('main', True)])
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# --- Document DSL generators ---
|
||||||
|
|
||||||
|
def generate_document_dsl(meta, preset_data, purpose):
|
||||||
|
purpose_key = f"document.{purpose.lower()}"
|
||||||
|
p = preset_data.get(purpose_key, {})
|
||||||
|
fd = p.get('fieldDefaults', {})
|
||||||
|
|
||||||
|
dispatch = {
|
||||||
|
'List': lambda: generate_document_list_dsl(meta, p),
|
||||||
|
'Choice': lambda: generate_document_choice_dsl(meta, p, preset_data),
|
||||||
|
'Item': lambda: generate_document_item_dsl(meta, p, fd),
|
||||||
|
}
|
||||||
|
return dispatch[purpose]()
|
||||||
|
|
||||||
|
|
||||||
|
def generate_document_list_dsl(meta, p):
|
||||||
|
columns = []
|
||||||
|
# All custom attributes as labelField
|
||||||
|
for attr in meta['Attributes']:
|
||||||
|
columns.append(OrderedDict([('labelField', attr['Name']), ('path', f"\u0421\u043f\u0438\u0441\u043e\u043a.{attr['Name']}")]))
|
||||||
|
# Hidden ref
|
||||||
|
if p.get('hiddenRef', True):
|
||||||
|
columns.append(OrderedDict([('labelField', '\u0421\u0441\u044b\u043b\u043a\u0430'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a.Ref'), ('visible', False)]))
|
||||||
|
|
||||||
|
table_el = OrderedDict([
|
||||||
|
('table', '\u0421\u043f\u0438\u0441\u043e\u043a'), ('path', '\u0421\u043f\u0438\u0441\u043e\u043a'),
|
||||||
|
('commandBarLocation', 'None'),
|
||||||
|
('tableAutofill', False),
|
||||||
|
('columns', columns),
|
||||||
|
])
|
||||||
|
|
||||||
|
form_props = OrderedDict()
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
form_props[k] = p['properties'][k]
|
||||||
|
|
||||||
|
return OrderedDict([
|
||||||
|
('title', meta['Synonym']),
|
||||||
|
('properties', form_props),
|
||||||
|
('elements', [table_el]),
|
||||||
|
('attributes', [
|
||||||
|
OrderedDict([
|
||||||
|
('name', '\u0421\u043f\u0438\u0441\u043e\u043a'), ('type', 'DynamicList'), ('main', True),
|
||||||
|
('settings', OrderedDict([('mainTable', f"Document.{meta['Name']}"), ('dynamicDataRead', True)])),
|
||||||
|
])
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
def generate_document_choice_dsl(meta, p, preset_data):
|
||||||
|
list_key = 'document.list'
|
||||||
|
lp = preset_data.get(list_key, {})
|
||||||
|
dsl = generate_document_list_dsl(meta, lp)
|
||||||
|
|
||||||
|
dsl['properties']['windowOpeningMode'] = 'LockOwnerWindow'
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
dsl['properties'][k] = p['properties'][k]
|
||||||
|
|
||||||
|
return dsl
|
||||||
|
|
||||||
|
|
||||||
|
def generate_document_item_dsl(meta, p, fd):
|
||||||
|
header_pos = (p.get('header') or {}).get('position', 'insidePage')
|
||||||
|
header_layout = (p.get('header') or {}).get('layout', '2col')
|
||||||
|
header_distribute = (p.get('header') or {}).get('distribute', 'even')
|
||||||
|
date_title = (p.get('header') or {}).get('dateTitle', '\u043e\u0442')
|
||||||
|
|
||||||
|
footer_fields = (p.get('footer') or {}).get('fields', [])
|
||||||
|
footer_pos = (p.get('footer') or {}).get('position', 'insidePage')
|
||||||
|
|
||||||
|
add_pos = (p.get('additional') or {}).get('position', 'page')
|
||||||
|
add_layout = (p.get('additional') or {}).get('layout', '2col')
|
||||||
|
add_bsp_group = (p.get('additional') or {}).get('bspGroup', True)
|
||||||
|
add_left = (p.get('additional') or {}).get('left', [])
|
||||||
|
add_right = (p.get('additional') or {}).get('right', [])
|
||||||
|
|
||||||
|
header_right = (p.get('header') or {}).get('right', [])
|
||||||
|
|
||||||
|
ts_exclude = ['\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b']
|
||||||
|
if (p.get('tabularSections') or {}).get('exclude'):
|
||||||
|
ts_exclude = p['tabularSections']['exclude']
|
||||||
|
ts_line_number = (p.get('tabularSections') or {}).get('lineNumber', True)
|
||||||
|
|
||||||
|
# Classify attributes
|
||||||
|
claimed = {}
|
||||||
|
for fn in footer_fields:
|
||||||
|
claimed[fn] = 'footer'
|
||||||
|
for fn in header_right:
|
||||||
|
claimed[fn] = 'header.right'
|
||||||
|
for fn in add_left:
|
||||||
|
claimed[fn] = 'additional.left'
|
||||||
|
for fn in add_right:
|
||||||
|
claimed[fn] = 'additional.right'
|
||||||
|
|
||||||
|
unclaimed = [attr for attr in meta['Attributes'] if attr['Name'] not in claimed]
|
||||||
|
|
||||||
|
# Distribute unclaimed
|
||||||
|
left_attrs = []
|
||||||
|
right_extra_attrs = []
|
||||||
|
if header_distribute == 'left':
|
||||||
|
left_attrs = unclaimed
|
||||||
|
elif header_distribute == 'right':
|
||||||
|
right_extra_attrs = unclaimed
|
||||||
|
else: # "even"
|
||||||
|
import math
|
||||||
|
half = math.ceil(len(unclaimed) / 2) if unclaimed else 0
|
||||||
|
left_attrs = unclaimed[:half]
|
||||||
|
right_extra_attrs = unclaimed[half:]
|
||||||
|
|
||||||
|
# Build ГруппаНомерДата
|
||||||
|
num_date_children = [
|
||||||
|
OrderedDict([('input', '\u041d\u043e\u043c\u0435\u0440'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Number'), ('autoMaxWidth', False), ('width', 9)]),
|
||||||
|
OrderedDict([('input', '\u0414\u0430\u0442\u0430'), ('path', '\u041e\u0431\u044a\u0435\u043a\u0442.Date'), ('title', date_title)]),
|
||||||
|
]
|
||||||
|
num_date_group = OrderedDict([
|
||||||
|
('group', 'horizontal'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u041d\u043e\u043c\u0435\u0440\u0414\u0430\u0442\u0430'), ('showTitle', False), ('children', num_date_children),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Build left column
|
||||||
|
left_children = [num_date_group]
|
||||||
|
for attr in left_attrs:
|
||||||
|
left_children.append(new_field_element(attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{attr['Name']}", attr['Type'], fd))
|
||||||
|
|
||||||
|
# Build right column
|
||||||
|
right_children = []
|
||||||
|
for rn in header_right:
|
||||||
|
r_attr = next((a for a in meta['Attributes'] if a['Name'] == rn), None)
|
||||||
|
if r_attr:
|
||||||
|
right_children.append(new_field_element(r_attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{r_attr['Name']}", r_attr['Type'], fd))
|
||||||
|
for attr in right_extra_attrs:
|
||||||
|
right_children.append(new_field_element(attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{attr['Name']}", attr['Type'], fd))
|
||||||
|
|
||||||
|
# Header group
|
||||||
|
if header_layout == '2col' and len(right_children) > 0:
|
||||||
|
header_group = OrderedDict([
|
||||||
|
('group', 'horizontal'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430'), ('showTitle', False), ('representation', 'none'),
|
||||||
|
('children', [
|
||||||
|
OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430\u041b\u0435\u0432\u043e'), ('showTitle', False), ('children', left_children)]),
|
||||||
|
OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430\u041f\u0440\u0430\u0432\u043e'), ('showTitle', False), ('children', right_children)]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
else:
|
||||||
|
# 1col or no right items
|
||||||
|
all_header_fields = left_children + right_children
|
||||||
|
header_group = OrderedDict([
|
||||||
|
('group', 'horizontal'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430'), ('showTitle', False), ('representation', 'none'),
|
||||||
|
('children', [
|
||||||
|
OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0428\u0430\u043f\u043a\u0430\u041b\u0435\u0432\u043e'), ('showTitle', False), ('children', all_header_fields)]),
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
# Footer elements
|
||||||
|
footer_elements = []
|
||||||
|
for fn in footer_fields:
|
||||||
|
f_attr = next((a for a in meta['Attributes'] if a['Name'] == fn), None)
|
||||||
|
if f_attr:
|
||||||
|
footer_elements.append(new_field_element(f_attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{f_attr['Name']}", f_attr['Type'], fd))
|
||||||
|
|
||||||
|
# Visible tabular sections
|
||||||
|
visible_ts = [ts for ts in meta['TabularSections'] if ts['Name'] not in ts_exclude]
|
||||||
|
|
||||||
|
# Additional page content
|
||||||
|
additional_page = None
|
||||||
|
if add_pos == 'page':
|
||||||
|
add_left_els = []
|
||||||
|
add_right_els = []
|
||||||
|
for aln in add_left:
|
||||||
|
al_attr = next((a for a in meta['Attributes'] if a['Name'] == aln), None)
|
||||||
|
if al_attr:
|
||||||
|
add_left_els.append(new_field_element(al_attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{al_attr['Name']}", al_attr['Type'], fd))
|
||||||
|
for arn in add_right:
|
||||||
|
ar_attr = next((a for a in meta['Attributes'] if a['Name'] == arn), None)
|
||||||
|
if ar_attr:
|
||||||
|
add_right_els.append(new_field_element(ar_attr['Name'], f"\u041e\u0431\u044a\u0435\u043a\u0442.{ar_attr['Name']}", ar_attr['Type'], fd))
|
||||||
|
add_page_children = []
|
||||||
|
if add_layout == '2col':
|
||||||
|
add_page_children.append(OrderedDict([
|
||||||
|
('group', 'horizontal'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b'), ('showTitle', False),
|
||||||
|
('children', [
|
||||||
|
OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u041b\u0435\u0432\u043e'), ('showTitle', False), ('children', add_left_els)]),
|
||||||
|
OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u041f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u044b\u041f\u0440\u0430\u0432\u043e'), ('showTitle', False), ('children', add_right_els)]),
|
||||||
|
]),
|
||||||
|
]))
|
||||||
|
else:
|
||||||
|
add_page_children.extend(add_left_els + add_right_els)
|
||||||
|
if add_bsp_group:
|
||||||
|
add_page_children.append(OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b')]))
|
||||||
|
additional_page = OrderedDict([('page', '\u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e'), ('title', '\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u043e'), ('children', add_page_children)])
|
||||||
|
|
||||||
|
# Build TS page elements
|
||||||
|
ts_pages = []
|
||||||
|
for ts in visible_ts:
|
||||||
|
ts_cols = []
|
||||||
|
if ts_line_number:
|
||||||
|
ts_cols.append(OrderedDict([('labelField', f"{ts['Name']}\u041d\u043e\u043c\u0435\u0440\u0421\u0442\u0440\u043e\u043a\u0438"), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}.LineNumber")]))
|
||||||
|
for col in ts['Columns']:
|
||||||
|
ts_cols.append(new_field_element(f"{ts['Name']}{col['Name']}", f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}.{col['Name']}", col['Type'], fd))
|
||||||
|
ts_pages.append(OrderedDict([
|
||||||
|
('page', f"\u0413\u0440\u0443\u043f\u043f\u0430{ts['Name']}"), ('title', ts['Synonym']),
|
||||||
|
('children', [
|
||||||
|
OrderedDict([('table', ts['Name']), ('path', f"\u041e\u0431\u044a\u0435\u043a\u0442.{ts['Name']}"), ('columns', ts_cols)])
|
||||||
|
]),
|
||||||
|
]))
|
||||||
|
|
||||||
|
# Assemble root elements
|
||||||
|
root_elements = []
|
||||||
|
|
||||||
|
if len(visible_ts) == 0:
|
||||||
|
# Simple form - no Pages
|
||||||
|
root_elements.append(header_group)
|
||||||
|
if footer_elements:
|
||||||
|
root_elements.extend(footer_elements)
|
||||||
|
if add_bsp_group and add_pos != 'none':
|
||||||
|
root_elements.append(OrderedDict([('group', 'vertical'), ('name', '\u0413\u0440\u0443\u043f\u043f\u0430\u0414\u043e\u043f\u043e\u043b\u043d\u0438\u0442\u0435\u043b\u044c\u043d\u044b\u0435\u0420\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u044b')]))
|
||||||
|
else:
|
||||||
|
# Pages form
|
||||||
|
if header_pos == 'abovePages':
|
||||||
|
root_elements.append(header_group)
|
||||||
|
pages_children = list(ts_pages)
|
||||||
|
if additional_page:
|
||||||
|
pages_children.append(additional_page)
|
||||||
|
root_elements.append(OrderedDict([('pages', '\u0413\u0440\u0443\u043f\u043f\u0430\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u044b'), ('children', pages_children)]))
|
||||||
|
else:
|
||||||
|
# insidePage (default)
|
||||||
|
osnovnoe_children = [header_group]
|
||||||
|
if footer_pos == 'insidePage' and footer_elements:
|
||||||
|
osnovnoe_children.extend(footer_elements)
|
||||||
|
pages_children = []
|
||||||
|
pages_children.append(OrderedDict([('page', '\u0413\u0440\u0443\u043f\u043f\u0430\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435'), ('title', '\u041e\u0441\u043d\u043e\u0432\u043d\u043e\u0435'), ('children', osnovnoe_children)]))
|
||||||
|
pages_children.extend(ts_pages)
|
||||||
|
if additional_page:
|
||||||
|
pages_children.append(additional_page)
|
||||||
|
root_elements.append(OrderedDict([('pages', '\u0413\u0440\u0443\u043f\u043f\u0430\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u044b'), ('children', pages_children)]))
|
||||||
|
|
||||||
|
# Footer below pages
|
||||||
|
if footer_pos == 'belowPages' and footer_elements:
|
||||||
|
root_elements.extend(footer_elements)
|
||||||
|
|
||||||
|
# Properties
|
||||||
|
form_props = OrderedDict([('autoTitle', False)])
|
||||||
|
if p.get('properties'):
|
||||||
|
for k in p['properties']:
|
||||||
|
form_props[k] = p['properties'][k]
|
||||||
|
|
||||||
|
return OrderedDict([
|
||||||
|
('title', meta['Synonym']),
|
||||||
|
('properties', form_props),
|
||||||
|
('elements', root_elements),
|
||||||
|
('attributes', [
|
||||||
|
OrderedDict([('name', '\u041e\u0431\u044a\u0435\u043a\u0442'), ('type', f"DocumentObject.{meta['Name']}"), ('main', True)])
|
||||||
|
]),
|
||||||
|
])
|
||||||
|
|
||||||
|
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
# END OF FROM-OBJECT MODE FUNCTIONS
|
||||||
|
# ═══════════════════════════════════════════════════════════════════════════
|
||||||
|
|
||||||
|
|
||||||
def esc_xml(s):
|
def esc_xml(s):
|
||||||
@@ -114,6 +866,8 @@ KNOWN_KEYS = {
|
|||||||
"type", "command", "stdCommand", "defaultButton", "locationInCommandBar",
|
"type", "command", "stdCommand", "defaultButton", "locationInCommandBar",
|
||||||
"src",
|
"src",
|
||||||
"autofill",
|
"autofill",
|
||||||
|
"choiceMode", "initialTreeView", "enableDrag", "enableStartDrag",
|
||||||
|
"rowPictureDataPath", "tableAutofill",
|
||||||
}
|
}
|
||||||
|
|
||||||
TYPE_KEYS = ["group", "input", "check", "label", "labelField", "table", "pages", "page",
|
TYPE_KEYS = ["group", "input", "check", "label", "labelField", "table", "pages", "page",
|
||||||
@@ -615,9 +1369,29 @@ def emit_table(lines, el, name, eid, indent):
|
|||||||
if el.get('searchStringLocation'):
|
if el.get('searchStringLocation'):
|
||||||
lines.append(f'{inner}<SearchStringLocation>{el["searchStringLocation"]}</SearchStringLocation>')
|
lines.append(f'{inner}<SearchStringLocation>{el["searchStringLocation"]}</SearchStringLocation>')
|
||||||
|
|
||||||
|
if el.get('choiceMode') is True:
|
||||||
|
lines.append(f'{inner}<ChoiceMode>true</ChoiceMode>')
|
||||||
|
if el.get('initialTreeView'):
|
||||||
|
lines.append(f'{inner}<InitialTreeView>{el["initialTreeView"]}</InitialTreeView>')
|
||||||
|
if el.get('enableStartDrag') is True:
|
||||||
|
lines.append(f'{inner}<EnableStartDrag>true</EnableStartDrag>')
|
||||||
|
if el.get('enableDrag') is True:
|
||||||
|
lines.append(f'{inner}<EnableDrag>true</EnableDrag>')
|
||||||
|
if el.get('rowPictureDataPath'):
|
||||||
|
lines.append(f'{inner}<RowPictureDataPath>{el["rowPictureDataPath"]}</RowPictureDataPath>')
|
||||||
|
|
||||||
# Companions
|
# Companions
|
||||||
emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner)
|
emit_companion(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner)
|
||||||
emit_companion(lines, 'AutoCommandBar', f'{name}\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c', inner)
|
# AutoCommandBar — with optional Autofill control
|
||||||
|
if el.get('tableAutofill') is not None:
|
||||||
|
acb_id = new_id()
|
||||||
|
acb_name = f'{name}\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c'
|
||||||
|
af_val = 'true' if el['tableAutofill'] else 'false'
|
||||||
|
lines.append(f'{inner}<AutoCommandBar name="{acb_name}" id="{acb_id}">')
|
||||||
|
lines.append(f'{inner}\t<Autofill>{af_val}</Autofill>')
|
||||||
|
lines.append(f'{inner}</AutoCommandBar>')
|
||||||
|
else:
|
||||||
|
emit_companion(lines, 'AutoCommandBar', f'{name}\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c', inner)
|
||||||
emit_companion(lines, 'SearchStringAddition', f'{name}\u0421\u0442\u0440\u043e\u043a\u0430\u041f\u043e\u0438\u0441\u043a\u0430', inner)
|
emit_companion(lines, 'SearchStringAddition', f'{name}\u0421\u0442\u0440\u043e\u043a\u0430\u041f\u043e\u0438\u0441\u043a\u0430', inner)
|
||||||
emit_companion(lines, 'ViewStatusAddition', f'{name}\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430', inner)
|
emit_companion(lines, 'ViewStatusAddition', f'{name}\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430', inner)
|
||||||
emit_companion(lines, 'SearchControlAddition', f'{name}\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u041f\u043e\u0438\u0441\u043a\u043e\u043c', inner)
|
emit_companion(lines, 'SearchControlAddition', f'{name}\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u041f\u043e\u0438\u0441\u043a\u043e\u043c', inner)
|
||||||
@@ -897,6 +1671,19 @@ def emit_attributes(lines, attrs, indent):
|
|||||||
lines.append(f'{inner}\t</Column>')
|
lines.append(f'{inner}\t</Column>')
|
||||||
lines.append(f'{inner}</Columns>')
|
lines.append(f'{inner}</Columns>')
|
||||||
|
|
||||||
|
# Settings (for DynamicList)
|
||||||
|
if attr.get('settings'):
|
||||||
|
s = attr['settings']
|
||||||
|
lines.append(f'{inner}<Settings xsi:type="DynamicList">')
|
||||||
|
si = f'{inner}\t'
|
||||||
|
if s.get('mainTable'):
|
||||||
|
lines.append(f'{si}<MainTable>{s["mainTable"]}</MainTable>')
|
||||||
|
mq = 'true' if s.get('manualQuery') else 'false'
|
||||||
|
lines.append(f'{si}<ManualQuery>{mq}</ManualQuery>')
|
||||||
|
ddr = 'true' if s.get('dynamicDataRead') else 'false'
|
||||||
|
lines.append(f'{si}<DynamicDataRead>{ddr}</DynamicDataRead>')
|
||||||
|
lines.append(f'{inner}</Settings>')
|
||||||
|
|
||||||
lines.append(f'{indent}\t</Attribute>')
|
lines.append(f'{indent}\t</Attribute>')
|
||||||
lines.append(f'{indent}</Attributes>')
|
lines.append(f'{indent}</Attributes>')
|
||||||
|
|
||||||
@@ -1001,6 +1788,7 @@ def emit_properties(lines, props, indent):
|
|||||||
lines.append(f'{indent}<{xml_name}>{val}</{xml_name}>')
|
lines.append(f'{indent}<{xml_name}>{val}</{xml_name}>')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def detect_format_version(d):
|
def detect_format_version(d):
|
||||||
while d:
|
while d:
|
||||||
cfg_path = os.path.join(d, "Configuration.xml")
|
cfg_path = os.path.join(d, "Configuration.xml")
|
||||||
@@ -1022,23 +1810,130 @@ def main():
|
|||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
global _next_id
|
global _next_id
|
||||||
|
|
||||||
parser = argparse.ArgumentParser(description='Compile 1C managed form from JSON', allow_abbrev=False)
|
parser = argparse.ArgumentParser(description='Compile 1C managed form from JSON or object metadata', allow_abbrev=False)
|
||||||
parser.add_argument('-JsonPath', type=str, required=True)
|
parser.add_argument('-JsonPath', type=str, default=None)
|
||||||
parser.add_argument('-OutputPath', type=str, required=True)
|
parser.add_argument('-OutputPath', type=str, required=True)
|
||||||
|
parser.add_argument('-FromObject', action='store_true', default=False)
|
||||||
|
parser.add_argument('-ObjectPath', type=str, default=None)
|
||||||
|
parser.add_argument('-Purpose', type=str, default=None)
|
||||||
|
parser.add_argument('-Preset', type=str, default='erp-standard')
|
||||||
|
parser.add_argument('-EmitDsl', type=str, default=None)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# Form name -> purpose mapping
|
||||||
|
_FORM_NAME_TO_PURPOSE = {
|
||||||
|
'\u0424\u043e\u0440\u043c\u0430\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430': 'Item', # ФормаДокумента
|
||||||
|
'\u0424\u043e\u0440\u043c\u0430\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u0430': 'Item', # ФормаЭлемента
|
||||||
|
'\u0424\u043e\u0440\u043c\u0430\u0421\u043f\u0438\u0441\u043a\u0430': 'List', # ФормаСписка
|
||||||
|
'\u0424\u043e\u0440\u043c\u0430\u0412\u044b\u0431\u043e\u0440\u0430': 'Choice', # ФормаВыбора
|
||||||
|
'\u0424\u043e\u0440\u043c\u0430\u0413\u0440\u0443\u043f\u043f\u044b': 'Folder', # ФормаГруппы
|
||||||
|
}
|
||||||
|
|
||||||
|
# Mutual exclusion validation
|
||||||
|
if args.FromObject and args.JsonPath:
|
||||||
|
print("Cannot use both -JsonPath and -FromObject. Choose one mode.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if not args.FromObject and not args.JsonPath:
|
||||||
|
print("Either -JsonPath or -FromObject is required.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Normalize OutputPath in from-object mode: append /Ext/Form.xml if missing
|
||||||
|
if args.FromObject:
|
||||||
|
out_norm = args.OutputPath.rstrip('/\\')
|
||||||
|
if not re.search(r'[/\\]Ext[/\\]Form\.xml$', out_norm):
|
||||||
|
if re.search(r'[/\\]Ext$', out_norm):
|
||||||
|
args.OutputPath = out_norm + '/Form.xml'
|
||||||
|
else:
|
||||||
|
args.OutputPath = out_norm + '/Ext/Form.xml'
|
||||||
|
print(f"[resolved] OutputPath -> {args.OutputPath}")
|
||||||
|
|
||||||
# --- Detect XML format version ---
|
# --- Detect XML format version ---
|
||||||
out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath)
|
out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath)
|
||||||
format_version = detect_format_version(os.path.dirname(out_path_resolved))
|
format_version = detect_format_version(os.path.dirname(out_path_resolved))
|
||||||
|
|
||||||
# --- 1. Load and validate JSON ---
|
# --- 0. From-object mode ---
|
||||||
json_path = args.JsonPath
|
if args.FromObject:
|
||||||
if not os.path.exists(json_path):
|
# Resolve object path and purpose from OutputPath convention:
|
||||||
print(f"File not found: {json_path}", file=sys.stderr)
|
# .../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml
|
||||||
sys.exit(1)
|
out_abs = out_path_resolved
|
||||||
|
parts = re.split(r'[/\\]', out_abs)
|
||||||
|
forms_idx = -1
|
||||||
|
for i in range(len(parts) - 1, -1, -1):
|
||||||
|
if parts[i] == 'Forms':
|
||||||
|
forms_idx = i
|
||||||
|
break
|
||||||
|
|
||||||
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
resolved_object_path = None
|
||||||
defn = json.load(f)
|
resolved_purpose = None
|
||||||
|
|
||||||
|
if forms_idx >= 2:
|
||||||
|
form_name = parts[forms_idx + 1]
|
||||||
|
object_name = parts[forms_idx - 1]
|
||||||
|
type_plural_and_above = os.sep.join(parts[:forms_idx - 1])
|
||||||
|
|
||||||
|
if form_name in _FORM_NAME_TO_PURPOSE:
|
||||||
|
resolved_purpose = _FORM_NAME_TO_PURPOSE[form_name]
|
||||||
|
|
||||||
|
candidate = os.path.join(type_plural_and_above, f'{object_name}.xml')
|
||||||
|
if os.path.exists(candidate):
|
||||||
|
resolved_object_path = candidate
|
||||||
|
|
||||||
|
# Apply: explicit -ObjectPath / -Purpose override resolved
|
||||||
|
from_obj_path = None
|
||||||
|
if args.ObjectPath:
|
||||||
|
from_obj_path = args.ObjectPath if os.path.isabs(args.ObjectPath) else os.path.join(os.getcwd(), args.ObjectPath)
|
||||||
|
if not from_obj_path.endswith('.xml'):
|
||||||
|
from_obj_path += '.xml'
|
||||||
|
elif resolved_object_path:
|
||||||
|
from_obj_path = resolved_object_path
|
||||||
|
print(f"[resolved] ObjectPath -> {from_obj_path}")
|
||||||
|
else:
|
||||||
|
print("Cannot derive object path from OutputPath. Use -ObjectPath explicitly.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if not os.path.exists(from_obj_path):
|
||||||
|
print(f"Object file not found: {from_obj_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
purpose = args.Purpose or resolved_purpose or 'Item'
|
||||||
|
if resolved_purpose and not args.Purpose:
|
||||||
|
print(f"[resolved] Purpose -> {purpose}")
|
||||||
|
|
||||||
|
meta = parse_object_meta(from_obj_path)
|
||||||
|
print(f"[from-object] Type={meta['Type']}, Name={meta['Name']}, Attrs={len(meta['Attributes'])}, TS={len(meta['TabularSections'])}")
|
||||||
|
|
||||||
|
preset_data = load_preset(args.Preset, os.path.dirname(os.path.abspath(__file__)), out_path_resolved)
|
||||||
|
|
||||||
|
supported = {'Document': ['Item', 'List', 'Choice'], 'Catalog': ['Item', 'Folder', 'List', 'Choice']}
|
||||||
|
if meta['Type'] not in supported:
|
||||||
|
print(f"Object type '{meta['Type']}' not supported. Supported: Document, Catalog.", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
if purpose not in supported[meta['Type']]:
|
||||||
|
print(f"Purpose '{purpose}' not valid for {meta['Type']}. Valid: {', '.join(supported[meta['Type']])}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if meta['Type'] == 'Document':
|
||||||
|
dsl = generate_document_dsl(meta, preset_data, purpose)
|
||||||
|
else:
|
||||||
|
dsl = generate_catalog_dsl(meta, preset_data, purpose)
|
||||||
|
|
||||||
|
if args.EmitDsl:
|
||||||
|
dsl_path = args.EmitDsl if os.path.isabs(args.EmitDsl) else os.path.join(os.getcwd(), args.EmitDsl)
|
||||||
|
os.makedirs(os.path.dirname(dsl_path) or '.', exist_ok=True)
|
||||||
|
with open(dsl_path, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(dsl, f, ensure_ascii=False, indent=2)
|
||||||
|
print(f"[from-object] DSL saved: {dsl_path}")
|
||||||
|
|
||||||
|
defn = json.loads(json.dumps(dsl)) # normalize OrderedDict to regular dict
|
||||||
|
else:
|
||||||
|
# --- 1. Load and validate JSON ---
|
||||||
|
json_path = args.JsonPath
|
||||||
|
if not os.path.exists(json_path):
|
||||||
|
print(f"File not found: {json_path}", file=sys.stderr)
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
with open(json_path, 'r', encoding='utf-8-sig') as f:
|
||||||
|
defn = json.load(f)
|
||||||
|
|
||||||
# --- 2. Main compilation ---
|
# --- 2. Main compilation ---
|
||||||
_next_id = 0
|
_next_id = 0
|
||||||
|
|||||||
Reference in New Issue
Block a user