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

Реквизит-диаграмма несёт <Settings xsi:type="d4p1:Chart"> — встроенный конфиг
(~110-130 версионно-вариативных полей: тип/серии/легенда/заголовок/шкалы/цвета/
оси, глубокая вложенность с повторяющимися именами). Корпус 8.3.24: 5 форм.

Подход (с пользователем): ГЕНЕРИК-движок. Ключ chart на реквизите; рекурсивный
захват/эмит поддерева d4p1, ключи = локальные имена тегов, порядок ключей =
порядок эмиссии → раундтрип ЛЮБОЙ версии/набора полей бит-в-бит (платформа
добавляет поля, не переставляет). Структуры распознаются по форме узла
(line {width,gap,style} / border {width,style} / font {kind} / ML / области
{left,right,top,bottom} / серии-массивы); малые name-set'ы: ML-поля, серии,
attrs-узлы (gaugeQualityBands). Расширяемость: любое из ~127 свойств — по
каноничному имени.

Авторинг с нуля: декомпиль рабочей диаграммы как шаблон + правка ядра
(chartType/серии/легенда/цвета). Default-fill через merge НЕ делаем — конфликт
с байт-точностью неполных форм (см. docs/form-dsl-spec.md).

Результат: 4 из 5 форм корпуса — байт-в-байт (включая версионно-вариативные).
5-я (точки/оси realPointData/realDataItems с типизир. значениями xsi:type,
xsi:nil, ML с префиксом d4p1:) → честный fail-ring3 (редкий вариант, не
поддержан генериком). Снят fail-ring3 для d4p1:Chart (GanttChart — Фаза 3).
Заодно фикс: d5p1:Dendrogram отсутствовал в specialTypeNs (ps1+py).

