mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-20 18:19:43 +03:00
fix(meta-compile): порядок элементов по канону выгрузки
Детект порядка, добавленный в раундтрип-харнес, показал 1242 расхождения на 4897 объектах (25%). Раньше они были невидимы: дифф строился через Compare-Object, то есть по мультимножеству строк, и позиция не проверялась. Ложных срабатываний там нет по построению — проверка включается только когда мультимножества уже совпали. Три категории, все — порядок эмиссии; DSL и декомпилятор не затронуты. 1. Виды детей регистров (1140 объектов). Компилятор печатал Resource, Dimension, Attribute. Канон, снятый с корпуса acc+erp (разброса внутри типа нет): Resource, Attribute, Dimension у информационного, накопления и расчёта; Dimension, Resource, Attribute — у бухгалтерского. Команды у платформы идут последними, как и было. 2. Квалификаторы в составном типе (61 объект). Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, потом блоки квалификаторов, а Emit-TypeContent рекурсивно печатал каждую часть целиком. На одиночном типе оба порядка совпадают — потому и не всплывало. Порядок самих блоков тоже канонический и НЕ зеркалит порядок типов: Number, String, Date (при типах boolean,string,dateTime,decimal квалификаторы идут Number,String,Date; контрпримеров в корпусе нет). 3. Стандартные реквизиты плана обмена (41 объект, весь список): блок начинается с ThisNode, а не с Ref. Проверка: - корпусный раундтрип 4897 объектов: порядок ≠ 1242 → 0, match не просел (150/1640/151/2600/314/42), TOTAL diff lines 0; - юнит-тесты 648/648 ps1, 645/648 py; - 1С-сертификация на платформе 8.3.24: регистры информационный, накопления, бухгалтерский, расчёта и план обмена — все приняты; - дрейф снэпшотов (10 файлов, 9 кейсов) сверен как чистая перестановка строк. NB: сверять нужно с нормализацией UUID-\d+ — раннер нумерует плейсхолдеры по порядку появления, и перестановка детей меняет, кому достанется UUID-015. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
4833c3b707
commit
a3aa8fe87c
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.83 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.84 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -608,10 +608,44 @@ function Emit-TypeContent {
|
||||
if (-not $typeStr) { return }
|
||||
|
||||
# Composite type: "Type1 + Type2 + Type3"
|
||||
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
|
||||
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
|
||||
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
|
||||
# расхождение вылезало только на составном.
|
||||
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
|
||||
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
|
||||
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
|
||||
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
|
||||
if ($typeStr.Contains(' + ')) {
|
||||
$parts = $typeStr -split '\s*\+\s*'
|
||||
$typeLines = New-Object System.Collections.ArrayList
|
||||
$qualBlocks = @{} # 'Number'|'String'|'Date' → строки блока
|
||||
foreach ($part in $parts) {
|
||||
# X пишет в StringBuilder, поэтому «перехват» — это запомнить длину, вызвать
|
||||
# эмиттер и откатить добавленное. В py-порту X добавляет в список, и там тот
|
||||
# же алгоритм выражен срезом — различие рантаймов, не логики.
|
||||
$before = $script:xml.Length
|
||||
Emit-TypeContent $indent $part.Trim()
|
||||
$chunk = $script:xml.ToString($before, $script:xml.Length - $before)
|
||||
[void]$script:xml.Remove($before, $script:xml.Length - $before)
|
||||
$curQual = $null
|
||||
foreach ($line in ($chunk -split "`r?`n")) {
|
||||
if ($line -eq '') { continue }
|
||||
if ($line -match '<v8:(String|Number|Date)Qualifiers>') {
|
||||
$curQual = $Matches[1]
|
||||
$qualBlocks[$curQual] = New-Object System.Collections.ArrayList
|
||||
}
|
||||
if ($curQual) {
|
||||
[void]$qualBlocks[$curQual].Add($line)
|
||||
if ($line -match '</v8:(String|Number|Date)Qualifiers>') { $curQual = $null }
|
||||
} else {
|
||||
[void]$typeLines.Add($line)
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($line in $typeLines) { X $line }
|
||||
foreach ($q in @('Number', 'String', 'Date')) {
|
||||
if ($qualBlocks.ContainsKey($q)) { foreach ($line in $qualBlocks[$q]) { X $line } }
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -1256,7 +1290,10 @@ $script:standardAttributesByType = @{
|
||||
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","ActionPeriodIsBasic","Description","Code")
|
||||
"BusinessProcess" = @("Ref","DeletionMark","Date","Number","Started","Completed","HeadTask")
|
||||
"Task" = @("Ref","DeletionMark","Date","Number","Executed","Description","RoutePoint","BusinessProcess")
|
||||
"ExchangePlan" = @("Ref","DeletionMark","Code","Description","ThisNode","SentNo","ReceivedNo")
|
||||
# Порядок снят с выгрузки: у плана обмена блок начинается с ThisNode, а не с Ref
|
||||
# (acc+erp, 8 объектов, разброса нет). Прочие типы в этой таблице совпадают с
|
||||
# платформой — расхождений порядка по ним корпусный раундтрип не показал.
|
||||
"ExchangePlan" = @("ThisNode","ReceivedNo","SentNo","Ref","DeletionMark","Description","Code")
|
||||
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
|
||||
}
|
||||
|
||||
@@ -4362,16 +4399,31 @@ if ($objType -in @("InformationRegister","AccumulationRegister","AccountingRegis
|
||||
$regCtx = switch ($objType) { "InformationRegister" { "register-info" } "CalculationRegister" { "register-calc" } default { "register-other" } }
|
||||
# Все семейства регистров: ресурсы/измерения — через богатый Emit-Attribute (общий слой object-свойств).
|
||||
$dimResCtx = switch ($objType) { "InformationRegister" { "register-info" } "AccumulationRegister" { "register-accum" } "CalculationRegister" { "register-calc" } "AccountingRegister" { "register-account" } default { $null } }
|
||||
foreach ($r in $resources) {
|
||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" }
|
||||
else { Emit-Resource "`t`t`t" $r $objType }
|
||||
}
|
||||
foreach ($d in $dims) {
|
||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" }
|
||||
else { Emit-Dimension "`t`t`t" $d $objType }
|
||||
}
|
||||
foreach ($a in $regAttrs) {
|
||||
Emit-Attribute "`t`t`t" $a $regCtx
|
||||
# Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
|
||||
# типа нет): у большинства регистров Resource, Attribute, Dimension, а у
|
||||
# бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
|
||||
# последними, как и здесь.
|
||||
$kindOrder = if ($objType -eq "AccountingRegister") { @('dim','res','attr') } else { @('res','attr','dim') }
|
||||
foreach ($kind in $kindOrder) {
|
||||
switch ($kind) {
|
||||
'res' {
|
||||
foreach ($r in $resources) {
|
||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" }
|
||||
else { Emit-Resource "`t`t`t" $r $objType }
|
||||
}
|
||||
}
|
||||
'dim' {
|
||||
foreach ($d in $dims) {
|
||||
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" }
|
||||
else { Emit-Dimension "`t`t`t" $d $objType }
|
||||
}
|
||||
}
|
||||
'attr' {
|
||||
foreach ($a in $regAttrs) {
|
||||
Emit-Attribute "`t`t`t" $a $regCtx
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($cmd in $regCommands) {
|
||||
Emit-Command "`t`t`t" $cmd.name $cmd.def
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.83 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.84 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -674,10 +674,46 @@ def emit_type_content(indent, type_str):
|
||||
if not type_str:
|
||||
return
|
||||
# Composite type: "Type1 + Type2 + Type3"
|
||||
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
|
||||
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
|
||||
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
|
||||
# расхождение вылезало только на составном.
|
||||
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
|
||||
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
|
||||
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
|
||||
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
|
||||
if ' + ' in type_str:
|
||||
parts = [p.strip() for p in type_str.split('+')]
|
||||
type_lines = []
|
||||
qual_blocks = {}
|
||||
for part in parts:
|
||||
# X добавляет в список lines, поэтому «перехват» — это срез и откат хвоста.
|
||||
# В PS-порту X пишет в StringBuilder и тот же алгоритм выражен через
|
||||
# Length/Remove — различие рантаймов, не логики.
|
||||
before = len(lines)
|
||||
emit_type_content(indent, part)
|
||||
chunk = lines[before:]
|
||||
del lines[before:]
|
||||
cur_qual = None
|
||||
for line in chunk:
|
||||
if not line:
|
||||
continue
|
||||
m = re.search(r'<v8:(String|Number|Date)Qualifiers>', line)
|
||||
if m:
|
||||
cur_qual = m.group(1)
|
||||
qual_blocks[cur_qual] = []
|
||||
if cur_qual:
|
||||
qual_blocks[cur_qual].append(line)
|
||||
if re.search(r'</v8:(String|Number|Date)Qualifiers>', line):
|
||||
cur_qual = None
|
||||
else:
|
||||
type_lines.append(line)
|
||||
for line in type_lines:
|
||||
X(line)
|
||||
for q in ('Number', 'String', 'Date'):
|
||||
if q in qual_blocks:
|
||||
for line in qual_blocks[q]:
|
||||
X(line)
|
||||
return
|
||||
type_str = resolve_type_str(type_str)
|
||||
# Boolean
|
||||
@@ -1288,7 +1324,10 @@ standard_attributes_by_type = {
|
||||
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'],
|
||||
'BusinessProcess': ['Ref', 'DeletionMark', 'Date', 'Number', 'Started', 'Completed', 'HeadTask'],
|
||||
'Task': ['Ref', 'DeletionMark', 'Date', 'Number', 'Executed', 'Description', 'RoutePoint', 'BusinessProcess'],
|
||||
'ExchangePlan': ['Ref', 'DeletionMark', 'Code', 'Description', 'ThisNode', 'SentNo', 'ReceivedNo'],
|
||||
# Порядок снят с выгрузки: у плана обмена блок начинается с ThisNode, а не с Ref
|
||||
# (acc+erp, 8 объектов, разброса нет). Прочие типы в этой таблице совпадают с
|
||||
# платформой — расхождений порядка по ним корпусный раундтрип не показал.
|
||||
'ExchangePlan': ['ThisNode', 'ReceivedNo', 'SentNo', 'Ref', 'DeletionMark', 'Description', 'Code'],
|
||||
'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'],
|
||||
}
|
||||
|
||||
@@ -4261,18 +4300,27 @@ if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegist
|
||||
# Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств).
|
||||
dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum',
|
||||
'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type)
|
||||
for r in resources:
|
||||
if dim_res_ctx:
|
||||
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
|
||||
# Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
|
||||
# типа нет): у большинства регистров Resource, Attribute, Dimension, а у
|
||||
# бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
|
||||
# последними, как и здесь.
|
||||
kind_order = ['dim', 'res', 'attr'] if obj_type == 'AccountingRegister' else ['res', 'attr', 'dim']
|
||||
for kind in kind_order:
|
||||
if kind == 'res':
|
||||
for r in resources:
|
||||
if dim_res_ctx:
|
||||
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
|
||||
else:
|
||||
emit_resource('\t\t\t', r, obj_type)
|
||||
elif kind == 'dim':
|
||||
for d in dims:
|
||||
if dim_res_ctx:
|
||||
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
|
||||
else:
|
||||
emit_dimension('\t\t\t', d, obj_type)
|
||||
else:
|
||||
emit_resource('\t\t\t', r, obj_type)
|
||||
for d in dims:
|
||||
if dim_res_ctx:
|
||||
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
|
||||
else:
|
||||
emit_dimension('\t\t\t', d, obj_type)
|
||||
for a in reg_attrs:
|
||||
emit_attribute('\t\t\t', a, reg_ctx)
|
||||
for a in reg_attrs:
|
||||
emit_attribute('\t\t\t', a, reg_ctx)
|
||||
for cmd in reg_commands:
|
||||
emit_command('\t\t\t', cmd['name'], cmd['def'])
|
||||
X('\t\t</ChildObjects>')
|
||||
|
||||
Reference in New Issue
Block a user