# form-compile v1.0 — Compile 1C managed form from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
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$tag>"
}
# --- 5. Type emitter ---
$script:formTypeSynonyms = New-Object System.Collections.Hashtable
$script:formTypeSynonyms["строка"] = "string"
$script:formTypeSynonyms["число"] = "decimal"
$script:formTypeSynonyms["булево"] = "boolean"
$script:formTypeSynonyms["дата"] = "date"
$script:formTypeSynonyms["датавремя"]= "dateTime"
$script:formTypeSynonyms["number"] = "decimal"
$script:formTypeSynonyms["bool"] = "boolean"
$script:formTypeSynonyms["справочникссылка"] = "CatalogRef"
$script:formTypeSynonyms["справочникобъект"] = "CatalogObject"
$script:formTypeSynonyms["документссылка"] = "DocumentRef"
$script:formTypeSynonyms["документобъект"] = "DocumentObject"
$script:formTypeSynonyms["перечислениессылка"] = "EnumRef"
$script:formTypeSynonyms["плансчетовссылка"] = "ChartOfAccountsRef"
$script:formTypeSynonyms["планвидовхарактеристикссылка"] = "ChartOfCharacteristicTypesRef"
$script:formTypeSynonyms["планвидоврасчётассылка"] = "ChartOfCalculationTypesRef"
$script:formTypeSynonyms["планвидоврасчетассылка"] = "ChartOfCalculationTypesRef"
$script:formTypeSynonyms["планобменассылка"] = "ExchangePlanRef"
$script:formTypeSynonyms["бизнеспроцессссылка"] = "BusinessProcessRef"
$script:formTypeSynonyms["задачассылка"] = "TaskRef"
$script:formTypeSynonyms["определяемыйтип"] = "DefinedType"
function Resolve-TypeStr {
param([string]$typeStr)
if (-not $typeStr) { return $typeStr }
if ($typeStr -match '^([^(]+)\((.+)\)$') {
$base = $Matches[1].Trim(); $params = $Matches[2]
$r = $script:formTypeSynonyms[$base.ToLower()]
if ($r) { return "$r($params)" }
return $typeStr
}
if ($typeStr.Contains('.')) {
$i = $typeStr.IndexOf('.')
$prefix = $typeStr.Substring(0, $i); $suffix = $typeStr.Substring($i)
$r = $script:formTypeSynonyms[$prefix.ToLower()]
if ($r) { return "$r$suffix" }
return $typeStr
}
$r = $script:formTypeSynonyms[$typeStr.ToLower()]
if ($r) { return $r }
return $typeStr
}
function Emit-Type {
param($typeStr, [string]$indent)
if (-not $typeStr) {
X "$indent"
return
}
$typeString = "$typeStr"
# Composite type: "Type1 | Type2" or "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)
$typeStr = Resolve-TypeStr $typeStr
# 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)"
}
$script:knownEvents = @{
"input" = @("OnChange","StartChoice","ChoiceProcessing","AutoComplete","TextEditEnd","Clearing","Creating","EditTextChange")
"check" = @("OnChange")
"label" = @("Click","URLProcessing")
"labelField"= @("OnChange","StartChoice","ChoiceProcessing","Click","URLProcessing","Clearing")
"table" = @("Selection","BeforeAddRow","AfterDeleteRow","BeforeDeleteRow","OnActivateRow","OnEditEnd","OnStartEdit","BeforeRowChange","BeforeEditEnd","ValueChoice","OnActivateCell","OnActivateField","Drag","DragStart","DragCheck","DragEnd","OnGetDataAtServer","BeforeLoadUserSettingsAtServer","OnUpdateUserSettingSetAtServer","OnChange")
"pages" = @("OnCurrentPageChange")
"page" = @("OnCurrentPageChange")
"button" = @("Click")
"picField" = @("OnChange","StartChoice","ChoiceProcessing","Click","Clearing")
"calendar" = @("OnChange","OnActivate")
"picture" = @("Click")
"cmdBar" = @()
"popup" = @()
"group" = @()
}
$script:knownFormEvents = @("OnCreateAtServer","OnOpen","BeforeClose","OnClose","NotificationProcessing","ChoiceProcessing","OnReadAtServer","AfterWriteAtServer","BeforeWriteAtServer","AfterWrite","BeforeWrite","OnWriteAtServer","FillCheckProcessingAtServer","OnLoadDataFromSettingsAtServer","BeforeLoadDataFromSettingsAtServer","OnSaveDataInSettingsAtServer","ExternalEvent","OnReopen","Opening")
function Emit-Events {
param($el, [string]$elementName, [string]$indent, [string]$typeKey)
if (-not $el.on) { return }
# Validate event names
if ($typeKey -and $script:knownEvents.ContainsKey($typeKey)) {
$allowed = $script:knownEvents[$typeKey]
foreach ($evt in $el.on) {
if ($allowed.Count -gt 0 -and $allowed -notcontains "$evt") {
Write-Host "[WARN] Unknown event '$evt' for $typeKey '$elementName'. Known: $($allowed -join ', ')"
}
}
}
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
}
# Validate known keys — warn about typos and unknown properties
$knownKeys = @{
# type keys
"group"=1;"input"=1;"check"=1;"label"=1;"labelField"=1;"table"=1;"pages"=1;"page"=1
"button"=1;"picture"=1;"picField"=1;"calendar"=1;"cmdBar"=1;"popup"=1
# naming & binding
"name"=1;"path"=1;"title"=1
# visibility & state
"visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1
# events
"on"=1;"handlers"=1
# layout
"titleLocation"=1;"representation"=1;"width"=1;"height"=1
"horizontalStretch"=1;"verticalStretch"=1;"autoMaxWidth"=1;"autoMaxHeight"=1
# input-specific
"multiLine"=1;"passwordMode"=1;"choiceButton"=1;"clearButton"=1
"spinButton"=1;"dropListButton"=1;"markIncomplete"=1;"skipOnInput"=1;"inputHint"=1
# label/hyperlink
"hyperlink"=1
# group-specific
"showTitle"=1;"united"=1
# hierarchy
"children"=1;"columns"=1
# table-specific
"changeRowSet"=1;"changeRowOrder"=1;"header"=1;"footer"=1
"commandBarLocation"=1;"searchStringLocation"=1
# pages-specific
"pagesRepresentation"=1
# button-specific
"type"=1;"command"=1;"stdCommand"=1;"defaultButton"=1;"locationInCommandBar"=1
# picture/decoration
"src"=1
# cmdBar-specific
"autofill"=1
}
foreach ($p in $el.PSObject.Properties) {
if (-not $knownKeys.ContainsKey($p.Name)) {
Write-Warning "Element '$($el.$typeKey)': unknown key '$($p.Name)' — ignored. Check SKILL.md for valid keys."
}
}
$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.visible -eq $false -or $el.hidden -eq $true) { X "$indentfalse" }
if ($el.enabled -eq $false -or $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 -typeKey "input"
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 -typeKey "check"
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"
X "$inner`t`tru"
X "$inner`t`t$(Esc-Xml "$($el.title)")"
X "$inner`t"
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 -typeKey "label"
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 -typeKey "labelField"
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" }
if ($el.footer -eq $true) { X "$inner" }
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 -typeKey "table"
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 -typeKey "pages"
# 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 -typeKey "picture"
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 -typeKey "picField"
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 -typeKey "calendar"
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$xmlName>"
}
}
# --- 12. Main compilation ---
# Title
if ($def.title) {
Emit-MLText -tag "Title" -text "$($def.title)" -indent "`t"
}
# Header
X ''
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)
# --- 13b. Auto-register form in parent object XML ---
# Infer parent from OutputPath: .../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml
$formXmlDir = [System.IO.Path]::GetDirectoryName($outPath)
$formNameDir = [System.IO.Path]::GetDirectoryName($formXmlDir)
$formsDir = [System.IO.Path]::GetDirectoryName($formNameDir)
$objectDir = [System.IO.Path]::GetDirectoryName($formsDir)
$typePluralDir = [System.IO.Path]::GetDirectoryName($objectDir)
$formName = [System.IO.Path]::GetFileName($formNameDir)
$objectName = [System.IO.Path]::GetFileName($objectDir)
$formsLeaf = [System.IO.Path]::GetFileName($formsDir)
if ($formsLeaf -eq 'Forms') {
$objectXmlPath = Join-Path $typePluralDir "$objectName.xml"
if (Test-Path $objectXmlPath) {
$objDoc = New-Object System.Xml.XmlDocument
$objDoc.PreserveWhitespace = $true
$objDoc.Load($objectXmlPath)
$nsMgr = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$childObjects = $objDoc.SelectSingleNode("//md:ChildObjects", $nsMgr)
if ($childObjects) {
$existing = $childObjects.SelectSingleNode("md:Form[text()='$formName']", $nsMgr)
if (-not $existing) {
$formElem = $objDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
$formElem.InnerText = $formName
$insertBefore = $childObjects.SelectSingleNode("md:Template", $nsMgr)
if (-not $insertBefore) { $insertBefore = $childObjects.SelectSingleNode("md:TabularSection", $nsMgr) }
if ($insertBefore) {
$childObjects.InsertBefore($formElem, $insertBefore) | Out-Null
$ws = $objDoc.CreateWhitespace("`n`t`t`t")
$childObjects.InsertBefore($ws, $insertBefore) | Out-Null
} else {
$lastChild = $childObjects.LastChild
if ($lastChild -and $lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
$childObjects.InsertBefore($objDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
} else {
$childObjects.AppendChild($objDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
$childObjects.AppendChild($formElem) | Out-Null
$childObjects.AppendChild($objDoc.CreateWhitespace("`n`t`t")) | Out-Null
}
}
$regEnc = New-Object System.Text.UTF8Encoding($true)
$regSettings = New-Object System.Xml.XmlWriterSettings
$regSettings.Encoding = $regEnc
$regSettings.Indent = $false
$regStream = New-Object System.IO.FileStream($objectXmlPath, [System.IO.FileMode]::Create)
$regWriter = [System.Xml.XmlWriter]::Create($regStream, $regSettings)
$objDoc.Save($regWriter)
$regWriter.Close()
$regStream.Close()
Write-Host " Registered: in $objectName.xml"
}
}
}
}
# --- 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)" }