feat(form-decompile,form-compile): Planner design-time Settings из ring-3 (Фаза 1 кластера Chart-Settings)

Реквизит planner-типа несёт <Settings xsi:type="pl:Planner"> — встроенный конфиг
поля-планировщика (элементы расписания + оформление/поведение + шкала времени).
Раньше декомпилятор делал fail-ring3 (третий вид Settings после TypeDescription/
DynamicList). Корпус 8.3.24: Planner Settings = 1 реальная форма (КонтактныеЛица/
ФормаЛиды), всё chart-семейство = 38 форм. Решение (с пользователем): структурный
DSL ради возможности модели СОЗДАВАТЬ дашборды/планировщики, не только раундтрипить.

DSL: ключ planner:{…} на реквизите (docs/form-dsl-spec.md):
- items[] (элементы расписания) + appearance/поведение-скаляры + timeScale
  (placement/levels[]/colors) + period;
- цвета verbatim, шрифт {kind:AutoFont}/ref, граница {width,style}, ML-форматы;
- компилятор подставляет дефолты для пропущенных ключей (краткий авторинг),
  декомпилятор — полный захват (раундтрип бит-в-бит).

Снят fail-ring3 для pl:Planner (Chart/GanttChart остаются — Фазы 2/3).
Заодно фикс: d5p1:Dendrogram отсутствовал в specialTypeNs (эмитился без
xmlns-префикса) — добавлен в карту (ps1+py).

Раундтрип бит-в-бит: синтетика upload/epf/Диаграммы (с items+period) +
реальная ФормаЛиды (без items/period, иные значения скаляров). Зеркало py
(ps1==py байт-в-байт). Кейс chart-fields расширен (+planner +dendrogram),
сертифицирован загрузкой в 1С. Регресс 41/41 (ps1+py).

