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>')
@@ -1,4 +1,4 @@
# skd-decompile v0.77 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.78 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1723,7 +1723,36 @@ function Build-Order {
return ,$out
}
# Build appearance dict from <dcsset:appearance> or <dcscor:item> list
# Прочитать <dcscor:value xsi:type="v8ui:Line"> в объект {@type:Line, width, gap, style}.
function Get-LineValue {
param($valNode)
$obj = [ordered]@{ '@type' = 'Line' }
$w = $valNode.GetAttribute("width")
$g = $valNode.GetAttribute("gap")
if ($w -ne '') { $obj['width'] = if ($w -match '^-?\d+$') { [int]$w } else { $w } }
if ($g -ne '') { $obj['gap'] = ($g -eq 'true') }
$styleNode = $valNode.SelectSingleNode("v8ui:style", $ns)
if ($styleNode) { $obj['style'] = $styleNode.InnerText }
return $obj
}
# Прочитать <dcscor:value> в JSON-значение: Font/Line/multilang/raw text.
# Возвращает то значение которое идёт в "value" slot.
function Read-AppearanceValueNode {
param($valNode)
if (-not $valNode) { return $null }
$vt = Get-LocalXsiType $valNode
if ($vt -eq 'LocalStringType') { return (Get-MLText $valNode) }
if ($vt -eq 'Font') { return (Get-FontValue $valNode) }
if ($vt -eq 'Line') { return (Get-LineValue $valNode) }
return $valNode.InnerText
}
# Build appearance dict from <dcsset:appearance> or <dcscor:item> list.
# Поддерживает Line-значения (граница) и nested SettingsParameterValue items
# (например СтильГраницы.Сверху). DSL form B (см. docs/skd-dsl-spec.md):
# - top-level Line: { "@type": "Line", "width", "gap", "style", "use"?, "items"? }
# - nested item: { "value": <значение>, "use"?: false }
function Get-SettingsAppearance {
param($appNode)
if (-not $appNode) { return $null }
@@ -1732,18 +1761,32 @@ function Get-SettingsAppearance {
$pName = Get-Text $it "dcscor:parameter"
$val = $it.SelectSingleNode("dcscor:value", $ns)
if (-not $pName -or -not $val) { continue }
$valType = Get-LocalXsiType $val
if ($valType -eq 'LocalStringType') {
$rawVal = Get-MLText $val
} elseif ($valType -eq 'Font') {
$rawVal = Get-FontValue $val
} else {
$rawVal = $val.InnerText
}
# wrapper {use:false, value} как в Get-AppearanceDict
$rawVal = Read-AppearanceValueNode $val
$useV = Get-Text $it "dcscor:use"
if ($useV -eq 'false') {
$dict[$pName] = [ordered]@{ value = $rawVal; use = $false }
# Nested dcscor:item внутри этого item — wrap form {value, use?}.
$nestedItems = [ordered]@{}
foreach ($sub in $it.SelectNodes("dcscor:item", $ns)) {
$subName = Get-Text $sub "dcscor:parameter"
$subVal = $sub.SelectSingleNode("dcscor:value", $ns)
if (-not $subName) { continue }
$subRaw = Read-AppearanceValueNode $subVal
$subUse = Get-Text $sub "dcscor:use"
$subEntry = [ordered]@{ value = $subRaw }
if ($subUse -eq 'false') { $subEntry['use'] = $false }
$nestedItems[$subName] = $subEntry
}
# Определяем форму вывода
$valIsLine = ($rawVal -is [System.Collections.IDictionary]) -and $rawVal.Contains('@type') -and ($rawVal['@type'] -eq 'Line')
if ($valIsLine) {
# top-level Line — атрибуты inline + опц. use/items
if ($useV -eq 'false') { $rawVal['use'] = $false }
if ($nestedItems.Count -gt 0) { $rawVal['items'] = $nestedItems }
$dict[$pName] = $rawVal
} elseif (($useV -eq 'false') -or ($nestedItems.Count -gt 0)) {
$wrap = [ordered]@{ value = $rawVal }
if ($useV -eq 'false') { $wrap['use'] = $false }
if ($nestedItems.Count -gt 0) { $wrap['items'] = $nestedItems }
$dict[$pName] = $wrap
} else {
$dict[$pName] = $rawVal
}
+25
View File
@@ -763,6 +763,31 @@ Wrapper эмитится только при наличии extra-полей; п
```
Все атрибуты исходного XML сохраняются — для bit-perfect.
#### Граница (v8ui:Line) в appearance
Граница — объект с маркером `@type: "Line"` (атрибуты `width`/`gap` и inner `<v8ui:style>` сериализуются inline):
```json
"СтильГраницы": { "@type": "Line", "width": 0, "gap": false, "style": "None" }
```
Стороны (`СтильГраницы.Сверху/.Снизу/.Слева/.Справа`) — nested SettingsParameterValue, кладутся в `items` (как у outputParameters wrapper):
```json
"СтильГраницы": {
"@type": "Line", "width": 0, "gap": false, "style": "None",
"items": {
"СтильГраницы.Сверху": {
"value": { "@type": "Line", "width": 1, "gap": false, "style": "Solid" },
"use": false
},
"СтильГраницы.Снизу": {
"value": { "@type": "Line", "width": 1, "gap": false, "style": "Double" }
}
}
}
```
Top-level Line хранится **плоско** (`@type`/`width`/`gap`/`style` + `use?`/`items?` на одном уровне). Nested items используют универсальный wrapper `{ value, use? }`у `value` тип любой (Line/Font/color/text). Значения `style`: `None`, `Solid`, `Double`, `LargeDashed`, `SmallDashed`, `Dotted` и т.п. (значения `v8ui:SpreadsheetDocumentCellLineType`).
### dataParameters
#### Автогенерация