mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 08:04:56 +03:00
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 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
---
|
||||
name: form-info
|
||||
description: Анализ структуры управляемой формы 1С (Form.xml) — элементы, реквизиты, команды, события
|
||||
argument-hint: <FormPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /form-info — Компактная сводка формы
|
||||
|
||||
Читает Form.xml управляемой формы и выводит компактную сводку: дерево элементов, реквизиты с типами, команды, события. Заменяет необходимость читать тысячи строк XML.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/form-info <FormPath>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|-----------|:------------:|--------------|---------------------------------------------|
|
||||
| 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` для постраничного просмотра.
|
||||
@@ -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/<FormName>/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: .../<ObjectType>/<ObjectName>/Forms/...
|
||||
if ($formsIdx -ge 2) {
|
||||
$objType = $parts[$formsIdx - 2]
|
||||
$objName = $parts[$formsIdx - 1]
|
||||
$objectContext = "$objType.$objName"
|
||||
}
|
||||
} else {
|
||||
# CommonForms pattern: .../<ObjectType>/<FormName>/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 }
|
||||
}
|
||||
@@ -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 # Спецификация встроенной справки
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
# Управляемые формы (Form)
|
||||
|
||||
Навыки группы `/form-*` позволяют анализировать управляемые формы 1С:Предприятия 8.3 из XML-исходников конфигурации, не читая тысячи строк XML.
|
||||
|
||||
## Навыки
|
||||
|
||||
| Навык | Параметры | Описание |
|
||||
|-------|-----------|----------|
|
||||
| `/form-info` | `<FormPath>` | Компактная сводка: дерево элементов, реквизиты, команды, события |
|
||||
|
||||
## Сценарии использования
|
||||
|
||||
### Анализ формы перед модификацией
|
||||
|
||||
Файлы 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, элементы, команды, реквизиты, система типов
|
||||
Reference in New Issue
Block a user