mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-17 08:15:16 +03:00
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:
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.105 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.106 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -3233,6 +3233,66 @@ function Emit-PlannerSettings {
|
||||
X "$ind</Settings>"
|
||||
}
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Chart design-time <Settings xsi:type="d4p1:Chart"> — генерик-эмиттер (зеркало
|
||||
# Build-ChartNode декомпилятора). Тип узла → форма XML: ML-поля (по имени), серии
|
||||
# (массив, повтор тега), line/border/font (по ключам), attrs-узлы (gaugeQualityBands),
|
||||
# иначе вложенный объект/скаляр. Порядок ключей = порядок эмиссии (раундтрип).
|
||||
$script:CHART_ML_FIELDS = @{ 'title'=1;'lbFormat'=1;'lbpFormat'=1;'vsFormat'=1;'dtFormat'=1;'dataSourceDescription'=1;'labelFormat'=1;'text'=1 }
|
||||
$script:CHART_ATTR_FIELDS = @{ 'gaugeQualityBands'=1 }
|
||||
$script:CHART_FONT_KEYS = @('ref','faceName','height','bold','italic','underline','strikeout','kind','scale')
|
||||
function Get-Keys { param($o) if ($o -is [System.Collections.IDictionary]) { return @($o.Keys) } else { return @($o.PSObject.Properties.Name) } }
|
||||
function Get-Prop { param($o, [string]$k) if ($o -is [System.Collections.IDictionary]) { return $o[$k] } else { $p = $o.PSObject.Properties[$k]; if ($p) { return $p.Value } else { return $null } } }
|
||||
function Emit-ChartNode {
|
||||
param([string]$name, $val, [string]$ind)
|
||||
if ($script:CHART_ML_FIELDS.Contains($name)) {
|
||||
if ($null -eq $val -or "$val" -eq '') { X "$ind<d4p1:$name/>"; return }
|
||||
X "$ind<d4p1:$name>"; Emit-MLItems -val $val -indent "$ind`t"; X "$ind</d4p1:$name>"; return
|
||||
}
|
||||
if (($val -is [System.Collections.IList]) -and ($val -isnot [string])) {
|
||||
foreach ($e in $val) { Emit-ChartNode $name $e $ind }
|
||||
return
|
||||
}
|
||||
if (($val -is [System.Management.Automation.PSCustomObject]) -or ($val -is [System.Collections.IDictionary])) {
|
||||
$keys = Get-Keys $val
|
||||
if ($script:CHART_ATTR_FIELDS.Contains($name)) {
|
||||
$attrs = @(); foreach ($k in $keys) { $v = Get-Prop $val $k; if ($v -is [bool]) { $v = PL-Bool $v }; $attrs += "$k=`"$(Esc-Xml "$v")`"" }
|
||||
X "$ind<d4p1:$name $($attrs -join ' ')/>"; return
|
||||
}
|
||||
if ($keys -contains 'gap') {
|
||||
$w = Get-Prop $val 'width'; $g = Get-Prop $val 'gap'; $st = Get-Prop $val 'style'
|
||||
X "$ind<d4p1:$name width=`"$w`" gap=`"$(PL-Bool $g)`">"
|
||||
X "$ind`t<v8ui:style xsi:type=`"v8ui:ChartLineType`">$(Esc-Xml "$st")</v8ui:style>"
|
||||
X "$ind</d4p1:$name>"; return
|
||||
}
|
||||
if (($keys -contains 'style') -and ($keys -contains 'width')) {
|
||||
$w = Get-Prop $val 'width'; $st = Get-Prop $val 'style'
|
||||
X "$ind<d4p1:$name width=`"$w`">"
|
||||
X "$ind`t<v8ui:style xsi:type=`"v8ui:ControlBorderType`">$(Esc-Xml "$st")</v8ui:style>"
|
||||
X "$ind</d4p1:$name>"; return
|
||||
}
|
||||
$isFont = $false; foreach ($fk in $script:CHART_FONT_KEYS) { if ($keys -contains $fk) { $isFont = $true; break } }
|
||||
if ($isFont) {
|
||||
$attrs = @(); foreach ($fk in $script:CHART_FONT_KEYS) { if ($keys -contains $fk) { $v = Get-Prop $val $fk; if ($v -is [bool]) { $v = PL-Bool $v }; $attrs += "$fk=`"$(Esc-Xml "$v")`"" } }
|
||||
X "$ind<d4p1:$name $($attrs -join ' ')/>"; return
|
||||
}
|
||||
if (@($keys).Count -eq 0) { X "$ind<d4p1:$name/>"; return }
|
||||
X "$ind<d4p1:$name>"
|
||||
foreach ($k in $keys) { Emit-ChartNode $k (Get-Prop $val $k) "$ind`t" }
|
||||
X "$ind</d4p1:$name>"
|
||||
return
|
||||
}
|
||||
if ($null -eq $val -or "$val" -eq '') { X "$ind<d4p1:$name/>"; return }
|
||||
if ($val -is [bool]) { X "$ind<d4p1:$name>$(PL-Bool $val)</d4p1:$name>"; return }
|
||||
X "$ind<d4p1:$name>$(Esc-Xml "$val")</d4p1:$name>"
|
||||
}
|
||||
function Emit-ChartSettings {
|
||||
param($chart, [string]$ind)
|
||||
X "$ind<Settings xmlns:d4p1=`"$($script:CHART_NS)`" xsi:type=`"d4p1:Chart`">"
|
||||
foreach ($k in (Get-Keys $chart)) { Emit-ChartNode $k (Get-Prop $chart $k) "$ind`t" }
|
||||
X "$ind</Settings>"
|
||||
}
|
||||
|
||||
function Emit-Appearance {
|
||||
param($el, [string]$indent, [string]$profile = 'field')
|
||||
if ($null -eq $el) { return }
|
||||
@@ -4982,6 +5042,10 @@ function Emit-Attributes {
|
||||
if ($attr.PSObject.Properties['planner'] -and $null -ne $attr.planner) {
|
||||
Emit-PlannerSettings -pl $attr.planner -ind $inner
|
||||
}
|
||||
# Chart design-time <Settings xsi:type="d4p1:Chart"> (встроенный конфиг диаграммы).
|
||||
if ($attr.PSObject.Properties['chart'] -and $null -ne $attr.chart) {
|
||||
Emit-ChartSettings -chart $attr.chart -ind $inner
|
||||
}
|
||||
|
||||
if ($attr.main -eq $true) {
|
||||
X "$inner<MainAttribute>true</MainAttribute>"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.105 — Compile 1C managed form from JSON or object metadata
|
||||
# form-compile v1.106 — Compile 1C managed form from JSON or object metadata
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -2939,6 +2939,71 @@ def emit_planner_settings(lines, pl, ind):
|
||||
lines.append(f'{ind}</Settings>')
|
||||
|
||||
|
||||
# ─────────────────────────────────────────────────────────────────────────────
|
||||
# Chart design-time <Settings xsi:type="d4p1:Chart"> — генерик-эмиттер (зеркало
|
||||
# Build-ChartNode декомпилятора + Emit-ChartNode ps1).
|
||||
CHART_ML_FIELDS = {'title', 'lbFormat', 'lbpFormat', 'vsFormat', 'dtFormat', 'dataSourceDescription', 'labelFormat', 'text'}
|
||||
CHART_ATTR_FIELDS = {'gaugeQualityBands'}
|
||||
CHART_FONT_KEYS = ('ref', 'faceName', 'height', 'bold', 'italic', 'underline', 'strikeout', 'kind', 'scale')
|
||||
|
||||
|
||||
def emit_chart_node(lines, name, val, ind):
|
||||
if name in CHART_ML_FIELDS:
|
||||
if val is None or str(val) == '':
|
||||
lines.append(f'{ind}<d4p1:{name}/>')
|
||||
return
|
||||
lines.append(f'{ind}<d4p1:{name}>')
|
||||
emit_ml_items(lines, f'{ind}\t', val)
|
||||
lines.append(f'{ind}</d4p1:{name}>')
|
||||
return
|
||||
if isinstance(val, list):
|
||||
for e in val:
|
||||
emit_chart_node(lines, name, e, ind)
|
||||
return
|
||||
if isinstance(val, dict):
|
||||
keys = list(val.keys())
|
||||
if name in CHART_ATTR_FIELDS:
|
||||
attrs = ' '.join(f'{k}="{esc_xml(_pl_bool(val[k]) if isinstance(val[k], bool) else str(val[k]))}"' for k in keys)
|
||||
lines.append(f'{ind}<d4p1:{name} {attrs}/>')
|
||||
return
|
||||
if 'gap' in val:
|
||||
lines.append(f'{ind}<d4p1:{name} width="{val.get("width")}" gap="{_pl_bool(val.get("gap"))}">')
|
||||
lines.append(f'{ind}\t<v8ui:style xsi:type="v8ui:ChartLineType">{esc_xml(str(val.get("style")))}</v8ui:style>')
|
||||
lines.append(f'{ind}</d4p1:{name}>')
|
||||
return
|
||||
if 'style' in val and 'width' in val:
|
||||
lines.append(f'{ind}<d4p1:{name} width="{val.get("width")}">')
|
||||
lines.append(f'{ind}\t<v8ui:style xsi:type="v8ui:ControlBorderType">{esc_xml(str(val.get("style")))}</v8ui:style>')
|
||||
lines.append(f'{ind}</d4p1:{name}>')
|
||||
return
|
||||
if any(fk in val for fk in CHART_FONT_KEYS):
|
||||
attrs = ' '.join(f'{fk}="{esc_xml(_pl_bool(val[fk]) if isinstance(val[fk], bool) else str(val[fk]))}"' for fk in CHART_FONT_KEYS if fk in val)
|
||||
lines.append(f'{ind}<d4p1:{name} {attrs}/>')
|
||||
return
|
||||
if not keys:
|
||||
lines.append(f'{ind}<d4p1:{name}/>')
|
||||
return
|
||||
lines.append(f'{ind}<d4p1:{name}>')
|
||||
for k in keys:
|
||||
emit_chart_node(lines, k, val[k], f'{ind}\t')
|
||||
lines.append(f'{ind}</d4p1:{name}>')
|
||||
return
|
||||
if val is None or str(val) == '':
|
||||
lines.append(f'{ind}<d4p1:{name}/>')
|
||||
return
|
||||
if isinstance(val, bool):
|
||||
lines.append(f'{ind}<d4p1:{name}>{_pl_bool(val)}</d4p1:{name}>')
|
||||
return
|
||||
lines.append(f'{ind}<d4p1:{name}>{esc_xml(str(val))}</d4p1:{name}>')
|
||||
|
||||
|
||||
def emit_chart_settings(lines, chart, ind):
|
||||
lines.append(f'{ind}<Settings xmlns:d4p1="{CHART_NS}" xsi:type="d4p1:Chart">')
|
||||
for k in list(chart.keys()):
|
||||
emit_chart_node(lines, k, chart[k], f'{ind}\t')
|
||||
lines.append(f'{ind}</Settings>')
|
||||
|
||||
|
||||
def emit_appearance(lines, el, indent, profile='field'):
|
||||
if not isinstance(el, dict):
|
||||
return
|
||||
@@ -4709,6 +4774,9 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None):
|
||||
# Planner design-time <Settings xsi:type="pl:Planner"> (встроенный конфиг планировщика).
|
||||
if attr.get('planner') is not None:
|
||||
emit_planner_settings(lines, attr['planner'], inner)
|
||||
# Chart design-time <Settings xsi:type="d4p1:Chart"> (встроенный конфиг диаграммы).
|
||||
if attr.get('chart') is not None:
|
||||
emit_chart_settings(lines, attr['chart'], inner)
|
||||
|
||||
if attr.get('main') is True:
|
||||
lines.append(f'{inner}<MainAttribute>true</MainAttribute>')
|
||||
|
||||
@@ -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 }
|
||||
|
||||
Reference in New Issue
Block a user