diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 1ffce5ee..1895357a 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.96 — Compile 1C managed form from JSON or object metadata +# form-compile v1.97 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -2616,6 +2616,8 @@ function Emit-Element { "pointerType"=1;"drawingSelectionShowMode"=1;"warningOnEditRepresentation"=1;"markingAppearance"=1 # report-form контекст (generic-скаляры элементов) "horizontalSpacing"=1;"representationInContextMenu"=1;"settingsNamedItemDetailedRepresentation"=1 + # хвост: высота элемента списка / ширина выпадающего списка / картинка кнопки выбора / прозрачный пиксель + "itemHeight"=1;"dropListWidth"=1;"choiceButtonPicture"=1;"transparentPixel"=1 # columnGroup-specific "showInHeader"=1 # radio-specific @@ -2920,6 +2922,9 @@ $script:genericScalars = @( @{ Tag='HorizontalSpacing'; Key='horizontalSpacing'; Kind='value' } @{ Tag='RepresentationInContextMenu'; Key='representationInContextMenu'; Kind='value' } @{ Tag='SettingsNamedItemDetailedRepresentation'; Key='settingsNamedItemDetailedRepresentation'; Kind='bool' } + # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) + @{ Tag='ItemHeight'; Key='itemHeight'; Kind='value' } + @{ Tag='DropListWidth'; Key='dropListWidth'; Kind='value' } ) function Emit-GenericScalars { @@ -3259,6 +3264,7 @@ function Emit-Input { } } if ($el.choiceButtonRepresentation) { X "$inner$($el.choiceButtonRepresentation)" } + Emit-PictureRef -val $el.choiceButtonPicture -picTag 'ChoiceButtonPicture' -indent $inner Emit-Layout -el $el -indent $inner -multiLineDefault ([bool]($el.multiLine -eq $true)) if ($el.inputHint) { @@ -3625,11 +3631,23 @@ function Emit-ChoiceParameters { foreach ($item in @($cp)) { if ($item -is [string]) { $item = ConvertFrom-ChoiceParamShorthand $item } $name = Get-ElProp $item @('name','имя') + # Наличие ключа value (≠ значения): hashtable (shorthand) vs PSCustomObject (JSON-объект) + if ($item -is [System.Collections.IDictionary]) { + $hasVal = $item.Contains('value') -or $item.Contains('значение') + } else { + $hasVal = ($null -ne $item.PSObject.Properties['value']) -or ($null -ne $item.PSObject.Properties['значение']) + } $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" + # Параметр выбора без значения → (платформа, 13 в корпусе); + # со значением (в т.ч. пустой строкой) → FormChoiceListDesTimeValue. + if (-not $hasVal) { + X "$indent`t`t" + } else { + X "$indent`t`t" + Emit-ChoiceParamValue -value $val -indent "$indent`t`t`t" + X "$indent`t`t" + } X "$indent`t" } X "$indent" @@ -3777,6 +3795,8 @@ function Emit-LabelField { if ($el.titleLocation) { X "$inner$(Map-TitleLoc "$($el.titleLocation)")" } if ($el.editMode) { X "$inner$($el.editMode)" } + # PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение + if ($null -ne $el.passwordMode) { X "$inner$(if ($el.passwordMode){'true'}else{'false'})" } Emit-ColumnPics -el $el -indent $inner # ВНИМАНИЕ: у LabelField платформенный тег именно (опечатка 1С), не . if ($el.hyperlink -eq $true) { X "$innertrue" } @@ -4125,6 +4145,7 @@ function Emit-PictureDecoration { if ($srcStr -match '^abs:(.*)$') { X "$inner`t$(Esc-Xml $matches[1])" } else { X "$inner`t$(Esc-Xml $srcStr)" } X "$inner`t$lt" + if ($el.transparentPixel) { X "$inner`t" } X "$inner" } diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index 1553d3a4..73a651ef 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.96 — Compile 1C managed form from JSON or object metadata +# form-compile v1.97 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -1837,6 +1837,8 @@ KNOWN_KEYS = { "pointerType", "drawingSelectionShowMode", "warningOnEditRepresentation", "markingAppearance", # report-form контекст (generic-скаляры элементов) "horizontalSpacing", "representationInContextMenu", "settingsNamedItemDetailedRepresentation", + # хвост: высота элемента списка / ширина выпадающего списка / картинка кнопки выбора / прозрачный пиксель + "itemHeight", "dropListWidth", "choiceButtonPicture", "transparentPixel", } # picture/picField — НИЗКИЙ приоритет: 'picture' это и тип (PictureDecoration), и свойство-иконка @@ -2175,12 +2177,18 @@ def emit_choice_parameters(lines, el, indent): if isinstance(item, str): item = from_choice_param_shorthand(item) name = get_el_prop(item, ('name', 'имя')) + has_val = isinstance(item, dict) and ('value' in item or 'значение' in item) 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') + # Параметр выбора без значения → (платформа, 13 в корпусе); + # со значением (в т.ч. пустой строкой) → FormChoiceListDesTimeValue. + if not has_val: + lines.append(f'{indent}\t\t') + else: + 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}') @@ -2741,6 +2749,9 @@ GENERIC_SCALARS = [ ('HorizontalSpacing', 'horizontalSpacing', 'value'), ('RepresentationInContextMenu', 'representationInContextMenu', 'value'), ('SettingsNamedItemDetailedRepresentation', 'settingsNamedItemDetailedRepresentation', 'bool'), + # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) + ('ItemHeight', 'itemHeight', 'value'), + ('DropListWidth', 'dropListWidth', 'value'), ] @@ -3313,6 +3324,7 @@ def emit_input(lines, el, name, eid, indent): lines.append(f'{inner}<{tag} xsi:type="{mvt}">{esc_xml(str(el[key]))}') if el.get('choiceButtonRepresentation'): lines.append(f'{inner}{el["choiceButtonRepresentation"]}') + emit_picture_ref(lines, el.get('choiceButtonPicture'), 'ChoiceButtonPicture', inner) emit_layout(lines, el, inner, multi_line_default=(el.get('multiLine') is True)) if el.get('inputHint'): @@ -3474,6 +3486,9 @@ def emit_label_field(lines, el, name, eid, indent): lines.append(f'{inner}{map_title_loc(el["titleLocation"])}') if el.get('editMode'): lines.append(f'{inner}{el["editMode"]}') + # PasswordMode на LabelField — платформа эмитит явный false (редко); факт. значение + if el.get('passwordMode') is not None: + lines.append(f'{inner}{"true" if el["passwordMode"] else "false"}') emit_column_pics(lines, el, inner) # ВНИМАНИЕ: у LabelField платформенный тег (опечатка 1С), не . if el.get('hyperlink') is True: @@ -3819,6 +3834,9 @@ def emit_picture_decoration(lines, el, name, eid, indent): else: lines.append(f'{inner}\t{esc_xml(src_str)}') lines.append(f'{inner}\t{lt}') + tpx = el.get('transparentPixel') + if tpx: + lines.append(f'{inner}\t') lines.append(f'{inner}') if el.get('hyperlink') is True: diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 814dd5a7..01385a2c 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.72 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.73 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1192,6 +1192,9 @@ $GENERIC_SCALARS = @( @{ Tag='HorizontalSpacing'; Key='horizontalSpacing'; Kind='value' } @{ Tag='RepresentationInContextMenu'; Key='representationInContextMenu'; Kind='value' } @{ Tag='SettingsNamedItemDetailedRepresentation'; Key='settingsNamedItemDetailedRepresentation'; Kind='bool' } + # Хвост: высота элемента списка (radio) / ширина выпадающего списка (input) + @{ Tag='ItemHeight'; Key='itemHeight'; Kind='value' } + @{ Tag='DropListWidth'; Key='dropListWidth'; Kind='value' } ) # Захват generic-скаляров. Специфичная обработка (если ключ уже задан) — побеждает. @@ -1479,6 +1482,7 @@ function Decompile-Element { } } $cbr = Get-Child $node 'ChoiceButtonRepresentation'; if ($cbr) { $obj['choiceButtonRepresentation'] = $cbr } + $cbp = Get-PictureRef $node 'ChoiceButtonPicture'; if ($null -ne $cbp) { $obj['choiceButtonPicture'] = $cbp } if ((Get-Child $node 'TextEdit') -eq 'false') { $obj['textEdit'] = $false } $cl = Decompile-ChoiceList $node; if ($cl) { $obj['choiceList'] = $cl } Add-FormatProps $obj $node @@ -1524,6 +1528,8 @@ function Decompile-Element { $em = Get-Child $node 'EditMode'; if ($em) { $obj['editMode'] = $em } # LabelField: тег (опечатка платформы), не if ((Get-Child $node 'Hiperlink') -eq 'true') { $obj['hyperlink'] = $true } + # PasswordMode на LabelField — платформа эмитит явный false (редко); захват факт. значения + $pm = Get-Child $node 'PasswordMode'; if ($null -ne $pm) { $obj['passwordMode'] = ($pm -eq 'true') } Add-FormatProps $obj $node } 'PictureDecoration' { @@ -1536,6 +1542,9 @@ function Decompile-Element { $abs = $node.SelectSingleNode("lf:Picture/xr:Abs", $ns) if ($ref) { $obj['src'] = $ref.InnerText } elseif ($abs) { $obj['src'] = "abs:$($abs.InnerText)" } # встроенная картинка → префикс abs: $lt = $node.SelectSingleNode("lf:Picture/xr:LoadTransparent", $ns); if ($lt -and $lt.InnerText -eq 'true') { $obj['loadTransparent'] = $true } + # Прозрачный пиксель картинки () — координаты фона прозрачности + $tpx = $node.SelectSingleNode("lf:Picture/xr:TransparentPixel", $ns) + if ($tpx) { $obj['transparentPixel'] = [ordered]@{ x = [int]$tpx.GetAttribute('x'); y = [int]$tpx.GetAttribute('y') } } if ((Get-Child $node 'Hyperlink') -eq 'true') { $obj['hyperlink'] = $true } } 'PictureField' { @@ -1762,7 +1771,7 @@ $titleNode = $root.SelectSingleNode("lf:Title", $ns) if ($titleNode) { $t = Get-LangText $titleNode; if ($null -ne $t) { $dsl['title'] = $t } } # properties (прямые скаляры под
, PascalCase → camelCase) -$KNOWN_FORM_PROPS = @('AutoTitle','ReportResult','DetailsData','ReportFormType','AutoShowState','ReportResultViewMode','ViewModeApplicationOnSetReportResult','WindowOpeningMode','CommandBarLocation','SaveDataInSettings','AutoSaveDataInSettings','AutoTime','UsePostingMode','RepostOnWrite','AutoURL','AutoFillCheck','Customizable','EnterKeyBehavior','VerticalScroll','Width','Height','Group','UseForFoldersAndItems','SaveWindowSettings','ScalingMode','VerticalSpacing') +$KNOWN_FORM_PROPS = @('AutoTitle','ReportResult','DetailsData','ReportFormType','AutoShowState','ReportResultViewMode','ViewModeApplicationOnSetReportResult','WindowOpeningMode','CommandBarLocation','SaveDataInSettings','AutoSaveDataInSettings','AutoTime','UsePostingMode','RepostOnWrite','AutoURL','AutoFillCheck','Customizable','EnterKeyBehavior','VerticalScroll','Width','Height','Group','UseForFoldersAndItems','SaveWindowSettings','ScalingMode','VerticalSpacing','VariantAppearance') $props = [ordered]@{} foreach ($pn in $KNOWN_FORM_PROPS) { $v = Get-Child $root $pn diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index f62af6d4..d3be5c8a 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -73,6 +73,7 @@ | `autoShowState` | `` | `Auto`, `DontShow`, `ShowOnComposition` | | `reportResultViewMode` | `` | `Auto` | | `viewModeApplicationOnSetReportResult` | `` | `Auto` | +| `variantAppearance` | `` | Имя реквизита оформления варианта (форма отчёта) | Нераспознанные ключи преобразуются с автоматическим PascalCase (первая буква в верхний регистр). diff --git a/tests/skills/cases/form-compile/input-fields.json b/tests/skills/cases/form-compile/input-fields.json index bd82331d..24ff65cc 100644 --- a/tests/skills/cases/form-compile/input-fields.json +++ b/tests/skills/cases/form-compile/input-fields.json @@ -33,7 +33,8 @@ { "name": "Отбор.Активный", "value": true }, { "name": "Отбор.Вид", "value": "Основной" }, { "name": "Отбор.Дата", "value": "2020-01-01T00:00:00" }, - { "name": "Отбор.Список", "value": ["Один", "Два"] } + { "name": "Отбор.Список", "value": ["Один", "Два"] }, + { "name": "Отбор.БезЗначения" } ], "choiceParameterLinks": [ { "name": "Отбор.Организация", "dataPath": "ОбычноеПоле" }, 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 37d262b8..1ecb72d8 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 @@ -261,6 +261,9 @@ + + +