# form-edit v1.0 — Edit 1C managed form elements # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] [Alias('Path')] [string]$FormPath, [Parameter(Mandatory)] [string]$JsonPath ) $ErrorActionPreference = "Stop" [Console]::OutputEncoding = [System.Text.Encoding]::UTF8 # === 1. Load Form.xml === if (-not (Test-Path $FormPath)) { Write-Error "File not found: $FormPath" exit 1 } if (-not (Test-Path $JsonPath)) { Write-Error "File not found: $JsonPath" exit 1 } $resolvedFormPath = (Resolve-Path $FormPath).Path $xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc.PreserveWhitespace = $true try { $xmlDoc.Load($resolvedFormPath) } catch { Write-Host "[ERROR] XML parse error: $($_.Exception.Message)" exit 1 } $formNs = "http://v8.1c.ru/8.3/xcf/logform" $v8Ns = "http://v8.1c.ru/8.1/data/core" $nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable) $nsMgr.AddNamespace("f", $formNs) $nsMgr.AddNamespace("v8", $v8Ns) $root = $xmlDoc.DocumentElement # === 2. Load JSON === $def = Get-Content -Raw -Encoding UTF8 $JsonPath | ConvertFrom-Json # === 3. Form name + header === $formName = [System.IO.Path]::GetFileNameWithoutExtension($FormPath) $parentDir = [System.IO.Path]::GetDirectoryName($resolvedFormPath) if ($parentDir) { $extDir = [System.IO.Path]::GetFileName($parentDir) if ($extDir -eq "Ext") { $formDir = [System.IO.Path]::GetDirectoryName($parentDir) if ($formDir) { $formName = [System.IO.Path]::GetFileName($formDir) } } } Write-Host "=== form-edit: $formName ===" Write-Host "" # === 4. Scan max IDs per pool === $script:nextElemId = 0 $script:nextAttrId = 0 $script:nextCmdId = 0 # Scan ALL element IDs via XPath (includes companions like ExtendedTooltip, ContextMenu) $rootCI = $root.SelectSingleNode("f:ChildItems", $nsMgr) if ($rootCI) { foreach ($elem in $rootCI.SelectNodes(".//*[@id]")) { $id = $elem.GetAttribute("id") if ($id -and $id -ne "-1") { try { $intId = [int]$id; if ($intId -gt $script:nextElemId) { $script:nextElemId = $intId } } catch {} } } } $acb = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr) if ($acb) { $id = $acb.GetAttribute("id") if ($id -and $id -ne "-1") { try { $intId = [int]$id; if ($intId -gt $script:nextElemId) { $script:nextElemId = $intId } } catch {} } } # Scan attribute IDs (including column IDs — same pool) foreach ($attr in $root.SelectNodes("f:Attributes/f:Attribute", $nsMgr)) { $id = $attr.GetAttribute("id") if ($id) { try { $intId = [int]$id; if ($intId -gt $script:nextAttrId) { $script:nextAttrId = $intId } } catch {} } # Column IDs are in the same pool as attribute IDs foreach ($col in $attr.SelectNodes("f:Columns/f:Column", $nsMgr)) { $colId = $col.GetAttribute("id") if ($colId) { try { $intColId = [int]$colId; if ($intColId -gt $script:nextAttrId) { $script:nextAttrId = $intColId } } catch {} } } } # Scan command IDs foreach ($cmd in $root.SelectNodes("f:Commands/f:Command", $nsMgr)) { $id = $cmd.GetAttribute("id") if ($id) { try { $intId = [int]$id; if ($intId -gt $script:nextCmdId) { $script:nextCmdId = $intId } } catch {} } } $script:nextElemId++ $script:nextAttrId++ $script:nextCmdId++ # --- 4b. Auto-detect extension mode (BaseForm present) --- $script:isExtension = $false $baseForm = $root.SelectSingleNode("f:BaseForm", $nsMgr) if ($baseForm) { $script:isExtension = $true if ($script:nextAttrId -lt 1000000) { $script:nextAttrId = 1000000 } if ($script:nextCmdId -lt 1000000) { $script:nextCmdId = 1000000 } if ($script:nextElemId -lt 1000000) { $script:nextElemId = 1000000 } } function New-ElemId { $id = $script:nextElemId; $script:nextElemId++; return $id } function New-AttrId { $id = $script:nextAttrId; $script:nextAttrId++; return $id } function New-CmdId { $id = $script:nextCmdId; $script:nextCmdId++; return $id } # For element emitters, New-Id = New-ElemId function New-Id { return New-ElemId } # === 5. Fragment helpers (StringBuilder + Emit-* from form-compile) === $script:xml = New-Object System.Text.StringBuilder 4096 function X { param([string]$text) $script:xml.AppendLine($text) | Out-Null } function Esc-Xml { param([string]$s) return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"') } 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" } # --- 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" $parts = $typeString -split '\s*[|+]\s*' X "$indent" foreach ($part in $parts) { Emit-SingleType -typeStr $part.Trim() -indent "$indent`t" } X "$indent" } function Emit-SingleType { param([string]$typeStr, [string]$indent) $typeStr = Resolve-TypeStr $typeStr if ($typeStr -eq "boolean") { X "$indentxs:boolean"; return } 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 } 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 } 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 } $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 } $uiTypes = @{ "FormattedString" = "v8ui:FormattedString"; "Picture" = "v8ui:Picture"; "Color" = "v8ui:Color"; "Font" = "v8ui:Font" } if ($uiTypes.ContainsKey($typeStr)) { X "$indent$($uiTypes[$typeStr])"; return } if ($typeStr -eq "DynamicList") { X "$indentcfg:DynamicList"; return } if ($typeStr -match '^DataComposition') { $dcsMap = @{ "DataCompositionSettings" = "dcsset:DataCompositionSettings"; "DataCompositionSchema" = "dcssch:DataCompositionSchema"; "DataCompositionComparisonType" = "dcscor:DataCompositionComparisonType" } if ($dcsMap.ContainsKey($typeStr)) { X "$indent$($dcsMap[$typeStr])"; return } } if ($typeStr -match '^(CatalogRef|CatalogObject|DocumentRef|DocumentObject|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef|InformationRegisterRecordSet|AccumulationRegisterRecordSet|DataProcessorObject)\.') { X "$indentcfg:$typeStr"; return } if ($typeStr.Contains('.')) { X "$indentcfg:$typeStr" } else { X "$indent$typeStr" } } # --- 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" } # --- Element helpers --- 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" = @() } 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) { $evtStr = if ($evt -is [string]) { "$evt" } else { "$($evt.event)" } if ($allowed.Count -gt 0 -and $allowed -notcontains $evtStr) { Write-Host "[WARN] Unknown event '$evtStr' for $typeKey '$elementName'. Known: $($allowed -join ', ')" } } } X "$indent" foreach ($evt in $el.on) { # Support both string ("OnChange") and object ({ "event": "OnChange", "callType": "After" }) if ($evt -is [string] -or -not $evt.event) { $evtName = "$evt" $handler = if ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" } else { Get-HandlerName -elementName $elementName -eventName $evtName } X "$indent`t$handler" } else { $evtName = "$($evt.event)" $handler = if ($evt.handler) { "$($evt.handler)" } elseif ($el.handlers -and $el.handlers.$evtName) { "$($el.handlers.$evtName)" } else { Get-HandlerName -elementName $elementName -eventName $evtName } $callTypeAttr = if ($evt.callType) { " callType=`"$($evt.callType)`"" } else { "" } 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-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 } } # --- Element emitters --- function Emit-Group { param($el, [string]$name, [int]$id, [string]$indent) X "$indent" $inner = "$indent`t" Emit-Title -el $el -name $name -indent $inner $groupVal = "$($el.group)" $orientation = switch ($groupVal) { "horizontal" { "Horizontal" } "vertical" { "Vertical" } "alwaysHorizontal" { "AlwaysHorizontal" } "alwaysVertical" { "AlwaysVertical" } default { $null } } if ($orientation) { X "$inner$orientation" } if ($groupVal -eq "collapsible") { X "$innerVertical"; X "$innerCollapsible" } if ($el.representation) { $repr = switch ("$($el.representation)") { "none" { "None" } "normal" { "NormalSeparation" } "weak" { "WeakSeparation" } "strong" { "StrongSeparation" } default { "$($el.representation)" } } X "$inner$repr" } if ($el.showTitle -eq $false) { X "$innerfalse" } if ($el.united -eq $false) { X "$innerfalse" } Emit-CommonFlags -el $el -indent $inner Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner 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 } 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)" } 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<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)" } 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" } 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
false
" } if ($el.footer -eq $true) { X "$inner" } if ($el.commandBarLocation) { X "$inner$($el.commandBarLocation)" } if ($el.searchStringLocation) { X "$inner$($el.searchStringLocation)" } 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 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 Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner Emit-Events -el $el -elementName $name -indent $inner -typeKey "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" } } Emit-Companion -tag "ExtendedTooltip" -name "${name}РасширеннаяПодсказка" -indent $inner 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)" } 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)" } 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 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-CommandBarEl { 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 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)" } 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" } # --- Element dispatcher --- function Emit-Element { param($el, [string]$indent) $typeKey = $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 $knownKeys = @{ "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 "name"=1;"path"=1;"title"=1 "visible"=1;"hidden"=1;"enabled"=1;"disabled"=1;"readOnly"=1 "on"=1;"handlers"=1 "titleLocation"=1;"representation"=1;"width"=1;"height"=1 "horizontalStretch"=1;"verticalStretch"=1;"autoMaxWidth"=1;"autoMaxHeight"=1 "multiLine"=1;"passwordMode"=1;"choiceButton"=1;"clearButton"=1 "spinButton"=1;"dropListButton"=1;"markIncomplete"=1;"skipOnInput"=1;"inputHint"=1 "hyperlink"=1;"showTitle"=1;"united"=1;"children"=1;"columns"=1 "changeRowSet"=1;"changeRowOrder"=1;"header"=1;"footer"=1 "commandBarLocation"=1;"searchStringLocation"=1;"pagesRepresentation"=1 "type"=1;"command"=1;"stdCommand"=1;"defaultButton"=1;"locationInCommandBar"=1 "src"=1;"autofill"=1 } foreach ($p in $el.PSObject.Properties) { if (-not $knownKeys.ContainsKey($p.Name)) { Write-Warning "Element '$($el.$typeKey)': unknown key '$($p.Name)' — ignored." } } $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-CommandBarEl -el $el -name $name -id $id -indent $indent } "popup" { Emit-Popup -el $el -name $name -id $id -indent $indent } } } # === 6. Find element by name recursively === function Find-Element($startNode, [string]$targetName) { foreach ($child in $startNode.ChildNodes) { if ($child.NodeType -ne 'Element') { continue } $childName = $child.GetAttribute("name") if ($childName -eq $targetName) { return $child } $ci = $child.SelectSingleNode("f:ChildItems", $nsMgr) if ($ci) { $found = Find-Element $ci $targetName if ($found) { return $found } } } return $null } # === 7. Detect indent level of a container's children === function Get-ChildIndent($container) { foreach ($child in $container.ChildNodes) { if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') { $text = $child.Value if ($text -match '^\r?\n(\t+)$') { return $Matches[1] } if ($text -match '^\r?\n(\t+)') { return $Matches[1] } } } # Fallback: count depth from root $depth = 0 $current = $container while ($current -and $current -ne $xmlDoc.DocumentElement) { $depth++ $current = $current.ParentNode } return "`t" * ($depth + 1) } # === 8. Insert node into container === function Insert-IntoContainer($container, $newNode, $afterName, $childIndent) { $refNode = $null if ($afterName) { # Find the after-element, then insert after it $afterElem = $null foreach ($child in $container.ChildNodes) { if ($child.NodeType -eq 'Element' -and $child.GetAttribute("name") -eq $afterName) { $afterElem = $child break } } if ($afterElem) { $refNode = $afterElem.NextSibling } else { Write-Host "[WARN] after='$afterName' not found in target container, appending at end" } } if (-not $refNode) { # Append at end: insert before trailing whitespace $trailing = $container.LastChild if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) { $refNode = $trailing } } $ws = $xmlDoc.CreateWhitespace("`r`n$childIndent") if ($refNode) { $container.InsertBefore($ws, $refNode) | Out-Null $container.InsertBefore($newNode, $refNode) | Out-Null } else { # Container is empty (self-closing) — add framing whitespace $container.AppendChild($ws) | Out-Null $container.AppendChild($newNode) | Out-Null $parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" } $closeWs = $xmlDoc.CreateWhitespace("`r`n$parentIndent") $container.AppendChild($closeWs) | Out-Null } } # === 9. Generate fragment, parse, import nodes === $allNsDecl = 'xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema"' function Parse-Fragment([string]$xmlText) { $fragDoc = New-Object System.Xml.XmlDocument $fragDoc.PreserveWhitespace = $true $fragDoc.LoadXml($xmlText) return $fragDoc } function Import-ElementNodes($fragDoc) { $nodes = @() foreach ($child in $fragDoc.DocumentElement.ChildNodes) { if ($child.NodeType -eq 'Element') { $nodes += $xmlDoc.ImportNode($child, $true) } } return $nodes } # === 10. Add elements === $addedElems = @() $companionCount = 0 if ($def.elements -and $def.elements.Count -gt 0) { # Resolve target container $targetCI = $null $intoName = if ($def.into) { "$($def.into)" } else { $null } $afterName = if ($def.after) { "$($def.after)" } else { $null } if ($intoName) { $targetGroup = Find-Element $rootCI $intoName if (-not $targetGroup) { Write-Host "[ERROR] Target group '$intoName' not found" exit 1 } $targetCI = $targetGroup.SelectSingleNode("f:ChildItems", $nsMgr) if (-not $targetCI) { # Create ChildItems for the group $targetCI = $xmlDoc.CreateElement("ChildItems", $formNs) $targetGroup.AppendChild($targetCI) | Out-Null } } elseif ($afterName) { # Find the after element globally and use its parent as target $afterElem = Find-Element $rootCI $afterName if (-not $afterElem) { Write-Host "[ERROR] Element '$afterName' not found" exit 1 } $targetCI = $afterElem.ParentNode } else { $targetCI = $rootCI } if (-not $targetCI) { # Create ChildItems section in form — insert after Events or AutoCommandBar $targetCI = $xmlDoc.CreateElement("ChildItems", $formNs) $insertAfter = $root.SelectSingleNode("f:Events", $nsMgr) if (-not $insertAfter) { $insertAfter = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr) } if ($insertAfter) { $refNode = $insertAfter.NextSibling $ws = $xmlDoc.CreateWhitespace("`r`n`t") $root.InsertBefore($ws, $refNode) | Out-Null $root.InsertBefore($targetCI, $refNode) | Out-Null } else { $root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null $root.AppendChild($targetCI) | Out-Null } # Also update $rootCI reference $rootCI = $targetCI } # Detect indent level $childIndent = Get-ChildIndent $targetCI # Check for duplicate element names foreach ($el in $def.elements) { $typeKey = $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 ($typeKey) { $elName = Get-ElementName -el $el -typeKey $typeKey $existing = Find-Element $rootCI $elName if ($existing) { Write-Host "[WARN] Element '$elName' already exists in form (id=$($existing.GetAttribute('id')))" } } } # Remember starting element ID for companion counting $startElemId = $script:nextElemId # Generate fragment $script:xml = New-Object System.Text.StringBuilder 4096 X "<_F $allNsDecl>" foreach ($el in $def.elements) { Emit-Element -el $el -indent $childIndent } X "" $fragDoc = Parse-Fragment $script:xml.ToString() $importedNodes = Import-ElementNodes $fragDoc # Count actual elements (non-companion) for reporting foreach ($el in $def.elements) { $typeKey = $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 } } $name = Get-ElementName -el $el -typeKey $typeKey $tagMap = @{ "group"="Group"; "input"="Input"; "check"="Check"; "label"="Label"; "labelField"="LabelField" "table"="Table"; "pages"="Pages"; "page"="Page"; "button"="Button" "picture"="Picture"; "picField"="PicField"; "calendar"="Calendar"; "cmdBar"="CmdBar"; "popup"="Popup" } $pathStr = if ($el.path) { " -> $($el.path)" } else { "" } $evtStr = if ($el.on) { " {$($el.on -join ', ')}" } else { "" } $addedElems += " + [$($tagMap[$typeKey])] $name$pathStr$evtStr" } # Insert each imported node foreach ($node in $importedNodes) { Insert-IntoContainer -container $targetCI -newNode $node -afterName $afterName -childIndent $childIndent # Only use afterName for the first insertion; subsequent ones go after the previous $afterName = $node.GetAttribute("name") } $totalNewElemIds = $script:nextElemId - $startElemId $companionCount = $totalNewElemIds - $def.elements.Count } # === 11. Add attributes === $addedAttrs = @() if ($def.attributes -and $def.attributes.Count -gt 0) { $attrsSection = $root.SelectSingleNode("f:Attributes", $nsMgr) if (-not $attrsSection) { # Create Attributes section — insert after ChildItems or after Events $attrsSection = $xmlDoc.CreateElement("Attributes", $formNs) # Find insertion point: after ChildItems or after the last pre-Attributes element $insertAfter = $rootCI if (-not $insertAfter) { $insertAfter = $root.SelectSingleNode("f:Events", $nsMgr) } if (-not $insertAfter) { $insertAfter = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr) } if ($insertAfter) { $refNode = $insertAfter.NextSibling $ws = $xmlDoc.CreateWhitespace("`r`n`t") $root.InsertBefore($ws, $refNode) | Out-Null $root.InsertBefore($attrsSection, $refNode) | Out-Null } else { $root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null $root.AppendChild($attrsSection) | Out-Null } } # Detect indent for attribute children $attrChildIndent = Get-ChildIndent $attrsSection if (-not $attrChildIndent -or $attrChildIndent -eq "") { $attrChildIndent = "`t`t" } # Generate attribute fragments $script:xml = New-Object System.Text.StringBuilder 2048 X "<_F $allNsDecl>" foreach ($attr in $def.attributes) { $attrId = New-AttrId $attrName = "$($attr.name)" X "$attrChildIndent" $inner = "$attrChildIndent`t" if ($attr.title) { Emit-MLText -tag "Title" -text "$($attr.title)" -indent $inner } 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)" } if ($attr.columns -and $attr.columns.Count -gt 0) { X "$inner" $colId = 1 foreach ($col in $attr.columns) { 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" $colId++ } X "$inner" } X "$attrChildIndent" $typeStr = if ($attr.type) { "$($attr.type)" } else { "(no type)" } $addedAttrs += " + ${attrName}: $typeStr (id=$attrId)" } X "" $fragDoc = Parse-Fragment $script:xml.ToString() $importedAttrs = Import-ElementNodes $fragDoc foreach ($node in $importedAttrs) { Insert-IntoContainer -container $attrsSection -newNode $node -afterName $null -childIndent $attrChildIndent } } # === 12. Add commands === $addedCmds = @() if ($def.commands -and $def.commands.Count -gt 0) { $cmdsSection = $root.SelectSingleNode("f:Commands", $nsMgr) if (-not $cmdsSection) { # Create Commands section — insert after Parameters or Attributes $cmdsSection = $xmlDoc.CreateElement("Commands", $formNs) $insertAfter = $root.SelectSingleNode("f:Parameters", $nsMgr) if (-not $insertAfter) { $insertAfter = $root.SelectSingleNode("f:Attributes", $nsMgr) } if (-not $insertAfter) { $insertAfter = $rootCI } if ($insertAfter) { $refNode = $insertAfter.NextSibling $ws = $xmlDoc.CreateWhitespace("`r`n`t") $root.InsertBefore($ws, $refNode) | Out-Null $root.InsertBefore($cmdsSection, $refNode) | Out-Null } else { $root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null $root.AppendChild($cmdsSection) | Out-Null } } $cmdChildIndent = Get-ChildIndent $cmdsSection if (-not $cmdChildIndent -or $cmdChildIndent -eq "") { $cmdChildIndent = "`t`t" } # Generate command fragments $script:xml = New-Object System.Text.StringBuilder 1024 X "<_F $allNsDecl>" foreach ($cmd in $def.commands) { $cmdId = New-CmdId $cmdName = "$($cmd.name)" X "$cmdChildIndent" $inner = "$cmdChildIndent`t" if ($cmd.title) { Emit-MLText -tag "Title" -text "$($cmd.title)" -indent $inner } # Support single action with optional callType, or multiple actions if ($cmd.actions) { # Multiple actions: [{ "callType": "Before", "handler": "..." }, ...] foreach ($act in $cmd.actions) { $actHandler = "$($act.handler)" $callTypeAttr = if ($act.callType) { " callType=`"$($act.callType)`"" } else { "" } X "$inner$actHandler" } } elseif ($cmd.action) { $callTypeAttr = if ($cmd.callType) { " callType=`"$($cmd.callType)`"" } else { "" } 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 "$cmdChildIndent" $actionStr = if ($cmd.action) { " -> $($cmd.action)" } elseif ($cmd.actions) { " -> $($cmd.actions.Count) action(s)" } else { "" } $addedCmds += " + ${cmdName}${actionStr} (id=$cmdId)" } X "" $fragDoc = Parse-Fragment $script:xml.ToString() $importedCmds = Import-ElementNodes $fragDoc foreach ($node in $importedCmds) { Insert-IntoContainer -container $cmdsSection -newNode $node -afterName $null -childIndent $cmdChildIndent } } # === 12b. Add form-level events === $addedFormEvents = @() if ($def.formEvents -and $def.formEvents.Count -gt 0) { $eventsSection = $root.SelectSingleNode("f:Events", $nsMgr) if (-not $eventsSection) { # Create Events section — insert after AutoCommandBar or at the beginning $eventsSection = $xmlDoc.CreateElement("Events", $formNs) $insertAfter = $root.SelectSingleNode("f:AutoCommandBar", $nsMgr) if ($insertAfter) { # Insert after AutoCommandBar (Events come after AutoCommandBar in 1C) $ws1 = $xmlDoc.CreateWhitespace("`r`n`t") $ws2 = $xmlDoc.CreateWhitespace("`r`n`t") if ($insertAfter.NextSibling) { $root.InsertBefore($ws1, $insertAfter.NextSibling) | Out-Null $root.InsertBefore($eventsSection, $ws1) | Out-Null $root.InsertBefore($ws2, $eventsSection) | Out-Null } else { $root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null $root.AppendChild($eventsSection) | Out-Null $root.AppendChild($xmlDoc.CreateWhitespace("`r`n")) | Out-Null } } else { $firstChild = $root.FirstChild if ($firstChild) { $ws = $xmlDoc.CreateWhitespace("`r`n`t") $root.InsertBefore($eventsSection, $firstChild) | Out-Null $root.InsertBefore($ws, $eventsSection) | Out-Null } else { $root.AppendChild($xmlDoc.CreateWhitespace("`r`n`t")) | Out-Null $root.AppendChild($eventsSection) | Out-Null } } } $evtChildIndent = Get-ChildIndent $eventsSection if (-not $evtChildIndent -or $evtChildIndent -eq "") { $evtChildIndent = "`t`t" } # Generate event fragments $script:xml = New-Object System.Text.StringBuilder 512 X "<_F $allNsDecl>" foreach ($fe in $def.formEvents) { $feName = "$($fe.name)" $feHandler = "$($fe.handler)" $callTypeAttr = if ($fe.callType) { " callType=`"$($fe.callType)`"" } else { "" } X "$evtChildIndent$feHandler" $ctStr = if ($fe.callType) { "[$($fe.callType)]" } else { "" } $addedFormEvents += " + $feName${ctStr} -> $feHandler" } X "" $fragDoc = Parse-Fragment $script:xml.ToString() $importedEvents = Import-ElementNodes $fragDoc foreach ($node in $importedEvents) { Insert-IntoContainer -container $eventsSection -newNode $node -afterName $null -childIndent $evtChildIndent } } # === 12c. Add element-level events === $addedElemEvents = @() if ($def.elementEvents -and $def.elementEvents.Count -gt 0) { if (-not $rootCI) { $rootCI = $root.SelectSingleNode("f:ChildItems", $nsMgr) } foreach ($ee in $def.elementEvents) { $targetName = "$($ee.element)" $targetEl = Find-Element $rootCI $targetName if (-not $targetEl) { Write-Host "[WARN] Element '$targetName' not found — skipping elementEvent" continue } # Find or create Events element within the target $targetEvents = $targetEl.SelectSingleNode("f:Events", $nsMgr) if (-not $targetEvents) { $targetEvents = $xmlDoc.CreateElement("Events", $formNs) # Insert Events before closing tag (after last property, before ChildItems if any) $ciNode = $targetEl.SelectSingleNode("f:ChildItems", $nsMgr) if ($ciNode) { $ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl)) $targetEl.InsertBefore($ws, $ciNode) | Out-Null $targetEl.InsertBefore($targetEvents, $ciNode) | Out-Null } else { $trailing = $targetEl.LastChild if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) { $ws = $xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl)) $targetEl.InsertBefore($ws, $trailing) | Out-Null $targetEl.InsertBefore($targetEvents, $trailing) | Out-Null } else { $targetEl.AppendChild($xmlDoc.CreateWhitespace("`r`n" + (Get-ChildIndent $targetEl))) | Out-Null $targetEl.AppendChild($targetEvents) | Out-Null } } } $eeChildIndent = Get-ChildIndent $targetEvents if (-not $eeChildIndent -or $eeChildIndent -eq "") { $parentIndent = Get-ChildIndent $targetEl $eeChildIndent = "$parentIndent`t" } # Create Event element $eeName = "$($ee.name)" $eeHandler = "$($ee.handler)" $callTypeAttr = if ($ee.callType) { " callType=`"$($ee.callType)`"" } else { "" } $script:xml = New-Object System.Text.StringBuilder 256 X "<_F $allNsDecl>" X "$eeChildIndent$eeHandler" X "" $fragDoc = Parse-Fragment $script:xml.ToString() $importedEE = Import-ElementNodes $fragDoc foreach ($node in $importedEE) { Insert-IntoContainer -container $targetEvents -newNode $node -afterName $null -childIndent $eeChildIndent } $ctStr = if ($ee.callType) { "[$($ee.callType)]" } else { "" } $addedElemEvents += " + $targetName.$eeName${ctStr} -> $eeHandler" } } # === 13. Save === $content = $xmlDoc.OuterXml # Ensure encoding declaration is uppercase UTF-8 $content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', '' $enc = New-Object System.Text.UTF8Encoding($true) [System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc) # === 14. Summary === if ($script:isExtension) { Write-Host "[EXTENSION] BaseForm detected — IDs start at 1000000+" Write-Host "" } if ($addedFormEvents.Count -gt 0) { Write-Host "Added form events:" foreach ($line in $addedFormEvents) { Write-Host $line } Write-Host "" } if ($addedElemEvents.Count -gt 0) { Write-Host "Added element events:" foreach ($line in $addedElemEvents) { Write-Host $line } Write-Host "" } if ($addedElems.Count -gt 0) { $posStr = "" if ($def.into) { $posStr += "into $($def.into)" } if ($def.after) { if ($posStr) { $posStr += ", " }; $posStr += "after $($def.after)" } if ($posStr) { $posStr = " ($posStr)" } Write-Host "Added elements${posStr}:" foreach ($line in $addedElems) { Write-Host $line } Write-Host "" } if ($addedAttrs.Count -gt 0) { Write-Host "Added attributes:" foreach ($line in $addedAttrs) { Write-Host $line } Write-Host "" } if ($addedCmds.Count -gt 0) { Write-Host "Added commands:" foreach ($line in $addedCmds) { Write-Host $line } Write-Host "" } Write-Host "---" $totalParts = @() if ($addedFormEvents.Count -gt 0) { $totalParts += "$($addedFormEvents.Count) form event(s)" } if ($addedElemEvents.Count -gt 0) { $totalParts += "$($addedElemEvents.Count) element event(s)" } if ($addedElems.Count -gt 0) { $compStr = if ($companionCount -gt 0) { " (+$companionCount companions)" } else { "" } $totalParts += "$($addedElems.Count) element(s)$compStr" } if ($addedAttrs.Count -gt 0) { $totalParts += "$($addedAttrs.Count) attribute(s)" } if ($addedCmds.Count -gt 0) { $totalParts += "$($addedCmds.Count) command(s)" } Write-Host "Total: $($totalParts -join ', ')" Write-Host "Run /form-validate to verify."