Files
cc-1c-skills/.claude/skills/meta-edit/scripts/meta-edit.ps1
T
Nick ShirokovandClaude Opus 4.6 cb4b5ec935 Add /meta-edit skill: point editing of 1C metadata objects
New skill for adding/removing/modifying attributes, tabular sections,
dimensions, resources, enum values, forms, templates, commands in
existing metadata XML. Supports Russian/English DSL synonyms,
positional insertion, idempotent add, auto-validation via meta-validate.

Also adds missing Source comment to meta-compile.ps1.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-14 15:02:23 +03:00

1740 lines
64 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# meta-edit v1.0 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$DefinitionFile,
[Parameter(Mandatory)]
[string]$ObjectPath,
[switch]$NoValidate
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# ============================================================
# Section 1: Parameters + loading
# ============================================================
# --- Load JSON definition ---
if (-not (Test-Path $DefinitionFile)) {
Write-Error "Definition file not found: $DefinitionFile"
exit 1
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$def = $jsonText | ConvertFrom-Json
# --- Resolve object path ---
if (Test-Path $ObjectPath -PathType Container) {
$dirName = Split-Path $ObjectPath -Leaf
$candidate = Join-Path $ObjectPath "$dirName.xml"
if (Test-Path $candidate) {
$ObjectPath = $candidate
} else {
Write-Error "Directory given but no $dirName.xml found inside"
exit 1
}
}
if (-not (Test-Path $ObjectPath)) {
Write-Error "Object file not found: $ObjectPath"
exit 1
}
$resolvedPath = (Resolve-Path $ObjectPath).Path
# --- Load XML ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
# --- Counters ---
$script:addCount = 0
$script:removeCount = 0
$script:modifyCount = 0
$script:warnCount = 0
function Warn($msg) {
Write-Host "[WARN] $msg" -ForegroundColor Yellow
$script:warnCount++
}
function Info($msg) {
Write-Host "[INFO] $msg" -ForegroundColor Cyan
}
# ============================================================
# Section 2: Detect object type
# ============================================================
$root = $script:xmlDoc.DocumentElement
if ($root.LocalName -ne "MetaDataObject") {
Write-Error "Root element must be MetaDataObject, got: $($root.LocalName)"
exit 1
}
# Find the first child element — this is the object type element
$script:objElement = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$script:objElement = $child
break
}
}
if (-not $script:objElement) {
Write-Error "No object element found under MetaDataObject"
exit 1
}
$script:objType = $script:objElement.LocalName
$script:mdNs = $script:objElement.NamespaceURI
# Find Properties and ChildObjects
$script:propertiesEl = $null
$script:childObjectsEl = $null
foreach ($child in $script:objElement.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Properties") { $script:propertiesEl = $child }
if ($child.LocalName -eq "ChildObjects") { $script:childObjectsEl = $child }
}
if (-not $script:propertiesEl) {
Write-Error "No <Properties> found in $($script:objType)"
exit 1
}
# Extract object name
$script:objName = ""
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Name") {
$script:objName = $child.InnerText.Trim()
break
}
}
Info "Object: $($script:objType).$($script:objName)"
# ============================================================
# Section 3: Synonym tables
# ============================================================
# Operation synonyms
$script:operationSynonyms = @{
"add" = "add"; "добавить" = "add"
"remove" = "remove"; "удалить" = "remove"
"modify" = "modify"; "изменить" = "modify"
}
# Child type synonyms
$script:childTypeSynonyms = @{
"attributes" = "attributes"; "реквизиты" = "attributes"; "attrs" = "attributes"
"tabularsections" = "tabularSections"; "табличныечасти" = "tabularSections"; "тч" = "tabularSections"; "ts" = "tabularSections"
"dimensions" = "dimensions"; "измерения" = "dimensions"; "dims" = "dimensions"
"resources" = "resources"; "ресурсы" = "resources"; "res" = "resources"
"enumvalues" = "enumValues"; "значения" = "enumValues"; "values" = "enumValues"
"columns" = "columns"; "графы" = "columns"; "колонки" = "columns"
"forms" = "forms"; "формы" = "forms"
"templates" = "templates"; "макеты" = "templates"
"commands" = "commands"; "команды" = "commands"
"properties" = "properties"; "свойства" = "properties"
}
# Type synonyms (from meta-compile)
$script:typeSynonyms = New-Object System.Collections.Hashtable
$script:typeSynonyms["число"] = "Number"
$script:typeSynonyms["строка"] = "String"
$script:typeSynonyms["булево"] = "Boolean"
$script:typeSynonyms["дата"] = "Date"
$script:typeSynonyms["датавремя"]= "DateTime"
$script:typeSynonyms["хранилищезначения"] = "ValueStorage"
$script:typeSynonyms["number"] = "Number"
$script:typeSynonyms["string"] = "String"
$script:typeSynonyms["boolean"] = "Boolean"
$script:typeSynonyms["date"] = "Date"
$script:typeSynonyms["datetime"] = "DateTime"
$script:typeSynonyms["valuestorage"] = "ValueStorage"
$script:typeSynonyms["bool"] = "Boolean"
# Reference synonyms
$script:typeSynonyms["справочникссылка"] = "CatalogRef"
$script:typeSynonyms["документссылка"] = "DocumentRef"
$script:typeSynonyms["перечислениессылка"] = "EnumRef"
$script:typeSynonyms["плансчетовссылка"] = "ChartOfAccountsRef"
$script:typeSynonyms["планвидовхарактеристикссылка"] = "ChartOfCharacteristicTypesRef"
$script:typeSynonyms["планвидоврасчётассылка"] = "ChartOfCalculationTypesRef"
$script:typeSynonyms["планвидоврасчетассылка"] = "ChartOfCalculationTypesRef"
$script:typeSynonyms["планобменассылка"] = "ExchangePlanRef"
$script:typeSynonyms["бизнеспроцессссылка"] = "BusinessProcessRef"
$script:typeSynonyms["задачассылка"] = "TaskRef"
$script:typeSynonyms["определяемыйтип"] = "DefinedType"
$script:typeSynonyms["definedtype"] = "DefinedType"
$script:typeSynonyms["catalogref"] = "CatalogRef"
$script:typeSynonyms["documentref"] = "DocumentRef"
$script:typeSynonyms["enumref"] = "EnumRef"
# ============================================================
# Section 4: Type system
# ============================================================
function Esc-Xml {
param([string]$s)
return $s.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
function Split-CamelCase {
param([string]$name)
if (-not $name) { return $name }
$result = [regex]::Replace($name, '([а-яё])([А-ЯЁ])', '$1 $2')
$result = [regex]::Replace($result, '([a-z])([A-Z])', '$1 $2')
if ($result.Length -gt 1) {
$result = $result.Substring(0,1) + $result.Substring(1).ToLower()
}
return $result
}
function New-Guid-String {
return [System.Guid]::NewGuid().ToString()
}
function Resolve-TypeStr {
param([string]$typeStr)
if (-not $typeStr) { return $typeStr }
# Parameterized: Number(15,2), Строка(100)
if ($typeStr -match '^([^(]+)\((.+)\)$') {
$baseName = $Matches[1].Trim()
$params = $Matches[2]
$resolved = $script:typeSynonyms[$baseName.ToLower()]
if ($resolved) { return "$resolved($params)" }
return $typeStr
}
# Reference: СправочникСсылка.Организации
if ($typeStr.Contains('.')) {
$dotIdx = $typeStr.IndexOf('.')
$prefix = $typeStr.Substring(0, $dotIdx)
$suffix = $typeStr.Substring($dotIdx)
$resolved = $script:typeSynonyms[$prefix.ToLower()]
if ($resolved) { return "$resolved$suffix" }
return $typeStr
}
# Simple
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
if ($resolved) { return $resolved }
return $typeStr
}
function Build-TypeContentXml {
param([string]$indent, [string]$typeStr)
if (-not $typeStr) { return "" }
$typeStr = Resolve-TypeStr $typeStr
$sb = New-Object System.Text.StringBuilder
# Boolean
if ($typeStr -eq "Boolean") {
$sb.AppendLine("$indent<v8:Type>xs:boolean</v8:Type>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# ValueStorage
if ($typeStr -eq "ValueStorage") {
$sb.AppendLine("$indent<v8:Type>xs:base64Binary</v8:Type>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# String or String(N)
if ($typeStr -match '^String(\((\d+)\))?$') {
$len = if ($Matches[2]) { $Matches[2] } else { "0" }
$sb.AppendLine("$indent<v8:Type>xs:string</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:StringQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:Length>$len</v8:Length>") | Out-Null
$sb.AppendLine("$indent`t<v8:AllowedLength>Variable</v8:AllowedLength>") | Out-Null
$sb.AppendLine("$indent</v8:StringQualifiers>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# Number(D,F) or Number(D,F,nonneg)
if ($typeStr -match '^Number\((\d+),(\d+)(,nonneg)?\)$') {
$digits = $Matches[1]; $fraction = $Matches[2]
$sign = if ($Matches[3]) { "Nonnegative" } else { "Any" }
$sb.AppendLine("$indent<v8:Type>xs:decimal</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:NumberQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:Digits>$digits</v8:Digits>") | Out-Null
$sb.AppendLine("$indent`t<v8:FractionDigits>$fraction</v8:FractionDigits>") | Out-Null
$sb.AppendLine("$indent`t<v8:AllowedSign>$sign</v8:AllowedSign>") | Out-Null
$sb.AppendLine("$indent</v8:NumberQualifiers>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# Number without params → Number(10,0)
if ($typeStr -eq "Number") {
$sb.AppendLine("$indent<v8:Type>xs:decimal</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:NumberQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:Digits>10</v8:Digits>") | Out-Null
$sb.AppendLine("$indent`t<v8:FractionDigits>0</v8:FractionDigits>") | Out-Null
$sb.AppendLine("$indent`t<v8:AllowedSign>Any</v8:AllowedSign>") | Out-Null
$sb.AppendLine("$indent</v8:NumberQualifiers>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# Date / DateTime
if ($typeStr -eq "Date") {
$sb.AppendLine("$indent<v8:Type>xs:dateTime</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:DateQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:DateFractions>Date</v8:DateFractions>") | Out-Null
$sb.AppendLine("$indent</v8:DateQualifiers>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
if ($typeStr -eq "DateTime") {
$sb.AppendLine("$indent<v8:Type>xs:dateTime</v8:Type>") | Out-Null
$sb.AppendLine("$indent<v8:DateQualifiers>") | Out-Null
$sb.AppendLine("$indent`t<v8:DateFractions>DateTime</v8:DateFractions>") | Out-Null
$sb.AppendLine("$indent</v8:DateQualifiers>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# DefinedType
if ($typeStr -match '^DefinedType\.(.+)$') {
$dtName = $Matches[1]
$sb.AppendLine("$indent<v8:TypeSet>cfg:DefinedType.$dtName</v8:TypeSet>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# Reference types
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') {
$sb.AppendLine("$indent<v8:Type>cfg:$typeStr</v8:Type>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
# Fallback
$sb.AppendLine("$indent<v8:Type>$typeStr</v8:Type>") | Out-Null
return $sb.ToString().TrimEnd("`r","`n")
}
function Build-ValueTypeXml {
param([string]$indent, [string]$typeStr)
$inner = Build-TypeContentXml "$indent`t" $typeStr
return "$indent<Type>`r`n$inner`r`n$indent</Type>"
}
function Build-FillValueXml {
param([string]$indent, [string]$typeStr)
if (-not $typeStr) {
return "$indent<FillValue xsi:nil=`"true`"/>"
}
$typeStr = Resolve-TypeStr $typeStr
if ($typeStr -eq "Boolean") {
return "$indent<FillValue xsi:type=`"xs:boolean`">false</FillValue>"
}
if ($typeStr -match '^String') {
return "$indent<FillValue xsi:type=`"xs:string`"/>"
}
if ($typeStr -match '^Number') {
return "$indent<FillValue xsi:type=`"xs:decimal`">0</FillValue>"
}
return "$indent<FillValue xsi:nil=`"true`"/>"
}
function Build-MLTextXml {
param([string]$indent, [string]$tag, [string]$text)
if (-not $text) {
return "$indent<$tag/>"
}
$lines = @(
"$indent<$tag>"
"$indent`t<v8:item>"
"$indent`t`t<v8:lang>ru</v8:lang>"
"$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
"$indent`t</v8:item>"
"$indent</$tag>"
)
return $lines -join "`r`n"
}
# ============================================================
# Section 5: DOM helpers
# ============================================================
$script:metaNs = "http://v8.1c.ru/8.3/MDClasses"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
function Import-Fragment([string]$xmlString) {
$wrapper = @"
<_W xmlns="http://v8.1c.ru/8.3/MDClasses"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"
xmlns:xs="http://www.w3.org/2001/XMLSchema">$xmlString</_W>
"@
$frag = New-Object System.Xml.XmlDocument
$frag.PreserveWhitespace = $true
$frag.LoadXml($wrapper)
$nodes = @()
foreach ($child in $frag.DocumentElement.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$nodes += $script:xmlDoc.ImportNode($child, $true)
}
}
return ,$nodes
}
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
$depth = 0
$current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) {
$depth++
$current = $current.ParentNode
}
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Remove-NodeWithWhitespace($node) {
$parent = $node.ParentNode
$prev = $node.PreviousSibling
$next = $node.NextSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($prev) | Out-Null
} elseif ($next -and ($next.NodeType -eq 'Whitespace' -or $next.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($next) | Out-Null
}
$parent.RemoveChild($node) | Out-Null
}
function Find-ElementByName($container, [string]$elemLocalName, [string]$nameValue) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -ne $elemLocalName) { continue }
# Look for Properties/Name or just Name child
$propsEl = $null
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Properties") {
$propsEl = $gc; break
}
}
$searchIn = if ($propsEl) { $propsEl } else { $child }
foreach ($gc in $searchIn.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Name" -and $gc.InnerText.Trim() -eq $nameValue) {
return $child
}
}
}
return $null
}
function Find-LastElementOfType($container, [string]$localName) {
$last = $null
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $localName) {
$last = $child
}
}
return $last
}
function Find-FirstElementOfType($container, [string]$localName) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $localName) {
return $child
}
}
return $null
}
function Ensure-ChildObjectsOpen {
if ($script:childObjectsEl) {
# Check if it's self-closing (no child elements)
$hasElements = $false
foreach ($ch in $script:childObjectsEl.ChildNodes) {
if ($ch.NodeType -eq 'Element') { $hasElements = $true; break }
}
if (-not $hasElements) {
# It's empty — we need to add whitespace for proper formatting
$indent = Get-ChildIndent $script:objElement
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$indent")
$script:childObjectsEl.AppendChild($closeWs) | Out-Null
}
return
}
# No ChildObjects at all — create one after Properties
$indent = Get-ChildIndent $script:objElement
$coXml = "`r`n$indent<ChildObjects>`r`n$indent</ChildObjects>"
# Insert after Properties
$refNode = $null
$foundProps = $false
foreach ($child in $script:objElement.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Properties") {
$foundProps = $true
continue
}
if ($foundProps -and $child.NodeType -eq 'Element') {
$refNode = $child
break
}
}
$coEl = $script:xmlDoc.CreateElement("ChildObjects", $script:mdNs)
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$indent")
$coEl.AppendChild($closeWs) | Out-Null
$wsB = $script:xmlDoc.CreateWhitespace("`r`n$indent")
if ($refNode) {
$script:objElement.InsertBefore($wsB, $refNode) | Out-Null
$script:objElement.InsertBefore($coEl, $wsB) | Out-Null
} else {
# After last child
$trailing = $script:objElement.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$script:objElement.InsertBefore($wsB, $trailing) | Out-Null
$script:objElement.InsertBefore($coEl, $trailing) | Out-Null
} else {
$script:objElement.AppendChild($wsB) | Out-Null
$script:objElement.AppendChild($coEl) | Out-Null
}
}
$script:childObjectsEl = $coEl
}
function Collapse-ChildObjectsIfEmpty {
if (-not $script:childObjectsEl) { return }
$hasElements = $false
foreach ($ch in $script:childObjectsEl.ChildNodes) {
if ($ch.NodeType -eq 'Element') { $hasElements = $true; break }
}
if (-not $hasElements) {
# Remove all whitespace children
while ($script:childObjectsEl.HasChildNodes) {
$script:childObjectsEl.RemoveChild($script:childObjectsEl.FirstChild) | Out-Null
}
}
}
# ============================================================
# Section 6: Fragment builders
# ============================================================
function Parse-AttributeShorthand {
param($val)
if ($val -is [string]) {
$str = "$val"
$parsed = @{
name = ""; type = ""; synonym = ""; comment = ""
flags = @(); fillChecking = ""; indexing = ""
after = ""; before = ""
}
# Split by | for flags
$parts = $str -split '\|', 2
$mainPart = $parts[0].Trim()
if ($parts.Count -gt 1) {
$flagStr = $parts[1].Trim()
$parsed.flags = @($flagStr -split ',' | ForEach-Object { $_.Trim().ToLower() } | Where-Object { $_ })
}
# Split by : for name and type
$colonParts = $mainPart -split ':', 2
$parsed.name = $colonParts[0].Trim()
if ($colonParts.Count -gt 1) {
$parsed.type = $colonParts[1].Trim()
}
$parsed.synonym = Split-CamelCase $parsed.name
return $parsed
}
# Object form
$name = "$($val.name)"
$result = @{
name = $name
type = if ($val.type) { "$($val.type)" } else { "" }
synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name }
comment = if ($val.comment) { "$($val.comment)" } else { "" }
flags = @(if ($val.flags) { $val.flags } else { @() })
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
after = if ($val.after) { "$($val.after)" } else { "" }
before = if ($val.before) { "$($val.before)" } else { "" }
}
# Map flags to properties
if ($result.flags -contains "req" -and -not $result.fillChecking) {
$result.fillChecking = "ShowError"
}
if ($result.flags -contains "index" -and -not $result.indexing) {
$result.indexing = "Index"
}
if ($result.flags -contains "indexadditional" -and -not $result.indexing) {
$result.indexing = "IndexWithAdditionalOrder"
}
return $result
}
function Parse-EnumValueShorthand {
param($val)
if ($val -is [string]) {
$name = "$val"
return @{
name = $name
synonym = Split-CamelCase $name
comment = ""
after = ""; before = ""
}
}
$name = "$($val.name)"
return @{
name = $name
synonym = if ($val.synonym) { "$($val.synonym)" } else { Split-CamelCase $name }
comment = if ($val.comment) { "$($val.comment)" } else { "" }
after = if ($val.after) { "$($val.after)" } else { "" }
before = if ($val.before) { "$($val.before)" } else { "" }
}
}
# Determine attribute context from object type
function Get-AttributeContext {
switch ($script:objType) {
"Catalog" { return "catalog" }
"Document" { return "document" }
{ $_ -in @("InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister") } { return "register" }
default { return "object" }
}
}
function Build-AttributeFragment {
param($parsed, [string]$context, [string]$indent)
if (-not $context) { $context = Get-AttributeContext }
$uuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<Attribute uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $parsed.name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
# Type
$typeStr = $parsed.type
if ($typeStr) {
$sb.AppendLine($(Build-ValueTypeXml "$indent`t`t" $typeStr)) | Out-Null
} else {
$sb.AppendLine("$indent`t`t<Type>") | Out-Null
$sb.AppendLine("$indent`t`t`t<v8:Type>xs:string</v8:Type>") | Out-Null
$sb.AppendLine("$indent`t`t</Type>") | Out-Null
}
$sb.AppendLine("$indent`t`t<PasswordMode>false</PasswordMode>") | Out-Null
$sb.AppendLine("$indent`t`t<Format/>") | Out-Null
$sb.AppendLine("$indent`t`t<EditFormat/>") | Out-Null
$sb.AppendLine("$indent`t`t<ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t<MarkNegatives>false</MarkNegatives>") | Out-Null
$sb.AppendLine("$indent`t`t<Mask/>") | Out-Null
$sb.AppendLine("$indent`t`t<MultiLine>false</MultiLine>") | Out-Null
$sb.AppendLine("$indent`t`t<ExtendedEdit>false</ExtendedEdit>") | Out-Null
$sb.AppendLine("$indent`t`t<MinValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t<MaxValue xsi:nil=`"true`"/>") | Out-Null
# FillFromFillingValue — for catalog/document/object contexts
if ($context -ne "register") {
$sb.AppendLine("$indent`t`t<FillFromFillingValue>false</FillFromFillingValue>") | Out-Null
$sb.AppendLine($(Build-FillValueXml "$indent`t`t" $typeStr)) | Out-Null
}
# FillChecking
$fillChecking = "DontCheck"
if ($parsed.flags -contains "req") { $fillChecking = "ShowError" }
if ($parsed.fillChecking) { $fillChecking = $parsed.fillChecking }
$sb.AppendLine("$indent`t`t<FillChecking>$fillChecking</FillChecking>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameterLinks/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameters/>") | Out-Null
$sb.AppendLine("$indent`t`t<QuickChoice>Auto</QuickChoice>") | Out-Null
$sb.AppendLine("$indent`t`t<CreateOnInput>Auto</CreateOnInput>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
$sb.AppendLine("$indent`t`t<LinkByType/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>") | Out-Null
# Use — catalog/document only
if ($context -eq "catalog" -or $context -eq "document") {
$sb.AppendLine("$indent`t`t<Use>ForItem</Use>") | Out-Null
}
# Indexing
$indexing = "DontIndex"
if ($parsed.flags -contains "index") { $indexing = "Index" }
if ($parsed.flags -contains "indexadditional") { $indexing = "IndexWithAdditionalOrder" }
if ($parsed.indexing) { $indexing = $parsed.indexing }
$sb.AppendLine("$indent`t`t<Indexing>$indexing</Indexing>") | Out-Null
$sb.AppendLine("$indent`t`t<FullTextSearch>Use</FullTextSearch>") | Out-Null
$sb.AppendLine("$indent`t`t<DataHistory>Use</DataHistory>") | Out-Null
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</Attribute>") | Out-Null
return $sb.ToString()
}
function Build-TabularSectionFragment {
param($tsDef, [string]$indent)
$tsName = "$($tsDef.name)"
$tsSynonym = if ($tsDef.synonym) { "$($tsDef.synonym)" } else { Split-CamelCase $tsName }
$uuid = New-Guid-String
$objType = $script:objType
$objName = $script:objName
$typePrefix = "${objType}TabularSection"
$rowPrefix = "${objType}TabularSectionRow"
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<TabularSection uuid=`"$uuid`">") | Out-Null
# InternalInfo
$sb.AppendLine("$indent`t<InternalInfo>") | Out-Null
$sb.AppendLine("$indent`t`t<xr:GeneratedType name=`"$typePrefix.$objName.$tsName`" category=`"TabularSection`">") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:TypeId>$(New-Guid-String)</xr:TypeId>") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:ValueId>$(New-Guid-String)</xr:ValueId>") | Out-Null
$sb.AppendLine("$indent`t`t</xr:GeneratedType>") | Out-Null
$sb.AppendLine("$indent`t`t<xr:GeneratedType name=`"$rowPrefix.$objName.$tsName`" category=`"TabularSectionRow`">") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:TypeId>$(New-Guid-String)</xr:TypeId>") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:ValueId>$(New-Guid-String)</xr:ValueId>") | Out-Null
$sb.AppendLine("$indent`t`t</xr:GeneratedType>") | Out-Null
$sb.AppendLine("$indent`t</InternalInfo>") | Out-Null
# Properties
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $tsName)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $tsSynonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
$sb.AppendLine("$indent`t`t<ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t<FillChecking>DontCheck</FillChecking>") | Out-Null
# StandardAttributes (LineNumber)
$sb.AppendLine("$indent`t`t<StandardAttributes>") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:StandardAttribute name=`"LineNumber`">") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:LinkByType/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:FillChecking>DontCheck</xr:FillChecking>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:MultiLine>false</xr:MultiLine>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:FillFromFillingValue>false</xr:FillFromFillingValue>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:CreateOnInput>Auto</xr:CreateOnInput>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:MaxValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ExtendedEdit>false</xr:ExtendedEdit>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:Format/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ChoiceForm/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:QuickChoice>Auto</xr:QuickChoice>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:EditFormat/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:PasswordMode>false</xr:PasswordMode>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:DataHistory>Use</xr:DataHistory>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:MarkNegatives>false</xr:MarkNegatives>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:MinValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:Synonym/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:Comment/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:FullTextSearch>Use</xr:FullTextSearch>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ChoiceParameterLinks/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:FillValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:Mask/>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<xr:ChoiceParameters/>") | Out-Null
$sb.AppendLine("$indent`t`t`t</xr:StandardAttribute>") | Out-Null
$sb.AppendLine("$indent`t`t</StandardAttributes>") | Out-Null
# Use — catalog/document only
if ($objType -in @("Catalog","Document")) {
$sb.AppendLine("$indent`t`t<Use>ForItem</Use>") | Out-Null
}
$sb.AppendLine("$indent`t</Properties>") | Out-Null
# ChildObjects with attrs
$columns = @()
if ($tsDef.attrs) { $columns = @($tsDef.attrs) }
elseif ($tsDef.attributes) { $columns = @($tsDef.attributes) }
elseif ($tsDef.реквизиты) { $columns = @($tsDef.реквизиты) }
if ($columns.Count -gt 0) {
$sb.AppendLine("$indent`t<ChildObjects>") | Out-Null
foreach ($col in $columns) {
$colParsed = Parse-AttributeShorthand $col
$sb.AppendLine($(Build-AttributeFragment $colParsed "tabular" "$indent`t`t")) | Out-Null
}
$sb.AppendLine("$indent`t</ChildObjects>") | Out-Null
} else {
$sb.AppendLine("$indent`t<ChildObjects/>") | Out-Null
}
$sb.Append("$indent</TabularSection>") | Out-Null
return $sb.ToString()
}
function Build-DimensionFragment {
param($parsed, [string]$registerType, [string]$indent)
if (-not $registerType) { $registerType = $script:objType }
$uuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<Dimension uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $parsed.name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
$typeStr = $parsed.type
if ($typeStr) {
$sb.AppendLine($(Build-ValueTypeXml "$indent`t`t" $typeStr)) | Out-Null
} else {
$sb.AppendLine("$indent`t`t<Type>") | Out-Null
$sb.AppendLine("$indent`t`t`t<v8:Type>xs:string</v8:Type>") | Out-Null
$sb.AppendLine("$indent`t`t</Type>") | Out-Null
}
$sb.AppendLine("$indent`t`t<PasswordMode>false</PasswordMode>") | Out-Null
$sb.AppendLine("$indent`t`t<Format/>") | Out-Null
$sb.AppendLine("$indent`t`t<EditFormat/>") | Out-Null
$sb.AppendLine("$indent`t`t<ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t<MarkNegatives>false</MarkNegatives>") | Out-Null
$sb.AppendLine("$indent`t`t<Mask/>") | Out-Null
$sb.AppendLine("$indent`t`t<MultiLine>false</MultiLine>") | Out-Null
$sb.AppendLine("$indent`t`t<ExtendedEdit>false</ExtendedEdit>") | Out-Null
$sb.AppendLine("$indent`t`t<MinValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t<MaxValue xsi:nil=`"true`"/>") | Out-Null
# InformationRegister: FillFromFillingValue, FillValue
if ($registerType -eq "InformationRegister") {
$fillFrom = if ($parsed.flags -contains "master") { "true" } else { "false" }
$sb.AppendLine("$indent`t`t<FillFromFillingValue>$fillFrom</FillFromFillingValue>") | Out-Null
$sb.AppendLine("$indent`t`t<FillValue xsi:nil=`"true`"/>") | Out-Null
}
$fillChecking = "DontCheck"
if ($parsed.flags -contains "req") { $fillChecking = "ShowError" }
$sb.AppendLine("$indent`t`t<FillChecking>$fillChecking</FillChecking>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameterLinks/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameters/>") | Out-Null
$sb.AppendLine("$indent`t`t<QuickChoice>Auto</QuickChoice>") | Out-Null
$sb.AppendLine("$indent`t`t<CreateOnInput>Auto</CreateOnInput>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
$sb.AppendLine("$indent`t`t<LinkByType/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>") | Out-Null
# InformationRegister: Master, MainFilter, DenyIncompleteValues
if ($registerType -eq "InformationRegister") {
$master = if ($parsed.flags -contains "master") { "true" } else { "false" }
$mainFilter = if ($parsed.flags -contains "mainfilter") { "true" } else { "false" }
$denyIncomplete = if ($parsed.flags -contains "denyincomplete") { "true" } else { "false" }
$sb.AppendLine("$indent`t`t<Master>$master</Master>") | Out-Null
$sb.AppendLine("$indent`t`t<MainFilter>$mainFilter</MainFilter>") | Out-Null
$sb.AppendLine("$indent`t`t<DenyIncompleteValues>$denyIncomplete</DenyIncompleteValues>") | Out-Null
}
# AccumulationRegister: DenyIncompleteValues
if ($registerType -eq "AccumulationRegister") {
$denyIncomplete = if ($parsed.flags -contains "denyincomplete") { "true" } else { "false" }
$sb.AppendLine("$indent`t`t<DenyIncompleteValues>$denyIncomplete</DenyIncompleteValues>") | Out-Null
}
$indexing = "DontIndex"
if ($parsed.flags -contains "index") { $indexing = "Index" }
$sb.AppendLine("$indent`t`t<Indexing>$indexing</Indexing>") | Out-Null
$sb.AppendLine("$indent`t`t<FullTextSearch>Use</FullTextSearch>") | Out-Null
# AccumulationRegister: UseInTotals
if ($registerType -eq "AccumulationRegister") {
$useInTotals = if ($parsed.flags -contains "nouseintotals") { "false" } else { "true" }
$sb.AppendLine("$indent`t`t<UseInTotals>$useInTotals</UseInTotals>") | Out-Null
}
# InformationRegister: DataHistory
if ($registerType -eq "InformationRegister") {
$sb.AppendLine("$indent`t`t<DataHistory>Use</DataHistory>") | Out-Null
}
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</Dimension>") | Out-Null
return $sb.ToString()
}
function Build-ResourceFragment {
param($parsed, [string]$registerType, [string]$indent)
if (-not $registerType) { $registerType = $script:objType }
$uuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<Resource uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $parsed.name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
$typeStr = $parsed.type
if ($typeStr) {
$sb.AppendLine($(Build-ValueTypeXml "$indent`t`t" $typeStr)) | Out-Null
} else {
# Default: Number(15,2)
$sb.AppendLine("$indent`t`t<Type>") | Out-Null
$sb.AppendLine("$indent`t`t`t<v8:Type>xs:decimal</v8:Type>") | Out-Null
$sb.AppendLine("$indent`t`t`t<v8:NumberQualifiers>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<v8:Digits>15</v8:Digits>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<v8:FractionDigits>2</v8:FractionDigits>") | Out-Null
$sb.AppendLine("$indent`t`t`t`t<v8:AllowedSign>Any</v8:AllowedSign>") | Out-Null
$sb.AppendLine("$indent`t`t`t</v8:NumberQualifiers>") | Out-Null
$sb.AppendLine("$indent`t`t</Type>") | Out-Null
}
$sb.AppendLine("$indent`t`t<PasswordMode>false</PasswordMode>") | Out-Null
$sb.AppendLine("$indent`t`t<Format/>") | Out-Null
$sb.AppendLine("$indent`t`t<EditFormat/>") | Out-Null
$sb.AppendLine("$indent`t`t<ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t<MarkNegatives>false</MarkNegatives>") | Out-Null
$sb.AppendLine("$indent`t`t<Mask/>") | Out-Null
$sb.AppendLine("$indent`t`t<MultiLine>false</MultiLine>") | Out-Null
$sb.AppendLine("$indent`t`t<ExtendedEdit>false</ExtendedEdit>") | Out-Null
$sb.AppendLine("$indent`t`t<MinValue xsi:nil=`"true`"/>") | Out-Null
$sb.AppendLine("$indent`t`t<MaxValue xsi:nil=`"true`"/>") | Out-Null
# InformationRegister: FillFromFillingValue, FillValue
if ($registerType -eq "InformationRegister") {
$sb.AppendLine("$indent`t`t<FillFromFillingValue>false</FillFromFillingValue>") | Out-Null
$sb.AppendLine("$indent`t`t<FillValue xsi:nil=`"true`"/>") | Out-Null
}
$fillChecking = "DontCheck"
if ($parsed.flags -contains "req") { $fillChecking = "ShowError" }
$sb.AppendLine("$indent`t`t<FillChecking>$fillChecking</FillChecking>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameterLinks/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceParameters/>") | Out-Null
$sb.AppendLine("$indent`t`t<QuickChoice>Auto</QuickChoice>") | Out-Null
$sb.AppendLine("$indent`t`t<CreateOnInput>Auto</CreateOnInput>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
$sb.AppendLine("$indent`t`t<LinkByType/>") | Out-Null
$sb.AppendLine("$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>") | Out-Null
# InformationRegister: Indexing, FullTextSearch, DataHistory
if ($registerType -eq "InformationRegister") {
$sb.AppendLine("$indent`t`t<Indexing>DontIndex</Indexing>") | Out-Null
$sb.AppendLine("$indent`t`t<FullTextSearch>Use</FullTextSearch>") | Out-Null
$sb.AppendLine("$indent`t`t<DataHistory>Use</DataHistory>") | Out-Null
}
# AccumulationRegister: FullTextSearch
if ($registerType -eq "AccumulationRegister") {
$sb.AppendLine("$indent`t`t<FullTextSearch>Use</FullTextSearch>") | Out-Null
}
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</Resource>") | Out-Null
return $sb.ToString()
}
function Build-EnumValueFragment {
param($parsed, [string]$indent)
$uuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<EnumValue uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $parsed.name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</EnumValue>") | Out-Null
return $sb.ToString()
}
function Build-ColumnFragment {
param($colDef, [string]$indent)
$uuid = New-Guid-String
$name = ""
$synonym = ""
$indexing = "DontIndex"
$references = @()
if ($colDef -is [string]) {
$name = "$colDef"
$synonym = Split-CamelCase $name
} else {
$name = "$($colDef.name)"
$synonym = if ($colDef.synonym) { "$($colDef.synonym)" } else { Split-CamelCase $name }
if ($colDef.indexing) { $indexing = "$($colDef.indexing)" }
if ($colDef.references) { $references = @($colDef.references) }
}
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<Column uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
$sb.AppendLine("$indent`t`t<Indexing>$indexing</Indexing>") | Out-Null
if ($references.Count -gt 0) {
$sb.AppendLine("$indent`t`t<References>") | Out-Null
foreach ($ref in $references) {
$sb.AppendLine("$indent`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$ref</xr:Item>") | Out-Null
}
$sb.AppendLine("$indent`t`t</References>") | Out-Null
} else {
$sb.AppendLine("$indent`t`t<References/>") | Out-Null
}
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</Column>") | Out-Null
return $sb.ToString()
}
function Build-SimpleChildFragment {
param([string]$tagName, [string]$name, [string]$indent)
# For Form, Template, Command — just a name wrapper
$uuid = New-Guid-String
$synonym = Split-CamelCase $name
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("$indent<$tagName uuid=`"$uuid`">") | Out-Null
$sb.AppendLine("$indent`t<Properties>") | Out-Null
$sb.AppendLine("$indent`t`t<Name>$(Esc-Xml $name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
# Forms get additional properties
if ($tagName -eq "Form") {
$sb.AppendLine("$indent`t`t<FormType>Ordinary</FormType>") | Out-Null
$sb.AppendLine("$indent`t`t<IncludeHelpInContents>false</IncludeHelpInContents>") | Out-Null
$sb.AppendLine("$indent`t`t<UsePurposes/>") | Out-Null
}
if ($tagName -eq "Template") {
$sb.AppendLine("$indent`t`t<TemplateType>SpreadsheetDocument</TemplateType>") | Out-Null
}
if ($tagName -eq "Command") {
$sb.AppendLine("$indent`t`t<Group>FormNavigationPanelGoTo</Group>") | Out-Null
$sb.AppendLine("$indent`t`t<Representation>Auto</Representation>") | Out-Null
$sb.AppendLine("$indent`t`t<ToolTip/>") | Out-Null
$sb.AppendLine("$indent`t`t<Picture/>") | Out-Null
$sb.AppendLine("$indent`t`t<Shortcut/>") | Out-Null
}
$sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</$tagName>") | Out-Null
return $sb.ToString()
}
# ============================================================
# Section 7: Name uniqueness check
# ============================================================
function Get-AllChildNames {
$names = @{}
if (-not $script:childObjectsEl) { return $names }
foreach ($child in $script:childObjectsEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$propsEl = $null
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Properties") {
$propsEl = $gc; break
}
}
if (-not $propsEl) { continue }
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Name") {
$n = $gc.InnerText.Trim()
if ($n) { $names[$n] = $child.LocalName }
break
}
}
# Also check ChildObjects of TabularSections for nested names
if ($child.LocalName -eq "TabularSection") {
foreach ($tsCh in $child.ChildNodes) {
if ($tsCh.NodeType -eq 'Element' -and $tsCh.LocalName -eq "ChildObjects") {
foreach ($tsChild in $tsCh.ChildNodes) {
if ($tsChild.NodeType -ne 'Element') { continue }
$tsProps = $null
foreach ($gc in $tsChild.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Properties") {
$tsProps = $gc; break
}
}
if ($tsProps) {
foreach ($gc in $tsProps.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Name") {
# TS attr names don't conflict with top-level
break
}
}
}
}
}
}
}
}
return $names
}
# ============================================================
# Section 8: Context and allowed child types
# ============================================================
$script:validChildTypes = @{
"Catalog" = @("attributes","tabularSections","forms","templates","commands")
"Document" = @("attributes","tabularSections","forms","templates","commands")
"ExchangePlan" = @("attributes","tabularSections","forms","templates","commands")
"ChartOfAccounts" = @("attributes","tabularSections","forms","templates","commands")
"ChartOfCharacteristicTypes" = @("attributes","tabularSections","forms","templates","commands")
"ChartOfCalculationTypes" = @("attributes","tabularSections","forms","templates","commands")
"BusinessProcess" = @("attributes","tabularSections","forms","templates","commands")
"Task" = @("attributes","tabularSections","forms","templates","commands")
"Report" = @("attributes","tabularSections","forms","templates","commands")
"DataProcessor" = @("attributes","tabularSections","forms","templates","commands")
"Enum" = @("enumValues","forms","templates","commands")
"InformationRegister" = @("dimensions","resources","attributes","forms","templates","commands")
"AccumulationRegister" = @("dimensions","resources","attributes","forms","templates","commands")
"AccountingRegister" = @("dimensions","resources","attributes","forms","templates","commands")
"CalculationRegister" = @("dimensions","resources","attributes","forms","templates","commands")
"DocumentJournal" = @("columns","forms","templates","commands")
"Constant" = @("forms")
}
# Canonical child order in ChildObjects
$script:childOrder = @(
"Resource", "Dimension", "Attribute", "TabularSection",
"AccountingFlag", "ExtDimensionAccountingFlag",
"EnumValue", "Column", "AddressingAttribute", "Recalculation",
"Form", "Template", "Command"
)
# Map from DSL child type to XML element name
$script:childTypeToXmlTag = @{
"attributes" = "Attribute"
"tabularSections" = "TabularSection"
"dimensions" = "Dimension"
"resources" = "Resource"
"enumValues" = "EnumValue"
"columns" = "Column"
"forms" = "Form"
"templates" = "Template"
"commands" = "Command"
}
# ============================================================
# Section 9: DSL key normalization
# ============================================================
function Resolve-OperationKey([string]$key) {
$k = $key.ToLower().Trim()
if ($script:operationSynonyms.ContainsKey($k)) {
return $script:operationSynonyms[$k]
}
return $null
}
function Resolve-ChildTypeKey([string]$key) {
$k = $key.ToLower().Trim()
if ($script:childTypeSynonyms.ContainsKey($k)) {
return $script:childTypeSynonyms[$k]
}
return $null
}
# ============================================================
# Section 10: ADD operations
# ============================================================
function Find-InsertionPoint {
param([string]$xmlTag, $parsed)
# Returns $refNode for Insert-BeforeElement (null = append)
if (-not $script:childObjectsEl) { return $null }
# Positional: after/before
if ($parsed.after) {
$afterEl = Find-ElementByName $script:childObjectsEl $xmlTag $parsed.after
if ($afterEl) {
# Insert after = insert before the next element sibling
$next = $afterEl.NextSibling
while ($next -and $next.NodeType -ne 'Element') { $next = $next.NextSibling }
if ($next -and $next.LocalName -eq $xmlTag) { return $next }
return $null # append
} else {
Warn "after='$($parsed.after)': element '$($parsed.after)' not found in $xmlTag, appending"
}
}
if ($parsed.before) {
$beforeEl = Find-ElementByName $script:childObjectsEl $xmlTag $parsed.before
if ($beforeEl) { return $beforeEl }
Warn "before='$($parsed.before)': element '$($parsed.before)' not found in $xmlTag, appending"
}
# Default: after last element of this type, or in canonical position
$lastOfType = Find-LastElementOfType $script:childObjectsEl $xmlTag
if ($lastOfType) {
$next = $lastOfType.NextSibling
while ($next -and $next.NodeType -ne 'Element') { $next = $next.NextSibling }
return $next # null means append (which is correct: after last of type)
}
# No elements of this type yet — find canonical position
$tagIdx = [array]::IndexOf($script:childOrder, $xmlTag)
if ($tagIdx -lt 0) { return $null }
# Find first element of any type that comes AFTER in the canonical order
for ($i = $tagIdx + 1; $i -lt $script:childOrder.Count; $i++) {
$nextTag = $script:childOrder[$i]
$firstOfNext = Find-FirstElementOfType $script:childObjectsEl $nextTag
if ($firstOfNext) { return $firstOfNext }
}
return $null # append at end
}
function Process-Add($addDef) {
$addDef.PSObject.Properties | ForEach-Object {
$rawKey = $_.Name
$items = $_.Value
$childType = Resolve-ChildTypeKey $rawKey
if (-not $childType) {
Warn "Unknown add child type: $rawKey"
return
}
# Validate allowed
$allowed = $script:validChildTypes[$script:objType]
if ($allowed -and $childType -notin $allowed) {
Warn "$childType not allowed for $($script:objType), skipping"
return
}
$xmlTag = $script:childTypeToXmlTag[$childType]
if (-not $xmlTag) {
Warn "No XML tag mapping for $childType"
return
}
Ensure-ChildObjectsOpen
$indent = Get-ChildIndent $script:childObjectsEl
$existingNames = Get-AllChildNames
switch ($childType) {
"attributes" {
foreach ($item in $items) {
$parsed = Parse-AttributeShorthand $item
if ($existingNames.ContainsKey($parsed.name)) {
Warn "Attribute '$($parsed.name)' already exists, skipping"
continue
}
$context = Get-AttributeContext
$fragmentXml = Build-AttributeFragment $parsed $context $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Attribute" $parsed
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added attribute: $($parsed.name)"
$script:addCount++
$existingNames[$parsed.name] = "Attribute"
}
}
"tabularSections" {
foreach ($item in $items) {
$tsName = if ($item -is [string]) { "$item" } else { "$($item.name)" }
if ($existingNames.ContainsKey($tsName)) {
Warn "TabularSection '$tsName' already exists, skipping"
continue
}
$tsDef = if ($item -is [string]) { @{ name = $item } } else { $item }
$fragmentXml = Build-TabularSectionFragment $tsDef $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "TabularSection" @{ after = ""; before = "" }
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added tabular section: $tsName"
$script:addCount++
$existingNames[$tsName] = "TabularSection"
}
}
"dimensions" {
foreach ($item in $items) {
$parsed = Parse-AttributeShorthand $item
if ($existingNames.ContainsKey($parsed.name)) {
Warn "Dimension '$($parsed.name)' already exists, skipping"
continue
}
$fragmentXml = Build-DimensionFragment $parsed $script:objType $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Dimension" $parsed
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added dimension: $($parsed.name)"
$script:addCount++
$existingNames[$parsed.name] = "Dimension"
}
}
"resources" {
foreach ($item in $items) {
$parsed = Parse-AttributeShorthand $item
if ($existingNames.ContainsKey($parsed.name)) {
Warn "Resource '$($parsed.name)' already exists, skipping"
continue
}
$fragmentXml = Build-ResourceFragment $parsed $script:objType $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Resource" $parsed
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added resource: $($parsed.name)"
$script:addCount++
$existingNames[$parsed.name] = "Resource"
}
}
"enumValues" {
foreach ($item in $items) {
$parsed = Parse-EnumValueShorthand $item
if ($existingNames.ContainsKey($parsed.name)) {
Warn "EnumValue '$($parsed.name)' already exists, skipping"
continue
}
$fragmentXml = Build-EnumValueFragment $parsed $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "EnumValue" $parsed
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added enum value: $($parsed.name)"
$script:addCount++
$existingNames[$parsed.name] = "EnumValue"
}
}
"columns" {
foreach ($item in $items) {
$colName = if ($item -is [string]) { "$item" } else { "$($item.name)" }
if ($existingNames.ContainsKey($colName)) {
Warn "Column '$colName' already exists, skipping"
continue
}
$fragmentXml = Build-ColumnFragment $item $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Column" @{ after = ""; before = "" }
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added column: $colName"
$script:addCount++
$existingNames[$colName] = "Column"
}
}
{ $_ -in @("forms","templates","commands") } {
$tagMap = @{ "forms" = "Form"; "templates" = "Template"; "commands" = "Command" }
$tag = $tagMap[$childType]
foreach ($item in $items) {
$itemName = if ($item -is [string]) { "$item" } else { "$($item.name)" }
if ($existingNames.ContainsKey($itemName)) {
Warn "$tag '$itemName' already exists, skipping"
continue
}
$fragmentXml = Build-SimpleChildFragment $tag $itemName $indent
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint $tag @{ after = ""; before = "" }
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added $($tag.ToLower()): $itemName"
$script:addCount++
$existingNames[$itemName] = $tag
}
}
}
}
}
# ============================================================
# Section 11: REMOVE operations
# ============================================================
function Process-Remove($removeDef) {
$removeDef.PSObject.Properties | ForEach-Object {
$rawKey = $_.Name
$names = $_.Value
$childType = Resolve-ChildTypeKey $rawKey
if (-not $childType) {
Warn "Unknown remove child type: $rawKey"
return
}
if ($childType -eq "properties") {
Warn "Cannot remove properties — use modify instead"
return
}
$xmlTag = $script:childTypeToXmlTag[$childType]
if (-not $xmlTag -or -not $script:childObjectsEl) {
Warn "No ChildObjects or unknown tag for $childType"
return
}
foreach ($name in $names) {
$nameStr = "$name"
$el = Find-ElementByName $script:childObjectsEl $xmlTag $nameStr
if (-not $el) {
Warn "$xmlTag '$nameStr' not found, skipping remove"
continue
}
Remove-NodeWithWhitespace $el
Info "Removed $($xmlTag.ToLower()): $nameStr"
$script:removeCount++
}
}
# Collapse if empty
Collapse-ChildObjectsIfEmpty
}
# ============================================================
# Section 12: MODIFY operations
# ============================================================
function Modify-Properties($propsDef) {
$propsDef.PSObject.Properties | ForEach-Object {
$propName = $_.Name
$propValue = $_.Value
# Find the property element in Properties
$propEl = $null
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
$propEl = $child
break
}
}
if (-not $propEl) {
Warn "Property '$propName' not found in Properties"
return
}
# Handle boolean values
$valueStr = "$propValue"
if ($propValue -is [bool]) {
$valueStr = if ($propValue) { "true" } else { "false" }
}
$propEl.InnerText = $valueStr
Info "Modified property: $propName = $valueStr"
$script:modifyCount++
}
}
function Modify-ChildElements($modifyDef, [string]$childType) {
$xmlTag = $script:childTypeToXmlTag[$childType]
if (-not $xmlTag -or -not $script:childObjectsEl) {
Warn "No ChildObjects or unknown tag for $childType"
return
}
$modifyDef.PSObject.Properties | ForEach-Object {
$elemName = $_.Name
$changes = $_.Value
$el = Find-ElementByName $script:childObjectsEl $xmlTag $elemName
if (-not $el) {
Warn "$xmlTag '$elemName' not found for modify"
return
}
# Find Properties inside the element
$propsEl = $null
foreach ($gc in $el.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Properties") {
$propsEl = $gc; break
}
}
if (-not $propsEl) {
Warn "$xmlTag '$elemName': no Properties element found"
return
}
$changes.PSObject.Properties | ForEach-Object {
$changeProp = $_.Name
$changeValue = $_.Value
switch ($changeProp) {
"name" {
# Rename
$nameEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Name") {
$nameEl = $gc; break
}
}
if ($nameEl) {
$oldName = $nameEl.InnerText.Trim()
$newName = "$changeValue"
$nameEl.InnerText = $newName
# Update Synonym if it was auto-generated (matches old CamelCase split)
$oldSynonym = Split-CamelCase $oldName
$synEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Synonym") {
$synEl = $gc; break
}
}
if ($synEl) {
# Check if current synonym matches auto-generated from old name
$currentSyn = ""
foreach ($item in $synEl.ChildNodes) {
if ($item.NodeType -eq 'Element' -and $item.LocalName -eq "item") {
foreach ($gc in $item.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "content") {
$currentSyn = $gc.InnerText.Trim()
}
}
}
}
if ($currentSyn -eq $oldSynonym -or -not $currentSyn) {
$newSynonym = Split-CamelCase $newName
$synXml = Build-MLTextXml (Get-ChildIndent $propsEl) "Synonym" $newSynonym
$newSynNodes = Import-Fragment $synXml
if ($newSynNodes.Count -gt 0) {
$propsEl.InsertAfter($newSynNodes[0], $synEl) | Out-Null
Remove-NodeWithWhitespace $synEl
}
}
}
Info "Renamed ${xmlTag}: $oldName -> $newName"
$script:modifyCount++
}
}
"type" {
# Change type
$typeEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Type") {
$typeEl = $gc; break
}
}
$newTypeStr = "$changeValue"
$typeIndent = Get-ChildIndent $propsEl
$newTypeXml = Build-ValueTypeXml $typeIndent $newTypeStr
$newTypeNodes = Import-Fragment $newTypeXml
if ($typeEl -and $newTypeNodes.Count -gt 0) {
$propsEl.InsertAfter($newTypeNodes[0], $typeEl) | Out-Null
Remove-NodeWithWhitespace $typeEl
} elseif ($newTypeNodes.Count -gt 0) {
# No existing Type — insert after Comment
$commentEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Comment") {
$commentEl = $gc; break
}
}
if ($commentEl) {
Insert-BeforeElement $propsEl $newTypeNodes[0] $commentEl.NextSibling $typeIndent
}
}
# Also update FillValue if present
$fillValEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "FillValue") {
$fillValEl = $gc; break
}
}
if ($fillValEl) {
$fillIndent = Get-ChildIndent $propsEl
$newFillXml = Build-FillValueXml $fillIndent $newTypeStr
$newFillNodes = Import-Fragment $newFillXml
if ($newFillNodes.Count -gt 0) {
$propsEl.InsertAfter($newFillNodes[0], $fillValEl) | Out-Null
Remove-NodeWithWhitespace $fillValEl
}
}
Info "Changed type of $xmlTag '$elemName': $newTypeStr"
$script:modifyCount++
}
"synonym" {
$synEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "Synonym") {
$synEl = $gc; break
}
}
$synIndent = Get-ChildIndent $propsEl
$newSynXml = Build-MLTextXml $synIndent "Synonym" "$changeValue"
$newSynNodes = Import-Fragment $newSynXml
if ($synEl -and $newSynNodes.Count -gt 0) {
$propsEl.InsertAfter($newSynNodes[0], $synEl) | Out-Null
Remove-NodeWithWhitespace $synEl
}
Info "Changed synonym of $xmlTag '$elemName': $changeValue"
$script:modifyCount++
}
default {
# Scalar property change (Indexing, FillChecking, Use, etc.)
$scalarEl = $null
foreach ($gc in $propsEl.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq $changeProp) {
$scalarEl = $gc; break
}
}
if ($scalarEl) {
$valueStr = "$changeValue"
if ($changeValue -is [bool]) {
$valueStr = if ($changeValue) { "true" } else { "false" }
}
$scalarEl.InnerText = $valueStr
Info "Modified $xmlTag '$elemName'.$changeProp = $valueStr"
$script:modifyCount++
} else {
Warn "$xmlTag '$elemName': property '$changeProp' not found"
}
}
}
}
}
}
function Process-Modify($modifyDef) {
$modifyDef.PSObject.Properties | ForEach-Object {
$rawKey = $_.Name
$value = $_.Value
$childType = Resolve-ChildTypeKey $rawKey
if (-not $childType) {
Warn "Unknown modify child type: $rawKey"
return
}
if ($childType -eq "properties") {
Modify-Properties $value
} else {
Modify-ChildElements $value $childType
}
}
}
# ============================================================
# Section 13: Main processing
# ============================================================
$def.PSObject.Properties | ForEach-Object {
$prop = $_
$opKey = Resolve-OperationKey $prop.Name
if (-not $opKey) {
Warn "Unknown operation: $($prop.Name)"
return
}
switch ($opKey) {
"add" { Process-Add $prop.Value }
"remove" { Process-Remove $prop.Value }
"modify" { Process-Modify $prop.Value }
}
}
# ============================================================
# Section 14: Save + validate
# ============================================================
# Save XML
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true) # with BOM
$settings.Indent = $false # preserve original whitespace
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
# Write using XmlWriter to get proper encoding declaration
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush()
$writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
# Fix encoding case: utf-8 → UTF-8 (cosmetic, 1C accepts both)
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
# Remove BOM from string if present (we'll add it as bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
$text = $text.Substring(1)
}
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Write with BOM
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath"
# ============================================================
# Section 15: Auto-validate
# ============================================================
if (-not $NoValidate) {
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\meta-validate") "scripts\meta-validate.ps1"
$validateScript = [System.IO.Path]::GetFullPath($validateScript)
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- Running meta-validate ---" -ForegroundColor DarkGray
& powershell.exe -NoProfile -File $validateScript -ObjectPath $resolvedPath
} else {
Write-Host ""
Write-Host "[SKIP] meta-validate not found at: $validateScript" -ForegroundColor DarkGray
}
}
# ============================================================
# Section 16: Summary
# ============================================================
Write-Host ""
Write-Host "=== meta-edit summary ===" -ForegroundColor Green
Write-Host " Object: $($script:objType).$($script:objName)"
Write-Host " Added: $($script:addCount)"
Write-Host " Removed: $($script:removeCount)"
Write-Host " Modified: $($script:modifyCount)"
if ($script:warnCount -gt 0) {
Write-Host " Warnings: $($script:warnCount)" -ForegroundColor Yellow
}
$totalChanges = $script:addCount + $script:removeCount + $script:modifyCount
if ($totalChanges -eq 0) {
Write-Host " No changes applied." -ForegroundColor Yellow
}
exit 0