feat(form-decompile,form-compile): вычисляемые поля + valueType поля DataSet динсписка (кластер C)

Две части пробела DataSet динамического списка:

1. Field valueType (63 формы/54). Поле набора <Field DataSetFieldField> может нести
   <dcssch:valueType> (тип значения; кастомные/вычисляемые поля). Декомпилятор ловил
   field/dataPath/title/nested, теперь и valueType (переиспользует Decompile-Type);
   компилятор эмитит после title через существующий emit_dl_value_type. 0 потерь на выборке.

2. CalculatedField (6 форм, редкое). Новый ключ settings.calculatedFields — зеркало skd:
   shorthand "Имя [Заголовок]: тип = Выражение #noField #noFilter #noGroup #noOrder"
   (порт Parse-CalcShorthand) или объект. Форм-специфика: dcssch:-теги, presentationExpression,
   orderExpression* (структура {expression,orderType,autoOrder} в namespace dcscommon с
   локальным xmlns), useRestriction{field,condition,group,order}. Эмиттер форм-специфичный
   (skd использует dcscom:-префикс и не имеет pres/orderExpression). Позиция в DataSet —
   после Field*, до Parameter*. Декомпилятор Build-CalcField (объектная форма для точного
   round-trip). Выборка calc-форм: calc-теги ушли из диффов (3/6 match, остаток — отдельный
   field appearance/presentationExpression).

Кейс dynamic-list-form (+grouping +calculatedFields: shorthand с флагами + объект с
presentationExpression/orderExpression/valueType) сертифицирован загрузкой в 1С (порядок
детей CalculatedField подтверждён платформой). Регресс 43/43 (ps1+py).

ps1==py байт-в-байт (сверено на кейсе). Фикс по пути: ps1 Parse-CalcShorthand — `\b` в
generator-heredoc превратился в backspace 0x08 → #-флаги не парсились (py был верен);
поймано прямой сверкой вывода ps1 vs py.

