diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 1895357a..0bc214af 100644 --- a/.claude/skills/form-compile/scripts/form-compile.ps1 +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -1,4 +1,4 @@ -# form-compile v1.97 — Compile 1C managed form from JSON or object metadata +# form-compile v1.98 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -4962,6 +4962,94 @@ function Emit-Commands { X "$indent" } +# Резолв ключа-группы древовидной формы → CommandGroup (зависит от панели). Дружелюбные +# алиасы стандартных form-групп; любой иной ключ (CommandGroup.X / GUID / "") — verbatim. +function Resolve-CommandGroupKey { + param([string]$key, [string]$panelTag) + $k = ($key -replace '\s','').ToLower() + if ($panelTag -eq 'NavigationPanel') { + switch ($k) { + 'important' { return 'FormNavigationPanelImportant' } + 'важное' { return 'FormNavigationPanelImportant' } + 'goto' { return 'FormNavigationPanelGoTo' } + 'перейти' { return 'FormNavigationPanelGoTo' } + 'seealso' { return 'FormNavigationPanelSeeAlso' } + 'смтакже' { return 'FormNavigationPanelSeeAlso' } + } + } else { + switch ($k) { + 'important' { return 'FormCommandBarImportant' } + 'важное' { return 'FormCommandBarImportant' } + 'createbasedon' { return 'FormCommandBarCreateBasedOn' } + 'создатьнаосновании' { return 'FormCommandBarCreateBasedOn' } + } + } + return $key # verbatim +} + +# Командный интерфейс формы (): панели CommandBar + NavigationPanel. +# Значение панели: МАССИВ (плоская форма; элемент может нести group) ИЛИ ОБЪЕКТ +# (древовидная форма: {группа: [команды]}, group берётся из ключа, элементы его не дублируют). +# Элемент: строка (голый command, Type=Auto) или объект. Порядок тегов: +# Command, Type(деф. Auto), Attribute, CommandGroup, Index, DefaultVisible, Visible(xr-flag). +function Emit-CommandInterface { + param($ci, [string]$indent) + if (-not $ci) { return } + $inner = "$indent`t" + $panels = @( + @{ Tag='CommandBar'; Syns=@('commandBar','команднаяПанель','КоманднаяПанель') }, + @{ Tag='NavigationPanel'; Syns=@('navigationPanel','панельНавигации','ПанельНавигации') } + ) + $present = @() + foreach ($p in $panels) { + $items = $null + foreach ($syn in $p.Syns) { if ($null -ne $ci.PSObject.Properties[$syn]) { $items = $ci.($syn); break } } + if ($null -ne $items) { $present += ,@{ Tag=$p.Tag; Items=$items } } + } + if ($present.Count -eq 0) { return } + X "$indent" + foreach ($p in $present) { + X "$inner<$($p.Tag)>" + # Нормализация: плоский список пар (элемент, group-из-дерева). Объект → дерево. + $flat = New-Object System.Collections.ArrayList + if ($p.Items -is [System.Management.Automation.PSCustomObject]) { + foreach ($prop in $p.Items.PSObject.Properties) { + $grpFromTree = Resolve-CommandGroupKey -key $prop.Name -panelTag $p.Tag + foreach ($it in @($prop.Value)) { [void]$flat.Add(@{ item=$it; treeGroup=$grpFromTree }) } + } + } else { + foreach ($it in @($p.Items)) { [void]$flat.Add(@{ item=$it; treeGroup=$null }) } + } + foreach ($fi in $flat) { + $item = $fi.item; $treeGroup = $fi.treeGroup + if ($item -is [string]) { + $cmd = $item; $type = 'Auto'; $attr = $null; $grp = $null; $idx = $null; $dv = $null; $vis = $null + } else { + $cmd = Get-ElProp $item @('command','команда') + $type = Get-ElProp $item @('type','тип'); if (-not $type) { $type = 'Auto' } + $attr = Get-ElProp $item @('attribute','реквизит') + $grp = Get-ElProp $item @('group','группа','группаКоманд') + $idx = Get-ElProp $item @('index','индекс') + $dv = Get-ElProp $item @('defaultVisible','видимость','видимостьПоУмолчанию') + $vis = Get-ElProp $item @('visible','видимостьПоРолям','настройкаВидимости') + } + # group из дерева побеждает (если задан и непустой); явный group элемента — фолбэк + if ($treeGroup) { $grp = $treeGroup } + X "$inner`t" + X "$inner`t`t$(Esc-Xml "$cmd")" + X "$inner`t`t$type" + if ($attr) { X "$inner`t`t$(Esc-Xml "$attr")" } + if ($grp) { X "$inner`t`t$(Esc-Xml "$grp")" } + if ($null -ne $idx) { X "$inner`t`t$idx" } + if ($null -ne $dv) { X "$inner`t`t$(if ($dv){'true'}else{'false'})" } + if ($null -ne $vis) { Emit-XrFlag -tag 'Visible' -val $vis -indent "$inner`t`t" } + X "$inner`t" + } + X "$inner" + } + X "$indent" +} + # --- 11. Properties emitter --- function Emit-Properties { @@ -5366,6 +5454,9 @@ Emit-Parameters -params $def.parameters -indent "`t" # 12i. Commands Emit-Commands -cmds $def.commands -indent "`t" +# 12i2. CommandInterface (командный интерфейс формы — последний дочерний Form) +Emit-CommandInterface -ci $def.commandInterface -indent "`t" + # 12j. Close X '' diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 73a651ef..77cd1855 100644 --- a/.claude/skills/form-compile/scripts/form-compile.py +++ b/.claude/skills/form-compile/scripts/form-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# form-compile v1.97 — Compile 1C managed form from JSON or object metadata +# form-compile v1.98 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -4655,6 +4655,86 @@ def emit_commands(lines, cmds, indent): lines.append(f'{indent}') +# Командный интерфейс формы (): панели CommandBar + NavigationPanel. +# Элемент: строка (голый command, Type=Auto) или dict. Порядок тегов: +# Command, Type(деф. Auto), Attribute, CommandGroup, Index, DefaultVisible, Visible(xr-flag). +def _resolve_command_group_key(key, panel_tag): + """Ключ-группа древовидной формы → CommandGroup (зависит от панели); иначе verbatim.""" + k = re.sub(r'\s', '', str(key)).lower() + if panel_tag == 'NavigationPanel': + m = {'important': 'FormNavigationPanelImportant', 'важное': 'FormNavigationPanelImportant', + 'goto': 'FormNavigationPanelGoTo', 'перейти': 'FormNavigationPanelGoTo', + 'seealso': 'FormNavigationPanelSeeAlso', 'смтакже': 'FormNavigationPanelSeeAlso'} + else: + m = {'important': 'FormCommandBarImportant', 'важное': 'FormCommandBarImportant', + 'createbasedon': 'FormCommandBarCreateBasedOn', 'создатьнаосновании': 'FormCommandBarCreateBasedOn'} + return m.get(k, key) + + +def emit_command_interface(lines, ci, indent): + if not ci: + return + inner = f'{indent}\t' + panels = [ + ('CommandBar', ('commandBar', 'команднаяПанель', 'КоманднаяПанель')), + ('NavigationPanel', ('navigationPanel', 'панельНавигации', 'ПанельНавигации')), + ] + present = [] + for tag, syns in panels: + items = None + for syn in syns: + if isinstance(ci, dict) and syn in ci: + items = ci[syn] + break + if items is not None: + present.append((tag, items)) + if not present: + return + lines.append(f'{indent}') + for tag, items in present: + lines.append(f'{inner}<{tag}>') + # Нормализация: плоский список пар (элемент, group-из-дерева). dict → древовидная форма. + flat = [] + if isinstance(items, dict): + for gkey, gitems in items.items(): + grp_tree = _resolve_command_group_key(gkey, tag) + for it in gitems: + flat.append((it, grp_tree)) + else: + for it in items: + flat.append((it, None)) + for item, tree_group in flat: + if isinstance(item, str): + cmd, typ, attr, grp, idx, dv, vis = item, 'Auto', None, None, None, None, None + else: + cmd = get_el_prop(item, ('command', 'команда')) + typ = get_el_prop(item, ('type', 'тип')) or 'Auto' + attr = get_el_prop(item, ('attribute', 'реквизит')) + grp = get_el_prop(item, ('group', 'группа', 'группаКоманд')) + idx = get_el_prop(item, ('index', 'индекс')) + dv = get_el_prop(item, ('defaultVisible', 'видимость', 'видимостьПоУмолчанию')) + vis = get_el_prop(item, ('visible', 'видимостьПоРолям', 'настройкаВидимости')) + # group из дерева побеждает (если задан и непустой); явный group элемента — фолбэк + if tree_group: + grp = tree_group + lines.append(f'{inner}\t') + lines.append(f'{inner}\t\t{esc_xml(str(cmd))}') + lines.append(f'{inner}\t\t{typ}') + if attr: + lines.append(f'{inner}\t\t{esc_xml(str(attr))}') + if grp: + lines.append(f'{inner}\t\t{esc_xml(str(grp))}') + if idx is not None: + lines.append(f'{inner}\t\t{idx}') + if dv is not None: + lines.append(f'{inner}\t\t{"true" if dv else "false"}') + if vis is not None: + emit_xr_flag(lines, 'Visible', vis, f'{inner}\t\t') + lines.append(f'{inner}\t') + lines.append(f'{inner}') + lines.append(f'{indent}') + + # --- Properties emitter --- PROP_MAP = { @@ -5204,6 +5284,9 @@ def main(): # Commands emit_commands(lines, defn.get('commands'), '\t') + # CommandInterface (командный интерфейс формы — последний дочерний Form) + emit_command_interface(lines, defn.get('commandInterface'), '\t') + # Close lines.append('') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 01385a2c..681aec79 100644 --- a/.claude/skills/form-decompile/scripts/form-decompile.ps1 +++ b/.claude/skills/form-decompile/scripts/form-decompile.ps1 @@ -1,4 +1,4 @@ -# form-decompile v0.73 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.74 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -145,7 +145,6 @@ function Fail-Ring3 { [Console]::Error.WriteLine("Для точечной работы с этой формой используй /form-edit.") exit 3 } -foreach ($el in $xmlDoc.SelectNodes("//*[local-name()='CommandInterface']")) { Fail-Ring3 -kind "CommandInterface" -loc "form/CommandInterface" } foreach ($el in $xmlDoc.SelectNodes("//*[local-name()='ConditionalAppearance']")) { Fail-Ring3 -kind "ConditionalAppearance" -loc "form/ConditionalAppearance" } # --- 1c. Compact JSON serializer (созвучно skd-decompile: 2-проб. indent, inline в пределах lineLimit) --- @@ -864,6 +863,38 @@ function Decompile-XrFlag { return $o } +# Командный интерфейс формы (): панели CommandBar + NavigationPanel, +# каждая — список переопределений команд (платформа эмитит ТОЛЬКО отклонения от авто-расстановки). +# Элемент: command (verbatim, "0"=пустой) + type (Auto опускаем) + attribute/group(CommandGroup)/index + +# defaultVisible(bool) + visible(xr-flag bool/{common,roles} — тот же механизм, что userVisible). +# Голый элемент (только команда, Type=Auto) → строковый shorthand. +function Decompile-CommandInterface { + $ciNode = $root.SelectSingleNode("lf:CommandInterface", $ns) + if (-not $ciNode) { return $null } + $ci = [ordered]@{} + foreach ($panel in @(@('CommandBar','commandBar'), @('NavigationPanel','navigationPanel'))) { + $pn = $ciNode.SelectSingleNode("lf:$($panel[0])", $ns) + if (-not $pn) { continue } + $items = New-Object System.Collections.ArrayList + foreach ($it in @($pn.SelectNodes("lf:Item", $ns))) { + $o = [ordered]@{} + $cmd = Get-Child $it 'Command' + $o['command'] = "$cmd" + $ty = Get-Child $it 'Type'; if ($ty -and $ty -ne 'Auto') { $o['type'] = $ty } + $at = Get-Child $it 'Attribute'; if ($at) { $o['attribute'] = $at } + $cg = Get-Child $it 'CommandGroup'; if ($cg) { $o['group'] = $cg } + $idx = Get-Child $it 'Index'; if ($null -ne $idx) { $o['index'] = [int]$idx } + $dv = Get-Child $it 'DefaultVisible'; if ($null -ne $dv) { $o['defaultVisible'] = ($dv -eq 'true') } + $vis = Decompile-XrFlag $it 'Visible'; if ($null -ne $vis) { $o['visible'] = $vis } + # Голый элемент (только command) → строка-shorthand; иначе объект + if ($o.Count -eq 1) { [void]$items.Add("$cmd") } else { [void]$items.Add($o) } + } + if ($items.Count -gt 0) { $ci[$panel[1]] = @($items) } + } + if ($ci.Count -gt 0) { return $ci } + return $null +} + # FunctionalOption.X…> → массив строк (префикс FunctionalOption. снят; GUID — как есть). function Decompile-FunctionalOptions { param($node) @@ -2092,6 +2123,10 @@ if ($cmdsNode) { if ($cmds.Count -gt 0) { $dsl['commands'] = @($cmds) } } +# commandInterface (форменный — последний дочерний Form) +$ci = Decompile-CommandInterface +if ($null -ne $ci) { $dsl['commandInterface'] = $ci } + # --- 6. Output --- $json = ConvertTo-CompactJson -obj $dsl if ($OutputPath) { diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index d3be5c8a..c48ab98d 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -940,6 +940,46 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и --- +## 7b. CommandInterface — командный интерфейс формы + +Форменный ключ `commandInterface` (XML ``, последний дочерний `
`). Две панели: +`commandBar` (командная панель) и `navigationPanel` (панель навигации). Платформа эмитит **только +переопределения** авто-расстановки (видимость/положение), поэтому в списке лишь изменённые команды. + +```json +"commandInterface": { + "commandBar": [ + { "command": "Form.Command.Печать", "defaultVisible": false, "group": "FormCommandBarImportant", + "visible": { "common": false, "roles": { "Бухгалтер": true } } }, + "CommonCommand.История" + ], + "navigationPanel": { + "important": [ { "command": "CommonCommand.СвязанныеДокументы", "defaultVisible": false, "visible": false } ], + "seeAlso": [ { "command": "CommonCommand.Заметки", "defaultVisible": false, "visible": false } ] + } +} +``` + +**Элемент** (объект или строка-shorthand = голый `command` со всеми умолчаниями): + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `command` | string | Ссылка на команду verbatim: `CommonCommand.X`, `Document.X.StandardCommand.Y`, `Form.Command.X`, `Form.StandardCommand.OK`, `"0"` (пустой/разделитель) | +| `type` | string | `Auto` (дефолт, опускаем) / `Added` | +| `defaultVisible` | bool | Видимость по умолчанию (``; на практике всегда `false` — скрыть видимую команду) | +| `visible` | bool/object | Видимость с исключениями по ролям — **тот же xr-flag, что `userVisible`/`use`** (§4.1c): `bool` или `{common, roles:{Имя:bool}}` | +| `group` | string | `` verbatim: `FormCommandBarImportant`/`FormNavigationPanelGoTo`/…, `CommandGroup.X` (именованная), GUID (расширение) | +| `index` | int | Порядок в группе (``) | +| `attribute` | string | Путь реквизита для элемента панели навигации (``) | + +**Две формы записи панели:** +- **Плоский массив** — каждый элемент опц. несёт `group` (полная общность; декомпилятор эмитит ЭТУ форму). +- **Дерево** (входной сахар) — объект `{группа: [команды]}`; `group` берётся из ключа, элементы его не дублируют. Дружелюбные алиасы (зависят от панели): navigation — `important`/`goTo`/`seeAlso` (рус. `важное`/`перейти`/`смТакже`), commandBar — `important`/`createBasedOn`; иной ключ (`CommandGroup.X`/GUID) — verbatim. + +Синонимы ключей: `команда`/`тип`/`видимость`/`видимостьПоРолям`/`группа`/`индекс`/`реквизит`. + +--- + ## 8. Система типов (shorthand) ### Примитивные типы diff --git a/tests/skills/cases/form-compile/commands.json b/tests/skills/cases/form-compile/commands.json index 7f1d393f..32b49b92 100644 --- a/tests/skills/cases/form-compile/commands.json +++ b/tests/skills/cases/form-compile/commands.json @@ -29,6 +29,13 @@ ], "commands": [ { "name": "Выполнить", "action": "ВыполнитьОбработка", "shortcut": "Ctrl+Enter", "use": false, "modifiesSavedData": true } - ] + ], + "commandInterface": { + "commandBar": { + "important": [ + { "command": "Form.Command.Выполнить", "defaultVisible": false, "index": 0, "visible": false } + ] + } + } } } diff --git a/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml index c1020baa..8ecea3e5 100644 --- a/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/commands/DataProcessors/Команды/Forms/Форма/Ext/Form.xml @@ -90,4 +90,18 @@ Ctrl+Enter + + + + Form.Command.Выполнить + Auto + FormCommandBarImportant + 0 + false + + false + + + +