feat(meta-compile,meta-decompile): кастомизация LineNumber табличной части (v1.32/v0.22)

У каждой ТЧ ровно один стандартный реквизит — НомерСтроки (LineNumber), и его
свойства (синоним/подсказка/полнотекстовый поиск/…) тоже переопределяемы.
Раньше компилятор эмитил only-default блок, декомпилятор кастомизацию не ловил.

DSL `lineNumber` на объектной форме ТЧ (omit-on-default по каждому свойству):
synonym/comment/fullTextSearch/tooltip/format/editFormat/choiceHistoryOnInput.
Emit-StandardAttribute расширен (Format/EditFormat через Emit-MLText,
ChoiceHistoryOnInput из ov — были захардкожены); Emit-TabularStandardAttributes
принимает spec и строит ov. Декомпилятор захватывает из
xr:StandardAttribute[@name='LineNumber'] → ключ lineNumber.

Покрыта КАСТОМИЗАЦИЯ (в корпусе 2/1728, только synonym — но задокументирован
полный набор из 7 свойств). 44/1728 ТЧ блок вовсе ОПУСКАЮТ — правило опущения не
выводится (FillChecking/Use не различают опускающие/имеющие), компилятор эмитит
блок всегда → not-covered хвост (решение совместное).

Аддитивно: all-default блок (1684/1728) эмитится идентично, регрессий нет
(TOTAL 78 без изменений). Регресс 47/47 ps1+py, ps1↔py identical. spec §5.1,
кейс catalog-ts-linenumber.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-04 19:03:26 +03:00
parent 177c9bb0fa
commit 027e6a4a89
10 changed files with 657 additions and 26 deletions
@@ -1,4 +1,4 @@
# meta-compile v1.31 — Compile 1C metadata object from JSON
# meta-compile v1.32 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1058,6 +1058,9 @@ function Emit-StandardAttribute {
$cf = OvOr 'ChoiceForm' ''
$cmt = OvOr 'Comment' ''
$msk = OvOr 'Mask' ''
$fmt = OvOr 'Format' $null
$efmt = OvOr 'EditFormat' $null
$chi = OvOr 'ChoiceHistoryOnInput' 'Auto'
X "$indent<xr:StandardAttribute name=`"$attrName`">"
X "$indent`t<xr:LinkByType/>"
X "$indent`t<xr:FillChecking>$fc</xr:FillChecking>"
@@ -1067,11 +1070,11 @@ function Emit-StandardAttribute {
X "$indent`t<xr:MaxValue xsi:nil=`"true`"/>"
Emit-MLText "$indent`t" "xr:ToolTip" $tt
X "$indent`t<xr:ExtendedEdit>false</xr:ExtendedEdit>"
X "$indent`t<xr:Format/>"
Emit-MLText "$indent`t" "xr:Format" $fmt
if ($cf) { X "$indent`t<xr:ChoiceForm>$(Esc-Xml "$cf")</xr:ChoiceForm>" } else { X "$indent`t<xr:ChoiceForm/>" }
X "$indent`t<xr:QuickChoice>Auto</xr:QuickChoice>"
X "$indent`t<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>"
X "$indent`t<xr:EditFormat/>"
X "$indent`t<xr:ChoiceHistoryOnInput>$chi</xr:ChoiceHistoryOnInput>"
Emit-MLText "$indent`t" "xr:EditFormat" $efmt
X "$indent`t<xr:PasswordMode>false</xr:PasswordMode>"
X "$indent`t<xr:DataHistory>$dh</xr:DataHistory>"
X "$indent`t<xr:MarkNegatives>false</xr:MarkNegatives>"
@@ -1134,11 +1137,24 @@ function Emit-StandardAttributes {
X "$indent</StandardAttributes>"
}
# TabularSection standard attributes (just LineNumber)
# TabularSection standard attributes (единственный — LineNumber/НомерСтроки). Блок эмитится всегда (платформа
# опускает его лишь у редкого хвоста ТЧ — правило не выведено, см. WORKFLOW). DSL `lineNumber` на объектной форме ТЧ
# переопределяет свойства (synonym/comment/fullTextSearch/tooltip/format/editFormat/choiceHistoryOnInput).
function Emit-TabularStandardAttributes {
param([string]$indent)
param([string]$indent, $lineNumber = $null)
$ov = $null
if ($lineNumber) {
$ov = @{}
if ($null -ne $lineNumber.synonym) { $ov['Synonym'] = $lineNumber.synonym }
if ($lineNumber.comment) { $ov['Comment'] = "$($lineNumber.comment)" }
if ($lineNumber.fullTextSearch) { $ov['FullTextSearch'] = "$($lineNumber.fullTextSearch)" }
if ($null -ne $lineNumber.tooltip) { $ov['ToolTip'] = $lineNumber.tooltip }
if ($null -ne $lineNumber.format) { $ov['Format'] = $lineNumber.format }
if ($null -ne $lineNumber.editFormat) { $ov['EditFormat'] = $lineNumber.editFormat }
if ($lineNumber.choiceHistoryOnInput) { $ov['ChoiceHistoryOnInput'] = "$($lineNumber.choiceHistoryOnInput)" }
}
X "$indent<StandardAttributes>"
Emit-StandardAttribute "$indent`t" "LineNumber"
Emit-StandardAttribute "$indent`t" "LineNumber" $ov
X "$indent</StandardAttributes>"
}
@@ -1662,7 +1678,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)
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null)
$uuid = New-Guid-String
X "$indent<TabularSection uuid=`"$uuid`">"
@@ -1689,7 +1705,7 @@ function Emit-TabularSection {
if ($tsComment) { X "$indent`t`t<Comment>$(Esc-XmlText $tsComment)</Comment>" } else { X "$indent`t`t<Comment/>" }
Emit-MLText "$indent`t`t" "ToolTip" $tsTooltip
X "$indent`t`t<FillChecking>DontCheck</FillChecking>"
Emit-TabularStandardAttributes "$indent`t`t"
Emit-TabularStandardAttributes "$indent`t`t" $tsLineNumber
# Use=ForItem only for Catalog tabular sections (Document does not have Use)
if ($objectType -eq "Catalog") {
X "$indent`t`t<Use>ForItem</Use>"
@@ -3478,10 +3494,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 }
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $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 } }
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber }
}
if ($def.tabularSections -is [array] -or $def.tabularSections.GetType().Name -eq "Object[]") {
foreach ($ts in $def.tabularSections) { $tsSections[$ts.name] = New-TsEntry $ts }
@@ -3529,7 +3545,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
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber
}
foreach ($af in $acctFlags) {
$afName = if ($af.name) { $af.name } else { "$af" }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.31 — Compile 1C metadata object from JSON
# meta-compile v1.32 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1070,6 +1070,9 @@ def emit_standard_attribute(indent, attr_name, ov=None):
cf = ov.get('ChoiceForm', '')
cmt = ov.get('Comment', '')
msk = ov.get('Mask', '')
fmt = ov.get('Format')
efmt = ov.get('EditFormat')
chi = ov.get('ChoiceHistoryOnInput', 'Auto')
X(f'{indent}<xr:StandardAttribute name="{attr_name}">')
X(f'{indent}\t<xr:LinkByType/>')
X(f'{indent}\t<xr:FillChecking>{fc}</xr:FillChecking>')
@@ -1079,14 +1082,14 @@ def emit_standard_attribute(indent, attr_name, ov=None):
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>')
X(f'{indent}\t<xr:Format/>')
emit_mltext(f'{indent}\t', 'xr:Format', fmt)
if cf:
X(f'{indent}\t<xr:ChoiceForm>{esc_xml(str(cf))}</xr:ChoiceForm>')
else:
X(f'{indent}\t<xr:ChoiceForm/>')
X(f'{indent}\t<xr:QuickChoice>Auto</xr:QuickChoice>')
X(f'{indent}\t<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>')
X(f'{indent}\t<xr:EditFormat/>')
X(f'{indent}\t<xr:ChoiceHistoryOnInput>{chi}</xr:ChoiceHistoryOnInput>')
emit_mltext(f'{indent}\t', 'xr:EditFormat', efmt)
X(f'{indent}\t<xr:PasswordMode>false</xr:PasswordMode>')
X(f'{indent}\t<xr:DataHistory>{dh}</xr:DataHistory>')
X(f'{indent}\t<xr:MarkNegatives>false</xr:MarkNegatives>')
@@ -1160,9 +1163,28 @@ def emit_standard_attributes(indent, object_type):
emit_standard_attribute(f'{indent}\t', a, ov)
X(f'{indent}</StandardAttributes>')
def emit_tabular_standard_attributes(indent):
def emit_tabular_standard_attributes(indent, line_number=None):
"""LineNumber/НомерСтроки ТЧ. Блок эмитится всегда. DSL `lineNumber` переопределяет
synonym/comment/fullTextSearch/tooltip/format/editFormat/choiceHistoryOnInput."""
ov = None
if line_number:
ov = {}
if line_number.get('synonym') is not None:
ov['Synonym'] = line_number['synonym']
if line_number.get('comment'):
ov['Comment'] = str(line_number['comment'])
if line_number.get('fullTextSearch'):
ov['FullTextSearch'] = str(line_number['fullTextSearch'])
if line_number.get('tooltip') is not None:
ov['ToolTip'] = line_number['tooltip']
if line_number.get('format') is not None:
ov['Format'] = line_number['format']
if line_number.get('editFormat') is not None:
ov['EditFormat'] = line_number['editFormat']
if line_number.get('choiceHistoryOnInput'):
ov['ChoiceHistoryOnInput'] = str(line_number['choiceHistoryOnInput'])
X(f'{indent}<StandardAttributes>')
emit_standard_attribute(f'{indent}\t', 'LineNumber')
emit_standard_attribute(f'{indent}\t', 'LineNumber', ov)
X(f'{indent}</StandardAttributes>')
# ---------------------------------------------------------------------------
@@ -1712,7 +1734,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):
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):
uid = new_uuid()
X(f'{indent}<TabularSection uuid="{uid}">')
type_prefix = f'{object_type}TabularSection'
@@ -1737,7 +1759,7 @@ def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_
X(f'{indent}\t\t<Comment/>')
emit_mltext(f'{indent}\t\t', 'ToolTip', ts_tooltip)
X(f'{indent}\t\t<FillChecking>DontCheck</FillChecking>')
emit_tabular_standard_attributes(f'{indent}\t\t')
emit_tabular_standard_attributes(f'{indent}\t\t', ts_line_number)
if object_type == 'Catalog':
X(f'{indent}\t\t<Use>ForItem</Use>')
X(f'{indent}\t</Properties>')
@@ -3265,10 +3287,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}
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': 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}
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber')}
if isinstance(ts_data, list):
for ts in ts_data:
ts_sections[ts['name']] = new_ts_entry(ts)
@@ -3317,7 +3339,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'])
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'))
for af in acct_flags:
af_name = af['name'] if isinstance(af, dict) else str(af)
emit_accounting_flag('\t\t\t', af_name)
@@ -1,4 +1,4 @@
# meta-decompile v0.21 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.22 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Пилот: только Catalog. Инверс meta-compile (omit-on-default: ключ эмитим только
@@ -573,11 +573,24 @@ if ($childObjs) {
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 { '' }
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt) {
# Кастомизация стандартного реквизита LineNumber (НомерСтроки) — omit-on-default по каждому свойству.
$lnObj = [ordered]@{}
$lnNode = $tsp.SelectSingleNode("md:StandardAttributes/xr:StandardAttribute[@name='LineNumber']", $nsm)
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 }
}
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $lnObj.Count -gt 0) {
$to = [ordered]@{}
if ($tsSynCustom) { $to['synonym'] = $tsSyn }
if ($null -ne $tsTt) { $to['tooltip'] = $tsTt }
if ($tsCmt) { $to['comment'] = $tsCmt }
if ($lnObj.Count -gt 0) { $to['lineNumber'] = $lnObj }
$to['attributes'] = $cols
$tsMap[$tsName] = $to
} else {