From 0941fc717d97aa78fbd6af037195042d37b10151 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Thu, 4 Jun 2026 18:53:28 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D1=81?= =?UTF-8?q?=D0=B5=D0=BC=D0=B0=D0=BD=D1=82=D0=B8=D0=BA=D0=B0=20TitleLocatio?= =?UTF-8?q?n=20(=D0=BA=D0=BB=D0=B0=D1=81=D1=82=D0=B5=D1=80=20G2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Принцип: компилятор не эмитит значение, равное дефолту платформы (который платформа сама не пишет в XML). Умный дефолт (check→Right, radio→None) — отдельная вещь, эмитится (он ≠ дефолт платформы Left). - net ключа titleLocation → умный дефолт; titleLocation: "" → подавить (дефолт платформы); значение → эмитить с маппингом регистра. - compiler PS1+PY: Emit-TitleLocation/emit_title_location + Map-TitleLoc (общий маппинг; у check раньше его не было — сырьё). - decompiler: Add-TitleLocation (дефолт → опустить, нет тега → "", иначе значение). - docs/form-dsl-spec: семантика titleLocation у check/radio. - tests: input-fields расширен (Right-дефолт / ""-подавление / явный Top), сертифицирован. АварийныйРежим: полный MATCH. Регресс 32/32 PS1+PY, churn нулевой. Co-Authored-By: Claude Opus 4.8 --- .../form-compile/scripts/form-compile.ps1 | 42 ++++++++----- .../form-compile/scripts/form-compile.py | 30 ++++++---- .../form-decompile/scripts/form-decompile.ps1 | 14 ++++- docs/form-dsl-spec.md | 4 +- .../cases/form-compile/input-fields.json | 8 ++- .../ПоляВвода/Forms/Форма/Ext/Form.xml | 59 ++++++++++++++++--- 6 files changed, 119 insertions(+), 38 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index b00e6fe3..3c5f04d3 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.26 — Compile 1C managed form from JSON or object metadata +# form-compile v1.27 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2034,6 +2034,30 @@ function Emit-Title { } } +function Map-TitleLoc { + param([string]$v) + switch ("$v".ToLower()) { + "none" { "None" } + "left" { "Left" } + "right" { "Right" } + "top" { "Top" } + "bottom" { "Bottom" } + "auto" { "Auto" } + default { "$v" } + } +} + +# TitleLocation у check/radio: нет ключа → умный дефолт (Right/None), эмитится; +# "" → подавить (= дефолт платформы, она его сама не пишет); значение → эмитить (маппинг регистра). +function Emit-TitleLocation { + param($el, [string]$indent, [string]$smartDefault) + if ($null -ne $el.PSObject.Properties['titleLocation']) { + if ($el.titleLocation) { X "$indent$(Map-TitleLoc "$($el.titleLocation)")" } + } elseif ($smartDefault) { + X "$indent$smartDefault" + } +} + function Emit-Group { param($el, [string]$name, [int]$id, [string]$indent) @@ -2196,8 +2220,7 @@ function Emit-Check { Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path) Emit-CommonFlags -el $el -indent $inner - $tl = if ($el.titleLocation) { "$($el.titleLocation)" } else { "Right" } - X "$inner$tl" + Emit-TitleLocation -el $el -indent $inner -smartDefault "Right" Emit-Layout -el $el -indent $inner @@ -2337,18 +2360,7 @@ function Emit-Radio { Emit-Title -el $el -name $name -indent $inner -auto:(-not $el.path) Emit-CommonFlags -el $el -indent $inner - # TitleLocation default is None for radio (matches typical configurator behavior) - $tl = if ($el.titleLocation) { - switch ("$($el.titleLocation)") { - "none" { "None" } - "left" { "Left" } - "right" { "Right" } - "top" { "Top" } - "bottom" { "Bottom" } - default { "$($el.titleLocation)" } - } - } else { "None" } - X "$inner$tl" + Emit-TitleLocation -el $el -indent $inner -smartDefault "None" # RadioButtonType: Auto | RadioButtons | Tumbler. Accept synonyms. $rbtRaw = if ($el.radioButtonType) { "$($el.radioButtonType)".Trim() } else { "Auto" } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index c69233be..64d9df88 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.26 — Compile 1C managed form from JSON or object metadata +# form-compile v1.27 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1620,6 +1620,23 @@ def emit_title(lines, el, name, indent, auto=False): emit_mltext(lines, indent, 'Title', title_from_name(name)) +_TITLE_LOC_MAP = {'none': 'None', 'left': 'Left', 'right': 'Right', 'top': 'Top', 'bottom': 'Bottom', 'auto': 'Auto'} + + +def map_title_loc(v): + return _TITLE_LOC_MAP.get(str(v).lower(), str(v)) + + +def emit_title_location(lines, el, indent, smart_default): + # Нет ключа → умный дефолт (Right/None), эмитится. "" → подавить (дефолт платформы). + # Значение → эмитить с маппингом регистра. + if 'titleLocation' in el: + if el.get('titleLocation'): + lines.append(f"{indent}{map_title_loc(el['titleLocation'])}") + elif smart_default: + lines.append(f"{indent}{smart_default}") + + # --- Type emitter --- V8_TYPES = { @@ -2007,8 +2024,7 @@ 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) - tl = el.get('titleLocation') or 'Right' - lines.append(f'{inner}{tl}') + emit_title_location(lines, el, inner, 'Right') emit_layout(lines, el, inner) @@ -2031,13 +2047,7 @@ def emit_radio_button_field(lines, el, name, eid, indent): emit_title(lines, el, name, inner, auto=not el.get('path')) emit_common_flags(lines, el, inner) - tl_raw = el.get('titleLocation') - if tl_raw: - loc_map = {'none': 'None', 'left': 'Left', 'right': 'Right', 'top': 'Top', 'bottom': 'Bottom'} - tl = loc_map.get(str(tl_raw), str(tl_raw)) - else: - tl = 'None' - lines.append(f'{inner}{tl}') + emit_title_location(lines, el, inner, 'None') rbt = normalize_radio_button_type(el.get('radioButtonType')) lines.append(f'{inner}{rbt}') diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index dbd36e77..32690714 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.5 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.6 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -203,6 +203,15 @@ function Add-Layout { $ha = Get-Child $node 'HorizontalAlign'; if ($ha) { $obj['horizontalAlign'] = $ha } } +# TitleLocation у check/radio (зеркало Emit-TitleLocation): +# тега нет → "" (дефолт платформы); значение = умный дефолт → опускаем; иначе пишем. +function Add-TitleLocation { + param($obj, $node, [string]$smartDefault) + $tl = Get-Child $node 'TitleLocation' + if ($null -eq $tl) { $obj['titleLocation'] = '' } + elseif ($tl -ne $smartDefault) { $obj['titleLocation'] = $tl.ToLower() } +} + # Суффиксы авто-имён обработчиков (инверсия компилятора) $HANDLER_SUFFIX = @{ 'OnChange'='ПриИзменении'; 'StartChoice'='НачалоВыбора'; 'ChoiceProcessing'='ОбработкаВыбора'; @@ -375,12 +384,13 @@ 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() } + Add-TitleLocation $obj $node 'Right' } 'RadioButtonField' { $obj[$key] = $name $dp = Get-Child $node 'DataPath'; if ($dp) { $obj['path'] = $dp } Add-CommonProps $obj $node $name + Add-TitleLocation $obj $node 'None' $rbt = Get-Child $node 'RadioButtonType'; if ($rbt) { $obj['radioButtonType'] = $rbt } $cc = Get-Child $node 'ColumnsCount'; if ($cc) { $obj['columnsCount'] = [int]$cc } $cl = $node.SelectSingleNode("lf:ChoiceList", $ns) diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 36d7a06b..d8153b7c 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -216,7 +216,7 @@ | Свойство | Тип | Описание | |----------|-----|----------| | `path` | string | DataPath | -| `titleLocation` | string | Расположение заголовка | +| `titleLocation` | string | Расположение заголовка. **Нет ключа** → умный дефолт `Right` (флажки почти всегда справа). **`""`** → дефолт платформы (`Left`, тег не пишется). Значение (`none`/`left`/`top`/…) → как указано | #### radio — RadioButtonField @@ -237,7 +237,7 @@ | `path` | string | DataPath | | `radioButtonType` | string | `Auto` (по умолчанию), `RadioButtons`, `Tumbler` | | `columnsCount` | int | Число колонок раскладки | -| `titleLocation` | string | Расположение заголовка (компилятор подставляет `None`, если не задан) | +| `titleLocation` | string | Расположение заголовка. **Нет ключа** → умный дефолт `None`. **`""`** → дефолт платформы (тег не пишется). Значение → как указано | | `choiceList` | array | Варианты выбора: массив `{ value, presentation }` | `choiceList[*]`: diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index 27c45e99..a29c9cc3 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -21,7 +21,9 @@ { "input": "ПолеПароля", "path": "ПолеПароля", "passwordMode": true, "title": "Пароль" }, { "input": "ПолеСКнопками", "path": "ПолеСКнопками", "choiceButton": true, "clearButton": true, "title": "Выбор" }, { "input": "ПолеПодсказка", "path": "ПолеПодсказка", "inputHint": "Введите значение...", "title": "Подсказка" }, - { "check": "Флаг", "path": "Флаг", "title": "Включено" } + { "check": "Флаг", "path": "Флаг", "title": "Включено" }, + { "check": "ФлагПлатформенный", "path": "ФлагПлатформенный", "title": "Слева", "titleLocation": "" }, + { "check": "ФлагЯвный", "path": "ФлагЯвный", "title": "Сверху", "titleLocation": "top" } ], "attributes": [ { "name": "Объект", "type": "DataProcessorObject.ПоляВвода", "main": true }, @@ -30,7 +32,9 @@ { "name": "ПолеПароля", "type": "string(50)" }, { "name": "ПолеСКнопками", "type": "string" }, { "name": "ПолеПодсказка", "type": "string" }, - { "name": "Флаг", "type": "boolean" } + { "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 d07eaea2..0a81de55 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 @@ -87,15 +87,38 @@ + + ФлагПлатформенный + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Слева</v8:content> + </v8:item> + + + + + + ФлагЯвный + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Сверху</v8:content> + </v8:item> + + Top + + + - + cfg:DataProcessorObject.ПоляВвода true - + <v8:item> <v8:lang>ru</v8:lang> @@ -110,7 +133,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="МногострочноеПоле" id="21"> + <Attribute name="МногострочноеПоле" id="27"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -125,7 +148,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеПароля" id="22"> + <Attribute name="ПолеПароля" id="28"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -140,7 +163,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеСКнопками" id="23"> + <Attribute name="ПолеСКнопками" id="29"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -155,7 +178,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="ПолеПодсказка" id="24"> + <Attribute name="ПолеПодсказка" id="30"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -170,7 +193,7 @@ </v8:StringQualifiers> </Type> </Attribute> - <Attribute name="Флаг" id="25"> + <Attribute name="Флаг" id="31"> <Title> <v8:item> <v8:lang>ru</v8:lang> @@ -181,5 +204,27 @@ <v8:Type>xs:boolean</v8:Type> </Type> </Attribute> + <Attribute name="ФлагПлатформенный" id="32"> + <Title> + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Флаг платформенный</v8:content> + </v8:item> + + + xs:boolean + + + + + <v8:item> + <v8:lang>ru</v8:lang> + <v8:content>Флаг явный</v8:content> + </v8:item> + + + xs:boolean + +