feat(meta-compile,meta-decompile): поддержка типа ExchangePlan (План обмена) (v1.34/v0.23)

Первый тип после Catalog-пилота. Декомпилятор расширен Catalog-only → Catalog+
ExchangePlan (снят гейт, type из XML, тип-зависимые дефолты descriptionLength/
createOnInput/dataLockControlMode, EP-свойства distributedInfoBase/
includeConfigurationExtensions/dataHistory-триплет). Компилятор
Emit-ExchangePlanProperties переписан на общие хелперы (InputByString-derive,
Characteristics, BasedOn, DataLockFields, презентации, формы) в каноническом
порядке — был устаревший (хардкод, пропущенный Characteristics, кривой порядок).

StandardAttributes EP: профиль Description/Code=ShowError + EP условный (блок при
кастомизации; редкий all-default EP его опускает — условная модель это ловит).

Общие фиксы (не только EP):
  • DSL-override стандартных реквизитов применялся лишь для условных типов —
    снят гейт (if $sa), теперь и для не-условных.
  • Доп./опциональные стандартные реквизиты вне фикс-списка (ExchangeDate у части
    EP, легаси) — эмиссия по факту ключа; декомпилятор эмитит по присутствию.
  • Пустой <Synonym/> реквизита ≠ авто-синоним из имени → декомпилятор пишет
    synonym:"" (латентный баг: у Catalog 0/4018, всплыл на EP).

