Add templates mode for DCS template binding analysis

- Map (without -Name): field bindings with non-trivial expression
  detection, group bindings organized by group name (groupTemplate,
  groupHeaderTemplate, groupFooterTemplate, fieldTemplate)
- Detail (-Name <group|field>): template content with rows, cells
  (static text and parameters), non-trivial expressions only
- Trivial filter: Field=Field and Field=Представление(Field) hidden
- Updated overview: shows binding type counts, templates hint in Next

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-02-10 19:56:56 +03:00
parent d72dd1678e
commit c306d18648
3 changed files with 357 additions and 10 deletions
+5 -2
View File
@@ -1,7 +1,7 @@
---
name: skd-info
description: Анализ структуры схемы компоновки данных 1С (СКД) — наборы, поля, параметры, варианты
argument-hint: <TemplatePath> [-Mode overview|query|fields|links|calculated|resources|params|variant|trace] [-Name <dataset|variant|field>]
argument-hint: <TemplatePath> [-Mode overview|query|fields|links|calculated|resources|params|variant|templates|trace] [-Name <dataset|variant|field|group>]
allowed-tools:
- Bash
- Read
@@ -18,7 +18,7 @@ allowed-tools:
|----------|----------|
| `TemplatePath` | Путь к Template.xml или каталогу макета (авто-резолв в `Ext/Template.xml`) |
| `Mode` | Режим анализа (по умолчанию `overview`) |
| `Name` | Имя набора (query), поля (fields/calculated/resources/trace) или варианта (variant) |
| `Name` | Имя набора (query), поля (fields/calculated/resources/trace), варианта (variant) или группировки/поля (templates) |
| `Batch` | Номер пакета запроса, 0 = все (только query) |
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
@@ -36,6 +36,8 @@ powershell.exe -NoProfile -File .claude\skills\skd-info\scripts\skd-info.ps1 -Te
... -Mode resources -Name СуммаНалога
... -Mode trace -Name "Коэффициент Ки"
... -Mode variant -Name 1
... -Mode templates
... -Mode templates -Name ВидНалоговойБазы
```
## Режимы
@@ -50,6 +52,7 @@ powershell.exe -NoProfile -File .claude\skills\skd-info\scripts\skd-info.ps1 -Te
| `resources` | Карта: имена ресурсов (`*` = групповые формулы) | Формулы агрегации по группировкам |
| `params` | Таблица параметров: тип, значение, видимость | — |
| `variant` | Список вариантов | Структура группировок + фильтры + вывод |
| `templates` | Карта привязок шаблонов (field/group) | Содержимое шаблона: строки, ячейки, выражения |
| `trace` | — | Полная цепочка: набор → вычисление → ресурс |
Паттерн: без `-Name` — карта/индекс, с `-Name` — деталь конкретного элемента.
@@ -174,6 +174,48 @@ DataParams: КлючВарианта="НоменклатураИЦены"
Output: style=ЧерноБелый groups=Separately totalsH=None totalsV=None
```
## templates — привязки шаблонов вывода
Три типа привязок: `fieldTemplate` (к полю), `groupTemplate` (к группировке, Header/Footer), `groupHeaderTemplate` (заголовок группы).
Без `-Name` — карта привязок:
```
=== Templates (70 defined: 49 field, 37 group) ===
Field bindings (49): (all trivial)
ОстаточнаяСтоимостьНа0101, ОстаточнаяСтоимостьНа0102, ...
Group bindings (37):
ВидНалоговойБазы
Header -> Макет3 (1 rows, 1 params)
СреднегодоваяСтоимость2019
Footer -> Макет50 (1 rows) spacer
GroupHeader -> Макет40 (3 rows)
```
С `-Name <группировка|поле>` — содержимое шаблонов:
```
=== Templates: СреднегодоваяСтоимость2019 ===
Footer -> Макет50 [1 rows, 1 cells]:
Row 1: (empty)
GroupHeader -> Макет40 [3 rows, 78 cells]:
Row 1: "№ п/п" | "###Группировки1###" | "Инв. номер" | ...
Row 2: "01.01" | "01.02" | ... | "31.12"
Row 3: "1" | "2" | ... | "26"
```
Для field-привязок:
```
=== Field template: ОстаточнаяСтоимостьНа0101 -> Макет4 ===
[1 rows, 1 cells]
Row 1: {ОстаточнаяСтоимостьНа0101}
(all params trivial)
```
**Тривиальность выражений**: `Поле = Поле` и `Поле = Представление(Поле)` считаются тривиальными и НЕ выводятся. Показываются только нетривиальные — когда выражение содержит другое поле, вызов метода, пустую строку и т.д.
## trace — трассировка поля от заголовка до запроса
Ищет поле по dataPath ИЛИ заголовку (включая подстроку) и показывает полную цепочку происхождения за один вызов:
+310 -8
View File
@@ -1,7 +1,7 @@
param(
[Parameter(Mandatory=$true)]
[string]$TemplatePath,
[ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace")]
[ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace", "templates")]
[string]$Mode = "overview",
[string]$Name,
[int]$Batch = 0,
@@ -44,6 +44,7 @@ $ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("dcsat", "http://v8.1c.ru/8.1/data-composition-system/area-template")
$root = $xmlDoc.DocumentElement
@@ -391,14 +392,19 @@ if ($Mode -eq "overview") {
}
}
# Templates — count only
$templates = $root.SelectNodes("s:template", $ns)
$groupTemplates = $root.SelectNodes("s:groupTemplate", $ns)
if ($templates.Count -gt 0 -or $groupTemplates.Count -gt 0) {
# Templates — count with binding types
$tplDefs = $root.SelectNodes("s:template", $ns)
$fieldTpls = $root.SelectNodes("s:fieldTemplate", $ns)
$groupTpls = $root.SelectNodes("s:groupTemplate", $ns)
$groupHeaderTpls = $root.SelectNodes("s:groupHeaderTemplate", $ns)
$groupFooterTpls = $root.SelectNodes("s:groupFooterTemplate", $ns)
$totalBindings = $fieldTpls.Count + $groupTpls.Count + $groupHeaderTpls.Count + $groupFooterTpls.Count
if ($tplDefs.Count -gt 0) {
$parts = @()
if ($templates.Count -gt 0) { $parts += "$($templates.Count) templates" }
if ($groupTemplates.Count -gt 0) { $parts += "$($groupTemplates.Count) group bindings" }
$lines.Add("Templates: " + ($parts -join ", "))
if ($fieldTpls.Count -gt 0) { $parts += "$($fieldTpls.Count) field" }
$grpCount = $groupTpls.Count + $groupHeaderTpls.Count + $groupFooterTpls.Count
if ($grpCount -gt 0) { $parts += "$grpCount group" }
$lines.Add("Templates: $($tplDefs.Count) defined ($($parts -join ', ') bindings)")
}
# Parameters — split visible/hidden
@@ -519,6 +525,9 @@ if ($Mode -eq "overview") {
} elseif ($variants.Count -gt 1) {
$hints += "-Mode variant -Name <N> variant structure (1..$($variants.Count))"
}
if ($tplDefs.Count -gt 0) {
$hints += "-Mode templates template bindings and expressions"
}
$hints += "-Mode trace -Name <f> trace field origin (by name or title)"
$lines.Add("Next:")
foreach ($h in $hints) { $lines.Add(" $h") }
@@ -1456,6 +1465,299 @@ elseif ($Mode -eq "trace") {
}
}
# ============================================================
# MODE: templates
# ============================================================
elseif ($Mode -eq "templates") {
# --- Helper: check if expression is trivial ---
function Is-TrivialExpr([string]$paramName, [string]$expr) {
$e = $expr.Trim()
$n = $paramName.Trim()
if ($e -eq $n) { return $true }
if ($e -eq "Представление($n)") { return $true }
return $false
}
# --- Helper: parse template content (rows/cells) ---
function Get-TemplateContent([System.Xml.XmlNode]$tplNode) {
$innerT = $tplNode.SelectSingleNode("s:template", $ns)
if (-not $innerT) { return @{ rows = 0; cells = @(); params = @(); nonTrivial = @() } }
$rows = $innerT.SelectNodes("dcsat:item", $ns)
$rowCount = $rows.Count
$cellData = [System.Collections.ArrayList]::new()
$rowIdx = 0
foreach ($row in $rows) {
$rowIdx++
$rowCells = [System.Collections.ArrayList]::new()
foreach ($cell in $row.SelectNodes("dcsat:tableCell", $ns)) {
$field = $cell.SelectSingleNode("dcsat:item", $ns)
if (-not $field) {
[void]$rowCells.Add("(empty)")
continue
}
$val = $field.SelectSingleNode("dcsat:value", $ns)
if (-not $val) {
[void]$rowCells.Add("(empty)")
continue
}
$xsiType = $val.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
if ($xsiType -like "*LocalStringType*") {
$text = Get-MLText $val
if ($text) { [void]$rowCells.Add("`"$text`"") }
else { [void]$rowCells.Add("(empty)") }
} elseif ($xsiType -like "*Parameter*") {
[void]$rowCells.Add("{$($val.InnerText)}")
} else {
[void]$rowCells.Add("(?)")
}
}
[void]$cellData.Add(@{ row = $rowIdx; cells = $rowCells.ToArray() })
}
# Parameters
$paramNodes = $tplNode.SelectNodes("s:parameter", $ns)
$paramList = [System.Collections.ArrayList]::new()
$nonTrivialList = [System.Collections.ArrayList]::new()
foreach ($p in $paramNodes) {
$pn = $p.SelectSingleNode("dcsat:name", $ns)
$pe = $p.SelectSingleNode("dcsat:expression", $ns)
if ($pn -and $pe) {
$pName = $pn.InnerText
$pExpr = $pe.InnerText
[void]$paramList.Add(@{ name = $pName; expression = $pExpr })
if (-not (Is-TrivialExpr $pName $pExpr)) {
[void]$nonTrivialList.Add(@{ name = $pName; expression = $pExpr })
}
}
}
return @{
rows = $rowCount
cells = $cellData.ToArray()
params = $paramList.ToArray()
nonTrivial = $nonTrivialList.ToArray()
}
}
# --- Build template name -> node index ---
$tplIndex = @{}
foreach ($t in $root.SelectNodes("s:template", $ns)) {
$tn = $t.SelectSingleNode("s:name", $ns)
if ($tn) { $tplIndex[$tn.InnerText] = $t }
}
# --- Parse bindings ---
# Group bindings: groupTemplate + groupHeaderTemplate
$groupBindings = [ordered]@{} # groupName -> @{ bindings = @(@{type; tplName; tplNode}) }
foreach ($gt in $root.SelectNodes("s:groupTemplate", $ns)) {
$gn = $gt.SelectSingleNode("s:groupName", $ns)
$gf = $gt.SelectSingleNode("s:groupField", $ns)
$gnStr = if ($gn) { $gn.InnerText } elseif ($gf) { $gf.InnerText } else { "(default)" }
$tt = $gt.SelectSingleNode("s:templateType", $ns)
$tn = $gt.SelectSingleNode("s:template", $ns)
$ttStr = if ($tt) { $tt.InnerText } else { "-" }
$tnStr = if ($tn) { $tn.InnerText } else { "-" }
if (-not $groupBindings.Contains($gnStr)) {
$groupBindings[$gnStr] = [System.Collections.ArrayList]::new()
}
[void]$groupBindings[$gnStr].Add(@{ type = $ttStr; tplName = $tnStr })
}
foreach ($ght in $root.SelectNodes("s:groupHeaderTemplate", $ns)) {
$gn = $ght.SelectSingleNode("s:groupName", $ns)
$gf = $ght.SelectSingleNode("s:groupField", $ns)
$gnStr = if ($gn) { $gn.InnerText } elseif ($gf) { $gf.InnerText } else { "(default)" }
$tt = $ght.SelectSingleNode("s:templateType", $ns)
$tn = $ght.SelectSingleNode("s:template", $ns)
$ttStr = if ($tt) { "GroupHeader" } else { "GroupHeader" }
$tnStr = if ($tn) { $tn.InnerText } else { "-" }
if (-not $groupBindings.Contains($gnStr)) {
$groupBindings[$gnStr] = [System.Collections.ArrayList]::new()
}
[void]$groupBindings[$gnStr].Add(@{ type = $ttStr; tplName = $tnStr })
}
foreach ($gft in $root.SelectNodes("s:groupFooterTemplate", $ns)) {
$gn = $gft.SelectSingleNode("s:groupName", $ns)
$gf = $gft.SelectSingleNode("s:groupField", $ns)
$gnStr = if ($gn) { $gn.InnerText } elseif ($gf) { $gf.InnerText } else { "(default)" }
$tn = $gft.SelectSingleNode("s:template", $ns)
$tnStr = if ($tn) { $tn.InnerText } else { "-" }
if (-not $groupBindings.Contains($gnStr)) {
$groupBindings[$gnStr] = [System.Collections.ArrayList]::new()
}
[void]$groupBindings[$gnStr].Add(@{ type = "GroupFooter"; tplName = $tnStr })
}
# Field bindings: fieldTemplate
$fieldBindings = [ordered]@{} # fieldName -> tplName
$fieldNonTrivial = [System.Collections.ArrayList]::new()
foreach ($ft in $root.SelectNodes("s:fieldTemplate", $ns)) {
$fn = $ft.SelectSingleNode("s:field", $ns)
$tn = $ft.SelectSingleNode("s:template", $ns)
if ($fn -and $tn) {
$fName = $fn.InnerText
$tName = $tn.InnerText
$fieldBindings[$fName] = $tName
# Check params for non-trivial expressions
if ($tplIndex.ContainsKey($tName)) {
$content = Get-TemplateContent $tplIndex[$tName]
foreach ($nt in $content.nonTrivial) {
[void]$fieldNonTrivial.Add(@{ field = $fName; template = $tName; name = $nt.name; expression = $nt.expression })
}
}
}
}
$totalTpl = $tplIndex.Count
$fieldCount = $fieldBindings.Count
$groupBindCount = 0
foreach ($k in $groupBindings.Keys) { $groupBindCount += $groupBindings[$k].Count }
if (-not $Name) {
# --- MAP mode ---
$lines.Add("=== Templates ($totalTpl defined: $fieldCount field, $groupBindCount group) ===")
# Field bindings
if ($fieldBindings.Count -gt 0) {
$lines.Add("")
if ($fieldNonTrivial.Count -eq 0) {
$fieldNames = @($fieldBindings.Keys)
if ($fieldNames.Count -le 8) {
$lines.Add("Field bindings ($fieldCount): $($fieldNames -join ', ') (all trivial)")
} else {
$lines.Add("Field bindings ($fieldCount): (all trivial)")
$lines.Add(" $($fieldNames[0..7] -join ', '), ...")
}
} else {
$trivialCount = $fieldBindings.Count - ($fieldNonTrivial | Select-Object -ExpandProperty field -Unique).Count
$lines.Add("Field bindings ($fieldCount, $trivialCount trivial):")
foreach ($nt in $fieldNonTrivial) {
$lines.Add(" $($nt.field): $($nt.name) = $($nt.expression)")
}
}
}
# Group bindings
if ($groupBindings.Count -gt 0) {
$lines.Add("")
$lines.Add("Group bindings ($groupBindCount):")
foreach ($gName in $groupBindings.Keys) {
$bindings = $groupBindings[$gName]
$parts = @()
foreach ($b in $bindings) {
$info = "$($b.type) -> $($b.tplName)"
if ($tplIndex.ContainsKey($b.tplName)) {
$content = Get-TemplateContent $tplIndex[$b.tplName]
# Check if any cell has content
$hasContent = $false
foreach ($r in $content.cells) {
foreach ($c in $r.cells) {
if ($c -ne "(empty)") { $hasContent = $true; break }
}
if ($hasContent) { break }
}
$info += " ($($content.rows) rows"
if ($content.params.Count -gt 0) { $info += ", $($content.params.Count) params" }
$info += ")"
if (-not $hasContent -and $content.params.Count -eq 0) {
$info += " spacer"
}
if ($content.nonTrivial.Count -gt 0) {
$ntNames = ($content.nonTrivial | ForEach-Object { $_.name }) -join ', '
$info += " *$ntNames"
}
}
$parts += $info
}
$lines.Add(" $gName")
foreach ($p in $parts) {
$lines.Add(" $p")
}
}
}
if ($fieldBindings.Count -gt 0 -or $groupBindings.Count -gt 0) {
$lines.Add("")
$lines.Add("Use -Name <group|field> for template details.")
}
} else {
# --- DETAIL mode ---
$found = $false
# Check group bindings first
if ($groupBindings.Contains($Name)) {
$found = $true
$bindings = $groupBindings[$Name]
$lines.Add("=== Templates: $Name ===")
foreach ($b in $bindings) {
$lines.Add("")
$tName = $b.tplName
if (-not $tplIndex.ContainsKey($tName)) {
$lines.Add("$($b.type) -> $tName (template not found)")
continue
}
$content = Get-TemplateContent $tplIndex[$tName]
$cellCount = 0
foreach ($r in $content.cells) { $cellCount += $r.cells.Count }
$lines.Add("$($b.type) -> $tName [$($content.rows) rows, $cellCount cells]:")
foreach ($r in $content.cells) {
$cellStr = $r.cells -join " | "
$lines.Add(" Row $($r.row): $cellStr")
}
if ($content.nonTrivial.Count -gt 0) {
$lines.Add(" Params:")
foreach ($nt in $content.nonTrivial) {
$lines.Add(" $($nt.name) = $($nt.expression)")
}
}
}
}
# Check field bindings
if ($fieldBindings.Contains($Name)) {
if ($found) { $lines.Add("") }
$found = $true
$tName = $fieldBindings[$Name]
$lines.Add("=== Field template: $Name -> $tName ===")
if ($tplIndex.ContainsKey($tName)) {
$content = Get-TemplateContent $tplIndex[$tName]
$cellCount = 0
foreach ($r in $content.cells) { $cellCount += $r.cells.Count }
$lines.Add("[$($content.rows) rows, $cellCount cells]")
foreach ($r in $content.cells) {
$cellStr = $r.cells -join " | "
$lines.Add(" Row $($r.row): $cellStr")
}
if ($content.nonTrivial.Count -gt 0) {
$lines.Add(" Non-trivial params:")
foreach ($nt in $content.nonTrivial) {
$lines.Add(" $($nt.name) = $($nt.expression)")
}
} else {
$lines.Add(" (all params trivial)")
}
}
}
if (-not $found) {
Write-Error "Group or field '$Name' not found in template bindings"
exit 1
}
}
}
# --- Output ---
$result = $lines.ToArray()