feat(skd-decompile): авто-генерация skd-styles.json для custom appearance

Категория C — закрыта для однородных шаблонов с custom appearance.

Refactor fingerprint → preset shape (11 полей: font/fontSize/bold/italic/
hAlign/vAlign/wrap/bgColor/textColor/borderColor/borders). vAlign теперь
учитывается в matching (раньше игнорировался).

Алгоритм:
1. При -OutputPath загружается existing skd-styles.json рядом (если есть);
   user presets накладываются на built-in по той же логике что и compile.
2. Каждая ячейка → Extract-CellPreset → Match-PresetByShape против
   effectivePresets (built-in + user).
3. Если не match — Allocate-CustomStyle: новый customN, регистрируется
   в effectivePresets и accumulator.
4. По окончании Save-UserStyles пишет skd-styles.json рядом с outputPath
   (preserved existing + новые customN).
5. Compile подхватит файл по своим search-путям (cwd/dirname/scan-up).

В SKILL.md не добавляем (custom стили — для round-trip, не для написания
модель с нуля; built-in `data/header/subheader/total/none` остаются
основным интерфейсом для модели).

- runner.mjs: новый preRun step `writeFile` для подготовки fixture-файлов
  в workDir (нужен для теста с предзаписанным skd-styles.json).
- Новый тест template-custom-style: preRun пишет myHeader preset,
  скомпилирует темплейт, decompile reverse'ит → переиспользует имя
  myHeader (не создаёт customN).
- v0.14 → v0.15.

Метрики:
- ERP-сэмпл 30: 24 → 0 sentinel'ов, clean 26 → 30/30
- Целевой корпус 40 отчётов: 39 → 25 sentinel'ов (часть закрыта), clean
  19 → 20/40. Остаточные — шаблоны с разными стилями в разных ячейках
  одного шаблона (нужно per-cell style override — отдельная задача).