Роундтрип 42 EP: match 0→39, TOTAL 754→146 (−81%). Остаток (3) — принятые хвосты
(TS-LineNumber-опущение, FillValue-пробелы). Catalog не регрессировал (108/120).
Регресс 47/47 ps1+py, ps1↔py identical (8/8 EP). spec §7.2a, кейс exchange-plan.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-05 14:50:28 +03:00
parent 8ea310f72e
commit d22bebb16f
6 changed files with 222 additions and 119 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.33 — Compile 1C metadata object from JSON
# meta-compile v1.34 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1037,6 +1037,11 @@ $script:stdAttrProfile = @{
"Parent" = @{ FillFromFillingValue = "true" }
"Description" = @{ FillChecking = "ShowError" }
}
# ExchangePlan: блок материализуется всегда (не условный), Наименование/Код → FillChecking=ShowError (корпус 40/38 из 41).
"ExchangePlan" = @{
"Description" = @{ FillChecking = "ShowError" }
"Code" = @{ FillChecking = "ShowError" }
}
}
# $ov — hashtable переопределений (профиль + DSL) для полей: FillChecking, FillFromFillingValue,
@@ -1097,7 +1102,7 @@ function Emit-StandardAttribute {
# Прочие типы (не в множестве) → блок эмитится всегда (текущее поведение, пока их правило не выведено).
# - stdAttrProfile[тип]: профиль материализованного блока (пусто = schema-дефолт), поверх — DSL-override.
# Миграция типа = добавить его в stdAttrConditionalTypes + stdAttrProfile и переснять снэпшоты; КОД НЕ ТРОГАЕМ.
$script:stdAttrConditionalTypes = @('Catalog')
$script:stdAttrConditionalTypes = @('Catalog', 'ExchangePlan')
function Emit-StandardAttributes {
param([string]$indent, [string]$objectType)
$attrs = $script:standardAttributesByType[$objectType]
@@ -1106,11 +1111,15 @@ function Emit-StandardAttributes {
$sa = $def.standardAttributes
if ($conditional -and $null -eq $sa) { return } # условный тип без кастомизации → блока нет
$profile = $script:stdAttrProfile[$objectType]; if (-not $profile) { $profile = @{} }
# Доп. (опциональные) стандартные реквизиты вне фикс-списка типа — напр. ExchangeDate у части ПланОбмена
# (легаси, присутствие не выводится из свойств). Эмитим по факту наличия ключа в DSL, ПЕРЕД фикс-списком (их позиция).
$extra = @()
if ($sa) { foreach ($k in $sa.PSObject.Properties.Name) { if ($attrs -notcontains $k) { $extra += $k } } }
X "$indent<StandardAttributes>"
foreach ($a in $attrs) {
foreach ($a in ($extra + $attrs)) {
$ov = @{}
if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } }
if ($conditional -and $sa) {
if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan)
$d = $sa.$a
if ($d) {
if ($null -ne $d.synonym) { $ov['Synonym'] = $d.synonym } # строка ИЛИ {ru,en}
@@ -2476,62 +2485,70 @@ function Emit-ExchangePlanProperties {
X "$i<Name>$(Esc-Xml $objName)</Name>"
Emit-MLText $i "Synonym" $synonym
X "$i<Comment/>"
X "$i<UseStandardCommands>true</UseStandardCommands>"
if ($def.comment) { X "$i<Comment>$(Esc-XmlText "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
$useStdCmd = if (Get-BoolProp "useStandardCommands" $true) { "true" } else { "false" }
X "$i<UseStandardCommands>$useStdCmd</UseStandardCommands>"
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "100" }
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "150" }
$codeAllowedLength = Get-EnumProp "CodeAllowedLength" "codeAllowedLength" "Variable"
X "$i<CodeLength>$codeLength</CodeLength>"
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
X "$i<DefaultPresentation>AsDescription</DefaultPresentation>"
X "$i<EditType>InDialog</EditType>"
X "$i<DefaultPresentation>$(Get-EnumProp 'DefaultPresentation' 'defaultPresentation' 'AsDescription')</DefaultPresentation>"
X "$i<EditType>$(Get-EnumProp 'EditType' 'editType' 'InDialog')</EditType>"
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
X "$i<QuickChoice>$quickChoice</QuickChoice>"
X "$i<ChoiceMode>$(Get-EnumProp 'ChoiceMode' 'choiceMode' 'BothWays')</ChoiceMode>"
# InputByString: override `inputByString` ЛИБО дефолт [Descr при D>0]+[Code при C>0] (prefix ExchangePlan).
if (Test-DefKey 'inputByString') {
$ibFields = @($def.inputByString | ForEach-Object { Expand-DataPath "$_" })
} else {
$ibFields = @()
if ([int]$descriptionLength -gt 0) { $ibFields += "ExchangePlan.$objName.StandardAttribute.Description" }
if ([int]$codeLength -gt 0) { $ibFields += "ExchangePlan.$objName.StandardAttribute.Code" }
}
Emit-FieldBlock $i "InputByString" $ibFields
X "$i<SearchStringModeOnInputByString>$(Get-EnumProp 'SearchStringModeOnInputByString' 'searchStringModeOnInputByString' 'Begin')</SearchStringModeOnInputByString>"
X "$i<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>"
X "$i<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>"
Emit-FormRef $i "DefaultObjectForm" $def.defaultObjectForm
Emit-FormRef $i "DefaultListForm" $def.defaultListForm
Emit-FormRef $i "DefaultChoiceForm" $def.defaultChoiceForm
Emit-FormRef $i "AuxiliaryObjectForm" $def.auxiliaryObjectForm
Emit-FormRef $i "AuxiliaryListForm" $def.auxiliaryListForm
Emit-FormRef $i "AuxiliaryChoiceForm" $def.auxiliaryChoiceForm
Emit-StandardAttributes $i "ExchangePlan"
Emit-Characteristics $i $def.characteristics
Emit-BasedOn $i $def.basedOn
$distributed = if ($def.distributedInfoBase -eq $true) { "true" } else { "false" }
$includeExt = if ($def.includeConfigurationExtensions -eq $true) { "true" } else { "false" }
X "$i<DistributedInfoBase>$distributed</DistributedInfoBase>"
X "$i<IncludeConfigurationExtensions>$includeExt</IncludeConfigurationExtensions>"
X "$i<BasedOn/>"
$quickChoice = if ($def.quickChoice -eq $true) { "true" } else { "false" }
X "$i<QuickChoice>$quickChoice</QuickChoice>"
X "$i<ChoiceMode>BothWays</ChoiceMode>"
X "$i<InputByString>"
X "$i`t<xr:Field>ExchangePlan.$objName.StandardAttribute.Description</xr:Field>"
X "$i`t<xr:Field>ExchangePlan.$objName.StandardAttribute.Code</xr:Field>"
X "$i</InputByString>"
X "$i<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>"
X "$i<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>"
X "$i<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>"
X "$i<DefaultObjectForm/>"
X "$i<DefaultListForm/>"
X "$i<DefaultChoiceForm/>"
X "$i<AuxiliaryObjectForm/>"
X "$i<AuxiliaryListForm/>"
X "$i<AuxiliaryChoiceForm/>"
X "$i<IncludeHelpInContents>false</IncludeHelpInContents>"
X "$i<DataLockFields/>"
$dataLockControlMode = Get-EnumProp "DataLockControlMode" "dataLockControlMode" "Automatic"
X "$i<DataLockControlMode>$dataLockControlMode</DataLockControlMode>"
$fullTextSearch = Get-EnumProp "FullTextSearch" "fullTextSearch" "Use"
X "$i<FullTextSearch>$fullTextSearch</FullTextSearch>"
X "$i<CreateOnInput>$(Get-EnumProp 'CreateOnInput' 'createOnInput' 'DontUse')</CreateOnInput>"
X "$i<ChoiceHistoryOnInput>$(Get-EnumProp 'ChoiceHistoryOnInput' 'choiceHistoryOnInput' 'Auto')</ChoiceHistoryOnInput>"
$inclHelp = if (Get-BoolProp "includeHelpInContents" $false) { "true" } else { "false" }
X "$i<IncludeHelpInContents>$inclHelp</IncludeHelpInContents>"
$dlFields = if (Test-DefKey 'dataLockFields') { @($def.dataLockFields | ForEach-Object { Expand-DataPath "$_" }) } else { @() }
Emit-FieldBlock $i "DataLockFields" $dlFields
X "$i<DataLockControlMode>$(Get-EnumProp 'DataLockControlMode' 'dataLockControlMode' 'Managed')</DataLockControlMode>"
X "$i<FullTextSearch>$(Get-EnumProp 'FullTextSearch' 'fullTextSearch' 'Use')</FullTextSearch>"
Emit-MLText $i "ObjectPresentation" $def.objectPresentation
Emit-MLText $i "ExtendedObjectPresentation" $def.extendedObjectPresentation
Emit-MLText $i "ListPresentation" $def.listPresentation
Emit-MLText $i "ExtendedListPresentation" $def.extendedListPresentation
Emit-MLText $i "Explanation" $def.explanation
X "$i<CreateOnInput>DontUse</CreateOnInput>"
X "$i<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>"
X "$i<DataHistory>DontUse</DataHistory>"
X "$i<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>"
X "$i<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>"
X "$i<DataHistory>$(Get-EnumProp 'DataHistory' 'dataHistory' 'DontUse')</DataHistory>"
$updDH = if (Get-BoolProp "updateDataHistoryImmediatelyAfterWrite" $false) { "true" } else { "false" }
X "$i<UpdateDataHistoryImmediatelyAfterWrite>$updDH</UpdateDataHistoryImmediatelyAfterWrite>"
$execDH = if (Get-BoolProp "executeAfterWriteDataHistoryVersionProcessing" $false) { "true" } else { "false" }
X "$i<ExecuteAfterWriteDataHistoryVersionProcessing>$execDH</ExecuteAfterWriteDataHistoryVersionProcessing>"
}
function Emit-ChartOfCharacteristicTypesProperties {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.33 — Compile 1C metadata object from JSON
# meta-compile v1.34 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1050,7 +1050,12 @@ std_attr_profile = {
'Owner': {'FillChecking': 'ShowError', 'FillFromFillingValue': 'true'},
'Parent': {'FillFromFillingValue': 'true'},
'Description': {'FillChecking': 'ShowError'},
}
},
# ExchangePlan: блок при кастомизации; Наименование/Код → FillChecking=ShowError (корпус 40/38 из 41).
'ExchangePlan': {
'Description': {'FillChecking': 'ShowError'},
'Code': {'FillChecking': 'ShowError'},
},
}
# ov — dict переопределений (профиль + DSL): FillChecking, FillFromFillingValue, Synonym,
@@ -1116,7 +1121,7 @@ def emit_standard_attribute(indent, attr_name, ov=None):
# Единый эмиттер блока StandardAttributes — поведение правят ДАННЫЕ, не форк кода (см. коммент в .ps1).
# std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes).
# Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты.
std_attr_conditional_types = {'Catalog'}
std_attr_conditional_types = {'Catalog', 'ExchangePlan'}
def emit_standard_attributes(indent, object_type):
attrs = standard_attributes_by_type.get(object_type)
if not attrs:
@@ -1126,10 +1131,13 @@ def emit_standard_attributes(indent, object_type):
if conditional and sa is None:
return
profile = std_attr_profile.get(object_type, {})
# Доп. (опциональные) стандартные реквизиты вне фикс-списка — напр. ExchangeDate у части ПланОбмена
# (легаси, присутствие не выводится). Эмитим по факту ключа в DSL, ПЕРЕД фикс-списком (их позиция).
extra = [k for k in sa if k not in attrs] if isinstance(sa, dict) else []
X(f'{indent}<StandardAttributes>')
for a in attrs:
for a in extra + list(attrs):
ov = dict(profile.get(a, {}))
if conditional and isinstance(sa, dict):
if isinstance(sa, dict):
d = sa.get(a)
if d:
if d.get('synonym') is not None:
@@ -2407,54 +2415,62 @@ def emit_exchange_plan_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
if defn.get('comment'):
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>')
else:
X(f'{i}<Comment/>')
X(f'{i}<UseStandardCommands>{"true" if get_bool_prop("useStandardCommands", True) else "false"}</UseStandardCommands>')
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '100'
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '150'
code_allowed_length = get_enum_prop('CodeAllowedLength', 'codeAllowedLength', 'Variable')
X(f'{i}<CodeLength>{code_length}</CodeLength>')
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
X(f'{i}<DefaultPresentation>AsDescription</DefaultPresentation>')
X(f'{i}<EditType>InDialog</EditType>')
X(f'{i}<DefaultPresentation>{get_enum_prop("DefaultPresentation", "defaultPresentation", "AsDescription")}</DefaultPresentation>')
X(f'{i}<EditType>{get_enum_prop("EditType", "editType", "InDialog")}</EditType>')
X(f'{i}<QuickChoice>{"true" if defn.get("quickChoice") is True else "false"}</QuickChoice>')
X(f'{i}<ChoiceMode>{get_enum_prop("ChoiceMode", "choiceMode", "BothWays")}</ChoiceMode>')
# InputByString: override `inputByString` ЛИБО дефолт [Descr при D>0]+[Code при C>0] (prefix ExchangePlan).
if 'inputByString' in defn:
ib_fields = [expand_data_path(str(x)) for x in (defn.get('inputByString') or [])]
else:
ib_fields = []
if int(description_length) > 0:
ib_fields.append(f'ExchangePlan.{obj_name}.StandardAttribute.Description')
if int(code_length) > 0:
ib_fields.append(f'ExchangePlan.{obj_name}.StandardAttribute.Code')
emit_field_block(i, 'InputByString', ib_fields)
X(f'{i}<SearchStringModeOnInputByString>{get_enum_prop("SearchStringModeOnInputByString", "searchStringModeOnInputByString", "Begin")}</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
emit_form_ref(i, 'DefaultObjectForm', defn.get('defaultObjectForm'))
emit_form_ref(i, 'DefaultListForm', defn.get('defaultListForm'))
emit_form_ref(i, 'DefaultChoiceForm', defn.get('defaultChoiceForm'))
emit_form_ref(i, 'AuxiliaryObjectForm', defn.get('auxiliaryObjectForm'))
emit_form_ref(i, 'AuxiliaryListForm', defn.get('auxiliaryListForm'))
emit_form_ref(i, 'AuxiliaryChoiceForm', defn.get('auxiliaryChoiceForm'))
emit_standard_attributes(i, 'ExchangePlan')
emit_characteristics(i, defn.get('characteristics'))
emit_based_on(i, defn.get('basedOn'))
distributed = 'true' if defn.get('distributedInfoBase') is True else 'false'
include_ext = 'true' if defn.get('includeConfigurationExtensions') is True else 'false'
X(f'{i}<DistributedInfoBase>{distributed}</DistributedInfoBase>')
X(f'{i}<IncludeConfigurationExtensions>{include_ext}</IncludeConfigurationExtensions>')
X(f'{i}<BasedOn/>')
quick_choice = 'true' if defn.get('quickChoice') is True else 'false'
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
X(f'{i}<InputByString>')
X(f'{i}\t<xr:Field>ExchangePlan.{obj_name}.StandardAttribute.Description</xr:Field>')
X(f'{i}\t<xr:Field>ExchangePlan.{obj_name}.StandardAttribute.Code</xr:Field>')
X(f'{i}</InputByString>')
X(f'{i}<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>')
X(f'{i}<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>')
X(f'{i}<DefaultObjectForm/>')
X(f'{i}<DefaultListForm/>')
X(f'{i}<DefaultChoiceForm/>')
X(f'{i}<AuxiliaryObjectForm/>')
X(f'{i}<AuxiliaryListForm/>')
X(f'{i}<AuxiliaryChoiceForm/>')
X(f'{i}<IncludeHelpInContents>false</IncludeHelpInContents>')
X(f'{i}<DataLockFields/>')
data_lock_control_mode = get_enum_prop('DataLockControlMode', 'dataLockControlMode', 'Automatic')
X(f'{i}<DataLockControlMode>{data_lock_control_mode}</DataLockControlMode>')
full_text_search = get_enum_prop('FullTextSearch', 'fullTextSearch', 'Use')
X(f'{i}<FullTextSearch>{full_text_search}</FullTextSearch>')
X(f'{i}<CreateOnInput>{get_enum_prop("CreateOnInput", "createOnInput", "DontUse")}</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>{get_enum_prop("ChoiceHistoryOnInput", "choiceHistoryOnInput", "Auto")}</ChoiceHistoryOnInput>')
X(f'{i}<IncludeHelpInContents>{"true" if get_bool_prop("includeHelpInContents", False) else "false"}</IncludeHelpInContents>')
dl_fields = [expand_data_path(str(x)) for x in defn.get('dataLockFields', [])] if 'dataLockFields' in defn else []
emit_field_block(i, 'DataLockFields', dl_fields)
X(f'{i}<DataLockControlMode>{get_enum_prop("DataLockControlMode", "dataLockControlMode", "Managed")}</DataLockControlMode>')
X(f'{i}<FullTextSearch>{get_enum_prop("FullTextSearch", "fullTextSearch", "Use")}</FullTextSearch>')
emit_mltext(i, 'ObjectPresentation', defn.get('objectPresentation'))
emit_mltext(i, 'ExtendedObjectPresentation', defn.get('extendedObjectPresentation'))
emit_mltext(i, 'ListPresentation', defn.get('listPresentation'))
emit_mltext(i, 'ExtendedListPresentation', defn.get('extendedListPresentation'))
emit_mltext(i, 'Explanation', defn.get('explanation'))
X(f'{i}<CreateOnInput>DontUse</CreateOnInput>')
X(f'{i}<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>')
X(f'{i}<DataHistory>DontUse</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>')
X(f'{i}<DataHistory>{get_enum_prop("DataHistory", "dataHistory", "DontUse")}</DataHistory>')
X(f'{i}<UpdateDataHistoryImmediatelyAfterWrite>{"true" if get_bool_prop("updateDataHistoryImmediatelyAfterWrite", False) else "false"}</UpdateDataHistoryImmediatelyAfterWrite>')
X(f'{i}<ExecuteAfterWriteDataHistoryVersionProcessing>{"true" if get_bool_prop("executeAfterWriteDataHistoryVersionProcessing", False) else "false"}</ExecuteAfterWriteDataHistoryVersionProcessing>')
def emit_chart_of_characteristic_types_properties(indent):
i = indent
@@ -1,7 +1,7 @@
# meta-decompile v0.22 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.23 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Пилот: только Catalog. Инверс meta-compile (omit-on-default: ключ эмитим только
# Поддержаны: Catalog, ExchangePlan. Инверс meta-compile (omit-on-default: ключ эмитим только
# когда значение в XML отличается от умолчания компилятора). Неподдерживаемый тип / не-MetaDataObject
# root → exit 3 (ring3, как form-decompile).
param(
@@ -91,8 +91,8 @@ foreach ($c in $rootEl.ChildNodes) { if ($c.NodeType -eq 'Element') { $objNode =
if (-not $objNode) { [Console]::Error.WriteLine("meta-decompile: пустой MetaDataObject"); exit 3 }
$objType = $objNode.LocalName
if ($objType -ne 'Catalog') {
[Console]::Error.WriteLine("meta-decompile: тип '$objType' пока не поддержан (пилот — только Catalog)"); exit 3
if ($objType -notin @('Catalog', 'ExchangePlan')) {
[Console]::Error.WriteLine("meta-decompile: тип '$objType' пока не поддержан (Catalog, ExchangePlan)"); exit 3
}
$props = $objNode.SelectSingleNode('md:Properties', $nsm)
@@ -261,8 +261,12 @@ function Attr-ToDsl {
$ml = $ap.SelectSingleNode('md:MultiLine', $nsm); if ($ml -and $ml.InnerText -eq 'true') { $flags += 'multiline' }
# Синоним/подсказка (строка ru-only ИЛИ {ru,en}).
$synVal = Get-MLValue ($ap.SelectSingleNode('md:Synonym', $nsm))
$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 -ne (Split-CamelWords $nm)) { $synCustom = $true } }
elseif ($null -ne $synVal) { $synCustom = $true } # {ru,en} = всегда кастом
$ttVal = Get-MLValue ($ap.SelectSingleNode('md:ToolTip', $nsm))
@@ -343,10 +347,11 @@ function Attr-ToDsl {
$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 }
if ($synCustom -or ($null -ne $ttVal) -or $extra.Count -gt 0) {
if ($synCustom -or $synEmpty -or ($null -ne $ttVal) -or $extra.Count -gt 0) {
$o = [ordered]@{ name = $nm }
if ($ts) { $o['type'] = $ts }
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) }
@@ -358,7 +363,7 @@ function Attr-ToDsl {
}
# === Сборка DSL ===
$dsl = [ordered]@{ type = 'Catalog'; name = $objName }
$dsl = [ordered]@{ type = $objType; name = $objName }
# Синоним объекта: строка ru-only ИЛИ {ru,en} (мультиязычно). Кастом → эмитим.
$synVal = Get-MLValue ($props.SelectSingleNode('md:Synonym', $nsm))
@@ -383,8 +388,12 @@ if ($ownersNode) {
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 = if ($objType -eq 'ExchangePlan') { 150 } else { 25 }
$createInpDef = if ($objType -eq 'ExchangePlan') { 'DontUse' } else { 'Use' }
$dataLockDef = if ($objType -eq 'ExchangePlan') { 'Managed' } else { 'Automatic' }
Add-IntProp 'codeLength' 'CodeLength' 9
Add-IntProp 'descriptionLength' 'DescriptionLength' 25
Add-IntProp 'descriptionLength' 'DescriptionLength' $descrLenDef
Add-EnumProp 'codeType' 'CodeType' 'String'
Add-EnumProp 'codeAllowedLength' 'CodeAllowedLength' 'Variable'
Add-BoolProp 'autonumbering' 'Autonumbering' $true
@@ -393,15 +402,23 @@ Add-EnumProp 'codeSeries' 'CodeSeries' 'WholeCatalog'
Add-EnumProp 'defaultPresentation' 'DefaultPresentation' 'AsDescription'
Add-BoolProp 'quickChoice' 'QuickChoice' $false
Add-EnumProp 'choiceMode' 'ChoiceMode' 'BothWays'
Add-EnumProp 'dataLockControlMode' 'DataLockControlMode' 'Automatic'
Add-EnumProp 'dataLockControlMode' 'DataLockControlMode' $dataLockDef
Add-EnumProp 'fullTextSearch' 'FullTextSearch' 'Use'
Add-BoolProp 'useStandardCommands' 'UseStandardCommands' $true
Add-EnumProp 'createOnInput' 'CreateOnInput' 'Use'
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'
# 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
}
# Короткая форма поля: <Type>.<Name>.StandardAttribute.X / .Attribute.X → StandardAttribute.X / Attribute.X
# (Expand-DataPath компилятора разворачивает частичную форму обратно — dogfood резолвера).
@@ -504,14 +521,32 @@ if ($charsNode) {
}
}
# --- StandardAttributes: блок есть ⟺ кастомизация ≥1 стандартного реквизита.
# Захватываем ОТКЛОНЕНИЯ от профиля материализованного блока (профиль компилятор восстановит сам).
# Профиль Catalog: Owner{FC=ShowError,FFV=true}, Parent{FFV=true}, Description{FC=ShowError}. ---
$catStdProfile = @{
'Owner' = @{ fillChecking = 'ShowError'; fillFromFillingValue = $true }
'Parent' = @{ fillFromFillingValue = $true }
'Description' = @{ fillChecking = 'ShowError' }
# --- 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' }
}
}
$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')
}
$stdFixed = if ($stdFixedByType.ContainsKey($objType)) { $stdFixedByType[$objType] } else { @() }
# Условные типы: блок эмитим-как-триггер даже пустым (материализуется при отклонении ≥1 реквизита от schema-default;
# у ExchangePlan это почти всегда — Description/Code=ShowError; редкий all-default EP блок опускает).
$stdConditionalTypes = @('Catalog', 'ExchangePlan')
$saNode = $props.SelectSingleNode('md:StandardAttributes', $nsm)
if ($saNode) {
$saMap = [ordered]@{}
@@ -543,9 +578,11 @@ if ($saNode) {
$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 }
if ($ov.Count -gt 0) { $saMap[$an] = $ov }
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
if ($ov.Count -gt 0 -or ($stdFixed -notcontains $an)) { $saMap[$an] = $ov }
}
$dsl['standardAttributes'] = $saMap # даже пустой = блок есть (чистый профиль)
# Условный тип (Catalog): пустой $saMap = триггер блока. Не-условный (ExchangePlan): блок и так эмитится → пустой не пишем.
if ($saMap.Count -gt 0 -or ($stdConditionalTypes -contains $objType)) { $dsl['standardAttributes'] = $saMap }
}
# --- ChildObjects: Attributes + TabularSections ---
+25
View File
@@ -594,6 +594,31 @@ omit-on-empty. Поля пишутся частичной формой (`Standar
| `attributes` | `[]` | → Attribute в ChildObjects |
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
### 7.2a ExchangePlan (План обмена)
Близок к Catalog (без иерархии/владельцев/кодогенерации), плюс два своих флага. Общий с Catalog слой: `synonym`,
`comment`, `useStandardCommands`, `codeLength`/`codeAllowedLength`/`descriptionLength`, `defaultPresentation`, `editType`,
`quickChoice`, `choiceMode`, `inputByString` (§7.1.5, дефолт [Наименование]+[Код] по длинам), формы (`defaultObjectForm`/
`defaultListForm`/`defaultChoiceForm`/`auxiliary*`), `standardAttributes` (§7.1.1), `characteristics` (§7.1.4), `basedOn`,
`dataLockFields`, презентации, `choiceHistoryOnInput`, `includeHelpInContents`.
| Поле JSON | Умолчание | XML элемент |
|-----------|----------|-------------|
| `distributedInfoBase` | `false` | DistributedInfoBase (РИБ — распределённая ИБ) |
| `includeConfigurationExtensions` | `false` | IncludeConfigurationExtensions |
| `createOnInput` | `DontUse` | CreateOnInput *(отличие от Catalog: там `Use`)* |
| `dataLockControlMode` | `Managed` | DataLockControlMode *(отличие от Catalog: там `Automatic`)* |
| `dataHistory` | `DontUse` | DataHistory |
| `updateDataHistoryImmediatelyAfterWrite` | `false` | UpdateDataHistoryImmediatelyAfterWrite |
| `executeAfterWriteDataHistoryVersionProcessing` | `false` | ExecuteAfterWriteDataHistoryVersionProcessing |
| `descriptionLength` | `150` | DescriptionLength *(отличие от Catalog: там `25`)* |
| `attributes` / `tabularSections` | `[]` / `{}` | → ChildObjects |
Стандартные реквизиты EP: Ref, DeletionMark, Code, Description, ThisNode, SentNo, ReceivedNo (профиль материализованного
блока: Наименование/Код → FillChecking=ShowError). Блок **условный** (как Catalog): материализуется при кастомизации —
ключ `standardAttributes`. Опциональный легаси-реквизит `ExchangeDate` (часть планов) поддержан как «доп.» член
`standardAttributes` (эмитится по факту наличия ключа, вне фикс-списка).
### 7.3 Enum
| Поле JSON | Умолчание | XML элемент |
@@ -3,9 +3,11 @@
"input": {
"type": "ExchangePlan",
"name": "ОбменСФилиалами",
"comment": "обмен данными с филиалами",
"codeLength": 9,
"descriptionLength": 100,
"distributedInfoBase": true,
"standardAttributes": { "Description": { "synonym": "Имя узла" }, "Code": { "fillChecking": "ShowError" } },
"attributes": ["ПутьКФайлу: String(500)"],
"tabularSections": [
{
@@ -32,13 +32,28 @@
<v8:content>Обмен сфилиалами</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Comment>обмен данными с филиалами</Comment>
<UseStandardCommands>true</UseStandardCommands>
<CodeLength>9</CodeLength>
<CodeAllowedLength>Variable</CodeAllowedLength>
<DescriptionLength>100</DescriptionLength>
<DefaultPresentation>AsDescription</DefaultPresentation>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>ExchangePlan.ОбменСФилиалами.StandardAttribute.Description</xr:Field>
<xr:Field>ExchangePlan.ОбменСФилиалами.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<StandardAttributes>
<xr:StandardAttribute name="Ref">
<xr:LinkByType/>
@@ -94,7 +109,7 @@
</xr:StandardAttribute>
<xr:StandardAttribute name="Code">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:FillChecking>ShowError</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
@@ -120,7 +135,7 @@
</xr:StandardAttribute>
<xr:StandardAttribute name="Description">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:FillChecking>ShowError</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
@@ -136,7 +151,12 @@
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Имя узла</v8:content>
</v8:item>
</xr:Synonym>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
@@ -223,35 +243,21 @@
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<Characteristics/>
<BasedOn/>
<DistributedInfoBase>true</DistributedInfoBase>
<IncludeConfigurationExtensions>false</IncludeConfigurationExtensions>
<BasedOn/>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>ExchangePlan.ОбменСФилиалами.StandardAttribute.Description</xr:Field>
<xr:Field>ExchangePlan.ОбменСФилиалами.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<CreateOnInput>DontUse</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<IncludeHelpInContents>false</IncludeHelpInContents>
<DataLockFields/>
<DataLockControlMode>Automatic</DataLockControlMode>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>DontUse</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>