From 580124a9710adb38d594d7ffcaf7995005168ed3 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 9 Feb 2026 11:09:42 +0300 Subject: [PATCH] Add form-info skill for compact analysis of 1C managed forms Parses Form.xml (up to 28K lines) and outputs a compact summary (40-180 lines): element tree with group orientation, data bindings, events, visibility flags, attributes with types, commands, and parameters. Co-Authored-By: Claude Opus 4.6 --- .claude/skills/form-info/SKILL.md | 178 ++++++ .../skills/form-info/scripts/form-info.ps1 | 511 ++++++++++++++++++ README.md | 3 + docs/form-guide.md | 141 +++++ 4 files changed, 833 insertions(+) create mode 100644 .claude/skills/form-info/SKILL.md create mode 100644 .claude/skills/form-info/scripts/form-info.ps1 create mode 100644 docs/form-guide.md diff --git a/.claude/skills/form-info/SKILL.md b/.claude/skills/form-info/SKILL.md new file mode 100644 index 00000000..eed56b7e --- /dev/null +++ b/.claude/skills/form-info/SKILL.md @@ -0,0 +1,178 @@ +--- +name: form-info +description: Анализ структуры управляемой формы 1С (Form.xml) — элементы, реквизиты, команды, события +argument-hint: +allowed-tools: + - Bash + - Read + - Glob +--- + +# /form-info — Компактная сводка формы + +Читает Form.xml управляемой формы и выводит компактную сводку: дерево элементов, реквизиты с типами, команды, события. Заменяет необходимость читать тысячи строк XML. + +## Использование + +``` +/form-info +``` + +## Параметры + +| Параметр | Обязательный | По умолчанию | Описание | +|-----------|:------------:|--------------|---------------------------------------------| +| FormPath | да | — | Путь к файлу Form.xml | +| Limit | нет | `150` | Макс. строк вывода (защита от переполнения) | +| Offset | нет | `0` | Пропустить N строк (для пагинации) | + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude\skills\form-info\scripts\form-info.ps1 -FormPath "<путь к Form.xml>" +``` + +С пагинацией: +```powershell +powershell.exe -NoProfile -File .claude\skills\form-info\scripts\form-info.ps1 -FormPath "<путь>" -Offset 150 +``` + +## Чтение вывода + +### Заголовок + +``` +=== Form: ФормаДокумента (Documents.РеализацияТоваровУслуг) === +``` + +Имя формы и контекст объекта определяются из пути к файлу. + +### Properties — свойства формы + +Только нестандартные свойства (отличающиеся от умолчания): + +``` +Properties: AutoTitle=false, WindowOpeningMode=LockOwnerWindow, CommandBarLocation=Bottom +``` + +### Events — обработчики событий формы + +``` +Events: + OnCreateAtServer -> ПриСозданииНаСервере + OnOpen -> ПриОткрытии +``` + +### Elements — дерево UI-элементов + +Компактное дерево с типами, привязками к данным, флагами и событиями: + +``` +Elements: + ├─ [Group:AH] ГруппаШапка + │ ├─ [Input] Организация -> Объект.Организация {OnChange} + │ └─ [Input] Договор -> Объект.Договор [hidden] {StartChoice} + ├─ [Table] Товары -> Объект.Товары + │ ├─ [Input] Номенклатура -> Объект.Товары.Номенклатура {OnChange} + │ └─ [Input] Сумма -> Объект.Товары.Сумма [ro] + └─ [Pages] Страницы + ├─ [Page] Основное (5 items) + └─ [Page] Печать (2 items) +``` + +**Сокращения типов элементов:** + +| Сокращение | Элемент | +|---|---| +| `[Group:V]` | UsualGroup Vertical | +| `[Group:H]` | UsualGroup Horizontal | +| `[Group:AH]` | UsualGroup AlwaysHorizontal | +| `[Group:AV]` | UsualGroup AlwaysVertical | +| `[Group]` | UsualGroup (ориентация по умолчанию) | +| `[Input]` | InputField | +| `[Check]` | CheckBoxField | +| `[Label]` | LabelDecoration | +| `[LabelField]` | LabelField | +| `[Picture]` | PictureDecoration | +| `[PicField]` | PictureField | +| `[Calendar]` | CalendarField | +| `[Table]` | Table | +| `[Button]` | Button | +| `[CmdBar]` | CommandBar | +| `[Pages]` | Pages | +| `[Page]` | Page (показывает кол-во элементов вместо раскрытия) | +| `[Popup]` | Popup | +| `[BtnGroup]` | ButtonGroup | + +**Флаги** (только при отклонении от умолчания): +- `[hidden]` — Visible=false +- `[disabled]` — Enabled=false +- `[ro]` — ReadOnly=true +- `,collapse` — Behavior=Collapsible (для групп) + +**Привязка**: `-> Объект.Поле` — DataPath или CommandName + +**События**: `{OnChange, StartChoice}` — имена обработчиков + +**Заголовок**: `[title:Текст]` — только если отличается от имени элемента + +### Attributes — реквизиты формы + +``` +Attributes: + *Объект: DocumentObject.РеализацияТоваров (main) + Валюта: CatalogRef.Валюты + Итого: decimal(15,2) + Таблица: ValueTable [Номенклатура: CatalogRef.Номенклатура, Кол: decimal(10,3)] + Список: DynamicList -> Catalog.Пользователи +``` + +- `*` и `(main)` — основной реквизит формы (MainAttribute) +- Типы ValueTable/ValueTree раскрывают колонки в `[...]` +- DynamicList показывает MainTable через `->` + +### Parameters — параметры формы + +``` +Parameters: + Ключ: DocumentRef.ЗакупкаТоваров (key) + Основание: DocumentRef.* +``` + +- `(key)` — ключевой параметр (KeyParameter) + +### Commands — команды формы + +``` +Commands: + Печать -> ПечатьДокумента [Ctrl+P] + Заполнить -> ЗаполнитьОбработка +``` + +Формат: `Имя -> Обработчик [Сочетание]` + +## Что пропускается + +Скрипт убирает 80%+ XML-объёма: +- Визуальные свойства (Width, Height, Color, Font, Border, Align, Stretch) +- Автогенерированные ExtendedTooltip и ContextMenu +- Мультиязычные обёртки (v8:item/v8:lang/v8:content) +- Namespace-декларации +- Атрибуты id + +Для точечного изучения деталей — используйте grep по имени элемента из сводки. + +## Когда использовать + +- **Перед модификацией формы**: понять структуру, найти нужную группу для вставки элемента +- **Анализ формы**: какие реквизиты, команды, обработчики задействованы +- **Навигация по большим формам**: 28K строк XML → 50-100 строк контекста + +## Защита от переполнения + +Вывод ограничен 150 строками по умолчанию. При превышении: +``` +[TRUNCATED] Shown 150 of 220 lines. Use -Offset 150 to continue. +``` + +Используйте `-Offset N` и `-Limit N` для постраничного просмотра. diff --git a/.claude/skills/form-info/scripts/form-info.ps1 b/.claude/skills/form-info/scripts/form-info.ps1 new file mode 100644 index 00000000..459a51f9 --- /dev/null +++ b/.claude/skills/form-info/scripts/form-info.ps1 @@ -0,0 +1,511 @@ +param( + [Parameter(Mandatory=$true)] + [string]$FormPath, + [int]$Limit = 150, + [int]$Offset = 0 +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- Validate path --- + +if (-not (Test-Path $FormPath)) { + Write-Error "File not found: $FormPath" + exit 1 +} + +# --- Load XML --- + +$xmlDoc = New-Object System.Xml.XmlDocument +$xmlDoc.PreserveWhitespace = $false +$xmlDoc.Load((Resolve-Path $FormPath).Path) + +$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) +$ns.AddNamespace("d", "http://v8.1c.ru/8.3/xcf/logform") +$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core") +$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui") +$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable") +$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema") +$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance") +$ns.AddNamespace("cfg", "http://v8.1c.ru/8.1/data/enterprise/current-config") +$ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings") + +$root = $xmlDoc.DocumentElement + +# --- Helper: extract multilang text --- + +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 "" +} + +# --- Helper: format type compactly --- + +function Format-Type($typeNode) { + if (-not $typeNode -or -not $typeNode.HasChildNodes) { return "" } + + $typeSet = $typeNode.SelectSingleNode("v8:TypeSet", $ns) + if ($typeSet) { + $val = $typeSet.InnerText + # Strip cfg: prefix for DefinedType, keep as-is + if ($val -like "cfg:*") { $val = $val.Substring(4) } + return $val + } + + $types = $typeNode.SelectNodes("v8:Type", $ns) + if ($types.Count -eq 0) { return "" } + + $parts = @() + foreach ($t in $types) { + $raw = $t.InnerText + switch -Wildcard ($raw) { + "xs:string" { + $sq = $typeNode.SelectSingleNode("v8:StringQualifiers/v8:Length", $ns) + $len = if ($sq) { [int]$sq.InnerText } else { 0 } + if ($len -gt 0) { $parts += "string($len)" } else { $parts += "string" } + } + "xs:decimal" { + $nq = $typeNode.SelectSingleNode("v8:NumberQualifiers", $ns) + if ($nq) { + $d = $nq.SelectSingleNode("v8:Digits", $ns) + $f = $nq.SelectSingleNode("v8:FractionDigits", $ns) + $digits = if ($d) { $d.InnerText } else { "0" } + $frac = if ($f) { $f.InnerText } else { "0" } + $parts += "decimal($digits,$frac)" + } else { + $parts += "decimal" + } + } + "xs:boolean" { $parts += "boolean" } + "xs:dateTime" { + $dq = $typeNode.SelectSingleNode("v8:DateQualifiers/v8:DateFractions", $ns) + if ($dq) { + switch ($dq.InnerText) { + "Date" { $parts += "date" } + "Time" { $parts += "time" } + default { $parts += "dateTime" } + } + } else { + $parts += "dateTime" + } + } + "xs:binary" { $parts += "binary" } + "cfg:*" { $parts += $raw.Substring(4) } + "v8:ValueTable" { $parts += "ValueTable" } + "v8:ValueTree" { $parts += "ValueTree" } + "v8:ValueListType" { $parts += "ValueList" } + "v8:TypeDescription" { $parts += "TypeDescription" } + "v8:Universal" { $parts += "Universal" } + "v8:FixedArray" { $parts += "FixedArray" } + "v8:FixedStructure" { $parts += "FixedStructure" } + "v8ui:FormattedString" { $parts += "FormattedString" } + "v8ui:Picture" { $parts += "Picture" } + "v8ui:Color" { $parts += "Color" } + "v8ui:Font" { $parts += "Font" } + "dcsset:*" { $parts += $raw.Replace("dcsset:", "DCS.") } + "dcssch:*" { $parts += $raw.Replace("dcssch:", "DCS.") } + "dcscor:*" { $parts += $raw.Replace("dcscor:", "DCS.") } + default { $parts += $raw } + } + } + + return ($parts -join " | ") +} + +# --- Helper: check if title differs from name --- + +function Test-TitleDiffers($node, [string]$name) { + $titleNode = $node.SelectSingleNode("d:Title", $ns) + if (-not $titleNode) { return $null } + $titleText = Get-MLText $titleNode + if (-not $titleText) { return $null } + # Normalize: remove spaces, lowercase + $normTitle = ($titleText -replace '\s', '').ToLower() + $normName = $name.ToLower() + if ($normTitle -eq $normName) { return $null } + return $titleText +} + +# --- Helper: get events as compact string --- + +function Get-EventsStr($node) { + $eventsNode = $node.SelectSingleNode("d:Events", $ns) + if (-not $eventsNode) { return "" } + $evts = @() + foreach ($e in $eventsNode.SelectNodes("d:Event", $ns)) { + $evts += $e.GetAttribute("name") + } + if ($evts.Count -eq 0) { return "" } + return " {$($evts -join ', ')}" +} + +# --- Helper: get flags --- + +function Get-Flags($node) { + $flags = @() + $vis = $node.SelectSingleNode("d:Visible", $ns) + if ($vis -and $vis.InnerText -eq "false") { $flags += "hidden" } + $en = $node.SelectSingleNode("d:Enabled", $ns) + if ($en -and $en.InnerText -eq "false") { $flags += "disabled" } + $ro = $node.SelectSingleNode("d:ReadOnly", $ns) + if ($ro -and $ro.InnerText -eq "true") { $flags += "ro" } + if ($flags.Count -eq 0) { return "" } + return " [$($flags -join ',')]" +} + +# --- Element type abbreviations --- + +$skipElements = @{ + "ExtendedTooltip" = $true + "ContextMenu" = $true + "AutoCommandBar" = $true + "SearchStringAddition" = $true + "ViewStatusAddition" = $true + "SearchControlAddition" = $true + "ColumnGroup" = $true +} + +function Get-ElementTag($node) { + $localName = $node.LocalName + switch ($localName) { + "UsualGroup" { + $groupNode = $node.SelectSingleNode("d:Group", $ns) + $orient = "" + if ($groupNode) { + switch ($groupNode.InnerText) { + "Vertical" { $orient = ":V" } + "Horizontal" { $orient = ":H" } + "AlwaysHorizontal" { $orient = ":AH" } + "AlwaysVertical" { $orient = ":AV" } + } + } + $beh = $node.SelectSingleNode("d:Behavior", $ns) + $collapse = "" + if ($beh -and $beh.InnerText -eq "Collapsible") { $collapse = ",collapse" } + return "[Group$orient$collapse]" + } + "InputField" { return "[Input]" } + "CheckBoxField" { return "[Check]" } + "LabelDecoration" { return "[Label]" } + "LabelField" { return "[LabelField]" } + "PictureDecoration" { return "[Picture]" } + "PictureField" { return "[PicField]" } + "CalendarField" { return "[Calendar]" } + "Table" { return "[Table]" } + "Button" { return "[Button]" } + "CommandBar" { return "[CmdBar]" } + "Pages" { return "[Pages]" } + "Page" { return "[Page]" } + "Popup" { return "[Popup]" } + "ButtonGroup" { return "[BtnGroup]" } + default { return "[$localName]" } + } +} + +# --- Count significant children (for Page summary) --- + +function Count-SignificantChildren($childItemsNode) { + if (-not $childItemsNode) { return 0 } + $count = 0 + foreach ($child in $childItemsNode.ChildNodes) { + if ($child.NodeType -ne "Element") { continue } + if ($skipElements.ContainsKey($child.LocalName)) { continue } + $count++ + } + return $count +} + +# --- Build element tree recursively --- + +$treeLines = [System.Collections.Generic.List[string]]::new() + +function Build-Tree($childItemsNode, [string]$prefix, [bool]$isLast) { + if (-not $childItemsNode) { return } + + # Collect significant children + $children = @() + foreach ($child in $childItemsNode.ChildNodes) { + if ($child.NodeType -ne "Element") { continue } + if ($skipElements.ContainsKey($child.LocalName)) { continue } + $children += $child + } + + for ($i = 0; $i -lt $children.Count; $i++) { + $child = $children[$i] + $last = ($i -eq $children.Count - 1) + $connector = if ($last) { [char]0x2514 + [string][char]0x2500 } else { [char]0x251C + [string][char]0x2500 } + $continuation = if ($last) { " " } else { [string][char]0x2502 + " " } + + $tag = Get-ElementTag $child + $name = $child.GetAttribute("name") + $flags = Get-Flags $child + $events = Get-EventsStr $child + + # DataPath or CommandName + $binding = "" + $dp = $child.SelectSingleNode("d:DataPath", $ns) + if ($dp) { + $binding = " -> $($dp.InnerText)" + } else { + $cn = $child.SelectSingleNode("d:CommandName", $ns) + if ($cn) { $binding = " -> $($cn.InnerText)" } + } + + # Title differs? + $titleStr = "" + $diffTitle = Test-TitleDiffers $child $name + if ($diffTitle) { $titleStr = " [title:$diffTitle]" } + + $line = "$prefix$connector $tag $name$binding$flags$titleStr$events" + $treeLines.Add($line) + + # Recurse into containers (but not Page — show summary) + $localName = $child.LocalName + if ($localName -eq "Page") { + $ci = $child.SelectSingleNode("d:ChildItems", $ns) + $cnt = Count-SignificantChildren $ci + # Append count to last line + $idx = $treeLines.Count - 1 + $treeLines[$idx] = $treeLines[$idx] + " ($cnt items)" + } elseif ($localName -in @("UsualGroup", "Pages", "Table", "CommandBar", "ButtonGroup", "Popup")) { + $ci = $child.SelectSingleNode("d:ChildItems", $ns) + if ($ci) { + Build-Tree $ci "$prefix$continuation" $last + } + } + } +} + +# --- Determine form name and object from path --- + +$resolvedPath = (Resolve-Path $FormPath).Path +$parts = $resolvedPath -split '[/\\]' + +$formName = "" +$objectContext = "" + +# Look for /Forms//Ext/Form.xml pattern +$formsIdx = -1 +for ($i = $parts.Count - 1; $i -ge 0; $i--) { + if ($parts[$i] -eq "Forms") { $formsIdx = $i; break } +} + +if ($formsIdx -ge 0 -and ($formsIdx + 1) -lt $parts.Count) { + $formName = $parts[$formsIdx + 1] + # Object is 2 levels up: ...///Forms/... + if ($formsIdx -ge 2) { + $objType = $parts[$formsIdx - 2] + $objName = $parts[$formsIdx - 1] + $objectContext = "$objType.$objName" + } +} else { + # CommonForms pattern: ...///Ext/Form.xml + $extIdx = -1 + for ($i = $parts.Count - 1; $i -ge 0; $i--) { + if ($parts[$i] -eq "Ext") { $extIdx = $i; break } + } + if ($extIdx -ge 2) { + $formName = $parts[$extIdx - 1] + $objType = $parts[$extIdx - 2] + $objectContext = $objType + } else { + $formName = [System.IO.Path]::GetFileNameWithoutExtension($FormPath) + } +} + +# --- Collect output --- + +$lines = @() + +# Header +$header = "=== Form: $formName" +if ($objectContext) { $header += " ($objectContext)" } +$header += " ===" +$lines += $header + +# --- Form properties --- + +$propNames = @( + "Title", "Width", "Height", "Group", + "WindowOpeningMode", "EnterKeyBehavior", "AutoTitle", "AutoURL", + "AutoFillCheck", "Customizable", "CommandBarLocation", + "SaveDataInSettings", "AutoSaveDataInSettings", + "AutoTime", "UsePostingMode", "RepostOnWrite", + "UseForFoldersAndItems", + "ReportResult", "DetailsData", "ReportFormType", + "VerticalScroll", "ScalingMode" +) + +$props = @() +foreach ($pn in $propNames) { + $pNode = $root.SelectSingleNode("d:$pn", $ns) + if ($pNode) { + $val = Get-MLText $pNode + if (-not $val) { $val = $pNode.InnerText } + $props += "$pn=$val" + } +} + +if ($props.Count -gt 0) { + $lines += "" + $lines += "Properties: $($props -join ', ')" +} + +# --- Excluded commands --- + +$excludedCmds = @() +foreach ($ec in $root.SelectNodes("d:CommandSet/d:ExcludedCommand", $ns)) { + $excludedCmds += $ec.InnerText +} + +# --- Form events --- + +$formEvents = $root.SelectSingleNode("d:Events", $ns) +if ($formEvents -and $formEvents.HasChildNodes) { + $lines += "" + $lines += "Events:" + foreach ($e in $formEvents.SelectNodes("d:Event", $ns)) { + $eName = $e.GetAttribute("name") + $eHandler = $e.InnerText + $lines += " $eName -> $eHandler" + } +} + +# --- Element tree --- + +$childItems = $root.SelectSingleNode("d:ChildItems", $ns) +if ($childItems) { + $lines += "" + $lines += "Elements:" + Build-Tree $childItems " " $false + $lines += $treeLines.ToArray() +} + +# --- Attributes --- + +$attrsNode = $root.SelectSingleNode("d:Attributes", $ns) +if ($attrsNode) { + $attrLines = @() + foreach ($attr in $attrsNode.SelectNodes("d:Attribute", $ns)) { + $aName = $attr.GetAttribute("name") + $typeNode = $attr.SelectSingleNode("d:Type", $ns) + $typeStr = Format-Type $typeNode + + $mainAttr = $attr.SelectSingleNode("d:MainAttribute", $ns) + $isMain = ($mainAttr -and $mainAttr.InnerText -eq "true") + + $prefix = if ($isMain) { "*" } else { " " } + $mainSuffix = if ($isMain) { " (main)" } else { "" } + + # DynamicList: show MainTable + $settings = $attr.SelectSingleNode("d:Settings", $ns) + $dynTable = "" + if ($settings -and $typeStr -eq "DynamicList") { + $mt = $settings.SelectSingleNode("d:MainTable", $ns) + if ($mt) { $dynTable = " -> $($mt.InnerText)" } + } + + # ValueTable/ValueTree columns + $colStr = "" + $columns = $attr.SelectSingleNode("d:Columns", $ns) + if ($columns -and ($typeStr -eq "ValueTable" -or $typeStr -eq "ValueTree")) { + $cols = @() + foreach ($col in $columns.SelectNodes("d:Column", $ns)) { + $cName = $col.GetAttribute("name") + $cTypeNode = $col.SelectSingleNode("d:Type", $ns) + $cType = Format-Type $cTypeNode + if ($cType) { $cols += "$cName`: $cType" } else { $cols += $cName } + } + if ($cols.Count -gt 0) { + $colStr = " [$($cols -join ', ')]" + } + } + + $line = " $prefix$aName`: $typeStr$colStr$dynTable$mainSuffix" + if (-not $typeStr -and -not $colStr -and -not $dynTable) { + $line = " $prefix$aName$mainSuffix" + } + $attrLines += $line + } + if ($attrLines.Count -gt 0) { + $lines += "" + $lines += "Attributes:" + $lines += $attrLines + } +} + +# --- Parameters --- + +$paramsNode = $root.SelectSingleNode("d:Parameters", $ns) +if ($paramsNode) { + $paramLines = @() + foreach ($param in $paramsNode.SelectNodes("d:Parameter", $ns)) { + $pName = $param.GetAttribute("name") + $typeNode = $param.SelectSingleNode("d:Type", $ns) + $typeStr = Format-Type $typeNode + + $keyParam = $param.SelectSingleNode("d:KeyParameter", $ns) + $isKey = ($keyParam -and $keyParam.InnerText -eq "true") + $keySuffix = if ($isKey) { " (key)" } else { "" } + + if ($typeStr) { + $paramLines += " $pName`: $typeStr$keySuffix" + } else { + $paramLines += " $pName$keySuffix" + } + } + if ($paramLines.Count -gt 0) { + $lines += "" + $lines += "Parameters:" + $lines += $paramLines + } +} + +# --- Commands --- + +$cmdsNode = $root.SelectSingleNode("d:Commands", $ns) +if ($cmdsNode) { + $cmdLines = @() + foreach ($cmd in $cmdsNode.SelectNodes("d:Command", $ns)) { + $cName = $cmd.GetAttribute("name") + $action = $cmd.SelectSingleNode("d:Action", $ns) + $shortcut = $cmd.SelectSingleNode("d:Shortcut", $ns) + + $actionStr = if ($action) { " -> $($action.InnerText)" } else { "" } + $scStr = if ($shortcut) { " [$($shortcut.InnerText)]" } else { "" } + + $cmdLines += " $cName$actionStr$scStr" + } + if ($cmdLines.Count -gt 0) { + $lines += "" + $lines += "Commands:" + $lines += $cmdLines + } +} + +# --- Truncation protection --- + +$totalLines = $lines.Count + +if ($Offset -gt 0) { + if ($Offset -ge $totalLines) { + Write-Host "[INFO] Offset $Offset exceeds total lines ($totalLines). Nothing to show." + exit 0 + } + $lines = $lines[$Offset..($totalLines - 1)] +} + +if ($lines.Count -gt $Limit) { + $shown = $lines[0..($Limit - 1)] + foreach ($l in $shown) { Write-Host $l } + $remaining = $totalLines - $Offset - $Limit + Write-Host "" + Write-Host "[TRUNCATED] Shown $Limit of $totalLines lines. Use -Offset $($Offset + $Limit) to continue." +} else { + foreach ($l in $lines) { Write-Host $l } +} diff --git a/README.md b/README.md index f42a918f..3e402407 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ |--------|--------|----------|------| | Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) | | Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) | +| Управляемые формы (Form) | `/form-info` | Анализ структуры управляемых форм из XML-исходников конфигурации | [Подробнее](docs/form-guide.md) | | Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — | ## Требования @@ -55,10 +56,12 @@ ├── mxl-validate/ # Валидация макета ├── mxl-compile/ # Компиляция макета из JSON ├── mxl-decompile/ # Декомпиляция макета в JSON +├── form-info/ # Анализ структуры управляемой формы └── img-grid/ # Сетка для анализа изображений docs/ ├── epf-guide.md # Гайд: внешние обработки ├── mxl-guide.md # Гайд: табличный документ +├── form-guide.md # Гайд: управляемые формы ├── 1c-xml-format-spec.md # Спецификация XML-формата ├── 1c-form-spec.md # Спецификация управляемых форм ├── 1c-help-spec.md # Спецификация встроенной справки diff --git a/docs/form-guide.md b/docs/form-guide.md new file mode 100644 index 00000000..cbc4e7ee --- /dev/null +++ b/docs/form-guide.md @@ -0,0 +1,141 @@ +# Управляемые формы (Form) + +Навыки группы `/form-*` позволяют анализировать управляемые формы 1С:Предприятия 8.3 из XML-исходников конфигурации, не читая тысячи строк XML. + +## Навыки + +| Навык | Параметры | Описание | +|-------|-----------|----------| +| `/form-info` | `` | Компактная сводка: дерево элементов, реквизиты, команды, события | + +## Сценарии использования + +### Анализ формы перед модификацией + +Файлы Form.xml содержат от сотен до десятков тысяч строк XML. 80% объёма — визуальный шум (цвета, шрифты, размеры, автогенерированные подсказки). `/form-info` извлекает суть. + +``` +> Покажи структуру формы документа РеализацияТоваровУслуг +``` + +Claude найдёт Form.xml и вызовет `/form-info`. Результат — компактная сводка (40–100 строк вместо тысяч): + +``` +=== Form: ФормаДокумента (Documents.РеализацияТоваровУслуг) === + +Properties: AutoTitle=false, CommandBarLocation=None + +Events: + OnCreateAtServer -> ПриСозданииНаСервере + OnOpen -> ПриОткрытии + +Elements: + ├─ [Group:AH] ГруппаШапка + │ ├─ [Input] Организация -> Объект.Организация {OnChange} + │ └─ [Input] Контрагент -> Объект.Контрагент {OnChange, StartChoice} + ├─ [Table] Товары -> Объект.Товары + │ ├─ [Input] Номенклатура -> Объект.Товары.Номенклатура {OnChange} + │ └─ [Input] Сумма -> Объект.Товары.Сумма [ro] + └─ [Pages] Страницы + ├─ [Page] Основное (5 items) + └─ [Page] Печать (2 items) + +Attributes: + *Объект: DocumentObject.РеализацияТоваровУслуг (main) + Валюта: CatalogRef.Валюты + ... + +Commands: + Печать -> ПечатьДокумента [Ctrl+P] +``` + +### Добавление элемента на форму + +Чтобы добавить поле на форму, нужно знать структуру групп и ориентацию (горизонтальная/вертикальная). `/form-info` показывает это в дереве элементов. + +``` +> Добавь поле "Склад" в шапку формы документа +``` + +Claude вызовет `/form-info`, увидит `[Group:AH] ГруппаШапка` → `[Group:V] ГруппаШапкаЛевая`, поймёт куда вставить элемент в XML. + +### Поиск обработчиков и привязок + +Из сводки видно, какие события подключены к каким элементам: + +``` +> Какие обработчики срабатывают при изменении контрагента? +``` + +Claude вызовет `/form-info` и найдёт: `[Input] Контрагент -> Объект.Контрагент {OnChange, StartChoice}` — далее откроет модуль формы и найдёт процедуры по именам событий. + +### Точечное погружение в детали + +Сводка — это карта. Для деталей по конкретному элементу достаточно grep по имени из сводки: + +``` +> Покажи все свойства элемента ДоговорКонтрагента +``` + +Claude сделает grep по Form.xml и найдёт полный XML-блок с ChoiceParameters, ChoiceFoldersAndItems, стилями и прочими свойствами. + +## Чтение вывода + +### Дерево элементов + +| Обозначение | Элемент | +|---|---| +| `[Group:V]` `[Group:H]` `[Group:AH]` `[Group:AV]` | Группа (Vertical / Horizontal / AlwaysHorizontal / AlwaysVertical) | +| `[Input]` | Поле ввода | +| `[Check]` | Флажок | +| `[Label]` | Декоративная надпись | +| `[LabelField]` | Поле надписи (привязанное к данным) | +| `[Table]` | Таблица | +| `[Pages]` / `[Page]` | Вкладки (страницы показывают количество элементов) | +| `[Button]` | Кнопка | +| `[CmdBar]` | Командная панель | +| `[Popup]` | Выпадающее меню | +| `[Picture]` | Декоративная картинка | +| `[PicField]` | Поле картинки | +| `[Calendar]` | Календарь | + +### Флаги + +Показываются только при отклонении от умолчания: +- `[hidden]` — элемент скрыт (Visible=false) +- `[disabled]` — элемент недоступен (Enabled=false) +- `[ro]` — только чтение (ReadOnly=true) +- `,collapse` — сворачиваемая группа (Behavior=Collapsible) + +### Привязки и события + +- `-> Объект.Поле` — DataPath (привязка к данным) +- `-> Form.Command.Имя` — привязка к команде формы +- `{OnChange, StartChoice}` — имена подключённых событий +- `[title:Текст]` — заголовок, если отличается от имени элемента + +### Реквизиты + +- `*Объект: DocumentObject.Реализация (main)` — основной реквизит формы +- `Таблица: ValueTable [Кол1: тип, Кол2: тип]` — таблица значений с колонками +- `Список: DynamicList -> Catalog.Пользователи` — динамический список с основной таблицей + +## Примеры слеш-команд + +``` +> /form-info upload/acc_8.3.24/Documents/РеализацияТоваровУслуг/Forms/ФормаДокумента/Ext/Form.xml +> /form-info src/МояОбработка/Forms/Форма/Ext/Form.xml +``` + +## Связь с EPF-навыками + +`/form-info` работает с формами из любых источников — конфигурации и внешних обработок. При работе с обработками: + +1. `/epf-add-form` — создать форму +2. `/form-info` — проанализировать существующую форму +3. Модифицировать Form.xml, ориентируясь на сводку +4. `/epf-build` — собрать EPF + +## Спецификации + +- [Управляемая форма](1c-form-spec.md) — Form.xml, элементы, команды, реквизиты, система типов