mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-30 16:36:56 +03:00
feat(skd-decompile): слои 4-5 — calculatedFields, totalFields, parameters
- calculatedFields: shorthand с [title], type, expression и #restrict-флагами; object form при appearance или multilang title. - totalFields: детект Func(name) и Func(expr) → shorthand "name: Func"/"name: Func(expr)"; object form при привязке к группе. - parameters: - shorthand с [title], type, value, @-флагами; - распознавание StandardPeriod variants → значение в shorthand; - @valueList, @hidden флаги; - availableValues с presentation; - object form для availableValues/multilang/composite type/expression. - autoDates-сворачивание: для каждого StandardPeriod-параметра ищем пару dependent с expression `&P.ДатаНачала`/`&P.ДатаОкончания` (распознаём по expression, не по имени) и сворачиваем в @autoDates на родителе. - decimal-тип всегда эмитится с явными (D,F) — JSON читаемее. - useRestriction суппрессим в параметрах (auto-generated для @hidden). Bit-perfect round-trip 7468→7468 байт на синтетике (3 calc + 2 total + 5 параметров включая @autoDates). Реальный ERP «АнализИзмененийЛичныхДанныхСотрудников» (1035 строк) — 0 warnings при декомпиляции. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
765e1d8885
commit
be69bc231c
@@ -1,4 +1,4 @@
|
|||||||
# skd-decompile v0.2 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
# skd-decompile v0.3 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -143,10 +143,10 @@ function Get-OneTypeShorthand {
|
|||||||
$sign = Get-Text $qualNumber "v8:AllowedSign"
|
$sign = Get-Text $qualNumber "v8:AllowedSign"
|
||||||
$signSuf = ''
|
$signSuf = ''
|
||||||
if ($sign -eq 'Nonnegative') { $signSuf = ',nonneg' }
|
if ($sign -eq 'Nonnegative') { $signSuf = ',nonneg' }
|
||||||
# defaults: 10,2,Any → "decimal"
|
# Always explicit (D,F) — JSON readable, no surprise from default folding
|
||||||
if ($d -eq 10 -and $f -eq 2 -and -not $signSuf) { return 'decimal' }
|
|
||||||
if ($f -eq 0) { return "decimal($d$signSuf)" }
|
if ($f -eq 0) { return "decimal($d$signSuf)" }
|
||||||
return "decimal($d,$f$($signSuf -replace '^,',''))".Replace('decimal(', 'decimal(').Replace(',,',',')
|
if ($signSuf) { return "decimal($d,$f$signSuf)" }
|
||||||
|
return "decimal($d,$f)"
|
||||||
}
|
}
|
||||||
return 'decimal'
|
return 'decimal'
|
||||||
}
|
}
|
||||||
@@ -294,6 +294,204 @@ function Build-Field {
|
|||||||
return $obj
|
return $obj
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Build calculatedField → shorthand string or object form
|
||||||
|
function Build-CalcField {
|
||||||
|
param($cfNode, [string]$loc)
|
||||||
|
$dataPath = Get-Text $cfNode "r:dataPath"
|
||||||
|
$expression = Get-Text $cfNode "r:expression"
|
||||||
|
$titleNode = $cfNode.SelectSingleNode("r:title", $ns)
|
||||||
|
$title = Get-MLText $titleNode
|
||||||
|
$valueTypeNode = $cfNode.SelectSingleNode("r:valueType", $ns)
|
||||||
|
$typeShort = Get-ValueTypeShorthand $valueTypeNode
|
||||||
|
$restrictTokens = Get-RestrictionTokens $cfNode.SelectSingleNode("r:useRestriction", $ns)
|
||||||
|
$appNode = $cfNode.SelectSingleNode("r:appearance", $ns)
|
||||||
|
$appearance = Get-AppearanceDict $appNode
|
||||||
|
|
||||||
|
# multilingual title (non-ru) → object form
|
||||||
|
$titleNeedsObject = ($title -is [System.Collections.IDictionary]) -or ($typeShort -is [array])
|
||||||
|
$needsObject = $appearance -or $titleNeedsObject
|
||||||
|
|
||||||
|
if (-not $needsObject) {
|
||||||
|
# shorthand: "Name [Title]: type = expression #restrict"
|
||||||
|
$s = $dataPath
|
||||||
|
if ($title) { $s += " [$title]" }
|
||||||
|
if ($typeShort) { $s += ": $typeShort" }
|
||||||
|
if ($expression) { $s += " = $expression" }
|
||||||
|
if ($restrictTokens) { $s += ' ' + ($restrictTokens -join ' ') }
|
||||||
|
return $s
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj = [ordered]@{ name = $dataPath }
|
||||||
|
if ($title) { $obj['title'] = $title }
|
||||||
|
if ($typeShort) { $obj['type'] = $typeShort }
|
||||||
|
if ($expression) { $obj['expression'] = $expression }
|
||||||
|
if ($restrictTokens) { $obj['restrict'] = ($restrictTokens | ForEach-Object { $_ -replace '^#','' }) }
|
||||||
|
if ($appearance) { $obj['appearance'] = $appearance }
|
||||||
|
return $obj
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build totalField → shorthand or object form
|
||||||
|
function Build-TotalField {
|
||||||
|
param($tfNode)
|
||||||
|
$dataPath = Get-Text $tfNode "r:dataPath"
|
||||||
|
$expression = Get-Text $tfNode "r:expression"
|
||||||
|
# Detect Func(<dataPath>) → shorthand "name: Func"
|
||||||
|
if ($expression -match '^(\w+)\(([^)]*)\)$') {
|
||||||
|
$func = $matches[1]
|
||||||
|
$inner = $matches[2].Trim()
|
||||||
|
if ($inner -eq $dataPath) {
|
||||||
|
return "$dataPath`: $func"
|
||||||
|
}
|
||||||
|
# "name: Func(expr)" form — also a valid shorthand
|
||||||
|
return "$dataPath`: $func($inner)"
|
||||||
|
}
|
||||||
|
# group attachment via groupItem — Ring 2 / object form
|
||||||
|
$groupNodes = $tfNode.SelectNodes("r:group", $ns)
|
||||||
|
$obj = [ordered]@{ dataPath = $dataPath; expression = $expression }
|
||||||
|
if ($groupNodes -and $groupNodes.Count -gt 0) {
|
||||||
|
$groups = @()
|
||||||
|
foreach ($g in $groupNodes) { $groups += $g.InnerText }
|
||||||
|
$obj['group'] = $groups
|
||||||
|
}
|
||||||
|
return $obj
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect StandardPeriod variant from <value> node
|
||||||
|
function Get-StandardPeriodVariant {
|
||||||
|
param($valueNode)
|
||||||
|
if (-not $valueNode) { return $null }
|
||||||
|
$variant = Get-Text $valueNode "v8:variant"
|
||||||
|
if ($variant) { return $variant }
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Build parameter → shorthand or object form
|
||||||
|
function Build-Parameter {
|
||||||
|
param($pNode, [string]$loc)
|
||||||
|
$name = Get-Text $pNode "r:name"
|
||||||
|
$titleNode = $pNode.SelectSingleNode("r:title", $ns)
|
||||||
|
$title = Get-MLText $titleNode
|
||||||
|
$valueTypeNode = $pNode.SelectSingleNode("r:valueType", $ns)
|
||||||
|
$typeShort = Get-ValueTypeShorthand $valueTypeNode
|
||||||
|
|
||||||
|
# value
|
||||||
|
$valueNode = $pNode.SelectSingleNode("r:value", $ns)
|
||||||
|
$valueDisplay = $null
|
||||||
|
$valueIsNil = $false
|
||||||
|
if ($valueNode) {
|
||||||
|
$nil = $valueNode.GetAttribute("nil", $NS_XSI)
|
||||||
|
if ($nil -eq 'true') { $valueIsNil = $true }
|
||||||
|
else {
|
||||||
|
$vType = Get-LocalXsiType $valueNode
|
||||||
|
if ($vType -eq 'StandardPeriod') {
|
||||||
|
$variant = Get-Text $valueNode "v8:variant"
|
||||||
|
if ($variant -and $variant -ne 'Custom') { $valueDisplay = $variant }
|
||||||
|
# Custom with explicit dates → object form (handled below via needsObject)
|
||||||
|
} elseif ($vType -eq 'DesignTimeValue') {
|
||||||
|
$valueDisplay = $valueNode.InnerText
|
||||||
|
} elseif ($vType -eq 'LocalStringType') {
|
||||||
|
$valueDisplay = Get-MLText $valueNode
|
||||||
|
} else {
|
||||||
|
$txt = $valueNode.InnerText
|
||||||
|
if ($txt) { $valueDisplay = $txt }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$valueListAllowed = (Get-Text $pNode "r:valueListAllowed") -eq 'true'
|
||||||
|
$availableAsField = Get-Text $pNode "r:availableAsField"
|
||||||
|
$hidden = $availableAsField -eq 'false'
|
||||||
|
$denyIncomplete = (Get-Text $pNode "r:denyIncompleteValues") -eq 'true'
|
||||||
|
$useAttr = Get-Text $pNode "r:use"
|
||||||
|
$useRestriction = (Get-Text $pNode "r:useRestriction") -eq 'true'
|
||||||
|
$expression = Get-Text $pNode "r:expression"
|
||||||
|
|
||||||
|
# availableValues
|
||||||
|
$avNodes = $pNode.SelectNodes("r:availableValue", $ns)
|
||||||
|
$availableValues = @()
|
||||||
|
foreach ($av in $avNodes) {
|
||||||
|
$avValNode = $av.SelectSingleNode("r:value", $ns)
|
||||||
|
$avPresNode = $av.SelectSingleNode("r:presentation", $ns)
|
||||||
|
$avEntry = [ordered]@{}
|
||||||
|
if ($avValNode) { $avEntry['value'] = $avValNode.InnerText }
|
||||||
|
if ($avPresNode) { $avEntry['presentation'] = Get-MLText $avPresNode }
|
||||||
|
$availableValues += $avEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
$flags = @()
|
||||||
|
|
||||||
|
$result = [ordered]@{
|
||||||
|
name = $name
|
||||||
|
title = $title
|
||||||
|
typeShort = $typeShort
|
||||||
|
valueDisplay = $valueDisplay
|
||||||
|
valueIsNil = $valueIsNil
|
||||||
|
valueListAllowed = $valueListAllowed
|
||||||
|
hidden = $hidden
|
||||||
|
denyIncomplete = $denyIncomplete
|
||||||
|
useAttr = $useAttr
|
||||||
|
useRestriction = $useRestriction
|
||||||
|
expression = $expression
|
||||||
|
availableValues = $availableValues
|
||||||
|
}
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
# Render parameter (after autoDates folding) → shorthand or object form
|
||||||
|
function Render-Parameter {
|
||||||
|
param($p)
|
||||||
|
$name = $p.name
|
||||||
|
$title = $p.title
|
||||||
|
$typeShort = $p.typeShort
|
||||||
|
$valueDisplay = $p.valueDisplay
|
||||||
|
$valueIsNil = $p.valueIsNil
|
||||||
|
$flags = @()
|
||||||
|
if ($p.autoDates) { $flags += '@autoDates' }
|
||||||
|
if ($p.valueListAllowed) { $flags += '@valueList' }
|
||||||
|
if ($p.hidden) { $flags += '@hidden' }
|
||||||
|
|
||||||
|
$titleNeedsObject = ($title -is [System.Collections.IDictionary])
|
||||||
|
$typeIsArray = ($typeShort -is [array])
|
||||||
|
$valueIsDict = ($valueDisplay -is [System.Collections.IDictionary])
|
||||||
|
|
||||||
|
# Object form needed if: availableValues, multilingual title, composite type,
|
||||||
|
# explicit denyIncomplete/use without @autoDates, useRestriction without autoDates, expression set
|
||||||
|
$needsObject = $false
|
||||||
|
if ($p.availableValues -and $p.availableValues.Count -gt 0) { $needsObject = $true }
|
||||||
|
if ($titleNeedsObject) { $needsObject = $true }
|
||||||
|
if ($typeIsArray) { $needsObject = $true }
|
||||||
|
if ($valueIsDict) { $needsObject = $true }
|
||||||
|
if (-not $p.autoDates) {
|
||||||
|
# @autoDates implies use=Always + denyIncomplete=true defaults — only object form if NOT autoDates
|
||||||
|
if ($p.denyIncomplete) { $needsObject = $true }
|
||||||
|
if ($p.useAttr) { $needsObject = $true }
|
||||||
|
}
|
||||||
|
# useRestriction is auto-generated by compile for @hidden params; ignore as object trigger
|
||||||
|
if ($p.expression) { $needsObject = $true }
|
||||||
|
|
||||||
|
if (-not $needsObject) {
|
||||||
|
$s = $name
|
||||||
|
if ($title) { $s += " [$title]" }
|
||||||
|
if ($typeShort) { $s += ": $typeShort" }
|
||||||
|
if (-not $valueIsNil -and $null -ne $valueDisplay -and $valueDisplay -ne '') { $s += " = $valueDisplay" }
|
||||||
|
if ($flags) { $s += ' ' + ($flags -join ' ') }
|
||||||
|
return $s
|
||||||
|
}
|
||||||
|
|
||||||
|
$obj = [ordered]@{ name = $name }
|
||||||
|
if ($title) { $obj['title'] = $title }
|
||||||
|
if ($typeShort) { $obj['type'] = $typeShort }
|
||||||
|
if (-not $valueIsNil -and $null -ne $valueDisplay -and $valueDisplay -ne '') { $obj['value'] = $valueDisplay }
|
||||||
|
if ($p.useAttr -and -not $p.autoDates) { $obj['use'] = $p.useAttr }
|
||||||
|
if ($p.denyIncomplete -and -not $p.autoDates) { $obj['denyIncompleteValues'] = $true }
|
||||||
|
if ($p.hidden) { $obj['hidden'] = $true }
|
||||||
|
if ($p.valueListAllowed) { $obj['valueListAllowed'] = $true }
|
||||||
|
if ($p.autoDates) { $obj['autoDates'] = $true }
|
||||||
|
if ($p.expression) { $obj['expression'] = $p.expression }
|
||||||
|
if ($p.availableValues -and $p.availableValues.Count -gt 0) { $obj['availableValues'] = $p.availableValues }
|
||||||
|
return $obj
|
||||||
|
}
|
||||||
|
|
||||||
# --- 4. dataSources ---
|
# --- 4. dataSources ---
|
||||||
|
|
||||||
$dataSources = @()
|
$dataSources = @()
|
||||||
@@ -357,11 +555,72 @@ foreach ($dsNode in $dsNodes) {
|
|||||||
$dataSets += $ds
|
$dataSets += $ds
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# --- 5b. calculatedFields ---
|
||||||
|
|
||||||
|
$calculatedFields = @()
|
||||||
|
$cfNodes = $root.SelectNodes("r:calculatedField", $ns)
|
||||||
|
$ci = 0
|
||||||
|
foreach ($cf in $cfNodes) {
|
||||||
|
$calculatedFields += (Build-CalcField -cfNode $cf -loc "calculatedField[$ci]")
|
||||||
|
$ci++
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- 5c. totalFields ---
|
||||||
|
|
||||||
|
$totalFields = @()
|
||||||
|
$tfNodes = $root.SelectNodes("r:totalField", $ns)
|
||||||
|
foreach ($tf in $tfNodes) { $totalFields += (Build-TotalField -tfNode $tf) }
|
||||||
|
|
||||||
|
# --- 5d. parameters with autoDates folding ---
|
||||||
|
|
||||||
|
$paramsRaw = @()
|
||||||
|
$pi = 0
|
||||||
|
$pNodes = $root.SelectNodes("r:parameter", $ns)
|
||||||
|
foreach ($p in $pNodes) {
|
||||||
|
$paramsRaw += (Build-Parameter -pNode $p -loc "parameter[$pi]")
|
||||||
|
$pi++
|
||||||
|
}
|
||||||
|
|
||||||
|
# Detect autoDates: for each StandardPeriod parameter P, look for two siblings with
|
||||||
|
# expression "&P.ДатаНачала" and "&P.ДатаОкончания". If both found, mark P with @autoDates
|
||||||
|
# and remove the companions.
|
||||||
|
$paramByName = @{}
|
||||||
|
foreach ($p in $paramsRaw) { $paramByName[$p.name] = $p }
|
||||||
|
|
||||||
|
$removedNames = @{}
|
||||||
|
foreach ($p in $paramsRaw) {
|
||||||
|
if ($p.typeShort -ne 'StandardPeriod') { continue }
|
||||||
|
$parentName = $p.name
|
||||||
|
$startExpr = '&' + $parentName + '.ДатаНачала'
|
||||||
|
$endExpr = '&' + $parentName + '.ДатаОкончания'
|
||||||
|
$startMatch = $null
|
||||||
|
$endMatch = $null
|
||||||
|
foreach ($q in $paramsRaw) {
|
||||||
|
if ($q.name -eq $parentName) { continue }
|
||||||
|
if ($q.expression -eq $startExpr) { $startMatch = $q.name }
|
||||||
|
elseif ($q.expression -eq $endExpr) { $endMatch = $q.name }
|
||||||
|
}
|
||||||
|
if ($startMatch -and $endMatch) {
|
||||||
|
$p['autoDates'] = $true
|
||||||
|
$removedNames[$startMatch] = $true
|
||||||
|
$removedNames[$endMatch] = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = @()
|
||||||
|
foreach ($p in $paramsRaw) {
|
||||||
|
if ($removedNames.ContainsKey($p.name)) { continue }
|
||||||
|
$parameters += (Render-Parameter -p $p)
|
||||||
|
}
|
||||||
|
|
||||||
# --- 6. Build top-level JSON object ---
|
# --- 6. Build top-level JSON object ---
|
||||||
|
|
||||||
$out = [ordered]@{}
|
$out = [ordered]@{}
|
||||||
if ($emitDataSources) { $out['dataSources'] = $dataSources }
|
if ($emitDataSources) { $out['dataSources'] = $dataSources }
|
||||||
$out['dataSets'] = $dataSets
|
$out['dataSets'] = $dataSets
|
||||||
|
if ($calculatedFields.Count -gt 0) { $out['calculatedFields'] = $calculatedFields }
|
||||||
|
if ($totalFields.Count -gt 0) { $out['totalFields'] = $totalFields }
|
||||||
|
if ($parameters.Count -gt 0) { $out['parameters'] = $parameters }
|
||||||
|
|
||||||
# --- 7. Serialize ---
|
# --- 7. Serialize ---
|
||||||
|
|
||||||
@@ -394,7 +653,7 @@ if ($OutputPath) {
|
|||||||
Write-Host "Warnings: $wPath ($($script:warnings.Count) issue(s))" -ForegroundColor Yellow
|
Write-Host "Warnings: $wPath ($($script:warnings.Count) issue(s))" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
[Console]::Error.WriteLine("Decompiled: dataSets=$($dataSets.Count), warnings=$($script:warnings.Count)")
|
[Console]::Error.WriteLine("Decompiled: dataSets=$($dataSets.Count), calc=$($calculatedFields.Count), totals=$($totalFields.Count), params=$($parameters.Count), warnings=$($script:warnings.Count)")
|
||||||
} else {
|
} else {
|
||||||
Write-Output $json
|
Write-Output $json
|
||||||
if ($script:warnings.Count -gt 0) {
|
if ($script:warnings.Count -gt 0) {
|
||||||
|
|||||||
Reference in New Issue
Block a user