diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1
index 2feb6f61..7444ebc0 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.67 — Compile 1C managed form from JSON or object metadata
+# form-compile v1.68 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -2940,6 +2940,11 @@ function Emit-Input {
Emit-ChoiceList -el $el -indent $inner
+ # Связи по типу / связи параметров выбора / параметры выбора
+ Emit-TypeLink -el $el -indent $inner
+ Emit-ChoiceParameterLinks -el $el -indent $inner
+ Emit-ChoiceParameters -el $el -indent $inner
+
# Оформление (цвета/шрифты/граница) — перед компаньонами
Emit-Appearance -el $el -indent $inner -profile 'field'
@@ -3177,6 +3182,103 @@ function Emit-ChoiceList {
X "$indent"
}
+# Чтение свойства из hashtable/PSCustomObject по списку синонимов (первый найденный, иначе $null).
+function Get-ElProp {
+ param($obj, [string[]]$names)
+ if ($null -eq $obj) { return $null }
+ foreach ($n in $names) {
+ if ($obj -is [System.Collections.IDictionary]) {
+ if ($obj.Contains($n)) { return $obj[$n] }
+ } elseif ($obj.PSObject -and $obj.PSObject.Properties[$n]) {
+ return $obj.PSObject.Properties[$n].Value
+ }
+ }
+ return $null
+}
+
+# Внутреннее значение параметра выбора (FormChoiceListDesTimeValue): + .
+# Скаляр → один Value (через Normalize-ChoiceValue); массив → v8:FixedArray из вложенных FormChoiceListDesTimeValue.
+function Emit-ChoiceParamValue {
+ param($value, [string]$indent)
+ X "$indent"
+ $isArray = ($value -is [System.Array]) -or ($value -is [System.Collections.IList] -and $value -isnot [string])
+ if ($isArray) {
+ X "$indent"
+ foreach ($v in $value) {
+ $norm = Normalize-ChoiceValue -value $v
+ X "$indent`t"
+ X "$indent`t`t"
+ X "$indent`t`t$(Esc-Xml $norm.Text)"
+ X "$indent`t"
+ }
+ X "$indent"
+ } else {
+ $norm = Normalize-ChoiceValue -value $value
+ X "$indent$(Esc-Xml $norm.Text)"
+ }
+}
+
+# (параметры выбора поля ввода) — [{name, value}]. value через Normalize-ChoiceValue;
+# массив значений → FixedArray. Рус. синонимы имя/значение.
+function Emit-ChoiceParameters {
+ param($el, [string]$indent)
+ $cp = $el.choiceParameters
+ if (-not $cp -or @($cp).Count -eq 0) { return }
+ X "$indent"
+ foreach ($item in @($cp)) {
+ $name = Get-ElProp $item @('name','имя')
+ $val = Get-ElProp $item @('value','значение')
+ X "$indent`t"
+ X "$indent`t`t"
+ Emit-ChoiceParamValue -value $val -indent "$indent`t`t`t"
+ X "$indent`t`t"
+ X "$indent`t"
+ }
+ X "$indent"
+}
+
+# (связи параметров выбора) — [{name, dataPath, valueChange?}].
+# valueChange всегда эмитится, дефолт Clear; forgiving Clear/DontChange + рус. синонимы.
+function Emit-ChoiceParameterLinks {
+ param($el, [string]$indent)
+ $cpl = $el.choiceParameterLinks
+ if (-not $cpl -or @($cpl).Count -eq 0) { return }
+ X "$indent"
+ foreach ($lk in @($cpl)) {
+ $name = Get-ElProp $lk @('name','имя')
+ $dp = Get-ElProp $lk @('dataPath','path','путь')
+ $vcRaw = Get-ElProp $lk @('valueChange','режимИзменения')
+ $vc = "Clear"
+ if ($vcRaw) {
+ $vc = switch -Regex ("$vcRaw".ToLower()) {
+ '^(clear|очистить|очистка)$' { "Clear"; break }
+ '^(dontchange|неизменять|неменять|нет)$' { "DontChange"; break }
+ default { "$vcRaw" }
+ }
+ }
+ X "$indent`t"
+ X "$indent`t`t$(Esc-Xml "$name")"
+ X "$indent`t`t$(Esc-Xml "$dp")"
+ X "$indent`t`t$vc"
+ X "$indent`t"
+ }
+ X "$indent"
+}
+
+# (связь по типу) — {dataPath, linkItem}. linkItem дефолт 0.
+function Emit-TypeLink {
+ param($el, [string]$indent)
+ $tl = $el.typeLink
+ if (-not $tl) { return }
+ $dp = Get-ElProp $tl @('dataPath','path','путь')
+ $li = Get-ElProp $tl @('linkItem','элементСвязи')
+ if ($null -eq $li) { $li = 0 }
+ X "$indent"
+ X "$indent`t$(Esc-Xml "$dp")"
+ X "$indent`t$li"
+ X "$indent"
+}
+
function Emit-Radio {
param($el, [string]$name, [int]$id, [string]$indent)
diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py
index 304948b6..84c09b99 100644
--- a/.claude/skills/form-compile/scripts/form-compile.py
+++ b/.claude/skills/form-compile/scripts/form-compile.py
@@ -2001,6 +2001,99 @@ def emit_choice_list(lines, el, indent):
lines.append(f'{indent}')
+def get_el_prop(obj, names):
+ # Читает свойство из dict по списку синонимов (первый найденный, иначе None).
+ if not isinstance(obj, dict):
+ return None
+ for n in names:
+ if n in obj:
+ return obj[n]
+ return None
+
+
+def emit_choice_param_value(lines, value, indent):
+ # Внутреннее значение параметра выбора (FormChoiceListDesTimeValue): + .
+ # Скаляр → один Value; массив → v8:FixedArray из вложенных FormChoiceListDesTimeValue.
+ lines.append(f'{indent}')
+ if isinstance(value, (list, tuple)):
+ lines.append(f'{indent}')
+ for v in value:
+ norm = normalize_choice_value(v)
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}\t\t')
+ lines.append(f'{indent}\t\t{esc_xml(norm["text"])}')
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}')
+ else:
+ norm = normalize_choice_value(value)
+ lines.append(f'{indent}{esc_xml(norm["text"])}')
+
+
+def emit_choice_parameters(lines, el, indent):
+ # (параметры выбора поля ввода) — [{name, value}]. value через
+ # normalize_choice_value; массив значений → FixedArray. Рус. синонимы имя/значение.
+ cp = el.get('choiceParameters') or []
+ if not cp:
+ return
+ lines.append(f'{indent}')
+ for item in cp:
+ name = get_el_prop(item, ('name', 'имя'))
+ val = get_el_prop(item, ('value', 'значение'))
+ name_s = '' if name is None else str(name)
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}\t\t')
+ emit_choice_param_value(lines, val, f'{indent}\t\t\t')
+ lines.append(f'{indent}\t\t')
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}')
+
+
+def emit_choice_parameter_links(lines, el, indent):
+ # (связи параметров выбора) — [{name, dataPath, valueChange?}].
+ # valueChange всегда эмитится, дефолт Clear; forgiving Clear/DontChange + рус. синонимы.
+ cpl = el.get('choiceParameterLinks') or []
+ if not cpl:
+ return
+ lines.append(f'{indent}')
+ for lk in cpl:
+ name = get_el_prop(lk, ('name', 'имя'))
+ dp = get_el_prop(lk, ('dataPath', 'path', 'путь'))
+ vc_raw = get_el_prop(lk, ('valueChange', 'режимИзменения'))
+ vc = 'Clear'
+ if vc_raw:
+ s = str(vc_raw).lower()
+ if s in ('clear', 'очистить', 'очистка'):
+ vc = 'Clear'
+ elif s in ('dontchange', 'неизменять', 'неменять', 'нет'):
+ vc = 'DontChange'
+ else:
+ vc = str(vc_raw)
+ name_s = '' if name is None else str(name)
+ dp_s = '' if dp is None else str(dp)
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}\t\t{esc_xml(name_s)}')
+ lines.append(f'{indent}\t\t{esc_xml(dp_s)}')
+ lines.append(f'{indent}\t\t{vc}')
+ lines.append(f'{indent}\t')
+ lines.append(f'{indent}')
+
+
+def emit_type_link(lines, el, indent):
+ # (связь по типу) — {dataPath, linkItem}. linkItem дефолт 0.
+ tl = el.get('typeLink')
+ if not tl:
+ return
+ dp = get_el_prop(tl, ('dataPath', 'path', 'путь'))
+ li = get_el_prop(tl, ('linkItem', 'элементСвязи'))
+ if li is None:
+ li = 0
+ dp_s = '' if dp is None else str(dp)
+ lines.append(f'{indent}')
+ lines.append(f'{indent}\t{esc_xml(dp_s)}')
+ lines.append(f'{indent}\t{li}')
+ lines.append(f'{indent}')
+
+
def normalize_radio_button_type(raw):
if not raw:
return "Auto"
@@ -2847,6 +2940,11 @@ def emit_input(lines, el, name, eid, indent):
emit_choice_list(lines, el, inner)
+ # Связи по типу / связи параметров выбора / параметры выбора
+ emit_type_link(lines, el, inner)
+ emit_choice_parameter_links(lines, el, inner)
+ emit_choice_parameters(lines, el, inner)
+
# Оформление (цвета/шрифты/граница) — перед компаньонами
emit_appearance(lines, el, inner, 'field')
diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1
index 7b8e4bcd..0ddd7585 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.47 — Decompile 1C managed Form.xml to JSON DSL (draft)
+# form-decompile v0.48 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -40,6 +40,7 @@ $NS_DCSSET = "http://v8.1c.ru/8.1/data-composition-system/settings"
$NS_DCSSCH = "http://v8.1c.ru/8.1/data-composition-system/schema"
$NS_DCSCOR = "http://v8.1c.ru/8.1/data-composition-system/core"
$NS_V8UI = "http://v8.1c.ru/8.1/data/ui"
+$NS_APP = "http://v8.1c.ru/8.2/managed-application/core"
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("lf", $NS_LF)
@@ -50,6 +51,7 @@ $ns.AddNamespace("dcsset", $NS_DCSSET)
$ns.AddNamespace("dcssch", $NS_DCSSCH)
$ns.AddNamespace("dcscor", $NS_DCSCOR)
$ns.AddNamespace("v8ui", $NS_V8UI)
+$ns.AddNamespace("app", $NS_APP)
# Каноничные GUID пустых контейнеров ListSettings (умолчание платформы, ~90% форм).
# Если ListSettings = пустой скелет с этими GUID → декомпилятор опускает настройки вовсе,
@@ -1178,6 +1180,70 @@ function Decompile-ChoiceList {
return $null
}
+# Значение Параметра выбора (): скаляр через Convert-TypedValue, либо
+# v8:FixedArray → массив скаляров (каждый — FormChoiceListDesTimeValue с внутренним lf:Value).
+function Convert-ChoiceParamValue {
+ param($valNode)
+ $vt = $valNode.GetAttribute("type", $NS_XSI)
+ if ($vt -match 'FixedArray$') {
+ $arr = New-Object System.Collections.ArrayList
+ foreach ($it in @($valNode.SelectNodes("v8:Value", $ns))) {
+ $inner = $it.SelectSingleNode("lf:Value", $ns)
+ if ($inner) { [void]$arr.Add((Convert-TypedValue -raw $inner.InnerText -xsiType ($inner.GetAttribute("type", $NS_XSI)))) }
+ }
+ return ,@($arr)
+ }
+ return Convert-TypedValue -raw $valNode.InnerText -xsiType $vt
+}
+
+# Инверсия Emit-ChoiceParameters: → [{name, value}].
+function Decompile-ChoiceParameters {
+ param($node)
+ $cpn = $node.SelectSingleNode("lf:ChoiceParameters", $ns)
+ if (-not $cpn) { return $null }
+ $items = New-Object System.Collections.ArrayList
+ foreach ($it in @($cpn.SelectNodes("app:item", $ns))) {
+ $o = [ordered]@{}
+ $o['name'] = $it.GetAttribute("name")
+ $valNode = $it.SelectSingleNode("app:value/lf:Value", $ns)
+ if ($valNode) { $o['value'] = Convert-ChoiceParamValue $valNode }
+ [void]$items.Add($o)
+ }
+ if ($items.Count -gt 0) { return ,@($items) }
+ return $null
+}
+
+# Инверсия Emit-ChoiceParameterLinks: →
+# [{name, dataPath, valueChange?}]. valueChange дефолт Clear → опускаем (компилятор восстановит).
+function Decompile-ChoiceParameterLinks {
+ param($node)
+ $cln = $node.SelectSingleNode("lf:ChoiceParameterLinks", $ns)
+ if (-not $cln) { return $null }
+ $items = New-Object System.Collections.ArrayList
+ foreach ($lk in @($cln.SelectNodes("xr:Link", $ns))) {
+ $o = [ordered]@{}
+ $o['name'] = Get-Text $lk "xr:Name"
+ $o['dataPath'] = Get-Text $lk "xr:DataPath"
+ $vc = Get-Text $lk "xr:ValueChange"
+ if ($vc -and $vc -ne 'Clear') { $o['valueChange'] = $vc }
+ [void]$items.Add($o)
+ }
+ if ($items.Count -gt 0) { return ,@($items) }
+ return $null
+}
+
+# Инверсия Emit-TypeLink: → {dataPath, linkItem}.
+function Decompile-TypeLink {
+ param($node)
+ $tn = $node.SelectSingleNode("lf:TypeLink", $ns)
+ if (-not $tn) { return $null }
+ $o = [ordered]@{}
+ $o['dataPath'] = Get-Text $tn "xr:DataPath"
+ $li = Get-Text $tn "xr:LinkItem"
+ if ($null -ne $li -and $li -ne '') { $o['linkItem'] = [int]$li }
+ return $o
+}
+
function Decompile-Element {
param($node)
$tag = $node.LocalName
@@ -1244,6 +1310,10 @@ function Decompile-Element {
}
$cbr = Get-Child $node 'ChoiceButtonRepresentation'; if ($cbr) { $obj['choiceButtonRepresentation'] = $cbr }
$cl = Decompile-ChoiceList $node; if ($cl) { $obj['choiceList'] = $cl }
+ # Параметры выбора / Связи параметров выбора / Связь по типу
+ $cp = Decompile-ChoiceParameters $node; if ($cp) { $obj['choiceParameters'] = $cp }
+ $cpl = Decompile-ChoiceParameterLinks $node; if ($cpl) { $obj['choiceParameterLinks'] = $cpl }
+ $tlk = Decompile-TypeLink $node; if ($tlk) { $obj['typeLink'] = $tlk }
}
'CheckBoxField' {
$obj[$key] = $name
diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md
index 1cbf9abf..efc0449e 100644
--- a/docs/form-dsl-spec.md
+++ b/docs/form-dsl-spec.md
@@ -364,6 +364,31 @@ companion-панели с собственным контентом. Оба не
| `verticalStretch` | bool | Растягивание по вертикали |
| `autoMaxWidth` | bool | Автомаксимальная ширина |
| `autoMaxHeight` | bool | Автомаксимальная высота |
+| `choiceParameters` | array | Параметры выбора — см. ниже |
+| `choiceParameterLinks` | array | Связи параметров выбора — см. ниже |
+| `typeLink` | object | Связь по типу — см. ниже |
+
+##### Параметры выбора, связи параметров выбора, связь по типу
+
+Свойства поля ввода, управляющие выбором значения и типом. Имена параметров (`"Отбор.Х"`) — строки 1С как есть.
+
+```json
+{ "input": "Контрагент", "path": "Объект.Контрагент",
+ "choiceParameters": [
+ { "name": "Отбор.Активный", "value": true },
+ { "name": "Отбор.ВидПродукции", "value": ["Enum.Виды.Агрохимикат", "Enum.Виды.Пестицид"] }
+ ],
+ "choiceParameterLinks": [
+ { "name": "Отбор.Организация", "dataPath": "Объект.Организация" },
+ { "name": "Отбор.Тип", "dataPath": "Объект.Тип", "valueChange": "DontChange" }
+ ],
+ "typeLink": { "dataPath": "Объект.ЗначениеДата", "linkItem": 0 }
+}
+```
+
+- **`choiceParameters`** — `[{ name, value }]`. `value` через ту же нормализацию, что `choiceList`: bool / число / строка / ссылка-путь (`Enum.X.Y`, `Catalog.X` и синонимы `Перечисление.`/`Справочник.`). **Массив** значений → фиксированный массив (`FixedArray`). Синонимы ключей: `имя`/`значение`.
+- **`choiceParameterLinks`** — `[{ name, dataPath, valueChange? }]`. `valueChange`: `Clear` (дефолт, опускается) / `DontChange`; forgiving `очистить`/`неизменять`. Синонимы: `имя`/`путь`/`режимИзменения`.
+- **`typeLink`** — `{ dataPath, linkItem }`. `linkItem` — индекс (дефолт `0`). Синонимы: `путь`/`элементСвязи`.
#### check — CheckBoxField
diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json
index 714591a4..e1129dcf 100644
--- a/tests/skills/cases/form-compile/input-fields.json
+++ b/tests/skills/cases/form-compile/input-fields.json
@@ -26,6 +26,18 @@
{ "value": "Второй", "presentation": "Второй вариант" }
]},
{ "input": "ПолеПодсказка", "path": "ПолеПодсказка", "inputHint": "Введите значение...", "title": "Подсказка" },
+ { "input": "ПолеСвязи", "path": "ПолеСвязи", "title": "Поле со связями",
+ "choiceParameters": [
+ { "name": "Отбор.Активный", "value": true },
+ { "name": "Отбор.Вид", "value": "Основной" },
+ { "name": "Отбор.Список", "value": ["Один", "Два"] }
+ ],
+ "choiceParameterLinks": [
+ { "name": "Отбор.Организация", "dataPath": "ОбычноеПоле" },
+ { "name": "Отбор.Тип", "dataPath": "ПолеСписокВыбора", "valueChange": "DontChange" }
+ ],
+ "typeLink": { "dataPath": "ПолеПодсказка", "linkItem": 0 }
+ },
{ "check": "Флаг", "path": "Флаг", "title": "Включено" },
{ "check": "ФлагПлатформенный", "path": "ФлагПлатформенный", "title": "Слева", "titleLocation": "" },
{ "check": "ФлагЯвный", "path": "ФлагЯвный", "title": "Сверху", "titleLocation": "top" },
@@ -39,6 +51,7 @@
{ "name": "ПолеСКнопками", "type": "string" },
{ "name": "ПолеСписокВыбора", "type": "string" },
{ "name": "ПолеПодсказка", "type": "string" },
+ { "name": "ПолеСвязи", "type": "string" },
{ "name": "Флаг", "type": "boolean" },
{ "name": "ФлагПлатформенный", "type": "boolean" },
{ "name": "ФлагЯвный", "type": "boolean" },
diff --git a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml
index 1e20b1d9..4a3887c2 100644
--- a/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml
+++ b/tests/skills/cases/form-compile/snapshots/input-fields/DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml
@@ -138,7 +138,63 @@
-
+
+ ПолеСвязи
+
+
+ ru
+ Поле со связями
+
+
+
+ ПолеПодсказка
+ 0
+
+
+
+ Отбор.Организация
+ ОбычноеПоле
+ Clear
+
+
+ Отбор.Тип
+ ПолеСписокВыбора
+ DontChange
+
+
+
+
+
+
+ true
+
+
+
+
+
+ Основной
+
+
+
+
+
+
+
+
+ Один
+
+
+
+ Два
+
+
+
+
+
+
+
+
+
Флаг
@@ -148,10 +204,10 @@
Auto
Right
-
-
+
+
-
+
ФлагПлатформенный
@@ -160,10 +216,10 @@
Auto
-
-
+
+
-
+
ФлагЯвный
@@ -173,10 +229,10 @@
Auto
Top
-
-
+
+
-
+
ФлагТумблер
@@ -186,18 +242,18 @@
Switcher
Right
-
-
+
+
-
+
cfg:DataProcessorObject.ПоляВвода
true
-
+
ru
@@ -212,7 +268,7 @@
-
+
ru
@@ -227,7 +283,7 @@
-
+
ru
@@ -242,7 +298,7 @@
-
+
ru
@@ -257,7 +313,7 @@
-
+
ru
@@ -272,7 +328,7 @@
-
+
ru
@@ -287,7 +343,22 @@
-
+
+
+
+ ru
+ Поле связи
+
+
+
+ xs:string
+
+ 0
+ Variable
+
+
+
+
ru
@@ -298,7 +369,7 @@
xs:boolean
-
+
ru
@@ -309,7 +380,7 @@
xs:boolean
-
+
ru
@@ -320,7 +391,7 @@
xs:boolean
-
+
ru