diff --git a/.claude/skills/skd-info/SKILL.md b/.claude/skills/skd-info/SKILL.md new file mode 100644 index 00000000..4cb4a713 --- /dev/null +++ b/.claude/skills/skd-info/SKILL.md @@ -0,0 +1,182 @@ +--- +name: skd-info +description: Анализ структуры схемы компоновки данных 1С (СКД) — наборы, поля, параметры, варианты +argument-hint: [-Mode overview|query|fields|params|variant] [-Name ] +allowed-tools: + - Bash + - Read + - Glob +--- + +# /skd-info — Анализ схемы компоновки данных + +Читает Template.xml схемы компоновки данных (СКД) и выводит компактную сводку. Заменяет необходимость читать тысячи строк XML. + +## Использование + +``` +/skd-info +/skd-info -Mode query -Name НаборДанных1 +``` + +## Параметры + +| Параметр | Обязательный | По умолчанию | Описание | +|--------------|:------------:|--------------|---------------------------------------------------| +| TemplatePath | да | — | Путь к Template.xml или каталогу макета | +| Mode | нет | `overview` | Режим: `overview`, `query`, `fields`, `params`, `variant` | +| Name | нет | — | Имя набора (query/fields) или варианта (variant) | +| Batch | нет | `0` | Номер пакета запроса (0 = все). Только для query | +| Limit | нет | `150` | Макс. строк вывода (защита от переполнения) | +| Offset | нет | `0` | Пропустить N строк (для пагинации) | +| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) | + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude\skills\skd-info\scripts\skd-info.ps1 -TemplatePath "<путь>" +``` + +С указанием режима: +```powershell +... -Mode query -Name НоменклатураСЦенами +... -Mode query -Name ДанныеТ13 -Batch 3 +... -Mode fields +... -Mode fields -Name НаборДанных1 +... -Mode params +... -Mode variant -Name Основной +... -Mode variant -Name 1 +``` + +Для большого вывода используй `-OutFile`: +```powershell +... -Mode query -Name ДанныеТ13 -OutFile test-tmp\query.txt +``` +Затем прочитай результат через Read tool. + +## Режимы + +### overview (по умолчанию) — оглавление + +Всегда 20-50 строк. Показывает "карту" схемы для выбора дальнейших действий: + +``` +=== DCS: Template.xml (361 lines) === + +Sources: ИсточникДанных1 (Local) + +Datasets: + [Query] НоменклатураСЦенами 7 fields, query 40 lines +Links: (none) +Calculated: УИД +Totals: Цена=Максимум(Цена) +Templates: Макет1(ТипЦен/Header) +Params: (none) + +Variants: + [1] НоменклатураИЦены "Номенклатура и цены" Table 3 filters + [2] НоменклатураБезЦен "Номенклатура без цен" Group(detail) 2 filters +``` + +Для DataSetUnion — дерево: +``` +Datasets: + [Union] РасчетНалога 35 fields + ├─ [Query] ДанныеПоСреднегодовой query 120 lines + └─ [Query] ДанныеПоКадастровой query 85 lines + [Object] ДопДанные objectName=Таблица 4 fields +``` + +### query — текст запроса + +Извлекает raw-текст запроса с деэкранированием XML (`&`→`&`, `>`→`>`). Для пакетных запросов — оглавление батчей: + +``` +=== Query: ДанныеТ13 (334 lines, 10 batches) === + Batch 1: lines 1-8 → ПОМЕСТИТЬ Представления_Периоды + Batch 2: lines 9-26 → ПОМЕСТИТЬ Представления_СотрудникиОрганизации + ... +--- Batch 1 --- +ВЫБРАТЬ + ДАТАВРЕМЯ(1, 1, 1) КАК Период +ПОМЕСТИТЬ Представления_Периоды +... +``` + +Фильтр по номеру батча: `-Batch 3` покажет только 3-й пакет. + +### fields — таблица полей + +``` +=== Fields: НоменклатураСЦенами (7) === + dataPath title role restrict format + Номенклатура - - - - + Номенклатура.Артикул "Артикул" - - - + Цена - - - ЧГ=0 +--- calculated --- + УИД = БухгалтерскиеОтчеты.ПолучитьУИДСсылкиСтрокой(Номенклатура) restrict:cond,grp,ord +--- totals --- + Цена = Максимум(Цена) + ПравоИнтерактивное = Максимум(ПравоИнтерактивное) [group:ОбъектМетаданных] +``` + +### params — параметры схемы + +``` +=== Parameters (16) === + Name Type Default Visible Expression + Период StandardPeriod LastMonth yes - + НачалоПериода DateTime - hidden &Период.ДатаНачала + Организация CatalogRef.Организации null yes - +``` + +### variant — структура варианта + +``` +=== Variant [1]: НоменклатураИЦены "Номенклатура и цены" === + +Structure: + Table "Таблица" + ├── Columns: [ТипЦен Items] + │ Selection: Auto, Цена + └── Rows: [Номенклатура Items] + Selection: Номенклатура, УИД, Auto + +Filter: + [ ] Номенклатура InHierarchy [user] + [ ] ТипЦен Equal + [x] ВАрхиве = false "Исключая скрытые товары" + +DataParams: КлючВарианта="НоменклатураИЦены" +Output: style=ЧерноБелый groups=Separately totalsH=None totalsV=None +``` + +## Разрешение пути + +- Прямой путь: `path/to/Template.xml` +- Каталог макета: `path/to/ИмяМакета/` → авто-резолв в `Ext/Template.xml` + +## Что не выводится + +- XML namespace-декларации +- Обёртки v8:item/v8:lang/v8:content (извлекаем чистый текст) +- userSettingID (GUID-ы пользовательских настроек) +- Дефолтные periodAdditionBegin/End = 0001-01-01 +- viewMode + +## Когда использовать + +- **Перед анализом отчёта**: overview для понимания структуры +- **Отладка данных**: query для просмотра текста запроса +- **Модификация полей**: fields для полного списка с ролями +- **Программный вызов**: params для списка параметров +- **Изменение вывода**: variant для структуры группировок и фильтров + +## Защита от переполнения + +Вывод ограничен 150 строками по умолчанию. При превышении: +``` +[TRUNCATED] Shown 150 of 400 lines. Use -Offset 150 to continue. +``` + +Используйте `-Offset N` и `-Limit N` для постраничного просмотра. diff --git a/.claude/skills/skd-info/scripts/skd-info.ps1 b/.claude/skills/skd-info/scripts/skd-info.ps1 new file mode 100644 index 00000000..39a6a0e6 --- /dev/null +++ b/.claude/skills/skd-info/scripts/skd-info.ps1 @@ -0,0 +1,961 @@ +param( + [Parameter(Mandatory=$true)] + [string]$TemplatePath, + [ValidateSet("overview", "query", "fields", "params", "variant")] + [string]$Mode = "overview", + [string]$Name, + [int]$Batch = 0, + [int]$Limit = 150, + [int]$Offset = 0, + [string]$OutFile +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- Resolve path --- + +if (-not $TemplatePath.EndsWith(".xml")) { + $candidate = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml" + if (Test-Path $candidate) { + $TemplatePath = $candidate + } +} + +if (-not (Test-Path $TemplatePath)) { + Write-Error "File not found: $TemplatePath" + exit 1 +} + +$resolvedPath = (Resolve-Path $TemplatePath).Path + +# --- Load XML --- + +$xmlDoc = New-Object System.Xml.XmlDocument +$xmlDoc.PreserveWhitespace = $false +$xmlDoc.Load($resolvedPath) + +$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) +$ns.AddNamespace("s", "http://v8.1c.ru/8.1/data-composition-system/schema") +$ns.AddNamespace("dcscom", "http://v8.1c.ru/8.1/data-composition-system/common") +$ns.AddNamespace("dcscor", "http://v8.1c.ru/8.1/data-composition-system/core") +$ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings") +$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") + +$root = $xmlDoc.DocumentElement + +# --- Helpers --- + +function Get-MLText($node) { + if (-not $node) { return "" } + $content = $node.SelectSingleNode("v8:item/v8:content", $ns) + if ($content) { return $content.InnerText } + $text = $node.InnerText.Trim() + if ($text) { return $text } + return "" +} + +function Unescape-Xml([string]$text) { + if (-not $text) { return $text } + $text = $text.Replace("&", "&") + $text = $text.Replace(">", ">") + $text = $text.Replace("<", "<") + $text = $text.Replace(""", '"') + $text = $text.Replace("'", "'") + return $text +} + +function Get-CompactType($valueTypeNode) { + if (-not $valueTypeNode) { return "" } + $types = @() + foreach ($t in $valueTypeNode.SelectNodes("v8:Type", $ns)) { + $raw = $t.InnerText + switch -Wildcard ($raw) { + "xs:string" { $types += "String" } + "xs:decimal" { $types += "Number" } + "xs:boolean" { $types += "Boolean" } + "xs:dateTime" { $types += "DateTime" } + "v8:StandardPeriod" { $types += "StandardPeriod" } + "v8:StandardBeginningDate" { $types += "StandardBeginningDate" } + "v8:AccountType" { $types += "AccountType" } + "v8:Null" { $types += "Null" } + default { + # Strip namespace prefixes like d4p1: cfg: + $clean = $raw -replace '^[a-zA-Z0-9]+:', '' + $types += $clean + } + } + } + if ($types.Count -eq 0) { return "" } + return ($types -join " | ") +} + +function Get-DataSetType($dsNode) { + $xsiType = $dsNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($xsiType -like "*DataSetQuery*") { return "Query" } + if ($xsiType -like "*DataSetObject*") { return "Object" } + if ($xsiType -like "*DataSetUnion*") { return "Union" } + return "Unknown" +} + +function Get-FieldCount($dsNode) { + return $dsNode.SelectNodes("s:field", $ns).Count +} + +function Get-QueryLineCount($dsNode) { + $queryNode = $dsNode.SelectSingleNode("s:query", $ns) + if (-not $queryNode) { return 0 } + $text = $queryNode.InnerText + return ($text -split "`n").Count +} + +function Get-StructureItemType($itemNode) { + $xsiType = $itemNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($xsiType -like "*StructureItemGroup*") { return "Group" } + if ($xsiType -like "*StructureItemTable*") { return "Table" } + if ($xsiType -like "*StructureItemChart*") { return "Chart" } + return "Unknown" +} + +function Get-GroupFields($itemNode) { + $fields = @() + foreach ($gi in $itemNode.SelectNodes("dcsset:groupItems/dcsset:item", $ns)) { + $fieldNode = $gi.SelectSingleNode("dcsset:field", $ns) + $groupType = $gi.SelectSingleNode("dcsset:groupType", $ns) + if ($fieldNode) { + $f = $fieldNode.InnerText + $gt = if ($groupType) { $groupType.InnerText } else { "" } + if ($gt -and $gt -ne "Items") { $f += "($gt)" } + $fields += $f + } + } + return $fields +} + +function Get-SelectionFields($itemNode) { + $fields = @() + foreach ($si in $itemNode.SelectNodes("dcsset:selection/dcsset:item", $ns)) { + $xsiType = $si.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($xsiType -like "*SelectedItemAuto*") { + $fields += "Auto" + } elseif ($xsiType -like "*SelectedItemField*") { + $f = $si.SelectSingleNode("dcsset:field", $ns) + if ($f) { $fields += $f.InnerText } + } elseif ($xsiType -like "*SelectedItemFolder*") { + $fields += "Folder" + } + } + return $fields +} + +function Get-FilterSummary($settingsNode) { + $filters = @() + foreach ($fi in $settingsNode.SelectNodes("dcsset:filter/dcsset:item", $ns)) { + $xsiType = $fi.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + + if ($xsiType -like "*FilterItemGroup*") { + $groupType = $fi.SelectSingleNode("dcsset:groupType", $ns) + $gt = if ($groupType) { $groupType.InnerText } else { "And" } + $subCount = $fi.SelectNodes("dcsset:item", $ns).Count + $filters += "[Group:$gt $subCount items]" + continue + } + + $use = $fi.SelectSingleNode("dcsset:use", $ns) + $isActive = if ($use -and $use.InnerText -eq "false") { "[ ]" } else { "[x]" } + + $left = $fi.SelectSingleNode("dcsset:left", $ns) + $comp = $fi.SelectSingleNode("dcsset:comparisonType", $ns) + $right = $fi.SelectSingleNode("dcsset:right", $ns) + $pres = $fi.SelectSingleNode("dcsset:presentation", $ns) + $userSetting = $fi.SelectSingleNode("dcsset:userSettingID", $ns) + + $leftStr = if ($left) { $left.InnerText } else { "?" } + $compStr = if ($comp) { $comp.InnerText } else { "?" } + $rightStr = "" + if ($right) { + $rightStr = " $($right.InnerText)" + } + + $presStr = "" + if ($pres) { + $pt = Get-MLText $pres + if ($pt) { $presStr = " `"$pt`"" } + } + + $userStr = "" + if ($userSetting) { $userStr = " [user]" } + + $filters += "$isActive $leftStr $compStr$rightStr$presStr$userStr" + } + return $filters +} + +function Build-StructureTree { + param($itemNode, [string]$prefix, [bool]$isLast, [System.Collections.Generic.List[string]]$outLines) + + $itemType = Get-StructureItemType $itemNode + $nameNode = $itemNode.SelectSingleNode("dcsset:name", $ns) + $itemName = if ($nameNode) { $nameNode.InnerText } else { "" } + + $groupFields = Get-GroupFields $itemNode + $groupStr = if ($groupFields.Count -gt 0) { "[" + ($groupFields -join ", ") + "]" } else { "(detail)" } + + $selFields = Get-SelectionFields $itemNode + $selStr = if ($selFields.Count -gt 0) { "Selection: " + ($selFields -join ", ") } else { "" } + + $line = "" + switch ($itemType) { + "Group" { + $line = "$itemType $groupStr" + if ($itemName) { $line = "$itemType `"$itemName`" $groupStr" } + } + "Table" { + $line = "Table" + if ($itemName) { $line = "Table `"$itemName`"" } + } + "Chart" { + $line = "Chart" + if ($itemName) { $line = "Chart `"$itemName`"" } + } + } + + $outLines.Add("$prefix$line") + if ($selStr -and $itemType -eq "Group") { + $outLines.Add("$prefix $selStr") + } + + # For Table, show columns and rows + if ($itemType -eq "Table") { + $columns = $itemNode.SelectNodes("dcsset:column", $ns) + $rows = $itemNode.SelectNodes("dcsset:row", $ns) + + foreach ($col in $columns) { + $colGroup = Get-GroupFields $col + $colGroupStr = if ($colGroup.Count -gt 0) { "[" + ($colGroup -join ", ") + "]" } else { "(detail)" } + $colSel = Get-SelectionFields $col + $colSelStr = if ($colSel.Count -gt 0) { "Selection: " + ($colSel -join ", ") } else { "" } + $connC = if ($rows.Count -gt 0) { [string][char]0x251C + [string][char]0x2500 + [string][char]0x2500 } else { [string][char]0x2514 + [string][char]0x2500 + [string][char]0x2500 } + $contC = if ($rows.Count -gt 0) { [string][char]0x2502 + " " } else { " " } + $outLines.Add("$prefix$connC Columns: $colGroupStr") + if ($colSelStr) { $outLines.Add("$prefix$contC $colSelStr") } + } + + foreach ($row in $rows) { + $rowGroup = Get-GroupFields $row + $rowGroupStr = if ($rowGroup.Count -gt 0) { "[" + ($rowGroup -join ", ") + "]" } else { "(detail)" } + $rowSel = Get-SelectionFields $row + $rowSelStr = if ($rowSel.Count -gt 0) { "Selection: " + ($rowSel -join ", ") } else { "" } + $outLines.Add("$prefix" + [string][char]0x2514 + [string][char]0x2500 + [string][char]0x2500 + " Rows: $rowGroupStr") + if ($rowSelStr) { $outLines.Add("$prefix $rowSelStr") } + } + } + + # Recurse into nested structure items (for Group) + if ($itemType -eq "Group") { + $children = $itemNode.SelectNodes("dcsset:item", $ns) + for ($i = 0; $i -lt $children.Count; $i++) { + $child = $children[$i] + $childType = $child.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($childType -like "*StructureItem*") { + $last = ($i -eq $children.Count - 1) + $connector = if ($last) { [string][char]0x2514 + [string][char]0x2500 + " " } else { [string][char]0x251C + [string][char]0x2500 + " " } + $continuation = if ($last) { " " } else { [string][char]0x2502 + " " } + Build-StructureTree -itemNode $child -prefix "$prefix$continuation" -isLast $last -outLines $outLines + } + } + } +} + +# --- Output collector --- + +$lines = [System.Collections.Generic.List[string]]::new() + +# Determine template name from path +$pathParts = $resolvedPath -split '[/\\]' +$templateName = $resolvedPath +for ($i = $pathParts.Count - 1; $i -ge 0; $i--) { + if ($pathParts[$i] -eq "Ext" -and $i -ge 1) { + $templateName = $pathParts[$i - 1] + break + } +} + +$totalXmlLines = (Get-Content $resolvedPath).Count + +# ============================================================ +# MODE: overview +# ============================================================ +if ($Mode -eq "overview") { + + $lines.Add("=== DCS: $templateName ($totalXmlLines lines) ===") + $lines.Add("") + + # Sources + $sources = @() + foreach ($ds in $root.SelectNodes("s:dataSource", $ns)) { + $dsName = $ds.SelectSingleNode("s:name", $ns).InnerText + $dsType = $ds.SelectSingleNode("s:dataSourceType", $ns).InnerText + $sources += "$dsName ($dsType)" + } + $lines.Add("Sources: " + ($sources -join ", ")) + $lines.Add("") + + # Datasets (recursive for Union) + $lines.Add("Datasets:") + foreach ($ds in $root.SelectNodes("s:dataSet", $ns)) { + $dsType = Get-DataSetType $ds + $dsName = $ds.SelectSingleNode("s:name", $ns).InnerText + $fieldCount = Get-FieldCount $ds + + switch ($dsType) { + "Query" { + $queryLines = Get-QueryLineCount $ds + $lines.Add(" [Query] $dsName $fieldCount fields, query $queryLines lines") + } + "Object" { + $objName = $ds.SelectSingleNode("s:objectName", $ns) + $objStr = if ($objName) { " objectName=$($objName.InnerText)" } else { "" } + $lines.Add(" [Object] $dsName$objStr $fieldCount fields") + } + "Union" { + $lines.Add(" [Union] $dsName $fieldCount fields") + foreach ($subDs in $ds.SelectNodes("s:item", $ns)) { + $subType = Get-DataSetType $subDs + $subName = $subDs.SelectSingleNode("s:name", $ns) + $subNameStr = if ($subName) { $subName.InnerText } else { "?" } + $subFields = Get-FieldCount $subDs + switch ($subType) { + "Query" { + $subQueryLines = Get-QueryLineCount $subDs + $lines.Add(" " + [string][char]0x251C + [string][char]0x2500 + " [Query] $subNameStr $subFields fields, query $subQueryLines lines") + } + "Object" { + $subObjName = $subDs.SelectSingleNode("s:objectName", $ns) + $subObjStr = if ($subObjName) { " objectName=$($subObjName.InnerText)" } else { "" } + $lines.Add(" " + [string][char]0x251C + [string][char]0x2500 + " [Object] $subNameStr$subObjStr $subFields fields") + } + default { + $lines.Add(" " + [string][char]0x251C + [string][char]0x2500 + " [$subType] $subNameStr $subFields fields") + } + } + } + } + } + } + + # Links + $links = $root.SelectNodes("s:dataSetLink", $ns) + if ($links.Count -gt 0) { + $linkStrs = @() + foreach ($lnk in $links) { + $srcDs = $lnk.SelectSingleNode("s:sourceDataSet", $ns).InnerText + $dstDs = $lnk.SelectSingleNode("s:destinationDataSet", $ns).InnerText + $srcExpr = $lnk.SelectSingleNode("s:sourceExpression", $ns).InnerText + $dstExpr = $lnk.SelectSingleNode("s:destinationExpression", $ns).InnerText + $paramNode = $lnk.SelectSingleNode("s:parameter", $ns) + $paramStr = if ($paramNode) { " param=$($paramNode.InnerText)" } else { "" } + $linkStrs += "$srcDs.$srcExpr -> $dstDs.$dstExpr$paramStr" + } + $lines.Add("Links: " + ($linkStrs -join "; ")) + } else { + $lines.Add("Links: (none)") + } + + # Calculated fields + $calcFields = $root.SelectNodes("s:calculatedField", $ns) + if ($calcFields.Count -gt 0) { + $calcNames = @() + foreach ($cf in $calcFields) { + $calcNames += $cf.SelectSingleNode("s:dataPath", $ns).InnerText + } + $lines.Add("Calculated: " + ($calcNames -join ", ")) + } + + # Totals + $totalFields = $root.SelectNodes("s:totalField", $ns) + if ($totalFields.Count -gt 0) { + $totalStrs = @() + foreach ($tf in $totalFields) { + $tfPath = $tf.SelectSingleNode("s:dataPath", $ns).InnerText + $tfExpr = $tf.SelectSingleNode("s:expression", $ns).InnerText + $tfGroup = $tf.SelectSingleNode("s:group", $ns) + $groupStr = "" + if ($tfGroup) { $groupStr = " [group:$($tfGroup.InnerText)]" } + $totalStrs += "$tfPath=$tfExpr$groupStr" + } + $lines.Add("Totals: " + ($totalStrs -join ", ")) + } + + # Templates + $templates = $root.SelectNodes("s:template", $ns) + $groupTemplates = $root.SelectNodes("s:groupTemplate", $ns) + if ($templates.Count -gt 0 -or $groupTemplates.Count -gt 0) { + $tplStrs = @() + foreach ($tpl in $templates) { + $tplName = $tpl.SelectSingleNode("s:name", $ns).InnerText + $tplStrs += $tplName + } + foreach ($gt in $groupTemplates) { + $gtField = $gt.SelectSingleNode("s:groupField", $ns).InnerText + $gtType = $gt.SelectSingleNode("s:templateType", $ns).InnerText + $gtTpl = $gt.SelectSingleNode("s:template", $ns).InnerText + $tplStrs += "$gtTpl($gtField/$gtType)" + } + $lines.Add("Templates: " + ($tplStrs -join ", ")) + } + + # Parameters + $params = $root.SelectNodes("s:parameter", $ns) + if ($params.Count -gt 0) { + $paramStrs = @() + foreach ($p in $params) { + $pName = $p.SelectSingleNode("s:name", $ns).InnerText + $paramStrs += $pName + } + $lines.Add("Params ($($params.Count)): " + ($paramStrs -join ", ")) + } else { + $lines.Add("Params: (none)") + } + + $lines.Add("") + + # Variants + $variants = $root.SelectNodes("s:settingsVariant", $ns) + if ($variants.Count -gt 0) { + $lines.Add("Variants:") + $varIdx = 0 + foreach ($v in $variants) { + $varIdx++ + $vName = $v.SelectSingleNode("dcsset:name", $ns).InnerText + $vPres = $v.SelectSingleNode("dcsset:presentation", $ns) + $vPresStr = "" + if ($vPres) { + $pt = Get-MLText $vPres + if ($pt) { $vPresStr = " `"$pt`"" } + } + + $settings = $v.SelectSingleNode("dcsset:settings", $ns) + $structItems = @() + if ($settings) { + foreach ($si in $settings.SelectNodes("dcsset:item", $ns)) { + $siType = Get-StructureItemType $si + $groupFields = Get-GroupFields $si + $groupStr = if ($groupFields.Count -gt 0) { "(" + ($groupFields -join ",") + ")" } else { "(detail)" } + $structItems += "$siType$groupStr" + } + } + $structStr = if ($structItems.Count -gt 0) { " " + ($structItems -join ", ") } else { "" } + + $filterCount = 0 + if ($settings) { + $filterCount = $settings.SelectNodes("dcsset:filter/dcsset:item", $ns).Count + } + $filterStr = if ($filterCount -gt 0) { " $filterCount filters" } else { "" } + + $lines.Add(" [$varIdx] $vName$vPresStr$structStr$filterStr") + } + } +} + +# ============================================================ +# MODE: query +# ============================================================ +elseif ($Mode -eq "query") { + + # Find dataset + $dataSets = $root.SelectNodes("s:dataSet", $ns) + $targetDs = $null + + if ($Name) { + # Search by name in top-level and nested + foreach ($ds in $dataSets) { + $dsNameNode = $ds.SelectSingleNode("s:name", $ns) + if ($dsNameNode -and $dsNameNode.InnerText -eq $Name) { $targetDs = $ds; break } + # Search in Union items + foreach ($subDs in $ds.SelectNodes("s:item", $ns)) { + $subNameNode = $subDs.SelectSingleNode("s:name", $ns) + if ($subNameNode -and $subNameNode.InnerText -eq $Name) { $targetDs = $subDs; break } + } + if ($targetDs) { break } + } + if (-not $targetDs) { + Write-Error "Dataset '$Name' not found" + exit 1 + } + } else { + # Take first Query dataset + foreach ($ds in $dataSets) { + $dsType = Get-DataSetType $ds + if ($dsType -eq "Query") { $targetDs = $ds; break } + if ($dsType -eq "Union") { + foreach ($subDs in $ds.SelectNodes("s:item", $ns)) { + if ((Get-DataSetType $subDs) -eq "Query") { $targetDs = $subDs; break } + } + if ($targetDs) { break } + } + } + if (-not $targetDs) { + Write-Error "No Query dataset found" + exit 1 + } + } + + $queryNode = $targetDs.SelectSingleNode("s:query", $ns) + if (-not $queryNode) { + Write-Error "Dataset has no query element" + exit 1 + } + + $rawQuery = Unescape-Xml $queryNode.InnerText + $dsNameStr = $targetDs.SelectSingleNode("s:name", $ns).InnerText + + # Split into batches + $batches = @() + $batchTexts = $rawQuery -split ';\s*\r?\n\s*/{16,}\s*\r?\n' + foreach ($bt in $batchTexts) { + $trimmed = $bt.Trim() + if ($trimmed) { $batches += $trimmed } + } + + $totalQueryLines = ($rawQuery -split "`n").Count + + if ($batches.Count -le 1) { + # Single query + $lines.Add("=== Query: $dsNameStr ($totalQueryLines lines) ===") + $lines.Add("") + foreach ($ql in ($rawQuery.Trim() -split "`n")) { + $lines.Add($ql.TrimEnd()) + } + } else { + $lines.Add("=== Query: $dsNameStr ($totalQueryLines lines, $($batches.Count) batches) ===") + + if ($Batch -eq 0) { + # Show TOC + $lineNum = 1 + for ($bi = 0; $bi -lt $batches.Count; $bi++) { + $batchLines = ($batches[$bi] -split "`n") + $endLine = $lineNum + $batchLines.Count - 1 + # Detect ПОМЕСТИТЬ target + $target = "" + foreach ($bl in $batchLines) { + if ($bl -match '^\s*(?:ПОМЕСТИТЬ|INTO)\s+(\S+)') { + $target = [char]0x2192 + " " + $Matches[1] + break + } + } + $lines.Add(" Batch $($bi + 1): lines $lineNum-$endLine $target") + $lineNum = $endLine + 3 # +separator + } + $lines.Add("") + + # Show all batches + for ($bi = 0; $bi -lt $batches.Count; $bi++) { + $lines.Add("--- Batch $($bi + 1) ---") + foreach ($ql in ($batches[$bi] -split "`n")) { + $lines.Add($ql.TrimEnd()) + } + $lines.Add("") + } + } else { + # Show specific batch + if ($Batch -gt $batches.Count) { + Write-Error "Batch $Batch not found (total: $($batches.Count))" + exit 1 + } + $lines.Add("") + $lines.Add("--- Batch $Batch ---") + foreach ($ql in ($batches[$Batch - 1] -split "`n")) { + $lines.Add($ql.TrimEnd()) + } + } + } +} + +# ============================================================ +# MODE: fields +# ============================================================ +elseif ($Mode -eq "fields") { + + $dataSets = $root.SelectNodes("s:dataSet", $ns) + + function Show-DataSetFields($dsNode) { + $dsType = Get-DataSetType $dsNode + $dsNameStr = $dsNode.SelectSingleNode("s:name", $ns).InnerText + $fields = $dsNode.SelectNodes("s:field", $ns) + + $lines.Add("=== Fields: $dsNameStr [$dsType] ($($fields.Count)) ===") + $lines.Add(" dataPath title role restrict format") + + foreach ($f in $fields) { + $dp = $f.SelectSingleNode("s:dataPath", $ns) + $dpStr = if ($dp) { $dp.InnerText } else { "-" } + + $titleNode = $f.SelectSingleNode("s:title", $ns) + $titleStr = if ($titleNode) { Get-MLText $titleNode } else { "" } + if (-not $titleStr) { $titleStr = "-" } + + # Role + $role = $f.SelectSingleNode("s:role", $ns) + $roleStr = "-" + if ($role) { + $roleParts = @() + foreach ($child in $role.ChildNodes) { + if ($child.NodeType -eq "Element" -and $child.InnerText -eq "true") { + $roleParts += $child.LocalName + } + } + if ($roleParts.Count -gt 0) { $roleStr = $roleParts -join "," } + } + + # UseRestriction + $restrict = $f.SelectSingleNode("s:useRestriction", $ns) + $restrictStr = "-" + if ($restrict) { + $restrictParts = @() + foreach ($child in $restrict.ChildNodes) { + if ($child.NodeType -eq "Element" -and $child.InnerText -eq "true") { + $restrictParts += $child.LocalName.Substring(0, [Math]::Min(4, $child.LocalName.Length)) + } + } + if ($restrictParts.Count -gt 0) { $restrictStr = $restrictParts -join "," } + } + + # Appearance format + $formatStr = "-" + $appearance = $f.SelectSingleNode("s:appearance", $ns) + if ($appearance) { + foreach ($appItem in $appearance.SelectNodes("dcscor:item", $ns)) { + $paramNode = $appItem.SelectSingleNode("dcscor:parameter", $ns) + $valNode = $appItem.SelectSingleNode("dcscor:value", $ns) + if ($paramNode -and ($paramNode.InnerText -eq "Формат" -or $paramNode.InnerText -eq "Format") -and $valNode) { + $formatStr = $valNode.InnerText + } + } + } + + # presentationExpression + $presExpr = $f.SelectSingleNode("s:presentationExpression", $ns) + $presStr = "" + if ($presExpr) { $presStr = " presExpr" } + + $dpPad = $dpStr.PadRight(35) + $titlePad = $titleStr.PadRight(22) + $rolePad = $roleStr.PadRight(10) + $restrictPad = $restrictStr.PadRight(12) + + $lines.Add(" $dpPad $titlePad $rolePad $restrictPad $formatStr$presStr") + } + } + + if ($Name) { + $found = $false + foreach ($ds in $dataSets) { + $dsNameNode = $ds.SelectSingleNode("s:name", $ns) + if ($dsNameNode -and $dsNameNode.InnerText -eq $Name) { + Show-DataSetFields $ds + $found = $true + break + } + foreach ($subDs in $ds.SelectNodes("s:item", $ns)) { + $subNameNode = $subDs.SelectSingleNode("s:name", $ns) + if ($subNameNode -and $subNameNode.InnerText -eq $Name) { + Show-DataSetFields $subDs + $found = $true + break + } + } + if ($found) { break } + } + if (-not $found) { + Write-Error "Dataset '$Name' not found" + exit 1 + } + } else { + # Show all datasets + $first = $true + foreach ($ds in $dataSets) { + if (-not $first) { $lines.Add("") } + $first = $false + Show-DataSetFields $ds + + $dsType = Get-DataSetType $ds + if ($dsType -eq "Union") { + foreach ($subDs in $ds.SelectNodes("s:item", $ns)) { + $lines.Add("") + Show-DataSetFields $subDs + } + } + } + } + + # Calculated fields + $calcFields = $root.SelectNodes("s:calculatedField", $ns) + if ($calcFields.Count -gt 0) { + $lines.Add("") + $lines.Add("--- calculated ---") + foreach ($cf in $calcFields) { + $cfPath = $cf.SelectSingleNode("s:dataPath", $ns).InnerText + $cfExpr = $cf.SelectSingleNode("s:expression", $ns).InnerText + $cfRestrict = $cf.SelectSingleNode("s:useRestriction", $ns) + $restrictStr = "" + if ($cfRestrict) { + $parts = @() + foreach ($child in $cfRestrict.ChildNodes) { + if ($child.NodeType -eq "Element" -and $child.InnerText -eq "true") { + $parts += $child.LocalName.Substring(0, [Math]::Min(4, $child.LocalName.Length)) + } + } + if ($parts.Count -gt 0) { $restrictStr = " restrict:" + ($parts -join ",") } + } + $lines.Add(" $cfPath = $cfExpr$restrictStr") + } + } + + # Total fields + $totalFields = $root.SelectNodes("s:totalField", $ns) + if ($totalFields.Count -gt 0) { + $lines.Add("") + $lines.Add("--- totals ---") + foreach ($tf in $totalFields) { + $tfPath = $tf.SelectSingleNode("s:dataPath", $ns).InnerText + $tfExpr = $tf.SelectSingleNode("s:expression", $ns).InnerText + $tfGroup = $tf.SelectSingleNode("s:group", $ns) + $groupStr = "" + if ($tfGroup) { $groupStr = " [group:$($tfGroup.InnerText)]" } + $lines.Add(" $tfPath = $tfExpr$groupStr") + } + } +} + +# ============================================================ +# MODE: params +# ============================================================ +elseif ($Mode -eq "params") { + + $params = $root.SelectNodes("s:parameter", $ns) + $lines.Add("=== Parameters ($($params.Count)) ===") + $lines.Add(" Name Type Default Visible Expression") + + foreach ($p in $params) { + $pName = $p.SelectSingleNode("s:name", $ns).InnerText + $pType = Get-CompactType $p.SelectSingleNode("s:valueType", $ns) + if (-not $pType) { $pType = "-" } + + # Default value + $valNode = $p.SelectSingleNode("s:value", $ns) + $valStr = "-" + if ($valNode) { + $nilAttr = $valNode.GetAttribute("nil", "http://www.w3.org/2001/XMLSchema-instance") + if ($nilAttr -eq "true") { + $valStr = "null" + } else { + $raw = $valNode.InnerText.Trim() + if ($raw -eq "0001-01-01T00:00:00") { + $valStr = "-" + } elseif ($raw) { + # Check for StandardPeriod variant + $variant = $valNode.SelectSingleNode("v8:variant", $ns) + if ($variant) { + $valStr = $variant.InnerText + } else { + $valStr = $raw + if ($valStr.Length -gt 15) { $valStr = $valStr.Substring(0, 12) + "..." } + } + } + } + } + + # Visibility + $useRestrict = $p.SelectSingleNode("s:useRestriction", $ns) + $visStr = "yes" + if ($useRestrict -and $useRestrict.InnerText -eq "true") { $visStr = "hidden" } + if ($useRestrict -and $useRestrict.InnerText -eq "false") { $visStr = "yes" } + + # Expression + $exprNode = $p.SelectSingleNode("s:expression", $ns) + $exprStr = "-" + if ($exprNode -and $exprNode.InnerText.Trim()) { + $exprStr = Unescape-Xml $exprNode.InnerText.Trim() + } + + # availableAsField + $availField = $p.SelectSingleNode("s:availableAsField", $ns) + $availStr = "" + if ($availField -and $availField.InnerText -eq "false") { $availStr = " [noField]" } + + $namePad = $pName.PadRight(33) + $typePad = $pType.PadRight(22) + $valPad = $valStr.PadRight(16) + $visPad = $visStr.PadRight(8) + + $lines.Add(" $namePad $typePad $valPad $visPad $exprStr$availStr") + } +} + +# ============================================================ +# MODE: variant +# ============================================================ +elseif ($Mode -eq "variant") { + + $variants = $root.SelectNodes("s:settingsVariant", $ns) + + $targetVariant = $null + if ($Name) { + # Try by name + $varIdx = 0 + foreach ($v in $variants) { + $varIdx++ + $vName = $v.SelectSingleNode("dcsset:name", $ns).InnerText + if ($vName -eq $Name -or "$varIdx" -eq $Name) { + $targetVariant = $v + $matchIdx = $varIdx + break + } + } + if (-not $targetVariant) { + Write-Error "Variant '$Name' not found" + exit 1 + } + } else { + # Take first + $targetVariant = $variants[0] + $matchIdx = 1 + if (-not $targetVariant) { + Write-Error "No variants found" + exit 1 + } + } + + $vName = $targetVariant.SelectSingleNode("dcsset:name", $ns).InnerText + $vPres = $targetVariant.SelectSingleNode("dcsset:presentation", $ns) + $vPresStr = "" + if ($vPres) { + $pt = Get-MLText $vPres + if ($pt) { $vPresStr = " `"$pt`"" } + } + + $lines.Add("=== Variant [$matchIdx]: $vName$vPresStr ===") + + $settings = $targetVariant.SelectSingleNode("dcsset:settings", $ns) + if (-not $settings) { + $lines.Add(" (empty settings)") + } else { + # Selection at settings level + $topSel = Get-SelectionFields $settings + if ($topSel.Count -gt 0) { + $lines.Add("") + $lines.Add("Selection: " + ($topSel -join ", ")) + } + + # Structure + $structItems = $settings.SelectNodes("dcsset:item", $ns) + $hasStruct = $false + foreach ($si in $structItems) { + $siXsiType = $si.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($siXsiType -like "*StructureItem*") { $hasStruct = $true; break } + } + + if ($hasStruct) { + $lines.Add("") + $lines.Add("Structure:") + foreach ($si in $structItems) { + $siXsiType = $si.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance") + if ($siXsiType -like "*StructureItem*") { + Build-StructureTree -itemNode $si -prefix " " -isLast $false -outLines $lines + } + } + } + + # Filter + $filters = Get-FilterSummary $settings + if ($filters.Count -gt 0) { + $lines.Add("") + $lines.Add("Filter:") + foreach ($f in $filters) { + $lines.Add(" $f") + } + } + + # Data parameters + $dataParams = $settings.SelectNodes("dcsset:dataParameters/dcsset:item", $ns) + if ($dataParams.Count -gt 0) { + $dpStrs = @() + foreach ($dp in $dataParams) { + $dpParam = $dp.SelectSingleNode("dcscor:parameter", $ns) + $dpVal = $dp.SelectSingleNode("dcscor:value", $ns) + if ($dpParam -and $dpVal) { + $dpStrs += "$($dpParam.InnerText)=`"$($dpVal.InnerText)`"" + } + } + if ($dpStrs.Count -gt 0) { + $lines.Add("") + $lines.Add("DataParams: " + ($dpStrs -join ", ")) + } + } + + # Output parameters + $outParams = $settings.SelectNodes("dcsset:outputParameters/dcscor:item", $ns) + if ($outParams.Count -gt 0) { + $opStrs = @() + foreach ($op in $outParams) { + $opParam = $op.SelectSingleNode("dcscor:parameter", $ns) + $opVal = $op.SelectSingleNode("dcscor:value", $ns) + if ($opParam -and $opVal) { + $paramName = $opParam.InnerText + $paramVal = $opVal.InnerText + # Shorten known long names + switch ($paramName) { + "МакетОформления" { $opStrs += "style=$paramVal" } + "РасположениеПолейГруппировки" { $opStrs += "groups=$paramVal" } + "ГоризонтальноеРасположениеОбщихИтогов" { $opStrs += "totalsH=$paramVal" } + "ВертикальноеРасположениеОбщихИтогов" { $opStrs += "totalsV=$paramVal" } + "ВыводитьЗаголовок" { $opStrs += "header=$paramVal" } + "ВыводитьОтбор" { $opStrs += "filter=$paramVal" } + "ВыводитьПараметрыДанных" { $opStrs += "dataParams=$paramVal" } + "РасположениеРеквизитов" { $opStrs += "attrs=$paramVal" } + default { $opStrs += "$paramName=$paramVal" } + } + } + } + if ($opStrs.Count -gt 0) { + $lines.Add("") + $lines.Add("Output: " + ($opStrs -join " ")) + } + } + } +} + +# --- Output --- + +$result = $lines.ToArray() +$totalLines = $result.Count + +# OutFile +if ($OutFile) { + $utf8Bom = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllLines((Join-Path (Get-Location) $OutFile), $result, $utf8Bom) + Write-Host "Written $totalLines lines to $OutFile" + exit 0 +} + +# Pagination +if ($Offset -gt 0) { + if ($Offset -ge $totalLines) { + Write-Host "[INFO] Offset $Offset exceeds total lines ($totalLines). Nothing to show." + exit 0 + } + $result = $result[$Offset..($totalLines - 1)] +} + +if ($result.Count -gt $Limit) { + $shown = $result[0..($Limit - 1)] + foreach ($l in $shown) { Write-Host $l } + Write-Host "" + Write-Host "[TRUNCATED] Shown $Limit of $totalLines lines. Use -Offset $($Offset + $Limit) to continue." +} else { + foreach ($l in $result) { Write-Host $l } +} diff --git a/docs/1c-dcs-spec.md b/docs/1c-dcs-spec.md index 6c07c0a3..c97b7242 100644 --- a/docs/1c-dcs-spec.md +++ b/docs/1c-dcs-spec.md @@ -446,7 +446,28 @@ DataCompositionSchema |---|---|---| | `dataPath` | да | Путь к полю | | `expression` | да | Агрегатная функция: `Сумма(...)`, `Количество(...)`, `Максимум(...)`, `Минимум(...)`, `Среднее(...)` | -| `group` | нет | Для какой группировки считать итоги | +| `group` | нет | Имя группировки, для которой считать итоги. Без `group` — для всех группировок | + +### Разные формулы для разных группировок + +Одно поле может иметь несколько записей `totalField` с разными формулами для разных группировок: + +```xml + + + ПравоИнтерактивное + Максимум(ПравоИнтерактивное) + ОбъектМетаданных + + + + ПравоИнтерактивное + Максимум(ПравоОтчета) + Отчет + +``` + +Это позволяет вычислять ресурс по-разному в зависимости от контекста группировки. ---