From cc2595b57a0ecd1dc5e6313e291687e98e094a20 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 9 Feb 2026 11:55:57 +0300 Subject: [PATCH] Add form-compile skill for generating Form.xml from JSON DSL Co-Authored-By: Claude Opus 4.6 --- .claude/skills/form-compile/SKILL.md | 121 ++ .../form-compile/scripts/form-compile.ps1 | 1052 +++++++++++++++++ README.md | 7 +- docs/form-dsl-spec.md | 462 ++++++++ docs/form-guide.md | 57 +- 5 files changed, 1692 insertions(+), 7 deletions(-) create mode 100644 .claude/skills/form-compile/SKILL.md create mode 100644 .claude/skills/form-compile/scripts/form-compile.ps1 create mode 100644 docs/form-dsl-spec.md diff --git a/.claude/skills/form-compile/SKILL.md b/.claude/skills/form-compile/SKILL.md new file mode 100644 index 00000000..90b9b59c --- /dev/null +++ b/.claude/skills/form-compile/SKILL.md @@ -0,0 +1,121 @@ +--- +name: form-compile +description: Компиляция управляемой формы 1С (Form.xml) из компактного JSON-определения +argument-hint: +allowed-tools: + - Bash + - Read + - Write + - Glob +--- + +# /form-compile — Генерация Form.xml из JSON DSL + +Принимает компактное JSON-определение формы (20–50 строк) и генерирует полный корректный Form.xml (100–500+ строк) с namespace-декларациями, автогенерированными companion-элементами, последовательными ID. + +## Использование + +``` +/form-compile +``` + +## Параметры + +| Параметр | Обязательный | Описание | +|------------|:------------:|-----------------------------------| +| JsonPath | да | Путь к JSON-определению формы | +| OutputPath | да | Путь к выходному файлу Form.xml | + +## Команда + +```powershell +powershell.exe -NoProfile -File .claude\skills\form-compile\scripts\form-compile.ps1 -JsonPath "" -OutputPath "" +``` + +## JSON DSL — краткая справка + +### Элементы (ключ определяет тип) + +| DSL ключ | XML элемент | Значение ключа | +|--------------|-------------------|---------------------------------------------------| +| `"group"` | UsualGroup | `"horizontal"` / `"vertical"` / `"alwaysHorizontal"` / `"alwaysVertical"` / `"collapsible"` | +| `"input"` | InputField | имя элемента | +| `"check"` | CheckBoxField | имя | +| `"label"` | LabelDecoration | имя | +| `"labelField"` | LabelField | имя | +| `"table"` | Table | имя | +| `"pages"` | Pages | имя | +| `"page"` | Page | имя | +| `"button"` | Button | имя | +| `"picture"` | PictureDecoration | имя | +| `"picField"` | PictureField | имя | +| `"calendar"` | CalendarField | имя | +| `"cmdBar"` | CommandBar | имя | +| `"popup"` | Popup | имя | + +### Общие свойства элементов + +- `"name"` — переопределить имя (по умолчанию из значения ключа типа) +- `"path"` — DataPath (привязка к данным) +- `"title"` — заголовок +- `"hidden": true` — Visible=false +- `"disabled": true` — Enabled=false +- `"readOnly": true` — ReadOnly=true +- `"on": ["OnChange", "StartChoice"]` — события с автоименованием обработчиков + +### Система типов (shorthand) + +| DSL | XML | +|------------------------|----------------------------------------| +| `"string"` / `"string(100)"` | `xs:string` + StringQualifiers | +| `"decimal(15,2)"` | `xs:decimal` + NumberQualifiers | +| `"decimal(10,0,nonneg)"` | с AllowedSign=Nonnegative | +| `"boolean"` | `xs:boolean` | +| `"date"` / `"dateTime"` / `"time"` | `xs:dateTime` + DateFractions | +| `"CatalogRef.Организации"` | `cfg:CatalogRef.Организации` | +| `"DocumentObject.Реализация"` | `cfg:DocumentObject.Реализация` | +| `"ValueTable"` | `v8:ValueTable` | +| `"ValueList"` | `v8:ValueListType` | +| `"Type1 \| Type2"` | составной тип | + +### Пример JSON + +```json +{ + "title": "Загрузка данных", + "properties": { "autoTitle": false }, + "events": { "OnCreateAtServer": "ПриСозданииНаСервере" }, + "elements": [ + { "group": "horizontal", "name": "Шапка", "children": [ + { "input": "Организация", "path": "Объект.Организация", "on": ["OnChange"] } + ]}, + { "table": "Товары", "path": "Объект.Товары", "columns": [ + { "input": "Номенклатура", "path": "Объект.Товары.Номенклатура" } + ]} + ], + "attributes": [ + { "name": "Объект", "type": "DataProcessorObject.ЗагрузкаДанных", "main": true } + ], + "commands": [ + { "name": "Загрузить", "action": "ЗагрузитьОбработка", "shortcut": "Ctrl+Enter" } + ] +} +``` + +### Автогенерация + +- **Companion-элементы**: ContextMenu, ExtendedTooltip и др. создаются автоматически с правильными именами и ID +- **Обработчики событий**: `"on": ["OnChange"]` → `ОрганизацияПриИзменении` +- **Namespace**: все 17 namespace-деклараций добавляются автоматически +- **ID**: последовательная нумерация, AutoCommandBar всегда id="-1" + +## Верификация + +Используйте `/form-info` для проверки результата: + +``` +/form-info +``` + +Структура в сводке должна совпадать с определением в JSON. + diff --git a/.claude/skills/form-compile/scripts/form-compile.ps1 b/.claude/skills/form-compile/scripts/form-compile.ps1 new file mode 100644 index 00000000..c10d1acf --- /dev/null +++ b/.claude/skills/form-compile/scripts/form-compile.ps1 @@ -0,0 +1,1052 @@ +param( + [Parameter(Mandatory)] + [string]$JsonPath, + + [Parameter(Mandatory)] + [string]$OutputPath +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +# --- 1. Load and validate JSON --- + +if (-not (Test-Path $JsonPath)) { + Write-Error "File not found: $JsonPath" + exit 1 +} + +$json = Get-Content -Raw -Encoding UTF8 $JsonPath +$def = $json | ConvertFrom-Json + +# --- 2. ID allocator --- + +$script:nextId = 1 +function New-Id { + $id = $script:nextId + $script:nextId++ + return $id +} + +# --- 3. XML helper --- + +$script:xml = New-Object System.Text.StringBuilder 8192 + +function X { + param([string]$text) + $script:xml.AppendLine($text) | Out-Null +} + +function Esc-Xml { + param([string]$s) + return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') +} + +# --- 4. Multilang helper --- + +function Emit-MLText { + param([string]$tag, [string]$text, [string]$indent) + X "$indent<$tag>" + X "$indent`t" + X "$indent`t`tru" + X "$indent`t`t$(Esc-Xml $text)" + X "$indent`t" + X "$indent" +} + +# --- 5. Type emitter --- + +function Emit-Type { + param($typeStr, [string]$indent) + + if (-not $typeStr) { + X "$indent" + return + } + + $typeString = "$typeStr" + + # Composite type: "Type1 | Type2" + $parts = $typeString -split '\s*\|\s*' + + X "$indent" + foreach ($part in $parts) { + $part = $part.Trim() + Emit-SingleType -typeStr $part -indent "$indent`t" + } + X "$indent" +} + +function Emit-SingleType { + param([string]$typeStr, [string]$indent) + + # boolean + if ($typeStr -eq "boolean") { + X "$indentxs:boolean" + return + } + + # string or string(N) + if ($typeStr -match '^string(\((\d+)\))?$') { + $len = if ($Matches[2]) { $Matches[2] } else { "0" } + X "$indentxs:string" + X "$indent" + X "$indent`t$len" + X "$indent`tVariable" + X "$indent" + return + } + + # decimal(D,F) or decimal(D,F,nonneg) + if ($typeStr -match '^decimal\((\d+),(\d+)(,nonneg)?\)$') { + $digits = $Matches[1] + $fraction = $Matches[2] + $sign = if ($Matches[3]) { "Nonnegative" } else { "Any" } + X "$indentxs:decimal" + X "$indent" + X "$indent`t$digits" + X "$indent`t$fraction" + X "$indent`t$sign" + X "$indent" + return + } + + # date / dateTime / time + if ($typeStr -match '^(date|dateTime|time)$') { + $fractions = switch ($typeStr) { + "date" { "Date" } + "dateTime" { "DateTime" } + "time" { "Time" } + } + X "$indentxs:dateTime" + X "$indent" + X "$indent`t$fractions" + X "$indent" + return + } + + # ValueTable, ValueTree, ValueList, etc. + $v8Types = @{ + "ValueTable" = "v8:ValueTable" + "ValueTree" = "v8:ValueTree" + "ValueList" = "v8:ValueListType" + "TypeDescription" = "v8:TypeDescription" + "Universal" = "v8:Universal" + "FixedArray" = "v8:FixedArray" + "FixedStructure" = "v8:FixedStructure" + } + if ($v8Types.ContainsKey($typeStr)) { + X "$indent$($v8Types[$typeStr])" + return + } + + # UI types + $uiTypes = @{ + "FormattedString" = "v8ui:FormattedString" + "Picture" = "v8ui:Picture" + "Color" = "v8ui:Color" + "Font" = "v8ui:Font" + } + if ($uiTypes.ContainsKey($typeStr)) { + X "$indent$($uiTypes[$typeStr])" + return + } + + # DCS types + if ($typeStr -match '^DataComposition') { + $dcsMap = @{ + "DataCompositionSettings" = "dcsset:DataCompositionSettings" + "DataCompositionSchema" = "dcssch:DataCompositionSchema" + "DataCompositionComparisonType" = "dcscor:DataCompositionComparisonType" + } + if ($dcsMap.ContainsKey($typeStr)) { + X "$indent$($dcsMap[$typeStr])" + return + } + } + + # DynamicList + if ($typeStr -eq "DynamicList") { + X "$indentcfg:DynamicList" + return + } + + # cfg: references (CatalogRef.XXX, DocumentObject.XXX, etc.) + if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.') { + X "$indentcfg:$typeStr" + return + } + + # Fallback: emit as-is with cfg: prefix if contains dot, otherwise v8: + if ($typeStr.Contains('.')) { + X "$indentcfg:$typeStr" + } else { + X "$indent$typeStr" + } +} + +# --- 6. Event handler name generator --- + +$script:eventSuffixMap = @{ + "OnChange" = "ПриИзменении" + "StartChoice" = "НачалоВыбора" + "ChoiceProcessing" = "ОбработкаВыбора" + "AutoComplete" = "АвтоПодбор" + "Clearing" = "Очистка" + "Opening" = "Открытие" + "Click" = "Нажатие" + "OnActivateRow" = "ПриАктивизацииСтроки" + "BeforeAddRow" = "ПередНачаломДобавления" + "BeforeDeleteRow" = "ПередУдалением" + "BeforeRowChange" = "ПередНачаломИзменения" + "OnStartEdit" = "ПриНачалеРедактирования" + "OnEndEdit" = "ПриОкончанииРедактирования" + "Selection" = "ВыборСтроки" + "OnCurrentPageChange" = "ПриСменеСтраницы" + "TextEditEnd" = "ОкончаниеВводаТекста" + "URLProcessing" = "ОбработкаНавигационнойСсылки" + "DragStart" = "НачалоПеретаскивания" + "Drag" = "Перетаскивание" + "DragCheck" = "ПроверкаПеретаскивания" + "Drop" = "Помещение" + "AfterDeleteRow" = "ПослеУдаления" +} + +function Get-HandlerName { + param([string]$elementName, [string]$eventName) + $suffix = $script:eventSuffixMap[$eventName] + if ($suffix) { + return "$elementName$suffix" + } + return "$elementName$eventName" +} + +# --- 7. Element emitters --- + +function Get-ElementName { + param($el, [string]$typeKey) + if ($el.name) { return "$($el.name)" } + return "$($el.$typeKey)" +} + +function Emit-Events { + param($el, [string]$elementName, [string]$indent) + + if (-not $el.on) { return } + + X "$indent" + foreach ($evt in $el.on) { + $evtName = "$evt" + $handler = if ($el.handlers -and $el.handlers.$evtName) { + "$($el.handlers.$evtName)" + } else { + Get-HandlerName -elementName $elementName -eventName $evtName + } + X "$indent`t$handler" + } + X "$indent" +} + +function Emit-Companion { + param([string]$tag, [string]$name, [string]$indent) + $id = New-Id + X "$indent<$tag name=`"$name`" id=`"$id`"/>" +} + +function Emit-Element { + param($el, [string]$indent) + + # Determine element type from key + $typeKey = $null + $xmlTag = $null + + foreach ($key in @("group","input","check","label","labelField","table","pages","page","button","picture","picField","calendar","cmdBar","popup")) { + if ($el.$key -ne $null) { + $typeKey = $key + break + } + } + + if (-not $typeKey) { + Write-Warning "Unknown element type, skipping" + return + } + + $name = Get-ElementName -el $el -typeKey $typeKey + $id = New-Id + + switch ($typeKey) { + "group" { Emit-Group -el $el -name $name -id $id -indent $indent } + "input" { Emit-Input -el $el -name $name -id $id -indent $indent } + "check" { Emit-Check -el $el -name $name -id $id -indent $indent } + "label" { Emit-Label -el $el -name $name -id $id -indent $indent } + "labelField" { Emit-LabelField -el $el -name $name -id $id -indent $indent } + "table" { Emit-Table -el $el -name $name -id $id -indent $indent } + "pages" { Emit-Pages -el $el -name $name -id $id -indent $indent } + "page" { Emit-Page -el $el -name $name -id $id -indent $indent } + "button" { Emit-Button -el $el -name $name -id $id -indent $indent } + "picture" { Emit-PictureDecoration -el $el -name $name -id $id -indent $indent } + "picField" { Emit-PictureField -el $el -name $name -id $id -indent $indent } + "calendar" { Emit-Calendar -el $el -name $name -id $id -indent $indent } + "cmdBar" { Emit-CommandBar -el $el -name $name -id $id -indent $indent } + "popup" { Emit-Popup -el $el -name $name -id $id -indent $indent } + } +} + +function Emit-CommonFlags { + param($el, [string]$indent) + if ($el.hidden -eq $true) { X "$indentfalse" } + if ($el.disabled -eq $true) { X "$indentfalse" } + if ($el.readOnly -eq $true) { X "$indenttrue" } +} + +function Emit-Title { + param($el, [string]$name, [string]$indent) + if ($el.title) { + Emit-MLText -tag "Title" -text "$($el.title)" -indent $indent + } +} + +function Emit-Group { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + Emit-Title -el $el -name $name -indent $inner + + # Group orientation + $groupVal = "$($el.group)" + $orientation = switch ($groupVal) { + "horizontal" { "Horizontal" } + "vertical" { "Vertical" } + "alwaysHorizontal" { "AlwaysHorizontal" } + "alwaysVertical" { "AlwaysVertical" } + default { $null } + } + if ($orientation) { X "$inner$orientation" } + + # Behavior + if ($groupVal -eq "collapsible") { + X "$innerVertical" + X "$innerCollapsible" + } + + # Representation + if ($el.representation) { + $repr = switch ("$($el.representation)") { + "none" { "None" } + "normal" { "NormalSeparation" } + "weak" { "WeakSeparation" } + "strong" { "StrongSeparation" } + default { "$($el.representation)" } + } + X "$inner$repr" + } + + # ShowTitle + if ($el.showTitle -eq $false) { X "$innerfalse" } + + # United + if ($el.united -eq $false) { X "$innerfalse" } + + Emit-CommonFlags -el $el -indent $inner + + # Companion: ExtendedTooltip + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + # Children + if ($el.children -and $el.children.Count -gt 0) { + X "$inner" + foreach ($child in $el.children) { + Emit-Element -el $child -indent "$inner`t" + } + X "$inner" + } + + X "$indent" +} + +function Emit-Input { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.titleLocation) { + $loc = switch ("$($el.titleLocation)") { + "none" { "None" } + "left" { "Left" } + "right" { "Right" } + "top" { "Top" } + "bottom" { "Bottom" } + default { "$($el.titleLocation)" } + } + X "$inner$loc" + } + + if ($el.multiLine -eq $true) { X "$innertrue" } + if ($el.passwordMode -eq $true) { X "$innertrue" } + if ($el.choiceButton -eq $false) { X "$innerfalse" } + if ($el.clearButton -eq $true) { X "$innertrue" } + if ($el.spinButton -eq $true) { X "$innertrue" } + if ($el.dropListButton -eq $true) { X "$innertrue" } + if ($el.markIncomplete -eq $true) { X "$innertrue" } + if ($el.skipOnInput -eq $true) { X "$innertrue" } + if ($el.autoMaxWidth -eq $false) { X "$innerfalse" } + if ($el.autoMaxHeight -eq $false) { X "$innerfalse" } + if ($el.width) { X "$inner$($el.width)" } + if ($el.height) { X "$inner$($el.height)" } + if ($el.horizontalStretch -eq $true) { X "$innertrue" } + if ($el.verticalStretch -eq $true) { X "$innertrue" } + + if ($el.inputHint) { + Emit-MLText -tag "InputHint" -text "$($el.inputHint)" -indent $inner + } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-Check { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.titleLocation) { + X "$inner$($el.titleLocation)" + } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-Label { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.title) { + $formatted = if ($el.hyperlink -eq $true) { "true" } else { "false" } + X "$inner" + X "$inner`t<v8:item>" + X "$inner`t`t<v8:lang>ru</v8:lang>" + X "$inner`t`t<v8:content>$(Esc-Xml "$($el.title)")</v8:content>" + X "$inner`t</v8:item>" + X "$inner" + } + + Emit-CommonFlags -el $el -indent $inner + + if ($el.hyperlink -eq $true) { X "$innertrue" } + if ($el.autoMaxWidth -eq $false) { X "$innerfalse" } + if ($el.autoMaxHeight -eq $false) { X "$innerfalse" } + if ($el.width) { X "$inner$($el.width)" } + if ($el.height) { X "$inner$($el.height)" } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-LabelField { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.hyperlink -eq $true) { X "$innertrue" } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-Table { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.representation) { + X "$inner$($el.representation)" + } + if ($el.changeRowSet -eq $true) { X "$innertrue" } + if ($el.changeRowOrder -eq $true) { X "$innertrue" } + if ($el.height) { X "$inner$($el.height)" } + if ($el.header -eq $false) { X "$inner
false
" } + if ($el.footer -eq $true) { X "$inner
true
" } + + if ($el.commandBarLocation) { + X "$inner$($el.commandBarLocation)" + } + if ($el.searchStringLocation) { + X "$inner$($el.searchStringLocation)" + } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "AutoCommandBar" -name "${name}КоманднаяПанель" -indent $inner + Emit-Companion -tag "SearchStringAddition" -name "${name}СтрокаПоиска" -indent $inner + Emit-Companion -tag "ViewStatusAddition" -name "${name}СостояниеПросмотра" -indent $inner + Emit-Companion -tag "SearchControlAddition" -name "${name}УправлениеПоиском" -indent $inner + + # Columns + if ($el.columns -and $el.columns.Count -gt 0) { + X "$inner" + foreach ($col in $el.columns) { + Emit-Element -el $col -indent "$inner`t" + } + X "$inner" + } + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent
" +} + +function Emit-Pages { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.pagesRepresentation) { + X "$inner$($el.pagesRepresentation)" + } + + Emit-CommonFlags -el $el -indent $inner + + # Companion + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + # Children (pages) + if ($el.children -and $el.children.Count -gt 0) { + X "$inner" + foreach ($child in $el.children) { + Emit-Element -el $child -indent "$inner`t" + } + X "$inner" + } + + X "$indent" +} + +function Emit-Page { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.group) { + $orientation = switch ("$($el.group)") { + "horizontal" { "Horizontal" } + "vertical" { "Vertical" } + "alwaysHorizontal" { "AlwaysHorizontal" } + "alwaysVertical" { "AlwaysVertical" } + default { $null } + } + if ($orientation) { X "$inner$orientation" } + } + + # Companion + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + # Children + if ($el.children -and $el.children.Count -gt 0) { + X "$inner" + foreach ($child in $el.children) { + Emit-Element -el $child -indent "$inner`t" + } + X "$inner" + } + + X "$indent" +} + +function Emit-Button { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" +} + +function Emit-PictureDecoration { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.picture -or $el.src) { + $ref = if ($el.src) { "$($el.src)" } else { "$($el.picture)" } + X "$inner" + X "$inner`t$ref" + X "$inner`ttrue" + X "$inner" + } + + if ($el.hyperlink -eq $true) { X "$innertrue" } + if ($el.width) { X "$inner$($el.width)" } + if ($el.height) { X "$inner$($el.height)" } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-PictureField { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.width) { X "$inner$($el.width)" } + if ($el.height) { X "$inner$($el.height)" } + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-Calendar { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.path) { X "$inner$($el.path)" } + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + # Companions + Emit-Companion -tag "ContextMenu" -name "${name}КонтекстноеМеню" -indent $inner + Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner + + Emit-Events -el $el -elementName $name -indent $inner + + X "$indent" +} + +function Emit-CommandBar { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + if ($el.autofill -eq $true) { X "$innertrue" } + + Emit-CommonFlags -el $el -indent $inner + + # Children + if ($el.children -and $el.children.Count -gt 0) { + X "$inner" + foreach ($child in $el.children) { + Emit-Element -el $child -indent "$inner`t" + } + X "$inner" + } + + X "$indent" +} + +function Emit-Popup { + param($el, [string]$name, [int]$id, [string]$indent) + + X "$indent" + $inner = "$indent`t" + + Emit-Title -el $el -name $name -indent $inner + Emit-CommonFlags -el $el -indent $inner + + if ($el.picture) { + X "$inner" + X "$inner`t$($el.picture)" + X "$inner`ttrue" + X "$inner" + } + + if ($el.representation) { + X "$inner$($el.representation)" + } + + # Children + if ($el.children -and $el.children.Count -gt 0) { + X "$inner" + foreach ($child in $el.children) { + Emit-Element -el $child -indent "$inner`t" + } + X "$inner" + } + + X "$indent" +} + +# --- 8. Attribute emitter --- + +function Emit-Attributes { + param($attrs, [string]$indent) + + if (-not $attrs -or $attrs.Count -eq 0) { return } + + X "$indent" + foreach ($attr in $attrs) { + $attrId = New-Id + $attrName = "$($attr.name)" + + X "$indent`t" + $inner = "$indent`t`t" + + if ($attr.title) { + Emit-MLText -tag "Title" -text "$($attr.title)" -indent $inner + } + + # Type + if ($attr.type) { + Emit-Type -typeStr "$($attr.type)" -indent $inner + } else { + X "$inner" + } + + if ($attr.main -eq $true) { + X "$innertrue" + } + if ($attr.savedData -eq $true) { + X "$innertrue" + } + if ($attr.fillChecking) { + X "$inner$($attr.fillChecking)" + } + + # Columns (for ValueTable/ValueTree) + if ($attr.columns -and $attr.columns.Count -gt 0) { + X "$inner" + foreach ($col in $attr.columns) { + $colId = New-Id + X "$inner`t" + if ($col.title) { + Emit-MLText -tag "Title" -text "$($col.title)" -indent "$inner`t`t" + } + Emit-Type -typeStr "$($col.type)" -indent "$inner`t`t" + X "$inner`t" + } + X "$inner" + } + + X "$indent`t" + } + X "$indent" +} + +# --- 9. Parameter emitter --- + +function Emit-Parameters { + param($params, [string]$indent) + + if (-not $params -or $params.Count -eq 0) { return } + + X "$indent" + foreach ($param in $params) { + X "$indent`t" + $inner = "$indent`t`t" + + Emit-Type -typeStr "$($param.type)" -indent $inner + + if ($param.key -eq $true) { + X "$innertrue" + } + + X "$indent`t" + } + X "$indent" +} + +# --- 10. Command emitter --- + +function Emit-Commands { + param($cmds, [string]$indent) + + if (-not $cmds -or $cmds.Count -eq 0) { return } + + X "$indent" + foreach ($cmd in $cmds) { + $cmdId = New-Id + X "$indent`t" + $inner = "$indent`t`t" + + if ($cmd.title) { + Emit-MLText -tag "Title" -text "$($cmd.title)" -indent $inner + } + + if ($cmd.action) { + X "$inner$($cmd.action)" + } + + if ($cmd.shortcut) { + X "$inner$($cmd.shortcut)" + } + + if ($cmd.picture) { + X "$inner" + X "$inner`t$($cmd.picture)" + X "$inner`ttrue" + X "$inner" + } + + if ($cmd.representation) { + X "$inner$($cmd.representation)" + } + + X "$indent`t" + } + X "$indent" +} + +# --- 11. Properties emitter --- + +function Emit-Properties { + param($props, [string]$indent) + + if (-not $props) { return } + + # camelCase -> PascalCase mapping for known properties + $propMap = @{ + "autoTitle" = "AutoTitle" + "windowOpeningMode" = "WindowOpeningMode" + "commandBarLocation" = "CommandBarLocation" + "saveDataInSettings" = "SaveDataInSettings" + "autoSaveDataInSettings" = "AutoSaveDataInSettings" + "autoTime" = "AutoTime" + "usePostingMode" = "UsePostingMode" + "repostOnWrite" = "RepostOnWrite" + "autoURL" = "AutoURL" + "autoFillCheck" = "AutoFillCheck" + "customizable" = "Customizable" + "enterKeyBehavior" = "EnterKeyBehavior" + "verticalScroll" = "VerticalScroll" + "scalingMode" = "ScalingMode" + "useForFoldersAndItems" = "UseForFoldersAndItems" + "reportResult" = "ReportResult" + "detailsData" = "DetailsData" + "reportFormType" = "ReportFormType" + "autoShowState" = "AutoShowState" + "width" = "Width" + "height" = "Height" + "group" = "Group" + } + + foreach ($p in $props.PSObject.Properties) { + $xmlName = if ($propMap.ContainsKey($p.Name)) { $propMap[$p.Name] } else { + # Auto PascalCase: first letter uppercase + $p.Name.Substring(0,1).ToUpper() + $p.Name.Substring(1) + } + # Convert boolean to lowercase string (PS renders as True/False) + $val = $p.Value + if ($val -is [bool]) { + $val = if ($val) { "true" } else { "false" } + } + X "$indent<$xmlName>$val" + } +} + +# --- 12. Main compilation --- + +# Title +if ($def.title) { + Emit-MLText -tag "Title" -text "$($def.title)" -indent "`t" +} + +# Header +X '' +X '
' + +# Oops — Title was emitted before header. Need to fix the order. +# Actually, let me restructure: build the body into a separate buffer, then assemble + +# Reset and rebuild properly +$script:xml = New-Object System.Text.StringBuilder 8192 +$script:nextId = 1 + +X '' +X '' + +# 12a. Title +if ($def.title) { + Emit-MLText -tag "Title" -text "$($def.title)" -indent "`t" +} + +# 12b. Properties +Emit-Properties -props $def.properties -indent "`t" + +# 12c. CommandSet (excluded commands) +if ($def.excludedCommands -and $def.excludedCommands.Count -gt 0) { + X "`t" + foreach ($cmd in $def.excludedCommands) { + X "`t`t$cmd" + } + X "`t" +} + +# 12d. AutoCommandBar (always present, id=-1) +X "`t" +X "`t`tRight" +X "`t`tfalse" +X "`t" + +# 12e. Events +if ($def.events) { + X "`t" + foreach ($p in $def.events.PSObject.Properties) { + X "`t`t$($p.Value)" + } + X "`t" +} + +# 12f. ChildItems (elements) +if ($def.elements -and $def.elements.Count -gt 0) { + X "`t" + foreach ($el in $def.elements) { + Emit-Element -el $el -indent "`t`t" + } + X "`t" +} + +# 12g. Attributes +Emit-Attributes -attrs $def.attributes -indent "`t" + +# 12h. Parameters +Emit-Parameters -params $def.parameters -indent "`t" + +# 12i. Commands +Emit-Commands -cmds $def.commands -indent "`t" + +# 12j. Close +X '
' + +# --- 13. Write output --- + +$outPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath } +$outDir = [System.IO.Path]::GetDirectoryName($outPath) +if (-not (Test-Path $outDir)) { + New-Item -ItemType Directory -Path $outDir -Force | Out-Null +} + +$enc = New-Object System.Text.UTF8Encoding($true) +[System.IO.File]::WriteAllText($outPath, $xml.ToString(), $enc) + +# --- 14. Summary --- + +$elCount = $script:nextId - 1 +Write-Host "[OK] Compiled: $OutputPath" +Write-Host " Elements+IDs: $elCount" +if ($def.attributes) { Write-Host " Attributes: $($def.attributes.Count)" } +if ($def.commands) { Write-Host " Commands: $($def.commands.Count)" } +if ($def.parameters) { Write-Host " Parameters: $($def.parameters.Count)" } diff --git a/README.md b/README.md index 3e402407..087724ca 100644 --- a/README.md +++ b/README.md @@ -21,7 +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) | +| Управляемые формы (Form) | 2 навыка `/form-*` | Анализ и генерация управляемых форм из XML-исходников | [Подробнее](docs/form-guide.md) | | Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — | ## Требования @@ -37,6 +37,7 @@ - [Сборка и разборка EPF](docs/build-spec.md) — команды `1cv8.exe`, параметры, коды возврата - [Табличный документ (MXL)](docs/1c-spreadsheet-spec.md) — XML-формат SpreadsheetDocument, совместимость версий - [MXL DSL](docs/mxl-dsl-spec.md) — JSON-формат описания макета для `/mxl-compile` и `/mxl-decompile` +- [Form DSL](docs/form-dsl-spec.md) — JSON-формат описания формы для `/form-compile` ## Структура репозитория @@ -57,6 +58,7 @@ ├── mxl-compile/ # Компиляция макета из JSON ├── mxl-decompile/ # Декомпиляция макета в JSON ├── form-info/ # Анализ структуры управляемой формы +├── form-compile/ # Компиляция формы из JSON └── img-grid/ # Сетка для анализа изображений docs/ ├── epf-guide.md # Гайд: внешние обработки @@ -67,5 +69,6 @@ docs/ ├── 1c-help-spec.md # Спецификация встроенной справки ├── build-spec.md # Спецификация сборки/разборки ├── 1c-spreadsheet-spec.md # Спецификация табличного документа -└── mxl-dsl-spec.md # Спецификация MXL DSL +├── mxl-dsl-spec.md # Спецификация MXL DSL +└── form-dsl-spec.md # Спецификация Form DSL ``` diff --git a/docs/form-dsl-spec.md b/docs/form-dsl-spec.md new file mode 100644 index 00000000..e847354d --- /dev/null +++ b/docs/form-dsl-spec.md @@ -0,0 +1,462 @@ +# Form DSL Specification + +Спецификация JSON-формата для `/form-compile` — компактного описания управляемых форм 1С:Предприятия 8.3. + +--- + +## 1. Корневой объект + +```json +{ + "title": "Заголовок формы", + "properties": { ... }, + "excludedCommands": [ ... ], + "events": { ... }, + "elements": [ ... ], + "attributes": [ ... ], + "parameters": [ ... ], + "commands": [ ... ] +} +``` + +| Поле | Тип | Описание | +|------|-----|----------| +| `title` | string | Заголовок формы (необязательный) | +| `properties` | object | Свойства формы (необязательный) | +| `excludedCommands` | string[] | Исключённые стандартные команды (необязательный) | +| `events` | object | Обработчики событий формы (необязательный) | +| `elements` | array | Дерево UI-элементов (необязательный) | +| `attributes` | array | Реквизиты формы (необязательный) | +| `parameters` | array | Параметры формы (необязательный) | +| `commands` | array | Команды формы (необязательный) | + +--- + +## 2. Properties — свойства формы + +Объект со свойствами в camelCase. Компилятор преобразует в PascalCase для XML. + +```json +"properties": { + "autoTitle": false, + "windowOpeningMode": "LockOwnerWindow", + "commandBarLocation": "Bottom" +} +``` + +### Поддерживаемые свойства + +| DSL ключ | XML элемент | Значения | +|----------|-------------|----------| +| `autoTitle` | `` | `true` / `false` | +| `windowOpeningMode` | `` | `LockOwnerWindow`, `Modeless` | +| `commandBarLocation` | `` | `Top`, `Bottom`, `None` | +| `saveDataInSettings` | `` | `UseList`, `Use`, `DontUse` | +| `autoSaveDataInSettings` | `` | `Use`, `DontUse` | +| `autoTime` | `` | `CurrentOrLast`, `Current`, `Last` | +| `usePostingMode` | `` | `Auto`, `Postings`, `Movements` | +| `repostOnWrite` | `` | `true` / `false` | +| `autoURL` | `` | `true` / `false` | +| `autoFillCheck` | `` | `true` / `false` | +| `customizable` | `` | `true` / `false` | +| `enterKeyBehavior` | `` | `DefaultButton`, `NewLine` | +| `verticalScroll` | `` | `useIfNecessary`, `Auto`, `AlwaysShow`, `Never` | +| `width` | `` | число | +| `height` | `` | число | +| `group` | `` | `Vertical`, `Horizontal`, `AlwaysHorizontal`, `AlwaysVertical` | +| `useForFoldersAndItems` | `` | `Folders`, `Items`, `FoldersAndItems` | + +Нераспознанные ключи преобразуются с автоматическим PascalCase (первая буква в верхний регистр). + +--- + +## 3. Events — обработчики событий формы + +```json +"events": { + "OnCreateAtServer": "ПриСозданииНаСервере", + "OnOpen": "ПриОткрытии" +} +``` + +Ключ — имя события, значение — имя процедуры-обработчика. + +### Доступные события + +| Событие | Описание | +|---------|----------| +| `OnCreateAtServer` | Создание формы на сервере | +| `OnOpen` | Открытие формы | +| `BeforeClose` | Перед закрытием | +| `OnClose` | При закрытии | +| `BeforeWrite` | Перед записью | +| `BeforeWriteAtServer` | Перед записью на сервере | +| `OnWriteAtServer` | При записи на сервере | +| `AfterWriteAtServer` | После записи на сервере | +| `AfterWrite` | После записи | +| `OnReadAtServer` | При чтении объекта | +| `NotificationProcessing` | Обработка оповещений | +| `ChoiceProcessing` | Обработка выбора | +| `FillCheckProcessingAtServer` | Проверка заполнения | + +--- + +## 4. Elements — дерево UI-элементов + +Массив объектов. Тип элемента определяется ключом-идентификатором. + +### 4.1. Общие свойства всех элементов + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `name` | string | Имя элемента (по умолчанию — из значения ключа типа) | +| `title` | string | Заголовок | +| `hidden` | bool | `true` → `false` | +| `disabled` | bool | `true` → `false` | +| `readOnly` | bool | `true` → `true` | +| `on` | string[] | Массив имён событий | +| `handlers` | object | Явные имена обработчиков: `{"OnChange": "МойОбработчик"}` | + +### 4.2. Автоименование обработчиков + +При указании `"on"` без `"handlers"` имя обработчика генерируется автоматически: + +``` +<ИмяЭлемента><РусскийСуффикс> +``` + +| Событие | Суффикс | +|---------|---------| +| `OnChange` | `ПриИзменении` | +| `StartChoice` | `НачалоВыбора` | +| `ChoiceProcessing` | `ОбработкаВыбора` | +| `AutoComplete` | `АвтоПодбор` | +| `Clearing` | `Очистка` | +| `Opening` | `Открытие` | +| `Click` | `Нажатие` | +| `OnActivateRow` | `ПриАктивизацииСтроки` | +| `BeforeAddRow` | `ПередНачаломДобавления` | +| `BeforeDeleteRow` | `ПередУдалением` | +| `BeforeRowChange` | `ПередНачаломИзменения` | +| `OnStartEdit` | `ПриНачалеРедактирования` | +| `OnEndEdit` | `ПриОкончанииРедактирования` | +| `Selection` | `ВыборСтроки` | +| `OnCurrentPageChange` | `ПриСменеСтраницы` | +| `TextEditEnd` | `ОкончаниеВводаТекста` | + +Пример: элемент `Контрагент` + событие `OnChange` → обработчик `КонтрагентПриИзменении`. + +### 4.3. Типы элементов + +#### group — UsualGroup + +```json +{ "group": "horizontal", "name": "ГруппаШапка", "children": [ ... ] } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `group` | string | Ориентация: `horizontal`, `vertical`, `alwaysHorizontal`, `alwaysVertical`, `collapsible` | +| `children` | array | Вложенные элементы | +| `showTitle` | bool | Показывать заголовок группы | +| `representation` | string | `none`, `normal`, `weak`, `strong` | +| `united` | bool | Объединение | + +#### input — InputField + +```json +{ "input": "Организация", "path": "Объект.Организация", "on": ["OnChange"] } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `path` | string | DataPath | +| `multiLine` | bool | Многострочный режим | +| `passwordMode` | bool | Режим пароля | +| `titleLocation` | string | `none`, `left`, `right`, `top`, `bottom` | +| `choiceButton` | bool | Показывать кнопку выбора | +| `clearButton` | bool | Показывать кнопку очистки | +| `spinButton` | bool | Показывать кнопку прокрутки | +| `dropListButton` | bool | Показывать кнопку раскрытия | +| `markIncomplete` | bool | Автопометка незаполненных | +| `skipOnInput` | bool | Пропускать при вводе | +| `inputHint` | string | Подсказка ввода (placeholder) | +| `width` | int | Ширина | +| `height` | int | Высота | +| `horizontalStretch` | bool | Растягивание по горизонтали | +| `verticalStretch` | bool | Растягивание по вертикали | +| `autoMaxWidth` | bool | Автомаксимальная ширина | +| `autoMaxHeight` | bool | Автомаксимальная высота | + +#### check — CheckBoxField + +```json +{ "check": "ФлагАктивности", "path": "Активен", "on": ["OnChange"] } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `path` | string | DataPath | +| `titleLocation` | string | Расположение заголовка | + +#### label — LabelDecoration + +```json +{ "label": "Подсказка", "title": "Выберите параметры", "hyperlink": true } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `title` | string | Текст надписи | +| `hyperlink` | bool | Режим гиперссылки | +| `width` | int | Ширина | +| `height` | int | Высота | +| `autoMaxWidth` | bool | Автомаксимальная ширина | +| `autoMaxHeight` | bool | Автомаксимальная высота | + +#### labelField — LabelField + +```json +{ "labelField": "СтатусОбработки", "path": "Статус" } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `path` | string | DataPath | +| `hyperlink` | bool | Режим гиперссылки | + +#### table — Table + +```json +{ + "table": "Товары", "path": "Объект.Товары", + "columns": [ + { "input": "Номенклатура", "path": "Объект.Товары.Номенклатура" } + ] +} +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `path` | string | DataPath | +| `columns` | array | Колонки (элементы input/check/labelField/picField) | +| `representation` | string | `List`, `Tree`, `HierarchicalList` | +| `changeRowSet` | bool | Разрешить добавление/удаление строк | +| `changeRowOrder` | bool | Разрешить перемещение строк | +| `height` | int | Высота в строках таблицы | +| `header` | bool | Показывать шапку | +| `footer` | bool | Показывать подвал | +| `commandBarLocation` | string | `None`, `Top`, `Bottom`, `Auto` | +| `searchStringLocation` | string | `None`, `Top`, `Bottom`, `CommandBar`, `Auto` | + +#### pages / page — Pages / Page + +```json +{ + "pages": "Страницы", "children": [ + { "page": "Основное", "children": [ ... ] }, + { "page": "Дополнительно", "children": [ ... ] } + ] +} +``` + +Page поддерживает `group` для задания ориентации содержимого и `children` для вложенных элементов. + +Pages поддерживает `pagesRepresentation`: `None`, `TabsOnTop`, `TabsOnBottom`, `TabsOnLeft`, `TabsOnRight`. + +#### button — Button + +```json +{ "button": "Загрузить", "command": "Загрузить", "defaultButton": true } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `command` | string | Имя команды формы (→ `Form.Command.`) | +| `stdCommand` | string | Стандартная команда (→ `Form.StandardCommand.`) | +| `type` | string | `usual`, `hyperlink`, `commandBar` | +| `defaultButton` | bool | Кнопка по умолчанию | +| `picture` | string | Ссылка на картинку (`StdPicture.Name`) | +| `representation` | string | `Auto`, `Picture`, `Text`, `PictureAndText` | +| `locationInCommandBar` | string | `InCommandBar`, `InAdditionalSubmenu` | + +#### picture — PictureDecoration + +```json +{ "picture": "Логотип", "src": "CommonPicture.Логотип" } +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `src` или `picture` (как свойство) | string | Ссылка на картинку | +| `hyperlink` | bool | Режим гиперссылки | +| `width` | int | Ширина | +| `height` | int | Высота | + +#### picField — PictureField + +```json +{ "picField": "Фото", "path": "Фотография" } +``` + +#### calendar — CalendarField + +```json +{ "calendar": "Дата", "path": "ДатаОтчета" } +``` + +#### cmdBar — CommandBar + +```json +{ "cmdBar": "КоманднаяПанель", "children": [ ... ] } +``` + +#### popup — Popup + +```json +{ "popup": "Печать", "picture": "StdPicture.Print", "children": [ ... ] } +``` + +--- + +## 5. Attributes — реквизиты формы + +```json +"attributes": [ + { "name": "Объект", "type": "DocumentObject.Реализация", "main": true }, + { "name": "Итого", "type": "decimal(15,2)" }, + { "name": "Таблица", "type": "ValueTable", "columns": [ + { "name": "Номенклатура", "type": "CatalogRef.Номенклатура" }, + { "name": "Количество", "type": "decimal(10,3)" } + ]} +] +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `name` | string | Имя реквизита (обязательно) | +| `type` | string | Тип (shorthand) | +| `main` | bool | Основной реквизит формы | +| `title` | string | Заголовок | +| `savedData` | bool | Сохраняемые данные | +| `fillChecking` | string | `Show`, `DontShow` | +| `columns` | array | Колонки для ValueTable/ValueTree | + +--- + +## 6. Parameters — параметры формы + +```json +"parameters": [ + { "name": "Ключ", "type": "DocumentRef.Реализация", "key": true }, + { "name": "Основание", "type": "DocumentRef.Реализация" } +] +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `name` | string | Имя параметра (обязательно) | +| `type` | string | Тип (shorthand) | +| `key` | bool | Ключевой параметр | + +--- + +## 7. Commands — команды формы + +```json +"commands": [ + { "name": "Печать", "action": "ПечатьОбработка", "shortcut": "Ctrl+P" }, + { "name": "Обновить", "action": "ОбновитьОбработка", "picture": "StdPicture.Refresh" } +] +``` + +| Свойство | Тип | Описание | +|----------|-----|----------| +| `name` | string | Имя команды (обязательно) | +| `action` | string | Имя процедуры-обработчика | +| `title` | string | Заголовок | +| `shortcut` | string | Клавиатурное сочетание | +| `picture` | string | Ссылка на картинку | +| `representation` | string | `Auto`, `Picture`, `Text`, `PictureAndText` | + +--- + +## 8. Система типов (shorthand) + +### Примитивные типы + +| DSL | XML | +|-----|-----| +| `"string"` | `xs:string` (неограниченная) | +| `"string(100)"` | `xs:string` + Length=100 | +| `"decimal(15,2)"` | `xs:decimal` + Digits=15, FractionDigits=2, AllowedSign=Any | +| `"decimal(10,0,nonneg)"` | `xs:decimal` + AllowedSign=Nonnegative | +| `"boolean"` | `xs:boolean` | +| `"date"` | `xs:dateTime` + DateFractions=Date | +| `"dateTime"` | `xs:dateTime` + DateFractions=DateTime | +| `"time"` | `xs:dateTime` + DateFractions=Time | + +### Ссылочные типы + +| DSL | XML | +|-----|-----| +| `"CatalogRef.Организации"` | `cfg:CatalogRef.Организации` | +| `"DocumentObject.Реализация"` | `cfg:DocumentObject.Реализация` | +| `"EnumRef.СтавкиНДС"` | `cfg:EnumRef.СтавкиНДС` | +| `"DataProcessorObject.ЗагрузкаДанных"` | `cfg:DataProcessorObject.ЗагрузкаДанных` | + +### Платформенные типы + +| DSL | XML | +|-----|-----| +| `"ValueTable"` | `v8:ValueTable` | +| `"ValueTree"` | `v8:ValueTree` | +| `"ValueList"` | `v8:ValueListType` | +| `"FormattedString"` | `v8ui:FormattedString` | +| `"Picture"` | `v8ui:Picture` | +| `"DynamicList"` | `cfg:DynamicList` | + +### Составные типы + +Разделитель `" | "`: + +```json +"type": "CatalogRef.Организации | CatalogRef.ИндивидуальныеПредприниматели" +``` + +--- + +## 9. Автогенерация + +### Companion-элементы + +Для каждого элемента автоматически создаются служебные вложенные элементы: + +| Тип элемента | Companions | +|---|---| +| UsualGroup | ExtendedTooltip | +| InputField | ContextMenu, ExtendedTooltip | +| CheckBoxField | ContextMenu, ExtendedTooltip | +| LabelDecoration | ContextMenu, ExtendedTooltip | +| LabelField | ContextMenu, ExtendedTooltip | +| PictureDecoration | ContextMenu, ExtendedTooltip | +| PictureField | ContextMenu, ExtendedTooltip | +| CalendarField | ContextMenu, ExtendedTooltip | +| Table | ContextMenu, AutoCommandBar, SearchStringAddition, ViewStatusAddition, SearchControlAddition | +| Pages | ExtendedTooltip | +| Page | ExtendedTooltip | +| Button | ExtendedTooltip | + +Именование: `КонтекстноеМеню`, `РасширеннаяПодсказка`, `КоманднаяПанель`, `СтрокаПоиска`, `СостояниеПросмотра`, `УправлениеПоиском`. + +### ID + +Последовательная нумерация начиная с 1. `AutoCommandBar` формы всегда имеет `id="-1"`. + +### Namespace + +Все 17 namespace-деклараций добавляются автоматически (version="2.17"). + +### Кодировка + +UTF-8 с BOM (как в файлах конфигурации 1С). diff --git a/docs/form-guide.md b/docs/form-guide.md index cbc4e7ee..6eff4648 100644 --- a/docs/form-guide.md +++ b/docs/form-guide.md @@ -1,12 +1,13 @@ # Управляемые формы (Form) -Навыки группы `/form-*` позволяют анализировать управляемые формы 1С:Предприятия 8.3 из XML-исходников конфигурации, не читая тысячи строк XML. +Навыки группы `/form-*` позволяют анализировать и генерировать управляемые формы 1С:Предприятия 8.3 из XML-исходников, не читая тысячи строк XML. ## Навыки | Навык | Параметры | Описание | |-------|-----------|----------| | `/form-info` | `` | Компактная сводка: дерево элементов, реквизиты, команды, события | +| `/form-compile` | ` ` | Генерация Form.xml из компактного JSON-определения | ## Сценарии использования @@ -120,22 +121,68 @@ Claude сделает grep по Form.xml и найдёт полный XML-бло - `Таблица: ValueTable [Кол1: тип, Кол2: тип]` — таблица значений с колонками - `Список: DynamicList -> Catalog.Пользователи` — динамический список с основной таблицей +## Генерация формы с нуля + +`/form-compile` принимает компактное JSON-определение (20–50 строк) и генерирует полный Form.xml (100–500+ строк) с namespace-декларациями, companion-элементами и последовательными ID. + +``` +> Создай форму загрузки данных с полями Организация, Контрагент, таблицей Товары и кнопкой Загрузить +``` + +Claude создаст JSON-определение и вызовет `/form-compile`. Пример JSON: + +```json +{ + "properties": { "autoTitle": false }, + "events": { "OnCreateAtServer": "ПриСозданииНаСервере" }, + "elements": [ + { "group": "horizontal", "name": "Шапка", "children": [ + { "input": "Организация", "path": "Объект.Организация", "on": ["OnChange"] }, + { "input": "Контрагент", "path": "Объект.Контрагент" } + ]}, + { "table": "Товары", "path": "Объект.Товары", "columns": [ + { "input": "Номенклатура", "path": "Объект.Товары.Номенклатура" }, + { "input": "Сумма", "path": "Объект.Товары.Сумма", "readOnly": true } + ]}, + { "button": "Загрузить", "command": "Загрузить" } + ], + "attributes": [ + { "name": "Объект", "type": "DataProcessorObject.ЗагрузкаДанных", "main": true } + ], + "commands": [ + { "name": "Загрузить", "action": "ЗагрузитьОбработка" } + ] +} +``` + +### Верификация результата + +После компиляции используйте `/form-info` для проверки: + +``` +> /form-info src/МояОбработка/Forms/Форма/Ext/Form.xml +``` + +Структура в сводке должна совпадать с определением в JSON. + ## Примеры слеш-команд ``` > /form-info upload/acc_8.3.24/Documents/РеализацияТоваровУслуг/Forms/ФормаДокумента/Ext/Form.xml > /form-info src/МояОбработка/Forms/Форма/Ext/Form.xml +> /form-compile src/form.json src/МояОбработка/Forms/Форма/Ext/Form.xml ``` ## Связь с EPF-навыками -`/form-info` работает с формами из любых источников — конфигурации и внешних обработок. При работе с обработками: +`/form-info` и `/form-compile` работают с формами из любых источников — конфигурации и внешних обработок. При работе с обработками: -1. `/epf-add-form` — создать форму -2. `/form-info` — проанализировать существующую форму -3. Модифицировать Form.xml, ориентируясь на сводку +1. `/epf-add-form` — создать форму (каркас) +2. `/form-compile` — сгенерировать Form.xml из JSON-определения +3. `/form-info` — проверить результат 4. `/epf-build` — собрать EPF ## Спецификации - [Управляемая форма](1c-form-spec.md) — Form.xml, элементы, команды, реквизиты, система типов +- [Form DSL](form-dsl-spec.md) — JSON-формат описания формы для `/form-compile`