mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-14 09:54:56 +03:00
0a1f8888dc
Add Resolve-TypeStr/resolve_type_str to form-compile and form-edit (PS+PY) that silently normalizes Russian type names (Строка→string, Число→decimal, Булево→boolean, etc.), Number→decimal alias, and Russian reference prefixes (СправочникСсылка→CatalogRef, etc.). Also accept + as composite type separator alongside |. This makes form skills more forgiving when the model uses meta-compile DSL conventions instead of form-specific ones. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1121 lines
43 KiB
Python
1121 lines
43 KiB
Python
#!/usr/bin/env python3
|
|
# form-compile v1.0 — Compile 1C managed form from JSON
|
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
import argparse
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
import uuid
|
|
|
|
|
|
def esc_xml(s):
|
|
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
|
|
|
|
|
def emit_mltext(lines, indent, tag, text):
|
|
if not text:
|
|
lines.append(f"{indent}<{tag}/>")
|
|
return
|
|
lines.append(f"{indent}<{tag}>")
|
|
lines.append(f"{indent}\t<v8:item>")
|
|
lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
|
lines.append(f"{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>")
|
|
lines.append(f"{indent}\t</v8:item>")
|
|
lines.append(f"{indent}</{tag}>")
|
|
|
|
|
|
def new_uuid():
|
|
return str(uuid.uuid4())
|
|
|
|
|
|
def write_utf8_bom(path, content):
|
|
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
|
f.write(content)
|
|
|
|
|
|
# --- ID allocator ---
|
|
_next_id = 0
|
|
|
|
def new_id():
|
|
global _next_id
|
|
_next_id += 1
|
|
return _next_id
|
|
|
|
|
|
# --- Event handler name generator ---
|
|
|
|
EVENT_SUFFIX_MAP = {
|
|
"OnChange": "\u041f\u0440\u0438\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u0438",
|
|
"StartChoice": "\u041d\u0430\u0447\u0430\u043b\u043e\u0412\u044b\u0431\u043e\u0440\u0430",
|
|
"ChoiceProcessing": "\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u0412\u044b\u0431\u043e\u0440\u0430",
|
|
"AutoComplete": "\u0410\u0432\u0442\u043e\u041f\u043e\u0434\u0431\u043e\u0440",
|
|
"Clearing": "\u041e\u0447\u0438\u0441\u0442\u043a\u0430",
|
|
"Opening": "\u041e\u0442\u043a\u0440\u044b\u0442\u0438\u0435",
|
|
"Click": "\u041d\u0430\u0436\u0430\u0442\u0438\u0435",
|
|
"OnActivateRow": "\u041f\u0440\u0438\u0410\u043a\u0442\u0438\u0432\u0438\u0437\u0430\u0446\u0438\u0438\u0421\u0442\u0440\u043e\u043a\u0438",
|
|
"BeforeAddRow": "\u041f\u0435\u0440\u0435\u0434\u041d\u0430\u0447\u0430\u043b\u043e\u043c\u0414\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u0438\u044f",
|
|
"BeforeDeleteRow": "\u041f\u0435\u0440\u0435\u0434\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u0435\u043c",
|
|
"BeforeRowChange": "\u041f\u0435\u0440\u0435\u0434\u041d\u0430\u0447\u0430\u043b\u043e\u043c\u0418\u0437\u043c\u0435\u043d\u0435\u043d\u0438\u044f",
|
|
"OnStartEdit": "\u041f\u0440\u0438\u041d\u0430\u0447\u0430\u043b\u0435\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f",
|
|
"OnEndEdit": "\u041f\u0440\u0438\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0438\u0420\u0435\u0434\u0430\u043a\u0442\u0438\u0440\u043e\u0432\u0430\u043d\u0438\u044f",
|
|
"Selection": "\u0412\u044b\u0431\u043e\u0440\u0421\u0442\u0440\u043e\u043a\u0438",
|
|
"OnCurrentPageChange": "\u041f\u0440\u0438\u0421\u043c\u0435\u043d\u0435\u0421\u0442\u0440\u0430\u043d\u0438\u0446\u044b",
|
|
"TextEditEnd": "\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u0435\u0412\u0432\u043e\u0434\u0430\u0422\u0435\u043a\u0441\u0442\u0430",
|
|
"URLProcessing": "\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430\u041d\u0430\u0432\u0438\u0433\u0430\u0446\u0438\u043e\u043d\u043d\u043e\u0439\u0421\u0441\u044b\u043b\u043a\u0438",
|
|
"DragStart": "\u041d\u0430\u0447\u0430\u043b\u043e\u041f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f",
|
|
"Drag": "\u041f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u0435",
|
|
"DragCheck": "\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430\u041f\u0435\u0440\u0435\u0442\u0430\u0441\u043a\u0438\u0432\u0430\u043d\u0438\u044f",
|
|
"Drop": "\u041f\u043e\u043c\u0435\u0449\u0435\u043d\u0438\u0435",
|
|
"AfterDeleteRow": "\u041f\u043e\u0441\u043b\u0435\u0423\u0434\u0430\u043b\u0435\u043d\u0438\u044f",
|
|
}
|
|
|
|
KNOWN_EVENTS = {
|
|
"input": ["OnChange", "StartChoice", "ChoiceProcessing", "AutoComplete", "TextEditEnd", "Clearing", "Creating", "EditTextChange"],
|
|
"check": ["OnChange"],
|
|
"label": ["Click", "URLProcessing"],
|
|
"labelField": ["OnChange", "StartChoice", "ChoiceProcessing", "Click", "URLProcessing", "Clearing"],
|
|
"table": ["Selection", "BeforeAddRow", "AfterDeleteRow", "BeforeDeleteRow", "OnActivateRow", "OnEditEnd", "OnStartEdit", "BeforeRowChange", "BeforeEditEnd", "ValueChoice", "OnActivateCell", "OnActivateField", "Drag", "DragStart", "DragCheck", "DragEnd", "OnGetDataAtServer", "BeforeLoadUserSettingsAtServer", "OnUpdateUserSettingSetAtServer", "OnChange"],
|
|
"pages": ["OnCurrentPageChange"],
|
|
"page": ["OnCurrentPageChange"],
|
|
"button": ["Click"],
|
|
"picField": ["OnChange", "StartChoice", "ChoiceProcessing", "Click", "Clearing"],
|
|
"calendar": ["OnChange", "OnActivate"],
|
|
"picture": ["Click"],
|
|
"cmdBar": [],
|
|
"popup": [],
|
|
"group": [],
|
|
}
|
|
|
|
KNOWN_FORM_EVENTS = [
|
|
"OnCreateAtServer", "OnOpen", "BeforeClose", "OnClose", "NotificationProcessing",
|
|
"ChoiceProcessing", "OnReadAtServer", "AfterWriteAtServer", "BeforeWriteAtServer",
|
|
"AfterWrite", "BeforeWrite", "OnWriteAtServer", "FillCheckProcessingAtServer",
|
|
"OnLoadDataFromSettingsAtServer", "BeforeLoadDataFromSettingsAtServer",
|
|
"OnSaveDataInSettingsAtServer", "ExternalEvent", "OnReopen", "Opening",
|
|
]
|
|
|
|
KNOWN_KEYS = {
|
|
"group", "input", "check", "label", "labelField", "table", "pages", "page",
|
|
"button", "picture", "picField", "calendar", "cmdBar", "popup",
|
|
"name", "path", "title",
|
|
"visible", "hidden", "enabled", "disabled", "readOnly",
|
|
"on", "handlers",
|
|
"titleLocation", "representation", "width", "height",
|
|
"horizontalStretch", "verticalStretch", "autoMaxWidth", "autoMaxHeight",
|
|
"multiLine", "passwordMode", "choiceButton", "clearButton",
|
|
"spinButton", "dropListButton", "markIncomplete", "skipOnInput", "inputHint",
|
|
"hyperlink",
|
|
"showTitle", "united",
|
|
"children", "columns",
|
|
"changeRowSet", "changeRowOrder", "header", "footer",
|
|
"commandBarLocation", "searchStringLocation",
|
|
"pagesRepresentation",
|
|
"type", "command", "stdCommand", "defaultButton", "locationInCommandBar",
|
|
"src",
|
|
"autofill",
|
|
}
|
|
|
|
TYPE_KEYS = ["group", "input", "check", "label", "labelField", "table", "pages", "page",
|
|
"button", "picture", "picField", "calendar", "cmdBar", "popup"]
|
|
|
|
|
|
def get_handler_name(element_name, event_name):
|
|
suffix = EVENT_SUFFIX_MAP.get(event_name)
|
|
if suffix:
|
|
return f"{element_name}{suffix}"
|
|
return f"{element_name}{event_name}"
|
|
|
|
|
|
def get_element_name(el, type_key):
|
|
if el.get('name'):
|
|
return str(el['name'])
|
|
return str(el.get(type_key, ''))
|
|
|
|
|
|
def emit_events(lines, el, element_name, indent, type_key):
|
|
if not el.get('on'):
|
|
return
|
|
|
|
# Validate event names
|
|
if type_key and type_key in KNOWN_EVENTS:
|
|
allowed = KNOWN_EVENTS[type_key]
|
|
for evt in el['on']:
|
|
if allowed and str(evt) not in allowed:
|
|
print(f"[WARN] Unknown event '{evt}' for {type_key} '{element_name}'. Known: {', '.join(allowed)}")
|
|
|
|
lines.append(f"{indent}<Events>")
|
|
for evt in el['on']:
|
|
evt_name = str(evt)
|
|
handlers = el.get('handlers')
|
|
if handlers and handlers.get(evt_name):
|
|
handler = str(handlers[evt_name])
|
|
else:
|
|
handler = get_handler_name(element_name, evt_name)
|
|
lines.append(f'{indent}\t<Event name="{evt_name}">{handler}</Event>')
|
|
lines.append(f"{indent}</Events>")
|
|
|
|
|
|
def emit_companion(lines, tag, name, indent):
|
|
cid = new_id()
|
|
lines.append(f'{indent}<{tag} name="{name}" id="{cid}"/>')
|
|
|
|
|
|
def emit_common_flags(lines, el, indent):
|
|
if el.get('visible') is False or el.get('hidden') is True:
|
|
lines.append(f"{indent}<Visible>false</Visible>")
|
|
if el.get('enabled') is False or el.get('disabled') is True:
|
|
lines.append(f"{indent}<Enabled>false</Enabled>")
|
|
if el.get('readOnly') is True:
|
|
lines.append(f"{indent}<ReadOnly>true</ReadOnly>")
|
|
|
|
|
|
def emit_title(lines, el, name, indent):
|
|
if el.get('title'):
|
|
emit_mltext(lines, indent, 'Title', str(el['title']))
|
|
|
|
|
|
# --- Type emitter ---
|
|
|
|
V8_TYPES = {
|
|
"ValueTable": "v8:ValueTable",
|
|
"ValueTree": "v8:ValueTree",
|
|
"ValueList": "v8:ValueListType",
|
|
"TypeDescription": "v8:TypeDescription",
|
|
"Universal": "v8:Universal",
|
|
"FixedArray": "v8:FixedArray",
|
|
"FixedStructure": "v8:FixedStructure",
|
|
}
|
|
|
|
UI_TYPES = {
|
|
"FormattedString": "v8ui:FormattedString",
|
|
"Picture": "v8ui:Picture",
|
|
"Color": "v8ui:Color",
|
|
"Font": "v8ui:Font",
|
|
}
|
|
|
|
DCS_MAP = {
|
|
"DataCompositionSettings": "dcsset:DataCompositionSettings",
|
|
"DataCompositionSchema": "dcssch:DataCompositionSchema",
|
|
"DataCompositionComparisonType": "dcscor:DataCompositionComparisonType",
|
|
}
|
|
|
|
CFG_REF_PATTERN = re.compile(
|
|
r'^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|'
|
|
r'ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|'
|
|
r'ExchangePlanRef|BusinessProcessRef|TaskRef|'
|
|
r'InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.'
|
|
)
|
|
|
|
|
|
_FORM_TYPE_SYNONYMS = {
|
|
"строка": "string", "число": "decimal", "булево": "boolean",
|
|
"дата": "date", "датавремя": "dateTime",
|
|
"number": "decimal", "bool": "boolean",
|
|
"справочникссылка": "CatalogRef", "справочникобъект": "CatalogObject",
|
|
"документссылка": "DocumentRef", "документобъект": "DocumentObject",
|
|
"перечислениессылка": "EnumRef",
|
|
"плансчетовссылка": "ChartOfAccountsRef",
|
|
"планвидовхарактеристикссылка": "ChartOfCharacteristicTypesRef",
|
|
"планвидоврасчётассылка": "ChartOfCalculationTypesRef",
|
|
"планвидоврасчетассылка": "ChartOfCalculationTypesRef",
|
|
"планобменассылка": "ExchangePlanRef",
|
|
"бизнеспроцессссылка": "BusinessProcessRef",
|
|
"задачассылка": "TaskRef",
|
|
"определяемыйтип": "DefinedType",
|
|
}
|
|
|
|
|
|
def resolve_type_str(type_str):
|
|
if not type_str:
|
|
return type_str
|
|
m = re.match(r'^([^(]+)\((.+)\)$', type_str)
|
|
if m:
|
|
base, params = m.group(1).strip(), m.group(2)
|
|
r = _FORM_TYPE_SYNONYMS.get(base.lower())
|
|
return f"{r}({params})" if r else type_str
|
|
if '.' in type_str:
|
|
i = type_str.index('.')
|
|
prefix, suffix = type_str[:i], type_str[i:]
|
|
r = _FORM_TYPE_SYNONYMS.get(prefix.lower())
|
|
return f"{r}{suffix}" if r else type_str
|
|
r = _FORM_TYPE_SYNONYMS.get(type_str.lower())
|
|
return r if r else type_str
|
|
|
|
|
|
def emit_single_type(lines, type_str, indent):
|
|
type_str = resolve_type_str(type_str)
|
|
# boolean
|
|
if type_str == 'boolean':
|
|
lines.append(f'{indent}<v8:Type>xs:boolean</v8:Type>')
|
|
return
|
|
|
|
# string or string(N)
|
|
m = re.match(r'^string(\((\d+)\))?$', type_str)
|
|
if m:
|
|
length = m.group(2) if m.group(2) else '0'
|
|
lines.append(f'{indent}<v8:Type>xs:string</v8:Type>')
|
|
lines.append(f'{indent}<v8:StringQualifiers>')
|
|
lines.append(f'{indent}\t<v8:Length>{length}</v8:Length>')
|
|
lines.append(f'{indent}\t<v8:AllowedLength>Variable</v8:AllowedLength>')
|
|
lines.append(f'{indent}</v8:StringQualifiers>')
|
|
return
|
|
|
|
# decimal(D,F) or decimal(D,F,nonneg)
|
|
m = re.match(r'^decimal\((\d+),(\d+)(,nonneg)?\)$', type_str)
|
|
if m:
|
|
digits = m.group(1)
|
|
fraction = m.group(2)
|
|
sign = 'Nonnegative' if m.group(3) else 'Any'
|
|
lines.append(f'{indent}<v8:Type>xs:decimal</v8:Type>')
|
|
lines.append(f'{indent}<v8:NumberQualifiers>')
|
|
lines.append(f'{indent}\t<v8:Digits>{digits}</v8:Digits>')
|
|
lines.append(f'{indent}\t<v8:FractionDigits>{fraction}</v8:FractionDigits>')
|
|
lines.append(f'{indent}\t<v8:AllowedSign>{sign}</v8:AllowedSign>')
|
|
lines.append(f'{indent}</v8:NumberQualifiers>')
|
|
return
|
|
|
|
# date / dateTime / time
|
|
m = re.match(r'^(date|dateTime|time)$', type_str)
|
|
if m:
|
|
fractions_map = {'date': 'Date', 'dateTime': 'DateTime', 'time': 'Time'}
|
|
fractions = fractions_map[type_str]
|
|
lines.append(f'{indent}<v8:Type>xs:dateTime</v8:Type>')
|
|
lines.append(f'{indent}<v8:DateQualifiers>')
|
|
lines.append(f'{indent}\t<v8:DateFractions>{fractions}</v8:DateFractions>')
|
|
lines.append(f'{indent}</v8:DateQualifiers>')
|
|
return
|
|
|
|
# V8 types
|
|
if type_str in V8_TYPES:
|
|
lines.append(f'{indent}<v8:Type>{V8_TYPES[type_str]}</v8:Type>')
|
|
return
|
|
|
|
# UI types
|
|
if type_str in UI_TYPES:
|
|
lines.append(f'{indent}<v8:Type>{UI_TYPES[type_str]}</v8:Type>')
|
|
return
|
|
|
|
# DCS types
|
|
if type_str.startswith('DataComposition'):
|
|
if type_str in DCS_MAP:
|
|
lines.append(f'{indent}<v8:Type>{DCS_MAP[type_str]}</v8:Type>')
|
|
return
|
|
|
|
# DynamicList
|
|
if type_str == 'DynamicList':
|
|
lines.append(f'{indent}<v8:Type>cfg:DynamicList</v8:Type>')
|
|
return
|
|
|
|
# cfg: references
|
|
if CFG_REF_PATTERN.match(type_str):
|
|
lines.append(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
|
return
|
|
|
|
# Fallback
|
|
if '.' in type_str:
|
|
lines.append(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
|
|
else:
|
|
lines.append(f'{indent}<v8:Type>{type_str}</v8:Type>')
|
|
|
|
|
|
def emit_type(lines, type_str, indent):
|
|
if not type_str:
|
|
lines.append(f'{indent}<Type/>')
|
|
return
|
|
|
|
type_string = str(type_str)
|
|
parts = [p.strip() for p in re.split(r'[|+]', type_string)]
|
|
|
|
lines.append(f'{indent}<Type>')
|
|
for part in parts:
|
|
emit_single_type(lines, part, f'{indent}\t')
|
|
lines.append(f'{indent}</Type>')
|
|
|
|
|
|
# --- Element emitters ---
|
|
|
|
def emit_element(lines, el, indent):
|
|
type_key = None
|
|
for key in TYPE_KEYS:
|
|
if el.get(key) is not None:
|
|
type_key = key
|
|
break
|
|
|
|
if not type_key:
|
|
print("WARNING: Unknown element type, skipping", file=sys.stderr)
|
|
return
|
|
|
|
# Validate known keys
|
|
for p_name in el.keys():
|
|
if p_name not in KNOWN_KEYS:
|
|
print(f"WARNING: Element '{el.get(type_key, '')}': unknown key '{p_name}' -- ignored. Check SKILL.md for valid keys.", file=sys.stderr)
|
|
|
|
name = get_element_name(el, type_key)
|
|
eid = new_id()
|
|
|
|
emitters = {
|
|
'group': emit_group,
|
|
'input': emit_input,
|
|
'check': emit_check,
|
|
'label': emit_label,
|
|
'labelField': emit_label_field,
|
|
'table': emit_table,
|
|
'pages': emit_pages,
|
|
'page': emit_page,
|
|
'button': emit_button,
|
|
'picture': emit_picture_decoration,
|
|
'picField': emit_picture_field,
|
|
'calendar': emit_calendar,
|
|
'cmdBar': emit_command_bar,
|
|
'popup': emit_popup,
|
|
}
|
|
|
|
emitter = emitters.get(type_key)
|
|
if emitter:
|
|
emitter(lines, el, name, eid, indent)
|
|
|
|
|
|
def emit_group(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<UsualGroup name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
emit_title(lines, el, name, inner)
|
|
|
|
# Group orientation
|
|
group_val = str(el.get('group', ''))
|
|
orientation_map = {
|
|
'horizontal': 'Horizontal',
|
|
'vertical': 'Vertical',
|
|
'alwaysHorizontal': 'AlwaysHorizontal',
|
|
'alwaysVertical': 'AlwaysVertical',
|
|
}
|
|
orientation = orientation_map.get(group_val)
|
|
if orientation:
|
|
lines.append(f'{inner}<Group>{orientation}</Group>')
|
|
|
|
# Behavior
|
|
if group_val == 'collapsible':
|
|
lines.append(f'{inner}<Group>Vertical</Group>')
|
|
lines.append(f'{inner}<Behavior>Collapsible</Behavior>')
|
|
|
|
# Representation
|
|
if el.get('representation'):
|
|
repr_map = {
|
|
'none': 'None',
|
|
'normal': 'NormalSeparation',
|
|
'weak': 'WeakSeparation',
|
|
'strong': 'StrongSeparation',
|
|
}
|
|
repr_val = repr_map.get(str(el['representation']), str(el['representation']))
|
|
lines.append(f'{inner}<Representation>{repr_val}</Representation>')
|
|
|
|
# ShowTitle
|
|
if el.get('showTitle') is False:
|
|
lines.append(f'{inner}<ShowTitle>false</ShowTitle>')
|
|
|
|
# United
|
|
if el.get('united') is False:
|
|
lines.append(f'{inner}<United>false</United>')
|
|
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
# Companion: ExtendedTooltip
|
|
emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
# Children
|
|
if el.get('children') and len(el['children']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for child in el['children']:
|
|
emit_element(lines, child, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
lines.append(f'{indent}</UsualGroup>')
|
|
|
|
|
|
def emit_input(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<InputField name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('titleLocation'):
|
|
loc_map = {'none': 'None', 'left': 'Left', 'right': 'Right', 'top': 'Top', 'bottom': 'Bottom'}
|
|
loc = loc_map.get(str(el['titleLocation']), str(el['titleLocation']))
|
|
lines.append(f'{inner}<TitleLocation>{loc}</TitleLocation>')
|
|
|
|
if el.get('multiLine') is True:
|
|
lines.append(f'{inner}<MultiLine>true</MultiLine>')
|
|
if el.get('passwordMode') is True:
|
|
lines.append(f'{inner}<PasswordMode>true</PasswordMode>')
|
|
if el.get('choiceButton') is False:
|
|
lines.append(f'{inner}<ChoiceButton>false</ChoiceButton>')
|
|
if el.get('clearButton') is True:
|
|
lines.append(f'{inner}<ClearButton>true</ClearButton>')
|
|
if el.get('spinButton') is True:
|
|
lines.append(f'{inner}<SpinButton>true</SpinButton>')
|
|
if el.get('dropListButton') is True:
|
|
lines.append(f'{inner}<DropListButton>true</DropListButton>')
|
|
if el.get('markIncomplete') is True:
|
|
lines.append(f'{inner}<AutoMarkIncomplete>true</AutoMarkIncomplete>')
|
|
if el.get('skipOnInput') is True:
|
|
lines.append(f'{inner}<SkipOnInput>true</SkipOnInput>')
|
|
if el.get('autoMaxWidth') is False:
|
|
lines.append(f'{inner}<AutoMaxWidth>false</AutoMaxWidth>')
|
|
if el.get('autoMaxHeight') is False:
|
|
lines.append(f'{inner}<AutoMaxHeight>false</AutoMaxHeight>')
|
|
if el.get('width'):
|
|
lines.append(f'{inner}<Width>{el["width"]}</Width>')
|
|
if el.get('height'):
|
|
lines.append(f'{inner}<Height>{el["height"]}</Height>')
|
|
if el.get('horizontalStretch') is True:
|
|
lines.append(f'{inner}<HorizontalStretch>true</HorizontalStretch>')
|
|
if el.get('verticalStretch') is True:
|
|
lines.append(f'{inner}<VerticalStretch>true</VerticalStretch>')
|
|
|
|
if el.get('inputHint'):
|
|
emit_mltext(lines, inner, 'InputHint', str(el['inputHint']))
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'input')
|
|
|
|
lines.append(f'{indent}</InputField>')
|
|
|
|
|
|
def emit_check(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<CheckBoxField name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('titleLocation'):
|
|
lines.append(f'{inner}<TitleLocation>{el["titleLocation"]}</TitleLocation>')
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'check')
|
|
|
|
lines.append(f'{indent}</CheckBoxField>')
|
|
|
|
|
|
def emit_label(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<LabelDecoration name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('title'):
|
|
formatted = 'true' if el.get('hyperlink') is True else 'false'
|
|
lines.append(f'{inner}<Title formatted="{formatted}">')
|
|
lines.append(f'{inner}\t<v8:item>')
|
|
lines.append(f'{inner}\t\t<v8:lang>ru</v8:lang>')
|
|
lines.append(f'{inner}\t\t<v8:content>{esc_xml(str(el["title"]))}</v8:content>')
|
|
lines.append(f'{inner}\t</v8:item>')
|
|
lines.append(f'{inner}</Title>')
|
|
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('hyperlink') is True:
|
|
lines.append(f'{inner}<Hyperlink>true</Hyperlink>')
|
|
if el.get('autoMaxWidth') is False:
|
|
lines.append(f'{inner}<AutoMaxWidth>false</AutoMaxWidth>')
|
|
if el.get('autoMaxHeight') is False:
|
|
lines.append(f'{inner}<AutoMaxHeight>false</AutoMaxHeight>')
|
|
if el.get('width'):
|
|
lines.append(f'{inner}<Width>{el["width"]}</Width>')
|
|
if el.get('height'):
|
|
lines.append(f'{inner}<Height>{el["height"]}</Height>')
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'label')
|
|
|
|
lines.append(f'{indent}</LabelDecoration>')
|
|
|
|
|
|
def emit_label_field(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<LabelField name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('hyperlink') is True:
|
|
lines.append(f'{inner}<Hyperlink>true</Hyperlink>')
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'labelField')
|
|
|
|
lines.append(f'{indent}</LabelField>')
|
|
|
|
|
|
def emit_table(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<Table name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('representation'):
|
|
lines.append(f'{inner}<Representation>{el["representation"]}</Representation>')
|
|
if el.get('changeRowSet') is True:
|
|
lines.append(f'{inner}<ChangeRowSet>true</ChangeRowSet>')
|
|
if el.get('changeRowOrder') is True:
|
|
lines.append(f'{inner}<ChangeRowOrder>true</ChangeRowOrder>')
|
|
if el.get('height'):
|
|
lines.append(f'{inner}<HeightInTableRows>{el["height"]}</HeightInTableRows>')
|
|
if el.get('header') is False:
|
|
lines.append(f'{inner}<Header>false</Header>')
|
|
if el.get('footer') is True:
|
|
lines.append(f'{inner}<Footer>true</Footer>')
|
|
|
|
if el.get('commandBarLocation'):
|
|
lines.append(f'{inner}<CommandBarLocation>{el["commandBarLocation"]}</CommandBarLocation>')
|
|
if el.get('searchStringLocation'):
|
|
lines.append(f'{inner}<SearchStringLocation>{el["searchStringLocation"]}</SearchStringLocation>')
|
|
|
|
# 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, '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, '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)
|
|
|
|
# Columns
|
|
if el.get('columns') and len(el['columns']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for col in el['columns']:
|
|
emit_element(lines, col, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
emit_events(lines, el, name, inner, 'table')
|
|
|
|
lines.append(f'{indent}</Table>')
|
|
|
|
|
|
def emit_pages(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<Pages name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('pagesRepresentation'):
|
|
lines.append(f'{inner}<PagesRepresentation>{el["pagesRepresentation"]}</PagesRepresentation>')
|
|
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
# Companion
|
|
emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'pages')
|
|
|
|
# Children (pages)
|
|
if el.get('children') and len(el['children']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for child in el['children']:
|
|
emit_element(lines, child, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
lines.append(f'{indent}</Pages>')
|
|
|
|
|
|
def emit_page(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<Page name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('group'):
|
|
orientation_map = {
|
|
'horizontal': 'Horizontal',
|
|
'vertical': 'Vertical',
|
|
'alwaysHorizontal': 'AlwaysHorizontal',
|
|
'alwaysVertical': 'AlwaysVertical',
|
|
}
|
|
orientation = orientation_map.get(str(el['group']))
|
|
if orientation:
|
|
lines.append(f'{inner}<Group>{orientation}</Group>')
|
|
|
|
# Companion
|
|
emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
# Children
|
|
if el.get('children') and len(el['children']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for child in el['children']:
|
|
emit_element(lines, child, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
lines.append(f'{indent}</Page>')
|
|
|
|
|
|
def emit_button(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<Button name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
# Type
|
|
if el.get('type'):
|
|
btn_type_map = {'usual': 'UsualButton', 'hyperlink': 'Hyperlink', 'commandBar': 'CommandBarButton'}
|
|
btn_type = btn_type_map.get(str(el['type']), str(el['type']))
|
|
lines.append(f'{inner}<Type>{btn_type}</Type>')
|
|
|
|
# CommandName
|
|
if el.get('command'):
|
|
lines.append(f'{inner}<CommandName>Form.Command.{el["command"]}</CommandName>')
|
|
if el.get('stdCommand'):
|
|
sc = str(el['stdCommand'])
|
|
m = re.match(r'^(.+)\.(.+)$', sc)
|
|
if m:
|
|
lines.append(f'{inner}<CommandName>Form.Item.{m.group(1)}.StandardCommand.{m.group(2)}</CommandName>')
|
|
else:
|
|
lines.append(f'{inner}<CommandName>Form.StandardCommand.{sc}</CommandName>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('defaultButton') is True:
|
|
lines.append(f'{inner}<DefaultButton>true</DefaultButton>')
|
|
|
|
# Picture
|
|
if el.get('picture'):
|
|
lines.append(f'{inner}<Picture>')
|
|
lines.append(f'{inner}\t<xr:Ref>{el["picture"]}</xr:Ref>')
|
|
lines.append(f'{inner}\t<xr:LoadTransparent>true</xr:LoadTransparent>')
|
|
lines.append(f'{inner}</Picture>')
|
|
|
|
if el.get('representation'):
|
|
lines.append(f'{inner}<Representation>{el["representation"]}</Representation>')
|
|
|
|
if el.get('locationInCommandBar'):
|
|
lines.append(f'{inner}<LocationInCommandBar>{el["locationInCommandBar"]}</LocationInCommandBar>')
|
|
|
|
# Companion
|
|
emit_companion(lines, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'button')
|
|
|
|
lines.append(f'{indent}</Button>')
|
|
|
|
|
|
def emit_picture_decoration(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<PictureDecoration name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('picture') or el.get('src'):
|
|
ref = str(el.get('src') or el.get('picture'))
|
|
lines.append(f'{inner}<Picture>')
|
|
lines.append(f'{inner}\t<xr:Ref>{ref}</xr:Ref>')
|
|
lines.append(f'{inner}\t<xr:LoadTransparent>true</xr:LoadTransparent>')
|
|
lines.append(f'{inner}</Picture>')
|
|
|
|
if el.get('hyperlink') is True:
|
|
lines.append(f'{inner}<Hyperlink>true</Hyperlink>')
|
|
if el.get('width'):
|
|
lines.append(f'{inner}<Width>{el["width"]}</Width>')
|
|
if el.get('height'):
|
|
lines.append(f'{inner}<Height>{el["height"]}</Height>')
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'picture')
|
|
|
|
lines.append(f'{indent}</PictureDecoration>')
|
|
|
|
|
|
def emit_picture_field(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<PictureField name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('width'):
|
|
lines.append(f'{inner}<Width>{el["width"]}</Width>')
|
|
if el.get('height'):
|
|
lines.append(f'{inner}<Height>{el["height"]}</Height>')
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'picField')
|
|
|
|
lines.append(f'{indent}</PictureField>')
|
|
|
|
|
|
def emit_calendar(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<CalendarField name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('path'):
|
|
lines.append(f'{inner}<DataPath>{el["path"]}</DataPath>')
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
# 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, 'ExtendedTooltip', f'{name}\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043d\u043d\u0430\u044f\u041f\u043e\u0434\u0441\u043a\u0430\u0437\u043a\u0430', inner)
|
|
|
|
emit_events(lines, el, name, inner, 'calendar')
|
|
|
|
lines.append(f'{indent}</CalendarField>')
|
|
|
|
|
|
def emit_command_bar(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<CommandBar name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
if el.get('autofill') is True:
|
|
lines.append(f'{inner}<Autofill>true</Autofill>')
|
|
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
# Children
|
|
if el.get('children') and len(el['children']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for child in el['children']:
|
|
emit_element(lines, child, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
lines.append(f'{indent}</CommandBar>')
|
|
|
|
|
|
def emit_popup(lines, el, name, eid, indent):
|
|
lines.append(f'{indent}<Popup name="{name}" id="{eid}">')
|
|
inner = f'{indent}\t'
|
|
|
|
emit_title(lines, el, name, inner)
|
|
emit_common_flags(lines, el, inner)
|
|
|
|
if el.get('picture'):
|
|
lines.append(f'{inner}<Picture>')
|
|
lines.append(f'{inner}\t<xr:Ref>{el["picture"]}</xr:Ref>')
|
|
lines.append(f'{inner}\t<xr:LoadTransparent>true</xr:LoadTransparent>')
|
|
lines.append(f'{inner}</Picture>')
|
|
|
|
if el.get('representation'):
|
|
lines.append(f'{inner}<Representation>{el["representation"]}</Representation>')
|
|
|
|
# Children
|
|
if el.get('children') and len(el['children']) > 0:
|
|
lines.append(f'{inner}<ChildItems>')
|
|
for child in el['children']:
|
|
emit_element(lines, child, f'{inner}\t')
|
|
lines.append(f'{inner}</ChildItems>')
|
|
|
|
lines.append(f'{indent}</Popup>')
|
|
|
|
|
|
# --- Attribute emitter ---
|
|
|
|
def emit_attributes(lines, attrs, indent):
|
|
if not attrs or len(attrs) == 0:
|
|
return
|
|
|
|
lines.append(f'{indent}<Attributes>')
|
|
for attr in attrs:
|
|
attr_id = new_id()
|
|
attr_name = str(attr['name'])
|
|
|
|
lines.append(f'{indent}\t<Attribute name="{attr_name}" id="{attr_id}">')
|
|
inner = f'{indent}\t\t'
|
|
|
|
if attr.get('title'):
|
|
emit_mltext(lines, inner, 'Title', str(attr['title']))
|
|
|
|
# Type
|
|
if attr.get('type'):
|
|
emit_type(lines, str(attr['type']), inner)
|
|
else:
|
|
lines.append(f'{inner}<Type/>')
|
|
|
|
if attr.get('main') is True:
|
|
lines.append(f'{inner}<MainAttribute>true</MainAttribute>')
|
|
if attr.get('savedData') is True:
|
|
lines.append(f'{inner}<SavedData>true</SavedData>')
|
|
if attr.get('fillChecking'):
|
|
lines.append(f'{inner}<FillChecking>{attr["fillChecking"]}</FillChecking>')
|
|
|
|
# Columns (for ValueTable/ValueTree)
|
|
if attr.get('columns') and len(attr['columns']) > 0:
|
|
lines.append(f'{inner}<Columns>')
|
|
for col in attr['columns']:
|
|
col_id = new_id()
|
|
lines.append(f'{inner}\t<Column name="{col["name"]}" id="{col_id}">')
|
|
if col.get('title'):
|
|
emit_mltext(lines, f'{inner}\t\t', 'Title', str(col['title']))
|
|
emit_type(lines, str(col.get('type', '')), f'{inner}\t\t')
|
|
lines.append(f'{inner}\t</Column>')
|
|
lines.append(f'{inner}</Columns>')
|
|
|
|
lines.append(f'{indent}\t</Attribute>')
|
|
lines.append(f'{indent}</Attributes>')
|
|
|
|
|
|
# --- Parameter emitter ---
|
|
|
|
def emit_parameters(lines, params, indent):
|
|
if not params or len(params) == 0:
|
|
return
|
|
|
|
lines.append(f'{indent}<Parameters>')
|
|
for param in params:
|
|
lines.append(f'{indent}\t<Parameter name="{param["name"]}">')
|
|
inner = f'{indent}\t\t'
|
|
|
|
emit_type(lines, str(param.get('type', '')), inner)
|
|
|
|
if param.get('key') is True:
|
|
lines.append(f'{inner}<KeyParameter>true</KeyParameter>')
|
|
|
|
lines.append(f'{indent}\t</Parameter>')
|
|
lines.append(f'{indent}</Parameters>')
|
|
|
|
|
|
# --- Command emitter ---
|
|
|
|
def emit_commands(lines, cmds, indent):
|
|
if not cmds or len(cmds) == 0:
|
|
return
|
|
|
|
lines.append(f'{indent}<Commands>')
|
|
for cmd in cmds:
|
|
cmd_id = new_id()
|
|
lines.append(f'{indent}\t<Command name="{cmd["name"]}" id="{cmd_id}">')
|
|
inner = f'{indent}\t\t'
|
|
|
|
if cmd.get('title'):
|
|
emit_mltext(lines, inner, 'Title', str(cmd['title']))
|
|
|
|
if cmd.get('action'):
|
|
lines.append(f'{inner}<Action>{cmd["action"]}</Action>')
|
|
|
|
if cmd.get('shortcut'):
|
|
lines.append(f'{inner}<Shortcut>{cmd["shortcut"]}</Shortcut>')
|
|
|
|
if cmd.get('picture'):
|
|
lines.append(f'{inner}<Picture>')
|
|
lines.append(f'{inner}\t<xr:Ref>{cmd["picture"]}</xr:Ref>')
|
|
lines.append(f'{inner}\t<xr:LoadTransparent>true</xr:LoadTransparent>')
|
|
lines.append(f'{inner}</Picture>')
|
|
|
|
if cmd.get('representation'):
|
|
lines.append(f'{inner}<Representation>{cmd["representation"]}</Representation>')
|
|
|
|
lines.append(f'{indent}\t</Command>')
|
|
lines.append(f'{indent}</Commands>')
|
|
|
|
|
|
# --- Properties emitter ---
|
|
|
|
PROP_MAP = {
|
|
"autoTitle": "AutoTitle",
|
|
"windowOpeningMode": "WindowOpeningMode",
|
|
"commandBarLocation": "CommandBarLocation",
|
|
"saveDataInSettings": "SaveDataInSettings",
|
|
"autoSaveDataInSettings": "AutoSaveDataInSettings",
|
|
"autoTime": "AutoTime",
|
|
"usePostingMode": "UsePostingMode",
|
|
"repostOnWrite": "RepostOnWrite",
|
|
"autoURL": "AutoURL",
|
|
"autoFillCheck": "AutoFillCheck",
|
|
"customizable": "Customizable",
|
|
"enterKeyBehavior": "EnterKeyBehavior",
|
|
"verticalScroll": "VerticalScroll",
|
|
"scalingMode": "ScalingMode",
|
|
"useForFoldersAndItems": "UseForFoldersAndItems",
|
|
"reportResult": "ReportResult",
|
|
"detailsData": "DetailsData",
|
|
"reportFormType": "ReportFormType",
|
|
"autoShowState": "AutoShowState",
|
|
"width": "Width",
|
|
"height": "Height",
|
|
"group": "Group",
|
|
}
|
|
|
|
|
|
def emit_properties(lines, props, indent):
|
|
if not props:
|
|
return
|
|
|
|
for p_name, p_value in props.items():
|
|
xml_name = PROP_MAP.get(p_name)
|
|
if not xml_name:
|
|
# Auto PascalCase
|
|
xml_name = p_name[0].upper() + p_name[1:]
|
|
|
|
# Convert boolean to lowercase
|
|
if isinstance(p_value, bool):
|
|
val = 'true' if p_value else 'false'
|
|
else:
|
|
val = str(p_value)
|
|
lines.append(f'{indent}<{xml_name}>{val}</{xml_name}>')
|
|
|
|
|
|
def main():
|
|
sys.stdout.reconfigure(encoding="utf-8")
|
|
sys.stderr.reconfigure(encoding="utf-8")
|
|
global _next_id
|
|
|
|
parser = argparse.ArgumentParser(description='Compile 1C managed form from JSON', allow_abbrev=False)
|
|
parser.add_argument('-JsonPath', type=str, required=True)
|
|
parser.add_argument('-OutputPath', type=str, required=True)
|
|
args = parser.parse_args()
|
|
|
|
# --- 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 ---
|
|
_next_id = 0
|
|
lines = []
|
|
|
|
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
|
lines.append('<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">')
|
|
|
|
# Title
|
|
form_title = defn.get('title')
|
|
if not form_title and defn.get('properties') and defn['properties'].get('title'):
|
|
form_title = defn['properties']['title']
|
|
if form_title:
|
|
emit_mltext(lines, '\t', 'Title', str(form_title))
|
|
|
|
# Properties (skip 'title' — handled above)
|
|
if defn.get('properties'):
|
|
props_clone = {k: v for k, v in defn['properties'].items() if k != 'title'}
|
|
emit_properties(lines, props_clone, '\t')
|
|
|
|
# CommandSet (excluded commands)
|
|
if defn.get('excludedCommands') and len(defn['excludedCommands']) > 0:
|
|
lines.append('\t<CommandSet>')
|
|
for cmd in defn['excludedCommands']:
|
|
lines.append(f'\t\t<ExcludedCommand>{cmd}</ExcludedCommand>')
|
|
lines.append('\t</CommandSet>')
|
|
|
|
# AutoCommandBar (always present, id=-1)
|
|
lines.append('\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">')
|
|
lines.append('\t\t<HorizontalAlign>Right</HorizontalAlign>')
|
|
lines.append('\t\t<Autofill>false</Autofill>')
|
|
lines.append('\t</AutoCommandBar>')
|
|
|
|
# Events
|
|
if defn.get('events'):
|
|
for evt_name in defn['events']:
|
|
if evt_name not in KNOWN_FORM_EVENTS:
|
|
print(f"[WARN] Unknown form event '{evt_name}'. Known: {', '.join(KNOWN_FORM_EVENTS)}")
|
|
lines.append('\t<Events>')
|
|
for evt_name, evt_handler in defn['events'].items():
|
|
lines.append(f'\t\t<Event name="{evt_name}">{evt_handler}</Event>')
|
|
lines.append('\t</Events>')
|
|
|
|
# ChildItems (elements)
|
|
if defn.get('elements') and len(defn['elements']) > 0:
|
|
lines.append('\t<ChildItems>')
|
|
for el in defn['elements']:
|
|
emit_element(lines, el, '\t\t')
|
|
lines.append('\t</ChildItems>')
|
|
|
|
# Attributes
|
|
emit_attributes(lines, defn.get('attributes'), '\t')
|
|
|
|
# Parameters
|
|
emit_parameters(lines, defn.get('parameters'), '\t')
|
|
|
|
# Commands
|
|
emit_commands(lines, defn.get('commands'), '\t')
|
|
|
|
# Close
|
|
lines.append('</Form>')
|
|
|
|
# --- 3. Write output ---
|
|
out_path = args.OutputPath
|
|
if not os.path.isabs(out_path):
|
|
out_path = os.path.join(os.getcwd(), out_path)
|
|
out_dir = os.path.dirname(out_path)
|
|
if out_dir and not os.path.exists(out_dir):
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
|
|
content = '\n'.join(lines) + '\n'
|
|
write_utf8_bom(out_path, content)
|
|
|
|
# --- 4. Auto-register form in parent object XML ---
|
|
# Infer parent from OutputPath: .../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml
|
|
form_xml_dir = os.path.dirname(out_path) # Ext
|
|
form_name_dir = os.path.dirname(form_xml_dir) # FormName
|
|
forms_dir = os.path.dirname(form_name_dir) # Forms
|
|
object_dir = os.path.dirname(forms_dir) # ObjectName
|
|
type_plural_dir = os.path.dirname(object_dir) # TypePlural
|
|
|
|
form_name = os.path.basename(form_name_dir)
|
|
object_name = os.path.basename(object_dir)
|
|
forms_leaf = os.path.basename(forms_dir)
|
|
|
|
if forms_leaf == 'Forms':
|
|
object_xml_path = os.path.join(type_plural_dir, f'{object_name}.xml')
|
|
if os.path.exists(object_xml_path):
|
|
with open(object_xml_path, 'r', encoding='utf-8-sig') as f:
|
|
raw_text = f.read()
|
|
|
|
# Check if already registered
|
|
if f'<Form>{form_name}</Form>' not in raw_text:
|
|
# Insert before </ChildObjects>
|
|
if '</ChildObjects>' in raw_text:
|
|
insert_line = f'\t\t\t<Form>{form_name}</Form>\n'
|
|
raw_text = raw_text.replace('</ChildObjects>', insert_line + '\t\t</ChildObjects>', 1)
|
|
elif '<ChildObjects/>' in raw_text:
|
|
replacement = f'<ChildObjects>\n\t\t\t<Form>{form_name}</Form>\n\t\t</ChildObjects>'
|
|
raw_text = raw_text.replace('<ChildObjects/>', replacement, 1)
|
|
|
|
write_utf8_bom(object_xml_path, raw_text)
|
|
print(f" Registered: <Form>{form_name}</Form> in {object_name}.xml")
|
|
|
|
# --- 5. Summary ---
|
|
el_count = _next_id
|
|
print(f"[OK] Compiled: {args.OutputPath}")
|
|
print(f" Elements+IDs: {el_count}")
|
|
if defn.get('attributes'):
|
|
print(f" Attributes: {len(defn['attributes'])}")
|
|
if defn.get('commands'):
|
|
print(f" Commands: {len(defn['commands'])}")
|
|
if defn.get('parameters'):
|
|
print(f" Parameters: {len(defn['parameters'])}")
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|