feat(form-decompile,form-compile): контент командных панелей таблицы — commandBar/contextMenu (companion tier 2)

Крупнейший левередж-кластер. Companion-панели элемента (AutoCommandBar/ContextMenu)
теперь несут контент как СВОЙСТВА:
  - commandBar  → <AutoCommandBar> (командная панель)
  - contextMenu → <ContextMenu>   (контекстное меню)

Значение: массив = shorthand для { children }; объект { autofill?, horizontalAlign?,
children[] }. children — обычная грамматика button/buttonGroup/popup.

Forgiving-синонимы (commandBar ← autoCommandBar/AutoCommandBar/autoCmdBar/cmdBar/
КоманднаяПанель; contextMenu ← ContextMenu/КонтекстноеМеню). Разведение «тип-элемент
vs панель-свойство» — по ТИПУ значения: строка = элемент-тип в дереве (cmdBar:"Имя"),
объект/массив = companion-панель этого элемента. Тип-синонимы применяются только к
строковому значению. Механизм общий (любой элемент), декомпилятор захватывает в
Decompile-Element, компилятор — Emit-CompanionPanel.

Новый ключ кнопки commandName — глобальная команда «как есть» (CommonCommand.X,
Catalog.X.Command.Y) без обёртки Form. (раньше попадала в command и ошибочно
оборачивалась в Form.Command.). stdCommand/command без изменений.

Декомпилятор: для дин-список-таблицы пустой AutoCommandBar(autofill=false) не пишет
commandBar (восстановит heuristic) — без шума. tableAutofill остаётся shorthand,
commandBar имеет приоритет.

