fix(skd): откат implicit viewMode=Normal — сохраняем точное присутствие

Реальные отчёты непоследовательны: одни filter/item имеют
<viewMode>Normal</viewMode> с userSettingID, другие — нет (зависит от
момента редактирования через UI). Стратегия "compile добавляет implicit
Normal когда есть userSettingID" даёт ложные ADDED строки в bit-perfect.

Меняю на корректную модель:
- decompile сохраняет viewMode даже = 'Normal' если node физически
  присутствует в XML (object form переходит автоматически)
- compile эмитит viewMode только если явно задан в JSON

Применено к: filter (item + group), dataParameters, conditionalAppearance,
selection items, order items.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-05-22 18:01:09 +03:00
parent 9aac032ac8
commit 38b5445f15
8 changed files with 44 additions and 71 deletions
@@ -1,4 +1,4 @@
# skd-compile v1.44 — Compile 1C DCS from JSON
# skd-compile v1.45 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -1957,10 +1957,8 @@ function Emit-FilterItem {
if ($item.presentation) {
Emit-MLText -tag "dcsset:presentation" -text $item.presentation -indent "$indent`t"
}
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if ($item.viewMode -or $item.userSettingID) {
$gvm = if ($item.viewMode) { "$($item.viewMode)" } else { 'Normal' }
X "$indent`t<dcsset:viewMode>$(Esc-Xml $gvm)</dcsset:viewMode>"
if ($item.viewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>"
}
if ($item.userSettingID) {
$guid = if ("$($item.userSettingID)" -eq "auto") { New-Guid-String } else { "$($item.userSettingID)" }
@@ -2009,10 +2007,9 @@ function Emit-FilterItem {
Emit-MLText -tag "dcsset:presentation" -text $item.presentation -indent "$indent`t"
}
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if ($item.viewMode -or $item.userSettingID) {
$vm = if ($item.viewMode) { "$($item.viewMode)" } else { 'Normal' }
X "$indent`t<dcsset:viewMode>$(Esc-Xml $vm)</dcsset:viewMode>"
# viewMode эмитим только если явно задан — присутствие в XML контекстно
if ($item.viewMode) {
X "$indent`t<dcsset:viewMode>$(Esc-Xml "$($item.viewMode)")</dcsset:viewMode>"
}
if ($item.userSettingID) {
@@ -2191,10 +2188,8 @@ function Emit-ConditionalAppearance {
X "$indent`t`t<dcsset:presentation xsi:type=`"xs:string`">$(Esc-Xml "$($ca.presentation)")</dcsset:presentation>"
}
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if ($ca.viewMode -or $ca.userSettingID) {
$cvm = if ($ca.viewMode) { "$($ca.viewMode)" } else { 'Normal' }
X "$indent`t`t<dcsset:viewMode>$(Esc-Xml $cvm)</dcsset:viewMode>"
if ($ca.viewMode) {
X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($ca.viewMode)")</dcsset:viewMode>"
}
# UserSettingID
@@ -2314,10 +2309,8 @@ function Emit-DataParameters {
}
}
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if ($dp.viewMode -or $dp.userSettingID) {
$dvm = if ($dp.viewMode) { "$($dp.viewMode)" } else { 'Normal' }
X "$indent`t`t<dcsset:viewMode>$(Esc-Xml $dvm)</dcsset:viewMode>"
if ($dp.viewMode) {
X "$indent`t`t<dcsset:viewMode>$(Esc-Xml "$($dp.viewMode)")</dcsset:viewMode>"
}
if ($dp.userSettingID) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-compile v1.44 — Compile 1C DCS from JSON
# skd-compile v1.45 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -1628,10 +1628,8 @@ def emit_filter_item(lines, item, indent):
emit_filter_item(lines, sub, f'{indent}\t')
if item.get('presentation'):
emit_mltext(lines, f'{indent}\t', 'dcsset:presentation', item['presentation'])
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if item.get('viewMode') or item.get('userSettingID'):
gvm = str(item['viewMode']) if item.get('viewMode') else 'Normal'
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(gvm)}</dcsset:viewMode>')
if item.get('viewMode'):
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
if item.get('userSettingID'):
guid = new_uuid() if str(item['userSettingID']) == 'auto' else str(item['userSettingID'])
lines.append(f'{indent}\t<dcsset:userSettingID>{esc_xml(guid)}</dcsset:userSettingID>')
@@ -1673,10 +1671,8 @@ def emit_filter_item(lines, item, indent):
if item.get('presentation'):
emit_mltext(lines, f'{indent}\t', 'dcsset:presentation', item["presentation"])
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if item.get('viewMode') or item.get('userSettingID'):
vm = str(item['viewMode']) if item.get('viewMode') else 'Normal'
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(vm)}</dcsset:viewMode>')
if item.get('viewMode'):
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
if item.get('userSettingID'):
uid = new_uuid() if str(item['userSettingID']) == 'auto' else str(item['userSettingID'])
@@ -1827,10 +1823,8 @@ def emit_conditional_appearance(lines, items, indent, block_view_mode=None):
if ca.get('presentation'):
lines.append(f'{indent}\t\t<dcsset:presentation xsi:type="xs:string">{esc_xml(str(ca["presentation"]))}</dcsset:presentation>')
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if ca.get('viewMode') or ca.get('userSettingID'):
cvm = str(ca['viewMode']) if ca.get('viewMode') else 'Normal'
lines.append(f'{indent}\t\t<dcsset:viewMode>{esc_xml(cvm)}</dcsset:viewMode>')
if ca.get('viewMode'):
lines.append(f'{indent}\t\t<dcsset:viewMode>{esc_xml(str(ca["viewMode"]))}</dcsset:viewMode>')
# UserSettingID
if ca.get('userSettingID'):
@@ -1921,10 +1915,8 @@ def emit_data_parameters(lines, items, indent):
else:
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(val))}</dcscor:value>')
# Platform always emits viewMode when userSettingID is present (implicit Normal).
if dp.get('viewMode') or dp.get('userSettingID'):
dvm = str(dp['viewMode']) if dp.get('viewMode') else 'Normal'
lines.append(f'{indent}\t\t<dcsset:viewMode>{esc_xml(dvm)}</dcsset:viewMode>')
if dp.get('viewMode'):
lines.append(f'{indent}\t\t<dcsset:viewMode>{esc_xml(str(dp["viewMode"]))}</dcsset:viewMode>')
if dp.get('userSettingID'):
uid = new_uuid() if str(dp['userSettingID']) == 'auto' else str(dp['userSettingID'])
@@ -1,4 +1,4 @@
# skd-decompile v0.29 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.30 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1297,9 +1297,9 @@ function Build-FilterItem {
$gObj = [ordered]@{ group = $groupName; items = $items }
$gPres = Get-Text $itemNode "dcsset:presentation"
if ($gPres) { $gObj['presentation'] = $gPres }
$gVM = Get-Text $itemNode "dcsset:viewMode"
# Normal — implicit when userSettingID present; skip unless non-default
if ($gVM -and $gVM -ne 'Normal') { $gObj['viewMode'] = $gVM }
# viewMode: сохраняем даже Normal если node присутствует (для bit-perfect)
$gVMNode = $itemNode.SelectSingleNode("dcsset:viewMode", $ns)
if ($gVMNode) { $gObj['viewMode'] = $gVMNode.InnerText }
$gUSID = Get-Text $itemNode "dcsset:userSettingID"
if ($gUSID) { $gObj['userSettingID'] = 'auto' }
$gUSPN = $itemNode.SelectSingleNode("dcsset:userSettingPresentation", $ns)
@@ -1322,7 +1322,9 @@ function Build-FilterItem {
$use = Get-Text $itemNode "dcsset:use"
$userId = Get-Text $itemNode "dcsset:userSettingID"
$viewMode = Get-Text $itemNode "dcsset:viewMode"
# viewMode: detect presence (даже = 'Normal') чтобы compile сделал bit-perfect
$vmNode = $itemNode.SelectSingleNode("dcsset:viewMode", $ns)
$viewMode = if ($vmNode) { $vmNode.InnerText } else { $null }
$userPresNode = $itemNode.SelectSingleNode("dcsset:userSettingPresentation", $ns)
$flags = @()
@@ -1330,19 +1332,20 @@ function Build-FilterItem {
if ($userId) { $flags += '@user' }
if ($viewMode -eq 'QuickAccess') { $flags += '@quickAccess' }
elseif ($viewMode -eq 'Inaccessible') { $flags += '@inaccessible' }
# Normal is the default — do not emit @normal
# Normal сохраняется только если node присутствовал — переходит в object form
# nullity ops have no value
$noValueOps = @('filled','notFilled')
if ($userPresNode) {
# object form
# Переход в object form: userSettingPresentation ИЛИ явный viewMode=Normal
# (Normal не выразим в shorthand, а отсутствие тоже нужно сохранить)
if ($userPresNode -or $viewMode -eq 'Normal') {
$obj = [ordered]@{ field = $field; op = $op }
if ($op -notin $noValueOps -and $null -ne $value) { $obj['value'] = $value }
if ($use -eq 'false') { $obj['use'] = $false }
if ($userId) { $obj['userSettingID'] = 'auto' }
if ($viewMode -and $viewMode -ne 'Normal') { $obj['viewMode'] = $viewMode }
$obj['userSettingPresentation'] = Get-MLText $userPresNode
if ($viewMode) { $obj['viewMode'] = $viewMode }
if ($userPresNode) { $obj['userSettingPresentation'] = Get-MLText $userPresNode }
return $obj
}
@@ -1376,12 +1379,11 @@ function Build-SelectionItem {
$fName = Get-Text $item "dcsset:field"
$titleNode = $item.SelectSingleNode("dcsset:lwsTitle", $ns)
$title = Get-MLText $titleNode
$vm = Get-Text $item "dcsset:viewMode"
$hasVM = $vm -and $vm -ne 'Normal'
if ($title -or $hasVM) {
$vmN = $item.SelectSingleNode("dcsset:viewMode", $ns)
if ($title -or $vmN) {
$obj = [ordered]@{ field = $fName }
if ($title) { $obj['title'] = $title }
if ($hasVM) { $obj['viewMode'] = $vm }
if ($vmN) { $obj['viewMode'] = $vmN.InnerText }
return $obj
}
return $fName
@@ -1428,11 +1430,11 @@ function Build-Order {
'OrderItemField' {
$fn = Get-Text $it "dcsset:field"
$ot = Get-Text $it "dcsset:orderType"
$vm = Get-Text $it "dcsset:viewMode"
if ($vm -and $vm -ne 'Normal') {
$vmN = $it.SelectSingleNode("dcsset:viewMode", $ns)
if ($vmN) {
$obj = [ordered]@{ field = $fn }
if ($ot -eq 'Desc') { $obj['direction'] = 'desc' }
$obj['viewMode'] = $vm
$obj['viewMode'] = $vmN.InnerText
$out += $obj
} else {
if ($ot -eq 'Desc') { $out += "$fn desc" } else { $out += $fn }
@@ -1493,8 +1495,8 @@ function Build-ConditionalAppearance {
if ($ap -and $ap.Count -gt 0) { $entry['appearance'] = $ap }
$pres = Get-Text $it "dcsset:presentation"
if ($pres) { $entry['presentation'] = $pres }
$vm = Get-Text $it "dcsset:viewMode"
if ($vm -and $vm -ne 'Normal') { $entry['viewMode'] = $vm }
$vmN = $it.SelectSingleNode("dcsset:viewMode", $ns)
if ($vmN) { $entry['viewMode'] = $vmN.InnerText }
$usid = Get-Text $it "dcsset:userSettingID"
if ($usid) { $entry['userSettingID'] = 'auto' }
$out += $entry
@@ -1640,8 +1642,9 @@ function Build-TableAxisBlock {
$op = Build-OutputParameters -opNode $opNode
if ($op -and $op.Count -gt 0) { $entry['outputParameters'] = $op }
# user-settings on the axis itself
$avm = Get-Text $node "dcsset:viewMode"
if ($avm -and $avm -ne 'Normal') { $entry['viewMode'] = $avm }
# viewMode: сохраняем даже Normal если node присутствует
$avmNode = $node.SelectSingleNode("dcsset:viewMode", $ns)
if ($avmNode) { $entry['viewMode'] = $avmNode.InnerText }
$ausid = Get-Text $node "dcsset:userSettingID"
if ($ausid) { $entry['userSettingID'] = 'auto' }
$ausPresNode = $node.SelectSingleNode("dcsset:userSettingPresentation", $ns)
@@ -165,7 +165,6 @@
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-001</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
@@ -176,44 +175,37 @@
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-002</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Флаг</dcscor:parameter>
<dcscor:value xsi:type="xs:boolean">true</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-003</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Сумма</dcscor:parameter>
<dcscor:value xsi:type="xs:decimal">0</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-004</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Ставка</dcscor:parameter>
<dcscor:value xsi:type="xs:decimal">13.5</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-005</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Метка</dcscor:parameter>
<dcscor:value xsi:type="xs:string">ТестовоеЗначение</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-006</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:use>false</dcscor:use>
<dcscor:parameter>ПустаяСтрока</dcscor:parameter>
<dcscor:value xsi:nil="true"/>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-007</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Валюта</dcscor:parameter>
<dcscor:value xsi:type="dcscor:DesignTimeValue">Справочник.Валюты.EmptyRef</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-008</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
@@ -132,7 +132,6 @@
<dcsset:use>false</dcsset:use>
<dcsset:left xsi:type="dcscor:Field">Организация</dcsset:left>
<dcsset:comparisonType>Equal</dcsset:comparisonType>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-001</dcsset:userSettingID>
</dcsset:item>
</dcsset:filter>
@@ -144,7 +143,6 @@
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-002</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
@@ -130,7 +130,6 @@
<dcsset:use>false</dcsset:use>
<dcsset:left xsi:type="dcscor:Field">Организация</dcsset:left>
<dcsset:comparisonType>Equal</dcsset:comparisonType>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-001</dcsset:userSettingID>
</dcsset:item>
<dcsset:item xsi:type="dcsset:FilterItemComparison">
@@ -151,7 +150,6 @@
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-002</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
@@ -297,7 +297,6 @@
<dcsset:use>false</dcsset:use>
<dcsset:left xsi:type="dcscor:Field">Организация</dcsset:left>
<dcsset:comparisonType>Equal</dcsset:comparisonType>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-001</dcsset:userSettingID>
</dcsset:item>
<dcsset:item xsi:type="dcsset:FilterItemGroup">
@@ -360,13 +359,11 @@
<v8:startDate>0001-01-01T00:00:00</v8:startDate>
<v8:endDate>0001-01-01T00:00:00</v8:endDate>
</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-003</dcsset:userSettingID>
</dcscor:item>
<dcscor:item xsi:type="dcsset:SettingsParameterValue">
<dcscor:parameter>Активные</dcscor:parameter>
<dcscor:value xsi:type="xs:boolean">true</dcscor:value>
<dcsset:viewMode>Normal</dcsset:viewMode>
<dcsset:userSettingID>UUID-004</dcsset:userSettingID>
</dcscor:item>
</dcsset:dataParameters>
@@ -11,7 +11,7 @@
"selection": ["Организация", { "folder": "Объёмы", "items": ["Сумма", "Количество"] }],
"filter": ["Организация = _ @off @user", { "group": "Or", "items": ["Статус = Активен", "Сумма > 1000"] }],
"order": ["Сумма desc"],
"conditionalAppearance": [{ "filter": ["Сумма > 10000"], "appearance": { "ЦветТекста": "style:НегативныйТекстЦвет" }, "presentation": "Большие суммы", "userSettingID": "auto" }],
"conditionalAppearance": [{ "filter": ["Сумма > 10000"], "appearance": { "ЦветТекста": "style:НегативныйТекстЦвет" }, "presentation": "Большие суммы", "viewMode": "Normal", "userSettingID": "auto" }],
"outputParameters": { "Заголовок": "Сводка по организациям" },
"dataParameters": "auto",
"structure": "Организация > Номенклатура > details"