feat(skd): v8ui:Line + nested side-styles в appearance

conditionalAppearance может содержать СтильГраницы со сложным value:
<dcscor:value xsi:type="v8ui:Line" width="0" gap="false">
  <v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">None</v8ui:style>
</dcscor:value>
+ nested <dcscor:item> для side-стилей (СтильГраницы.Сверху/.Снизу/.Слева/.Справа),
каждый со своим v8ui:Line value и опц. <dcscor:use>false</dcscor:use>.

Раньше теряли всю структуру и эмитили <value xsi:type="xs:string">None</value>.

DSL form B (выбранный пользователем) — Line как top-level плоский объект:
"СтильГраницы": {
  "@type": "Line", "width": 0, "gap": false, "style": "None",
  "items": {
    "СтильГраницы.Сверху": {
      "value": { "@type": "Line", "width": 1, "gap": false, "style": "Solid" },
      "use": false
    }
  }
}

Nested items — универсальный wrapper {value, use?, items?} (как у outputParameters).
Эмитятся как siblings <dcscor:item> внутри родительского <dcscor:item> (после
закрытия родительского <dcscor:value>).

decompile: Read-AppearanceValueNode распознаёт Line и возвращает inline объект;
Get-SettingsAppearance читает nested dcscor:item children и собирает их в items.
compile (PS+Py): emit_appearance_value расширен — Line ветка + рекурсивный
вызов для items siblings.

