From b4fc9bf42c420df06152cdf15e004ac9ac13c1fc Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 4 Jun 2026 20:29:31 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D0=BB?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=BE=D0=B2=D1=8B=D0=B5=20=D1=81=D0=B2=D0=BE?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B0=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B9?= =?UTF-8?q?=20+=20=D1=84=D0=B8=D0=BA=D1=81=20Hiperlink=20(=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B5=D1=80=20L)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit БАГ: у LabelField платформенный тег (опечатка 1С), компилятор эмитил — гиперссылка не работала и не роундтрипилась. Проверено по корпусу: LabelField→Hiperlink во всех версиях формата (2.17 и 2.20). - compiler PS1+PY: LabelField ; EditMode (input/check/labelField); CheckBoxType (check, умный дефолт Auto + suppress как radioButtonType). - decompiler: editMode, checkBoxType (Auto→опустить), markIncomplete (раньше не ловился), labelField читает . - docs/form-dsl-spec: editMode, checkBoxType, примечание про Hiperlink. - tests: input-fields расширен (editMode/checkBoxType/labelField+hyperlink), сертифицирован. Регресс 32/32 PS1+PY, churn по флажкам обновлён и сертифицирован. Co-Authored-By: Claude Opus 4.8 --- .../form-compile/scripts/form-compile.ps1 | 18 +++- .../form-compile/scripts/form-compile.py | 21 ++++- .../form-decompile/scripts/form-decompile.ps1 | 13 ++- docs/form-dsl-spec.md | 6 +- .../cases/form-compile/input-fields.json | 9 +- .../ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml | 1 + .../ПоляВвода/Forms/Форма/Ext/Form.xml | 94 +++++++++++++------ .../КартинкаВСтроке/Forms/Форма/Ext/Form.xml | 1 + 8 files changed, 121 insertions(+), 42 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index a5984b09..29bc4043 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.29 — Compile 1C managed form from JSON or object metadata +# form-compile v1.30 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -1914,7 +1914,7 @@ function Emit-Element { # columnGroup-specific "showInHeader"=1 # radio-specific - "radioButtonType"=1;"choiceList"=1;"columnsCount"=1 + "radioButtonType"=1;"choiceList"=1;"columnsCount"=1;"checkBoxType"=1;"editMode"=1 # naming & binding "name"=1;"path"=1;"title"=1 # visibility & state @@ -2210,6 +2210,7 @@ function Emit-Input { if ($el.spinButton -eq $true) { X "$innertrue" } if ($el.dropListButton -eq $true) { X "$innertrue" } if ($el.markIncomplete -eq $true) { X "$innertrue" } + if ($el.editMode) { X "$inner$($el.editMode)" } if ($el.textEdit -eq $false) { X "$innerfalse" } Emit-Layout -el $el -indent $inner -multiLineDefault ([bool]($el.multiLine -eq $true)) @@ -2237,6 +2238,15 @@ function Emit-Check { Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path) Emit-CommonFlags -el $el -indent $inner + if ($el.editMode) { X "$inner$($el.editMode)" } + # CheckBoxType: нет ключа → умный дефолт Auto; "" → подавить; значение → маппинг + if ($null -ne $el.PSObject.Properties['checkBoxType']) { + if ($el.checkBoxType) { + $cbt = switch ("$($el.checkBoxType)".ToLower()) { 'auto' {'Auto'} 'checkbox' {'CheckBox'} 'switcher' {'Switcher'} 'tumbler' {'Tumbler'} default {"$($el.checkBoxType)"} } + X "$inner$cbt" + } + } else { X "$innerAuto" } + Emit-TitleLocation -el $el -indent $inner -smartDefault "Right" Emit-Layout -el $el -indent $inner @@ -2502,7 +2512,9 @@ function Emit-LabelField { Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path) Emit-CommonFlags -el $el -indent $inner - if ($el.hyperlink -eq $true) { X "$innertrue" } + if ($el.editMode) { X "$inner$($el.editMode)" } + # ВНИМАНИЕ: у LabelField платформенный тег именно (опечатка 1С), не . + if ($el.hyperlink -eq $true) { X "$innertrue" } Emit-Layout -el $el -indent $inner # Companions diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 87b1bf44..58c35536 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.29 — Compile 1C managed form from JSON or object metadata +# form-compile v1.30 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1341,7 +1341,7 @@ KNOWN_KEYS = { "group", "columnGroup", "buttonGroup", "input", "check", "radio", "label", "labelField", "table", "pages", "page", "button", "picture", "picField", "calendar", "cmdBar", "popup", "showInHeader", - "radioButtonType", "choiceList", "columnsCount", + "radioButtonType", "choiceList", "columnsCount", "checkBoxType", "editMode", "name", "path", "title", "visible", "hidden", "enabled", "disabled", "readOnly", "userVisible", "on", "handlers", @@ -2013,6 +2013,8 @@ def emit_input(lines, el, name, eid, indent): lines.append(f'{inner}true') if el.get('markIncomplete') is True: lines.append(f'{inner}true') + if el.get('editMode'): + lines.append(f'{inner}{el["editMode"]}') if el.get('textEdit') is False: lines.append(f'{inner}false') emit_layout(lines, el, inner, multi_line_default=(el.get('multiLine') is True)) @@ -2039,6 +2041,16 @@ def emit_check(lines, el, name, eid, indent): emit_title(lines, el, name, inner, auto=not el.get('path')) emit_common_flags(lines, el, inner) + if el.get('editMode'): + lines.append(f'{inner}{el["editMode"]}') + # CheckBoxType: нет ключа → умный дефолт Auto; "" → подавить; значение → маппинг + _cbt_map = {'auto': 'Auto', 'checkbox': 'CheckBox', 'switcher': 'Switcher', 'tumbler': 'Tumbler'} + if 'checkBoxType' in el: + if el.get('checkBoxType'): + lines.append(f'{inner}{_cbt_map.get(str(el["checkBoxType"]).lower(), el["checkBoxType"])}') + else: + lines.append(f'{inner}Auto') + emit_title_location(lines, el, inner, 'Right') emit_layout(lines, el, inner) @@ -2150,8 +2162,11 @@ def emit_label_field(lines, el, name, eid, indent): emit_title(lines, el, name, inner, auto=not el.get('path')) emit_common_flags(lines, el, inner) + if el.get('editMode'): + lines.append(f'{inner}{el["editMode"]}') + # ВНИМАНИЕ: у LabelField платформенный тег (опечатка 1С), не . if el.get('hyperlink') is True: - lines.append(f'{inner}true') + lines.append(f'{inner}true') emit_layout(lines, el, inner) # Companions diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index dcc44a9e..f62e9b1e 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.7 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.8 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -374,6 +374,8 @@ function Decompile-Element { Add-CommonProps $obj $node $name if ((Get-Child $node 'MultiLine') -eq 'true') { $obj['multiLine'] = $true } if ((Get-Child $node 'PasswordMode') -eq 'true') { $obj['passwordMode'] = $true } + if ((Get-Child $node 'AutoMarkIncomplete') -eq 'true') { $obj['markIncomplete'] = $true } + $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } $tl = Get-Child $node 'TitleLocation'; if ($tl) { $obj['titleLocation'] = $tl.ToLower() } $ih = $node.SelectSingleNode("lf:InputHint", $ns); if ($ih) { $t = Get-LangText $ih; if ($t) { $obj['inputHint'] = $t } } foreach ($p in @('ChoiceButton','ClearButton','SpinButton','DropListButton')) { @@ -384,6 +386,11 @@ function Decompile-Element { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name + $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } + # CheckBoxType: Auto = умный дефолт → опустить; нет тега → ""; иначе значение + $cbt = Get-Child $node 'CheckBoxType' + if ($null -eq $cbt) { $obj['checkBoxType'] = '' } + elseif ($cbt -ne 'Auto') { $obj['checkBoxType'] = $cbt.Substring(0,1).ToLower() + $cbt.Substring(1) } Add-TitleLocation $obj $node 'Right' } 'RadioButtonField' { @@ -419,7 +426,9 @@ function Decompile-Element { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name - if ((Get-Child $node 'Hyperlink') -eq 'true') { $obj['hyperlink'] = $true } + $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } + # LabelField: тег (опечатка платформы), не + if ((Get-Child $node 'Hiperlink') -eq 'true') { $obj['hyperlink'] = $true } } 'PictureDecoration' { $obj[$key] = $name diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 724338d8..4e3e19d9 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -198,6 +198,7 @@ | `spinButton` | bool | Показывать кнопку прокрутки | | `dropListButton` | bool | Показывать кнопку раскрытия | | `markIncomplete` | bool | Автопометка незаполненных | +| `editMode` | string | Режим редактирования: `EnterOnInput`, `Directly` | | `skipOnInput` | bool | Пропускать при вводе | | `inputHint` | string | Подсказка ввода (placeholder) | | `width` | int | Ширина | @@ -216,6 +217,8 @@ | Свойство | Тип | Описание | |----------|-----|----------| | `path` | string | DataPath | +| `checkBoxType` | string | Вид флажка. **Нет ключа** → умный дефолт `Auto`. **`""`** → дефолт платформы (тег не пишется). Значения: `auto`, `checkBox`, `switcher`, `tumbler` | +| `editMode` | string | Режим редактирования: `EnterOnInput`, `Directly` | | `titleLocation` | string | Расположение заголовка. **Нет ключа** → умный дефолт `Right` (флажки почти всегда справа). **`""`** → дефолт платформы (`Left`, тег не пишется). Значение (`none`/`left`/`top`/…) → как указано | #### radio — RadioButtonField @@ -271,7 +274,8 @@ | Свойство | Тип | Описание | |----------|-----|----------| | `path` | string | DataPath | -| `hyperlink` | bool | Режим гиперссылки | +| `hyperlink` | bool | Режим гиперссылки (у LabelField платформенный тег `` — опечатка 1С, компилятор учитывает) | +| `editMode` | string | Режим редактирования: `EnterOnInput`, `Directly` | #### table — Table diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index a29c9cc3..86bcfaf3 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -16,14 +16,16 @@ "input": { "title": "Поля ввода", "elements": [ - { "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле" }, + { "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "editMode": "EnterOnInput" }, + { "labelField": "Ссылка", "path": "ОбычноеПоле", "hyperlink": true }, { "input": "МногострочноеПоле", "path": "МногострочноеПоле", "multiLine": true, "height": 5, "title": "Комментарий" }, { "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" }, { "input": "ПолеСКнопками", "path": "ПолеСКнопками", "choiceButton": true, "clearButton": true, "title": "Выбор" }, { "input": "ПолеПодсказка", "path": "ПолеПодсказка", "inputHint": "Введите значение...", "title": "Подсказка" }, { "check": "Флаг", "path": "Флаг", "title": "Включено" }, { "check": "ФлагПлатформенный", "path": "ФлагПлатформенный", "title": "Слева", "titleLocation": "" }, - { "check": "ФлагЯвный", "path": "ФлагЯвный", "title": "Сверху", "titleLocation": "top" } + { "check": "ФлагЯвный", "path": "ФлагЯвный", "title": "Сверху", "titleLocation": "top" }, + { "check": "ФлагТумблер", "path": "ФлагТумблер", "title": "Тумблер", "checkBoxType": "switcher" } ], "attributes": [ { "name": "Объект", "type": "DataProcessorObject.ПоляВвода", "main": true }, @@ -34,7 +36,8 @@ { "name": "ПолеПодсказка", "type": "string" }, { "name": "Флаг", "type": "boolean" }, { "name": "ФлагПлатформенный", "type": "boolean" }, - { "name": "ФлагЯвный", "type": "boolean" } + { "name": "ФлагЯвный", "type": "boolean" }, + { "name": "ФлагТумблер", "type": "boolean" } ] } } diff --git a/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml index 38352ce8..0f7a3e87 100644 --- a/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/file-dialog/DataProcessors/ЗагрузкаИзФайла/Forms/Форма/Ext/Form.xml @@ -53,6 +53,7 @@ ПерваяСтрокаЗаголовок + Auto Right 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 0a81de55..9d975018 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 @@ -17,10 +17,17 @@ Обычное поле + EnterOnInput - + + ОбычноеПоле + true + + + + МногострочноеПоле <v8:item> @@ -31,10 +38,10 @@ <MultiLine>true</MultiLine> <AutoMaxWidth>false</AutoMaxWidth> <Height>5</Height> - <ContextMenu name="МногострочноеПолеКонтекстноеМеню" id="5"/> - <ExtendedTooltip name="МногострочноеПолеРасширеннаяПодсказка" id="6"/> + <ContextMenu name="МногострочноеПолеКонтекстноеМеню" id="8"/> + <ExtendedTooltip name="МногострочноеПолеРасширеннаяПодсказка" id="9"/> </InputField> - <InputField name="ПолеПароля" id="7"> + <InputField name="ПолеПароля" id="10"> <DataPath>ПолеПароля</DataPath> <Title> <v8:item> @@ -43,10 +50,10 @@ </v8:item> true - - + + - + ПолеСКнопками <v8:item> @@ -55,10 +62,10 @@ </v8:item> true - - + + - + ПолеПодсказка <v8:item> @@ -72,10 +79,10 @@ <v8:content>Введите значение...</v8:content> </v8:item> </InputHint> - <ContextMenu name="ПолеПодсказкаКонтекстноеМеню" id="14"/> - <ExtendedTooltip name="ПолеПодсказкаРасширеннаяПодсказка" id="15"/> + <ContextMenu name="ПолеПодсказкаКонтекстноеМеню" id="17"/> + <ExtendedTooltip name="ПолеПодсказкаРасширеннаяПодсказка" id="18"/> </InputField> - <CheckBoxField name="Флаг" id="16"> + <CheckBoxField name="Флаг" id="19"> <DataPath>Флаг</DataPath> <Title> <v8:item> @@ -83,11 +90,12 @@ <v8:content>Включено</v8:content> </v8:item> + Auto Right - - + + - + ФлагПлатформенный <v8:item> @@ -95,10 +103,11 @@ <v8:content>Слева</v8:content> </v8:item> - - + Auto + + - + ФлагЯвный <v8:item> @@ -106,19 +115,33 @@ <v8:content>Сверху</v8:content> </v8:item> + Auto Top - - + + + + + ФлагТумблер + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Тумблер</v8:content> + </v8:item> + + Switcher + Right + + - + cfg:DataProcessorObject.ПоляВвода true - + <v8:item> <v8:lang>ru</v8:lang> @@ -133,7 +156,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="МногострочноеПоле" id="27"> + <Attribute name="МногострочноеПоле" id="33"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -148,7 +171,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеПароля" id="28"> + <Attribute name="ПолеПароля" id="34"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -163,7 +186,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеСКнопками" id="29"> + <Attribute name="ПолеСКнопками" id="35"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -178,7 +201,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеПодсказка" id="30"> + <Attribute name="ПолеПодсказка" id="36"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -193,7 +216,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="Флаг" id="31"> + <Attribute name="Флаг" id="37"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -204,7 +227,7 @@ <v8:Type>xs:boolean</v8:Type> </Type> </Attribute> - <Attribute name="ФлагПлатформенный" id="32"> + <Attribute name="ФлагПлатформенный" id="38"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -215,7 +238,7 @@ <v8:Type>xs:boolean</v8:Type> </Type> </Attribute> - <Attribute name="ФлагЯвный" id="33"> + <Attribute name="ФлагЯвный" id="39"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -226,5 +249,16 @@ <v8:Type>xs:boolean</v8:Type> </Type> </Attribute> + <Attribute name="ФлагТумблер" id="40"> + <Title> + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Флаг тумблер</v8:content> + </v8:item> + + + xs:boolean + + diff --git a/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml index b15a2177..31c545ce 100644 --- a/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/picture-field/DataProcessors/КартинкаВСтроке/Forms/Форма/Ext/Form.xml @@ -60,6 +60,7 @@ Флаг + Auto Right