Files
cc-1c-skills/.github/skills/meta-decompile/scripts/meta-decompile.ps1
T
2026-07-12 11:42:37 +00:00

1496 lines
94 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-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
# InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, BusinessProcess, Task, Enum. Инверс meta-compile (omit-on-default: ключ эмитим только
# когда значение в XML отличается от умолчания компилятора). Неподдерживаемый тип / не-MetaDataObject
# root → exit 3 (ring3, как form-decompile).
param(
[Parameter(Mandatory)]
[Alias('Path')]
[string]$ObjectPath,
[string]$OutputPath
)
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
if (-not (Test-Path -LiteralPath $ObjectPath)) {
[Console]::Error.WriteLine("meta-decompile: файл не найден: $ObjectPath"); exit 2
}
# --- JSON-эмиттер (контроль порядка/массивов/кириллицы) ---
function ConvertTo-CompactJson {
param($node, [int]$depth = 0)
$pad = " " * $depth
$pad1 = " " * ($depth + 1)
if ($null -eq $node) { return "null" }
if ($node -is [bool]) { return $(if ($node) { "true" } else { "false" }) }
if ($node -is [int] -or $node -is [long] -or $node -is [double]) { return "$node" }
if ($node -is [System.Collections.IDictionary]) {
if ($node.Count -eq 0) { return "{}" }
$items = @()
foreach ($k in $node.Keys) {
$items += "$pad1$(Quote-Json ([string]$k)): $(ConvertTo-CompactJson $node[$k] ($depth + 1))"
}
return "{`n" + ($items -join ",`n") + "`n$pad}"
}
if ($node -is [System.Collections.IList]) {
if ($node.Count -eq 0) { return "[]" }
# Массив скаляров-строк — компактно в строку; массив объектов — по строкам.
$allScalar = $true
foreach ($e in $node) { if ($e -is [System.Collections.IDictionary] -or $e -is [System.Collections.IList]) { $allScalar = $false; break } }
if ($allScalar) {
$items = @(); foreach ($e in $node) { $items += (ConvertTo-CompactJson $e ($depth + 1)) }
return "[" + ($items -join ", ") + "]"
}
$items = @(); foreach ($e in $node) { $items += "$pad1$(ConvertTo-CompactJson $e ($depth + 1))" }
return "[`n" + ($items -join ",`n") + "`n$pad]"
}
return Quote-Json ([string]$node)
}
function Quote-Json {
param([string]$s)
$sb = New-Object System.Text.StringBuilder
[void]$sb.Append('"')
foreach ($ch in $s.ToCharArray()) {
switch ($ch) {
'"' { [void]$sb.Append('\"') }
'\' { [void]$sb.Append('\\') }
"`n" { [void]$sb.Append('\n') }
"`r" { [void]$sb.Append('\r') }
"`t" { [void]$sb.Append('\t') }
default {
if ([int]$ch -lt 32) { [void]$sb.Append(('\u{0:x4}' -f [int]$ch)) }
else { [void]$sb.Append($ch) }
}
}
}
[void]$sb.Append('"')
return $sb.ToString()
}
# --- XML загрузка + namespace manager ---
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $true
$doc.Load((Resolve-Path -LiteralPath $ObjectPath).Path)
$nsm = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$nsm.AddNamespace('md', 'http://v8.1c.ru/8.3/MDClasses')
$nsm.AddNamespace('v8', 'http://v8.1c.ru/8.1/data/core')
$nsm.AddNamespace('xr', 'http://v8.1c.ru/8.3/xcf/readable')
$nsm.AddNamespace('xsi', 'http://www.w3.org/2001/XMLSchema-instance')
$nsm.AddNamespace('app', 'http://v8.1c.ru/8.2/managed-application/core')
$rootEl = $doc.DocumentElement
if ($rootEl.LocalName -ne 'MetaDataObject') {
[Console]::Error.WriteLine("meta-decompile: ожидался root <MetaDataObject>, получен <$($rootEl.LocalName)>"); exit 3
}
# Первый элемент-потомок MetaDataObject = объект; его LocalName = тип.
$objNode = $null
foreach ($c in $rootEl.ChildNodes) { if ($c.NodeType -eq 'Element') { $objNode = $c; break } }
if (-not $objNode) { [Console]::Error.WriteLine("meta-decompile: пустой MetaDataObject"); exit 3 }
$objType = $objNode.LocalName
if ($objType -notin @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate')) {
[Console]::Error.WriteLine("meta-decompile: тип '$objType' пока не поддержан (…, CommonPicture, CommonTemplate)"); exit 3
}
$props = $objNode.SelectSingleNode('md:Properties', $nsm)
function P { param([string]$tag) $n = $props.SelectSingleNode("md:$tag", $nsm); if ($n) { return $n.InnerText } else { return $null } }
# --- Synonym (ru) ---
function Get-MLru {
param($node)
if (-not $node) { return $null }
$it = $node.SelectSingleNode("v8:item[v8:lang='ru']/v8:content", $nsm)
if ($it) { return $it.InnerText }
return $null
}
# ML-значение → строка (если единственный item ru) ЛИБО [ordered]{lang:content} (мультиязычно, порядок из XML).
# null если контента нет. Компактная строка для ru-only, объект для мультиязычных.
function Get-MLValue {
param($node)
if (-not $node) { return $null }
$items = @($node.SelectNodes('v8:item', $nsm))
if ($items.Count -eq 0) { return $null }
if ($items.Count -eq 1) {
$lang = $items[0].SelectSingleNode('v8:lang', $nsm).InnerText
$content = $items[0].SelectSingleNode('v8:content', $nsm).InnerText
# Единственный ru-item: пустое содержимое ≡ отсутствие значения → $null (иначе tooltip:"" ≠ self-close).
if ($lang -eq 'ru') { if ($content -eq '') { return $null } else { return $content } }
}
$o = [ordered]@{}
foreach ($it in $items) {
$l = $it.SelectSingleNode('v8:lang', $nsm).InnerText
$c = $it.SelectSingleNode('v8:content', $nsm).InnerText
$o[$l] = $c
}
return $o
}
# Авто-синоним: точное зеркало Split-CamelCase из meta-compile (стр.354). Совпало → ключ опускаем.
# ВАЖНО: логика должна совпадать байт-в-байт с компилятором, иначе ложные «синоним==авто» → диффы.
function Split-CamelWords {
param([string]$name)
if (-not $name) { return $name }
$result = [regex]::Replace($name, '([а-яё])([А-ЯЁ])', '$1 $2')
$result = [regex]::Replace($result, '([a-z])([A-Z])', '$1 $2')
# HE-эвристика (зеркало Split-CamelCase компилятора): сохраняем прогон заглавных >=2, если сразу за ним
# НЕ буква (пробел/цифра/спецсимвол/конец); прилипшие предлоги/бренды перед буквой → лоуэркейз. Первый символ — как есть.
if ($result.Length -gt 1) {
$chars = $result.ToCharArray()
$n = $chars.Length
$keep = New-Object 'bool[]' $n
$i = 0
while ($i -lt $n) {
if ([char]::IsUpper($chars[$i])) {
$j = $i
while ($j -lt $n -and [char]::IsUpper($chars[$j])) { $j++ }
$afterBoundary = ($j -eq $n) -or (-not [char]::IsLetter($chars[$j]))
if (($j - $i) -ge 2 -and $afterBoundary) { for ($k = $i; $k -lt $j; $k++) { $keep[$k] = $true } }
$i = $j
} else { $i++ }
}
$sb = New-Object System.Text.StringBuilder
for ($idx = 0; $idx -lt $n; $idx++) {
$c = $chars[$idx]
if ($idx -eq 0 -or $keep[$idx]) { [void]$sb.Append($c) }
elseif ([char]::IsUpper($c)) { [void]$sb.Append([char]::ToLower($c)) }
else { [void]$sb.Append($c) }
}
$result = $sb.ToString()
}
return $result
}
$objName = P 'Name'
# --- Тип реквизита: <Type> → shorthand-строка ---
function Strip-NsPrefix { param([string]$s) if ($s -match ':') { return ($s -split ':', 2)[1] } else { return $s } }
function Get-TypeShorthand {
param($typeNode)
if (-not $typeNode) { return "" }
$parts = @()
$children = @($typeNode.ChildNodes | Where-Object { $_.NodeType -eq 'Element' })
for ($i = 0; $i -lt $children.Count; $i++) {
$c = $children[$i]
$ln = $c.LocalName
if ($ln -eq 'Type') {
$raw = $c.InnerText.Trim()
$next = if ($i + 1 -lt $children.Count) { $children[$i+1] } else { $null }
switch -regex ($raw) {
'(^|:)boolean$' { $parts += 'Boolean'; break }
'(^|:)string$' {
$len = '10'; $al = ''
$sq = if ($next -and $next.LocalName -eq 'StringQualifiers') { $next } else { $typeNode.SelectSingleNode('v8:StringQualifiers', $nsm) }
if ($sq) {
$l = $sq.SelectSingleNode('v8:Length', $nsm); if ($l) { $len = $l.InnerText }
$aln = $sq.SelectSingleNode('v8:AllowedLength', $nsm); if ($aln -and $aln.InnerText -eq 'Fixed') { $al = ',fixed' }
}
$parts += "String($len$al)"; break
}
'(^|:)decimal$' {
$d = '10'; $f = '0'; $sign = ''
$nq = if ($next -and $next.LocalName -eq 'NumberQualifiers') { $next } else { $typeNode.SelectSingleNode('v8:NumberQualifiers', $nsm) }
if ($nq) {
$dn = $nq.SelectSingleNode('v8:Digits', $nsm); if ($dn) { $d = $dn.InnerText }
$fn = $nq.SelectSingleNode('v8:FractionDigits', $nsm); if ($fn) { $f = $fn.InnerText }
$sn = $nq.SelectSingleNode('v8:AllowedSign', $nsm); if ($sn -and $sn.InnerText -eq 'Nonnegative') { $sign = ',nonneg' }
}
$parts += "Number($d,$f$sign)"; break
}
'(^|:)dateTime$' {
$fr = 'DateTime'
$dq = if ($next -and $next.LocalName -eq 'DateQualifiers') { $next } else { $typeNode.SelectSingleNode('v8:DateQualifiers', $nsm) }
if ($dq) { $dn = $dq.SelectSingleNode('v8:DateFractions', $nsm); if ($dn) { $fr = $dn.InnerText } }
$parts += $fr; break # Date | DateTime
}
'(^|:)base64Binary$' { $parts += 'ValueStorage'; break }
default { $parts += (Strip-NsPrefix $raw) } # cfg:CatalogRef.X → CatalogRef.X
}
} elseif ($ln -eq 'TypeSet') {
$parts += (Strip-NsPrefix $c.InnerText.Trim()) # cfg:DefinedType.X → DefinedType.X
}
}
return ($parts -join ' + ')
}
# Скалярное значение параметра выбора (<Value xsi:type=...>) → JSON-значение (bool/число/строка).
# string/dateTime/DesignTimeRef → строка (компилятор auto-детектит обратно).
function Convert-ChScalarNode {
param($vN)
$xt = $vN.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
$txt = $vN.InnerText
if ($xt -match 'boolean$') { return ($txt -eq 'true') }
if ($xt -match 'decimal$') {
if ($txt -match '^-?\d+$') { return [int]$txt }
return [double]::Parse($txt, [System.Globalization.CultureInfo]::InvariantCulture)
}
return $txt
}
# app:value (тип прямо на узле) → значение ЛИБО массив (v8:FixedArray с детьми v8:Value).
function Get-ChoiceParamValue {
param($valNode)
$xt = $valNode.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($xt -match 'FixedArray$') {
$arr = [System.Collections.ArrayList]@()
foreach ($sub in @($valNode.SelectNodes('v8:Value', $nsm))) { [void]$arr.Add((Convert-ChScalarNode $sub)) }
return ,$arr # унарная запятая: PS иначе разворачивает 1-элементную коллекцию при return → FixedArray станет скаляром
}
return Convert-ChScalarNode $valNode
}
# <ChoiceParameterLinks> → [{name,dataPath,valueChange?}] | строки "name=dataPath". $tag: 'md:...' (реквизит) | 'xr:...' (станд.).
function Parse-ChoiceParameterLinks {
param($parent, [string]$tag)
$node = $parent.SelectSingleNode($tag, $nsm)
if (-not $node) { return $null }
$links = @($node.SelectNodes('xr:Link', $nsm))
if ($links.Count -eq 0) { return $null }
$arr = [System.Collections.ArrayList]@()
foreach ($lk in $links) {
$lName = $lk.SelectSingleNode('xr:Name', $nsm).InnerText
$lDp = $lk.SelectSingleNode('xr:DataPath', $nsm).InnerText
$vcN = $lk.SelectSingleNode('xr:ValueChange', $nsm); $vcv = if ($vcN) { $vcN.InnerText } else { 'Clear' }
if ($vcv -eq 'Clear') { [void]$arr.Add("$lName=$lDp") }
else { [void]$arr.Add([ordered]@{ name = $lName; dataPath = $lDp; valueChange = $vcv }) }
}
return $arr
}
# <ChoiceParameters> → [{name,value?}]. $tag: 'md:...' | 'xr:...'.
function Parse-ChoiceParameters {
param($parent, [string]$tag)
$node = $parent.SelectSingleNode($tag, $nsm)
if (-not $node) { return $null }
$items = @($node.SelectNodes('app:item', $nsm))
if ($items.Count -eq 0) { return $null }
$arr = [System.Collections.ArrayList]@()
foreach ($it in $items) {
$pName = $it.GetAttribute('name')
$valN = $it.SelectSingleNode('app:value', $nsm)
$nilAttr = if ($valN) { $valN.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') } else { '' }
if (-not $valN -or $nilAttr -eq 'true') { [void]$arr.Add([ordered]@{ name = $pName }) }
else { $o = [ordered]@{ name = $pName }; $o['value'] = Get-ChoiceParamValue $valN; [void]$arr.Add($o) }
}
return $arr
}
# --- Реквизит → DSL: shorthand-строка "Имя: Тип | флаги" ЛИБО object-форма при кастомном синониме.
# (Синоним ≠ авто → object {name, type, synonym, [flags]}; иначе компактный shorthand.) ---
function Attr-ToDsl {
param($attrNode)
$ap = $attrNode.SelectSingleNode('md:Properties', $nsm)
$nm = ($ap.SelectSingleNode('md:Name', $nsm)).InnerText
$ts = Get-TypeShorthand ($ap.SelectSingleNode('md:Type', $nsm))
$flags = @()
$fc = $ap.SelectSingleNode('md:FillChecking', $nsm); if ($fc -and $fc.InnerText -eq 'ShowError') { $flags += 'req' }
$ix = $ap.SelectSingleNode('md:Indexing', $nsm)
if ($ix) { if ($ix.InnerText -eq 'Index') { $flags += 'index' } elseif ($ix.InnerText -eq 'IndexWithAdditionalOrder') { $flags += 'indexAdditional' } }
$ml = $ap.SelectSingleNode('md:MultiLine', $nsm); if ($ml -and $ml.InnerText -eq 'true') { $flags += 'multiline' }
# Синоним/подсказка (строка ru-only ИЛИ {ru,en}).
$synNode = $ap.SelectSingleNode('md:Synonym', $nsm)
$synVal = Get-MLValue $synNode
$synCustom = $false
# Пустой <Synonym/> (узел есть, значения нет) ≠ авто-синоним из имени → явный пустой (synonym:"").
# У Catalog-реквизитов не встречается (0/4018), у части ExchangePlan — да.
$synEmpty = ($synNode -and $null -eq $synVal)
if ($synVal -is [string]) { if ($synVal -cne (Split-CamelWords $nm)) { $synCustom = $true } }
elseif ($null -ne $synVal) { $synCustom = $true } # {ru,en} = всегда кастом
$ttVal = Get-MLValue ($ap.SelectSingleNode('md:ToolTip', $nsm))
# Extra-свойства реквизита (omit-on-default). Наличие любого → object-форма.
# $en(tag) → InnerText узла или $null.
$en = { param($tag) $n = $ap.SelectSingleNode("md:$tag", $nsm); if ($n) { $n.InnerText } else { $null } }
$extra = [ordered]@{}
$v = & $en 'Comment'; if ($v) { $extra['comment'] = $v }
$v = & $en 'FullTextSearch'; if ($v -and $v -ne 'Use') { $extra['fullTextSearch'] = $v }
$v = & $en 'FillFromFillingValue'; if ($v -eq 'true') { $extra['fillFromFillingValue'] = $true }
$v = & $en 'CreateOnInput'; if ($v -and $v -ne 'Auto') { $extra['createOnInput'] = $v }
$v = & $en 'QuickChoice'; if ($v -and $v -ne 'Auto') { $extra['quickChoice'] = $v }
$v = & $en 'DataHistory'; if ($v -and $v -ne 'Use') { $extra['dataHistory'] = $v }
$v = & $en 'Use'; if ($v -and $v -ne 'ForItem') { $extra['use'] = $v }
$v = & $en 'PasswordMode'; if ($v -eq 'true') { $extra['passwordMode'] = $true }
$v = & $en 'Mask'; if ($v) { $extra['mask'] = $v }
$v = & $en 'ChoiceHistoryOnInput'; if ($v -and $v -ne 'Auto') { $extra['choiceHistoryOnInput'] = $v }
$v = & $en 'FillChecking'; if ($v -eq 'ShowWarning') { $extra['fillChecking'] = 'ShowWarning' }
$v = & $en 'ExtendedEdit'; if ($v -eq 'true') { $extra['extendedEdit'] = $true }
$v = & $en 'MarkNegatives'; if ($v -eq 'true') { $extra['markNegatives'] = $true }
$v = & $en 'ChoiceFoldersAndItems'; if ($v -and $v -ne 'Items') { $extra['choiceFoldersAndItems'] = $v }
$v = & $en 'ChoiceForm'; if ($v) { $extra['choiceForm'] = $v }
# Регистро-специфика измерения (теги присутствуют только у Dimension → безвредно для прочих).
$v = & $en 'Master'; if ($v -eq 'true') { $extra['master'] = $true }
$v = & $en 'MainFilter'; if ($v -eq 'true') { $extra['mainFilter'] = $true }
$v = & $en 'DenyIncompleteValues'; if ($v -eq 'true') { $extra['denyIncompleteValues'] = $true }
$v = & $en 'UseInTotals'; if ($v -eq 'false') { $extra['useInTotals'] = $false } # дефолт true → захват при false
$v = & $en 'BaseDimension'; if ($v -eq 'true') { $extra['baseDimension'] = $true }
$v = & $en 'ScheduleLink'; if ($v) { $extra['scheduleLink'] = $v } # ссылка на измерение графика (пустой → пропуск)
$v = & $en 'Balance'; if ($v -eq 'true') { $extra['balance'] = $true }
$v = & $en 'AccountingFlag'; if ($v) { $extra['accountingFlag'] = $v } # ссылка на признак учёта ПС (пустой → пропуск)
$v = & $en 'ExtDimensionAccountingFlag'; if ($v) { $extra['extDimensionAccountingFlag'] = $v }
$v = & $en 'AddressingDimension'; if ($v) { $extra['addressingDimension'] = $v } # ссылка на измерение регистра исполнителей
# MinValue/MaxValue — граница диапазона (omit при nil). Тип сохраняем: xs:string→строка, xs:decimal→число.
foreach ($mm in @('MinValue','MaxValue')) {
$mn = $ap.SelectSingleNode("md:$mm", $nsm)
if (-not $mn) { continue }
if ($mn.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -eq 'true') { continue }
$key = if ($mm -eq 'MinValue') { 'minValue' } else { 'maxValue' }
$xt = $mn.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
$txt = $mn.InnerText
if ($xt -match 'decimal|int|double|float') {
if ($txt -match '^-?\d+$') { $extra[$key] = [long]$txt } else { $extra[$key] = [double]$txt }
} else { $extra[$key] = $txt }
}
$fmtV = Get-MLValue ($ap.SelectSingleNode('md:Format', $nsm)); if ($null -ne $fmtV) { $extra['format'] = $fmtV }
$efmtV = Get-MLValue ($ap.SelectSingleNode('md:EditFormat', $nsm)); if ($null -ne $efmtV) { $extra['editFormat'] = $efmtV }
# FillValue (значение заполнения). Форма по умолчанию зависит от типа реквизита: String→typed-empty,
# Number→zero, всё остальное→nil. Эмитим `fillValue` только при отклонении от дефолта (§4.2 spec).
# nil у String/Number → JSON null (nil-override); реальное значение → строка/bool/число/DTR-путь verbatim.
$fvNode = $ap.SelectSingleNode('md:FillValue', $nsm)
if ($fvNode) {
$fcat = 'Other'
if ($ts -match '\+') { $fcat = 'Other' }
elseif ($ts -match '^Boolean') { $fcat = 'Boolean' }
elseif ($ts -match '^String') { $fcat = 'String' }
elseif ($ts -match '^Number') { $fcat = 'Number' }
elseif ($ts -match '^(Date|DateTime)') { $fcat = 'Date' }
$nilAttr = $fvNode.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance')
$xsiT = $fvNode.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
$fvText = $fvNode.InnerText
if ($nilAttr -eq 'true') {
if ($fcat -eq 'String' -or $fcat -eq 'Number') { $extra['fillValue'] = $null } # nil-override
# иначе nil — это дефолт → пропускаем
} elseif ($xsiT -match 'boolean$') {
$extra['fillValue'] = ($fvText -eq 'true')
} elseif ($xsiT -match 'decimal$') {
# Захватываем как ЧИСЛО (не строку): на составном типе компилятор берёт xsi-тип из JSON-значения —
# строка "0" дала бы xs:string, число 0 → xs:decimal. У плоского Number эмиссия и так type-aware.
if (-not ($fcat -eq 'Number' -and ($fvText -eq '0' -or $fvText -eq ''))) {
$extra['fillValue'] = if ($fvText -match '^-?\d+$') { [long]$fvText } else { [double]$fvText }
}
} elseif ($xsiT -match 'string$') {
if (-not ($fcat -eq 'String' -and $fvText -eq '')) { $extra['fillValue'] = $fvText }
} elseif ($xsiT -match 'dateTime$') {
$extra['fillValue'] = $fvText
} elseif ($xsiT -match 'DesignTimeRef$') {
# Пустой DTR (ссылочный fillValue без значения) ≠ nil/xs:string → маркер emptyRef (иначе тип терялся в xs:string).
if ($fvText -eq '') { $extra['fillValue'] = [ordered]@{ emptyRef = $true } } else { $extra['fillValue'] = $fvText }
}
}
# LinkByType (связь по типу): DataPath + LinkItem. Пусто → пропускаем. linkItem=0 → компактно строкой.
$lbtNode = $ap.SelectSingleNode('md:LinkByType', $nsm)
if ($lbtNode) {
$dpN = $lbtNode.SelectSingleNode('xr:DataPath', $nsm)
if ($dpN -and $dpN.InnerText) {
$liN = $lbtNode.SelectSingleNode('xr:LinkItem', $nsm)
$li = if ($liN -and $liN.InnerText) { [int]$liN.InnerText } else { 0 }
if ($li -eq 0) { $extra['linkByType'] = $dpN.InnerText }
else { $extra['linkByType'] = [ordered]@{ dataPath = $dpN.InnerText; linkItem = $li } }
}
}
$cplArr = Parse-ChoiceParameterLinks $ap 'md:ChoiceParameterLinks'; if ($null -ne $cplArr) { $extra['choiceParameterLinks'] = $cplArr }
$cpArr = Parse-ChoiceParameters $ap 'md:ChoiceParameters'; if ($null -ne $cpArr) { $extra['choiceParameters'] = $cpArr }
# Пустой <Type/> (реквизит без типа / произвольный) → $ts=''. Отличаем от «дефолтного» отсутствия:
# заставляем объектную форму с явным type:'' (компилятор без маркера подставил бы xs:string).
$typeEmpty = ($ts -eq '')
if ($synCustom -or $synEmpty -or ($null -ne $ttVal) -or $extra.Count -gt 0 -or $typeEmpty) {
$o = [ordered]@{ name = $nm }
if ($ts) { $o['type'] = $ts } elseif ($typeEmpty) { $o['type'] = '' }
if ($synCustom) { $o['synonym'] = $synVal }
elseif ($synEmpty) { $o['synonym'] = '' }
if ($null -ne $ttVal) { $o['tooltip'] = $ttVal }
foreach ($k in $extra.Keys) { $o[$k] = $extra[$k] }
if ($flags.Count -gt 0) { $o['flags'] = [System.Collections.ArrayList]@($flags) }
return $o
}
$head = if ($ts) { "${nm}: $ts" } else { $nm }
if ($flags.Count -gt 0) { return "$head | " + ($flags -join ', ') }
return $head
}
# === Сборка DSL ===
$dsl = [ordered]@{ type = $objType; name = $objName }
# Синоним объекта: строка ru-only ИЛИ {ru,en} (мультиязычно). Кастом → эмитим.
# Пустой <Synonym/> (узел есть, значения нет) ≠ авто-синоним из имени → явный synonym:"" (иначе компилятор до-генерит из имени).
$synNodeObj = $props.SelectSingleNode('md:Synonym', $nsm)
$synVal = Get-MLValue $synNodeObj
if ($synVal -is [string]) { if ($synVal -cne (Split-CamelWords $objName)) { $dsl['synonym'] = $synVal } }
elseif ($null -ne $synVal) { $dsl['synonym'] = $synVal }
elseif ($synNodeObj) { $dsl['synonym'] = '' }
$cmt = P 'Comment'; if ($cmt) { $dsl['comment'] = $cmt }
# Свойства Catalog (omit-on-default). Порядок ключей — как удобно DSL.
function Add-BoolProp { param([string]$key, [string]$tag, [bool]$default) $v = P $tag; if ($null -ne $v) { $b = ($v -eq 'true'); if ($b -ne $default) { $dsl[$key] = $b } } }
function Add-EnumProp { param([string]$key, [string]$tag, [string]$default) $v = P $tag; if ($null -ne $v -and $v -ne '' -and $v -ne $default) { $dsl[$key] = $v } }
function Add-IntProp { param([string]$key, [string]$tag, [int]$default) $v = P $tag; if ($null -ne $v -and $v -ne '') { $iv = [int]$v; if ($iv -ne $default) { $dsl[$key] = $iv } } }
Add-BoolProp 'hierarchical' 'Hierarchical' $false
Add-EnumProp 'hierarchyType' 'HierarchyType' 'HierarchyFoldersAndItems'
Add-BoolProp 'limitLevelCount' 'LimitLevelCount' $false
Add-IntProp 'levelCount' 'LevelCount' 2
Add-BoolProp 'foldersOnTop' 'FoldersOnTop' $true
# owners
$ownersNode = $props.SelectSingleNode('md:Owners', $nsm)
if ($ownersNode) {
$items = @($ownersNode.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($items.Count -gt 0) { $dsl['owners'] = [System.Collections.ArrayList]@($items | ForEach-Object { if ($_ -match '^Catalog\.') { ($_ -split '\.', 2)[1] } else { $_ } }) }
}
Add-EnumProp 'subordinationUse' 'SubordinationUse' 'ToItems'
# Тип-зависимые дефолты (компилятор задаёт их по типу — декомпилятор обязан зеркалить, иначе omit ≠ значению).
$descrLenDef = switch ($objType) { 'ExchangePlan' { 150 } 'ChartOfCharacteristicTypes' { 100 } 'ChartOfCalculationTypes' { 100 } default { 25 } }
$codeLenDef = if ($objType -eq 'ChartOfCalculationTypes') { 5 } else { 9 }
$createInpDef = if ($objType -in @('Catalog', 'Document')) { 'Use' } else { 'DontUse' }
$dataLockDef = 'Managed' # компилятор эмитит Managed по умолчанию для всех типов (авторинг); Automatic несётся в DSL явно
$codeSeriesDef = switch ($objType) { 'ChartOfCharacteristicTypes' { 'WholeCharacteristicKind' } 'ChartOfAccounts' { 'WholeChartOfAccounts' } default { 'WholeCatalog' } }
$checkUniqueDef = ($objType -in @('ChartOfCharacteristicTypes', 'ChartOfAccounts', 'Document', 'DocumentNumerator')) # ПВХ/ПС/Документ/Нумератор дефолт true, Catalog false
$defPresDef = if ($objType -eq 'ChartOfAccounts') { 'AsCode' } else { 'AsDescription' } # ПС по умолчанию AsCode
Add-IntProp 'codeLength' 'CodeLength' $codeLenDef
Add-IntProp 'descriptionLength' 'DescriptionLength' $descrLenDef
Add-EnumProp 'codeType' 'CodeType' 'String'
Add-EnumProp 'codeAllowedLength' 'CodeAllowedLength' 'Variable'
Add-BoolProp 'autonumbering' 'Autonumbering' $true
Add-BoolProp 'checkUnique' 'CheckUnique' $checkUniqueDef
Add-EnumProp 'codeSeries' 'CodeSeries' $codeSeriesDef
Add-EnumProp 'defaultPresentation' 'DefaultPresentation' $defPresDef
if ($objType -ne 'Constant') { Add-BoolProp 'quickChoice' 'QuickChoice' $(if ($objType -eq 'Enum') { $true } else { $false }) } # Enum дефолт true; прочие false. Constant: QuickChoice — ENUM (Auto), не bool → ловим отдельно ниже
Add-EnumProp 'choiceMode' 'ChoiceMode' 'BothWays'
Add-EnumProp 'dataLockControlMode' 'DataLockControlMode' $dataLockDef
Add-EnumProp 'fullTextSearch' 'FullTextSearch' 'Use'
Add-BoolProp 'useStandardCommands' 'UseStandardCommands' $(if ($objType -in @('Enum', 'CommonForm')) { $false } else { $true }) # Enum/CommonForm дефолт false (корпус); прочие (вкл. Report/DataProcessor) — true
Add-EnumProp 'createOnInput' 'CreateOnInput' $createInpDef
Add-EnumProp 'editType' 'EditType' 'InDialog'
Add-BoolProp 'includeHelpInContents' 'IncludeHelpInContents' $false
Add-EnumProp 'choiceHistoryOnInput' 'ChoiceHistoryOnInput' 'Auto'
Add-EnumProp 'predefinedDataUpdate' 'PredefinedDataUpdate' 'Auto'
Add-EnumProp 'searchStringModeOnInputByString' 'SearchStringModeOnInputByString' 'Begin'
Add-EnumProp 'fullTextSearchOnInputByString' 'FullTextSearchOnInputByString' 'DontUse'
# ExchangePlan-специфичные свойства (у Catalog этих тегов нет → блок не трогает его).
if ($objType -eq 'ExchangePlan') {
Add-BoolProp 'distributedInfoBase' 'DistributedInfoBase' $false
Add-BoolProp 'includeConfigurationExtensions' 'IncludeConfigurationExtensions' $false
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# ChartOfCharacteristicTypes-специфичные свойства.
if ($objType -eq 'ChartOfCharacteristicTypes') {
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
$cev = P 'CharacteristicExtValues'; if ($cev) { $dsl['characteristicExtValues'] = $cev }
# Type — тип значения характеристики; valueType при отличии от дефолта (Boolean+String(100)+Number(15,2)+DateTime).
$vtNode = $props.SelectSingleNode('md:Type', $nsm)
if ($vtNode) {
$vtStr = Get-TypeShorthand $vtNode
if ($vtStr -and $vtStr -ne 'Boolean + String(100) + Number(15,2) + DateTime') { $dsl['valueType'] = $vtStr }
}
}
# ChartOfAccounts-специфичные свойства (у Catalog этих тегов нет → блок его не трогает).
if ($objType -eq 'ChartOfAccounts') {
$edt = P 'ExtDimensionTypes'; if ($edt) { $dsl['extDimensionTypes'] = $edt }
# Дефолт зеркалит компилятор: с ПВХ — 3, без — 0 (платформа не даёт > 0 без ПВХ).
Add-IntProp 'maxExtDimensionCount' 'MaxExtDimensionCount' $(if ($edt) { 3 } else { 0 })
$cm = P 'CodeMask'; if ($cm) { $dsl['codeMask'] = $cm }
Add-BoolProp 'autoOrderByCode' 'AutoOrderByCode' $true
Add-IntProp 'orderLength' 'OrderLength' 9
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# ChartOfCalculationTypes-специфичные свойства.
if ($objType -eq 'ChartOfCalculationTypes') {
Add-EnumProp 'dependenceOnCalculationTypes' 'DependenceOnCalculationTypes' 'DontUse'
Add-BoolProp 'actionPeriodUse' 'ActionPeriodUse' $false
# BaseCalculationTypes — список ссылок на ПВР (omit-on-empty, verbatim).
$bctNode = $props.SelectSingleNode('md:BaseCalculationTypes', $nsm)
if ($bctNode) {
$bctItems = @($bctNode.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($bctItems.Count -gt 0) { $dsl['baseCalculationTypes'] = [System.Collections.ArrayList]@($bctItems) }
}
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# Document-специфичные свойства: нумерация, проведение, движения, DataHistory-триплет.
if ($objType -eq 'Document') {
$numRef = P 'Numerator'; if ($numRef) { $dsl['numerator'] = $numRef }
Add-EnumProp 'numberType' 'NumberType' 'String'
Add-IntProp 'numberLength' 'NumberLength' 11
Add-EnumProp 'numberAllowedLength' 'NumberAllowedLength' 'Variable'
Add-EnumProp 'numberPeriodicity' 'NumberPeriodicity' 'Year'
# CheckUnique/Autonumbering у Document уже покрыты общим блоком (дефолты true/true совпадают).
Add-EnumProp 'posting' 'Posting' 'Allow'
Add-EnumProp 'realTimePosting' 'RealTimePosting' 'Deny'
Add-EnumProp 'registerRecordsDeletion' 'RegisterRecordsDeletion' 'AutoDelete'
Add-EnumProp 'registerRecordsWritingOnPost' 'RegisterRecordsWritingOnPost' 'WriteSelected'
Add-EnumProp 'sequenceFilling' 'SequenceFilling' 'AutoFill'
Add-BoolProp 'postInPrivilegedMode' 'PostInPrivilegedMode' $true
Add-BoolProp 'unpostInPrivilegedMode' 'UnpostInPrivilegedMode' $true
# RegisterRecords — движения (список MDObjectRef, omit-on-empty, verbatim).
$rrNode = $props.SelectSingleNode('md:RegisterRecords', $nsm)
if ($rrNode) {
$rrItems = @($rrNode.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($rrItems.Count -gt 0) { $dsl['registerRecords'] = [System.Collections.ArrayList]@($rrItems) }
}
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# InformationRegister-специфичные свойства: периодичность, режим записи, срезы, DataHistory-триплет.
if ($objType -eq 'InformationRegister') {
Add-EnumProp 'periodicity' 'InformationRegisterPeriodicity' 'Nonperiodical'
Add-EnumProp 'writeMode' 'WriteMode' 'Independent'
Add-BoolProp 'mainFilterOnPeriod' 'MainFilterOnPeriod' $false
Add-BoolProp 'enableTotalsSliceFirst' 'EnableTotalsSliceFirst' $false
Add-BoolProp 'enableTotalsSliceLast' 'EnableTotalsSliceLast' $false
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# AccumulationRegister-специфичные свойства: тип регистра, разделение итогов.
if ($objType -eq 'AccumulationRegister') {
Add-EnumProp 'registerType' 'RegisterType' 'Balance'
Add-BoolProp 'enableTotalsSplitting' 'EnableTotalsSplitting' $true
}
# AccountingRegister-специфичные свойства: ПС-связь, корреспонденция, коррекция периода, разделение итогов.
if ($objType -eq 'AccountingRegister') {
$coa = P 'ChartOfAccounts'; if ($coa) { $dsl['chartOfAccounts'] = $coa }
Add-BoolProp 'correspondence' 'Correspondence' $false
Add-IntProp 'periodAdjustmentLength' 'PeriodAdjustmentLength' 0
Add-BoolProp 'enableTotalsSplitting' 'EnableTotalsSplitting' $true
}
# CalculationRegister-специфичные свойства: ПВР-связь, периоды расчёта, график (все ссылки/enum — verbatim).
if ($objType -eq 'CalculationRegister') {
$cct = P 'ChartOfCalculationTypes'; if ($cct) { $dsl['chartOfCalculationTypes'] = $cct }
Add-EnumProp 'periodicity' 'Periodicity' 'Month'
Add-BoolProp 'actionPeriod' 'ActionPeriod' $false
Add-BoolProp 'basePeriod' 'BasePeriod' $false
$sch = P 'Schedule'; if ($sch) { $dsl['schedule'] = $sch }
$schv = P 'ScheduleValue'; if ($schv) { $dsl['scheduleValue'] = $schv }
$schd = P 'ScheduleDate'; if ($schd) { $dsl['scheduleDate'] = $schd }
}
# BusinessProcess-специфичные свойства: нумерация (Document-стиль), связь с задачей, привилегированный режим.
if ($objType -eq 'BusinessProcess') {
Add-EnumProp 'numberType' 'NumberType' 'String'
Add-IntProp 'numberLength' 'NumberLength' 11
Add-EnumProp 'numberAllowedLength' 'NumberAllowedLength' 'Variable'
Add-EnumProp 'numberPeriodicity' 'NumberPeriodicity' 'Nonperiodical'
# CheckUnique/Autonumbering дефолт true (совпадает с компилятором → захват только при false).
Add-BoolProp 'checkUnique' 'CheckUnique' $true
Add-BoolProp 'autonumbering' 'Autonumbering' $true
$tsk = P 'Task'; if ($tsk) { $dsl['task'] = $tsk }
Add-BoolProp 'createTaskInPrivilegedMode' 'CreateTaskInPrivilegedMode' $true
}
# Task-специфичные свойства: нумерация, адресация, текущий исполнитель.
if ($objType -eq 'Task') {
Add-EnumProp 'numberType' 'NumberType' 'String'
Add-IntProp 'numberLength' 'NumberLength' 14
Add-EnumProp 'numberAllowedLength' 'NumberAllowedLength' 'Variable'
Add-BoolProp 'checkUnique' 'CheckUnique' $true
Add-BoolProp 'autonumbering' 'Autonumbering' $true
$tnap = P 'TaskNumberAutoPrefix'; if ($tnap -and $tnap -ne 'BusinessProcessNumber') { $dsl['taskNumberAutoPrefix'] = $tnap }
Add-IntProp 'descriptionLength' 'DescriptionLength' 150
$addr = P 'Addressing'; if ($addr) { $dsl['addressing'] = $addr }
$maa = P 'MainAddressingAttribute'; if ($maa) { $dsl['mainAddressingAttribute'] = $maa }
$cp = P 'CurrentPerformer'; if ($cp) { $dsl['currentPerformer'] = $cp }
Add-EnumProp 'defaultPresentation' 'DefaultPresentation' 'AsDescription'
}
# Report-специфичные свойства: формы (плоские ref, не *ObjectForm), схема компоновки, хранилища вариантов/настроек.
if ($objType -eq 'Report') {
$dfm = P 'DefaultForm'; if ($dfm) { $dsl['defaultForm'] = $dfm }
$afm = P 'AuxiliaryForm'; if ($afm) { $dsl['auxiliaryForm'] = $afm }
$mdcs = P 'MainDataCompositionSchema'; if ($mdcs) { $dsl['mainDataCompositionSchema'] = $mdcs }
$dsf = P 'DefaultSettingsForm'; if ($dsf) { $dsl['defaultSettingsForm'] = $dsf }
$asf = P 'AuxiliarySettingsForm'; if ($asf) { $dsl['auxiliarySettingsForm'] = $asf }
$dvf = P 'DefaultVariantForm'; if ($dvf) { $dsl['defaultVariantForm'] = $dvf }
$vs = P 'VariantsStorage'; if ($vs) { $dsl['variantsStorage'] = $vs }
$ss = P 'SettingsStorage'; if ($ss) { $dsl['settingsStorage'] = $ss }
$ep = Get-MLValue ($props.SelectSingleNode('md:ExtendedPresentation', $nsm)); if ($null -ne $ep) { $dsl['extendedPresentation'] = $ep }
}
# DataProcessor-специфичные свойства: формы (плоские ref).
if ($objType -eq 'DataProcessor') {
$dfm = P 'DefaultForm'; if ($dfm) { $dsl['defaultForm'] = $dfm }
$afm = P 'AuxiliaryForm'; if ($afm) { $dsl['auxiliaryForm'] = $afm }
$ep = Get-MLValue ($props.SelectSingleNode('md:ExtendedPresentation', $nsm)); if ($null -ne $ep) { $dsl['extendedPresentation'] = $ep }
}
# DefinedType — тип-псевдоним: только Name/Synonym/Comment/Type. valueType (составной через ' + ').
if ($objType -eq 'DefinedType') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm))
if ($vt) { $dsl['valueType'] = $vt }
}
# FunctionalOption — функциональная опция: Location (хранилище значения) + PrivilegedGetMode + Content (зависимые объекты).
if ($objType -eq 'FunctionalOption') {
$loc = P 'Location'; if ($loc) { $dsl['location'] = $loc }
Add-BoolProp 'privilegedGetMode' 'PrivilegedGetMode' $true # корпус 2864/2864 true → дефолт true, ловим false
$contentNode = $props.SelectSingleNode('md:Content', $nsm)
if ($contentNode) {
$items = @($contentNode.SelectNodes('xr:Object', $nsm) | ForEach-Object { $_.InnerText })
if ($items.Count -gt 0) { $dsl['content'] = [System.Collections.ArrayList]@($items) }
}
}
# DocumentJournal — журнал документов: формы (плоские ref) + регистрируемые документы. Колонки → ChildObjects (ниже).
if ($objType -eq 'DocumentJournal') {
$dfm = P 'DefaultForm'; if ($dfm) { $dsl['defaultForm'] = $dfm }
$afm = P 'AuxiliaryForm'; if ($afm) { $dsl['auxiliaryForm'] = $afm }
$rdNode = $props.SelectSingleNode('md:RegisteredDocuments', $nsm)
if ($rdNode) {
$rdItems = @($rdNode.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($rdItems.Count -gt 0) { $dsl['registeredDocuments'] = [System.Collections.ArrayList]@($rdItems) }
}
}
# Sequence — последовательность документов: граница, документы, движения, измерения (ChildObjects ниже).
if ($objType -eq 'Sequence') {
Add-EnumProp 'moveBoundaryOnPosting' 'MoveBoundaryOnPosting' 'DontMove'
foreach ($ll in @(@('Documents','documents'), @('RegisterRecords','registerRecords'))) {
$ln = $props.SelectSingleNode("md:$($ll[0])", $nsm)
if ($ln) {
$items = @($ln.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($items.Count -gt 0) { $dsl[$ll[1]] = [System.Collections.ArrayList]@($items) }
}
}
# dataLockControlMode покрыт общим блоком (дефолт Managed для всех типов).
}
# FilterCriterion — критерий отбора: тип значения + состав (объекты отбора) + формы.
if ($objType -eq 'FilterCriterion') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm)); if ($vt) { $dsl['valueType'] = $vt }
$cn = $props.SelectSingleNode('md:Content', $nsm)
if ($cn) {
$items = @($cn.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($items.Count -gt 0) { $dsl['content'] = [System.Collections.ArrayList]@($items) }
}
$dfm = P 'DefaultForm'; if ($dfm) { $dsl['defaultForm'] = $dfm }
$afm = P 'AuxiliaryForm'; if ($afm) { $dsl['auxiliaryForm'] = $afm }
}
# DocumentNumerator — нумератор документов: параметры нумерации (без InternalInfo/ChildObjects).
if ($objType -eq 'DocumentNumerator') {
Add-EnumProp 'numberType' 'NumberType' 'String'
Add-IntProp 'numberLength' 'NumberLength' 11
Add-EnumProp 'numberAllowedLength' 'NumberAllowedLength' 'Variable'
Add-EnumProp 'numberPeriodicity' 'NumberPeriodicity' 'Year'
# checkUnique покрыт общим блоком (дефолт true для DocumentNumerator).
}
# SettingsStorage — хранилище настроек: формы сохранения/загрузки (плоские ref).
if ($objType -eq 'SettingsStorage') {
foreach ($fp in @(@('DefaultSaveForm','defaultSaveForm'), @('DefaultLoadForm','defaultLoadForm'), @('AuxiliarySaveForm','auxiliarySaveForm'), @('AuxiliaryLoadForm','auxiliaryLoadForm'))) {
$fv = P $fp[0]; if ($fv) { $dsl[$fp[1]] = $fv }
}
}
# CommonModule — общий модуль: флаги контекста компиляции + повторное использование значений (тело .bsl вне скоупа).
if ($objType -eq 'CommonModule') {
Add-BoolProp 'global' 'Global' $false
Add-BoolProp 'clientManagedApplication' 'ClientManagedApplication' $false
Add-BoolProp 'server' 'Server' $false
Add-BoolProp 'externalConnection' 'ExternalConnection' $false
Add-BoolProp 'clientOrdinaryApplication' 'ClientOrdinaryApplication' $false
Add-BoolProp 'serverCall' 'ServerCall' $false
Add-BoolProp 'privileged' 'Privileged' $false
Add-EnumProp 'returnValuesReuse' 'ReturnValuesReuse' 'DontUse'
}
# EventSubscription — подписка на событие: источники (набор типов), событие, обработчик.
if ($objType -eq 'EventSubscription') {
$srcNode = $props.SelectSingleNode('md:Source', $nsm)
if ($srcNode) {
# Источник — набор v8:Type (конкретный CatalogObject.X) И/ИЛИ v8:TypeSet (голый метатип ExchangePlanObject).
$srcTypes = @($srcNode.SelectNodes('v8:Type|v8:TypeSet', $nsm) | ForEach-Object { Strip-NsPrefix $_.InnerText.Trim() })
if ($srcTypes.Count -gt 0) { $dsl['source'] = [System.Collections.ArrayList]@($srcTypes) }
}
Add-EnumProp 'event' 'Event' 'BeforeWrite'
$h = P 'Handler'; if ($h) { $dsl['handler'] = $h }
}
# CommonForm — общая форма (метаданные; содержимое формы Ext/Form.xml вне роундтрипа, территория form-compile).
if ($objType -eq 'CommonForm') {
Add-EnumProp 'formType' 'FormType' 'Managed'
# UsePurposes — дефолт [PlatformApplication, MobilePlatformApplication]; захват при отличии.
$upNode = $props.SelectSingleNode('md:UsePurposes', $nsm)
if ($upNode) {
$ups = @($upNode.SelectNodes('v8:Value', $nsm) | ForEach-Object { $_.InnerText })
$def2 = @('PlatformApplication', 'MobilePlatformApplication')
$same = ($ups.Count -eq $def2.Count); if ($same) { for ($k=0; $k -lt $ups.Count; $k++) { if ($ups[$k] -ne $def2[$k]) { $same=$false; break } } }
if (-not $same -and $ups.Count -gt 0) { $dsl['usePurposes'] = [System.Collections.ArrayList]@($ups) }
}
$ep = Get-MLValue ($props.SelectSingleNode('md:ExtendedPresentation', $nsm)); if ($null -ne $ep) { $dsl['extendedPresentation'] = $ep }
}
# SessionParameter — параметр сеанса: только тип значения.
if ($objType -eq 'SessionParameter') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm)); if ($vt) { $dsl['valueType'] = $vt }
}
# FunctionalOptionsParameter — параметр функ. опции: Use (список MDObjectRef).
if ($objType -eq 'FunctionalOptionsParameter') {
$un = $props.SelectSingleNode('md:Use', $nsm)
if ($un) { $items = @($un.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText }); if ($items.Count -gt 0) { $dsl['use'] = [System.Collections.ArrayList]@($items) } }
}
# WSReference — WS-ссылка: URL расположения WSDL (+InternalInfo Manager).
if ($objType -eq 'WSReference') {
$url = P 'LocationURL'; if ($url) { $dsl['locationURL'] = $url }
}
# CommonPicture — общая картинка: доступность (содержимое Ext/Picture вне скоупа).
if ($objType -eq 'CommonPicture') {
Add-BoolProp 'availabilityForChoice' 'AvailabilityForChoice' $false
Add-BoolProp 'availabilityForAppearance' 'AvailabilityForAppearance' $false
}
# CommonTemplate — общий макет: тип макета (содержимое Ext/Template.* вне скоупа).
if ($objType -eq 'CommonTemplate') {
Add-EnumProp 'templateType' 'TemplateType' 'SpreadsheetDocument'
}
# Общий захват структурного <Picture> (зеркало Emit-CommandPicture). Пишет в $tgt ключи picture/loadTransparent.
function Get-PictureToDsl { param($propsNode, $tgt)
$refN = $propsNode.SelectSingleNode('md:Picture/xr:Ref', $nsm)
$absN = $propsNode.SelectSingleNode('md:Picture/xr:Abs', $nsm)
if ($refN -or $absN) {
$psrc = if ($refN) { $refN.InnerText } else { "abs:$($absN.InnerText)" }
$ltN = $propsNode.SelectSingleNode('md:Picture/xr:LoadTransparent', $nsm)
$ltFalse = ($ltN -and $ltN.InnerText -eq 'false')
$tpxN = $propsNode.SelectSingleNode('md:Picture/xr:TransparentPixel', $nsm)
if ($tpxN) {
$po = [ordered]@{ src = $psrc }; if ($ltFalse) { $po['loadTransparent'] = $false }
$po['transparentPixel'] = [ordered]@{ x = [int]$tpxN.GetAttribute('x'); y = [int]$tpxN.GetAttribute('y') }
$tgt['picture'] = $po
} else { $tgt['picture'] = $psrc; if ($ltFalse) { $tgt['loadTransparent'] = $false } }
}
}
# CommandGroup — группа команд: представление, подсказка, картинка, категория.
if ($objType -eq 'CommandGroup') {
Add-EnumProp 'representation' 'Representation' 'Auto'
$tt = Get-MLValue ($props.SelectSingleNode('md:ToolTip', $nsm)); if ($null -ne $tt) { $dsl['tooltip'] = $tt }
Get-PictureToDsl $props $dsl
Add-EnumProp 'category' 'Category' 'NavigationPanel'
}
# CommonCommand — общая команда: группа, представление, подсказка, картинка, параметр, режимы.
if ($objType -eq 'CommonCommand') {
$grp = P 'Group'; if ($grp) { $dsl['group'] = $grp }
Add-EnumProp 'representation' 'Representation' 'Auto'
$tt = Get-MLValue ($props.SelectSingleNode('md:ToolTip', $nsm)); if ($null -ne $tt) { $dsl['tooltip'] = $tt }
Get-PictureToDsl $props $dsl
$sc = P 'Shortcut'; if ($sc) { $dsl['shortcut'] = $sc }
$cpt = Get-TypeShorthand ($props.SelectSingleNode('md:CommandParameterType', $nsm)); if ($cpt) { $dsl['commandParameterType'] = $cpt }
Add-EnumProp 'parameterUseMode' 'ParameterUseMode' 'Single'
Add-BoolProp 'modifiesData' 'ModifiesData' $false
Add-EnumProp 'onMainServerUnavalableBehavior' 'OnMainServerUnavalableBehavior' 'Auto'
}
# CommonAttribute — общий реквизит: тип + value-свойства + состав объектов + свойства разделения данных.
if ($objType -eq 'CommonAttribute') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm)); if ($vt -and $vt -ne 'String(0)') { $dsl['valueType'] = $vt }
Add-BoolProp 'passwordMode' 'PasswordMode' $false
foreach ($mlp in @(@('Format','format'), @('EditFormat','editFormat'), @('ToolTip','tooltip'))) {
$mv = Get-MLValue ($props.SelectSingleNode("md:$($mlp[0])", $nsm)); if ($null -ne $mv) { $dsl[$mlp[1]] = $mv }
}
Add-BoolProp 'markNegatives' 'MarkNegatives' $false
$msk = P 'Mask'; if ($msk) { $dsl['mask'] = $msk }
Add-BoolProp 'multiLine' 'MultiLine' $false
Add-BoolProp 'extendedEdit' 'ExtendedEdit' $false
foreach ($mm in @(@('MinValue','minValue'), @('MaxValue','maxValue'))) {
$mn = $props.SelectSingleNode("md:$($mm[0])", $nsm)
if ($mn -and $mn.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -ne 'true') {
$mxt = $mn.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($mxt -match 'decimal$') { $dsl[$mm[1]] = if ($mn.InnerText -match '^-?\d+$') { [long]$mn.InnerText } else { [double]$mn.InnerText } } else { $dsl[$mm[1]] = $mn.InnerText }
}
}
Add-BoolProp 'fillFromFillingValue' 'FillFromFillingValue' $false
# FillValue: тип-зависимый дефолт (String→typed-empty, Number→0, прочее→nil). Захват при отклонении.
# nil на String/Number-типе ≠ дефолт → маркер {nil:true} (напр. системный реквизит-разделитель ОбластьДанных).
$catVt = if ($vt) { $vt } else { 'String(0)' }
$fvN = $props.SelectSingleNode('md:FillValue', $nsm)
if ($fvN) {
$fvNil = ($fvN.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -eq 'true')
if ($fvNil) {
if ($catVt -match '^(String|Number)') { $dsl['fillValue'] = [ordered]@{ nil = $true } }
} else {
$fvXt = $fvN.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($fvXt -match 'DesignTimeRef$' -and $fvN.InnerText -eq '') { $dsl['fillValue'] = [ordered]@{ emptyRef = $true } }
elseif ($fvXt -match 'decimal$') { if ($fvN.InnerText -ne '0') { $dsl['fillValue'] = if ($fvN.InnerText -match '^-?\d+$') { [long]$fvN.InnerText } else { [double]$fvN.InnerText } } }
elseif (-not [string]::IsNullOrEmpty($fvN.InnerText)) { $dsl['fillValue'] = $fvN.InnerText }
}
}
Add-EnumProp 'fillChecking' 'FillChecking' 'DontCheck'
Add-EnumProp 'choiceFoldersAndItems' 'ChoiceFoldersAndItems' 'Items'
$cpl = Parse-ChoiceParameterLinks $props 'md:ChoiceParameterLinks'; if ($null -ne $cpl) { $dsl['choiceParameterLinks'] = $cpl }
$cp = Parse-ChoiceParameters $props 'md:ChoiceParameters'; if ($null -ne $cp) { $dsl['choiceParameters'] = $cp }
Add-EnumProp 'quickChoice' 'QuickChoice' 'Auto'
Add-EnumProp 'createOnInput' 'CreateOnInput' 'Auto'
$cf = P 'ChoiceForm'; if ($cf) { $dsl['choiceForm'] = $cf }
Add-EnumProp 'choiceHistoryOnInput' 'ChoiceHistoryOnInput' 'Auto'
# Content — объекты, к которым добавлен общий реквизит.
$cn = $props.SelectSingleNode('md:Content', $nsm)
if ($cn) {
$cArr = [System.Collections.ArrayList]@()
foreach ($it in @($cn.SelectNodes('xr:Item', $nsm))) {
$mdN = $it.SelectSingleNode('xr:Metadata', $nsm); $mdv = if ($mdN) { $mdN.InnerText } else { '' }
$useN = $it.SelectSingleNode('xr:Use', $nsm); $usev = if ($useN) { $useN.InnerText } else { 'Use' }
$csN = $it.SelectSingleNode('xr:ConditionalSeparation', $nsm); $csv = if ($csN) { $csN.InnerText } else { '' }
if ($usev -eq 'Use' -and -not $csv) { [void]$cArr.Add($mdv) }
else { $io = [ordered]@{ metadata = $mdv }; if ($usev -ne 'Use') { $io['use'] = $usev }; if ($csv) { $io['conditionalSeparation'] = $csv }; [void]$cArr.Add($io) }
}
if ($cArr.Count -gt 0) { $dsl['content'] = $cArr }
}
Add-EnumProp 'autoUse' 'AutoUse' 'DontUse'
Add-EnumProp 'dataSeparation' 'DataSeparation' 'DontUse'
Add-EnumProp 'separatedDataUse' 'SeparatedDataUse' 'Independently'
$dsv = P 'DataSeparationValue'; if ($dsv) { $dsl['dataSeparationValue'] = $dsv }
$dsu = P 'DataSeparationUse'; if ($dsu) { $dsl['dataSeparationUse'] = $dsu }
$cs2 = P 'ConditionalSeparation'; if ($cs2) { $dsl['conditionalSeparation'] = $cs2 }
Add-EnumProp 'usersSeparation' 'UsersSeparation' 'DontUse'
Add-EnumProp 'authenticationSeparation' 'AuthenticationSeparation' 'DontUse'
Add-EnumProp 'configurationExtensionsSeparation' 'ConfigurationExtensionsSeparation' 'DontUse'
Add-EnumProp 'indexing' 'Indexing' 'DontIndex'
Add-EnumProp 'dataHistory' 'DataHistory' 'Use'
# fullTextSearch покрыт общим блоком (дефолт Use).
}
# ScheduledJob — регламентное задание: метод, ключ, флаги, рестарт.
if ($objType -eq 'ScheduledJob') {
$mn = P 'MethodName'; if ($mn) { $dsl['methodName'] = $mn }
$descr = P 'Description'; if ($descr) { $dsl['description'] = $descr }
$k = P 'Key'; if ($k) { $dsl['key'] = $k }
Add-BoolProp 'use' 'Use' $false
Add-BoolProp 'predefined' 'Predefined' $false
Add-IntProp 'restartCountOnFailure' 'RestartCountOnFailure' 3
Add-IntProp 'restartIntervalOnFailure' 'RestartIntervalOnFailure' 10
}
# Constant — богатый одиночный реквизит: Type + свойства значения (как у реквизита) + object-уровень.
if ($objType -eq 'Constant') {
$vt = Get-TypeShorthand ($props.SelectSingleNode('md:Type', $nsm))
if ($vt) { $dsl['valueType'] = $vt } else { $dsl['valueType'] = '' }
$dfm = P 'DefaultForm'; if ($dfm) { $dsl['defaultForm'] = $dfm }
$ep = Get-MLValue ($props.SelectSingleNode('md:ExtendedPresentation', $nsm)); if ($null -ne $ep) { $dsl['extendedPresentation'] = $ep }
Add-BoolProp 'passwordMode' 'PasswordMode' $false
$fmt = Get-MLValue ($props.SelectSingleNode('md:Format', $nsm)); if ($null -ne $fmt) { $dsl['format'] = $fmt }
$efmt = Get-MLValue ($props.SelectSingleNode('md:EditFormat', $nsm)); if ($null -ne $efmt) { $dsl['editFormat'] = $efmt }
$tt = Get-MLValue ($props.SelectSingleNode('md:ToolTip', $nsm)); if ($null -ne $tt) { $dsl['tooltip'] = $tt }
Add-BoolProp 'markNegatives' 'MarkNegatives' $false
$msk = P 'Mask'; if ($msk) { $dsl['mask'] = $msk }
Add-BoolProp 'multiLine' 'MultiLine' $false
Add-BoolProp 'extendedEdit' 'ExtendedEdit' $false
# MinValue/MaxValue (дефолт nil) — типизированное значение при наличии.
foreach ($mm in @(@('MinValue','minValue'), @('MaxValue','maxValue'))) {
$mn = $props.SelectSingleNode("md:$($mm[0])", $nsm)
if ($mn -and $mn.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -ne 'true') {
$mxt = $mn.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($mxt -match 'decimal$') { $dsl[$mm[1]] = if ($mn.InnerText -match '^-?\d+$') { [long]$mn.InnerText } else { [double]$mn.InnerText } }
else { $dsl[$mm[1]] = $mn.InnerText }
}
}
Add-EnumProp 'fillChecking' 'FillChecking' 'DontCheck'
Add-EnumProp 'choiceFoldersAndItems' 'ChoiceFoldersAndItems' 'Items'
$cpl = Parse-ChoiceParameterLinks $props 'md:ChoiceParameterLinks'; if ($null -ne $cpl) { $dsl['choiceParameterLinks'] = $cpl }
$cp = Parse-ChoiceParameters $props 'md:ChoiceParameters'; if ($null -ne $cp) { $dsl['choiceParameters'] = $cp }
Add-EnumProp 'quickChoice' 'QuickChoice' 'Auto'
$cf = P 'ChoiceForm'; if ($cf) { $dsl['choiceForm'] = $cf }
$lbtNode = $props.SelectSingleNode('md:LinkByType', $nsm)
if ($lbtNode) {
$dpN = $lbtNode.SelectSingleNode('md:DataPath', $nsm)
if ($dpN -and $dpN.InnerText) {
$liN = $lbtNode.SelectSingleNode('md:LinkItem', $nsm)
$li = if ($liN -and $liN.InnerText) { [int]$liN.InnerText } else { 0 }
$dsl['linkByType'] = if ($li -eq 0) { $dpN.InnerText } else { [ordered]@{ dataPath = $dpN.InnerText; linkItem = $li } }
}
}
Add-EnumProp 'dataHistory' 'DataHistory' 'DontUse'
Add-BoolProp 'updateDataHistoryImmediatelyAfterWrite' 'UpdateDataHistoryImmediatelyAfterWrite' $false
Add-BoolProp 'executeAfterWriteDataHistoryVersionProcessing' 'ExecuteAfterWriteDataHistoryVersionProcessing' $false
}
# Короткая форма поля: <Type>.<Name>.StandardAttribute.X / .Attribute.X → StandardAttribute.X / Attribute.X
# (Expand-DataPath компилятора разворачивает частичную форму обратно — dogfood резолвера).
function Short-Field { param([string]$full) if ($full -match '\.(StandardAttribute|Attribute)\.(.+)$') { return "$($Matches[1]).$($Matches[2])" } return $full }
# InputByString — эмитим только при отличии от выведенного дефолта [Descr при D>0]+[Code при C>0].
$ibNode = $props.SelectSingleNode('md:InputByString', $nsm)
if ($ibNode) {
$ibActual = @($ibNode.SelectNodes('xr:Field', $nsm) | ForEach-Object { $_.InnerText })
$clv = P 'CodeLength'; $dlv = P 'DescriptionLength'
$cl = if ($clv -and $clv -ne '') { [int]$clv } else { 9 }
$dl = if ($dlv -and $dlv -ne '') { [int]$dlv } else { 25 }
$ibDef = @()
if ($dl -gt 0) { $ibDef += "StandardAttribute.Description" }
if ($cl -gt 0) { $ibDef += "StandardAttribute.Code" }
$ibShort = @($ibActual | ForEach-Object { Short-Field $_ })
$same = ($ibShort.Count -eq $ibDef.Count)
if ($same) { for ($k = 0; $k -lt $ibShort.Count; $k++) { if ($ibShort[$k] -ne $ibDef[$k]) { $same = $false; break } } }
if (-not $same) { $dsl['inputByString'] = [System.Collections.ArrayList]@($ibShort) }
}
# BasedOn — «ввод на основании», список MDObjectRef (omit-on-empty).
$boNode = $props.SelectSingleNode('md:BasedOn', $nsm)
if ($boNode) {
$boItems = @($boNode.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($boItems.Count -gt 0) { $dsl['basedOn'] = [System.Collections.ArrayList]@($boItems) }
}
# DataLockFields — поля блокировки данных (omit-on-empty).
$dlfNode = $props.SelectSingleNode('md:DataLockFields', $nsm)
if ($dlfNode) {
$dlfFields = @($dlfNode.SelectNodes('xr:Field', $nsm) | ForEach-Object { Short-Field $_.InnerText })
if ($dlfFields.Count -gt 0) { $dsl['dataLockFields'] = [System.Collections.ArrayList]@($dlfFields) }
}
# Формы по умолчанию (компилятор пишет пусто → omit-on-empty; значение = ссылка на форму verbatim).
function Add-FormRef { param([string]$key, [string]$tag) $v = P $tag; if ($v) { $dsl[$key] = $v } }
Add-FormRef 'defaultObjectForm' 'DefaultObjectForm'
Add-FormRef 'defaultFolderForm' 'DefaultFolderForm'
Add-FormRef 'defaultListForm' 'DefaultListForm'
Add-FormRef 'defaultChoiceForm' 'DefaultChoiceForm'
Add-FormRef 'defaultFolderChoiceForm' 'DefaultFolderChoiceForm'
Add-FormRef 'auxiliaryObjectForm' 'AuxiliaryObjectForm'
Add-FormRef 'auxiliaryFolderForm' 'AuxiliaryFolderForm'
Add-FormRef 'auxiliaryListForm' 'AuxiliaryListForm'
Add-FormRef 'auxiliaryChoiceForm' 'AuxiliaryChoiceForm'
Add-FormRef 'auxiliaryFolderChoiceForm' 'AuxiliaryFolderChoiceForm'
Add-FormRef 'defaultRecordForm' 'DefaultRecordForm'
Add-FormRef 'auxiliaryRecordForm' 'AuxiliaryRecordForm'
# Презентации (ML, компилятор пишет пусто → omit-on-empty).
foreach ($pp in @(
@('ObjectPresentation','objectPresentation'), @('ExtendedObjectPresentation','extendedObjectPresentation'),
@('RecordPresentation','recordPresentation'), @('ExtendedRecordPresentation','extendedRecordPresentation'),
@('ListPresentation','listPresentation'), @('ExtendedListPresentation','extendedListPresentation'),
@('Explanation','explanation'))) {
$pv = Get-MLValue ($props.SelectSingleNode("md:$($pp[0])", $nsm))
if ($null -ne $pv) { $dsl[$pp[1]] = $pv }
}
# --- Characteristics (привязка ПВХ). Короткая форма: поля bare/partial, filterValue без каталога, from полный.
function Shorten-CharField { param([string]$full, [string]$from)
if ($full.StartsWith("$from.")) {
$rest = $full.Substring($from.Length + 1)
if ($rest -match '^StandardAttribute\.(Ref|Parent|Owner)$') { return $Matches[1] } # ссылочные станд. → голое
if ($rest -match '^Attribute\.(.+)$') { return $Matches[1] } # кастом → голое
return $rest # прочие StandardAttribute.X / Dimension.X / Resource.X → частичное (безопасно)
}
return $full
}
$charsNode = $props.SelectSingleNode('md:Characteristics', $nsm)
if ($charsNode) {
$chList = @($charsNode.SelectNodes('xr:Characteristic', $nsm))
if ($chList.Count -gt 0) {
$chArr = [System.Collections.ArrayList]@()
foreach ($ch in $chList) {
$ct = $ch.SelectSingleNode('xr:CharacteristicTypes', $nsm)
$cv = $ch.SelectSingleNode('xr:CharacteristicValues', $nsm)
$tFrom = $ct.GetAttribute('from'); $vFrom = $cv.GetAttribute('from')
$gt = { param($n, $node) $x = $node.SelectSingleNode("xr:$n", $nsm); if ($x) { $x.InnerText } else { "" } }
$giv = { param($n, $node) $x = $node.SelectSingleNode("xr:$n", $nsm); if ($x -and $x.InnerText -ne '') { [int]$x.InnerText } else { -1 } }
$tfvNode = $ct.SelectSingleNode('xr:TypesFilterValue', $nsm)
$tfvNil = if ($tfvNode) { $tfvNode.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') } else { '' }
$types = [ordered]@{
from = $tFrom
key = Shorten-CharField (& $gt 'KeyField' $ct) $tFrom
filterField = Shorten-CharField (& $gt 'TypesFilterField' $ct) $tFrom
filterValue = if ($tfvNil -eq 'true') { $null } else { Convert-ChScalarNode $tfvNode }
}
$dpf = & $giv 'DataPathField' $ct; if ($dpf -ne -1) { $types['dataPathField'] = $dpf }
$mvu = & $giv 'MultipleValuesUseField' $ct; if ($mvu -ne -1) { $types['multipleValuesUseField'] = $mvu }
$values = [ordered]@{
from = $vFrom
object = Shorten-CharField (& $gt 'ObjectField' $cv) $vFrom
type = Shorten-CharField (& $gt 'TypeField' $cv) $vFrom
value = Shorten-CharField (& $gt 'ValueField' $cv) $vFrom
}
$mvk = & $giv 'MultipleValuesKeyField' $cv; if ($mvk -ne -1) { $values['multipleValuesKeyField'] = $mvk }
$mvo = & $giv 'MultipleValuesOrderField' $cv; if ($mvo -ne -1) { $values['multipleValuesOrderField'] = $mvo }
[void]$chArr.Add([ordered]@{ types = $types; values = $values })
}
$dsl['characteristics'] = $chArr
}
}
# --- StandardAttributes: захватываем ОТКЛОНЕНИЯ от профиля материализованного блока (профиль компилятор
# восстановит сам). Профиль зеркалит stdAttrProfile компилятора — по типу объекта.
# Catalog: Owner{FC=ShowError,FFV=true}, Parent{FFV=true}, Description{FC=ShowError}.
# ExchangePlan: Description{FC=ShowError}, Code{FC=ShowError} (блок всегда материализован). ---
$stdProfileByType = @{
'Catalog' = @{
'Owner' = @{ fillChecking = 'ShowError'; fillFromFillingValue = $true }
'Parent' = @{ fillFromFillingValue = $true }
'Description' = @{ fillChecking = 'ShowError' }
}
'ExchangePlan' = @{
'Description' = @{ fillChecking = 'ShowError' }
'Code' = @{ fillChecking = 'ShowError' }
}
'ChartOfCharacteristicTypes' = @{
'Description' = @{ fillChecking = 'ShowError' }
'Parent' = @{ fillFromFillingValue = $true }
}
'ChartOfAccounts' = @{
'Description' = @{ fillChecking = 'ShowError' }
'Code' = @{ fillChecking = 'ShowError' }
'Parent' = @{ fillFromFillingValue = $true }
}
'ChartOfCalculationTypes' = @{
'Description' = @{ fillChecking = 'ShowError' }
}
'Document' = @{
'Date' = @{ fillChecking = 'ShowError' }
}
}
$catStdProfile = if ($stdProfileByType.ContainsKey($objType)) { $stdProfileByType[$objType] } else { @{} }
# Фикс-список стандартных реквизитов типа (зеркало standardAttributesByType компилятора) — чтобы отличать
# доп./опциональные (напр. ExchangeDate у ПланОбмена), которые эмитим по факту присутствия даже all-default.
$stdFixedByType = @{
'Catalog' = @('PredefinedDataName','Predefined','Ref','DeletionMark','IsFolder','Owner','Parent','Description','Code')
'ExchangePlan' = @('Ref','DeletionMark','Code','Description','ThisNode','SentNo','ReceivedNo')
'ChartOfCharacteristicTypes' = @('PredefinedDataName','Predefined','Ref','DeletionMark','Description','Code','Parent','ValueType')
'ChartOfAccounts' = @('PredefinedDataName','Order','OffBalance','Type','Description','Code','Parent','Predefined','DeletionMark','Ref')
'ChartOfCalculationTypes' = @('PredefinedDataName','Predefined','Ref','DeletionMark','ActionPeriodIsBasic','Description','Code')
'Document' = @('Ref','DeletionMark','Date','Number','Posted')
'Enum' = @('Order','Ref')
'DocumentJournal' = @('Type','Ref','Date','Posted','DeletionMark','Number')
}
$stdFixed = if ($stdFixedByType.ContainsKey($objType)) { $stdFixedByType[$objType] } else { @() }
# Условные типы: блок эмитим-как-триггер даже пустым (материализуется при отклонении ≥1 реквизита от schema-default;
# у ExchangePlan это почти всегда — Description/Code=ShowError; редкий all-default EP блок опускает).
$stdConditionalTypes = @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document')
$saNode = $props.SelectSingleNode('md:StandardAttributes', $nsm)
if ($saNode) {
$saMap = [ordered]@{}
foreach ($sa in @($saNode.SelectNodes('xr:StandardAttribute', $nsm))) {
$an = $sa.GetAttribute('name')
$prof = if ($catStdProfile.ContainsKey($an)) { $catStdProfile[$an] } else { @{} }
$ov = [ordered]@{}
# FillChecking (профиль или DontCheck)
$fcN = $sa.SelectSingleNode('xr:FillChecking', $nsm); $fc = if ($fcN) { $fcN.InnerText } else { 'DontCheck' }
$profFc = if ($prof.ContainsKey('fillChecking')) { $prof['fillChecking'] } else { 'DontCheck' }
if ($fc -ne $profFc) { $ov['fillChecking'] = $fc }
# FillFromFillingValue (профиль или false)
$ffvN = $sa.SelectSingleNode('xr:FillFromFillingValue', $nsm); $ffv = ($ffvN -and $ffvN.InnerText -eq 'true')
$profFfv = ($prof['fillFromFillingValue'] -eq $true)
if ($ffv -ne $profFfv) { $ov['fillFromFillingValue'] = $ffv }
# Synonym / ToolTip (профиль пуст) — строка ru | {ru,en}
$syn = Get-MLValue ($sa.SelectSingleNode('xr:Synonym', $nsm))
if ($null -ne $syn) { $ov['synonym'] = $syn }
$tt = Get-MLValue ($sa.SelectSingleNode('xr:ToolTip', $nsm))
if ($null -ne $tt) { $ov['tooltip'] = $tt }
# FullTextSearch / DataHistory (профиль = Use)
$ftsN = $sa.SelectSingleNode('xr:FullTextSearch', $nsm); if ($ftsN -and $ftsN.InnerText -ne 'Use') { $ov['fullTextSearch'] = $ftsN.InnerText }
$dhN = $sa.SelectSingleNode('xr:DataHistory', $nsm); if ($dhN -and $dhN.InnerText -ne 'Use') { $ov['dataHistory'] = $dhN.InnerText }
# FillValue (дефолт nil) — DTR-путь/строка/bool. Comment/Mask/ChoiceForm (дефолт пусто).
$fvN = $sa.SelectSingleNode('xr:FillValue', $nsm)
if ($fvN -and $fvN.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -ne 'true') {
$fvXt = $fvN.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($fvXt -match 'DesignTimeRef$' -and $fvN.InnerText -eq '') { $ov['fillValue'] = [ordered]@{ emptyRef = $true } }
elseif ($fvXt -match 'TypeDescription$' -and $fvN.InnerText -eq '') { $ov['fillValue'] = [ordered]@{ typeDescription = $true } } # пустое типизированное (реквизит ValueType ПВХ) ≠ xs:string
else { $ov['fillValue'] = Convert-ChScalarNode $fvN }
}
$saCmt = $sa.SelectSingleNode('xr:Comment', $nsm); if ($saCmt -and $saCmt.InnerText) { $ov['comment'] = $saCmt.InnerText }
$saMsk = $sa.SelectSingleNode('xr:Mask', $nsm); if ($saMsk -and $saMsk.InnerText) { $ov['mask'] = $saMsk.InnerText }
$saFmt = Get-MLValue ($sa.SelectSingleNode('xr:Format', $nsm)); if ($null -ne $saFmt) { $ov['format'] = $saFmt }
$saEfmt = Get-MLValue ($sa.SelectSingleNode('xr:EditFormat', $nsm)); if ($null -ne $saEfmt) { $ov['editFormat'] = $saEfmt }
$saCf = $sa.SelectSingleNode('xr:ChoiceForm', $nsm); if ($saCf -and $saCf.InnerText) { $ov['choiceForm'] = $saCf.InnerText }
$saCpl = Parse-ChoiceParameterLinks $sa 'xr:ChoiceParameterLinks'; if ($null -ne $saCpl) { $ov['choiceParameterLinks'] = $saCpl }
$saCp = Parse-ChoiceParameters $sa 'xr:ChoiceParameters'; if ($null -ne $saCp) { $ov['choiceParameters'] = $saCp }
# LinkByType стандартного реквизита (ExtDimensionN→Account у регистра бухгалтерии). DataPath полный verbatim.
$saLbt = $sa.SelectSingleNode('xr:LinkByType', $nsm)
if ($saLbt) {
$saLbtDp = $saLbt.SelectSingleNode('xr:DataPath', $nsm)
if ($saLbtDp -and $saLbtDp.InnerText) {
$saLbtLi = $saLbt.SelectSingleNode('xr:LinkItem', $nsm)
$li = if ($saLbtLi -and $saLbtLi.InnerText) { [int]$saLbtLi.InnerText } else { 0 }
$ov['linkByType'] = [ordered]@{ dataPath = $saLbtDp.InnerText; linkItem = $li }
}
}
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
if ($ov.Count -gt 0 -or ($stdFixed -notcontains $an)) { $saMap[$an] = $ov }
}
# Условный тип (Catalog): пустой $saMap = триггер блока. Не-условный (ExchangePlan): блок и так эмитится → пустой не пишем.
if ($saMap.Count -gt 0 -or ($stdConditionalTypes -contains $objType)) { $dsl['standardAttributes'] = $saMap }
} elseif ($objType -in @('InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'DocumentJournal')) {
# Регистр/БП/Задача опускают all-default блок стандартных реквизитов (правило не выводимо) — компилятор эмитит его
# по дефолту, поэтому отсутствие фиксируем opt-out `standardAttributes:""` (дом-конвенция суппресса).
$dsl['standardAttributes'] = ''
}
# --- ChildObjects: Attributes + TabularSections ---
$childObjs = $objNode.SelectSingleNode('md:ChildObjects', $nsm)
if ($childObjs) {
$attrs = @($childObjs.SelectNodes('md:Attribute', $nsm))
if ($attrs.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $attrs) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['attributes'] = $arr
}
# Enum: значения перечисления. Плоский элемент name/synonym/comment. Короткая форма — строка "Имя"
# (синоним == авто из имени, без comment, не мультиязычный); иначе объект {name, synonym?, comment?}.
$evNodes = @($childObjs.SelectNodes('md:EnumValue', $nsm))
if ($evNodes.Count -gt 0) {
$evArr = [System.Collections.ArrayList]@()
foreach ($ev in $evNodes) {
$evp = $ev.SelectSingleNode('md:Properties', $nsm)
$evName = ($evp.SelectSingleNode('md:Name', $nsm)).InnerText
$evSynNode = $evp.SelectSingleNode('md:Synonym', $nsm)
$evSyn = Get-MLValue $evSynNode
$evCmtN = $evp.SelectSingleNode('md:Comment', $nsm); $evCmt = if ($evCmtN) { $evCmtN.InnerText } else { '' }
# $evSynVal: строка ≠ авто → кастом; {ru,en} → кастом; пустой `<Synonym/>` (node есть, значения нет) →
# явный '' (≠ авто-синоним из имени, аналог object-level фикса); авто/отсутствие → $null (короткая строка).
$evSynVal = $null
if ($evSyn -is [string]) { if ($evSyn -cne (Split-CamelWords $evName)) { $evSynVal = $evSyn } }
elseif ($null -ne $evSyn) { $evSynVal = $evSyn }
elseif ($evSynNode) { $evSynVal = '' }
if (($null -ne $evSynVal) -or $evCmt) {
$o = [ordered]@{ name = $evName }
if ($null -ne $evSynVal) { $o['synonym'] = $evSynVal }
if ($evCmt) { $o['comment'] = $evCmt }
[void]$evArr.Add($o)
} else {
[void]$evArr.Add($evName)
}
}
$dsl['values'] = $evArr
}
# DocumentJournal: колонки. Каждая — object {name, synonym?, comment?, indexing?, references[]}.
# References — список MDObjectRef-путей к реквизитам регистрируемых документов (verbatim).
$colNodes = @($childObjs.SelectNodes('md:Column', $nsm))
if ($colNodes.Count -gt 0) {
$colArr = [System.Collections.ArrayList]@()
foreach ($col in $colNodes) {
$cp = $col.SelectSingleNode('md:Properties', $nsm)
$cName = ($cp.SelectSingleNode('md:Name', $nsm)).InnerText
$o = [ordered]@{ name = $cName }
$cSynNode = $cp.SelectSingleNode('md:Synonym', $nsm)
$cSyn = Get-MLValue $cSynNode
if ($cSyn -is [string]) { if ($cSyn -cne (Split-CamelWords $cName)) { $o['synonym'] = $cSyn } }
elseif ($null -ne $cSyn) { $o['synonym'] = $cSyn }
elseif ($cSynNode) { $o['synonym'] = '' } # пустой <Synonym/> ≠ авто-синоним → явный ''
$cCmtN = $cp.SelectSingleNode('md:Comment', $nsm); if ($cCmtN -and $cCmtN.InnerText) { $o['comment'] = $cCmtN.InnerText }
$cIdxN = $cp.SelectSingleNode('md:Indexing', $nsm); if ($cIdxN -and $cIdxN.InnerText -ne 'DontIndex') { $o['indexing'] = $cIdxN.InnerText }
$cRefNode = $cp.SelectSingleNode('md:References', $nsm)
$refs = [System.Collections.ArrayList]@()
if ($cRefNode) { foreach ($it in @($cRefNode.SelectNodes('xr:Item', $nsm))) { [void]$refs.Add($it.InnerText) } }
$o['references'] = $refs
[void]$colArr.Add($o)
}
$dsl['columns'] = $colArr
}
# ChartOfAccounts: признаки учёта (AccountingFlag) и признаки учёта субконто (ExtDimensionAccountingFlag) —
# структурно как реквизит, захватываем тем же Attr-ToDsl (тип Boolean уходит в короткую запись).
$acctFlagNodes = @($childObjs.SelectNodes('md:AccountingFlag', $nsm))
if ($acctFlagNodes.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $acctFlagNodes) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['accountingFlags'] = $arr
}
$extDimFlagNodes = @($childObjs.SelectNodes('md:ExtDimensionAccountingFlag', $nsm))
if ($extDimFlagNodes.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $extDimFlagNodes) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['extDimensionAccountingFlags'] = $arr
}
# Sequence: измерения несут DocumentMap/RegisterRecordsMap (соответствие реквизитам документов/движениям) —
# Attr-ToDsl их не знает → отдельный захват объектной формой. Прочие типы (регистры) — общий Attr-ToDsl.
$dimNodes = @($childObjs.SelectNodes('md:Dimension', $nsm))
if ($dimNodes.Count -gt 0 -and $objType -eq 'Sequence') {
$arr = [System.Collections.ArrayList]@()
foreach ($dn in $dimNodes) {
$dp = $dn.SelectSingleNode('md:Properties', $nsm)
$dName = ($dp.SelectSingleNode('md:Name', $nsm)).InnerText
$o = [ordered]@{ name = $dName }
$dSyn = Get-MLValue ($dp.SelectSingleNode('md:Synonym', $nsm))
if ($dSyn -is [string]) { if ($dSyn -cne (Split-CamelWords $dName)) { $o['synonym'] = $dSyn } }
elseif ($null -ne $dSyn) { $o['synonym'] = $dSyn }
$dCmtN = $dp.SelectSingleNode('md:Comment', $nsm); if ($dCmtN -and $dCmtN.InnerText) { $o['comment'] = $dCmtN.InnerText }
$dt = Get-TypeShorthand ($dp.SelectSingleNode('md:Type', $nsm)); if ($dt) { $o['type'] = $dt }
foreach ($mp in @(@('DocumentMap','documentMap'), @('RegisterRecordsMap','registerRecordsMap'))) {
$mn = $dp.SelectSingleNode("md:$($mp[0])", $nsm)
if ($mn) {
$mItems = @($mn.SelectNodes('xr:Item', $nsm) | ForEach-Object { $_.InnerText })
if ($mItems.Count -gt 0) { $o[$mp[1]] = [System.Collections.ArrayList]@($mItems) }
}
}
[void]$arr.Add($o)
}
$dsl['dimensions'] = $arr
} elseif ($dimNodes.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $dimNodes) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['dimensions'] = $arr
}
$resNodes = @($childObjs.SelectNodes('md:Resource', $nsm))
if ($resNodes.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $resNodes) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['resources'] = $arr
}
# Задача: реквизиты адресации (AddressingAttribute) — структурно как реквизит + AddressingDimension.
$addrNodes = @($childObjs.SelectNodes('md:AddressingAttribute', $nsm))
if ($addrNodes.Count -gt 0) {
$arr = [System.Collections.ArrayList]@()
foreach ($a in $addrNodes) { [void]$arr.Add((Attr-ToDsl $a)) }
$dsl['addressingAttributes'] = $arr
}
$tsNodes = @($childObjs.SelectNodes('md:TabularSection', $nsm))
if ($tsNodes.Count -gt 0) {
$tsMap = [ordered]@{}
foreach ($ts in $tsNodes) {
$tsp = $ts.SelectSingleNode('md:Properties', $nsm)
$tsName = ($tsp.SelectSingleNode('md:Name', $nsm)).InnerText
$tco = $ts.SelectSingleNode('md:ChildObjects', $nsm)
$cols = [System.Collections.ArrayList]@()
if ($tco) { foreach ($ca in @($tco.SelectNodes('md:Attribute', $nsm))) { [void]$cols.Add((Attr-ToDsl $ca)) } }
# Синоним/подсказка/комментарий ТЧ. Кастом → объектная форма {synonym?, tooltip?, comment?, attributes}.
$tsSyn = Get-MLValue ($tsp.SelectSingleNode('md:Synonym', $nsm))
$tsSynCustom = $false
if ($tsSyn -is [string]) { if ($tsSyn -cne (Split-CamelWords $tsName)) { $tsSynCustom = $true } }
elseif ($null -ne $tsSyn) { $tsSynCustom = $true }
$tsTt = Get-MLValue ($tsp.SelectSingleNode('md:ToolTip', $nsm))
$tsCmtN = $tsp.SelectSingleNode('md:Comment', $nsm); $tsCmt = if ($tsCmtN) { $tsCmtN.InnerText } else { '' }
# FillChecking ТЧ (обязательность заполнения; omit при DontCheck).
$tsFcN = $tsp.SelectSingleNode('md:FillChecking', $nsm); $tsFc = if ($tsFcN -and $tsFcN.InnerText -ne 'DontCheck') { $tsFcN.InnerText } else { '' }
# Use ТЧ (иерархические Catalog/ПВХ: ForItem/ForFolder/ForFolderAndItem; omit при дефолте ForItem).
$tsUseN = $tsp.SelectSingleNode('md:Use', $nsm); $tsUse = if ($tsUseN -and $tsUseN.InnerText -ne 'ForItem') { $tsUseN.InnerText } else { '' }
# TS-блок стандартных реквизитов (LineNumber). Наличие блока — пер-ТЧ артефакт (~6% ТЧ его опускают,
# правило не выводимо). Faithful roundtrip: нет блока → маркер подавления `lineNumber: ""` (дом-конвенция);
# есть блок → захват кастомизации LineNumber (omit-on-default по свойству), all-default → без ключа.
$lnObj = [ordered]@{}
$saTsNode = $tsp.SelectSingleNode('md:StandardAttributes', $nsm)
$hasBlock = ($saTsNode -and @($saTsNode.SelectNodes('xr:StandardAttribute', $nsm)).Count -gt 0)
$lnNode = if ($hasBlock) { $saTsNode.SelectSingleNode("xr:StandardAttribute[@name='LineNumber']", $nsm) } else { $null }
if ($lnNode) {
$lnSyn = Get-MLValue ($lnNode.SelectSingleNode('xr:Synonym', $nsm)); if ($null -ne $lnSyn) { $lnObj['synonym'] = $lnSyn }
$lnCmtN = $lnNode.SelectSingleNode('xr:Comment', $nsm); if ($lnCmtN -and $lnCmtN.InnerText) { $lnObj['comment'] = $lnCmtN.InnerText }
$lnFtsN = $lnNode.SelectSingleNode('xr:FullTextSearch', $nsm); if ($lnFtsN -and $lnFtsN.InnerText -ne 'Use') { $lnObj['fullTextSearch'] = $lnFtsN.InnerText }
$lnTt = Get-MLValue ($lnNode.SelectSingleNode('xr:ToolTip', $nsm)); if ($null -ne $lnTt) { $lnObj['tooltip'] = $lnTt }
$lnFmt = Get-MLValue ($lnNode.SelectSingleNode('xr:Format', $nsm)); if ($null -ne $lnFmt) { $lnObj['format'] = $lnFmt }
$lnEfmt = Get-MLValue ($lnNode.SelectSingleNode('xr:EditFormat', $nsm)); if ($null -ne $lnEfmt) { $lnObj['editFormat'] = $lnEfmt }
$lnChiN = $lnNode.SelectSingleNode('xr:ChoiceHistoryOnInput', $nsm); if ($lnChiN -and $lnChiN.InnerText -ne 'Auto') { $lnObj['choiceHistoryOnInput'] = $lnChiN.InnerText }
# FillValue НомерСтроки: дефолт nil; редкая аномалия xs:decimal 0 → захват числом (иначе теряется в nil).
$lnFvN = $lnNode.SelectSingleNode('xr:FillValue', $nsm)
if ($lnFvN -and $lnFvN.GetAttribute('nil', 'http://www.w3.org/2001/XMLSchema-instance') -ne 'true') {
$lnFvT = $lnFvN.GetAttribute('type', 'http://www.w3.org/2001/XMLSchema-instance')
if ($lnFvT -match 'decimal$') { $lnObj['fillValue'] = if ($lnFvN.InnerText -match '^-?\d+$') { [long]$lnFvN.InnerText } else { [double]$lnFvN.InnerText } }
}
}
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $tsFc -or $tsUse -or $lnObj.Count -gt 0 -or (-not $hasBlock)) {
$to = [ordered]@{}
if ($tsSynCustom) { $to['synonym'] = $tsSyn }
if ($null -ne $tsTt) { $to['tooltip'] = $tsTt }
if ($tsCmt) { $to['comment'] = $tsCmt }
if ($tsFc) { $to['fillChecking'] = $tsFc }
if ($tsUse) { $to['use'] = $tsUse }
if (-not $hasBlock) { $to['lineNumber'] = '' } elseif ($lnObj.Count -gt 0) { $to['lineNumber'] = $lnObj }
$to['attributes'] = $cols
$tsMap[$tsName] = $to
} else {
$tsMap[$tsName] = $cols
}
}
$dsl['tabularSections'] = $tsMap
}
# --- Commands (полноблочные <Command> в ChildObjects) → DSL commands (map имя→объект, omit-on-default).
# Тела модулей команд (CommandModule.bsl) — вне скоупа (как ObjectModule). ---
$cmdNodes = @($childObjs.SelectNodes('md:Command', $nsm))
if ($cmdNodes.Count -gt 0) {
$cmdMap = [ordered]@{}
foreach ($cm in $cmdNodes) {
$cp = $cm.SelectSingleNode('md:Properties', $nsm)
$cn = ($cp.SelectSingleNode('md:Name', $nsm)).InnerText
$o = [ordered]@{}
$syn = Get-MLValue ($cp.SelectSingleNode('md:Synonym', $nsm))
if ($syn -is [string]) { if ($syn -cne (Split-CamelWords $cn)) { $o['synonym'] = $syn } }
elseif ($null -ne $syn) { $o['synonym'] = $syn }
$cmtN = $cp.SelectSingleNode('md:Comment', $nsm); if ($cmtN -and $cmtN.InnerText) { $o['comment'] = $cmtN.InnerText }
$grpN = $cp.SelectSingleNode('md:Group', $nsm); if ($grpN -and $grpN.InnerText) { $o['group'] = $grpN.InnerText }
$cpt = Get-TypeShorthand ($cp.SelectSingleNode('md:CommandParameterType', $nsm)); if ($cpt) { $o['commandParameterType'] = $cpt }
$pumN = $cp.SelectSingleNode('md:ParameterUseMode', $nsm); if ($pumN -and $pumN.InnerText -ne 'Single') { $o['parameterUseMode'] = $pumN.InnerText }
$mdN = $cp.SelectSingleNode('md:ModifiesData', $nsm); if ($mdN -and $mdN.InnerText -eq 'true') { $o['modifiesData'] = $true }
$repN = $cp.SelectSingleNode('md:Representation', $nsm); if ($repN -and $repN.InnerText -ne 'Auto') { $o['representation'] = $repN.InnerText }
$ctt = Get-MLValue ($cp.SelectSingleNode('md:ToolTip', $nsm)); if ($null -ne $ctt) { $o['tooltip'] = $ctt }
# <Picture> — структурный блок (зеркало form-decompile Set-CommandPicture). Дефолт LoadTransparent=true:
# скаляр `picture` + sibling `loadTransparent:false` при отклонении; объект {src,loadTransparent?,transparentPixel} при TransparentPixel.
$refN = $cp.SelectSingleNode('md:Picture/xr:Ref', $nsm)
$absN = $cp.SelectSingleNode('md:Picture/xr:Abs', $nsm)
if ($refN -or $absN) {
$psrc = if ($refN) { $refN.InnerText } else { "abs:$($absN.InnerText)" }
$ltN = $cp.SelectSingleNode('md:Picture/xr:LoadTransparent', $nsm)
$ltFalse = ($ltN -and $ltN.InnerText -eq 'false')
$tpxN = $cp.SelectSingleNode('md:Picture/xr:TransparentPixel', $nsm)
if ($tpxN) {
$po = [ordered]@{ src = $psrc }
if ($ltFalse) { $po['loadTransparent'] = $false }
$po['transparentPixel'] = [ordered]@{ x = [int]$tpxN.GetAttribute('x'); y = [int]$tpxN.GetAttribute('y') }
$o['picture'] = $po
} else {
$o['picture'] = $psrc
if ($ltFalse) { $o['loadTransparent'] = $false }
}
}
$scN = $cp.SelectSingleNode('md:Shortcut', $nsm); if ($scN -and $scN.InnerText) { $o['shortcut'] = $scN.InnerText }
$osuN = $cp.SelectSingleNode('md:OnMainServerUnavalableBehavior', $nsm); if ($osuN -and $osuN.InnerText -ne 'Auto') { $o['onMainServerUnavalableBehavior'] = $osuN.InnerText }
$cmdMap[$cn] = $o
}
$dsl['commands'] = $cmdMap
}
}
# --- Предопределённые (соседний Ext/Predefined.xml) → DSL predefined.
# Плоский элемент → строка "(Код) Имя [Наименование]" (Наименование: ==авто → опустить; '' → []; иначе [текст]).
# Группа/иерархия → object {name, [code], [description], isFolder, childItems}. codeType — из свойства каталога. ---
$objDir = Split-Path -Parent (Resolve-Path -LiteralPath $ObjectPath).Path
$predefPath = Join-Path (Join-Path (Join-Path $objDir $objName) 'Ext') 'Predefined.xml'
if (Test-Path -LiteralPath $predefPath) {
$pdoc = New-Object System.Xml.XmlDocument
$pdoc.Load($predefPath)
function PredefItem-ToDsl {
param($itemEl)
$name = ($itemEl.SelectSingleNode("*[local-name()='Name']")).InnerText
$codeEl = $itemEl.SelectSingleNode("*[local-name()='Code']")
$code = if ($codeEl -and $codeEl.InnerText) { $codeEl.InnerText } else { '' }
$descEl = $itemEl.SelectSingleNode("*[local-name()='Description']")
$desc = if ($descEl) { $descEl.InnerText } else { '' }
$folderEl = $itemEl.SelectSingleNode("*[local-name()='IsFolder']")
$isFolder = ($folderEl -and $folderEl.InnerText -eq 'true')
$childContainer = $itemEl.SelectSingleNode("*[local-name()='ChildItems']")
# ВАЖНО: обернуть весь if в @(), иначе PS распаковывает одноэлементный @(...) из if-блока
# обратно в узел → $kids.Count = $null → папки с ОДНИМ ребёнком теряют его.
$kids = @(if ($childContainer) { $childContainer.SelectNodes("*[local-name()='Item']") } else { @() })
# Type — тип значения предопределённой характеристики (ПВХ). Наличие → object-форма (в строку не влезает).
# Наличие узла <Type> (даже пустого <Type/>) → object-форма с ключом type ('' для пустого); нет узла (Catalog) → без.
$typeEl = $itemEl.SelectSingleNode("*[local-name()='Type']")
$typeStr = if ($typeEl) { Get-TypeShorthand $typeEl } else { $null }
$auto = Split-CamelWords $name
# Компактная строка для плоских: без узла Type (Catalog) ИЛИ с непустым типом → "(Код) Имя [Наим]: Тип".
# Пустой <Type/> в короткую не влезает (нужен явный маркер) → object-форма с type:''.
if (-not $isFolder -and $kids.Count -eq 0 -and ($null -eq $typeStr -or $typeStr -ne '')) {
$s = if ($code) { "($code) $name" } else { $name }
if ($desc -eq '') { $s = "$s []" }
elseif ($desc -cne $auto) { $s = "$s [$desc]" }
if ($typeStr) { $s = "${s}: $typeStr" }
return $s
}
# Группа/иерархия/с типом → объект.
$o = [ordered]@{ name = $name }
if ($code) { $o['code'] = $code }
if ($desc -eq '') { $o['description'] = '' }
elseif ($desc -cne $auto) { $o['description'] = $desc }
if ($null -ne $typeStr) { $o['type'] = $typeStr }
if ($isFolder) { $o['isFolder'] = $true }
if ($kids.Count -gt 0) {
$sub = [System.Collections.ArrayList]@()
foreach ($k in $kids) { [void]$sub.Add((PredefItem-ToDsl $k)) }
$o['childItems'] = $sub
}
return $o
}
# Предопределённые СЧЕТА Плана счетов — отдельная грамматика (AccountType/OffBalance/Order/AccountingFlags/
# ExtDimensionTypes). Флаги: захватываем только TRUE (компилятор развернёт по def-порядку признаков плана).
function PredefAccount-ToDsl {
param($itemEl)
$name = ($itemEl.SelectSingleNode("*[local-name()='Name']")).InnerText
$codeEl = $itemEl.SelectSingleNode("*[local-name()='Code']")
$code = if ($codeEl -and $codeEl.InnerText) { $codeEl.InnerText } else { '' }
$descEl = $itemEl.SelectSingleNode("*[local-name()='Description']")
$desc = if ($descEl) { $descEl.InnerText } else { '' }
$atEl = $itemEl.SelectSingleNode("*[local-name()='AccountType']")
$acctType = if ($atEl) { $atEl.InnerText } else { 'ActivePassive' }
$offEl = $itemEl.SelectSingleNode("*[local-name()='OffBalance']")
$off = ($offEl -and $offEl.InnerText -eq 'true')
$ordEl = $itemEl.SelectSingleNode("*[local-name()='Order']")
$order = if ($ordEl) { $ordEl.InnerText } else { '' }
$auto = Split-CamelWords $name
# TRUE-флаги (leaf после последней точки в ref).
$trueFlags = @()
foreach ($fl in @($itemEl.SelectNodes("*[local-name()='AccountingFlags']/*[local-name()='Flag']"))) {
if ($fl.InnerText -eq 'true') { $r = $fl.GetAttribute('ref'); $trueFlags += ($r -split '\.')[-1] }
}
# ExtDimensionTypes → subconto. Короткая запись "Тип | Признак1, Признак2": срезаем префикс ПВХ видов субконто
# плана (extDimensionTypes); «Только обороты» (Turnover=true) — предопределённый признак-токен `Turnover` первым
# в списке (наравне с добавленными). Захватываем только TRUE-признаки (компилятор развернёт по def-порядку).
$edtPfx = if ($dsl['extDimensionTypes']) { "$($dsl['extDimensionTypes'])." } else { $null }
$subconto = [System.Collections.ArrayList]@()
foreach ($edt in @($itemEl.SelectNodes("*[local-name()='ExtDimensionTypes']/*[local-name()='ExtDimensionType']"))) {
$scT = $edt.GetAttribute('name')
if ($edtPfx -and $scT.StartsWith($edtPfx)) { $scT = $scT.Substring($edtPfx.Length) }
$tEl = $edt.SelectSingleNode("*[local-name()='Turnover']")
$scFlagsOut = @()
if ($tEl -and $tEl.InnerText -eq 'true') { $scFlagsOut += 'Turnover' }
foreach ($fl in @($edt.SelectNodes("*[local-name()='AccountingFlags']/*[local-name()='Flag']"))) {
if ($fl.InnerText -eq 'true') { $r = $fl.GetAttribute('ref'); $scFlagsOut += ($r -split '\.')[-1] }
}
if ($scFlagsOut.Count -gt 0) { [void]$subconto.Add("$scT | " + ($scFlagsOut -join ', ')) }
else { [void]$subconto.Add($scT) }
}
$childContainer = $itemEl.SelectSingleNode("*[local-name()='ChildItems']")
$kids = @(if ($childContainer) { $childContainer.SelectNodes("*[local-name()='Item']") } else { @() })
$o = [ordered]@{ name = $name }
if ($code) { $o['code'] = $code }
# -cne (регистрочувствительно!): PS `-ne` регистронезависим → «…ОС» == «…ос» и description ошибочно
# опускался, а компилятор регенерил lowercase (Split-CamelCase). Хвостовые аббревиатуры (ОС/НМА) теряли регистр.
if ($desc -cne $auto) { $o['description'] = $desc }
$o['accountType'] = $acctType
if ($off) { $o['offBalance'] = $true }
$o['order'] = $order
if ($trueFlags.Count -gt 0) { $o['flags'] = [System.Collections.ArrayList]@($trueFlags) }
if ($subconto.Count -gt 0) { $o['subconto'] = $subconto }
if ($kids.Count -gt 0) {
$sub = [System.Collections.ArrayList]@()
foreach ($k in $kids) { [void]$sub.Add((PredefAccount-ToDsl $k)) }
$o['childItems'] = $sub
}
return $o
}
# Предопределённые ВИДЫ РАСЧЁТА (плоские): Name/Code/Description/ActionPeriodIsBase. Строка при
# ActionPeriodIsBase=false, объект — при true.
function PredefCalcType-ToDsl {
param($itemEl)
$name = ($itemEl.SelectSingleNode("*[local-name()='Name']")).InnerText
$codeEl = $itemEl.SelectSingleNode("*[local-name()='Code']"); $code = if ($codeEl -and $codeEl.InnerText) { $codeEl.InnerText } else { '' }
$descEl = $itemEl.SelectSingleNode("*[local-name()='Description']"); $desc = if ($descEl) { $descEl.InnerText } else { '' }
$apibEl = $itemEl.SelectSingleNode("*[local-name()='ActionPeriodIsBase']"); $apib = ($apibEl -and $apibEl.InnerText -eq 'true')
$auto = Split-CamelWords $name
if (-not $apib) {
$s = if ($code) { "($code) $name" } else { $name }
if ($desc -eq '') { $s = "$s []" } elseif ($desc -cne $auto) { $s = "$s [$desc]" }
return $s
}
$o = [ordered]@{ name = $name }
if ($code) { $o['code'] = $code }
if ($desc -cne $auto) { $o['description'] = $desc }
$o['actionPeriodIsBase'] = $true
return $o
}
$rootItems = [System.Collections.ArrayList]@()
if ($objType -eq 'ChartOfAccounts') {
foreach ($it in @($pdoc.DocumentElement.SelectNodes("*[local-name()='Item']"))) { [void]$rootItems.Add((PredefAccount-ToDsl $it)) }
} elseif ($objType -eq 'ChartOfCalculationTypes') {
foreach ($it in @($pdoc.DocumentElement.SelectNodes("*[local-name()='Item']"))) { [void]$rootItems.Add((PredefCalcType-ToDsl $it)) }
} else {
foreach ($it in @($pdoc.DocumentElement.SelectNodes("*[local-name()='Item']"))) { [void]$rootItems.Add((PredefItem-ToDsl $it)) }
}
if ($rootItems.Count -gt 0) { $dsl['predefined'] = $rootItems }
}
# --- Состав плана обмена (соседний Ext/Content.xml) → DSL content (ExchangePlan).
# Каждый <Item>{<Metadata>MDRef</Metadata><AutoRecord>Deny|Allow</AutoRecord>}.
# Deny (дефолт) → строка "MDRef"; Allow → "MDRef: autoRecord". ---
if ($objType -eq 'ExchangePlan') {
$contentPath = Join-Path (Join-Path (Join-Path $objDir $objName) 'Ext') 'Content.xml'
if (Test-Path -LiteralPath $contentPath) {
$cdoc = New-Object System.Xml.XmlDocument
$cdoc.Load($contentPath)
$contentItems = [System.Collections.ArrayList]@()
foreach ($it in @($cdoc.DocumentElement.SelectNodes("*[local-name()='Item']"))) {
$mdEl = $it.SelectSingleNode("*[local-name()='Metadata']")
if (-not $mdEl -or -not $mdEl.InnerText) { continue }
$ref = $mdEl.InnerText
$arEl = $it.SelectSingleNode("*[local-name()='AutoRecord']")
$ar = if ($arEl) { $arEl.InnerText } else { 'Deny' }
if ($ar -eq 'Allow') { [void]$contentItems.Add("${ref}: autoRecord") } else { [void]$contentItems.Add($ref) }
}
if ($contentItems.Count -gt 0) { $dsl['content'] = $contentItems }
}
}
# === Вывод ===
$json = ConvertTo-CompactJson $dsl 0
if ($OutputPath) {
[System.IO.File]::WriteAllText($OutputPath, $json, (New-Object System.Text.UTF8Encoding($false)))
} else {
[Console]::Out.WriteLine($json)
}