feat(form-decompile,form-compile): дополнения командной панели таблицы (Search/ViewStatus/SearchControl)

Дополнения встроенного поиска таблицы как тип-элементы — обе позиции:

(1) Кастомные (в AutoCommandBar/ChildItems) → элементы в commandBar:
    { "searchString": "Имя", "source": "Список", "width": 15, ... }
    Полный набор свойств поля (Emit-Layout/Appearance/CommonFlags/tooltip);
    source дефолт = родительская таблица; horizontalLocation auto/left/right.

(2) Стандартные (авто-генерация на уровне таблицы) → per-table карта
    отклонений additions: { viewStatus: { horizontalLocation: "left" } }.

Тип-как-ключ searchString/viewStatus/searchControl, forgiving-синонимы
(XML-тег, <Type>, рус.имя, имя «Вид» из конфигуратора). Декомпилятор разводит
по позиции (ChildItems → commandBar.children; прямые дети <Table> → карта
additions, только deviations); убран из COMPANION_TAGS, +ELEMENT_KEY.

Хвост: CommandBarLocation авто-вывод для дин-список-таблицы — суппресс-маркер
"" (компилятор инжектит None, верно по корпусу 203≈213; декомпилятор инвертирует:
нет тега → "", None → опускает, иначе → захват).