Sample30 total: 767 → 729 строк diff (-38).
This commit is contained in:
Nick Shirokov
2026-05-24 18:10:25 +03:00
parent 8cb7309ee5
commit 91ef1d07eb
4 changed files with 159 additions and 24 deletions
@@ -1,4 +1,4 @@
# skd-compile v1.94 — Compile 1C DCS from JSON
# skd-compile v1.95 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -2321,12 +2321,34 @@ function Emit-AppearanceValue {
X "$indent<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
# Распознаём wrapper {use: false, value: ...} (необходимо отличать от multilang dict).
# Helper для проверки property/key на PSCustomObject/IDictionary
function _HasKey { param($o, [string]$k)
if ($o -is [PSCustomObject]) { return [bool]$o.PSObject.Properties[$k] }
if ($o -is [System.Collections.IDictionary]) { return $o.Contains($k) }
return $false
}
function _Get { param($o, [string]$k)
if ($o -is [PSCustomObject]) { return $o.$k }
if ($o -is [System.Collections.IDictionary]) { return $o[$k] }
return $null
}
# Распознаём wrapper {value:..., use?:false, items?:{}}.
# Top-level Line-value хранится плоско ({@type:Line, width, gap, style, use?, items?}) —
# отличаем от wrapper по наличию @type на самом val.
$isTopLevelLine = (_HasKey $val '@type') -and ("$(_Get $val '@type')" -eq 'Line')
$useWrapper = $false
$innerVal = $val
if ($val -is [PSCustomObject] -and $val.PSObject.Properties['use'] -and $val.use -eq $false -and $val.PSObject.Properties['value']) {
$useWrapper = $true
$innerVal = $val.value
$nestedItems = $null
if ($isTopLevelLine) {
# items/use лежат рядом с @type
if ((_HasKey $val 'use') -and ((_Get $val 'use') -eq $false)) { $useWrapper = $true }
if (_HasKey $val 'items') { $nestedItems = (_Get $val 'items') }
} elseif ((_HasKey $val 'value') -and (($val -is [PSCustomObject]) -or ($val -is [System.Collections.IDictionary]))) {
# Обычный wrapper {value, use?, items?}
$innerVal = (_Get $val 'value')
if ((_HasKey $val 'use') -and ((_Get $val 'use') -eq $false)) { $useWrapper = $true }
if (_HasKey $val 'items') { $nestedItems = (_Get $val 'items') }
}
if ($useWrapper) { X "$indent`t<dcscor:use>false</dcscor:use>" }
@@ -2340,8 +2362,18 @@ function Emit-AppearanceValue {
} elseif ($innerVal -is [System.Collections.IDictionary]) {
if ($innerVal.Contains('@type') -and "$($innerVal['@type'])" -eq 'Font') { $isFontDict = $true }
}
# Line dict ({@type: "Line", width, gap, style}) → <dcscor:value xsi:type="v8ui:Line" ...><v8ui:style>...
$isLineDict = $false
if (_HasKey $innerVal '@type') { $isLineDict = ("$(_Get $innerVal '@type')" -eq 'Line') }
$isDict = ($innerVal -is [hashtable]) -or ($innerVal -is [System.Collections.IDictionary]) -or ($innerVal -is [PSCustomObject])
if ($isFontDict) {
if ($isLineDict) {
$lw = if (_HasKey $innerVal 'width') { _Get $innerVal 'width' } else { 0 }
$lg = if (_HasKey $innerVal 'gap') { if ((_Get $innerVal 'gap')) { 'true' } else { 'false' } } else { 'false' }
$ls = if (_HasKey $innerVal 'style') { "$(_Get $innerVal 'style')" } else { 'None' }
X "$indent`t<dcscor:value xsi:type=`"v8ui:Line`" width=`"$lw`" gap=`"$lg`">"
X "$indent`t`t<v8ui:style xsi:type=`"v8ui:SpreadsheetDocumentCellLineType`">$(Esc-Xml $ls)</v8ui:style>"
X "$indent`t</dcscor:value>"
} elseif ($isFontDict) {
$attrParts = @()
foreach ($attrName in @('ref','faceName','height','bold','italic','underline','strikeout','kind','scale')) {
$av = $null
@@ -2388,6 +2420,20 @@ function Emit-AppearanceValue {
X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $actualVal)</dcscor:value>"
}
}
# Nested SettingsParameterValue items (например СтильГраницы.Сверху/.Снизу/.Слева/.Справа).
# Эмитим как siblings <dcscor:item> внутри родительского <dcscor:item>.
if ($nestedItems) {
$niProps = if ($nestedItems -is [PSCustomObject]) { $nestedItems.PSObject.Properties } else { $null }
if ($niProps) {
foreach ($np in $niProps) {
Emit-AppearanceValue -key $np.Name -val $np.Value -indent "$indent`t"
}
} elseif ($nestedItems -is [System.Collections.IDictionary]) {
foreach ($nk in $nestedItems.Keys) {
Emit-AppearanceValue -key $nk -val $nestedItems[$nk] -indent "$indent`t"
}
}
}
X "$indent</dcscor:item>"
}
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-compile v1.94 — Compile 1C DCS from JSON
# skd-compile v1.95 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -1921,19 +1921,36 @@ def emit_order(lines, items, indent, skip_auto=False, block_view_mode=None, bloc
def emit_appearance_value(lines, key, val, indent):
lines.append(f'{indent}<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
# \u0420\u0430\u0441\u043f\u043e\u0437\u043d\u0430\u0451\u043c wrapper {use: false, value: ...} \u2014 \u043d\u043e \u0442\u043e\u043b\u044c\u043a\u043e \u0435\u0441\u043b\u0438 \u0435\u0441\u0442\u044c \u043e\u0431\u0430 \u043a\u043b\u044e\u0447\u0430.
# Top-level Line \u0445\u0440\u0430\u043d\u0438\u0442\u0441\u044f \u043f\u043b\u043e\u0441\u043a\u043e ({@type: "Line", width, gap, style, use?, items?}).
# \u041e\u0431\u044b\u0447\u043d\u044b\u0439 wrapper: {value, use?, items?}.
is_top_level_line = isinstance(val, dict) and val.get('@type') == 'Line'
use_wrapper = False
inner_val = val
if isinstance(val, dict) and 'use' in val and val['use'] is False and 'value' in val:
use_wrapper = True
nested_items = None
if is_top_level_line:
if val.get('use') is False:
use_wrapper = True
nested_items = val.get('items')
elif isinstance(val, dict) and 'value' in val:
inner_val = val['value']
if val.get('use') is False:
use_wrapper = True
nested_items = val.get('items')
if use_wrapper:
lines.append(f'{indent}\t<dcscor:use>false</dcscor:use>')
lines.append(f'{indent}\t<dcscor:parameter>{esc_xml(key)}</dcscor:parameter>')
# Line dict ({@type: "Line", width, gap, style}) \u2192 <dcscor:value xsi:type="v8ui:Line" ...>
if isinstance(inner_val, dict) and inner_val.get('@type') == 'Line':
lw = inner_val.get('width', 0)
lg = 'true' if inner_val.get('gap') else 'false'
ls = str(inner_val.get('style', 'None'))
lines.append(f'{indent}\t<dcscor:value xsi:type="v8ui:Line" width="{lw}" gap="{lg}">')
lines.append(f'{indent}\t\t<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">{esc_xml(ls)}</v8ui:style>')
lines.append(f'{indent}\t</dcscor:value>')
# Font dict ({@type: "Font", ref, faceName, height, bold, ...}) \u2192 <dcscor:value xsi:type="v8ui:Font" .../>
if isinstance(inner_val, dict) and inner_val.get('@type') == 'Font':
elif isinstance(inner_val, dict) and inner_val.get('@type') == 'Font':
attr_parts = []
for attr_name in ('ref', 'faceName', 'height', 'bold', 'italic', 'underline', 'strikeout', 'kind', 'scale'):
if attr_name in inner_val:
@@ -1967,6 +1984,10 @@ def emit_appearance_value(lines, key, val, indent):
lines.append(f'{indent}\t<dcscor:value xsi:type="v8ui:Color">{esc_xml(actual_val)}</dcscor:value>')
else:
lines.append(f'{indent}\t<dcscor:value xsi:type="xs:string">{esc_xml(actual_val)}</dcscor:value>')
# Nested SettingsParameterValue items (СтильГраницы.Сверху/.Снизу/.Слева/.Справа).
if nested_items and isinstance(nested_items, dict):
for nk, nv in nested_items.items():
emit_appearance_value(lines, nk, nv, f'{indent}\t')
lines.append(f'{indent}</dcscor:item>')