Ограничение Phase 1: dimensions/item.dimensionValues пока всегда пустые.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-10 13:35:10 +03:00
co-authored by Claude Opus 4.8
parent 29f288fe1d
commit f064d53eb2
6 changed files with 664 additions and 19 deletions
@@ -1,4 +1,4 @@
# form-decompile v0.79 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.80 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -152,7 +152,7 @@ foreach ($el in $xmlDoc.SelectNodes("//*[local-name()='ConditionalAppearance']/*
# (встроенная конфигурация диаграммы) пока НЕ воспроизводим → честный скип, чтобы не потерять молча.
foreach ($s in $xmlDoc.SelectNodes("//*[local-name()='Attribute']/*[local-name()='Settings']")) {
$st = $s.GetAttribute("type", $NS_XSI)
if ($st -and $st -notmatch 'TypeDescription$' -and $st -notmatch 'DynamicList$') {
if ($st -and $st -notmatch 'TypeDescription$' -and $st -notmatch 'DynamicList$' -and $st -notmatch 'Planner$') {
Fail-Ring3 -kind "Attribute>Settings типа '$st' (design-time конфигурация, напр. диаграмма)" -loc "Attribute/Settings"
}
}
@@ -1833,6 +1833,131 @@ function Decompile-Element {
return $obj
}
# ─────────────────────────────────────────────────────────────────────────────
# Planner design-time <Settings xsi:type="pl:Planner"> → объект planner на реквизите.
# Полный захват каждого поля (раундтрип бит-в-бит); зеркало 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 }
function Build-PlannerFont {
param($node)
if (-not $node) { return $null }
$o = [ordered]@{}
foreach ($a in @('ref','faceName','height','bold','italic','underline','strikeout','kind','scale')) {
$av = $node.GetAttribute($a); if ($av -ne '') { $o[$a] = $av }
}
if ($o.Count -eq 0) { return $null }
return $o
}
function Build-PlannerBorder {
param($node)
if (-not $node) { return $null }
$o = [ordered]@{}
$w = $node.GetAttribute('width'); if ($w -ne '') { $o['width'] = [int]$w }
$st = $node.SelectSingleNode("*[local-name()='style']"); if ($st) { $o['style'] = $st.InnerText }
return $o
}
function Build-PlannerItem {
param($itn)
$o = [ordered]@{}
$valNode = $itn.SelectSingleNode("*[local-name()='value']")
if ($valNode -and $valNode.GetAttribute('nil', $NS_XSI) -ne 'true' -and $valNode.InnerText) { $o['value'] = $valNode.InnerText }
$o['text'] = (Get-Child $itn 'text')
$tt = Get-Child $itn 'tooltip'; if ($tt) { $o['tooltip'] = $tt }
$o['begin'] = (Get-Child $itn 'begin')
$o['end'] = (Get-Child $itn 'end')
$o['borderColor'] = (Get-Child $itn 'borderColor')
$o['backColor'] = (Get-Child $itn 'backColor')
$o['textColor'] = (Get-Child $itn 'textColor')
$fnt = Build-PlannerFont ($itn.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $o['font'] = $fnt }
$o['replacementDate'] = (Get-Child $itn 'replacementDate')
$o['deleted'] = (PLD-Bool (Get-Child $itn 'deleted'))
$o['id'] = (Get-Child $itn 'id')
$o['textFormatted'] = (PLD-Bool (Get-Child $itn 'textFormatted'))
$brd = Build-PlannerBorder ($itn.SelectSingleNode("*[local-name()='border']")); if ($brd) { $o['border'] = $brd }
$o['editMode'] = (Get-Child $itn 'editMode')
return $o
}
function Build-PlannerLevel {
param($lvn)
$o = [ordered]@{}
$o['measure'] = (Get-Child $lvn 'measure')
$o['interval'] = (PLD-Int (Get-Child $lvn 'interval'))
$o['show'] = (PLD-Bool (Get-Child $lvn 'show'))
$lineNode = $lvn.SelectSingleNode("*[local-name()='line']")
if ($lineNode) {
$ln = [ordered]@{}
$w = $lineNode.GetAttribute('width'); if ($w -ne '') { $ln['width'] = [int]$w }
$g = $lineNode.GetAttribute('gap'); if ($g -ne '') { $ln['gap'] = ($g -eq 'true') }
$st = $lineNode.SelectSingleNode("*[local-name()='style']"); if ($st) { $ln['style'] = $st.InnerText }
$o['line'] = $ln
}
$o['scaleColor'] = (Get-Child $lvn 'scaleColor')
$o['dayFormatRule'] = (Get-Child $lvn 'dayFormatRule')
$fmtNode = $lvn.SelectSingleNode("*[local-name()='format']")
if ($fmtNode) { $f = Get-LangText $fmtNode; if ($null -ne $f) { $o['format'] = $f } }
$labelsNode = $lvn.SelectSingleNode("*[local-name()='labels']")
if ($labelsNode) { $o['labels'] = [ordered]@{ ticks = (PLD-Int (Get-Child $labelsNode 'ticks')) } }
$o['backColor'] = (Get-Child $lvn 'backColor')
$o['textColor'] = (Get-Child $lvn 'textColor')
$o['showPereodicalLabels'] = (PLD-Bool (Get-Child $lvn 'showPereodicalLabels'))
return $o
}
function Build-PlannerTimeScale {
param($tsn)
$o = [ordered]@{}
$o['placement'] = (Get-Child $tsn 'placement')
$levels = New-Object System.Collections.ArrayList
foreach ($lvn in @($tsn.SelectNodes("*[local-name()='level']"))) { [void]$levels.Add((Build-PlannerLevel $lvn)) }
$o['levels'] = @($levels)
$o['transparent'] = (PLD-Bool (Get-Child $tsn 'transparent'))
$o['backColor'] = (Get-Child $tsn 'backColor')
$o['textColor'] = (Get-Child $tsn 'textColor')
$o['currentLevel'] = (PLD-Int (Get-Child $tsn 'currentLevel'))
return $o
}
function Build-PlannerSettings {
param($setNode)
$pl = [ordered]@{}
$itemNodes = @($setNode.SelectNodes("*[local-name()='item']"))
if ($itemNodes.Count -gt 0) {
$items = New-Object System.Collections.ArrayList
foreach ($itn in $itemNodes) { [void]$items.Add((Build-PlannerItem $itn)) }
$pl['items'] = @($items)
}
$pl['borderColor'] = (Get-Child $setNode 'borderColor')
$pl['backColor'] = (Get-Child $setNode 'backColor')
$pl['textColor'] = (Get-Child $setNode 'textColor')
$pl['lineColor'] = (Get-Child $setNode 'lineColor')
$fnt = Build-PlannerFont ($setNode.SelectSingleNode("*[local-name()='font']")); if ($fnt) { $pl['font'] = $fnt }
$pl['beginOfRepresentationPeriod'] = (Get-Child $setNode 'beginOfRepresentationPeriod')
$pl['endOfRepresentationPeriod'] = (Get-Child $setNode 'endOfRepresentationPeriod')
$pl['alignElementsOfTimeScale'] = (PLD-Bool (Get-Child $setNode 'alignElementsOfTimeScale'))
$pl['displayTimeScaleWrapHeaders'] = (PLD-Bool (Get-Child $setNode 'displayTimeScaleWrapHeaders'))
$pl['displayWrapHeaders'] = (PLD-Bool (Get-Child $setNode 'displayWrapHeaders'))
$wfNode = $setNode.SelectSingleNode("*[local-name()='timeScaleWrapHeadersFormat']")
if ($wfNode) { $wf = Get-LangText $wfNode; if ($null -ne $wf) { $pl['timeScaleWrapHeadersFormat'] = $wf } }
$pl['periodicVariantUnit'] = (Get-Child $setNode 'periodicVariantUnit')
$pl['periodicVariantRepetition'] = (PLD-Int (Get-Child $setNode 'periodicVariantRepetition'))
$pl['timeScaleWrapBeginIndent'] = (PLD-Int (Get-Child $setNode 'timeScaleWrapBeginIndent'))
$pl['timeScaleWrapEndIndent'] = (PLD-Int (Get-Child $setNode 'timeScaleWrapEndIndent'))
$tsNode = $setNode.SelectSingleNode("*[local-name()='timeScale']")
if ($tsNode) { $pl['timeScale'] = (Build-PlannerTimeScale $tsNode) }
$perNode = $setNode.SelectSingleNode("*[local-name()='period']")
if ($perNode) { $pl['period'] = [ordered]@{ begin = (Get-Child $perNode 'begin'); end = (Get-Child $perNode 'end') } }
$pl['displayCurrentDate'] = (PLD-Bool (Get-Child $setNode 'displayCurrentDate'))
$pl['itemsTimeRepresentation'] = (Get-Child $setNode 'itemsTimeRepresentation')
$pl['itemsBehaviorWhenSpaceInsufficient'] = (Get-Child $setNode 'itemsBehaviorWhenSpaceInsufficient')
$pl['autoMinColumnWidth'] = (PLD-Bool (Get-Child $setNode 'autoMinColumnWidth'))
$pl['autoMinRowHeight'] = (PLD-Bool (Get-Child $setNode 'autoMinRowHeight'))
$pl['minColumnWidth'] = (PLD-Int (Get-Child $setNode 'minColumnWidth'))
$pl['minRowHeight'] = (PLD-Int (Get-Child $setNode 'minRowHeight'))
$pl['fixDimensionsHeader'] = (Get-Child $setNode 'fixDimensionsHeader')
$pl['fixTimeScaleHeader'] = (Get-Child $setNode 'fixTimeScaleHeader')
$brd = Build-PlannerBorder ($setNode.SelectSingleNode("*[local-name()='border']")); if ($brd) { $pl['border'] = $brd }
$pl['newItemsTextType'] = (Get-Child $setNode 'newItemsTextType')
return $pl
}
# --- 5. Form-level assembly ---
$dsl = [ordered]@{}
@@ -1964,6 +2089,10 @@ if ($attrsNode) {
$vt = Decompile-Type $setNode
$ao['valueType'] = if ($vt) { $vt } else { '' } # пустой Settings → маркер ""
}
# Planner design-time <Settings xsi:type="pl:Planner"> → объект planner (полный захват).
elseif ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'Planner$') {
$ao['planner'] = Build-PlannerSettings $setNode
}
if ((Get-Child $a 'MainAttribute') -eq 'true') { $ao['main'] = $true }
elseif ($suppressMainName -and $ao['name'] -eq $suppressMainName) { $ao['main'] = $false }
$vw = Decompile-XrFlag $a 'View'; if ($null -ne $vw) { $ao['view'] = $vw }
@@ -2053,9 +2182,9 @@ if ($attrsNode) {
$ao['useAlways'] = @($shorts)
}
}
# Settings динамического списка
# Settings динамического списка (только xsi:type=DynamicList; Planner/TypeDescription — выше)
$setNode = $a.SelectSingleNode("lf:Settings", $ns)
if ($setNode) {
if ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'DynamicList$') {
$so = [ordered]@{}
# AutoFillAvailableFields — дефолт true, платформа эмитит только отклонение (false). Захват «как есть».
$afaf = Get-Child $setNode 'AutoFillAvailableFields'; if ($null -ne $afaf) { $so['autoFillAvailableFields'] = ($afaf -eq 'true') }