Зеркало py (байт-в-байт). Синтет-фикстура (upload/epf/ДополненияКП) — perfect
round-trip LOST 0/ADDED 0. Кейс dynamic-list-form расширен (кастомное+override),
сертифицирован в 1С. Регресс 39/39 ps1+py.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-08 16:04:52 +03:00
co-authored by Claude Opus 4.8
parent 01b598ce8f
commit d06c2c49bb
6 changed files with 322 additions and 57 deletions
@@ -2329,20 +2329,85 @@ function Emit-CompanionPanel {
X "$indent</$tag>"
}
# Табличный addition (СтрокаПоиска/СостояниеПросмотра/УправлениеПоиском) с AdditionSource.
# Item = имя таблицы, Type фиксирован по виду; внутри — companion ContextMenu/ExtendedTooltip.
# Дополнения командной панели таблицы: тип DSL → XML-тег + AdditionSource.Type.
$script:additionTypeMap = [ordered]@{
'searchString' = @{ Tag = 'SearchStringAddition'; Type = 'SearchStringRepresentation'; Suffix = 'СтрокаПоиска' }
'viewStatus' = @{ Tag = 'ViewStatusAddition'; Type = 'ViewStatusRepresentation'; Suffix = 'СостояниеПросмотра' }
'searchControl' = @{ Tag = 'SearchControlAddition'; Type = 'SearchControl'; Suffix = 'УправлениеПоиском' }
}
# Синонимы типа дополнения (для override-карты additions и резолва тип-ключа).
$script:additionKeySynonyms = @{
'searchString' = @('SearchStringAddition','SearchStringRepresentation','строкаПоиска','отображениеСтрокиПоиска')
'viewStatus' = @('ViewStatusAddition','ViewStatusRepresentation','состояниеПросмотра')
'searchControl' = @('SearchControlAddition','SearchControl','управлениеПоиском')
}
# HorizontalLocation: auto (дефолт, тег опускаем) / left / right; forgiving + рус.синонимы.
function Get-HLocation {
param($el)
$v = if ($el -and $el.PSObject.Properties['horizontalLocation']) { $el.horizontalLocation } else { $null }
if (-not $v) { return $null }
switch -Regex ("$v".ToLower()) {
'^(auto|авто)$' { return $null } # дефолт — не эмитим
'^(left|слева|лево)$' { return 'Left' }
'^(right|справа|право)$' { return 'Right' }
default { return "$v" }
}
}
# Тело дополнения: AdditionSource + свойства (как у поля) + companions. $props может быть $null
# (стандартное дополнение без отклонений). Порядок 1С-толерантен (diff порядок-независим).
function Emit-AdditionBody {
param($props, [string]$source, [string]$srcType, [string]$addName, [string]$indent)
$inner = "$indent`t"
X "$inner<AdditionSource>"
X "$inner`t<Item>$source</Item>"
X "$inner`t<Type>$srcType</Type>"
X "$inner</AdditionSource>"
if ($props) {
if ($props.PSObject.Properties['title'] -and $props.title) { Emit-MLText -tag "Title" -text $props.title -indent $inner }
Emit-CommonFlags -el $props -indent $inner
if ($props.tooltip) { Emit-MLText -tag "ToolTip" -text $props.tooltip -indent $inner }
if ($props.tooltipRepresentation) { X "$inner<ToolTipRepresentation>$($props.tooltipRepresentation)</ToolTipRepresentation>" }
$hl = Get-HLocation $props; if ($hl) { X "$inner<HorizontalLocation>$hl</HorizontalLocation>" }
Emit-Layout -el $props -indent $inner
Emit-Appearance -el $props -indent $inner -profile 'field'
}
Emit-Companion -tag "ContextMenu" -name "${addName}КонтекстноеМеню" -indent $inner
Emit-Companion -tag "ExtendedTooltip" -name "${addName}РасширеннаяПодсказка" -indent $inner
}
# Кастомное дополнение (тип-элемент в commandBar): source дефолтит в текущую таблицу.
function Emit-Addition {
param($el, [string]$name, [int]$id, [string]$typeKey, [string]$indent)
$map = $script:additionTypeMap[$typeKey]
$source = if ($el.source) { "$($el.source)" } elseif ($script:currentTableName) { $script:currentTableName } else { '' }
X "$indent<$($map.Tag) name=`"$name`" id=`"$id`">"
Emit-AdditionBody -props $el -source $source -srcType $map.Type -addName $name -indent $indent
X "$indent</$($map.Tag)>"
}
# Стандартное табличное дополнение (авто-генерация на уровне таблицы). $override — объект отклонений
# из per-table карты additions (или $null = чистый дефолт).
function Emit-TableAddition {
param([string]$tag, [string]$tableName, [string]$nameSuffix, [string]$srcType, [string]$indent)
$addName = "$tableName$nameSuffix"
param([string]$typeKey, [string]$tableName, [string]$indent, $override = $null)
$map = $script:additionTypeMap[$typeKey]
$addName = "$tableName$($map.Suffix)"
$id = New-Id
X "$indent<$tag name=`"$addName`" id=`"$id`">"
X "$indent`t<AdditionSource>"
X "$indent`t`t<Item>$tableName</Item>"
X "$indent`t`t<Type>$srcType</Type>"
X "$indent`t</AdditionSource>"
Emit-Companion -tag "ContextMenu" -name "${addName}КонтекстноеМеню" -indent "$indent`t"
Emit-Companion -tag "ExtendedTooltip" -name "${addName}РасширеннаяПодсказка" -indent "$indent`t"
X "$indent</$tag>"
X "$indent<$($map.Tag) name=`"$addName`" id=`"$id`">"
Emit-AdditionBody -props $override -source $tableName -srcType $map.Type -addName $addName -indent $indent
X "$indent</$($map.Tag)>"
}
# Прочитать override-объект для типа дополнения из per-table карты additions (с синонимами).
function Get-AdditionOverride {
param($additions, [string]$typeKey)
if ($null -eq $additions) { return $null }
foreach ($k in @($typeKey) + $script:additionKeySynonyms[$typeKey]) {
$p = $additions.PSObject.Properties[$k]
if ($p) { return $p.Value }
}
return $null
}
function Emit-Element {
@@ -2392,6 +2457,20 @@ function Emit-Element {
"Кнопка" = "button"
"Popup" = "popup"
"ВсплывающееМеню" = "popup"
# Дополнения командной панели таблицы (тип-как-ключ) — forgiving: XML-тег/Type/рус.имя → канон
"SearchStringAddition" = "searchString"
"SearchStringRepresentation" = "searchString"
"строкаПоиска" = "searchString"
"отображениеСтрокиПоиска" = "searchString"
"Отображение строки поиска" = "searchString"
"ViewStatusAddition" = "viewStatus"
"ViewStatusRepresentation" = "viewStatus"
"состояниеПросмотра" = "viewStatus"
"Состояние просмотра" = "viewStatus"
"SearchControlAddition" = "searchControl"
"SearchControl" = "searchControl"
"управлениеПоиском" = "searchControl"
"Управление поиском" = "searchControl"
}
foreach ($pair in $synonyms.GetEnumerator()) {
if ($null -ne $el.PSObject.Properties[$pair.Key] -and $null -eq $el.PSObject.Properties[$pair.Value]) {
@@ -2410,7 +2489,7 @@ function Emit-Element {
# у popup/button/cmdBar. Тип-ключ владельца (popup/button/…) должен выиграть.
# pages/page ПЕРЕД group: у Page/Pages ключ 'group' — это направление раскладки детей
# (<Group>Horizontal</Group>), а не тип UsualGroup. Реальная UsualGroup ключа page/pages не несёт.
foreach ($key in @("columnGroup","buttonGroup","pages","page","group","input","check","radio","label","labelField","table","button","calendar","cmdBar","popup","picField","picture")) {
foreach ($key in @("columnGroup","buttonGroup","pages","page","group","input","check","radio","label","labelField","table","button","calendar","cmdBar","popup","searchString","viewStatus","searchControl","picField","picture")) {
if ($el.$key -ne $null) {
$typeKey = $key
break
@@ -2484,6 +2563,8 @@ function Emit-Element {
"autofill"=1
# AutoCommandBar-маркер (autofill heuristic) на элементе/таблице
"autoCmdBar"=1
# дополнения командной панели таблицы (тип-ключи + свойства)
"searchString"=1;"viewStatus"=1;"searchControl"=1;"source"=1;"horizontalLocation"=1;"additions"=1
}
# Оформление (цвета/шрифты/граница) — авто-регистрация из самих структур, чтобы allowlist
# не дрейфовал при добавлении новых ключей/синонимов. Канонические + forgiving-синонимы.
@@ -2514,6 +2595,9 @@ function Emit-Element {
"page" { Emit-Page -el $el -name $name -id $id -indent $indent }
"button" { Emit-Button -el $el -name $name -id $id -indent $indent -inCmdBar $inCmdBar }
"picture" { Emit-PictureDecoration -el $el -name $name -id $id -indent $indent }
"searchString" { Emit-Addition -el $el -name $name -typeKey "searchString" -id $id -indent $indent }
"viewStatus" { Emit-Addition -el $el -name $name -typeKey "viewStatus" -id $id -indent $indent }
"searchControl" { Emit-Addition -el $el -name $name -typeKey "searchControl" -id $id -indent $indent }
"picField" { Emit-PictureField -el $el -name $name -id $id -indent $indent }
"calendar" { Emit-Calendar -el $el -name $name -id $id -indent $indent }
"cmdBar" { Emit-CommandBar -el $el -name $name -id $id -indent $indent }
@@ -3529,6 +3613,7 @@ function Emit-DynListTableBlock {
function Emit-Table {
param($el, [string]$name, [int]$id, [string]$indent)
$script:currentTableName = $name # дефолт source для кастомных дополнений в commandBar
X "$indent<Table name=`"$name`" id=`"$id`">"
$inner = "$indent`t"
@@ -3606,9 +3691,10 @@ function Emit-Table {
Emit-Companion -tag "AutoCommandBar" -name "${name}КоманднаяПанель" -indent $inner
}
Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner -content $el.extendedTooltip
Emit-TableAddition -tag "SearchStringAddition" -tableName $name -nameSuffix "СтрокаПоиска" -srcType "SearchStringRepresentation" -indent $inner
Emit-TableAddition -tag "ViewStatusAddition" -tableName $name -nameSuffix "СостояниеПросмотра" -srcType "ViewStatusRepresentation" -indent $inner
Emit-TableAddition -tag "SearchControlAddition" -tableName $name -nameSuffix "УправлениеПоиском" -srcType "SearchControl" -indent $inner
$adds = $el.additions
Emit-TableAddition -typeKey 'searchString' -tableName $name -indent $inner -override (Get-AdditionOverride $adds 'searchString')
Emit-TableAddition -typeKey 'viewStatus' -tableName $name -indent $inner -override (Get-AdditionOverride $adds 'viewStatus')
Emit-TableAddition -typeKey 'searchControl' -tableName $name -indent $inner -override (Get-AdditionOverride $adds 'searchControl')
# Columns
if ($el.columns -and $el.columns.Count -gt 0) {
@@ -1811,6 +1811,8 @@ KNOWN_KEYS = {
"userSettingsGroup", "rowsPicture",
# AutoCommandBar-маркер (autofill heuristic) на элементе/таблице
"autoCmdBar",
# дополнения командной панели таблицы (тип-ключи + свойства)
"searchString", "viewStatus", "searchControl", "source", "horizontalLocation", "additions",
}
# picture/picField — НИЗКИЙ приоритет: 'picture' это и тип (PictureDecoration), и свойство-иконка
@@ -1818,7 +1820,7 @@ KNOWN_KEYS = {
# pages/page ПЕРЕД group: у Page/Pages ключ 'group' — это направление раскладки детей
# (<Group>Horizontal</Group>), а не тип UsualGroup. Реальная UsualGroup ключа page/pages не несёт.
TYPE_KEYS = ["columnGroup", "buttonGroup", "pages", "page", "group", "input", "check", "radio", "label", "labelField", "table",
"button", "calendar", "cmdBar", "popup", "picField", "picture"]
"button", "calendar", "cmdBar", "popup", "searchString", "viewStatus", "searchControl", "picField", "picture"]
# Synonyms: model often writes XML name or Russian (ПолеПереключателя/RadioButtonField → radio)
ELEMENT_TYPE_SYNONYMS = {
@@ -1857,6 +1859,20 @@ ELEMENT_TYPE_SYNONYMS = {
"Кнопка": "button",
"Popup": "popup",
"ВсплывающееМеню": "popup",
# дополнения командной панели таблицы — forgiving: XML-тег/Type/рус.имя → канон
"SearchStringAddition": "searchString",
"SearchStringRepresentation": "searchString",
"строкаПоиска": "searchString",
"отображениеСтрокиПоиска": "searchString",
"Отображение строки поиска": "searchString",
"ViewStatusAddition": "viewStatus",
"ViewStatusRepresentation": "viewStatus",
"состояниеПросмотра": "viewStatus",
"Состояние просмотра": "viewStatus",
"SearchControlAddition": "searchControl",
"SearchControl": "searchControl",
"управлениеПоиском": "searchControl",
"Управление поиском": "searchControl",
}
# Тип-синонимы, применяемые ТОЛЬКО к строковому значению (имя элемента); объект/массив
@@ -2324,18 +2340,89 @@ def emit_companion_panel(lines, tag, name, indent, panel):
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}'
# Дополнения командной панели таблицы: тип DSL → XML-тег + AdditionSource.Type + суффикс имени.
ADDITION_TYPE_MAP = {
'searchString': {'tag': 'SearchStringAddition', 'type': 'SearchStringRepresentation', 'suffix': 'СтрокаПоиска'},
'viewStatus': {'tag': 'ViewStatusAddition', 'type': 'ViewStatusRepresentation', 'suffix': 'СостояниеПросмотра'},
'searchControl': {'tag': 'SearchControlAddition', 'type': 'SearchControl', 'suffix': 'УправлениеПоиском'},
}
ADDITION_KEY_SYNONYMS = {
'searchString': ['SearchStringAddition', 'SearchStringRepresentation', 'строкаПоиска', 'отображениеСтрокиПоиска'],
'viewStatus': ['ViewStatusAddition', 'ViewStatusRepresentation', 'состояниеПросмотра'],
'searchControl': ['SearchControlAddition', 'SearchControl', 'управлениеПоиском'],
}
# Имя текущей таблицы — дефолт source для кастомных дополнений в commandBar.
_current_table_name = {'name': None}
def get_hlocation(el):
# HorizontalLocation: auto (дефолт, опускаем) / left / right; forgiving + рус.
if not isinstance(el, dict):
return None
v = el.get('horizontalLocation')
if not v:
return None
s = str(v).lower()
if s in ('auto', 'авто'):
return None
if s in ('left', 'слева', 'лево'):
return 'Left'
if s in ('right', 'справа', 'право'):
return 'Right'
return str(v)
def emit_addition_body(lines, props, source, src_type, add_name, indent):
# Тело дополнения: AdditionSource + свойства (как у поля) + companions. props может быть None.
inner = f'{indent}\t'
lines.append(f'{inner}<AdditionSource>')
lines.append(f'{inner}\t<Item>{source}</Item>')
lines.append(f'{inner}\t<Type>{src_type}</Type>')
lines.append(f'{inner}</AdditionSource>')
if props:
if props.get('title'):
emit_mltext(lines, inner, 'Title', props['title'])
emit_common_flags(lines, props, inner)
if props.get('tooltip'):
emit_mltext(lines, inner, 'ToolTip', props['tooltip'])
if props.get('tooltipRepresentation'):
lines.append(f'{inner}<ToolTipRepresentation>{props["tooltipRepresentation"]}</ToolTipRepresentation>')
hl = get_hlocation(props)
if hl:
lines.append(f'{inner}<HorizontalLocation>{hl}</HorizontalLocation>')
emit_layout(lines, props, inner)
emit_appearance(lines, props, inner, 'field')
emit_companion(lines, 'ContextMenu', f'{add_name}КонтекстноеМеню', inner)
emit_companion(lines, 'ExtendedTooltip', f'{add_name}РасширеннаяПодсказка', inner)
def emit_addition(lines, el, name, eid, type_key, indent):
# Кастомное дополнение (тип-элемент в commandBar): source дефолтит в текущую таблицу.
m = ADDITION_TYPE_MAP[type_key]
source = el.get('source') or _current_table_name['name'] or ''
lines.append(f'{indent}<{m["tag"]} name="{name}" id="{eid}">')
emit_addition_body(lines, el, source, m['type'], name, indent)
lines.append(f'{indent}</{m["tag"]}>')
def emit_table_addition(lines, type_key, table_name, indent, override=None):
# Стандартное табличное дополнение (авто-генерация). override — объект отклонений из карты additions.
m = ADDITION_TYPE_MAP[type_key]
add_name = f'{table_name}{m["suffix"]}'
aid = new_id()
lines.append(f'{indent}<{tag} name="{add_name}" id="{aid}">')
lines.append(f'{indent}\t<AdditionSource>')
lines.append(f'{indent}\t\t<Item>{table_name}</Item>')
lines.append(f'{indent}\t\t<Type>{src_type}</Type>')
lines.append(f'{indent}\t</AdditionSource>')
emit_companion(lines, 'ContextMenu', f'{add_name}КонтекстноеМеню', f'{indent}\t')
emit_companion(lines, 'ExtendedTooltip', f'{add_name}РасширеннаяПодсказка', f'{indent}\t')
lines.append(f'{indent}</{tag}>')
lines.append(f'{indent}<{m["tag"]} name="{add_name}" id="{aid}">')
emit_addition_body(lines, override, table_name, m['type'], add_name, indent)
lines.append(f'{indent}</{m["tag"]}>')
def get_addition_override(additions, type_key):
# Прочитать override-объект для типа из per-table карты additions (с синонимами).
if not isinstance(additions, dict):
return None
for k in [type_key] + ADDITION_KEY_SYNONYMS[type_key]:
if k in additions:
return additions[k]
return None
# Role-adjustable boolean (xr:Common + 0..N xr:Value name="Role.X").
@@ -2866,6 +2953,9 @@ def emit_element(lines, el, indent, in_cmd_bar=False):
'calendar': emit_calendar,
'cmdBar': emit_command_bar,
'popup': emit_popup,
'searchString': lambda lines, el, name, eid, indent: emit_addition(lines, el, name, eid, 'searchString', indent),
'viewStatus': lambda lines, el, name, eid, indent: emit_addition(lines, el, name, eid, 'viewStatus', indent),
'searchControl': lambda lines, el, name, eid, indent: emit_addition(lines, el, name, eid, 'searchControl', indent),
}
emitter = emitters.get(type_key)
@@ -3240,6 +3330,7 @@ def emit_dynlist_table_block(lines, el, indent):
def emit_table(lines, el, name, eid, indent):
_current_table_name['name'] = name # дефолт source для кастомных дополнений в commandBar
lines.append(f'{indent}<Table name="{name}" id="{eid}">')
inner = f'{indent}\t'
@@ -3331,9 +3422,10 @@ def emit_table(lines, el, name, eid, indent):
else:
emit_companion(lines, 'AutoCommandBar', f'{name}\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c', inner)
emit_companion(lines, '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_table_addition(lines, 'SearchStringAddition', name, '\u0421\u0442\u0440\u043e\u043a\u0430\u041f\u043e\u0438\u0441\u043a\u0430', 'SearchStringRepresentation', inner)
emit_table_addition(lines, 'ViewStatusAddition', name, '\u0421\u043e\u0441\u0442\u043e\u044f\u043d\u0438\u0435\u041f\u0440\u043e\u0441\u043c\u043e\u0442\u0440\u0430', 'ViewStatusRepresentation', inner)
emit_table_addition(lines, 'SearchControlAddition', name, '\u0423\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u0438\u0435\u041f\u043e\u0438\u0441\u043a\u043e\u043c', 'SearchControl', inner)
adds = el.get('additions')
emit_table_addition(lines, 'searchString', name, inner, get_addition_override(adds, 'searchString'))
emit_table_addition(lines, 'viewStatus', name, inner, get_addition_override(adds, 'viewStatus'))
emit_table_addition(lines, 'searchControl', name, inner, get_addition_override(adds, 'searchControl'))
# Columns
if el.get('columns') and len(el['columns']) > 0: