feat(skd-compile): viewMode/itemsViewMode на блоках и structure items

DSL расширения (item-level — паттерн object form расширен):
- selection: {field, viewMode}
- order: {field, direction, viewMode} (новая object form)
- structure group: {type:group, viewMode, itemsViewMode}

DSL расширения (block-level на settings):
- selectionViewMode, filterViewMode, orderViewMode
- conditionalAppearanceViewMode
- itemsViewMode (на самих settings)

Compile эмитит viewMode/itemsViewMode только если явно задано в JSON —
это позволяет decompile сохранить точное наличие/отсутствие из XML и
получить bit-perfect round-trip (платформа эмитит эти теги
контекстно — на ABCXYZ-стиле для каждого блока, а в простых отчётах
без пользовательских настроек — не эмитит).

Дополнительно:
- Пустой LocalStringType теперь эмитится как self-closing (как платформа)
- Убран default order/selection=["Auto"] на StructureItemGroup
  (раньше compile дефолтил, теперь эмитит только если задано)

В SKILL.md не упоминаем — фича редкая. Полное описание в spec.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-22 15:38:29 +03:00
co-authored by Claude Opus 4.7
parent 501abd9fac
commit bf4005bf76
14 changed files with 214 additions and 139 deletions
@@ -1,4 +1,4 @@
# skd-compile v1.37 — Compile 1C DCS from JSON
# skd-compile v1.38 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -122,6 +122,15 @@ function Resolve-QueryValue {
function Emit-MLText {
param([string]$tag, $text, [string]$indent, [switch]$NoXsiType)
# Empty value → self-closing tag (matches platform output)
if ($null -eq $text -or ($text -is [string] -and $text -eq '')) {
if ($NoXsiType) {
X "$indent<$tag/>"
} else {
X "$indent<$tag xsi:type=`"v8:LocalStringType`"/>"
}
return
}
if ($NoXsiType) {
X "$indent<$tag>"
} else {
@@ -1870,11 +1879,14 @@ function Emit-SelectionItem {
if ($item.title) {
Emit-MLText -tag "dcsset:lwsTitle" -text $item.title -indent "$indent`t" -NoXsiType
}
if ($item.viewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>"
}
X "$indent</dcsset:item>"
}
function Emit-Selection {
param($items, [string]$indent, [switch]$skipAuto)
param($items, [string]$indent, [switch]$skipAuto, $blockViewMode = $null)
if (-not $items -or $items.Count -eq 0) { return }
@@ -1883,6 +1895,9 @@ function Emit-Selection {
if ($skipAuto -and ($item -is [string]) -and $item -eq 'Auto') { continue }
Emit-SelectionItem -item $item -indent "$indent`t"
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:selection>"
}
@@ -1971,7 +1986,7 @@ function Emit-FilterItem {
}
function Emit-Filter {
param($items, [string]$indent)
param($items, [string]$indent, $blockViewMode = $null)
if (-not $items -or $items.Count -eq 0) { return }
@@ -2003,11 +2018,14 @@ function Emit-Filter {
Emit-FilterItem -item $item -indent "$indent`t"
}
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:filter>"
}
function Emit-Order {
param($items, [string]$indent, [switch]$skipAuto)
param($items, [string]$indent, [switch]$skipAuto, $blockViewMode = $null)
if (-not $items -or $items.Count -eq 0) { return }
@@ -2029,8 +2047,28 @@ function Emit-Order {
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
X "$indent`t</dcsset:item>"
}
} else {
# Object form: { field, direction, viewMode }
if ($item.field -eq "Auto" -or $item.type -eq "auto") {
if (-not $skipAuto) {
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemAuto`"/>"
}
continue
}
$dir = if ($item.direction) { "$($item.direction)" } else { "Asc" }
if ($dir -match '^(?i)desc$') { $dir = "Desc" } elseif ($dir -match '^(?i)asc$') { $dir = "Asc" }
X "$indent`t<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
X "$indent`t`t<dcsset:field>$(Esc-Xml "$($item.field)")</dcsset:field>"
X "$indent`t`t<dcsset:orderType>$dir</dcsset:orderType>"
if ($item.viewMode) {
X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>"
}
X "$indent`t</dcsset:item>"
}
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:order>"
}
@@ -2071,7 +2109,7 @@ function Emit-AppearanceValue {
}
function Emit-ConditionalAppearance {
param($items, [string]$indent)
param($items, [string]$indent, $blockViewMode = $null)
if (-not $items -or $items.Count -eq 0) { return }
@@ -2124,11 +2162,14 @@ function Emit-ConditionalAppearance {
X "$indent`t</dcsset:item>"
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:conditionalAppearance>"
}
function Emit-OutputParameters {
param($params, [string]$indent)
param($params, [string]$indent, $blockViewMode = $null)
if (-not $params) { return }
@@ -2152,11 +2193,14 @@ function Emit-OutputParameters {
}
X "$indent`t</dcscor:item>"
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:outputParameters>"
}
function Emit-DataParameters {
param($items, [string]$indent)
param($items, [string]$indent, $blockViewMode = $null)
if (-not $items -or $items.Count -eq 0) { return }
@@ -2242,6 +2286,9 @@ function Emit-DataParameters {
X "$indent`t</dcscor:item>"
}
if ($null -ne $blockViewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$blockViewMode")</dcsset:viewMode>"
}
X "$indent</dcsset:dataParameters>"
}
@@ -2333,15 +2380,13 @@ function Emit-StructureItem {
$gb = if ($item.groupBy) { $item.groupBy } else { $item.groupFields }
Emit-GroupItems -groupBy $gb -indent "$indent`t"
# Default order to ["Auto"] if not specified
$orderItems = $item.order
if (-not $orderItems) { $orderItems = @("Auto") }
Emit-Order -items $orderItems -indent "$indent`t"
# Default selection to ["Auto"] if not specified
$selItems = $item.selection
if (-not $selItems) { $selItems = @("Auto") }
Emit-Selection -items $selItems -indent "$indent`t"
# Emit order/selection only if specified — platform doesn't always emit them on group
if ($item.order) {
Emit-Order -items $item.order -indent "$indent`t"
}
if ($item.selection) {
Emit-Selection -items $item.selection -indent "$indent`t"
}
Emit-Filter -items $item.filter -indent "$indent`t"
@@ -2356,6 +2401,16 @@ function Emit-StructureItem {
}
}
# viewMode/itemsViewMode on StructureItemGroup are context-dependent —
# platform emits them in some shapes (top-level single group) but not always.
# Emit only when explicitly set in JSON (preserves bit-perfect round-trip).
if ($item.viewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>"
}
if ($item.itemsViewMode) {
X "$indent`t<dcsset:itemsViewMode>$(Esc-Xml "$($item.itemsViewMode)")</dcsset:itemsViewMode>"
}
X "$indent</dcsset:item>"
}
elseif ($type -eq "table") {
@@ -2500,27 +2555,34 @@ function Emit-SettingsVariants {
$s = $v.settings
# Helper: resolve XViewMode from settings — emit only if explicitly set
function Get-BlockVM([string]$key) {
$prop = "${key}ViewMode"
if ($s.PSObject.Properties[$prop]) { return "$($s.$prop)" }
return $null
}
# Selection (Auto items only belong at group level, not top-level settings)
if ($s.selection) {
Emit-Selection -items $s.selection -indent "`t`t`t" -skipAuto
Emit-Selection -items $s.selection -indent "`t`t`t" -skipAuto -blockViewMode (Get-BlockVM 'selection')
}
# Filter
if ($s.filter) {
Emit-Filter -items $s.filter -indent "`t`t`t"
Emit-Filter -items $s.filter -indent "`t`t`t" -blockViewMode (Get-BlockVM 'filter')
}
# Order (Auto items only belong at group level, not top-level settings)
if ($s.order) {
Emit-Order -items $s.order -indent "`t`t`t" -skipAuto
Emit-Order -items $s.order -indent "`t`t`t" -skipAuto -blockViewMode (Get-BlockVM 'order')
}
# ConditionalAppearance
if ($s.conditionalAppearance) {
Emit-ConditionalAppearance -items $s.conditionalAppearance -indent "`t`t`t"
Emit-ConditionalAppearance -items $s.conditionalAppearance -indent "`t`t`t" -blockViewMode (Get-BlockVM 'conditionalAppearance')
}
# OutputParameters
# OutputParameters (platform does NOT emit <viewMode> on this block)
if ($s.outputParameters) {
Emit-OutputParameters -params $s.outputParameters -indent "`t`t`t"
}
@@ -2585,6 +2647,11 @@ function Emit-SettingsVariants {
}
}
# <dcsset:itemsViewMode> on <dcsset:settings> — emit only if explicitly set
if ($s.itemsViewMode) {
X "`t`t`t<dcsset:itemsViewMode>$(Esc-Xml "$($s.itemsViewMode)")</dcsset:itemsViewMode>"
}
X "`t`t</dcsset:settings>"
X "`t</settingsVariant>"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-compile v1.37 — Compile 1C DCS from JSON
# skd-compile v1.38 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -37,6 +37,13 @@ def resolve_query_value(val, base_dir):
def emit_mltext(lines, indent, tag, text, no_xsi_type=False):
# Empty value → self-closing tag (matches platform output)
if text is None or (isinstance(text, str) and text == ''):
if no_xsi_type:
lines.append(f"{indent}<{tag}/>")
else:
lines.append(f'{indent}<{tag} xsi:type="v8:LocalStringType"/>')
return
if not text:
lines.append(f"{indent}<{tag}/>")
return
@@ -1553,10 +1560,12 @@ def emit_selection_item(lines, item, indent):
lines.append(f'{indent}\t<dcsset:field>{esc_xml(str(item["field"]))}</dcsset:field>')
if item.get('title'):
emit_mltext(lines, f'{indent}\t', 'dcsset:lwsTitle', item['title'], no_xsi_type=True)
if item.get('viewMode'):
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
lines.append(f'{indent}</dcsset:item>')
def emit_selection(lines, items, indent, skip_auto=False):
def emit_selection(lines, items, indent, skip_auto=False, block_view_mode=None):
if not items or len(items) == 0:
return
lines.append(f'{indent}<dcsset:selection>')
@@ -1564,6 +1573,8 @@ def emit_selection(lines, items, indent, skip_auto=False):
if skip_auto and isinstance(item, str) and item == 'Auto':
continue
emit_selection_item(lines, item, f'{indent}\t')
if block_view_mode is not None:
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(block_view_mode))}</dcsset:viewMode>')
lines.append(f'{indent}</dcsset:selection>')
@@ -1639,7 +1650,7 @@ def emit_filter_item(lines, item, indent):
lines.append(f'{indent}</dcsset:item>')
def emit_filter(lines, items, indent):
def emit_filter(lines, items, indent, block_view_mode=None):
if not items or len(items) == 0:
return
@@ -1664,10 +1675,12 @@ def emit_filter(lines, items, indent):
emit_filter_item(lines, filter_obj, f'{indent}\t')
else:
emit_filter_item(lines, item, f'{indent}\t')
if block_view_mode is not None:
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(block_view_mode))}</dcsset:viewMode>')
lines.append(f'{indent}</dcsset:filter>')
def emit_order(lines, items, indent, skip_auto=False):
def emit_order(lines, items, indent, skip_auto=False, block_view_mode=None):
if not items or len(items) == 0:
return
@@ -1689,6 +1702,25 @@ def emit_order(lines, items, indent, skip_auto=False):
lines.append(f'{indent}\t\t<dcsset:field>{esc_xml(field)}</dcsset:field>')
lines.append(f'{indent}\t\t<dcsset:orderType>{direction}</dcsset:orderType>')
lines.append(f'{indent}\t</dcsset:item>')
else:
# Object form: { field, direction, viewMode }
if str(item.get('field', '')) == 'Auto' or item.get('type') == 'auto':
if not skip_auto:
lines.append(f'{indent}\t<dcsset:item xsi:type="dcsset:OrderItemAuto"/>')
continue
d = str(item.get('direction', 'Asc'))
if re.match(r'(?i)^desc$', d):
d = 'Desc'
elif re.match(r'(?i)^asc$', d):
d = 'Asc'
lines.append(f'{indent}\t<dcsset:item xsi:type="dcsset:OrderItemField">')
lines.append(f'{indent}\t\t<dcsset:field>{esc_xml(str(item["field"]))}</dcsset:field>')
lines.append(f'{indent}\t\t<dcsset:orderType>{d}</dcsset:orderType>')
if item.get('viewMode'):
lines.append(f'{indent}\t\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
lines.append(f'{indent}\t</dcsset:item>')
if block_view_mode is not None:
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(block_view_mode))}</dcsset:viewMode>')
lines.append(f'{indent}</dcsset:order>')
@@ -1723,7 +1755,7 @@ def emit_appearance_value(lines, key, val, indent):
lines.append(f'{indent}</dcscor:item>')
def emit_conditional_appearance(lines, items, indent):
def emit_conditional_appearance(lines, items, indent, block_view_mode=None):
if not items or len(items) == 0:
return
@@ -1767,6 +1799,8 @@ def emit_conditional_appearance(lines, items, indent):
lines.append(f'{indent}\t\t<dcsset:userSettingID>{esc_xml(uid)}</dcsset:userSettingID>')
lines.append(f'{indent}\t</dcsset:item>')
if block_view_mode is not None:
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(block_view_mode))}</dcsset:viewMode>')
lines.append(f'{indent}</dcsset:conditionalAppearance>')
@@ -1932,13 +1966,11 @@ def emit_structure_item(lines, item, indent):
emit_group_items(lines, item.get('groupBy') or item.get('groupFields'), f'{indent}\t')
# Default order to ["Auto"] if not specified
order_items = item.get('order') or ['Auto']
emit_order(lines, order_items, f'{indent}\t')
# Default selection to ["Auto"] if not specified
sel_items = item.get('selection') or ['Auto']
emit_selection(lines, sel_items, f'{indent}\t')
# Emit order/selection only if specified — platform doesn't always emit them on group
if item.get('order'):
emit_order(lines, item['order'], f'{indent}\t')
if item.get('selection'):
emit_selection(lines, item['selection'], f'{indent}\t')
emit_filter(lines, item.get('filter'), f'{indent}\t')
@@ -1950,6 +1982,12 @@ def emit_structure_item(lines, item, indent):
for child in item['children']:
emit_structure_item(lines, child, f'{indent}\t')
# viewMode/itemsViewMode — emit only when explicitly set (context-dependent)
if item.get('viewMode'):
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
if item.get('itemsViewMode'):
lines.append(f'{indent}\t<dcsset:itemsViewMode>{esc_xml(str(item["itemsViewMode"]))}</dcsset:itemsViewMode>')
lines.append(f'{indent}</dcsset:item>')
elif item_type == 'table':
@@ -2062,23 +2100,30 @@ def emit_settings_variants(lines, defn):
s = v.get('settings', {})
# Helper: resolve XViewMode from settings — emit only if explicitly set
def _block_vm(key):
prop = f'{key}ViewMode'
if prop in s:
return str(s[prop])
return None
# Selection
if s.get('selection'):
emit_selection(lines, s['selection'], '\t\t\t', skip_auto=True)
emit_selection(lines, s['selection'], '\t\t\t', skip_auto=True, block_view_mode=_block_vm('selection'))
# Filter
if s.get('filter'):
emit_filter(lines, s['filter'], '\t\t\t')
emit_filter(lines, s['filter'], '\t\t\t', block_view_mode=_block_vm('filter'))
# Order
if s.get('order'):
emit_order(lines, s['order'], '\t\t\t', skip_auto=True)
emit_order(lines, s['order'], '\t\t\t', skip_auto=True, block_view_mode=_block_vm('order'))
# ConditionalAppearance
if s.get('conditionalAppearance'):
emit_conditional_appearance(lines, s['conditionalAppearance'], '\t\t\t')
emit_conditional_appearance(lines, s['conditionalAppearance'], '\t\t\t', block_view_mode=_block_vm('conditionalAppearance'))
# OutputParameters
# OutputParameters (platform does NOT emit <viewMode> on this block)
if s.get('outputParameters'):
emit_output_parameters(lines, s['outputParameters'], '\t\t\t')
@@ -2135,6 +2180,10 @@ def emit_settings_variants(lines, defn):
for item in struct_items:
emit_structure_item(lines, item, '\t\t\t')
# <dcsset:itemsViewMode> on settings — emit only if explicitly set
if s.get('itemsViewMode'):
lines.append(f'\t\t\t<dcsset:itemsViewMode>{esc_xml(str(s["itemsViewMode"]))}</dcsset:itemsViewMode>')
lines.append('\t\t</dcsset:settings>')
lines.append('\t</settingsVariant>')