From f534add7b46b087fd2de267d5cdcc8097ae2d64d Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Wed, 10 Jun 2026 13:57:54 +0300 Subject: [PATCH] =?UTF-8?q?feat(form-decompile,form-compile):=20=D0=B8?= =?UTF-8?q?=D0=B7=D0=BC=D0=B5=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=BB?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=80=D0=BE=D0=B2=D1=89=D0=B8=D0=BA=D0=B0=20?= =?UTF-8?q?(dimensions)=20=E2=80=94=20Planner=20Phase=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Расширение Phase 1 кластера Chart-Settings: реквизит pl:Planner теперь несёт измерения планировщика ( — «Измерения» в конфигураторе) с элементами. DSL planner.dimensions[]: объект разреза (value — ссылка xr:DesignTimeRef или nil, text-заголовок, цвета, font) + elements[] (элементы измерения, РЕКУРСИВНЫ — могут нести вложенные elements, как показывает UI колонкой «Элементы»; поле showOnlySubordinatesAreas). Тип value авто-выводится: ссылочный вид → xsi:type="xr:DesignTimeRef", иначе xs:string. Пустой текст → самозакрывающийся (как в выгрузке). Общие хелперы Emit/Get-PlannerValue/Text применены и к элементам расписания (items). Раундтрип бит-в-бит: синтетика upload/epf/Диаграммы (items + 2 dimensions + вложенные elements + period). Зеркало py (ps1==py байт-в-байт). Кейс chart-fields расширен измерением (nil-разрез + xs:string-элемент + showOnlySubordinatesAreas), сертифицирован загрузкой в 1С. Регресс 41/41 (ps1+py). Ограничение: item.dimensionValues (привязка элемента расписания к элементам измерений) пока всегда пустой. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../form-compile/scripts/form-compile.ps1 | 66 +++++++++++++++-- .../form-compile/scripts/form-compile.py | 74 ++++++++++++++++--- .../form-decompile/scripts/form-decompile.ps1 | 46 +++++++++++- docs/form-dsl-spec.md | 3 +- .../cases/form-compile/chart-fields.json | 5 ++ .../Диаграммы/Forms/Форма/Ext/Form.xml | 19 +++++ 6 files changed, 192 insertions(+), 21 deletions(-) diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 index 16259fec..102871d7 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.104 — Compile 1C managed form from JSON or object metadata +# form-compile v1.105 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$JsonPath, @@ -3048,6 +3048,27 @@ function Emit-PlannerColor { param([string]$tag, $o, [string]$key, [string]$ind) X "$ind$(Esc-Xml "$(PL-Get $o $key 'auto')")" } +# /… — пустое → самозакрывающийся тег (как в выгрузке платформы). +function Emit-PlannerText { + param([string]$tag, $v, [string]$ind) + if ([string]::IsNullOrEmpty("$v")) { X "$ind" } + else { X "$ind$(Esc-Xml "$v")" } +} +# Признак ссылочного значения (объект разреза/элемент-ссылка) → xsi:type="xr:DesignTimeRef"; +# иначе xs:string. Покрывает англ. (Enum.X.EnumValue.Y) и рус. (Справочник.X) метатипы. +function Test-PlannerRef { + param([string]$v) + return ($v -match '^(Enum|Catalog|Document|ChartOfAccounts|ChartOfCalculationTypes|ChartOfCharacteristicTypes|ExchangePlan|BusinessProcess|Task)\.' -or ` + $v -match '\.EnumValue\.' -or $v -match 'EmptyRef$' -or ` + $v -match '^(Перечисление|Справочник|Документ|ПланСчетов|ПланВидовХарактеристик|ПланВидовРасчета|ПланОбмена|БизнесПроцесс|Задача)\.') +} +# — nil (нет значения) / xr:DesignTimeRef (ссылка) / xs:string (строка/прочее). +function Emit-PlannerValue { + param($v, [string]$ind) + if ($null -eq $v -or "$v" -eq '') { X "$ind"; return } + $t = if (Test-PlannerRef "$v") { 'xr:DesignTimeRef' } else { 'xs:string' } + X "$ind$(Esc-Xml "$v")" +} function Emit-PlannerFont { param($o, [string]$ind) $f = PL-Get $o 'font' $null @@ -3114,12 +3135,9 @@ function Emit-PlannerItem { param($it, [string]$ind) X "$ind" $ii = "$ind`t" - $val = PL-Get $it 'value' $null - if ($null -eq $val) { X "$ii" } - else { X "$ii$(Esc-Xml "$val")" } - X "$ii$(Esc-Xml "$(PL-Get $it 'text' '')")" - $tt = PL-Get $it 'tooltip' '' - if ("$tt" -eq '') { X "$ii" } else { X "$ii$(Esc-Xml "$tt")" } + Emit-PlannerValue (PL-Get $it 'value' $null) $ii + Emit-PlannerText 'text' (PL-Get $it 'text' '') $ii + Emit-PlannerText 'tooltip' (PL-Get $it 'tooltip' '') $ii X "$ii$(PL-Get $it 'begin' '0001-01-01T00:00:00')" X "$ii$(PL-Get $it 'end' '0001-01-01T00:00:00')" Emit-PlannerColor 'borderColor' $it 'borderColor' $ii @@ -3137,11 +3155,45 @@ function Emit-PlannerItem { X "$ii$(Esc-Xml "$(PL-Get $it 'editMode' 'EnableEdit')")" X "$ind" } +# Элемент измерения ( внутри ) — рекурсивен: может нести вложенные +# элементы (UI: колонка «Элементы» у элемента). Порядок: value, text, цвета, font, +# вложенные элементы, showOnlySubordinatesAreas, textFormatted. +function Emit-PlannerDimElement { + param($el, [string]$ind) + X "$ind" + $ii = "$ind`t" + Emit-PlannerValue (PL-Get $el 'value' $null) $ii + Emit-PlannerText 'text' (PL-Get $el 'text' '') $ii + Emit-PlannerColor 'borderColor' $el 'borderColor' $ii + Emit-PlannerColor 'backColor' $el 'backColor' $ii + Emit-PlannerColor 'textColor' $el 'textColor' $ii + Emit-PlannerFont $el $ii + foreach ($sub in @(PL-Get $el 'elements' @())) { Emit-PlannerDimElement $sub $ii } + X "$ii$(PL-Bool (PL-Get $el 'showOnlySubordinatesAreas' $true))" + X "$ii$(PL-Bool (PL-Get $el 'textFormatted' $false))" + X "$ind" +} +# Измерение планировщика () — объект разреза + его элементы. +function Emit-PlannerDimension { + param($d, [string]$ind) + X "$ind" + $di = "$ind`t" + Emit-PlannerValue (PL-Get $d 'value' $null) $di + Emit-PlannerText 'text' (PL-Get $d 'text' '') $di + Emit-PlannerColor 'borderColor' $d 'borderColor' $di + Emit-PlannerColor 'backColor' $d 'backColor' $di + Emit-PlannerColor 'textColor' $d 'textColor' $di + Emit-PlannerFont $d $di + foreach ($el in @(PL-Get $d 'elements' @())) { Emit-PlannerDimElement $el $di } + X "$di$(PL-Bool (PL-Get $d 'textFormatted' $false))" + X "$ind" +} function Emit-PlannerSettings { param($pl, [string]$ind) X "$ind" $si = "$ind`t" foreach ($it in @(PL-Get $pl 'items' @())) { Emit-PlannerItem $it $si } + foreach ($d in @(PL-Get $pl 'dimensions' @())) { Emit-PlannerDimension $d $si } Emit-PlannerColor 'borderColor' $pl 'borderColor' $si Emit-PlannerColor 'backColor' $pl 'backColor' $si Emit-PlannerColor 'textColor' $pl 'textColor' $si diff --git a/.claude/skills/form-compile/scripts/form-compile.py b/.claude/skills/form-compile/scripts/form-compile.py index d2089903..f830721a 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.104 — Compile 1C managed form from JSON or object metadata +# form-compile v1.105 — Compile 1C managed form from JSON or object metadata # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import copy @@ -2740,6 +2740,31 @@ def emit_planner_color(lines, tag, o, key, ind): lines.append(f'{ind}{esc_xml(str(_pl_get(o, key, "auto")))}') +def emit_planner_text(lines, tag, v, ind): + if v is None or str(v) == '': + lines.append(f'{ind}') + else: + lines.append(f'{ind}{esc_xml(str(v))}') + + +_PLANNER_REF_RE = re.compile( + r'^(Enum|Catalog|Document|ChartOfAccounts|ChartOfCalculationTypes|ChartOfCharacteristicTypes|ExchangePlan|BusinessProcess|Task)\.' + r'|\.EnumValue\.|EmptyRef$' + r'|^(Перечисление|Справочник|Документ|ПланСчетов|ПланВидовХарактеристик|ПланВидовРасчета|ПланОбмена|БизнесПроцесс|Задача)\.') + + +def test_planner_ref(v): + return bool(_PLANNER_REF_RE.search(str(v))) + + +def emit_planner_value(lines, v, ind): + if v is None or str(v) == '': + lines.append(f'{ind}') + return + t = 'xr:DesignTimeRef' if test_planner_ref(v) else 'xs:string' + lines.append(f'{ind}{esc_xml(str(v))}') + + def emit_planner_font(lines, o, ind): f = _pl_get(o, 'font') if f is None: @@ -2814,17 +2839,9 @@ def emit_planner_timescale(lines, ts, ind): def emit_planner_item(lines, it, ind): lines.append(f'{ind}') ii = f'{ind}\t' - val = _pl_get(it, 'value') - if val is None: - lines.append(f'{ii}') - else: - lines.append(f'{ii}{esc_xml(str(val))}') - lines.append(f'{ii}{esc_xml(str(_pl_get(it, "text", "")))}') - tt = _pl_get(it, 'tooltip', '') - if str(tt) == '': - lines.append(f'{ii}') - else: - lines.append(f'{ii}{esc_xml(str(tt))}') + emit_planner_value(lines, _pl_get(it, 'value'), ii) + emit_planner_text(lines, 'text', _pl_get(it, 'text', ''), ii) + emit_planner_text(lines, 'tooltip', _pl_get(it, 'tooltip', ''), ii) lines.append(f'{ii}{_pl_get(it, "begin", "0001-01-01T00:00:00")}') lines.append(f'{ii}{_pl_get(it, "end", "0001-01-01T00:00:00")}') emit_planner_color(lines, 'borderColor', it, 'borderColor', ii) @@ -2845,11 +2862,44 @@ def emit_planner_item(lines, it, ind): lines.append(f'{ind}') +def emit_planner_dim_element(lines, el, ind): + lines.append(f'{ind}') + ii = f'{ind}\t' + emit_planner_value(lines, _pl_get(el, 'value'), ii) + emit_planner_text(lines, 'text', _pl_get(el, 'text', ''), ii) + emit_planner_color(lines, 'borderColor', el, 'borderColor', ii) + emit_planner_color(lines, 'backColor', el, 'backColor', ii) + emit_planner_color(lines, 'textColor', el, 'textColor', ii) + emit_planner_font(lines, el, ii) + for sub in _pl_get(el, 'elements', []): + emit_planner_dim_element(lines, sub, ii) + lines.append(f'{ii}{_pl_bool(_pl_get(el, "showOnlySubordinatesAreas", True))}') + lines.append(f'{ii}{_pl_bool(_pl_get(el, "textFormatted", False))}') + lines.append(f'{ind}') + + +def emit_planner_dimension(lines, d, ind): + lines.append(f'{ind}') + di = f'{ind}\t' + emit_planner_value(lines, _pl_get(d, 'value'), di) + emit_planner_text(lines, 'text', _pl_get(d, 'text', ''), di) + emit_planner_color(lines, 'borderColor', d, 'borderColor', di) + emit_planner_color(lines, 'backColor', d, 'backColor', di) + emit_planner_color(lines, 'textColor', d, 'textColor', di) + emit_planner_font(lines, d, di) + for el in _pl_get(d, 'elements', []): + emit_planner_dim_element(lines, el, di) + lines.append(f'{di}{_pl_bool(_pl_get(d, "textFormatted", False))}') + lines.append(f'{ind}') + + def emit_planner_settings(lines, pl, ind): lines.append(f'{ind}') si = f'{ind}\t' for it in _pl_get(pl, 'items', []): emit_planner_item(lines, it, si) + for d in _pl_get(pl, 'dimensions', []): + emit_planner_dimension(lines, d, si) emit_planner_color(lines, 'borderColor', pl, 'borderColor', si) emit_planner_color(lines, 'backColor', pl, 'backColor', si) emit_planner_color(lines, 'textColor', pl, 'textColor', si) diff --git a/.claude/skills/form-decompile/scripts/form-decompile.ps1 b/.claude/skills/form-decompile/scripts/form-decompile.ps1 index 545309d8..9e98ee18 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.80 — Decompile 1C managed Form.xml to JSON DSL (draft) +# form-decompile v0.81 — Decompile 1C managed Form.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью. param( @@ -1838,6 +1838,13 @@ function Decompile-Element { # Полный захват каждого поля (раундтрип бит-в-бит); зеркало Emit-PlannerSettings. function PLD-Bool { param($v) if ($null -eq $v) { return $null } return ($v -eq 'true') } function PLD-Int { param($v) if ($null -eq $v) { return $null } return [int]$v } +# → текст (тип xr:DesignTimeRef/xs:string выводится компилятором из вида значения); nil → $null. +function Get-PlannerValue { + param($node) + if (-not $node) { return $null } + if ($node.GetAttribute('nil', $NS_XSI) -eq 'true') { return $null } + if ($node.InnerText) { return $node.InnerText } else { return $null } +} function Build-PlannerFont { param($node) if (-not $node) { return $null } @@ -1877,6 +1884,37 @@ function Build-PlannerItem { $o['editMode'] = (Get-Child $itn 'editMode') return $o } +function Build-PlannerDimElement { + param($eln) + $o = [ordered]@{} + $v = Get-PlannerValue ($eln.SelectSingleNode("*[local-name()='value']")); if ($null -ne $v) { $o['value'] = $v } + $o['text'] = (Get-Child $eln 'text') + $o['borderColor'] = (Get-Child $eln 'borderColor') + $o['backColor'] = (Get-Child $eln 'backColor') + $o['textColor'] = (Get-Child $eln 'textColor') + $fnt = Build-PlannerFont ($eln.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $o['font'] = $fnt } + $subs = New-Object System.Collections.ArrayList + foreach ($s in @($eln.SelectNodes("*[local-name()='item']"))) { [void]$subs.Add((Build-PlannerDimElement $s)) } + if ($subs.Count -gt 0) { $o['elements'] = @($subs) } + $sos = Get-Child $eln 'showOnlySubordinatesAreas'; if ($null -ne $sos) { $o['showOnlySubordinatesAreas'] = ($sos -eq 'true') } + $o['textFormatted'] = (PLD-Bool (Get-Child $eln 'textFormatted')) + return $o +} +function Build-PlannerDimension { + param($dn) + $o = [ordered]@{} + $v = Get-PlannerValue ($dn.SelectSingleNode("*[local-name()='value']")); if ($null -ne $v) { $o['value'] = $v } + $o['text'] = (Get-Child $dn 'text') + $o['borderColor'] = (Get-Child $dn 'borderColor') + $o['backColor'] = (Get-Child $dn 'backColor') + $o['textColor'] = (Get-Child $dn 'textColor') + $fnt = Build-PlannerFont ($dn.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $o['font'] = $fnt } + $els = New-Object System.Collections.ArrayList + foreach ($e in @($dn.SelectNodes("*[local-name()='item']"))) { [void]$els.Add((Build-PlannerDimElement $e)) } + if ($els.Count -gt 0) { $o['elements'] = @($els) } + $o['textFormatted'] = (PLD-Bool (Get-Child $dn 'textFormatted')) + return $o +} function Build-PlannerLevel { param($lvn) $o = [ordered]@{} @@ -1924,6 +1962,12 @@ function Build-PlannerSettings { foreach ($itn in $itemNodes) { [void]$items.Add((Build-PlannerItem $itn)) } $pl['items'] = @($items) } + $dimNodes = @($setNode.SelectNodes("*[local-name()='dimension']")) + if ($dimNodes.Count -gt 0) { + $dims = New-Object System.Collections.ArrayList + foreach ($dn in $dimNodes) { [void]$dims.Add((Build-PlannerDimension $dn)) } + $pl['dimensions'] = @($dims) + } $pl['borderColor'] = (Get-Child $setNode 'borderColor') $pl['backColor'] = (Get-Child $setNode 'backColor') $pl['textColor'] = (Get-Child $setNode 'textColor') diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md index 063e6b93..9b03086d 100644 --- a/docs/form-dsl-spec.md +++ b/docs/form-dsl-spec.md @@ -852,6 +852,7 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и | Ключ planner | Тип | Назначение | |---|---|---| | `items` | array | Элементы планировщика (``): `value`(nil по умолч.)/`text`/`tooltip`/`begin`/`end`/`borderColor`/`backColor`/`textColor`/`font`/`replacementDate`/`deleted`/`id`(авто-GUID)/`textFormatted`/`border`/`editMode` | +| `dimensions` | array | Измерения планировщика (``, «Измерения» в конфигураторе): `value` (объект разреза — ссылка `Enum.X.EnumValue.Y`/`Справочник.X`, опускается → nil)/`text` (заголовок)/`borderColor`/`backColor`/`textColor`/`font`/`elements`/`textFormatted`. `elements` — элементы измерения (рекурсивны, могут нести вложенные `elements`): `value`/`text`/цвета/`font`/`showOnlySubordinatesAreas`(bool)/`textFormatted`. Тип `value` авто: ссылочный вид → `xsi:type="xr:DesignTimeRef"`, иначе `xs:string` | | `borderColor`/`backColor`/`textColor`/`lineColor` | color | Цвета планировщика (умолч. `auto`) | | `font` | font | Шрифт (умолч. `{kind:"AutoFont"}`) | | `beginOfRepresentationPeriod`/`endOfRepresentationPeriod` | dateTime | Период представления | @@ -866,7 +867,7 @@ Forgiving-синонимы типа: XML-имя (`SpreadSheetDocumentField`) и | `minColumnWidth`/`minRowHeight` | int | Минимальные размеры | | `border` | border | Рамка планировщика (`{ width, style }`) | -> **Ограничение Phase 1:** `dimensions` (измерения планировщика) и `item.dimensionValues` пока всегда пустые (захват только пустого блока). Конфиг Chart/GanttChart (`d4p1:*`) — отдельная фаза. +> **Ограничение Phase 1:** `item.dimensionValues` (привязка элемента расписания к элементам измерений) пока всегда пустой (захват только пустого блока). Сами измерения (`dimensions`) поддержаны. Конфиг Chart/GanttChart (`d4p1:*`) — отдельная фаза. ### settings — динамический список diff --git a/tests/skills/cases/form-compile/chart-fields.json b/tests/skills/cases/form-compile/chart-fields.json index 63a1bbcb..a2d2e520 100644 --- a/tests/skills/cases/form-compile/chart-fields.json +++ b/tests/skills/cases/form-compile/chart-fields.json @@ -35,6 +35,11 @@ "items": [ { "text": "Встреча", "begin": "2026-06-09T01:00:00", "end": "2026-06-09T04:00:00" } ], + "dimensions": [ + { "text": "Измерение", "elements": [ + { "value": "А", "text": "Элемент А", "showOnlySubordinatesAreas": true } + ] } + ], "period": { "begin": "2026-06-09T00:00:00", "end": "2026-06-09T23:59:59" } } } ] diff --git a/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml b/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml index 7769c5d6..9627dc6c 100644 --- a/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml +++ b/tests/skills/cases/form-compile/snapshots/chart-fields/DataProcessors/Диаграммы/Forms/Форма/Ext/Form.xml @@ -171,6 +171,25 @@ EnableEdit + + + Измерение + auto + auto + auto + + + А + Элемент А + auto + auto + auto + + true + false + + false + auto auto auto