mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-12 08:54:57 +03:00
feat(skd-decompile): scaffold — Ring 3 fail-fast, sentinel/warnings, query extraction
Layer 1 of the skd-decompile plan: SKILL.md with disable-model-invocation, ps1 skeleton with XML→JSON pipeline, namespace probe for non-DCS root, sentinel/warnings accumulator, and DataSetQuery extraction (query only). Test case minimal-query demonstrates round-trip via skd-compile preRun. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: skd-decompile
|
||||
description: Декомпиляция схемы компоновки данных 1С (СКД) в JSON-черновик в формате skd-compile. Используй когда нужно создать новый отчёт по образцу существующего или провести структурный рефакторинг. Для точечных правок используй skd-edit
|
||||
argument-hint: <TemplatePath> [-OutputPath <out.json>]
|
||||
disable-model-invocation: true
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /skd-decompile — извлечение JSON-черновика из Template.xml СКД
|
||||
|
||||
Читает существующий `Template.xml` (DataCompositionSchema) и эмитит JSON в формате, который принимает `/skd-compile`. Получившийся JSON — **черновик**: гарантируется только структурная эквивалентность, не байтовая.
|
||||
|
||||
## Когда использовать
|
||||
|
||||
- **Scaffold нового отчёта по образцу.** Взять существующий СКД, получить JSON, поправить параметры/поля/шаблоны, скомпилировать в новый отчёт.
|
||||
- **Глобальный рефакторинг.** Когда правка структурная (переписать вариант, перерисовать шаблон), а не точечная.
|
||||
|
||||
## Когда **не** использовать
|
||||
|
||||
- **Точечные правки готового отчёта** — добавить поле, фильтр, итог, переименовать. Для этого есть `/skd-edit`: точечно, без полной реконструкции, без риска потерь.
|
||||
- **Анализ схемы** — для обзора используй `/skd-info` (overview/query/fields/variant/templates).
|
||||
|
||||
## Параметры и команда
|
||||
|
||||
| Параметр | Описание |
|
||||
|----------|----------|
|
||||
| `TemplatePath` | Путь к Template.xml (обязательный) |
|
||||
| `OutputPath` | Путь к выходному JSON. Если не задан — JSON в stdout |
|
||||
|
||||
```powershell
|
||||
# В файл (рядом, если есть warnings, кладётся <basename>.warnings.md)
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>" -OutputPath "<out.json>"
|
||||
|
||||
# В stdout
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-decompile.ps1" -TemplatePath "<Template.xml>"
|
||||
```
|
||||
|
||||
## Гарантии и ограничения
|
||||
|
||||
- **JSON всегда валиден** — компилируется через `skd-compile` без синтаксических ошибок.
|
||||
- **Покрытие — DSL `skd-compile`.** Конструкции XML вне DSL отмечаются sentinel-объектом `{"__unsupported__": {...}}` и описаны в `<basename>.warnings.md` рядом с JSON. `skd-compile` фейлится при наличии sentinel — это специально, чтобы пользователь сначала разобрался с непокрытым.
|
||||
- **Не байтовая эквивалентность.** После round-trip XML структурно эквивалентен оригиналу, но порядок атрибутов/секций может отличаться.
|
||||
- **Стиль ячейки** определяется по совпадению с одним из built-in (`header`/`data`/`subheader`/`total`) или user-стилей из `presets/skills/skd/skd-styles.json`. Точечный custom appearance не сворачивается → sentinel.
|
||||
|
||||
## Не поддерживается (fail-fast)
|
||||
|
||||
- Picture cells в шаблонах (`<dcsat:Picture>`).
|
||||
- Параметры типа ХранилищеЗначения.
|
||||
- Sibling templates / templateCondition (вариативные шаблоны).
|
||||
- Не-СКД корневые XML (например, spreadsheet `<document>` — для них есть `/mxl-decompile`).
|
||||
|
||||
При обнаружении — скрипт пишет в stderr понятное сообщение и завершается с ненулевым кодом.
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/skd-compile -DefinitionFile <out.json> -OutputPath <new-Template.xml> — обратная компиляция
|
||||
/skd-validate <new-Template.xml> — валидация результата
|
||||
/skd-info <new-Template.xml> — визуальный осмотр
|
||||
```
|
||||
@@ -0,0 +1,159 @@
|
||||
# skd-decompile v0.1 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
|
||||
[string]$OutputPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- 0. Resolve and validate input ---
|
||||
|
||||
if (-not (Test-Path $TemplatePath)) {
|
||||
Write-Error "Template not found: $TemplatePath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$TemplatePath = (Resolve-Path $TemplatePath).Path
|
||||
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $false
|
||||
$xmlDoc.Load($TemplatePath)
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# Ring 3: not a DataCompositionSchema → fail-fast
|
||||
if ($root.LocalName -ne 'DataCompositionSchema') {
|
||||
Write-Error "Root element <$($root.LocalName)> is not <DataCompositionSchema>. This is not a SKD template (perhaps a spreadsheet — use /mxl-decompile)."
|
||||
exit 2
|
||||
}
|
||||
|
||||
# --- 1. Namespace manager ---
|
||||
|
||||
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$ns.AddNamespace("dcs", "http://v8.1c.ru/8.1/data-composition-system/schema")
|
||||
$ns.AddNamespace("dcscom", "http://v8.1c.ru/8.1/data-composition-system/common")
|
||||
$ns.AddNamespace("dcscor", "http://v8.1c.ru/8.1/data-composition-system/core")
|
||||
$ns.AddNamespace("dcsset", "http://v8.1c.ru/8.1/data-composition-system/settings")
|
||||
$ns.AddNamespace("dcsat", "http://v8.1c.ru/8.1/data-composition-system/areatemplate")
|
||||
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
$ns.AddNamespace("v8ui", "http://v8.1c.ru/8.1/data/ui")
|
||||
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
|
||||
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
|
||||
# Root may use default namespace = schema; XPath needs explicit prefix
|
||||
$rootNS = $root.NamespaceURI
|
||||
if (-not $rootNS) { $rootNS = "http://v8.1c.ru/8.1/data-composition-system/schema" }
|
||||
# Re-bind dcs to actual root namespace if different
|
||||
$ns.AddNamespace("r", $rootNS)
|
||||
|
||||
# --- 2. Warnings accumulator ---
|
||||
|
||||
$script:warnings = @()
|
||||
$script:warningCounter = 0
|
||||
|
||||
function Add-Warning {
|
||||
param([string]$kind, [string]$loc, [string]$detail)
|
||||
$script:warningCounter++
|
||||
$id = "W{0:D3}" -f $script:warningCounter
|
||||
$script:warnings += [ordered]@{ id = $id; kind = $kind; loc = $loc; detail = $detail }
|
||||
return $id
|
||||
}
|
||||
|
||||
function New-Sentinel {
|
||||
param([string]$kind, [string]$loc, [string]$detail)
|
||||
$id = Add-Warning -kind $kind -loc $loc -detail $detail
|
||||
return [ordered]@{ '__unsupported__' = [ordered]@{ id = $id; kind = $kind; loc = $loc } }
|
||||
}
|
||||
|
||||
# --- 3. Extract: dataSets (query only, no fields yet) ---
|
||||
|
||||
function Get-Text {
|
||||
param($node, [string]$xpath)
|
||||
if (-not $node) { return $null }
|
||||
$n = $node.SelectSingleNode($xpath, $ns)
|
||||
if ($n) { return $n.InnerText } else { return $null }
|
||||
}
|
||||
|
||||
$dataSets = @()
|
||||
$dsNodes = $root.SelectNodes("r:dataSet", $ns)
|
||||
foreach ($dsNode in $dsNodes) {
|
||||
$xsiType = $dsNode.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
$name = Get-Text $dsNode "r:name"
|
||||
$ds = [ordered]@{ name = $name }
|
||||
|
||||
switch -Regex ($xsiType) {
|
||||
'DataSetQuery$' {
|
||||
$query = Get-Text $dsNode "r:query"
|
||||
# Decode XML entities — XmlDocument already decoded & → & in InnerText
|
||||
$ds['query'] = $query
|
||||
# fields — layer 3
|
||||
}
|
||||
'DataSetObject$' {
|
||||
$ds['objectName'] = Get-Text $dsNode "r:objectName"
|
||||
}
|
||||
'DataSetUnion$' {
|
||||
$ds['__unsupported__'] = (New-Sentinel -kind 'DataSetUnion' -loc "dataSet[name=$name]" -detail 'Реализуется в слое 15')['__unsupported__']
|
||||
}
|
||||
default {
|
||||
$ds['__unsupported__'] = (New-Sentinel -kind "DataSetType:$xsiType" -loc "dataSet[name=$name]" -detail "Неизвестный тип набора данных")['__unsupported__']
|
||||
}
|
||||
}
|
||||
|
||||
$dataSets += $ds
|
||||
}
|
||||
|
||||
# --- 4. Build top-level JSON object ---
|
||||
|
||||
$out = [ordered]@{
|
||||
dataSets = $dataSets
|
||||
}
|
||||
|
||||
# --- 5. Serialize ---
|
||||
|
||||
$json = $out | ConvertTo-Json -Depth 32
|
||||
|
||||
# Unescape \uXXXX → UTF-8 literals (PS 5.1 ConvertTo-Json escapes non-ASCII)
|
||||
$json = [regex]::Replace($json, '\\u([0-9a-fA-F]{4})', {
|
||||
param($m)
|
||||
[char][int]("0x" + $m.Groups[1].Value)
|
||||
})
|
||||
|
||||
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)
|
||||
|
||||
# Write warnings.md alongside, if any
|
||||
if ($script:warnings.Count -gt 0) {
|
||||
$wPath = [System.IO.Path]::ChangeExtension($OutputPath, $null).TrimEnd('.') + '.warnings.md'
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
[void]$sb.AppendLine("# skd-decompile warnings")
|
||||
[void]$sb.AppendLine("")
|
||||
[void]$sb.AppendLine("Source: $TemplatePath")
|
||||
[void]$sb.AppendLine("")
|
||||
foreach ($w in $script:warnings) {
|
||||
[void]$sb.AppendLine("- **$($w.id)** ($($w.kind)) at `$($w.loc)`: $($w.detail)")
|
||||
}
|
||||
[System.IO.File]::WriteAllText($wPath, $sb.ToString(), $enc)
|
||||
Write-Host "Warnings written: $wPath ($($script:warnings.Count) issue(s))" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Summary to stderr
|
||||
$stats = "dataSets=$($dataSets.Count), warnings=$($script:warnings.Count)"
|
||||
[Console]::Error.WriteLine("Decompiled: $stats")
|
||||
} else {
|
||||
Write-Output $json
|
||||
if ($script:warnings.Count -gt 0) {
|
||||
[Console]::Error.WriteLine("Warnings ($($script:warnings.Count)):")
|
||||
foreach ($w in $script:warnings) {
|
||||
[Console]::Error.WriteLine(" $($w.id) [$($w.kind)] $($w.loc): $($w.detail)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"script": "skd-decompile/scripts/skd-decompile",
|
||||
"setup": "none",
|
||||
"args": [
|
||||
{ "flag": "-TemplatePath", "from": "workPath", "field": "templatePath" }
|
||||
],
|
||||
"snapshot": {
|
||||
"root": "workDir",
|
||||
"normalizeUuids": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Минимальный СКД с одним DataSetQuery",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "skd-compile/scripts/skd-compile",
|
||||
"input": {
|
||||
"dataSets": [{
|
||||
"name": "НаборДанных1",
|
||||
"query": "ВЫБРАТЬ Номенклатура.Наименование КАК Наименование ИЗ Справочник.Номенклатура КАК Номенклатура",
|
||||
"fields": ["Наименование"]
|
||||
}]
|
||||
},
|
||||
"args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "Template.xml" },
|
||||
"cwd": "{workDir}"
|
||||
}
|
||||
],
|
||||
"params": { "templatePath": "Template.xml" },
|
||||
"expect": { "stdoutContains": "НаборДанных1" }
|
||||
}
|
||||
Reference in New Issue
Block a user