C-остаток (в BACKLOG): свойства обычного <Field> — presentationExpression + appearance
(формат/цвет поля) — отдельный подкластер.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-12 19:38:14 +03:00
co-authored by Claude Opus 4.8
parent 43d36119cb
commit 73b07a6fbc
6 changed files with 292 additions and 4 deletions
@@ -1,4 +1,4 @@
# form-compile v1.139 — Compile 1C managed form from JSON or object metadata
# form-compile v1.140 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$JsonPath,
@@ -2067,6 +2067,80 @@ function Emit-ListGrouping {
Emit-ListGroupingLevels $levels 0 $indent
}
# === Вычисляемые поля DataSet динамического списка (<CalculatedField>) ===
# Зеркало skd: shorthand "Имя [Заголовок]: тип = Выражение #noField #noFilter #noGroup #noOrder"
# или объект. Форм-специфика: dcssch:-теги + presentationExpression/orderExpression (dcscommon ns).
$script:calcRestrictMap = @{ 'noField'='field'; 'noFilter'='condition'; 'noCondition'='condition'; 'noGroup'='group'; 'noOrder'='order' }
$script:dcsCommonNs = 'http://v8.1c.ru/8.1/data-composition-system/common'
function Parse-CalcShorthand {
param([string]$s)
$restrict = @()
foreach ($m in [regex]::Matches($s, '#(noField|noFilter|noCondition|noGroup|noOrder)\b')) { $restrict += $m.Groups[1].Value }
$s = [regex]::Replace($s, '\s*#(noField|noFilter|noCondition|noGroup|noOrder)\b', '')
$eq = $s.IndexOf('=')
if ($eq -gt 0) { $lhs = $s.Substring(0, $eq); $rhs = $s.Substring($eq + 1).Trim() } else { $lhs = $s; $rhs = '' }
$title = ''
if ($lhs -match '\[([^\]]+)\]') { $title = $Matches[1]; $lhs = $lhs -replace '\s*\[[^\]]+\]', '' }
$lhs = $lhs.Trim()
$type = ''; $dataPath = $lhs
if ($lhs.Contains(':')) { $parts = $lhs -split ':', 2; $dataPath = $parts[0].Trim(); $type = Resolve-TypeStr ($parts[1].Trim()) }
return @{ dataPath = $dataPath; expression = $rhs; type = $type; title = $title; restrict = $restrict }
}
function Emit-CalcFields {
param($calcFields, [string]$indent)
if (-not $calcFields) { return }
foreach ($cf in $calcFields) {
$pres = $null; $orderExpr = $null; $restrict = @()
if ($cf -is [string]) {
$p = Parse-CalcShorthand $cf
$dataPath = "$($p.dataPath)"; $expression = "$($p.expression)"; $title = $p.title; $typeStr = "$($p.type)"
foreach ($r in $p.restrict) { if ($script:calcRestrictMap[$r]) { $restrict += $script:calcRestrictMap[$r] } }
} else {
$dataPath = if ($cf.dataPath) { "$($cf.dataPath)" } elseif ($cf.field) { "$($cf.field)" } else { "$($cf.name)" }
$expression = "$($cf.expression)"
$title = $cf.title
$typeStr = if ($cf.valueType) { "$($cf.valueType)" } elseif ($cf.type) { "$($cf.type)" } else { '' }
$ur = if ($cf.useRestriction) { $cf.useRestriction } elseif ($cf.restrict) { $cf.restrict } else { $null }
if ($ur -is [System.Management.Automation.PSCustomObject] -or $ur -is [hashtable]) {
foreach ($k in 'field','condition','group','order') { if ($ur.$k -eq $true) { $restrict += $k } }
} elseif ($ur -is [string]) {
foreach ($tok in ($ur -split '\s+')) { $t = $tok.Trim().TrimStart('#'); if ($t) { $restrict += $(if ($script:calcRestrictMap[$t]) { $script:calcRestrictMap[$t] } else { $t }) } }
} elseif ($ur) {
foreach ($r in $ur) { $rr = "$r"; $restrict += $(if ($script:calcRestrictMap[$rr]) { $script:calcRestrictMap[$rr] } else { $rr }) }
}
$pres = $cf.presentationExpression
$orderExpr = $cf.orderExpression
}
$ci = "$indent`t"
X "$indent<CalculatedField>"
X "$ci<dcssch:dataPath>$(Esc-Xml $dataPath)</dcssch:dataPath>"
X "$ci<dcssch:expression>$(Esc-Xml $expression)</dcssch:expression>"
if ($title) { Emit-MLText -tag 'dcssch:title' -text $title -indent $ci -xsiType 'v8:LocalStringType' }
if ($restrict.Count -gt 0) {
X "$ci<dcssch:useRestriction>"
foreach ($r in @('field','condition','group','order')) { if ($restrict -contains $r) { X "$ci`t<dcssch:$r>true</dcssch:$r>" } }
X "$ci</dcssch:useRestriction>"
}
if ($pres) { X "$ci<dcssch:presentationExpression>$(Esc-Xml "$pres")</dcssch:presentationExpression>" }
if ($orderExpr) {
$oeList = if ($orderExpr -is [System.Collections.IList]) { $orderExpr } else { @($orderExpr) }
foreach ($oe in $oeList) {
if ($oe -is [string]) { $exprV = $oe; $oType = 'Asc'; $auto = 'false' }
else { $exprV = "$($oe.expression)"; $oType = if ($oe.orderType) { "$($oe.orderType)" } else { 'Asc' }; $auto = if ($oe.autoOrder) { 'true' } else { 'false' } }
X "$ci<dcssch:orderExpression>"
X "$ci`t<expression xmlns=`"$($script:dcsCommonNs)`">$(Esc-Xml $exprV)</expression>"
X "$ci`t<orderType xmlns=`"$($script:dcsCommonNs)`">$oType</orderType>"
X "$ci`t<autoOrder xmlns=`"$($script:dcsCommonNs)`">$auto</autoOrder>"
X "$ci</dcssch:orderExpression>"
}
}
if ($typeStr) { Emit-DLValueType -typeStr $typeStr -indent $ci }
X "$indent</CalculatedField>"
}
}
# --- 5. Type emitter ---
$script:formTypeSynonyms = New-Object System.Collections.Hashtable
@@ -5552,9 +5626,13 @@ function Emit-Attributes {
Emit-MLItems -val $fld.title -indent "$si`t`t"
X "$si`t</dcssch:title>"
}
# valueType поля набора (тип значения; вычисляемые/кастомные поля)
if ($fld.valueType) { Emit-DLValueType -typeStr "$($fld.valueType)" -indent "$si`t" }
X "$si</Field>"
}
}
# Вычисляемые поля DataSet (<CalculatedField>) — после Field*, до Parameter*.
Emit-CalcFields -calcFields $st.calculatedFields -indent $si
# Schema-параметры дин-списка (DataCompositionSchemaParameter) — после Field*, до MainTable.
Emit-DLParameters -params $st.parameters -indent $si
if ($st.mainTable) { X "$si<MainTable>$(Normalize-MetaTypeRef "$($st.mainTable)")</MainTable>" }