This commit is contained in:
Nick Shirokov
2026-05-21 19:32:17 +03:00
parent 4bd8f27dec
commit 3119700c71
6 changed files with 400 additions and 104 deletions
@@ -1,4 +1,4 @@
# skd-decompile v0.14 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.15 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -646,126 +646,203 @@ function Render-Parameter {
return $obj
}
# --- 3b. Built-in style fingerprints ---
# --- 3b. Built-in style presets (preset-shape: 11 полей) ---
# Fingerprints for built-in styles (header/data/subheader/total).
# Each is the set of "shape-defining" appearance items the style emits.
# Width/Height/merge flags are excluded — they're per-cell.
$script:builtinStyleFingerprints = @{
'header' = @{
ЦветФона = 'd8p1:ReportHeaderBackColor'
ЦветГраницы = 'd8p1:ReportLineColor'
СтильГраницы = 'None|0'
'СтильГраницы.Слева' = 'Solid|1'
'СтильГраницы.Сверху' = 'Solid|1'
'СтильГраницы.Справа' = 'Solid|1'
'СтильГраницы.Снизу' = 'Solid|1'
Шрифт = 'Arial|10|false|false|false|false'
ГоризонтальноеПоложение = 'Center'
Размещение = 'Wrap'
# Имена 5 встроенных стилей. Совпадает с compile presets.
$script:builtinPresetNames = @('none','data','header','subheader','total')
# Преобразовать compile-style preset hashtable в наш canonical preset shape.
# Canonical поля: font, fontSize, bold, italic, hAlign, vAlign, wrap, bgColor, textColor, borderColor, borders.
$script:builtinPresets = @{
'none' = @{
font = $null; fontSize = $null; bold = $false; italic = $false
hAlign = $null; vAlign = $null; wrap = $false
bgColor = $null; textColor = $null
borderColor = $null; borders = $false
}
'data' = @{
ЦветФона = 'd8p1:ReportGroup1BackColor'
ЦветГраницы = 'd8p1:ReportLineColor'
СтильГраницы = 'None|0'
'СтильГраницы.Слева' = 'Solid|1'
'СтильГраницы.Сверху' = 'Solid|1'
'СтильГраницы.Справа' = 'Solid|1'
'СтильГраницы.Снизу' = 'Solid|1'
Шрифт = 'Arial|10|false|false|false|false'
font = 'Arial'; fontSize = 10; bold = $false; italic = $false
hAlign = $null; vAlign = $null; wrap = $false
bgColor = 'style:ReportGroup1BackColor'; textColor = $null
borderColor = 'style:ReportLineColor'; borders = $true
}
'header' = @{
font = 'Arial'; fontSize = 10; bold = $false; italic = $false
hAlign = 'Center'; vAlign = $null; wrap = $true
bgColor = 'style:ReportHeaderBackColor'; textColor = $null
borderColor = 'style:ReportLineColor'; borders = $true
}
'subheader' = @{
ЦветГраницы = 'd8p1:ReportLineColor'
СтильГраницы = 'None|0'
'СтильГраницы.Слева' = 'Solid|1'
'СтильГраницы.Сверху' = 'Solid|1'
'СтильГраницы.Справа' = 'Solid|1'
'СтильГраницы.Снизу' = 'Solid|1'
Шрифт = 'Arial|10|false|false|false|false'
ГоризонтальноеПоложение = 'Center'
font = 'Arial'; fontSize = 10; bold = $false; italic = $false
hAlign = 'Center'; vAlign = $null; wrap = $true
bgColor = $null; textColor = $null
borderColor = 'style:ReportLineColor'; borders = $true
}
'total' = @{
ЦветГраницы = 'd8p1:ReportLineColor'
СтильГраницы = 'None|0'
'СтильГраницы.Слева' = 'Solid|1'
'СтильГраницы.Сверху' = 'Solid|1'
'СтильГраницы.Справа' = 'Solid|1'
'СтильГраницы.Снизу' = 'Solid|1'
Шрифт = 'Arial|10|false|false|false|false'
font = 'Arial'; fontSize = 10; bold = $false; italic = $false
hAlign = $null; vAlign = $null; wrap = $false
bgColor = $null; textColor = $null
borderColor = 'style:ReportLineColor'; borders = $true
}
}
# Normalize an <dcsat:appearance> node to a hashtable of "shape" keys (excluding
# per-cell items: widths, heights, merge flags, drilldown). Used for style matching.
function Get-AppearanceFingerprint {
# effectivePresets = built-in + любые user-переопределения, загруженные из skd-styles.json
$script:effectivePresets = @{}
foreach ($k in $script:builtinPresets.Keys) {
$copy = @{}
foreach ($f in $script:builtinPresets[$k].Keys) { $copy[$f] = $script:builtinPresets[$k][$f] }
$script:effectivePresets[$k] = $copy
}
# existingUserPresetsRaw — копия загруженного skd-styles.json (PSCustomObject) для merge при записи.
$script:existingUserPresetsRaw = $null
# customStylesAccumulator — новые customN, накопленные в текущем прогоне, для записи в skd-styles.json.
$script:customStylesAccumulator = [ordered]@{}
# Счётчик customN
$script:customStyleCounter = 0
# Normalize color value: 'd8p1:ReportHeaderBackColor' → 'style:ReportHeaderBackColor'
function Normalize-Color {
param($valNode)
if (-not $valNode) { return $null }
$txt = $valNode.InnerText
if ($txt -match '^d\d+p\d+:(.+)$') { return 'style:' + $matches[1] }
return $txt
}
# Build preset hashtable (11 полей) из <dcsat:appearance>.
# Возвращает $null если у ячейки нет ни одного стилевого атрибута (только per-cell).
function Extract-CellPreset {
param($appNode)
$fp = @{}
if (-not $appNode) { return $fp }
# Top-level dcscor:item children
if (-not $appNode) { return $null }
$preset = @{
font = $null; fontSize = $null; bold = $false; italic = $false
hAlign = $null; vAlign = $null; wrap = $false
bgColor = $null; textColor = $null
borderColor = $null; borders = $false
}
$hasAnyStyle = $false
foreach ($it in $appNode.SelectNodes("dcscor:item", $ns)) {
$pName = Get-Text $it "dcscor:parameter"
$val = $it.SelectSingleNode("dcscor:value", $ns)
if (-not $pName -or -not $val) { continue }
# Skip per-cell keys
if (-not $pName) { continue }
if ($pName -in @('МинимальнаяШирина','МаксимальнаяШирина','МинимальнаяВысота','ОбъединятьПоВертикали','ОбъединятьПоГоризонтали','Расшифровка')) { continue }
$valType = Get-LocalXsiType $val
switch ($valType) {
'Color' { $fp[$pName] = $val.InnerText }
'Line' {
$w = $val.GetAttribute("width")
$styleNode = $val.SelectSingleNode("v8ui:style", $ns)
$lineStyle = if ($styleNode) { $styleNode.InnerText } else { '' }
$fp[$pName] = "$lineStyle|$w"
switch ($pName) {
'Шрифт' {
if ($val) {
$preset.font = $val.GetAttribute("faceName")
$h = $val.GetAttribute("height")
if ($h) { $preset.fontSize = [int]$h }
$preset.bold = ($val.GetAttribute("bold") -eq 'true')
$preset.italic = ($val.GetAttribute("italic") -eq 'true')
$hasAnyStyle = $true
}
}
'Font' {
$face = $val.GetAttribute("faceName")
$h = $val.GetAttribute("height")
$b = $val.GetAttribute("bold")
$i = $val.GetAttribute("italic")
$u = $val.GetAttribute("underline")
$s = $val.GetAttribute("strikeout")
$fp[$pName] = "$face|$h|$b|$i|$u|$s"
}
default { $fp[$pName] = $val.InnerText }
}
# Nested sub-items under СтильГраницы (left/top/right/bottom)
foreach ($sub in $it.SelectNodes("dcscor:item", $ns)) {
$subName = Get-Text $sub "dcscor:parameter"
$subVal = $sub.SelectSingleNode("dcscor:value", $ns)
if (-not $subName -or -not $subVal) { continue }
$subType = Get-LocalXsiType $subVal
if ($subType -eq 'Line') {
$w = $subVal.GetAttribute("width")
$styleNode = $subVal.SelectSingleNode("v8ui:style", $ns)
$lineStyle = if ($styleNode) { $styleNode.InnerText } else { '' }
$fp[$subName] = "$lineStyle|$w"
'ЦветФона' { if ($val) { $preset.bgColor = Normalize-Color $val; $hasAnyStyle = $true } }
'ЦветТекста' { if ($val) { $preset.textColor = Normalize-Color $val; $hasAnyStyle = $true } }
'ЦветГраницы' { if ($val) { $preset.borderColor = Normalize-Color $val; $hasAnyStyle = $true } }
'СтильГраницы' {
# borders = true если есть sub-items для 4 сторон со style=Solid
$sidesFound = 0
foreach ($sub in $it.SelectNodes("dcscor:item", $ns)) {
$subName = Get-Text $sub "dcscor:parameter"
if ($subName -match '^СтильГраницы\.(Слева|Сверху|Справа|Снизу)$') { $sidesFound++ }
}
if ($sidesFound -gt 0) { $preset.borders = $true; $hasAnyStyle = $true }
}
'ГоризонтальноеПоложение' { if ($val) { $preset.hAlign = $val.InnerText; $hasAnyStyle = $true } }
'ВертикальноеПоложение' { if ($val) { $preset.vAlign = $val.InnerText; $hasAnyStyle = $true } }
'Размещение' { if ($val -and $val.InnerText -eq 'Wrap') { $preset.wrap = $true; $hasAnyStyle = $true } }
}
}
# Translate d8p1: colors to canonical "d8p1:Name" form (InnerText already contains prefix)
return $fp
if (-not $hasAnyStyle) { return $null }
return $preset
}
# Check if appearance fingerprint matches a built-in style.
# Returns style name or $null.
function Match-BuiltinStyle {
param($fp)
foreach ($styleName in $script:builtinStyleFingerprints.Keys) {
$expected = $script:builtinStyleFingerprints[$styleName]
$match = $true
# All expected keys must be present with matching value
foreach ($k in $expected.Keys) {
if (-not $fp.ContainsKey($k) -or $fp[$k] -ne $expected[$k]) { $match = $false; break }
}
if (-not $match) { continue }
# Must not have extra keys that aren't in the expected fingerprint
$extras = @($fp.Keys | Where-Object { -not $expected.ContainsKey($_) })
if ($extras.Count -ne 0) { continue }
return $styleName
# Deep-equality двух preset hashtables (11 полей).
function Compare-Preset {
param($a, $b)
foreach ($key in @('font','fontSize','bold','italic','hAlign','vAlign','wrap','bgColor','textColor','borderColor','borders')) {
if ($a[$key] -ne $b[$key]) { return $false }
}
return $true
}
# Найти имя preset'а в effectivePresets по shape. Возвращает имя или $null.
function Match-PresetByShape {
param($cellPreset)
if (-not $cellPreset) { return $null }
foreach ($name in $script:effectivePresets.Keys) {
if (Compare-Preset $cellPreset $script:effectivePresets[$name]) { return $name }
}
return $null
}
# Аллокация customN для нового, не-matched preset'а. Регистрирует в effectivePresets+accumulator.
function Allocate-CustomStyle {
param($cellPreset)
# Поиск свободного customN
$script:customStyleCounter++
$name = "custom$($script:customStyleCounter)"
while ($script:effectivePresets.ContainsKey($name)) {
$script:customStyleCounter++
$name = "custom$($script:customStyleCounter)"
}
$script:effectivePresets[$name] = $cellPreset
$script:customStylesAccumulator[$name] = $cellPreset
return $name
}
# Загрузка skd-styles.json рядом с outputPath (если есть) и наслоение на effectivePresets.
function Load-UserStyles {
param([string]$dirPath)
if (-not $dirPath) { return }
$stylesPath = Join-Path $dirPath 'skd-styles.json'
if (-not (Test-Path $stylesPath)) { return }
$raw = Get-Content -Raw -Encoding UTF8 $stylesPath | ConvertFrom-Json
$script:existingUserPresetsRaw = $raw
foreach ($prop in $raw.PSObject.Properties) {
# Compile-логика: data defaults → built-in if name match → user keys
$preset = @{}
foreach ($k in $script:builtinPresets['data'].Keys) { $preset[$k] = $script:builtinPresets['data'][$k] }
if ($script:builtinPresets.ContainsKey($prop.Name)) {
foreach ($k in $script:builtinPresets[$prop.Name].Keys) { $preset[$k] = $script:builtinPresets[$prop.Name][$k] }
}
foreach ($up in $prop.Value.PSObject.Properties) {
$preset[$up.Name] = $up.Value
}
$script:effectivePresets[$prop.Name] = $preset
}
}
# Запись skd-styles.json: preserved existing user presets + новые customN.
function Save-UserStyles {
param([string]$dirPath)
if (-not $dirPath) { return }
if ($script:customStylesAccumulator.Count -eq 0 -and -not $script:existingUserPresetsRaw) { return }
$stylesPath = Join-Path $dirPath 'skd-styles.json'
$out = [ordered]@{}
# Сначала existing (preserve порядок и значения)
if ($script:existingUserPresetsRaw) {
foreach ($prop in $script:existingUserPresetsRaw.PSObject.Properties) {
$out[$prop.Name] = $prop.Value
}
}
# Потом новые customN
foreach ($name in $script:customStylesAccumulator.Keys) {
if ($out.Contains($name)) { continue }
$out[$name] = $script:customStylesAccumulator[$name]
}
if ($out.Count -eq 0) { return }
$json = $out | ConvertTo-Json -Depth 8
$json = [regex]::Replace($json, '\\u([0-9a-fA-F]{4})', { param($m) [char][int]("0x" + $m.Groups[1].Value) })
$enc = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($stylesPath, $json, $enc)
[Console]::Error.WriteLine("Saved skd-styles.json (custom styles: $($script:customStylesAccumulator.Count))")
}
# Extract per-cell width/minHeight/merge from appearance.
function Get-CellPerCellAttrs {
param($appNode)
@@ -875,18 +952,21 @@ function Build-Template {
# Style detection (skip empty cells with no appearance, and merge cells)
if ($appNode -and -not $perCell.mergeV -and -not $perCell.mergeH) {
$fp = Get-AppearanceFingerprint $appNode
if ($fp.Count -gt 0) {
# Ячейка имеет стилевые атрибуты — пробуем match с built-in
$cellPreset = Extract-CellPreset $appNode
if ($null -ne $cellPreset) {
# Ячейка имеет стилевые атрибуты — match против effectivePresets, иначе аллоцируем custom
$hasAnyNonEmptyFp = $true
$matched = Match-BuiltinStyle $fp
$matched = Match-PresetByShape $cellPreset
if ($null -eq $matched) {
$matched = Allocate-CustomStyle $cellPreset
}
if ($null -eq $detectedStyle) {
$detectedStyle = $matched
} elseif ($matched -ne $detectedStyle) {
$styleMismatch = $true
}
}
# Пустой fp (только per-cell width/merge) — ячейка без стиля, не контрибутирует.
# Если cellPreset = $null — ячейка без стилевых атрибутов (только per-cell width/merge), не контрибутирует.
}
# Drilldown attachment
@@ -1448,6 +1528,16 @@ function Try-StructureShorthand {
# --- 4. dataSources ---
# Резолв outputPath и загрузка user-стилей до обработки шаблонов
$script:outputDir = $null
if ($OutputPath) {
if (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
$OutputPath = Join-Path (Get-Location).Path $OutputPath
}
$script:outputDir = [System.IO.Path]::GetDirectoryName($OutputPath)
Load-UserStyles -dirPath $script:outputDir
}
$dataSources = @()
$dsourceNodes = $root.SelectNodes("r:dataSource", $ns)
foreach ($dsn in $dsourceNodes) {
@@ -1716,11 +1806,9 @@ $json = [regex]::Replace($json, '\\u([0-9a-fA-F]{4})', {
})
if ($OutputPath) {
if (-not [System.IO.Path]::IsPathRooted($OutputPath)) {
$OutputPath = Join-Path (Get-Location).Path $OutputPath
}
$enc = New-Object System.Text.UTF8Encoding($false)
[System.IO.File]::WriteAllText($OutputPath, $json, $enc)
Save-UserStyles -dirPath $script:outputDir
if ($script:warnings.Count -gt 0) {
$wPath = [System.IO.Path]::ChangeExtension($OutputPath, $null).TrimEnd('.') + '.warnings.md'
@@ -0,0 +1,120 @@
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
<dataSet xsi:type="DataSetQuery">
<name>Тест</name>
<field xsi:type="DataSetFieldField">
<dataPath>Поле</dataPath>
<field>Поле</field>
<valueType>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</valueType>
</field>
<dataSource>ИсточникДанных1</dataSource>
<query>ВЫБРАТЬ * ИЗ Справочник.Сотрудники</query>
</dataSet>
<template>
<name>Заголовок</name>
<template xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template" xsi:type="dcsat:AreaTemplate">
<dcsat:item xsi:type="dcsat:TableRow">
<dcsat:tableCell>
<dcsat:item xsi:type="dcsat:Field">
<dcsat:value xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>A</v8:content>
</v8:item>
</dcsat:value>
</dcsat:item>
<dcsat:appearance>
<dcscor:item>
<dcscor:parameter>ЦветФона</dcscor:parameter>
<dcscor:value xmlns:d8p1="http://v8.1c.ru/8.1/data/ui/style" xsi:type="v8ui:Color">d8p1:ReportHeaderBackColor</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>ЦветГраницы</dcscor:parameter>
<dcscor:value xmlns:d8p1="http://v8.1c.ru/8.1/data/ui/style" xsi:type="v8ui:Color">d8p1:ReportLineColor</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>СтильГраницы</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Line" width="0" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">None</v8ui:style>
</dcscor:value>
<dcscor:item>
<dcscor:parameter>СтильГраницы.Слева</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Line" width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>СтильГраницы.Сверху</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Line" width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>СтильГраницы.Справа</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Line" width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>СтильГраницы.Снизу</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Line" width="1" gap="false">
<v8ui:style xsi:type="v8ui:SpreadsheetDocumentCellLineType">Solid</v8ui:style>
</dcscor:value>
</dcscor:item>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>Шрифт</dcscor:parameter>
<dcscor:value xsi:type="v8ui:Font" faceName="Calibri" height="11" bold="false" italic="false" underline="false" strikeout="false" kind="Absolute" scale="100"/>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>ГоризонтальноеПоложение</dcscor:parameter>
<dcscor:value xsi:type="v8ui:HorizontalAlign">Center</dcscor:value>
</dcscor:item>
<dcscor:item>
<dcscor:parameter>ВертикальноеПоложение</dcscor:parameter>
<dcscor:value xsi:type="v8ui:VerticalAlign">Center</dcscor:value>
</dcscor:item>
</dcsat:appearance>
</dcsat:tableCell>
</dcsat:item>
</template>
</template>
<settingsVariant>
<dcsset:name>Основной</dcsset:name>
<dcsset:presentation xsi:type="v8:LocalStringType">
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основной</v8:content>
</v8:item>
</dcsset:presentation>
<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">
<dcsset:selection>
</dcsset:selection>
<dcsset:item xsi:type="dcsset:StructureItemGroup">
<dcsset:order>
<dcsset:item xsi:type="dcsset:OrderItemAuto"/>
</dcsset:order>
<dcsset:selection>
<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>
</dcsset:selection>
</dcsset:item>
</dcsset:settings>
</settingsVariant>
</DataCompositionSchema>
@@ -0,0 +1,22 @@
{
"dataSets": [
{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": [
"Поле: string"
]
}
],
"templates": [
{
"name": "Заголовок",
"style": "myHeader",
"rows": [
[
"A"
]
]
}
]
}
@@ -0,0 +1,15 @@
{
"myHeader": {
"font": "Calibri",
"fontSize": 11,
"bold": false,
"italic": false,
"hAlign": "Center",
"vAlign": "Center",
"wrap": false,
"bgColor": "style:ReportHeaderBackColor",
"textColor": null,
"borderColor": "style:ReportLineColor",
"borders": true
}
}
@@ -0,0 +1,42 @@
{
"name": "Шаблон с custom стилем (Calibri 11) — переиспользование user preset из skd-styles.json",
"preRun": [
{
"writeFile": {
"path": "skd-styles.json",
"content": {
"myHeader": {
"font": "Calibri",
"fontSize": 11,
"bold": false,
"italic": false,
"hAlign": "Center",
"vAlign": "Center",
"wrap": false,
"bgColor": "style:ReportHeaderBackColor",
"textColor": null,
"borderColor": "style:ReportLineColor",
"borders": true
}
}
}
},
{
"script": "skd-compile/scripts/skd-compile",
"input": {
"dataSets": [{
"name": "Тест",
"query": "ВЫБРАТЬ * ИЗ Справочник.Сотрудники",
"fields": ["Поле: string"]
}],
"templates": [
{ "name": "Заголовок", "style": "myHeader", "rows": [["A"]] }
]
},
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
"cwd": "{workDir}"
}
],
"params": { "templatePath": "Template.xml" },
"outputPath": "decompiled.json"
}
+9
View File
@@ -517,6 +517,15 @@ async function runCaseAsync(testCase, opts) {
// Pre-run steps
if (caseData.preRun) {
for (const step of caseData.preRun) {
// writeFile step — записать произвольный файл в workDir перед запуском скрипта
if (step.writeFile) {
const wfPath = join(workDir, step.writeFile.path);
const wfContent = typeof step.writeFile.content === 'string'
? step.writeFile.content
: JSON.stringify(step.writeFile.content, null, 2);
writeFileSync(wfPath, wfContent, 'utf8');
continue;
}
const preScript = resolveScript(step.script, opts.runtime);
const preArgs = [];
for (const [flag, value] of Object.entries(step.args || {})) {