From 42df4cd6b15822e3608b22ed4e2f9e88aaa883b2 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Mon, 23 Mar 2026 20:38:04 +0300 Subject: [PATCH] feat(skd-compile): compact AreaTemplate DSL, fix dataPath and presentation fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix empty dataPath when field is specified as object { field, title } - Add title to presentation fallback chain: presentation → title → name - Add compact DSL for AreaTemplate: rows/widths/style instead of raw XML - Built-in style presets: header, data, subheader, total - User-defined presets via skd-styles.json (project-level overrides) - Support vertical merge ("|"), parameters ("{Name}"), static text, null cells - Update SKILL.md, skd-dsl-spec.md, skd-guide.md with template DSL docs - Add examples/skd-styles.json with all supported keys - Bump version v1.1 → v1.2 Co-Authored-By: Claude Opus 4.6 (1M context) --- .claude/skills/skd-compile/SKILL.md | 60 +++- .../skd-compile/examples/skd-styles.json | 30 ++ .../skd-compile/scripts/skd-compile.ps1 | 293 +++++++++++++++++- .../skills/skd-compile/scripts/skd-compile.py | 246 ++++++++++++++- docs/skd-dsl-spec.md | 96 +++++- docs/skd-guide.md | 34 ++ 6 files changed, 725 insertions(+), 34 deletions(-) create mode 100644 .claude/skills/skd-compile/examples/skd-styles.json diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md index 2da95218..afa3beaa 100644 --- a/.claude/skills/skd-compile/SKILL.md +++ b/.claude/skills/skd-compile/SKILL.md @@ -31,7 +31,7 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p ## JSON DSL — краткий справочник -Полная спецификация: `docs/skd-dsl-spec.md`. +Справочник ниже. Все примеры компилируемы как есть. ### Корневая структура @@ -41,6 +41,8 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p "calculatedFields": [...], "totalFields": [...], "parameters": [...], + "templates": [...], + "groupTemplates": [...], "dataSetLinks": [...], "settingsVariants": [...] } @@ -58,7 +60,7 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p Запрос поддерживает `@file` — ссылку на внешний .sql файл вместо inline-текста: `"query": "@queries/sales.sql"`. Путь разрешается относительно JSON-файла, затем CWD. -### Поля — shorthand +### Поля — shorthand и объектная форма ``` "Наименование" — просто имя @@ -67,6 +69,12 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p "Служебное: string #noFilter #noOrder" — + ограничения ``` +Объектная форма — когда нужен title или другие свойства: +```json +{ "field": "ОстатокНаНачалоПериода", "title": "Остаток на начало периода" } +``` +`dataPath` автоматически берётся из `field`, если не указан явно. + Типы: `string`, `string(N)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`. Ссылочные типы эмитируются с inline namespace `d5p1:` (`http://v8.1c.ru/8.1/data/enterprise/current-config`). Сборка EPF со ссылочными типами требует базу с соответствующей конфигурацией. **Синонимы типов** (русские и альтернативные): `число` = decimal, `строка` = string, `булево` = boolean, `дата` = date, `датаВремя` = dateTime, `СтандартныйПериод` = StandardPeriod, `СправочникСсылка.X` = CatalogRef.X, `ДокументСсылка.X` = DocumentRef.X, `int`/`number` = decimal, `bool` = boolean. Регистронезависимые. @@ -143,6 +151,7 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p ```json "settingsVariants": [{ "name": "Основной", + "title": "Продажи по организациям", "settings": { "selection": ["Номенклатура", "Количество", "Auto"], "filter": ["Организация = _ @off @user"], @@ -188,6 +197,53 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p ] ``` +### Шаблоны вывода — компактный DSL + +Вместо raw XML (`template`) — табличное описание через `rows` + именованный стиль `style`: + +```json +"templates": [ + { + "name": "Макет1", + "style": "header", + "widths": [36, 33, 16, 17], + "minHeight": 24.75, + "rows": [ + ["Виды кассы", "Валюта", "Остаток на начало\nпериода", "Остаток на\nконец периода"], + ["|", "|", "|", "|"], + ["К1", "К2", "К3", "К4"] + ] + }, + { + "name": "Макет2", + "style": "data", + "widths": [36, 33, 16, 17], + "rows": [["{ВидКассы}", "{Валюта}", "{Остаток}", "{ОстатокКонец}"]], + "parameters": [ + { "name": "ВидКассы", "expression": "Представление(Счет)" }, + { "name": "Остаток", "expression": "ОстатокНаНачалоПериода" } + ] + } +] +``` + +Синтаксис ячеек: `"текст"` — статика, `"{Имя}"` — параметр, `"|"` — объединение с ячейкой выше, `null` — пустая. + +Встроенные стили: `header` (фон, центр, перенос), `data` (фон группы), `subheader` (без фона, центр), `total` (без фона). Все — Arial 10, рамки Solid 1px, цвета через стили платформы. + +Пользовательские стили: файл `skd-styles.json` рядом с JSON или в корне проекта. Все допустимые ключи и формат цветов — в `examples/skd-styles.json`. + +Raw XML (`"template": "<...>"`) остаётся как fallback. Детект: если есть `rows` — DSL, иначе — raw. + +### Привязки макетов к группировкам + +```json +"groupTemplates": [ + { "groupField": "Счет", "templateType": "GroupHeader", "template": "Макет1" }, + { "groupField": "Счет", "templateType": "Header", "template": "Макет2" } +] +``` + ## Примеры ### Минимальный diff --git a/.claude/skills/skd-compile/examples/skd-styles.json b/.claude/skills/skd-compile/examples/skd-styles.json new file mode 100644 index 00000000..0ec04688 --- /dev/null +++ b/.claude/skills/skd-compile/examples/skd-styles.json @@ -0,0 +1,30 @@ +{ + "header": { + "font": "Arial", + "fontSize": 10, + "bold": false, + "italic": false, + "hAlign": "Center", + "vAlign": "Center", + "wrap": true, + "bgColor": "style:ReportHeaderBackColor", + "textColor": null, + "borderColor": "style:ReportLineColor", + "borders": true + }, + "data": { + "bgColor": "style:ReportGroup1BackColor", + "borderColor": "style:ReportLineColor" + }, + "total": { + "bold": true, + "borderColor": "style:ReportLineColor" + }, + "myProjectStyle": { + "bgColor": "#FFE0E0", + "textColor": "#990000", + "fontSize": 12, + "bold": true, + "hAlign": "Right" + } +} diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index bb02e1ae..918389e7 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -1,4 +1,4 @@ -# skd-compile v1.1 — Compile 1C DCS from JSON +# skd-compile v1.2 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -529,7 +529,7 @@ function Emit-Field { $f = Parse-FieldShorthand $fieldDef } else { $f = @{ - dataPath = "$($fieldDef.dataPath)" + dataPath = if ($fieldDef.dataPath) { "$($fieldDef.dataPath)" } elseif ($fieldDef.field) { "$($fieldDef.field)" } else { "" } field = if ($fieldDef.field) { "$($fieldDef.field)" } else { "$($fieldDef.dataPath)" } title = if ($fieldDef.title) { "$($fieldDef.title)" } else { "" } type = if ($fieldDef.type) { Resolve-TypeStr "$($fieldDef.type)" } else { "" } @@ -931,25 +931,286 @@ function Emit-ParamValue { } } +# === AreaTemplate DSL === + +# Built-in style presets +$script:areaStylePresets = @{ + data = @{ + 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 = @{ + font = 'Arial'; fontSize = 10; bold = $false; italic = $false + hAlign = 'Center'; vAlign = $null; wrap = $true + bgColor = $null; textColor = $null + borderColor = 'style:ReportLineColor'; borders = $true + } + total = @{ + font = 'Arial'; fontSize = 10; bold = $false; italic = $false + hAlign = $null; vAlign = $null; wrap = $false + bgColor = $null; textColor = $null + borderColor = 'style:ReportLineColor'; borders = $true + } +} + +# Load user presets from skd-styles.json (same dir as definition or cwd) +$script:userStylesLoaded = $false +foreach ($stylesDir in @($script:queryBaseDir, (Get-Location).Path)) { + $stylesFile = Join-Path $stylesDir "skd-styles.json" + if (Test-Path $stylesFile) { + $userStyles = Get-Content -Raw -Encoding UTF8 $stylesFile | ConvertFrom-Json + foreach ($prop in $userStyles.PSObject.Properties) { + $preset = @{} + # Start from 'data' defaults + foreach ($k in $script:areaStylePresets['data'].Keys) { + $preset[$k] = $script:areaStylePresets['data'][$k] + } + # If overriding existing preset, start from it instead + if ($script:areaStylePresets.ContainsKey($prop.Name)) { + foreach ($k in $script:areaStylePresets[$prop.Name].Keys) { + $preset[$k] = $script:areaStylePresets[$prop.Name][$k] + } + } + # Apply user overrides + foreach ($up in $prop.Value.PSObject.Properties) { + $preset[$up.Name] = $up.Value + } + $script:areaStylePresets[$prop.Name] = $preset + } + $script:userStylesLoaded = $true + break + } +} + +function Emit-ColorValue { + param([string]$color, [string]$indent) + if ($color.StartsWith('style:')) { + $styleName = $color.Substring(6) + X "$indentd8p1:$styleName" + } else { + X "$indent$(Esc-Xml $color)" + } +} + +function Emit-CellAppearance { + param($style, [double]$width = 0, [bool]$vMerge = $false, [double]$minHeight = 0) + $ind = "`t`t`t`t`t" + X "`t`t`t`t" + # Background color + if ($style.bgColor) { + X "$ind" + X "$ind`tЦветФона" + Emit-ColorValue $style.bgColor "$ind`t" + X "$ind" + } + # Text color + if ($style.textColor) { + X "$ind" + X "$ind`tЦветТекста" + Emit-ColorValue $style.textColor "$ind`t" + X "$ind" + } + # Border color + border style (4 sides) + if ($style.borders) { + if ($style.borderColor) { + X "$ind" + X "$ind`tЦветГраницы" + Emit-ColorValue $style.borderColor "$ind`t" + X "$ind" + } + X "$ind" + X "$ind`tСтильГраницы" + X "$ind`t" + X "$ind`t`tNone" + X "$ind`t" + foreach ($side in @('Слева','Сверху','Справа','Снизу')) { + X "$ind`t" + X "$ind`t`tСтильГраницы.$side" + X "$ind`t`t" + X "$ind`t`t`tSolid" + X "$ind`t`t" + X "$ind`t" + } + X "$ind" + } + # Font + $boldStr = if ($style.bold) { "true" } else { "false" } + $italicStr = if ($style.italic) { "true" } else { "false" } + X "$ind" + X "$ind`tШрифт" + X "$ind`t" + X "$ind" + # Horizontal alignment + if ($style.hAlign) { + X "$ind" + X "$ind`tГоризонтальноеПоложение" + X "$ind`t$(Esc-Xml $style.hAlign)" + X "$ind" + } + # Vertical alignment + if ($style.vAlign) { + X "$ind" + X "$ind`tВертикальноеПоложение" + X "$ind`t$(Esc-Xml $style.vAlign)" + X "$ind" + } + # Text placement (wrap) + if ($style.wrap) { + X "$ind" + X "$ind`tРазмещение" + X "$ind`tWrap" + X "$ind" + } + # Width + if ($width -gt 0) { + X "$ind" + X "$ind`tМинимальнаяШирина" + X "$ind`t$width" + X "$ind" + X "$ind" + X "$ind`tМаксимальнаяШирина" + X "$ind`t$width" + X "$ind" + } + # Min height + if ($minHeight -gt 0) { + X "$ind" + X "$ind`tМинимальнаяВысота" + X "$ind`t$minHeight" + X "$ind" + } + # Vertical merge + if ($vMerge) { + X "$ind" + X "$ind`tОбъединятьПоВертикали" + X "$ind`ttrue" + X "$ind" + } + X "`t`t`t`t" +} + +function Emit-AreaTemplateDSL { + param($t) + $styleName = if ($t.style) { "$($t.style)" } else { "data" } + if (-not $script:areaStylePresets.ContainsKey($styleName)) { + Write-Warning "Unknown area style preset '$styleName', falling back to 'data'" + $styleName = "data" + } + $style = $script:areaStylePresets[$styleName] + + $rows = @($t.rows) + $widths = if ($t.widths) { @($t.widths) } else { @() } + $minHeight = if ($t.minHeight) { [double]$t.minHeight } else { 0 } + $colCount = if ($widths.Count -gt 0) { $widths.Count } else { $rows[0].Count } + + # Build merge map: vMerge[row][col] = $true if cell is merged with above + $vMerge = @{} + for ($r = $rows.Count - 1; $r -ge 1; $r--) { + $vMerge[$r] = @{} + for ($c = 0; $c -lt $colCount; $c++) { + $cellVal = $rows[$r][$c] + if ($cellVal -is [string] -and $cellVal -eq '|') { + $vMerge[$r][$c] = $true + } + } + } + if (-not $vMerge.ContainsKey(0)) { $vMerge[0] = @{} } + + X "`t" +} + # === Templates === function Emit-Templates { if (-not $def.templates) { return } foreach ($t in $def.templates) { - X "`t" } } @@ -1565,7 +1826,7 @@ function Emit-SettingsVariants { X "`t" X "`t`t$(Esc-Xml "$($v.name)")" - $pres = if ($v.presentation) { "$($v.presentation)" } else { "$($v.name)" } + $pres = if ($v.presentation) { "$($v.presentation)" } elseif ($v.title) { "$($v.title)" } else { "$($v.name)" } X "`t`t" X "`t`t`t" X "`t`t`t`tru" diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index 551f152e..3f41f3ae 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.py +++ b/.claude/skills/skd-compile/scripts/skd-compile.py @@ -1,5 +1,5 @@ #!/usr/bin/env python3 -# skd-compile v1.1 — Compile 1C DCS from JSON +# skd-compile v1.2 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -417,7 +417,7 @@ def emit_field(lines, field_def, indent): f = parse_field_shorthand(field_def) else: f = { - 'dataPath': str(field_def.get('dataPath', '')), + 'dataPath': str(field_def.get('dataPath', '')) or str(field_def.get('field', '')), 'field': str(field_def.get('field', '')) or str(field_def.get('dataPath', '')), 'title': str(field_def.get('title', '')) if field_def.get('title') else '', 'type': resolve_type_str(str(field_def['type'])) if field_def.get('type') else '', @@ -772,23 +772,238 @@ def emit_parameters(lines, defn): emit_single_param(lines, None, end_parsed) +# === AreaTemplate DSL === + +AREA_STYLE_PRESETS = { + 'data': { + 'font': 'Arial', 'fontSize': 10, 'bold': False, 'italic': False, + 'hAlign': None, 'vAlign': None, 'wrap': False, + 'bgColor': 'style:ReportGroup1BackColor', 'textColor': None, + 'borderColor': 'style:ReportLineColor', 'borders': True, + }, + 'header': { + 'font': 'Arial', 'fontSize': 10, 'bold': False, 'italic': False, + 'hAlign': 'Center', 'vAlign': None, 'wrap': True, + 'bgColor': 'style:ReportHeaderBackColor', 'textColor': None, + 'borderColor': 'style:ReportLineColor', 'borders': True, + }, + 'subheader': { + 'font': 'Arial', 'fontSize': 10, 'bold': False, 'italic': False, + 'hAlign': 'Center', 'vAlign': None, 'wrap': True, + 'bgColor': None, 'textColor': None, + 'borderColor': 'style:ReportLineColor', 'borders': True, + }, + 'total': { + 'font': 'Arial', 'fontSize': 10, 'bold': False, 'italic': False, + 'hAlign': None, 'vAlign': None, 'wrap': False, + 'bgColor': None, 'textColor': None, + 'borderColor': 'style:ReportLineColor', 'borders': True, + }, +} + + +def load_user_styles(base_dir): + for d in [base_dir, os.getcwd()]: + p = os.path.join(d, 'skd-styles.json') + if os.path.isfile(p): + with open(p, 'r', encoding='utf-8-sig') as f: + user_styles = json.load(f) + for name, overrides in user_styles.items(): + base = dict(AREA_STYLE_PRESETS.get(name, AREA_STYLE_PRESETS['data'])) + base.update(overrides) + AREA_STYLE_PRESETS[name] = base + return + + +def _emit_color_value(lines, color, indent): + if color.startswith('style:'): + style_name = color[6:] + lines.append(f'{indent}d8p1:{style_name}') + else: + lines.append(f'{indent}{esc_xml(color)}') + + +def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0): + ind = '\t\t\t\t\t' + lines.append('\t\t\t\t') + # Background color + if style.get('bgColor'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0426\u0432\u0435\u0442\u0424\u043e\u043d\u0430') + _emit_color_value(lines, style['bgColor'], f'{ind}\t') + lines.append(f'{ind}') + # Text color + if style.get('textColor'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0426\u0432\u0435\u0442\u0422\u0435\u043a\u0441\u0442\u0430') + _emit_color_value(lines, style['textColor'], f'{ind}\t') + lines.append(f'{ind}') + # Borders + if style.get('borders'): + if style.get('borderColor'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0426\u0432\u0435\u0442\u0413\u0440\u0430\u043d\u0438\u0446\u044b') + _emit_color_value(lines, style['borderColor'], f'{ind}\t') + lines.append(f'{ind}') + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0421\u0442\u0438\u043b\u044c\u0413\u0440\u0430\u043d\u0438\u0446\u044b') + lines.append(f'{ind}\t') + lines.append(f'{ind}\t\tNone') + lines.append(f'{ind}\t') + for side in ['\u0421\u043b\u0435\u0432\u0430', '\u0421\u0432\u0435\u0440\u0445\u0443', '\u0421\u043f\u0440\u0430\u0432\u0430', '\u0421\u043d\u0438\u0437\u0443']: + lines.append(f'{ind}\t') + lines.append(f'{ind}\t\t\u0421\u0442\u0438\u043b\u044c\u0413\u0440\u0430\u043d\u0438\u0446\u044b.{side}') + lines.append(f'{ind}\t\t') + lines.append(f'{ind}\t\t\tSolid') + lines.append(f'{ind}\t\t') + lines.append(f'{ind}\t') + lines.append(f'{ind}') + # Font + bold_str = 'true' if style.get('bold') else 'false' + italic_str = 'true' if style.get('italic') else 'false' + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0428\u0440\u0438\u0444\u0442') + lines.append(f'{ind}\t') + lines.append(f'{ind}') + # Horizontal alignment + if style.get('hAlign'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u044c\u043d\u043e\u0435\u041f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435') + lines.append(f'{ind}\t{esc_xml(style["hAlign"])}') + lines.append(f'{ind}') + # Vertical alignment + if style.get('vAlign'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u044c\u043d\u043e\u0435\u041f\u043e\u043b\u043e\u0436\u0435\u043d\u0438\u0435') + lines.append(f'{ind}\t{esc_xml(style["vAlign"])}') + lines.append(f'{ind}') + # Wrap + if style.get('wrap'): + lines.append(f'{ind}') + lines.append(f'{ind}\t\u0420\u0430\u0437\u043c\u0435\u0449\u0435\u043d\u0438\u0435') + lines.append(f'{ind}\tWrap') + lines.append(f'{ind}') + # Width + if width and width > 0: + lines.append(f'{ind}') + lines.append(f'{ind}\t\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f\u0428\u0438\u0440\u0438\u043d\u0430') + lines.append(f'{ind}\t{width}') + lines.append(f'{ind}') + lines.append(f'{ind}') + lines.append(f'{ind}\t\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f\u0428\u0438\u0440\u0438\u043d\u0430') + lines.append(f'{ind}\t{width}') + lines.append(f'{ind}') + # Min height + if min_height and min_height > 0: + lines.append(f'{ind}') + lines.append(f'{ind}\t\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f\u0412\u044b\u0441\u043e\u0442\u0430') + lines.append(f'{ind}\t{min_height}') + lines.append(f'{ind}') + # Vertical merge + if v_merge: + lines.append(f'{ind}') + lines.append(f'{ind}\t\u041e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u041f\u043e\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438') + lines.append(f'{ind}\ttrue') + lines.append(f'{ind}') + lines.append('\t\t\t\t') + + +def _emit_area_template_dsl(lines, t): + style_name = str(t.get('style', '')) or 'data' + if style_name not in AREA_STYLE_PRESETS: + print(f"Warning: Unknown area style preset '{style_name}', falling back to 'data'", file=sys.stderr) + style_name = 'data' + style = AREA_STYLE_PRESETS[style_name] + + rows = list(t['rows']) + widths = list(t.get('widths', [])) + min_height = float(t.get('minHeight', 0)) + col_count = len(widths) if widths else len(rows[0]) + + # Build merge map + v_merge = {} + for r in range(len(rows) - 1, 0, -1): + v_merge[r] = {} + for c in range(col_count): + cell_val = rows[r][c] if c < len(rows[r]) else None + if isinstance(cell_val, str) and cell_val == '|': + v_merge[r][c] = True + if 0 not in v_merge: + v_merge[0] = {} + + lines.append('\t') + + # === Templates === def emit_templates(lines, defn): if not defn.get('templates'): return for t in defn['templates']: - lines.append('\t') + if t.get('rows'): + _emit_area_template_dsl(lines, t) + else: + lines.append('\t') # === GroupTemplates === @@ -1288,7 +1503,7 @@ def emit_settings_variants(lines, defn): lines.append('\t') lines.append(f'\t\t{esc_xml(str(v["name"]))}') - pres = str(v.get('presentation', '')) or str(v['name']) + pres = str(v.get('presentation', '')) or str(v.get('title', '')) or str(v['name']) lines.append('\t\t') lines.append('\t\t\t') lines.append('\t\t\t\tru') @@ -1375,6 +1590,9 @@ def main(): global query_base_dir query_base_dir = os.path.dirname(def_file) if args.DefinitionFile else os.getcwd() + # Load user style presets + load_user_styles(query_base_dir) + # --- 2. Resolve defaults --- # DataSources diff --git a/docs/skd-dsl-spec.md b/docs/skd-dsl-spec.md index 4e419667..a3dcb43f 100644 --- a/docs/skd-dsl-spec.md +++ b/docs/skd-dsl-spec.md @@ -712,9 +712,99 @@ XML-маппинг — по `` на каждый элемент: ## 10. Макеты и привязки (templates, groupTemplates) -Редко используются. Поддерживаются в объектной форме, близкой к XML. +### templates — компактный DSL (рекомендуемый) -### templates +Табличное описание шаблона вывода. Содержимое задаётся через `rows`, оформление — через именованный пресет `style`. + +```json +"templates": [ + { + "name": "Макет1", + "style": "header", + "widths": [36, 33, 16, 17], + "minHeight": 24.75, + "rows": [ + ["Виды кассы", "Валюта", "Остаток на начало\nпериода", "Остаток на\nконец\nпериода"], + ["|", "|", "|", "|"], + ["К1", "К2", "К3", "К4"] + ] + }, + { + "name": "Макет2", + "style": "data", + "widths": [36, 33, 16, 17], + "rows": [["{ВидКассы}", "{Валюта}", "{ОстатокНачало}", "{ОстатокКонец}"]], + "parameters": [ + { "name": "ВидКассы", "expression": "Представление(СчетМеждународногоУчета)" }, + { "name": "ОстатокНачало", "expression": "ОстатокНаНачалоПериода" } + ] + }, + { + "name": "Макет3", + "style": "total", + "widths": [36, 33, 16, 17], + "rows": [["Итого", "Х", "{ОстатокНачало}", "{ОстатокКонец}"]], + "parameters": [ + { "name": "ОстатокНачало", "expression": "ОстатокНаНачалоПериода" } + ] + } +] +``` + +#### Свойства шаблона + +| Свойство | Описание | +|----------|----------| +| `name` | Имя макета (ссылаются groupTemplate) | +| `rows` | Массив строк; каждая строка — массив ячеек | +| `style` | Именованный пресет оформления (по умолчанию `"data"`) | +| `widths` | Массив ширин колонок (применяется ко всем строкам) | +| `minHeight` | Минимальная высота первой строки (для шапок) | +| `parameters` | Параметры макета — выражения для подстановки | + +#### Синтаксис ячеек + +| Значение | Описание | +|----------|----------| +| `"текст"` | Статический текст (`v8:LocalStringType`) | +| `"{Имя}"` | Параметр шаблона (`dcscor:Parameter`), задаётся через `parameters` | +| `"\|"` | Вертикальное объединение с ячейкой выше | +| `null` | Пустая ячейка (без содержимого) | + +#### Встроенные пресеты стилей + +| Пресет | Фон | Шрифт | Выравнивание | Перенос | Рамки | +|--------|-----|-------|-------------|---------|-------| +| `header` | ReportHeaderBackColor | Arial 10 | Center | да | Solid 1px | +| `data` | ReportGroup1BackColor | Arial 10 | — | нет | Solid 1px | +| `subheader` | — | Arial 10 | Center | да | Solid 1px | +| `total` | — | Arial 10 | — | нет | Solid 1px | + +#### Пользовательские пресеты (skd-styles.json) + +Файл `skd-styles.json` в директории определения или в корне проекта. Переопределяет встроенные пресеты или добавляет новые: + +```json +{ + "header": { + "bgColor": "style:ReportHeaderBackColor", + "borderColor": "style:ReportLineColor", + "bold": true + }, + "myStyle": { + "font": "Arial", "fontSize": 12, + "bgColor": "#FFE0E0" + } +} +``` + +Допустимые ключи: `font`, `fontSize`, `bold`, `italic`, `hAlign`, `vAlign`, `wrap`, `bgColor`, `textColor`, `borderColor`, `borders`. Недостающие ключи берутся из пресета `data`. + +Формат цветов: `"style:ИмяСтиля"` (ссылка на стиль платформы) или `"#RRGGBB"` (прямой цвет). + +### templates — raw XML (fallback) + +Для нестандартных случаев — raw XML вставляется как есть: ```json "templates": [ @@ -728,6 +818,8 @@ XML-маппинг — по `` на каждый элемент: ] ``` +Детект: если есть `rows` — используется компактный DSL, иначе — raw XML из `template`. + ### groupTemplates ```json diff --git a/docs/skd-guide.md b/docs/skd-guide.md index 7b559490..39a2c021 100644 --- a/docs/skd-guide.md +++ b/docs/skd-guide.md @@ -100,6 +100,40 @@ - **structure shorthand**: `"Поле1 > Поле2 > details"` — `>` разделяет уровни группировки - **conditionalAppearance**: условное оформление с автоопределением типов значений (Color, Boolean, LocalStringType) +### Шаблоны вывода — компактный DSL + +Для отчётов с фиксированным оформлением (ФСД, ведомости) — табличное описание вместо raw XML: + +```json +"templates": [ + { + "name": "Макет1", "style": "header", + "widths": [36, 33, 16, 17], "minHeight": 24.75, + "rows": [ + ["Виды кассы", "Валюта", "Остаток на начало\nпериода", "Остаток на конец периода"], + ["|", "|", "|", "|"], + ["К1", "К2", "К3", "К4"] + ] + }, + { + "name": "Макет2", "style": "data", + "widths": [36, 33, 16, 17], + "rows": [["{ВидКассы}", "{Валюта}", "{Остаток}", "{ОстатокКонец}"]], + "parameters": [ + { "name": "ВидКассы", "expression": "Представление(Счет)" } + ] + } +], +"groupTemplates": [ + { "groupField": "Счет", "templateType": "GroupHeader", "template": "Макет1" }, + { "groupField": "Счет", "templateType": "Header", "template": "Макет2" } +] +``` + +Синтаксис ячеек: `"текст"` — статика, `"{Имя}"` — параметр, `"|"` — объединение с ячейкой выше, `null` — пустая. + +Встроенные стили: `header` (фон, центр, перенос), `data` (фон группы), `subheader` (без фона, центр), `total` (без фона). Все — Arial 10, рамки Solid 1px, цвета через стили платформы. Пользовательские стили — через `skd-styles.json` в директории проекта. + ### Объектная форма Все секции поддерживают полную объектную форму для сложных случаев (title, appearance, role с выражениями, userSettingID, userSettingPresentation, conditionalAppearance, группы фильтров And/Or/Not и т.д.). Подробности — в [спецификации SKD DSL](skd-dsl-spec.md).