feat(form-decompile,form-compile): командные панели (кластер D)

- form-decompile: форменный AutoCommandBar → autoCmdBar-элемент; ButtonGroup;
  команды tooltip/currentRowUse; choiceList value type (число/булево) — раньше.
- form-compile (PS1+PY): новый тип ButtonGroup; команды ToolTip/CurrentRowUse.
- docs/form-dsl-spec: buttonGroup, autoCmdBar (панель формы), tooltip/currentRowUse.
- tests: кейс form-compile/button-group (+snapshot, платформенно валидирован).

АварийныйРежим раундтрип: diff 44→18. Регресс form-compile зелёный (PS1+PY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-04 16:22:07 +03:00
parent 7c38422e2c
commit 67eaa1c3c8
14 changed files with 631 additions and 10 deletions
@@ -1,4 +1,4 @@
# form-compile v1.23 — Compile 1C managed form from JSON or object metadata
# form-compile v1.24 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -1878,7 +1878,7 @@ function Emit-Element {
$typeKey = $null
$xmlTag = $null
foreach ($key in @("columnGroup","group","input","check","radio","label","labelField","table","pages","page","button","picture","picField","calendar","cmdBar","popup")) {
foreach ($key in @("columnGroup","buttonGroup","group","input","check","radio","label","labelField","table","pages","page","button","picture","picField","calendar","cmdBar","popup")) {
if ($el.$key -ne $null) {
$typeKey = $key
break
@@ -1893,7 +1893,7 @@ function Emit-Element {
# Validate known keys — warn about typos and unknown properties
$knownKeys = @{
# type keys
"group"=1;"columnGroup"=1;"input"=1;"check"=1;"radio"=1;"label"=1;"labelField"=1;"table"=1;"pages"=1;"page"=1
"group"=1;"columnGroup"=1;"buttonGroup"=1;"input"=1;"check"=1;"radio"=1;"label"=1;"labelField"=1;"table"=1;"pages"=1;"page"=1
"button"=1;"picture"=1;"picField"=1;"calendar"=1;"cmdBar"=1;"popup"=1
# columnGroup-specific
"showInHeader"=1
@@ -1945,6 +1945,7 @@ function Emit-Element {
switch ($typeKey) {
"group" { Emit-Group -el $el -name $name -id $id -indent $indent }
"columnGroup" { Emit-ColumnGroup -el $el -name $name -id $id -indent $indent }
"buttonGroup" { Emit-ButtonGroup -el $el -name $name -id $id -indent $indent }
"input" { Emit-Input -el $el -name $name -id $id -indent $indent }
"check" { Emit-Check -el $el -name $name -id $id -indent $indent }
"radio" { Emit-Radio -el $el -name $name -id $id -indent $indent }
@@ -2787,6 +2788,35 @@ function Emit-CommandBar {
X "$indent</CommandBar>"
}
function Emit-ButtonGroup {
param($el, [string]$name, [int]$id, [string]$indent)
X "$indent<ButtonGroup name=`"$name`" id=`"$id`">"
$inner = "$indent`t"
Emit-Title -el $el -name $name -indent $inner
if ($el.representation) {
X "$inner<Representation>$($el.representation)</Representation>"
}
Emit-CommonFlags -el $el -indent $inner
# Companion: ExtendedTooltip
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner
# Children (кнопки в контексте командной панели)
if ($el.children -and $el.children.Count -gt 0) {
X "$inner<ChildItems>"
foreach ($child in $el.children) {
Emit-Element -el $child -indent "$inner`t" -inCmdBar $true
}
X "$inner</ChildItems>"
}
X "$indent</ButtonGroup>"
}
function Emit-Popup {
param($el, [string]$name, [int]$id, [string]$indent)
@@ -2933,10 +2963,18 @@ function Emit-Commands {
Emit-MLText -tag "Title" -text "$cmdTitle" -indent $inner
}
if ($cmd.tooltip) {
Emit-MLText -tag "ToolTip" -text "$($cmd.tooltip)" -indent $inner
}
if ($cmd.action) {
X "$inner<Action>$($cmd.action)</Action>"
}
if ($cmd.currentRowUse) {
X "$inner<CurrentRowUse>$($cmd.currentRowUse)</CurrentRowUse>"
}
if ($cmd.shortcut) {
X "$inner<Shortcut>$($cmd.shortcut)</Shortcut>"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.23 — Compile 1C managed form from JSON or object metadata
# form-compile v1.24 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -1338,7 +1338,7 @@ KNOWN_FORM_EVENTS = [
]
KNOWN_KEYS = {
"group", "columnGroup", "input", "check", "radio", "label", "labelField", "table", "pages", "page",
"group", "columnGroup", "buttonGroup", "input", "check", "radio", "label", "labelField", "table", "pages", "page",
"button", "picture", "picField", "calendar", "cmdBar", "popup",
"showInHeader",
"radioButtonType", "choiceList", "columnsCount",
@@ -1364,7 +1364,7 @@ KNOWN_KEYS = {
"rowPictureDataPath", "tableAutofill",
}
TYPE_KEYS = ["columnGroup", "group", "input", "check", "radio", "label", "labelField", "table", "pages", "page",
TYPE_KEYS = ["columnGroup", "buttonGroup", "group", "input", "check", "radio", "label", "labelField", "table", "pages", "page",
"button", "picture", "picField", "calendar", "cmdBar", "popup"]
# Synonyms: model often writes XML name or Russian (ПолеПереключателя/RadioButtonField → radio)
@@ -1792,6 +1792,7 @@ def emit_element(lines, el, indent, in_cmd_bar=False):
emitters = {
'group': emit_group,
'columnGroup': emit_column_group,
'buttonGroup': emit_button_group,
'input': emit_input,
'check': emit_check,
'radio': emit_radio_button_field,
@@ -2452,6 +2453,30 @@ def emit_popup(lines, el, name, eid, indent):
lines.append(f'{indent}</Popup>')
def emit_button_group(lines, el, name, eid, indent):
lines.append(f'{indent}<ButtonGroup name="{name}" id="{eid}">')
inner = f'{indent}\t'
emit_title(lines, el, name, inner)
if el.get('representation'):
lines.append(f'{inner}<Representation>{el["representation"]}</Representation>')
emit_common_flags(lines, el, inner)
# Companion: ExtendedTooltip
emit_companion(lines, 'ExtendedTooltip', f'{name}РасширеннаяПодсказка', 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', in_cmd_bar=True)
lines.append(f'{inner}</ChildItems>')
lines.append(f'{indent}</ButtonGroup>')
# --- Attribute emitter ---
def emit_attributes(lines, attrs, indent):
@@ -2554,9 +2579,15 @@ def emit_commands(lines, cmds, indent):
if cmd_title:
emit_mltext(lines, inner, 'Title', str(cmd_title))
if cmd.get('tooltip'):
emit_mltext(lines, inner, 'ToolTip', str(cmd['tooltip']))
if cmd.get('action'):
lines.append(f'{inner}<Action>{cmd["action"]}</Action>')
if cmd.get('currentRowUse'):
lines.append(f'{inner}<CurrentRowUse>{cmd["currentRowUse"]}</CurrentRowUse>')
if cmd.get('shortcut'):
lines.append(f'{inner}<Shortcut>{cmd["shortcut"]}</Shortcut>')
@@ -1,4 +1,4 @@
# form-decompile v0.2 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.3 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -280,7 +280,7 @@ function Decompile-Type {
# --- 4. Element dispatch ---
$ELEMENT_KEY = @{
'UsualGroup'='group'; 'ColumnGroup'='columnGroup'; 'InputField'='input'; 'CheckBoxField'='check';
'UsualGroup'='group'; 'ColumnGroup'='columnGroup'; 'ButtonGroup'='buttonGroup'; 'InputField'='input'; 'CheckBoxField'='check';
'RadioButtonField'='radio'; 'LabelDecoration'='label'; 'LabelField'='labelField';
'PictureDecoration'='picture'; 'PictureField'='picField'; 'CalendarField'='calendar';
'Table'='table'; 'Pages'='pages'; 'Page'='page'; 'Button'='button'; 'CommandBar'='cmdBar'; 'Popup'='popup'
@@ -455,6 +455,13 @@ function Decompile-Element {
$rep = Get-Child $node 'Representation'; if ($rep) { $obj['representation'] = $rep }
$lic = Get-Child $node 'LocationInCommandBar'; if ($lic) { $obj['locationInCommandBar'] = $lic }
}
'ButtonGroup' {
$obj[$key] = $name
Add-CommonProps $obj $node $name
$rep = Get-Child $node 'Representation'; if ($rep) { $obj['representation'] = $rep }
$kids = Decompile-Children $node
if ($kids) { $obj['children'] = $kids }
}
'CommandBar' {
$obj[$key] = $name
Add-CommonProps $obj $node $name
@@ -507,9 +514,25 @@ if ($evForm) {
if ($evMap.Count -gt 0) { $dsl['events'] = $evMap }
}
# elements
# elements (+ форменный AutoCommandBar как autoCmdBar-элемент, если у него есть содержимое)
$elemList = New-Object System.Collections.ArrayList
$acb = $root.SelectSingleNode("lf:AutoCommandBar", $ns)
if ($acb) {
$haln = Get-Child $acb 'HorizontalAlign'
$acbAutofill = Get-Child $acb 'Autofill'
$acbKids = Decompile-Children $acb
if ($haln -or ($acbAutofill -eq 'false') -or $acbKids) {
$acbObj = [ordered]@{}
$acbObj['autoCmdBar'] = $acb.GetAttribute("name")
if ($haln) { $acbObj['horizontalAlign'] = $haln }
if ($acbAutofill -eq 'false') { $acbObj['autofill'] = $false }
if ($acbKids) { $acbObj['children'] = $acbKids }
[void]$elemList.Add($acbObj)
}
}
$elements = Decompile-Children $root
if ($elements) { $dsl['elements'] = $elements }
if ($elements) { foreach ($e in $elements) { [void]$elemList.Add($e) } }
if ($elemList.Count -gt 0) { $dsl['elements'] = @($elemList) }
# attributes
$attrsNode = $root.SelectSingleNode("lf:Attributes", $ns)
@@ -560,6 +583,8 @@ if ($cmdsNode) {
$co = [ordered]@{}; $co['name'] = $c.GetAttribute("name")
$act = Get-Child $c 'Action'; if ($act) { $co['action'] = $act }
$tNode = $c.SelectSingleNode("lf:Title", $ns); if ($tNode) { $t = Get-LangText $tNode; if ($null -ne $t) { $co['title'] = $t } }
$ttNode = $c.SelectSingleNode("lf:ToolTip", $ns); if ($ttNode) { $t = Get-LangText $ttNode; if ($null -ne $t) { $co['tooltip'] = $t } }
$cru = Get-Child $c 'CurrentRowUse'; if ($cru) { $co['currentRowUse'] = $cru }
$sc = Get-Child $c 'Shortcut'; if ($sc) { $co['shortcut'] = $sc }
$ref = $c.SelectSingleNode("lf:Picture/xr:Ref", $ns); if ($ref) { $co['picture'] = $ref.InnerText }
$rep = Get-Child $c 'Representation'; if ($rep) { $co['representation'] = $rep }