diff --git a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 index b703243c..5668e942 100644 --- a/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 +++ b/.claude/skills/skd-decompile/scripts/skd-decompile.ps1 @@ -1,4 +1,4 @@ -# skd-decompile v0.3 — Decompile 1C DCS Template.xml to JSON DSL (draft) +# skd-decompile v0.4 — Decompile 1C DCS Template.xml to JSON DSL (draft) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -38,7 +38,7 @@ $NS_SCHEMA = "http://v8.1c.ru/8.1/data-composition-system/schema" $NS_COM = "http://v8.1c.ru/8.1/data-composition-system/common" $NS_COR = "http://v8.1c.ru/8.1/data-composition-system/core" $NS_SET = "http://v8.1c.ru/8.1/data-composition-system/settings" -$NS_AT = "http://v8.1c.ru/8.1/data-composition-system/areatemplate" +$NS_AT = "http://v8.1c.ru/8.1/data-composition-system/area-template" $NS_V8 = "http://v8.1c.ru/8.1/data/core" $NS_V8UI = "http://v8.1c.ru/8.1/data/ui" $NS_XS = "http://www.w3.org/2001/XMLSchema" @@ -492,6 +492,302 @@ function Render-Parameter { return $obj } +# --- 3b. Built-in style fingerprints --- + +# Fingerprints for built-in styles (header/data/subheader/total). +# Each is the set of "shape-defining" appearance items the style emits. +# Width/Height/merge flags are excluded — they're per-cell. +$script:builtinStyleFingerprints = @{ + 'header' = @{ + ЦветФона = 'd8p1:ReportHeaderBackColor' + ЦветГраницы = 'd8p1:ReportLineColor' + СтильГраницы = 'None|0' + 'СтильГраницы.Слева' = 'Solid|1' + 'СтильГраницы.Сверху' = 'Solid|1' + 'СтильГраницы.Справа' = 'Solid|1' + 'СтильГраницы.Снизу' = 'Solid|1' + Шрифт = 'Arial|10|false|false|false|false' + ГоризонтальноеПоложение = 'Center' + Размещение = 'Wrap' + } + 'data' = @{ + ЦветФона = 'd8p1:ReportGroup1BackColor' + ЦветГраницы = 'd8p1:ReportLineColor' + СтильГраницы = 'None|0' + 'СтильГраницы.Слева' = 'Solid|1' + 'СтильГраницы.Сверху' = 'Solid|1' + 'СтильГраницы.Справа' = 'Solid|1' + 'СтильГраницы.Снизу' = 'Solid|1' + Шрифт = 'Arial|10|false|false|false|false' + } + 'subheader' = @{ + ЦветГраницы = 'd8p1:ReportLineColor' + СтильГраницы = 'None|0' + 'СтильГраницы.Слева' = 'Solid|1' + 'СтильГраницы.Сверху' = 'Solid|1' + 'СтильГраницы.Справа' = 'Solid|1' + 'СтильГраницы.Снизу' = 'Solid|1' + Шрифт = 'Arial|10|false|false|false|false' + ГоризонтальноеПоложение = 'Center' + } + 'total' = @{ + ЦветГраницы = 'd8p1:ReportLineColor' + СтильГраницы = 'None|0' + 'СтильГраницы.Слева' = 'Solid|1' + 'СтильГраницы.Сверху' = 'Solid|1' + 'СтильГраницы.Справа' = 'Solid|1' + 'СтильГраницы.Снизу' = 'Solid|1' + Шрифт = 'Arial|10|false|false|false|false' + } +} + +# Normalize an node to a hashtable of "shape" keys (excluding +# per-cell items: widths, heights, merge flags, drilldown). Used for style matching. +function Get-AppearanceFingerprint { + param($appNode) + $fp = @{} + if (-not $appNode) { return $fp } + # Top-level dcscor:item children + foreach ($it in $appNode.SelectNodes("dcscor:item", $ns)) { + $pName = Get-Text $it "dcscor:parameter" + $val = $it.SelectSingleNode("dcscor:value", $ns) + if (-not $pName -or -not $val) { continue } + # Skip per-cell keys + if ($pName -in @('МинимальнаяШирина','МаксимальнаяШирина','МинимальнаяВысота','ОбъединятьПоВертикали','ОбъединятьПоГоризонтали','Расшифровка')) { continue } + $valType = Get-LocalXsiType $val + switch ($valType) { + 'Color' { $fp[$pName] = $val.InnerText } + 'Line' { + $w = $val.GetAttribute("width") + $styleNode = $val.SelectSingleNode("v8ui:style", $ns) + $lineStyle = if ($styleNode) { $styleNode.InnerText } else { '' } + $fp[$pName] = "$lineStyle|$w" + } + 'Font' { + $face = $val.GetAttribute("faceName") + $h = $val.GetAttribute("height") + $b = $val.GetAttribute("bold") + $i = $val.GetAttribute("italic") + $u = $val.GetAttribute("underline") + $s = $val.GetAttribute("strikeout") + $fp[$pName] = "$face|$h|$b|$i|$u|$s" + } + default { $fp[$pName] = $val.InnerText } + } + # Nested sub-items under СтильГраницы (left/top/right/bottom) + foreach ($sub in $it.SelectNodes("dcscor:item", $ns)) { + $subName = Get-Text $sub "dcscor:parameter" + $subVal = $sub.SelectSingleNode("dcscor:value", $ns) + if (-not $subName -or -not $subVal) { continue } + $subType = Get-LocalXsiType $subVal + if ($subType -eq 'Line') { + $w = $subVal.GetAttribute("width") + $styleNode = $subVal.SelectSingleNode("v8ui:style", $ns) + $lineStyle = if ($styleNode) { $styleNode.InnerText } else { '' } + $fp[$subName] = "$lineStyle|$w" + } + } + } + # Translate d8p1: colors to canonical "d8p1:Name" form (InnerText already contains prefix) + return $fp +} + +# Check if appearance fingerprint matches a built-in style. +# Returns style name or $null. +function Match-BuiltinStyle { + param($fp) + foreach ($styleName in $script:builtinStyleFingerprints.Keys) { + $expected = $script:builtinStyleFingerprints[$styleName] + $match = $true + # All expected keys must be present with matching value + foreach ($k in $expected.Keys) { + if (-not $fp.ContainsKey($k) -or $fp[$k] -ne $expected[$k]) { $match = $false; break } + } + if (-not $match) { continue } + # Must not have extra keys that aren't in the expected fingerprint + $extras = @($fp.Keys | Where-Object { -not $expected.ContainsKey($_) }) + if ($extras.Count -ne 0) { continue } + return $styleName + } + return $null +} + +# Extract per-cell width/minHeight/merge from appearance. +function Get-CellPerCellAttrs { + param($appNode) + $attrs = @{ width = $null; height = $null; mergeV = $false; mergeH = $false; drilldown = $null } + if (-not $appNode) { return $attrs } + foreach ($it in $appNode.SelectNodes("dcscor:item", $ns)) { + $pName = Get-Text $it "dcscor:parameter" + $val = $it.SelectSingleNode("dcscor:value", $ns) + if (-not $pName) { continue } + switch ($pName) { + 'МинимальнаяШирина' { if ($val) { $attrs.width = $val.InnerText } } + 'МинимальнаяВысота' { if ($val) { $attrs.height = $val.InnerText } } + 'ОбъединятьПоВертикали' { if ($val -and $val.InnerText -eq 'true') { $attrs.mergeV = $true } } + 'ОбъединятьПоГоризонтали' { if ($val -and $val.InnerText -eq 'true') { $attrs.mergeH = $true } } + 'Расшифровка' { + # value xsi:type=dcscor:Parameter pointing to Расшифровка_X + if ($val) { + $paramRef = $val.InnerText + if ($paramRef -match '^Расшифровка_(.+)$') { $attrs.drilldown = $matches[1] } + } + } + } + } + return $attrs +} + +# Extract cell content: string text, "{ParamName}", "|", ">", or $null +function Get-CellContent { + param($cellNode, $perCellAttrs) + # Check merge flags first — empty cells with these flags are "|" or ">" + if ($perCellAttrs.mergeV) { return '|' } + if ($perCellAttrs.mergeH) { return '>' } + + $item = $cellNode.SelectSingleNode("dcsat:item", $ns) + if (-not $item) { return $null } + $itemType = Get-LocalXsiType $item + $valNode = $item.SelectSingleNode("dcsat:value", $ns) + if (-not $valNode) { return $null } + $valType = Get-LocalXsiType $valNode + + if ($itemType -eq 'Field' -and $valType -eq 'Parameter') { + return '{' + $valNode.InnerText + '}' + } + if ($valType -eq 'LocalStringType') { + $text = Get-MLText $valNode + if ($text -is [System.Collections.IDictionary]) { + # multilang in template cell — keep as-is; emit via object form (Ring 2 candidate) + return $text + } + return $text + } + # Fallback: take inner text + return $valNode.InnerText +} + +# Build template parameter entry. Returns hashtable with `name` + `expression` (+ optional `drilldown`) +function Build-TemplateParameter { + param($pNode) + $pType = Get-LocalXsiType $pNode + $obj = [ordered]@{} + $obj['name'] = Get-Text $pNode "dcsat:name" + if ($pType -eq 'ExpressionAreaTemplateParameter') { + $obj['expression'] = Get-Text $pNode "dcsat:expression" + } elseif ($pType -eq 'DetailsAreaTemplateParameter') { + # Marker — handled by drilldown folding logic in Build-Template + $obj['__details__'] = $true + $obj['expression'] = Get-Text $pNode "dcsat:expression" + } + return $obj +} + +# Build template entry from