mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-01 09:17:44 +03:00
feat(meta-compile,meta-decompile): свойства формата 2.20 (платформа 8.3.27)
Дельта формата 2.17→2.20 содержит три безусловных свойства, которых компилятор
не эмитил. Все три пишутся ТОЛЬКО при формате >= 2.20 (Detect-FormatVersion),
поэтому 2.17-проекты не меняются: полная сюита зелёная, ни один существующий
снэпшот не сдвинулся.
- xr:TypeReductionMode — каждому стандартному реквизиту, после CreateOnInput.
TransformValues, кроме Owner → Deny (правило проверено против выгрузки acc:
9 из 9 реквизитов совпали, включая Owner).
- TypeReductionMode — измерениям регистра СВЕДЕНИЙ (у прочих семейств и у
реквизитов/ресурсов платформа его не пишет).
- LineNumberLength — табличным частям, последним в Properties.
LineNumberLength — прикладная возможность 8.3.27 (5..9 → до 999 999 999 строк
вместо 99 999), поэтому получил полноценный DSL-ключ и описание в spec §5.2.
Его дефолт зависит НЕ от версии формата, а от режима совместимости на момент
создания ТЧ (<=8_3_26 → 5, >=8_3_27 → 9) — платформа фиксирует значение и позже
не пересчитывает, поэтому в одной конфигурации соседствуют ТЧ с 5 и 9. Отсюда
новая Detect-CompatibilityMode: читает CompatibilityMode из Configuration.xml
(префикс 64 КБ — тег лежит на ~11-12 КБ, существующим 2000 байт не хватает).
Декомпилятор: TypeReductionMode захватывается только при отклонении от правила
(компилятор выводит его сам), LineNumberLength — всегда при наличии тега:
выводить его дефолт значило бы дублировать логику компилятора с риском разойтись.
Компараторы версий числовые по компонентам — строковое сравнение неверно
("2.9" > "2.17" лексикографически).
Тест-инфра: setup-фикстуры empty-config-220 и empty-config-220-compat24
(строятся тем же cf-init), два кейса — по одному на каждую ось.
Проверка: роундтрип реального 2.20-документа БП (АвансовыйОтчет, 7 ТЧ) —
по новым тегам 0 расхождений, значения и позиции совпали; остаточный хвост
52/39 идентичен такому же на 2.17, то есть пред-существующий. Сюита 570/570
ps1, 567+3 skipped py, ps1==py. 1С-сертификация обоих кейсов на 8.3.27 ✓.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
769b4d3dbd
commit
068928646d
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.67 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.68 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -1321,6 +1321,12 @@ function Emit-StandardAttribute {
|
||||
X "$indent`t<xr:MultiLine>false</xr:MultiLine>"
|
||||
X "$indent`t<xr:FillFromFillingValue>$ffv</xr:FillFromFillingValue>"
|
||||
X "$indent`t<xr:CreateOnInput>Auto</xr:CreateOnInput>"
|
||||
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
|
||||
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
|
||||
if ($script:isFormat220) {
|
||||
$trm = OvOr 'TypeReductionMode' $(if ($attrName -ceq 'Owner') { 'Deny' } else { 'TransformValues' })
|
||||
X "$indent`t<xr:TypeReductionMode>$trm</xr:TypeReductionMode>"
|
||||
}
|
||||
X "$indent`t<xr:MaxValue xsi:nil=`"true`"/>"
|
||||
Emit-MLText "$indent`t" "xr:ToolTip" $tt
|
||||
X "$indent`t<xr:ExtendedEdit>false</xr:ExtendedEdit>"
|
||||
@@ -1949,6 +1955,12 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<DataHistory>$dh</DataHistory>"
|
||||
}
|
||||
}
|
||||
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
|
||||
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
|
||||
if ($script:isFormat220 -and $elemTag -eq "Dimension" -and $context -eq "register-info") {
|
||||
$trm = if ($parsed.typeReductionMode) { "$($parsed.typeReductionMode)" } else { "TransformValues" }
|
||||
X "$indent`t`t<TypeReductionMode>$trm</TypeReductionMode>"
|
||||
}
|
||||
|
||||
X "$indent`t</Properties>"
|
||||
X "$indent</$elemTag>"
|
||||
@@ -2020,7 +2032,7 @@ function Emit-Command {
|
||||
# --- 9. TabularSection emitter ---
|
||||
|
||||
function Emit-TabularSection {
|
||||
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null)
|
||||
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null, $tsLineNumberLength = $null)
|
||||
$uuid = New-Guid-String
|
||||
X "$indent<TabularSection uuid=`"$uuid`">"
|
||||
|
||||
@@ -2059,6 +2071,12 @@ function Emit-TabularSection {
|
||||
$use = if ($tsUse) { "$tsUse" } else { "ForItem" }
|
||||
X "$indent`t`t<Use>$use</Use>"
|
||||
}
|
||||
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
|
||||
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
|
||||
if ($script:isFormat220) {
|
||||
$lnl = if ($null -ne $tsLineNumberLength -and "$tsLineNumberLength" -ne '') { [int]$tsLineNumberLength } else { $script:lineNumberLengthDefault }
|
||||
X "$indent`t`t<LineNumberLength>$lnl</LineNumberLength>"
|
||||
}
|
||||
X "$indent`t</Properties>"
|
||||
|
||||
$tsContext = if ($objectType -in @("DataProcessor","Report")) { "processor-tabular" } else { "tabular" }
|
||||
@@ -3984,7 +4002,51 @@ function Detect-FormatVersion([string]$dir) {
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
# Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
|
||||
# (≤Version8_3_26 → 5, ≥Version8_3_27 → 9; платформа фиксирует значение при СОЗДАНии ТЧ).
|
||||
# NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки) — это
|
||||
# независимые вещи, читаются из одного файла разными функциями.
|
||||
# Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала (в отличие от version=
|
||||
# в первой строке), 2000 байт Detect-FormatVersion сюда не хватает.
|
||||
function Detect-CompatibilityMode([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
# NB: длина файла — в БАЙТАХ, а Substring режет по СИМВОЛАМ (кириллица = 2 байта),
|
||||
# поэтому ограничиваем по длине уже декодированной строки.
|
||||
$text = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||
$head = $text.Substring(0, [Math]::Min(65536, $text.Length))
|
||||
if ($head -match '<CompatibilityMode>([^<]+)</CompatibilityMode>') { return $Matches[1].Trim() }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "Version8_3_24"
|
||||
}
|
||||
|
||||
# Номер версии режима совместимости для сравнений: "Version8_3_27" → 80327, "Version8_5_1" → 80501.
|
||||
function Get-CompatModeRank([string]$mode) {
|
||||
if ($mode -match '^Version(\d+)_(\d+)_(\d+)$') {
|
||||
return [int]$Matches[1] * 10000 + [int]$Matches[2] * 100 + [int]$Matches[3]
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
|
||||
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $OutputDir
|
||||
$script:compatMode = Detect-CompatibilityMode $OutputDir
|
||||
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
|
||||
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
|
||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
|
||||
|
||||
# --- 15. Main assembler ---
|
||||
|
||||
@@ -4064,10 +4126,10 @@ if ($objType -in $typesWithAttrTS) {
|
||||
# Нормализуем в $tsSections[name] = @{ columns; synonym; tooltip; comment }.
|
||||
function New-TsEntry { param($val)
|
||||
if ($val -is [array] -or $val.GetType().Name -eq 'Object[]') {
|
||||
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null }
|
||||
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null; lineNumberLength = $null }
|
||||
}
|
||||
$cols = if ($val.attributes) { @($val.attributes) } elseif ($val.columns) { @($val.columns) } else { @() }
|
||||
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use }
|
||||
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use; lineNumberLength = $val.lineNumberLength }
|
||||
}
|
||||
if ($def.tabularSections -is [array] -or $def.tabularSections.GetType().Name -eq "Object[]") {
|
||||
foreach ($ts in $def.tabularSections) { $tsSections[$ts.name] = New-TsEntry $ts }
|
||||
@@ -4117,7 +4179,7 @@ if ($objType -in $typesWithAttrTS) {
|
||||
}
|
||||
foreach ($tsName in $tsSections.Keys) {
|
||||
$tsE = $tsSections[$tsName]
|
||||
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use
|
||||
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use $tsE.lineNumberLength
|
||||
}
|
||||
foreach ($af in $acctFlags) {
|
||||
Emit-Attribute "`t`t`t" $af "account-flag" "AccountingFlag"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.67 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.68 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -1340,6 +1340,11 @@ def emit_standard_attribute(indent, attr_name, ov=None):
|
||||
X(f'{indent}\t<xr:MultiLine>false</xr:MultiLine>')
|
||||
X(f'{indent}\t<xr:FillFromFillingValue>{ffv}</xr:FillFromFillingValue>')
|
||||
X(f'{indent}\t<xr:CreateOnInput>Auto</xr:CreateOnInput>')
|
||||
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
|
||||
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
|
||||
if is_format_220:
|
||||
trm = ov.get('TypeReductionMode', 'Deny' if attr_name == 'Owner' else 'TransformValues')
|
||||
X(f'{indent}\t<xr:TypeReductionMode>{trm}</xr:TypeReductionMode>')
|
||||
X(f'{indent}\t<xr:MaxValue xsi:nil="true"/>')
|
||||
emit_mltext(f'{indent}\t', 'xr:ToolTip', tt)
|
||||
X(f'{indent}\t<xr:ExtendedEdit>false</xr:ExtendedEdit>')
|
||||
@@ -2012,6 +2017,10 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if context not in ('chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
|
||||
X(f'{indent}\t\t<DataHistory>{parsed.get("dataHistory") or "Use"}</DataHistory>')
|
||||
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
|
||||
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
|
||||
if is_format_220 and elem_tag == 'Dimension' and context == 'register-info':
|
||||
X(f'{indent}\t\t<TypeReductionMode>{parsed.get("typeReductionMode") or "TransformValues"}</TypeReductionMode>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</{elem_tag}>')
|
||||
|
||||
@@ -2088,7 +2097,7 @@ def emit_command(indent, cmd_name, cmd):
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</Command>')
|
||||
|
||||
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None):
|
||||
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None, ts_line_number_length=None):
|
||||
uid = new_uuid()
|
||||
X(f'{indent}<TabularSection uuid="{uid}">')
|
||||
type_prefix = f'{object_type}TabularSection'
|
||||
@@ -2119,6 +2128,11 @@ def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_
|
||||
emit_tabular_standard_attributes(f'{indent}\t\t', ts_line_number)
|
||||
if object_type in ('Catalog', 'ChartOfCharacteristicTypes'):
|
||||
X(f'{indent}\t\t<Use>{ts_use if ts_use else "ForItem"}</Use>')
|
||||
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
|
||||
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
|
||||
if is_format_220:
|
||||
lnl = int(ts_line_number_length) if ts_line_number_length not in (None, '') else line_number_length_default
|
||||
X(f'{indent}\t\t<LineNumberLength>{lnl}</LineNumberLength>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
ts_context = 'processor-tabular' if object_type in ('DataProcessor', 'Report') else 'tabular'
|
||||
X(f'{indent}\t<ChildObjects>')
|
||||
@@ -3887,7 +3901,44 @@ def detect_format_version(d):
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
def detect_compatibility_mode(d):
|
||||
"""Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
|
||||
(<=Version8_3_26 → 5, >=Version8_3_27 → 9; платформа фиксирует значение при СОЗДАНИИ ТЧ).
|
||||
NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки).
|
||||
Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала."""
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(65536)
|
||||
m = re.search(r'<CompatibilityMode>([^<]+)</CompatibilityMode>', head)
|
||||
if m:
|
||||
return m.group(1).strip()
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "Version8_3_24"
|
||||
|
||||
|
||||
def compat_mode_rank(mode):
|
||||
""""Version8_3_27" → 80327, "Version8_5_1" → 80501."""
|
||||
m = re.match(r'^Version(\d+)_(\d+)_(\d+)$', mode or '')
|
||||
return int(m.group(1)) * 10000 + int(m.group(2)) * 100 + int(m.group(3)) if m else 0
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
format_version = detect_format_version(output_dir)
|
||||
compat_mode = detect_compatibility_mode(output_dir)
|
||||
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
|
||||
is_format_220 = format_rank(format_version) >= 220
|
||||
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
|
||||
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 15. Main assembler
|
||||
@@ -3980,10 +4031,10 @@ if obj_type in types_with_attr_ts:
|
||||
# Значение ТЧ: массив колонок (синоним авто) ЛИБО объект {attributes/columns, synonym, tooltip, comment}.
|
||||
def new_ts_entry(val):
|
||||
if isinstance(val, list):
|
||||
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None}
|
||||
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None, 'lineNumberLength': None}
|
||||
cols = _as_list(val.get('attributes') or val.get('columns') or [])
|
||||
return {'columns': cols, 'synonym': val.get('synonym'), 'tooltip': val.get('tooltip'),
|
||||
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use')}
|
||||
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use'), 'lineNumberLength': val.get('lineNumberLength')}
|
||||
if isinstance(ts_data, list):
|
||||
for ts in ts_data:
|
||||
ts_sections[ts['name']] = new_ts_entry(ts)
|
||||
@@ -4035,7 +4086,7 @@ if obj_type in types_with_attr_ts:
|
||||
emit_attribute('\t\t\t', a, context)
|
||||
for ts_name in ts_order:
|
||||
e = ts_sections[ts_name]
|
||||
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'))
|
||||
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'), e.get('lineNumberLength'))
|
||||
for af in acct_flags:
|
||||
emit_attribute('\t\t\t', af, 'account-flag', 'AccountingFlag')
|
||||
for edf in ext_dim_flags:
|
||||
|
||||
Reference in New Issue
Block a user