feat(form-decompile,form-compile): AdditionalColumns — доп. колонки табличных частей объекта

<Columns><AdditionalColumns table="Объект.ТабЧасть"><Column>…</AdditionalColumns></Columns>
у главного реквизита-объекта (3654 формы, 10187 блоков) — форма-определённые доп.
колонки табличных частей. Декомпилятор читал только прямые <Column> (SelectNodes lf:Column),
теряя AdditionalColumns целиком (часто весь <Columns> блок объекта).

Ключ реквизита additionalColumns: [{ table, columns: [<col>] }]; <col> — та же грамматика,
что у columns (name/type/title/functionalOptions). Общие хелперы Emit/Decompile-AttrColumn
(переиспользуются прямыми колонками и AdditionalColumns). Порядок схемы: прямые <Column>
сначала, затем AdditionalColumns-группы.

TOTAL diff lines выборки 2.17: 3695 → 3347 (-348). Attribute>Columns/AdditionalColumns
residual → 0. Новый кейс additional-columns (DataProcessor с табчастью + форма) сертифицирован
в 1С (8.3.24). Регресс form-compile 34/34 зелёный на ps + python.
decompile v0.37, compile v1.55.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-07 13:47:28 +03:00
co-authored by Claude Opus 4.8
parent 6056a4a5af
commit 786bdf97d9
14 changed files with 621 additions and 33 deletions
@@ -1,4 +1,4 @@
# form-compile v1.54 — Compile 1C managed form from JSON or object metadata
# form-compile v1.55 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -3616,6 +3616,17 @@ function Emit-FunctionalOptions {
X "$indent</FunctionalOptions>"
}
# Колонка реквизита (ValueTable/Tree или AdditionalColumns): name/Title/Type/FunctionalOptions.
function Emit-AttrColumn {
param($col, [string]$indent)
$colId = New-Id
X "$indent<Column name=`"$($col.name)`" id=`"$colId`">"
if ($col.title) { Emit-MLText -tag "Title" -text $col.title -indent "$indent`t" }
Emit-Type -typeStr "$($col.type)" -indent "$indent`t"
Emit-FunctionalOptions -fo $col.functionalOptions -indent "$indent`t"
X "$indent</Column>"
}
function Emit-Attributes {
param($attrs, [string]$indent)
@@ -3684,19 +3695,22 @@ function Emit-Attributes {
Emit-FunctionalOptions -fo $attr.functionalOptions -indent $inner
# Columns (for ValueTable/ValueTree). Для дин-списка (есть settings) колонки НЕ эмитим —
# они служат лишь для формирования UseAlways (поля выше).
if ($attr.columns -and $attr.columns.Count -gt 0 -and -not $attr.settings) {
# Columns: прямые <Column> (ValueTable/Tree) + <AdditionalColumns table="X"> (доп. колонки
# табличных частей объекта). Порядок схемы: прямые сначала, затем AdditionalColumns-группы.
# Для дин-списка (есть settings) прямые колонки НЕ эмитим (служат лишь для UseAlways).
$hasDirectCols = $attr.columns -and $attr.columns.Count -gt 0 -and -not $attr.settings
$hasAddCols = $attr.additionalColumns -and @($attr.additionalColumns).Count -gt 0
if ($hasDirectCols -or $hasAddCols) {
X "$inner<Columns>"
foreach ($col in $attr.columns) {
$colId = New-Id
X "$inner`t<Column name=`"$($col.name)`" id=`"$colId`">"
if ($col.title) {
Emit-MLText -tag "Title" -text $col.title -indent "$inner`t`t"
if ($hasDirectCols) {
foreach ($col in $attr.columns) { Emit-AttrColumn -col $col -indent "$inner`t" }
}
if ($hasAddCols) {
foreach ($ac in @($attr.additionalColumns)) {
X "$inner`t<AdditionalColumns table=`"$($ac.table)`">"
foreach ($col in @($ac.columns)) { Emit-AttrColumn -col $col -indent "$inner`t`t" }
X "$inner`t</AdditionalColumns>"
}
Emit-Type -typeStr "$($col.type)" -indent "$inner`t`t"
Emit-FunctionalOptions -fo $col.functionalOptions -indent "$inner`t`t"
X "$inner`t</Column>"
}
X "$inner</Columns>"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-compile v1.54 — Compile 1C managed form from JSON or object metadata
# form-compile v1.55 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import copy
@@ -3292,6 +3292,17 @@ def emit_functional_options(lines, fo, indent):
lines.append(f'{indent}</FunctionalOptions>')
def emit_attr_column(lines, col, indent):
# Колонка реквизита (ValueTable/Tree или AdditionalColumns): name/Title/Type/FunctionalOptions.
col_id = new_id()
lines.append(f'{indent}<Column name="{col["name"]}" id="{col_id}">')
if col.get('title'):
emit_mltext(lines, f'{indent}\t', 'Title', col['title'])
emit_type(lines, str(col.get('type', '')), f'{indent}\t')
emit_functional_options(lines, col.get('functionalOptions'), f'{indent}\t')
lines.append(f'{indent}</Column>')
def emit_attributes(lines, attrs, indent):
if not attrs or len(attrs) == 0:
return
@@ -3354,18 +3365,21 @@ def emit_attributes(lines, attrs, indent):
emit_functional_options(lines, attr.get('functionalOptions'), inner)
# Columns (for ValueTable/ValueTree). Для дин-списка (есть settings) колонки НЕ эмитим —
# они служат лишь для формирования UseAlways.
if attr.get('columns') and len(attr['columns']) > 0 and not attr.get('settings'):
# Columns: прямые <Column> + <AdditionalColumns table="X"> (доп. колонки табличных частей объекта).
# Прямые сначала, затем AdditionalColumns-группы. Для дин-списка (settings) прямые НЕ эмитим.
has_direct_cols = bool(attr.get('columns')) and len(attr['columns']) > 0 and not attr.get('settings')
has_add_cols = bool(attr.get('additionalColumns')) and len(attr['additionalColumns']) > 0
if has_direct_cols or has_add_cols:
lines.append(f'{inner}<Columns>')
for col in attr['columns']:
col_id = new_id()
lines.append(f'{inner}\t<Column name="{col["name"]}" id="{col_id}">')
if col.get('title'):
emit_mltext(lines, f'{inner}\t\t', 'Title', col['title'])
emit_type(lines, str(col.get('type', '')), f'{inner}\t\t')
emit_functional_options(lines, col.get('functionalOptions'), f'{inner}\t\t')
lines.append(f'{inner}\t</Column>')
if has_direct_cols:
for col in attr['columns']:
emit_attr_column(lines, col, f'{inner}\t')
if has_add_cols:
for ac in attr['additionalColumns']:
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}">')
for col in (ac.get('columns') or []):
emit_attr_column(lines, col, f'{inner}\t\t')
lines.append(f'{inner}\t</AdditionalColumns>')
lines.append(f'{inner}</Columns>')
# Settings (динамический список)
@@ -1,4 +1,4 @@
# form-decompile v0.36 — Decompile 1C managed Form.xml to JSON DSL (draft)
# form-decompile v0.37 — Decompile 1C managed Form.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
param(
@@ -816,6 +816,16 @@ function Decompile-FunctionalOptions {
return $null
}
# Колонка реквизита (прямая или внутри AdditionalColumns): name/type/title/functionalOptions.
function Decompile-AttrColumn {
param($c)
$co = [ordered]@{}; $co['name'] = $c.GetAttribute("name")
$cty = Decompile-Type ($c.SelectSingleNode("lf:Type", $ns)); if ($cty) { $co['type'] = $cty }
$ctNode = $c.SelectSingleNode("lf:Title", $ns); if ($ctNode) { $t = Get-LangText $ctNode; if ($null -ne $t) { $co['title'] = $t } }
$cfo = Decompile-FunctionalOptions $c; if ($cfo) { $co['functionalOptions'] = $cfo }
return $co
}
# Общие свойства элемента (visible/enabled/readonly/title/events) → в hash
function Add-CommonProps {
param($obj, $node, [string]$elName)
@@ -1320,14 +1330,21 @@ if ($attrsNode) {
$colsNode = $a.SelectSingleNode("lf:Columns", $ns)
if ($colsNode) {
$cols = New-Object System.Collections.ArrayList
foreach ($c in @($colsNode.SelectNodes("lf:Column", $ns))) {
$co = [ordered]@{}; $co['name'] = $c.GetAttribute("name")
$cty = Decompile-Type ($c.SelectSingleNode("lf:Type", $ns)); if ($cty) { $co['type'] = $cty }
$ctNode = $c.SelectSingleNode("lf:Title", $ns); if ($ctNode) { $t = Get-LangText $ctNode; if ($null -ne $t) { $co['title'] = $t } }
$cfo = Decompile-FunctionalOptions $c; if ($cfo) { $co['functionalOptions'] = $cfo }
[void]$cols.Add($co)
}
foreach ($c in @($colsNode.SelectNodes("lf:Column", $ns))) { [void]$cols.Add((Decompile-AttrColumn $c)) }
if ($cols.Count -gt 0) { $ao['columns'] = @($cols) }
# AdditionalColumns: доп. колонки табличных частей объекта (группа на табличную часть)
$addNodes = @($colsNode.SelectNodes("lf:AdditionalColumns", $ns))
if ($addNodes.Count -gt 0) {
$addList = New-Object System.Collections.ArrayList
foreach ($an in $addNodes) {
$acObj = [ordered]@{}; $acObj['table'] = $an.GetAttribute("table")
$acCols = New-Object System.Collections.ArrayList
foreach ($c in @($an.SelectNodes("lf:Column", $ns))) { [void]$acCols.Add((Decompile-AttrColumn $c)) }
$acObj['columns'] = @($acCols)
[void]$addList.Add($acObj)
}
$ao['additionalColumns'] = @($addList)
}
}
# UseAlways: поля, всегда читаемые. Префикс "ИмяРеквизита." снимаем.
# ValueTable (есть columns): useAlways:true на совпавшей колонке; остальные → массив атрибута.