TOTAL diff lines выборки 2.17: 7560 → 5293 (-2267), match 11 → 13,
cascade LOST 3414 → 1805. Table>ContextMenu/Table>AutoCommandBar ушли из топа impact.
Снапшот table сертифицирован в 1С (8.3.24); регресс form-compile 33/33 зелёный
на ps + python. decompile v0.29, compile v1.48.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-06 21:30:08 +03:00
parent d484a5b7ec
commit 7c765137db
6 changed files with 336 additions and 62 deletions
@@ -1,4 +1,4 @@
# form-compile v1.47 — Compile 1C managed form from JSON or object metadata
# form-compile v1.48 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -2264,6 +2264,38 @@ function Emit-Companion {
X "$indent</$tag>"
}
# Companion-командная-панель (ContextMenu/AutoCommandBar) с контентом: { autofill?, children?[] }
# или массив = shorthand для { children }. Пусто/нет → self-closing companion (как Emit-Companion).
# Дети — обычная грамматика button/buttonGroup/popup (Emit-Element, inCmdBar).
function Emit-CompanionPanel {
param([string]$tag, [string]$name, [string]$indent, $panel)
$id = New-Id
$autofill = $null
$children = $null
$halign = $null
if ($panel -is [array]) {
$children = $panel
} elseif ($null -ne $panel) {
if ($null -ne $panel.PSObject.Properties['autofill'] -and $null -ne $panel.autofill) { $autofill = [bool]$panel.autofill }
if ($null -ne $panel.PSObject.Properties['horizontalAlign'] -and "$($panel.horizontalAlign)" -ne '') { $halign = "$($panel.horizontalAlign)" }
$children = $panel.children
}
$hasChildren = $children -and @($children).Count -gt 0
if ($null -eq $autofill -and -not $hasChildren -and -not $halign) {
X "$indent<$tag name=`"$name`" id=`"$id`"/>"
return
}
X "$indent<$tag name=`"$name`" id=`"$id`">"
if ($halign) { X "$indent`t<HorizontalAlign>$halign</HorizontalAlign>" }
if ($null -ne $autofill) { X "$indent`t<Autofill>$(if ($autofill){'true'}else{'false'})</Autofill>" }
if ($hasChildren) {
X "$indent`t<ChildItems>"
foreach ($c in @($children)) { Emit-Element -el $c -indent "$indent`t`t" -inCmdBar $true }
X "$indent`t</ChildItems>"
}
X "$indent</$tag>"
}
# Табличный addition (СтрокаПоиска/СостояниеПросмотра/УправлениеПоиском) с AdditionSource.
# Item = имя таблицы, Type фиксирован по виду; внутри — companion ContextMenu/ExtendedTooltip.
function Emit-TableAddition {
@@ -2283,8 +2315,14 @@ function Emit-TableAddition {
function Emit-Element {
param($el, [string]$indent, [bool]$inCmdBar = $false)
# Companion-панели (объект/массив-значение) → commandBar/contextMenu, до тип-синонимов.
Normalize-PanelSynonyms $el
# Silent synonyms: model often writes XML name or Russian (ПолеПереключателя/RadioButtonField → radio).
# Maps any synonym to canonical short DSL key.
# commandBar/autoCommandBar/КоманднаяПанель → тип-элемент ТОЛЬКО при строковом значении (имя);
# объект/массив уже отнесён к панель-свойству выше.
$strOnlyKeys = @('commandBar','autoCommandBar','КоманднаяПанель')
$synonyms = @{
"commandBar" = "cmdBar"
"autoCommandBar" = "autoCmdBar"
@@ -2324,6 +2362,7 @@ function Emit-Element {
}
foreach ($pair in $synonyms.GetEnumerator()) {
if ($null -ne $el.PSObject.Properties[$pair.Key] -and $null -eq $el.PSObject.Properties[$pair.Value]) {
if ($strOnlyKeys -contains $pair.Key -and -not ($el.($pair.Key) -is [string])) { continue }
$val = $el.($pair.Key)
$el.PSObject.Properties.Remove($pair.Key) | Out-Null
$el | Add-Member -NotePropertyName $pair.Value -NotePropertyValue $val -Force
@@ -2357,6 +2396,8 @@ function Emit-Element {
"radioButtonType"=1;"choiceList"=1;"columnsCount"=1;"checkBoxType"=1;"editMode"=1
# naming & binding
"name"=1;"path"=1;"title"=1;"tooltip"=1;"tooltipRepresentation"=1;"extendedTooltip"=1
# companion-панели (свойства): командная панель + контекстное меню
"commandBar"=1;"contextMenu"=1
# visibility & state
"visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1;"userVisible"=1
# events ("events" — основной формат; on/handlers — legacy, принимаются ради совместимости)
@@ -2393,7 +2434,7 @@ function Emit-Element {
# pages-specific
"pagesRepresentation"=1
# button-specific
"type"=1;"command"=1;"stdCommand"=1;"defaultButton"=1;"locationInCommandBar"=1
"type"=1;"command"=1;"commandName"=1;"stdCommand"=1;"defaultButton"=1;"locationInCommandBar"=1
# picture/decoration
"src"=1;"valuesPicture"=1;"loadTransparent"=1
# cmdBar-specific
@@ -2709,7 +2750,7 @@ function Emit-Input {
}
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "input"
@@ -2742,7 +2783,7 @@ function Emit-Check {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "check"
@@ -2967,7 +3008,7 @@ function Emit-Radio {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "radio"
@@ -3007,7 +3048,7 @@ function Emit-Label {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "label"
@@ -3033,7 +3074,7 @@ function Emit-LabelField {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "labelField"
@@ -3136,9 +3177,11 @@ function Emit-Table {
}
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
# AutoCommandBar — with optional Autofill control
if ($null -ne $el.tableAutofill) {
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
# AutoCommandBar: приоритет commandBar-свойства (контент); иначе tableAutofill-shorthand; иначе пусто.
if ($null -ne $el.commandBar) {
Emit-CompanionPanel -tag "AutoCommandBar" -name "${name}КоманднаяПанель" -indent $inner -panel $el.commandBar
} elseif ($null -ne $el.tableAutofill) {
$acbId = New-Id
X "$inner<AutoCommandBar name=`"${name}КоманднаяПанель`" id=`"$acbId`">"
$afVal = if ($el.tableAutofill) { "true" } else { "false" }
@@ -3285,6 +3328,10 @@ function Emit-Button {
if ($el.command) {
X "$inner<CommandName>Form.Command.$($el.command)</CommandName>"
}
# commandName — глобальная команда «как есть» (CommonCommand.X, Catalog.X.Command.Y …), без обёртки Form.
if ($el.commandName -and -not $el.command) {
X "$inner<CommandName>$($el.commandName)</CommandName>"
}
if ($el.stdCommand) {
$sc = "$($el.stdCommand)"
if ($sc -match '^(.+)\.(.+)$') {
@@ -3294,7 +3341,7 @@ function Emit-Button {
}
}
$btnAuto = -not ($el.command -or $el.stdCommand)
$btnAuto = -not ($el.command -or $el.commandName -or $el.stdCommand)
Emit-Title -el $el -name $name -indent $inner -auto:$btnAuto
Emit-CommonFlags -el $el -indent $inner
@@ -3347,7 +3394,7 @@ function Emit-PictureDecoration {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "picture"
@@ -3381,7 +3428,7 @@ function Emit-PictureField {
Emit-Layout -el $el -indent $inner
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "picField"
@@ -3423,7 +3470,7 @@ function Emit-Calendar {
if ($null -ne $el.showMonthsPanel) { $v = if ($el.showMonthsPanel) { "true" } else { "false" }; X "$inner<ShowMonthsPanel>$v</ShowMonthsPanel>" }
# Companions
Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner
Emit-CompanionPanel -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner -panel $el.contextMenu
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-Events -el $el -elementName $name -indent $inner -typeKey "calendar"
@@ -3755,17 +3802,59 @@ function Emit-Properties {
# --- 11b. Pre-pass: synonyms, main attribute inference, heuristics, autoCmdBar extraction ---
# Companion-панели как СВОЙСТВА элемента (значение объект/массив): любой знакомый
# синоним → каноника commandBar / contextMenu. Разводим с одноимёнными ТИПАМИ-элементами
# по типу значения: строка = элемент-тип (имя), объект/массив = панель-свойство.
function Normalize-PanelSynonyms {
param($el)
if ($null -eq $el) { return }
$panelSyns = @{
'commandBar' = @('commandBar','autoCommandBar','AutoCommandBar','autoCmdBar','cmdBar','КоманднаяПанель')
'contextMenu' = @('contextMenu','ContextMenu','КонтекстноеМеню')
}
foreach ($canon in $panelSyns.Keys) {
foreach ($syn in $panelSyns[$canon]) {
$p = $el.PSObject.Properties[$syn]
if ($null -ne $p -and ($p.Value -is [array] -or $p.Value -is [System.Management.Automation.PSCustomObject])) {
if ($syn -ne $canon -and $null -eq $el.PSObject.Properties[$canon]) {
$v = $p.Value
$el.PSObject.Properties.Remove($syn) | Out-Null
$el | Add-Member -NotePropertyName $canon -NotePropertyValue $v -Force
}
break
}
}
}
}
function Normalize-ElementSynonyms {
param($el)
if ($null -eq $el) { return }
$synonyms = @{ "commandBar" = "cmdBar"; "autoCommandBar" = "autoCmdBar"; "extTooltip" = "extendedTooltip" }
foreach ($pair in $synonyms.GetEnumerator()) {
if ($null -ne $el.PSObject.Properties[$pair.Key] -and $null -eq $el.PSObject.Properties[$pair.Value]) {
Normalize-PanelSynonyms $el
# Тип-синонимы (commandBar/autoCommandBar → элемент-тип) применяем ТОЛЬКО к строковому
# значению (имя элемента); объект/массив уже отнесён к панель-свойству выше.
$typeSyn = @{ "commandBar" = "cmdBar"; "autoCommandBar" = "autoCmdBar" }
foreach ($pair in $typeSyn.GetEnumerator()) {
$src = $el.PSObject.Properties[$pair.Key]
if ($null -ne $src -and ($src.Value -is [string]) -and $null -eq $el.PSObject.Properties[$pair.Value]) {
$val = $el.($pair.Key)
$el.PSObject.Properties.Remove($pair.Key) | Out-Null
$el | Add-Member -NotePropertyName $pair.Value -NotePropertyValue $val -Force
}
}
if ($el.PSObject.Properties["extTooltip"] -and $null -eq $el.PSObject.Properties["extendedTooltip"]) {
$val = $el.extTooltip
$el.PSObject.Properties.Remove("extTooltip") | Out-Null
$el | Add-Member -NotePropertyName "extendedTooltip" -NotePropertyValue $val -Force
}
# Рекурсия в детей панелей (commandBar/contextMenu) — нормализуем кнопки/группы внутри
foreach ($pk in @('commandBar','contextMenu')) {
$pp = $el.PSObject.Properties[$pk]
if ($null -ne $pp) {
$kids = if ($pp.Value -is [array]) { $pp.Value } elseif ($null -ne $pp.Value) { $pp.Value.children } else { $null }
if ($kids) { foreach ($child in $kids) { Normalize-ElementSynonyms $child } }
}
}
if ($el.PSObject.Properties["children"] -and $el.children) {
foreach ($child in $el.children) { Normalize-ElementSynonyms $child }
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.47 — Compile 1C managed form from JSON or object metadata
# form-compile v1.48 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -1779,7 +1779,8 @@ KNOWN_KEYS = {
"commandBarLocation", "searchStringLocation", "viewStatusLocation", "searchControlLocation",
"excludedCommands",
"pagesRepresentation",
"type", "command", "stdCommand", "defaultButton", "locationInCommandBar",
"type", "command", "commandName", "stdCommand", "defaultButton", "locationInCommandBar",
"commandBar", "contextMenu",
"src", "valuesPicture", "loadTransparent",
"autofill",
"choiceMode", "initialTreeView", "enableDrag", "enableStartDrag",
@@ -1834,6 +1835,28 @@ ELEMENT_TYPE_SYNONYMS = {
"ВсплывающееМеню": "popup",
}
# Тип-синонимы, применяемые ТОЛЬКО к строковому значению (имя элемента); объект/массив
# у того же слова — companion-панель (свойство), см. normalize_panel_synonyms.
STR_ONLY_TYPE_SYNONYMS = {"commandBar", "autoCommandBar", "КоманднаяПанель"}
# Companion-панели как СВОЙСТВА (значение объект/массив): синоним → каноника.
PANEL_SYNONYMS = {
'commandBar': ['commandBar', 'autoCommandBar', 'AutoCommandBar', 'autoCmdBar', 'cmdBar', 'КоманднаяПанель'],
'contextMenu': ['contextMenu', 'ContextMenu', 'КонтекстноеМеню'],
}
def normalize_panel_synonyms(el):
if not isinstance(el, dict):
return
for canon, syns in PANEL_SYNONYMS.items():
for syn in syns:
if syn in el and isinstance(el[syn], (list, dict)):
if syn != canon and canon not in el:
el[canon] = el.pop(syn)
break
# Maps Russian/English root of typed reference path to canonical English root
REF_ROOT_SYNONYMS = {
"Перечисление": "Enum",
@@ -2045,6 +2068,38 @@ def emit_companion(lines, tag, name, indent, content=None):
lines.append(f'{indent}</{tag}>')
def emit_companion_panel(lines, tag, name, indent, panel):
# Companion-командная-панель (ContextMenu/AutoCommandBar) с контентом: { autofill?, horizontalAlign?, children?[] }
# или массив = shorthand для { children }. Пусто/нет → self-closing.
cid = new_id()
autofill = None
halign = None
children = None
if isinstance(panel, list):
children = panel
elif panel is not None:
if panel.get('autofill') is not None:
autofill = bool(panel.get('autofill'))
if panel.get('horizontalAlign'):
halign = str(panel.get('horizontalAlign'))
children = panel.get('children')
has_children = bool(children) and len(children) > 0
if autofill is None and not has_children and not halign:
lines.append(f'{indent}<{tag} name="{name}" id="{cid}"/>')
return
lines.append(f'{indent}<{tag} name="{name}" id="{cid}">')
if halign:
lines.append(f'{indent}\t<HorizontalAlign>{halign}</HorizontalAlign>')
if autofill is not None:
lines.append(f'{indent}\t<Autofill>{"true" if autofill else "false"}</Autofill>')
if has_children:
lines.append(f'{indent}\t<ChildItems>')
for c in children:
emit_element(lines, c, f'{indent}\t\t', in_cmd_bar=True)
lines.append(f'{indent}\t</ChildItems>')
lines.append(f'{indent}</{tag}>')
def emit_table_addition(lines, tag, table_name, name_suffix, src_type, indent):
# Табличный addition с AdditionSource (Item = имя таблицы, Type фиксирован).
add_name = f'{table_name}{name_suffix}'
@@ -2372,9 +2427,15 @@ def emit_type(lines, type_str, indent):
# --- Element emitters ---
def emit_element(lines, el, indent, in_cmd_bar=False):
# Silent synonyms: model often writes XML name or Russian (ПолеПереключателя/RadioButtonField → radio)
# Companion-панели (объект/массив-значение) → commandBar/contextMenu, до тип-синонимов.
normalize_panel_synonyms(el)
# Silent synonyms: model often writes XML name or Russian (ПолеПереключателя/RadioButtonField → radio).
# commandBar/autoCommandBar/КоманднаяПанель → тип-элемент ТОЛЬКО при строковом значении (имя).
for src, dst in ELEMENT_TYPE_SYNONYMS.items():
if src in el and dst not in el:
if src in STR_ONLY_TYPE_SYNONYMS and not isinstance(el[src], str):
continue
el[dst] = el.pop(src)
type_key = None
@@ -2562,7 +2623,7 @@ def emit_input(lines, el, name, eid, indent):
emit_mltext(lines, inner, 'InputHint', 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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'input')
@@ -2595,7 +2656,7 @@ def emit_check(lines, el, name, eid, indent):
emit_layout(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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'check')
@@ -2654,7 +2715,7 @@ def emit_radio_button_field(lines, el, name, eid, indent):
emit_layout(lines, el, inner)
emit_companion(lines, 'ContextMenu', f'{name}КонтекстноеМеню', inner)
emit_companion_panel(lines, 'ContextMenu', f'{name}КонтекстноеМеню', inner, el.get('contextMenu'))
emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', inner, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'radio')
@@ -2693,7 +2754,7 @@ def emit_label(lines, el, name, eid, indent):
emit_layout(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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'label')
@@ -2721,7 +2782,7 @@ def emit_label_field(lines, el, name, eid, indent):
emit_layout(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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'labelField')
@@ -2834,9 +2895,11 @@ def emit_table(lines, el, name, eid, indent):
lines.append(f'{inner}</CommandSet>')
# 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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
# AutoCommandBar — with optional Autofill control
if el.get('tableAutofill') is not None:
if el.get('commandBar') is not None:
emit_companion_panel(lines, 'AutoCommandBar', f'{name}\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c', inner, el.get('commandBar'))
elif 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'
@@ -2963,6 +3026,9 @@ def emit_button(lines, el, name, eid, indent, in_cmd_bar=False):
# CommandName
if el.get('command'):
lines.append(f'{inner}<CommandName>Form.Command.{el["command"]}</CommandName>')
# commandName — глобальная команда «как есть» (CommonCommand.X, Catalog.X.Command.Y …), без обёртки Form.
if el.get('commandName') and not el.get('command'):
lines.append(f'{inner}<CommandName>{el["commandName"]}</CommandName>')
if el.get('stdCommand'):
sc = str(el['stdCommand'])
m = re.match(r'^(.+)\.(.+)$', sc)
@@ -2971,7 +3037,7 @@ def emit_button(lines, el, name, eid, indent, in_cmd_bar=False):
else:
lines.append(f'{inner}<CommandName>Form.StandardCommand.{sc}</CommandName>')
emit_title(lines, el, name, inner, auto=not (el.get('command') or el.get('stdCommand')))
emit_title(lines, el, name, inner, auto=not (el.get('command') or el.get('commandName') or el.get('stdCommand')))
emit_common_flags(lines, el, inner)
if el.get('defaultButton') is True:
@@ -3019,7 +3085,7 @@ def emit_picture_decoration(lines, el, name, eid, indent):
emit_layout(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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'picture')
@@ -3053,7 +3119,7 @@ def emit_picture_field(lines, el, name, eid, indent):
emit_layout(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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'picField')
@@ -3091,7 +3157,7 @@ def emit_calendar(lines, el, name, eid, indent):
lines.append(f'{inner}<ShowMonthsPanel>{"true" if el["showMonthsPanel"] else "false"}</ShowMonthsPanel>')
# 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_panel(lines, 'ContextMenu', f'{name}\u041a\u043e\u043d\u0442\u0435\u043a\u0441\u0442\u043d\u043e\u0435\u041c\u0435\u043d\u044e', inner, el.get('contextMenu'))
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, el.get('extendedTooltip'))
emit_events(lines, el, name, inner, 'calendar')
@@ -3646,10 +3712,22 @@ def main():
def _normalize_synonyms(el):
if not isinstance(el, dict):
return
# Companion-панели (объект/массив-значение) → commandBar/contextMenu
normalize_panel_synonyms(el)
# Тип-синонимы: commandBar/autoCommandBar → элемент-тип ТОЛЬКО при строковом значении
synonyms = {'commandBar': 'cmdBar', 'autoCommandBar': 'autoCmdBar', 'extTooltip': 'extendedTooltip'}
for src, dst in synonyms.items():
if src in el and dst not in el:
if src in STR_ONLY_TYPE_SYNONYMS and not isinstance(el[src], str):
continue
el[dst] = el.pop(src)
# Рекурсия в детей панелей (commandBar/contextMenu)
for pk in ('commandBar', 'contextMenu'):
pv = el.get(pk)
kids = pv if isinstance(pv, list) else (pv.get('children') if isinstance(pv, dict) else None)
if isinstance(kids, list):
for child in kids:
_normalize_synonyms(child)
if isinstance(el.get('children'), list):
for child in el['children']:
_normalize_synonyms(child)
@@ -1,4 +1,4 @@
# form-decompile v0.28 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.29 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -883,6 +883,28 @@ function Decompile-Children {
return ,@($list)
}
# Инверсия Emit-CompanionPanel: companion-командная-панель (ContextMenu/AutoCommandBar) с контентом
# → { autofill?, horizontalAlign?, children?[] } либо $null, если companion пустой (self-closing).
# $isDynListTable: для дин-список-таблицы пустой AutoCommandBar с autofill=false восстановит
# эвристика компилятора — молчим (как с tableAutofill), чтобы не плодить ключ.
function Decompile-CompanionPanel {
param($node, [string]$tag, [bool]$isDynListTable = $false)
$p = $node.SelectSingleNode("lf:$tag", $ns)
if (-not $p) { return $null }
$autofillRaw = Get-Child $p 'Autofill'
$halign = Get-Child $p 'HorizontalAlign'
$kids = Decompile-Children $p
$hasKids = $kids -and @($kids).Count -gt 0
if (-not $hasKids -and $null -eq $autofillRaw -and -not $halign) { return $null }
if ($isDynListTable -and $tag -eq 'AutoCommandBar' -and -not $hasKids -and -not $halign -and $autofillRaw -eq 'false') { return $null }
$o = [ordered]@{}
if ($halign) { $o['horizontalAlign'] = $halign }
if ($autofillRaw -eq 'false') { $o['autofill'] = $false }
elseif ($autofillRaw -eq 'true') { $o['autofill'] = $true }
if ($hasKids) { $o['children'] = $kids }
return $o
}
function Decompile-Element {
param($node)
$tag = $node.LocalName
@@ -1091,7 +1113,7 @@ function Decompile-Element {
if ($cmd -match '^Form\.Command\.(.+)$') { $obj['command'] = $matches[1] }
elseif ($cmd -match '^Form\.StandardCommand\.(.+)$') { $obj['stdCommand'] = $matches[1] }
elseif ($cmd -match '^Form\.Item\.(.+)\.StandardCommand\.(.+)$') { $obj['stdCommand'] = "$($matches[1]).$($matches[2])" }
else { $obj['command'] = $cmd }
else { $obj['commandName'] = $cmd }
}
Add-CommonProps $obj $node $name
$type = Get-Child $node 'Type'
@@ -1129,7 +1151,7 @@ function Decompile-Element {
if (-not $obj.Contains('title')) {
$autoTitle = $false
if ($tag -in @('LabelDecoration','Page','Popup')) { $autoTitle = $true }
elseif ($tag -eq 'Button') { $autoTitle = -not ($obj.Contains('command') -or $obj.Contains('stdCommand')) }
elseif ($tag -eq 'Button') { $autoTitle = -not ($obj.Contains('command') -or $obj.Contains('commandName') -or $obj.Contains('stdCommand')) }
elseif ($tag -in @('InputField','CheckBoxField','RadioButtonField','LabelField','Table','CalendarField')) { $autoTitle = -not $obj.Contains('path') }
if ($autoTitle) { $obj['title'] = '' }
}
@@ -1137,6 +1159,12 @@ function Decompile-Element {
# extendedTooltip: контент companion <ExtendedTooltip><Title> (любой элемент)
$etTitle = $node.SelectSingleNode("lf:ExtendedTooltip/lf:Title", $ns)
if ($etTitle) { $et = Get-MLFormattedValue $etTitle; if ($null -ne $et) { $obj['extendedTooltip'] = $et } }
# companion-панели с контентом: AutoCommandBar → commandBar, ContextMenu → contextMenu (любой элемент)
$isDynListTable = ($tag -eq 'Table') -and (Has-Child $node 'UpdateOnDataChange')
$cb = Decompile-CompanionPanel $node 'AutoCommandBar' $isDynListTable
if ($null -ne $cb) { $obj['commandBar'] = $cb }
$cm = Decompile-CompanionPanel $node 'ContextMenu'
if ($null -ne $cm) { $obj['contextMenu'] = $cm }
return $obj
}
+35 -1
View File
@@ -115,6 +115,8 @@
| `disabled` | bool | `true``<Enabled>false</Enabled>` |
| `readOnly` | bool | `true``<ReadOnly>true</ReadOnly>` |
| `userVisible` | bool/object | Пользовательская видимость по ролям (`<UserVisible>`). См. §4.1c. Отсутствие = виден всем |
| `commandBar` | object/array | Командная панель элемента (companion `<AutoCommandBar>`) с контентом. См. §4.1d |
| `contextMenu` | object/array | Контекстное меню элемента (companion `<ContextMenu>`) с контентом. См. §4.1d |
| `events` | object | Обработчики событий: `{ "ИмяСобытия": "ИмяОбработчика" }` — тот же формат, что у событий формы (§3). Значение `null` → имя по конвенции (§4.2). См. §4.2 |
| `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) |
| `tooltip` | string/object | Всплывающая подсказка элемента (`<ToolTip>`). Строка → ru, объект `{ "ru": …, "en": … }` → мультиязычный (как `title`). Эмитится сразу после `title` |
@@ -161,6 +163,37 @@
{ "name": "Команда", "use": { "common": false, "roles": { "Роль.Бухгалтер": true } } }
```
### 4.1d. Companion-панели элемента (`commandBar` / `contextMenu`)
Командная панель (`<AutoCommandBar>`) и контекстное меню (`<ContextMenu>`) элемента — это
companion-панели с собственным контентом. Оба несут одну грамматику.
| Ключ | XML companion | Forgiving-синонимы (при объект/массив-значении) |
|------|---------------|--------------------------------------------------|
| `commandBar` | `<AutoCommandBar>` | `autoCommandBar`, `AutoCommandBar`, `autoCmdBar`, `cmdBar`, `КоманднаяПанель` |
| `contextMenu` | `<ContextMenu>` | `ContextMenu`, `КонтекстноеМеню` |
**Значение** (обе формы):
- массив `[ … ]` → shorthand для `{ "children": [ … ] }`;
- объект `{ "autofill"?: bool, "children": [ … ] }` (+ `horizontalAlign` у `commandBar`).
`children` — обычная грамматика кнопок: `button` (с `command`/`commandName`/`stdCommand`), `buttonGroup`, `popup`.
- `autofill`: отсутствует → дефолт платформы (тег не эмитим); `false` → подавить автозаполнение панели.
- Отсутствие свойства целиком → пустой companion (как обычно).
**Разведение тип-элемента и панель-свойства — по типу значения:** `cmdBar: "Имя"` (строка) — это
отдельный элемент-панель в дереве (§4.3); `commandBar: { … }` (объект/массив) — companion-панель *данного*
элемента. Поэтому модель может писать панель таблицы любым знакомым словом.
```jsonc
{ "table": "Список", "path": "Список",
"commandBar": { "autofill": false, "children": [
{ "button": "Создать", "command": "СоздатьЭлемент" } ] },
"contextMenu": { "children": [
{ "button": "Карта", "commandName": "CommonCommand.КартаМаршрута" } ] } }
```
### 4.1a. Общие layout-свойства
Применимы к любому элементу (размеры, растягивание, выравнивание внутри родителя). Эмитятся только при указании.
@@ -447,7 +480,8 @@ Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `Tabs
| Свойство | Тип | Описание |
|----------|-----|----------|
| `command` | string | Имя команды формы (→ `Form.Command.<name>`) |
| `stdCommand` | string | Стандартная команда (→ `Form.StandardCommand.<name>`) |
| `commandName` | string | Глобальная команда «как есть» (`CommonCommand.X`, `Catalog.X.Command.Y` …) — без обёртки `Form.` |
| `stdCommand` | string | Стандартная команда (→ `Form.StandardCommand.<name>`; `X.Y``Form.Item.X.StandardCommand.Y`) |
| `type` | string | `usual`, `hyperlink`, `commandBar` |
| `defaultButton` | bool | Кнопка по умолчанию |
| `picture` | string | Ссылка на картинку (`StdPicture.Name`) |
@@ -21,60 +21,82 @@
<ExcludedCommand>MoveUp</ExcludedCommand>
<ExcludedCommand>MoveDown</ExcludedCommand>
</CommandSet>
<ContextMenu name="ДанныеКонтекстноеМеню" id="2"/>
<AutoCommandBar name="ДанныеКоманднаяПанель" id="3"/>
<ExtendedTooltip name="ДанныеРасширеннаяПодсказка" id="4"/>
<SearchStringAddition name="ДанныеСтрокаПоиска" id="5">
<ContextMenu name="ДанныеКонтекстноеМеню" id="2">
<ChildItems>
<ButtonGroup name="МенюГруппа" id="3">
<ExtendedTooltip name="МенюГруппаРасширеннаяПодсказка" id="4"/>
<ChildItems>
<Button name="МенюОбновить" id="5">
<Type>CommandBarButton</Type>
<CommandName>Form.Command.Обновить</CommandName>
<ExtendedTooltip name="МенюОбновитьРасширеннаяПодсказка" id="6"/>
</Button>
</ChildItems>
</ButtonGroup>
</ChildItems>
</ContextMenu>
<AutoCommandBar name="ДанныеКоманднаяПанель" id="7">
<Autofill>false</Autofill>
<ChildItems>
<Button name="ПанельОбновить" id="8">
<Type>CommandBarButton</Type>
<CommandName>Form.Command.Обновить</CommandName>
<ExtendedTooltip name="ПанельОбновитьРасширеннаяПодсказка" id="9"/>
</Button>
</ChildItems>
</AutoCommandBar>
<ExtendedTooltip name="ДанныеРасширеннаяПодсказка" id="10"/>
<SearchStringAddition name="ДанныеСтрокаПоиска" id="11">
<AdditionSource>
<Item>Данные</Item>
<Type>SearchStringRepresentation</Type>
</AdditionSource>
<ContextMenu name="ДанныеСтрокаПоискаКонтекстноеМеню" id="6"/>
<ExtendedTooltip name="ДанныеСтрокаПоискаРасширеннаяПодсказка" id="7"/>
<ContextMenu name="ДанныеСтрокаПоискаКонтекстноеМеню" id="12"/>
<ExtendedTooltip name="ДанныеСтрокаПоискаРасширеннаяПодсказка" id="13"/>
</SearchStringAddition>
<ViewStatusAddition name="ДанныеСостояниеПросмотра" id="8">
<ViewStatusAddition name="ДанныеСостояниеПросмотра" id="14">
<AdditionSource>
<Item>Данные</Item>
<Type>ViewStatusRepresentation</Type>
</AdditionSource>
<ContextMenu name="ДанныеСостояниеПросмотраКонтекстноеМеню" id="9"/>
<ExtendedTooltip name="ДанныеСостояниеПросмотраРасширеннаяПодсказка" id="10"/>
<ContextMenu name="ДанныеСостояниеПросмотраКонтекстноеМеню" id="15"/>
<ExtendedTooltip name="ДанныеСостояниеПросмотраРасширеннаяПодсказка" id="16"/>
</ViewStatusAddition>
<SearchControlAddition name="ДанныеУправлениеПоиском" id="11">
<SearchControlAddition name="ДанныеУправлениеПоиском" id="17">
<AdditionSource>
<Item>Данные</Item>
<Type>SearchControl</Type>
</AdditionSource>
<ContextMenu name="ДанныеУправлениеПоискомКонтекстноеМеню" id="12"/>
<ExtendedTooltip name="ДанныеУправлениеПоискомРасширеннаяПодсказка" id="13"/>
<ContextMenu name="ДанныеУправлениеПоискомКонтекстноеМеню" id="18"/>
<ExtendedTooltip name="ДанныеУправлениеПоискомРасширеннаяПодсказка" id="19"/>
</SearchControlAddition>
<ChildItems>
<InputField name="Дата" id="14">
<InputField name="Дата" id="20">
<DataPath>Данные.Дата</DataPath>
<ContextMenu name="ДатаКонтекстноеМеню" id="15"/>
<ExtendedTooltip name="ДатаРасширеннаяПодсказка" id="16"/>
<ContextMenu name="ДатаКонтекстноеМеню" id="21"/>
<ExtendedTooltip name="ДатаРасширеннаяПодсказка" id="22"/>
</InputField>
<InputField name="Сумма" id="17">
<InputField name="Сумма" id="23">
<DataPath>Данные.Сумма</DataPath>
<ContextMenu name="СуммаКонтекстноеМеню" id="18"/>
<ExtendedTooltip name="СуммаРасширеннаяПодсказка" id="19"/>
<ContextMenu name="СуммаКонтекстноеМеню" id="24"/>
<ExtendedTooltip name="СуммаРасширеннаяПодсказка" id="25"/>
</InputField>
<InputField name="Комментарий" id="20">
<InputField name="Комментарий" id="26">
<DataPath>Данные.Комментарий</DataPath>
<ContextMenu name="КомментарийКонтекстноеМеню" id="21"/>
<ExtendedTooltip name="КомментарийРасширеннаяПодсказка" id="22"/>
<ContextMenu name="КомментарийКонтекстноеМеню" id="27"/>
<ExtendedTooltip name="КомментарийРасширеннаяПодсказка" id="28"/>
</InputField>
</ChildItems>
</Table>
</ChildItems>
<Attributes>
<Attribute name="Объект" id="23">
<Attribute name="Объект" id="29">
<Type>
<v8:Type>cfg:DataProcessorObject.Таблица</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
<Attribute name="Данные" id="24">
<Attribute name="Данные" id="30">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
@@ -85,7 +107,7 @@
<v8:Type>v8:ValueTable</v8:Type>
</Type>
<Columns>
<Column name="Дата" id="25">
<Column name="Дата" id="31">
<Type>
<v8:Type>xs:dateTime</v8:Type>
<v8:DateQualifiers>
@@ -93,7 +115,7 @@
</v8:DateQualifiers>
</Type>
</Column>
<Column name="Сумма" id="26">
<Column name="Сумма" id="32">
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
@@ -103,7 +125,7 @@
</v8:NumberQualifiers>
</Type>
</Column>
<Column name="Комментарий" id="27">
<Column name="Комментарий" id="33">
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
@@ -115,4 +137,15 @@
</Columns>
</Attribute>
</Attributes>
<Commands>
<Command name="Обновить" id="34">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Обновить</v8:content>
</v8:item>
</Title>
<Action>ОбновитьОбработка</Action>
</Command>
</Commands>
</Form>
+13 -1
View File
@@ -18,7 +18,16 @@
"elements": [
{ "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top",
"viewStatusLocation": "None", "searchControlLocation": "None",
"excludedCommands": ["Add", "Delete", "MoveUp", "MoveDown"], "columns": [
"excludedCommands": ["Add", "Delete", "MoveUp", "MoveDown"],
"commandBar": { "autofill": false, "children": [
{ "button": "ПанельОбновить", "command": "Обновить" }
]},
"contextMenu": { "children": [
{ "buttonGroup": "МенюГруппа", "children": [
{ "button": "МенюОбновить", "command": "Обновить" }
]}
]},
"columns": [
{ "input": "Дата", "path": "Данные.Дата" },
{ "input": "Сумма", "path": "Данные.Сумма" },
{ "input": "Комментарий", "path": "Данные.Комментарий" }
@@ -31,6 +40,9 @@
{ "name": "Сумма", "type": "decimal(15,2)" },
{ "name": "Комментарий", "type": "string(200)" }
]}
],
"commands": [
{ "name": "Обновить", "action": "ОбновитьОбработка" }
]
}
}