From c43041c0b78b30f07ae85c1d706018cb642fb47f Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Fri, 5 Jun 2026 13:33:05 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20TitleLocat?= =?UTF-8?q?ion=20=D1=83=20LabelField/PictureField/Table=20(=D0=BA=D0=BB?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B5=D1=80=20TitleLocation)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TitleLocation обрабатывался только у input (passthrough), check/radio (smart-default) и calendar. У LabelField (7362 в корпусе), PictureField (2479) и Table (381) тег молча терялся — ни декомпилятор, ни компилятор его не знали. Профиль доли элементов с тегом: Table 2.9%, LabelField 15.8%, PictureField 80.5% (но 20% без тега). Платформа НЕ всегда эмитит → выбран passthrough (эмитим при наличии ключа, как у input/calendar), не smart-default. Корректно и консистентно; переиспользован существующий ключ titleLocation + Map-TitleLoc. Декомпилятор (ps1): захват titleLocation в трёх ветках. Компилятор (ps1+py): эмиссия в Emit-LabelField/Emit-Table/Emit-PictureField в позиции по схеме. spec §4.1: titleLocation вынесен в общие свойства с пометкой охвата. Тест-покрытие добавлено в input-fields (labelField=left), picture-field (picField=none), table (table=top) — снэпшоты сертифицированы в 1С 8.3.24. Раундтрип 60 форм с TitleLocation на label/pic/table/radio: остатка нет. Регресс ps+py 33/33. Co-Authored-By: Claude Opus 4.8 --- .claude/skills/form-compile/scripts/form-compile.ps1 | 6 +++++- .claude/skills/form-compile/scripts/form-compile.py | 9 ++++++++- .claude/skills/form-decompile/scripts/form-decompile.ps1 | 5 ++++- docs/form-dsl-spec.md | 1 + tests/skills/cases/form-compile/input-fields.json | 2 +- tests/skills/cases/form-compile/picture-field.json | 2 +- .../DataProcessors/ПоляВвода/Forms/Форма/Ext/Form.xml | 1 + .../КартинкаВСтроке/Forms/Форма/Ext/Form.xml | 1 + .../DataProcessors/Таблица/Forms/Форма/Ext/Form.xml | 1 + tests/skills/cases/form-compile/table.json | 2 +- 10 files changed, 24 insertions(+), 6 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 040b2367..6aac38b0 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.33 — Compile 1C managed form from JSON or object metadata +# form-compile v1.34 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2551,6 +2551,7 @@ function Emit-LabelField { Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path) Emit-CommonFlags -el $el -indent $inner + if ($el.titleLocation) { X "$inner$(Map-TitleLoc "$($el.titleLocation)")" } if ($el.editMode) { X "$inner$($el.editMode)" } # ВНИМАНИЕ: у LabelField платформенный тег именно (опечатка 1С), не . if ($el.hyperlink -eq $true) { X "$innertrue" } @@ -2579,6 +2580,7 @@ function Emit-Table { if ($el.representation) { X "$inner$($el.representation)" } + if ($el.titleLocation) { X "$inner$(Map-TitleLoc "$($el.titleLocation)")" } if ($el.changeRowSet -eq $true) { X "$innertrue" } if ($el.changeRowOrder -eq $true) { X "$innertrue" } if ($el.height) { X "$inner$($el.height)" } @@ -2833,6 +2835,8 @@ function Emit-PictureField { Emit-Title -el $el -name $name -indent $inner Emit-CommonFlags -el $el -indent $inner + if ($el.titleLocation) { X "$inner$(Map-TitleLoc "$($el.titleLocation)")" } + # ValuesPicture — picture (collection) used to render the field's value. # Required for a Boolean-bound PictureField to actually show an icon. # loadTransparent emitted only when true (1С default is false). diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 7f8f86d8..9dd3a3ac 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.33 — Compile 1C managed form from JSON or object metadata +# form-compile v1.34 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -2199,6 +2199,8 @@ 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('titleLocation'): + lines.append(f'{inner}{map_title_loc(el["titleLocation"])}') if el.get('editMode'): lines.append(f'{inner}{el["editMode"]}') # ВНИМАНИЕ: у LabelField платформенный тег (опечатка 1С), не . @@ -2227,6 +2229,8 @@ def emit_table(lines, el, name, eid, indent): if el.get('representation'): lines.append(f'{inner}{el["representation"]}') + if el.get('titleLocation'): + lines.append(f'{inner}{map_title_loc(el["titleLocation"])}') if el.get('changeRowSet') is True: lines.append(f'{inner}true') if el.get('changeRowOrder') is True: @@ -2465,6 +2469,9 @@ def emit_picture_field(lines, el, name, eid, indent): emit_title(lines, el, name, inner) emit_common_flags(lines, el, inner) + if el.get('titleLocation'): + lines.append(f'{inner}{map_title_loc(el["titleLocation"])}') + # ValuesPicture \u2014 picture (collection) used to render the field's value. # Required for a Boolean-bound PictureField to actually show an icon. # loadTransparent emitted only when true (1\u0421 default is false). diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 309a93de..bc4c36cc 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.13 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.14 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -401,6 +401,7 @@ function Decompile-Element { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name + $tl = Get-Child $node 'TitleLocation'; if ($tl) { $obj['titleLocation'] = $tl.ToLower() } $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } # LabelField: тег (опечатка платформы), не if ((Get-Child $node 'Hiperlink') -eq 'true') { $obj['hyperlink'] = $true } @@ -416,6 +417,7 @@ function Decompile-Element { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name + $tl = Get-Child $node 'TitleLocation'; if ($tl) { $obj['titleLocation'] = $tl.ToLower() } $ref = $node.SelectSingleNode("lf:ValuesPicture/xr:Ref", $ns); if ($ref) { $obj['valuesPicture'] = $ref.InnerText } } 'CalendarField' { @@ -433,6 +435,7 @@ function Decompile-Element { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name + $tl = Get-Child $node 'TitleLocation'; if ($tl) { $obj['titleLocation'] = $tl.ToLower() } $rep = Get-Child $node 'Representation'; if ($rep) { $obj['representation'] = $rep } if ((Get-Child $node 'ChangeRowSet') -eq 'true') { $obj['changeRowSet'] = $true } if ((Get-Child $node 'ChangeRowOrder') -eq 'true') { $obj['changeRowOrder'] = $true } diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 793924fd..e4b8820a 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -115,6 +115,7 @@ | `disabled` | bool | `true` → `false` | | `readOnly` | bool | `true` → `true` | | `events` | object | Обработчики событий: `{ "ИмяСобытия": "ИмяОбработчика" }` — тот же формат, что у событий формы (§3). Значение `null` → имя по конвенции (§4.2). См. §4.2 | +| `titleLocation` | string | Расположение заголовка: `none`/`left`/`right`/`top`/`bottom`/`auto`. Эмитится при наличии (input, labelField, picField, table, calendar). У `check`/`radio` — особая семантика с умным дефолтом (см. их разделы) | ### 4.1a. Общие layout-свойства diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index 86bcfaf3..1a7ecb3c 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -17,7 +17,7 @@ "title": "Поля ввода", "elements": [ { "input": "ОбычноеПоле", "path": "ОбычноеПоле", "title": "Обычное поле", "editMode": "EnterOnInput" }, - { "labelField": "Ссылка", "path": "ОбычноеПоле", "hyperlink": true }, + { "labelField": "Ссылка", "path": "ОбычноеПоле", "titleLocation": "left", "hyperlink": true }, { "input": "МногострочноеПоле", "path": "МногострочноеПоле", "multiLine": true, "height": 5, "title": "Комментарий" }, { "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" }, { "input": "ПолеСКнопками", "path": "ПолеСКнопками", "choiceButton": true, "clearButton": true, "title": "Выбор" }, diff --git a/tests/skills/cases/form-compile/picture-field.json b/tests/skills/cases/form-compile/picture-field.json index 1352ed49..b84f89e9 100644 --- a/tests/skills/cases/form-compile/picture-field.json +++ b/tests/skills/cases/form-compile/picture-field.json @@ -20,7 +20,7 @@ "on": ["Selection"], "handlers": { "Selection": "ТаблицаДанныхВыбор" }, "columns": [ { "input": "ТаблицаДанныхНоменклатура", "path": "ТаблицаДанных.Номенклатура" }, - { "picField": "ТаблицаДанныхКартинка", "path": "ТаблицаДанных.Картинка", "valuesPicture": "StdPicture.Favorites", "loadTransparent": true }, + { "picField": "ТаблицаДанныхКартинка", "path": "ТаблицаДанных.Картинка", "titleLocation": "none", "valuesPicture": "StdPicture.Favorites", "loadTransparent": true }, { "check": "ТаблицаДанныхКартинкаФлаг", "path": "ТаблицаДанных.Картинка", "title": "Флаг" } ]} ], 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 9d975018..c8742959 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 @@ -23,6 +23,7 @@ ОбычноеПоле + Left true 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 31c545ce..7abc74ff 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 @@ -45,6 +45,7 @@ ТаблицаДанных.Картинка + None StdPicture.Favorites true diff --git a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml index eaa9d72e..5c3bd19c 100644 --- a/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/table/DataProcessors/Таблица/Forms/Форма/Ext/Form.xml @@ -11,6 +11,7 @@ Данные + ToptrueNoneNone diff --git a/tests/skills/cases/form-compile/table.json b/tests/skills/cases/form-compile/table.json index 4ceea0b7..311d81f7 100644 --- a/tests/skills/cases/form-compile/table.json +++ b/tests/skills/cases/form-compile/table.json @@ -16,7 +16,7 @@ "input": { "title": "Просмотр данных", "elements": [ - { "table": "Данные", "path": "Данные", "changeRowSet": true, + { "table": "Данные", "path": "Данные", "changeRowSet": true, "titleLocation": "top", "viewStatusLocation": "None", "searchControlLocation": "None", "excludedCommands": ["Add", "Delete", "MoveUp", "MoveDown"], "columns": [ { "input": "Дата", "path": "Данные.Дата" },