Декомпилятор ps1-only (генерик-рекурсия); компилятор зеркало py (ps1==py
байт-в-байт). Кейс chart-settings (полная диаграмма из эталона
ПроверкаКонтрагента) сертифицирован загрузкой в 1С. Регресс 42/42.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-10 16:01:15 +03:00
co-authored by Claude Opus 4.8
parent f534add7b4
commit 1333e09ff8
14 changed files with 1287 additions and 4 deletions
@@ -1,4 +1,4 @@
# form-decompile v0.81 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.82 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -152,9 +152,15 @@ 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$' -and $st -notmatch 'Planner$') {
if ($st -and $st -notmatch 'TypeDescription$' -and $st -notmatch 'DynamicList$' -and $st -notmatch 'Planner$' -and $st -notmatch 'd4p1:Chart$') {
Fail-Ring3 -kind "Attribute>Settings типа '$st' (design-time конфигурация, напр. диаграмма)" -loc "Attribute/Settings"
}
# Chart с точками/осями (realPointData/realDataItems): типизированные значения (xsi:type),
# xsi:nil и ML с префиксом d4p1: (а не v8:) генерик-движок не сохраняет → честный скип.
# Частые дашборд-диаграммы (серии/легенда/оформление) поддержаны.
elseif ($st -match 'd4p1:Chart$' -and ($s.OuterXml -match '<d4p1:\w+ xsi:type=' -or $s.OuterXml -match '<d4p1:\w+ xsi:nil=' -or $s.OuterXml -match '<d4p1:item[ >]')) {
Fail-Ring3 -kind "Attribute>Settings d4p1:Chart с точками/осями (типизированные значения/d4p1-ML)" -loc "Attribute/Settings"
}
}
# --- 1c. Compact JSON serializer (созвучно skd-decompile: 2-проб. indent, inline в пределах lineLimit) ---
@@ -2002,6 +2008,69 @@ function Build-PlannerSettings {
return $pl
}
# ─────────────────────────────────────────────────────────────────────────────
# Chart design-time <Settings xsi:type="d4p1:Chart"> → объект chart. Генерик-движок:
# рекурсивный захват поддерева d4p1; структуры (line/border/font/ML/области/серии)
# детектируются по форме узла. Малые name-set'ы: ML-поля (даже ru-only/пустые → ML),
# серии (всегда массив), attrs-узлы. Порядок ключей JSON = порядок XML (раундтрип).
$CHART_ML_FIELDS = @{ 'title'=1;'lbFormat'=1;'lbpFormat'=1;'vsFormat'=1;'dtFormat'=1;'dataSourceDescription'=1;'labelFormat'=1;'text'=1 }
$CHART_SERIES_FIELDS = @{ 'realSeriesData'=1;'realExSeriesData'=1;'realPointData'=1;'realDataItems'=1 }
$CHART_ATTR_FIELDS = @{ 'gaugeQualityBands'=1 }
function Conv-ChartScalar {
param([string]$v)
if ($v -eq 'true') { return $true }
if ($v -eq 'false') { return $false }
return $v
}
function Build-ChartNode {
param($n, [string]$name)
# ML-поле → строка/мапа/"" (даже ru-only форсим в ML на эмите по имени)
if ($CHART_ML_FIELDS.Contains($name)) {
$ml = Get-LangText $n
if ($null -eq $ml) { return '' } else { return $ml }
}
$kids = @($n.SelectNodes("*"))
if ($kids.Count -eq 0) {
# лист: attrs-only (шрифт/gaugeQualityBands) или текст
$attrs = @($n.Attributes | Where-Object { $_.Name -ne 'xmlns' -and -not $_.Name.StartsWith('xmlns:') -and $_.Name -ne 'xsi:type' -and $_.Name -ne 'xsi:nil' })
if ($attrs.Count -gt 0) {
$o = [ordered]@{}; foreach ($a in $attrs) { $o[$a.Name] = (Conv-ChartScalar $a.Value) }; return $o
}
return (Conv-ChartScalar $n.InnerText)
}
# line/border: дочерний v8ui:style (+ width[/gap])
$styleChild = $n.SelectSingleNode("*[local-name()='style']")
if ($styleChild) {
$o = [ordered]@{}
$w = $n.GetAttribute('width'); if ($w -ne '') { $o['width'] = [int]$w }
$g = $n.GetAttribute('gap'); if ($g -ne '') { $o['gap'] = ($g -eq 'true') }
$o['style'] = $styleChild.InnerText
return $o
}
# вложенный объект d4p1 (область/шкала/titleArea/серия): группируем детей по имени
$o = [ordered]@{}
foreach ($c in $kids) {
$ln = $c.LocalName
$val = Build-ChartNode $c $ln
if ($CHART_SERIES_FIELDS.Contains($ln)) {
if (-not $o.Contains($ln)) { $o[$ln] = New-Object System.Collections.ArrayList }
[void]$o[$ln].Add($val)
} elseif ($o.Contains($ln)) {
if ($o[$ln] -isnot [System.Collections.IList]) { $tmp = New-Object System.Collections.ArrayList; [void]$tmp.Add($o[$ln]); $o[$ln] = $tmp }
[void]$o[$ln].Add($val)
} else {
$o[$ln] = $val
}
}
# нормализуем ArrayList → @() для сериализации
foreach ($k in @($o.Keys)) { if ($o[$k] -is [System.Collections.ArrayList]) { $o[$k] = @($o[$k]) } }
return $o
}
function Build-ChartSettings {
param($setNode)
return (Build-ChartNode $setNode '')
}
# --- 5. Form-level assembly ---
$dsl = [ordered]@{}
@@ -2137,6 +2206,10 @@ if ($attrsNode) {
elseif ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'Planner$') {
$ao['planner'] = Build-PlannerSettings $setNode
}
# Chart design-time <Settings xsi:type="d4p1:Chart"> → объект chart (генерик-захват).
elseif ($setNode -and $setNode.GetAttribute("type", $NS_XSI) -match 'd4p1:Chart$') {
$ao['chart'] = Build-ChartSettings $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 }