mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-11 08:24:57 +03:00
Add mxl-compile skill: DSL compiler for SpreadsheetDocument
JSON DSL format compiles to valid 1C Template.xml with correct format palettes, merges, named areas, and rowStyle gap-filling. Tested on simple (4 areas) and invoice (7 areas) templates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
---
|
||||
name: mxl-compile
|
||||
description: Компиляция табличного документа (MXL) из JSON-определения
|
||||
argument-hint: <JsonPath> <OutputPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /mxl-compile — Компилятор макета из DSL
|
||||
|
||||
Принимает компактное JSON-определение макета и генерирует корректный Template.xml для табличного документа 1С. Claude описывает *что* нужно (области, параметры, стили), скрипт обеспечивает *корректность* XML (палитры, индексы, объединения, namespace).
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/mxl-compile <JsonPath> <OutputPath>
|
||||
```
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|------------------------------------|
|
||||
| JsonPath | да | Путь к JSON-определению макета |
|
||||
| OutputPath | да | Путь для генерации Template.xml |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/mxl-compile/scripts/mxl-compile.ps1 -JsonPath "<путь>.json" -OutputPath "<путь>/Template.xml"
|
||||
```
|
||||
|
||||
## Рабочий процесс
|
||||
|
||||
1. Claude пишет JSON-определение (Write tool) → файл `.json`
|
||||
2. Claude вызывает `/mxl-compile` для генерации Template.xml
|
||||
3. Claude вызывает `/mxl-validate` для проверки корректности
|
||||
4. Claude вызывает `/mxl-info` для верификации структуры
|
||||
|
||||
## JSON-схема
|
||||
|
||||
### Пример
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 10,
|
||||
"defaultWidth": 30,
|
||||
"columnWidths": { "1": 15, "2-8": 40, "9-10": 50 },
|
||||
|
||||
"fonts": {
|
||||
"default": { "face": "Arial", "size": 10 },
|
||||
"bold": { "face": "Arial", "size": 10, "bold": true },
|
||||
"header": { "face": "Arial", "size": 14, "bold": true }
|
||||
},
|
||||
|
||||
"styles": {
|
||||
"default": {},
|
||||
"header": { "font": "header", "align": "center" },
|
||||
"label": { "font": "bold" },
|
||||
"bordered": { "border": "all" },
|
||||
"bordered-right": { "border": "all", "align": "right" },
|
||||
"total-right": { "font": "bold", "border": "top", "align": "right" }
|
||||
},
|
||||
|
||||
"areas": [
|
||||
{
|
||||
"name": "Заголовок",
|
||||
"rows": [
|
||||
{ "height": 20, "cells": [
|
||||
{ "col": 1, "span": 10, "style": "header", "param": "ТекстЗаголовка" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ШапкаТаблицы",
|
||||
"rows": [
|
||||
{ "rowStyle": "bordered", "cells": [
|
||||
{ "col": 1, "text": "№" },
|
||||
{ "col": 2, "span": 6, "text": "Наименование" },
|
||||
{ "col": 9, "text": "Кол-во" },
|
||||
{ "col": 10, "text": "Сумма" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Строка",
|
||||
"rows": [
|
||||
{ "rowStyle": "bordered", "cells": [
|
||||
{ "col": 1, "param": "НомерСтроки" },
|
||||
{ "col": 2, "span": 6, "param": "Товар", "detail": "Номенклатура" },
|
||||
{ "col": 9, "style": "bordered-right", "param": "Количество" },
|
||||
{ "col": 10, "style": "bordered-right", "param": "Сумма" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Итого",
|
||||
"rows": [
|
||||
{ "cells": [
|
||||
{ "col": 8, "span": 2, "style": "total-right", "text": "Итого:" },
|
||||
{ "col": 10, "style": "total-right", "param": "Всего" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Верхний уровень
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `columns` | да | — | Количество колонок |
|
||||
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию |
|
||||
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"` |
|
||||
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
||||
| `styles` | нет | `{}` | Именованные стили |
|
||||
| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) |
|
||||
|
||||
### Шрифты (`fonts.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `face` | `"Arial"` | Имя шрифта |
|
||||
| `size` | `10` | Размер |
|
||||
| `bold` | `false` | Жирный |
|
||||
| `italic` | `false` | Курсив |
|
||||
|
||||
Шрифт `"default"` используется когда стиль не указывает шрифт явно.
|
||||
|
||||
### Стили (`styles.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `font` | `"default"` | Ссылка на имя шрифта |
|
||||
| `align` | — | `left`, `center`, `right` |
|
||||
| `valign` | — | `top`, `center` |
|
||||
| `border` | — | `none`, `all`, `bottom`, `top` |
|
||||
| `wrap` | `false` | Перенос текста |
|
||||
|
||||
### Области (`areas[]`)
|
||||
|
||||
| Поле | Обяз. | Описание |
|
||||
|------|:-----:|----------|
|
||||
| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` |
|
||||
| `rows` | да | Массив строк |
|
||||
|
||||
### Строки (`rows[]`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `height` | — | Высота строки (если не задана, используется авто) |
|
||||
| `rowStyle` | — | Стиль для ВСЕХ колонок (заполняет пустоты рамками) |
|
||||
| `cells` | `[]` | Массив ячеек |
|
||||
|
||||
Строка без `cells` и `rowStyle` → пустая строка.
|
||||
|
||||
### Ячейки (`cells[]`)
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `col` | да | — | Позиция колонки (1-based) |
|
||||
| `span` | нет | `1` | Количество объединённых колонок |
|
||||
| `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) |
|
||||
| `param` | нет | — | Параметр заполнения |
|
||||
| `detail` | нет | — | Параметр расшифровки (только с `param`) |
|
||||
| `text` | нет | — | Статический текст |
|
||||
| `template` | нет | — | Шаблонный текст с `[Параметр]` |
|
||||
|
||||
Тип заполнения определяется автоматически:
|
||||
- `param` → fillType=Parameter
|
||||
- `template` → fillType=Template
|
||||
- `text` → fillType=Text
|
||||
|
||||
### `rowStyle` — автозаполнение
|
||||
|
||||
Когда задан `rowStyle`, скрипт создаёт ячейки для ВСЕХ колонок строки. Позиции без явных ячеек заполняются пустыми ячейками с указанным стилем. Это обеспечивает сплошные рамки в табличных строках.
|
||||
|
||||
## Ограничения MVP
|
||||
|
||||
Текущая версия не поддерживает:
|
||||
- Множественные наборы колонок
|
||||
- Области типа Columns / Rectangle
|
||||
- Рисунки (штрихкоды, картинки)
|
||||
- Объединение по строкам (rowspan)
|
||||
- Числовые форматы
|
||||
- Фон ячеек
|
||||
|
||||
Эти возможности будут добавлены в будущих версиях без переделки основной архитектуры.
|
||||
@@ -0,0 +1,587 @@
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$JsonPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$OutputPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# --- 1. Load and validate JSON ---
|
||||
|
||||
if (-not (Test-Path $JsonPath)) {
|
||||
Write-Error "File not found: $JsonPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$json = Get-Content -Raw -Encoding UTF8 $JsonPath
|
||||
$def = $json | ConvertFrom-Json
|
||||
|
||||
if (-not $def.columns) {
|
||||
Write-Error "Required field 'columns' is missing"
|
||||
exit 1
|
||||
}
|
||||
if (-not $def.areas) {
|
||||
Write-Error "Required field 'areas' is missing"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$totalColumns = [int]$def.columns
|
||||
$defaultWidth = if ($def.defaultWidth) { [int]$def.defaultWidth } else { 10 }
|
||||
|
||||
# --- 2. Build font palette ---
|
||||
|
||||
$fontMap = [ordered]@{} # name -> 0-based index
|
||||
$fontEntries = @() # array of hashtables
|
||||
|
||||
function Add-Font {
|
||||
param([string]$name, $fontDef)
|
||||
$face = if ($fontDef.face) { $fontDef.face } else { "Arial" }
|
||||
$size = if ($fontDef.size) { [int]$fontDef.size } else { 10 }
|
||||
$bold = if ($fontDef.bold -eq $true) { "true" } else { "false" }
|
||||
$italic = if ($fontDef.italic -eq $true) { "true" } else { "false" }
|
||||
|
||||
$idx = $script:fontEntries.Count
|
||||
$script:fontMap[$name] = $idx
|
||||
$script:fontEntries += @{
|
||||
Face = $face
|
||||
Size = $size
|
||||
Bold = $bold
|
||||
Italic = $italic
|
||||
}
|
||||
}
|
||||
|
||||
# Add user-defined fonts
|
||||
$hasDefault = $false
|
||||
if ($def.fonts) {
|
||||
foreach ($prop in $def.fonts.PSObject.Properties) {
|
||||
if ($prop.Name -eq "default") { $hasDefault = $true }
|
||||
Add-Font -name $prop.Name -fontDef $prop.Value
|
||||
}
|
||||
}
|
||||
|
||||
# Ensure default font exists
|
||||
if (-not $hasDefault) {
|
||||
$defaultDef = New-Object PSObject -Property @{ face = "Arial"; size = 10 }
|
||||
Add-Font -name "default" -fontDef $defaultDef
|
||||
}
|
||||
|
||||
# --- 3. Determine line palette ---
|
||||
|
||||
$hasBorders = $false
|
||||
|
||||
# Scan styles for border usage
|
||||
if ($def.styles) {
|
||||
foreach ($prop in $def.styles.PSObject.Properties) {
|
||||
if ($prop.Value.border -and $prop.Value.border -ne "none") {
|
||||
$hasBorders = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$solidLineIndex = -1
|
||||
if ($hasBorders) {
|
||||
$solidLineIndex = 0
|
||||
}
|
||||
|
||||
# --- 4. Parse column width specs ---
|
||||
|
||||
function Parse-ColumnSpec {
|
||||
param([string]$spec)
|
||||
$cols = @()
|
||||
foreach ($part in $spec -split ',') {
|
||||
$part = $part.Trim()
|
||||
if ($part -match '^(\d+)-(\d+)$') {
|
||||
$from = [int]$Matches[1]
|
||||
$to = [int]$Matches[2]
|
||||
for ($i = $from; $i -le $to; $i++) { $cols += $i }
|
||||
} else {
|
||||
$cols += [int]$part
|
||||
}
|
||||
}
|
||||
return $cols
|
||||
}
|
||||
|
||||
# Build column width map: 1-based col -> width
|
||||
$colWidthMap = @{}
|
||||
if ($def.columnWidths) {
|
||||
foreach ($prop in $def.columnWidths.PSObject.Properties) {
|
||||
$width = [int]$prop.Value
|
||||
$columns = Parse-ColumnSpec $prop.Name
|
||||
foreach ($c in $columns) {
|
||||
$colWidthMap[$c] = $width
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 5. Style resolver ---
|
||||
|
||||
function Resolve-Style {
|
||||
param([string]$styleName, [string]$fillType)
|
||||
|
||||
$fontIdx = $fontMap["default"]
|
||||
$lb = -1; $tb = -1; $rb = -1; $bb = -1
|
||||
$ha = ""; $va = ""
|
||||
$wrap = $false
|
||||
|
||||
if ($styleName -and $def.styles) {
|
||||
$style = $def.styles.$styleName
|
||||
if ($style) {
|
||||
# Font
|
||||
if ($style.font -and $fontMap.Contains($style.font)) {
|
||||
$fontIdx = $fontMap[$style.font]
|
||||
}
|
||||
|
||||
# Borders
|
||||
if ($style.border) {
|
||||
switch ($style.border) {
|
||||
"all" {
|
||||
$lb = $solidLineIndex; $tb = $solidLineIndex
|
||||
$rb = $solidLineIndex; $bb = $solidLineIndex
|
||||
}
|
||||
"bottom" { $bb = $solidLineIndex }
|
||||
"top" { $tb = $solidLineIndex }
|
||||
"none" { }
|
||||
}
|
||||
}
|
||||
|
||||
# Alignment
|
||||
if ($style.align) {
|
||||
switch ($style.align) {
|
||||
"left" { $ha = "Left" }
|
||||
"center" { $ha = "Center" }
|
||||
"right" { $ha = "Right" }
|
||||
}
|
||||
}
|
||||
if ($style.valign) {
|
||||
switch ($style.valign) {
|
||||
"top" { $va = "Top" }
|
||||
"center" { $va = "Center" }
|
||||
}
|
||||
}
|
||||
|
||||
# Wrap
|
||||
if ($style.wrap -eq $true) { $wrap = $true }
|
||||
}
|
||||
}
|
||||
|
||||
return @{
|
||||
FontIdx = $fontIdx
|
||||
LB = $lb; TB = $tb; RB = $rb; BB = $bb
|
||||
HA = $ha; VA = $va
|
||||
Wrap = $wrap
|
||||
FillType = $fillType
|
||||
}
|
||||
}
|
||||
|
||||
# --- 6. Format palette builder ---
|
||||
|
||||
$formatRegistry = [ordered]@{} # key -> hashtable with properties
|
||||
$formatOrder = @() # ordered keys for index assignment
|
||||
|
||||
function Get-FormatKey {
|
||||
param(
|
||||
[int]$fontIdx = -1,
|
||||
[int]$lb = -1, [int]$tb = -1, [int]$rb = -1, [int]$bb = -1,
|
||||
[string]$ha = "", [string]$va = "",
|
||||
[bool]$wrap = $false,
|
||||
[string]$fillType = "",
|
||||
[int]$width = -1,
|
||||
[int]$height = -1
|
||||
)
|
||||
return "f=$fontIdx|lb=$lb|tb=$tb|rb=$rb|bb=$bb|ha=$ha|va=$va|wr=$wrap|ft=$fillType|w=$width|h=$height"
|
||||
}
|
||||
|
||||
function Register-Format {
|
||||
param([string]$key, [hashtable]$props)
|
||||
if (-not $script:formatRegistry.Contains($key)) {
|
||||
$script:formatRegistry[$key] = $props
|
||||
$script:formatOrder += $key
|
||||
}
|
||||
# Return 1-based index
|
||||
$idx = 0
|
||||
foreach ($k in $script:formatRegistry.Keys) {
|
||||
$idx++
|
||||
if ($k -eq $key) { return $idx }
|
||||
}
|
||||
return $idx
|
||||
}
|
||||
|
||||
# 6a. Default width format
|
||||
$defaultFormatKey = Get-FormatKey -width $defaultWidth
|
||||
$defaultFormatIndex = Register-Format -key $defaultFormatKey -props @{ Width = $defaultWidth }
|
||||
|
||||
# 6b. Column width formats
|
||||
$colFormatMap = @{} # 1-based col -> format index
|
||||
foreach ($col in $colWidthMap.Keys) {
|
||||
$w = $colWidthMap[$col]
|
||||
$key = Get-FormatKey -width $w
|
||||
$idx = Register-Format -key $key -props @{ Width = $w }
|
||||
$colFormatMap[[int]$col] = $idx
|
||||
}
|
||||
|
||||
# 6c. Scan areas for row heights and cell formats
|
||||
# We need to do two passes: first collect all formats, then generate XML
|
||||
|
||||
# Helper: determine fillType from cell content
|
||||
function Get-FillType {
|
||||
param($cell)
|
||||
if ($cell.param) { return "Parameter" }
|
||||
if ($cell.template) { return "Template" }
|
||||
if ($cell.text) { return "Text" }
|
||||
return ""
|
||||
}
|
||||
|
||||
# Helper: register a cell format and return its index
|
||||
function Register-CellFormat {
|
||||
param($styleName, [string]$fillType)
|
||||
$resolved = Resolve-Style -styleName $styleName -fillType $fillType
|
||||
$key = Get-FormatKey -fontIdx $resolved.FontIdx `
|
||||
-lb $resolved.LB -tb $resolved.TB -rb $resolved.RB -bb $resolved.BB `
|
||||
-ha $resolved.HA -va $resolved.VA `
|
||||
-wrap $resolved.Wrap -fillType $resolved.FillType
|
||||
$props = @{
|
||||
FontIdx = $resolved.FontIdx
|
||||
LB = $resolved.LB; TB = $resolved.TB
|
||||
RB = $resolved.RB; BB = $resolved.BB
|
||||
HA = $resolved.HA; VA = $resolved.VA
|
||||
Wrap = $resolved.Wrap
|
||||
FillType = $resolved.FillType
|
||||
}
|
||||
return Register-Format -key $key -props $props
|
||||
}
|
||||
|
||||
# Pre-register all formats from areas
|
||||
foreach ($area in $def.areas) {
|
||||
foreach ($row in $area.rows) {
|
||||
# Row height format
|
||||
if ($row.height) {
|
||||
$hKey = Get-FormatKey -height ([int]$row.height)
|
||||
Register-Format -key $hKey -props @{ Height = [int]$row.height } | Out-Null
|
||||
}
|
||||
|
||||
# rowStyle gap-fill format (no content → no fillType)
|
||||
if ($row.rowStyle) {
|
||||
Register-CellFormat -styleName $row.rowStyle -fillType "" | Out-Null
|
||||
}
|
||||
|
||||
# Explicit cell formats
|
||||
if ($row.cells) {
|
||||
foreach ($cell in $row.cells) {
|
||||
$cellStyle = if ($cell.style) { $cell.style } elseif ($row.rowStyle) { $row.rowStyle } else { "default" }
|
||||
$ft = Get-FillType $cell
|
||||
Register-CellFormat -styleName $cellStyle -fillType $ft | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 7. Generate XML ---
|
||||
|
||||
$xml = New-Object System.Text.StringBuilder 4096
|
||||
|
||||
function X {
|
||||
param([string]$text)
|
||||
$script:xml.AppendLine($text) | Out-Null
|
||||
}
|
||||
|
||||
# 7a. Header
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X '<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" 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">'
|
||||
|
||||
# 7b. Language settings
|
||||
X "`t<languageSettings>"
|
||||
X "`t`t<currentLanguage>ru</currentLanguage>"
|
||||
X "`t`t<defaultLanguage>ru</defaultLanguage>"
|
||||
X "`t`t<languageInfo>"
|
||||
X "`t`t`t<id>ru</id>"
|
||||
X "`t`t`t<code>Русский</code>"
|
||||
X "`t`t`t<description>Русский</description>"
|
||||
X "`t`t</languageInfo>"
|
||||
X "`t</languageSettings>"
|
||||
|
||||
# 7c. Columns
|
||||
X "`t<columns>"
|
||||
X "`t`t<size>$totalColumns</size>"
|
||||
|
||||
# Emit columnsItem for columns with non-default widths
|
||||
foreach ($col in ($colFormatMap.Keys | Sort-Object)) {
|
||||
$fmtIdx = $colFormatMap[$col]
|
||||
$colIdx = $col - 1 # Convert to 0-based
|
||||
X "`t`t<columnsItem>"
|
||||
X "`t`t`t<index>$colIdx</index>"
|
||||
X "`t`t`t<column>"
|
||||
X "`t`t`t`t<formatIndex>$fmtIdx</formatIndex>"
|
||||
X "`t`t`t</column>"
|
||||
X "`t`t</columnsItem>"
|
||||
}
|
||||
|
||||
X "`t</columns>"
|
||||
|
||||
# 7d. Rows — main generation loop
|
||||
$globalRow = 0
|
||||
$merges = @()
|
||||
$namedItems = @()
|
||||
$totalRowCount = 0
|
||||
|
||||
foreach ($area in $def.areas) {
|
||||
$areaStartRow = $globalRow
|
||||
$areaName = $area.name
|
||||
|
||||
foreach ($row in $area.rows) {
|
||||
$rowHasContent = $false
|
||||
$rowCells = @() # array of { Col(0-based), FormatIdx, Content }
|
||||
|
||||
# Determine row height format
|
||||
$rowFormatIdx = 0
|
||||
if ($row.height) {
|
||||
$hKey = Get-FormatKey -height ([int]$row.height)
|
||||
# Find format index for this key
|
||||
$rIdx = 0
|
||||
foreach ($k in $formatRegistry.Keys) {
|
||||
$rIdx++
|
||||
if ($k -eq $hKey) { $rowFormatIdx = $rIdx; break }
|
||||
}
|
||||
}
|
||||
|
||||
if ($row.cells -and $row.cells.Count -gt 0) {
|
||||
$rowHasContent = $true
|
||||
|
||||
# Build set of occupied columns (1-based)
|
||||
$occupiedCols = @{}
|
||||
foreach ($cell in $row.cells) {
|
||||
$colStart = [int]$cell.col
|
||||
$colSpan = if ($cell.span) { [int]$cell.span } else { 1 }
|
||||
for ($c = $colStart; $c -lt ($colStart + $colSpan); $c++) {
|
||||
$occupiedCols[$c] = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Generate explicit cells
|
||||
foreach ($cell in $row.cells) {
|
||||
$colStart = [int]$cell.col
|
||||
$colSpan = if ($cell.span) { [int]$cell.span } else { 1 }
|
||||
$cellStyle = if ($cell.style) { $cell.style } elseif ($row.rowStyle) { $row.rowStyle } else { "default" }
|
||||
$ft = Get-FillType $cell
|
||||
$fmtIdx = Register-CellFormat -styleName $cellStyle -fillType $ft
|
||||
|
||||
$cellInfo = @{
|
||||
Col = $colStart - 1 # 0-based
|
||||
FormatIdx = $fmtIdx
|
||||
Param = $cell.param
|
||||
Detail = $cell.detail
|
||||
Text = $cell.text
|
||||
Template = $cell.template
|
||||
}
|
||||
$rowCells += $cellInfo
|
||||
|
||||
# Collect merge
|
||||
if ($colSpan -gt 1) {
|
||||
$merges += @{
|
||||
R = $globalRow
|
||||
C = $colStart - 1
|
||||
W = $colSpan - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generate gap-fill cells for rowStyle
|
||||
if ($row.rowStyle) {
|
||||
$gapFmtIdx = Register-CellFormat -styleName $row.rowStyle -fillType ""
|
||||
for ($c = 1; $c -le $totalColumns; $c++) {
|
||||
if (-not $occupiedCols.ContainsKey($c)) {
|
||||
$rowCells += @{
|
||||
Col = $c - 1 # 0-based
|
||||
FormatIdx = $gapFmtIdx
|
||||
Param = $null
|
||||
Detail = $null
|
||||
Text = $null
|
||||
Template = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Sort cells by column
|
||||
$rowCells = $rowCells | Sort-Object { $_.Col }
|
||||
|
||||
} elseif ($row.rowStyle) {
|
||||
# Row with only rowStyle, no explicit cells — fill all columns
|
||||
$rowHasContent = $true
|
||||
$gapFmtIdx = Register-CellFormat -styleName $row.rowStyle -fillType ""
|
||||
for ($c = 0; $c -lt $totalColumns; $c++) {
|
||||
$rowCells += @{
|
||||
Col = $c
|
||||
FormatIdx = $gapFmtIdx
|
||||
Param = $null
|
||||
Detail = $null
|
||||
Text = $null
|
||||
Template = $null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Emit rowsItem
|
||||
X "`t<rowsItem>"
|
||||
X "`t`t<index>$globalRow</index>"
|
||||
X "`t`t<row>"
|
||||
|
||||
if ($rowFormatIdx -gt 0) {
|
||||
X "`t`t`t<formatIndex>$rowFormatIdx</formatIndex>"
|
||||
}
|
||||
|
||||
if (-not $rowHasContent) {
|
||||
X "`t`t`t<empty>true</empty>"
|
||||
} else {
|
||||
foreach ($cellInfo in $rowCells) {
|
||||
X "`t`t`t<c>"
|
||||
X "`t`t`t`t<i>$($cellInfo.Col)</i>"
|
||||
X "`t`t`t`t<c>"
|
||||
X "`t`t`t`t`t<f>$($cellInfo.FormatIdx)</f>"
|
||||
|
||||
if ($cellInfo.Param) {
|
||||
X "`t`t`t`t`t<parameter>$($cellInfo.Param)</parameter>"
|
||||
if ($cellInfo.Detail) {
|
||||
X "`t`t`t`t`t<detailParameter>$($cellInfo.Detail)</detailParameter>"
|
||||
}
|
||||
}
|
||||
|
||||
if ($cellInfo.Text) {
|
||||
X "`t`t`t`t`t<tl>"
|
||||
X "`t`t`t`t`t`t<v8:item>"
|
||||
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "`t`t`t`t`t`t`t<v8:content>$($cellInfo.Text)</v8:content>"
|
||||
X "`t`t`t`t`t`t</v8:item>"
|
||||
X "`t`t`t`t`t</tl>"
|
||||
}
|
||||
|
||||
if ($cellInfo.Template) {
|
||||
X "`t`t`t`t`t<tl>"
|
||||
X "`t`t`t`t`t`t<v8:item>"
|
||||
X "`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "`t`t`t`t`t`t`t<v8:content>$($cellInfo.Template)</v8:content>"
|
||||
X "`t`t`t`t`t`t</v8:item>"
|
||||
X "`t`t`t`t`t</tl>"
|
||||
}
|
||||
|
||||
X "`t`t`t`t</c>"
|
||||
X "`t`t`t</c>"
|
||||
}
|
||||
}
|
||||
|
||||
X "`t`t</row>"
|
||||
X "`t</rowsItem>"
|
||||
|
||||
$globalRow++
|
||||
}
|
||||
|
||||
$areaEndRow = $globalRow - 1
|
||||
$namedItems += @{
|
||||
Name = $areaName
|
||||
BeginRow = $areaStartRow
|
||||
EndRow = $areaEndRow
|
||||
}
|
||||
}
|
||||
|
||||
$totalRowCount = $globalRow
|
||||
|
||||
# 7e. Scalar metadata
|
||||
X "`t<templateMode>true</templateMode>"
|
||||
X "`t<defaultFormatIndex>$defaultFormatIndex</defaultFormatIndex>"
|
||||
X "`t<height>$totalRowCount</height>"
|
||||
X "`t<vgRows>$totalRowCount</vgRows>"
|
||||
|
||||
# 7f. Merges
|
||||
foreach ($m in $merges) {
|
||||
X "`t<merge>"
|
||||
X "`t`t<r>$($m.R)</r>"
|
||||
X "`t`t<c>$($m.C)</c>"
|
||||
X "`t`t<w>$($m.W)</w>"
|
||||
X "`t</merge>"
|
||||
}
|
||||
|
||||
# 7g. Named items
|
||||
foreach ($ni in $namedItems) {
|
||||
X "`t<namedItem xsi:type=`"NamedItemCells`">"
|
||||
X "`t`t<name>$($ni.Name)</name>"
|
||||
X "`t`t<area>"
|
||||
X "`t`t`t<type>Rows</type>"
|
||||
X "`t`t`t<beginRow>$($ni.BeginRow)</beginRow>"
|
||||
X "`t`t`t<endRow>$($ni.EndRow)</endRow>"
|
||||
X "`t`t`t<beginColumn>-1</beginColumn>"
|
||||
X "`t`t`t<endColumn>-1</endColumn>"
|
||||
X "`t`t</area>"
|
||||
X "`t</namedItem>"
|
||||
}
|
||||
|
||||
# 7h. Line palette
|
||||
if ($hasBorders) {
|
||||
X "`t<line width=`"1`" gap=`"false`">"
|
||||
X "`t`t<v8ui:style xsi:type=`"v8ui:SpreadsheetDocumentCellLineType`">Solid</v8ui:style>"
|
||||
X "`t</line>"
|
||||
}
|
||||
|
||||
# 7i. Font palette
|
||||
foreach ($fe in $fontEntries) {
|
||||
X "`t<font faceName=`"$($fe.Face)`" height=`"$($fe.Size)`" bold=`"$($fe.Bold)`" italic=`"$($fe.Italic)`" underline=`"false`" strikeout=`"false`" kind=`"Absolute`" scale=`"100`"/>"
|
||||
}
|
||||
|
||||
# 7j. Format palette
|
||||
foreach ($key in $formatRegistry.Keys) {
|
||||
$fmt = $formatRegistry[$key]
|
||||
X "`t<format>"
|
||||
|
||||
if ($fmt.FontIdx -ne $null -and $fmt.FontIdx -ge 0) {
|
||||
X "`t`t<font>$($fmt.FontIdx)</font>"
|
||||
}
|
||||
if ($fmt.LB -ne $null -and $fmt.LB -ge 0) {
|
||||
X "`t`t<leftBorder>$($fmt.LB)</leftBorder>"
|
||||
}
|
||||
if ($fmt.TB -ne $null -and $fmt.TB -ge 0) {
|
||||
X "`t`t<topBorder>$($fmt.TB)</topBorder>"
|
||||
}
|
||||
if ($fmt.RB -ne $null -and $fmt.RB -ge 0) {
|
||||
X "`t`t<rightBorder>$($fmt.RB)</rightBorder>"
|
||||
}
|
||||
if ($fmt.BB -ne $null -and $fmt.BB -ge 0) {
|
||||
X "`t`t<bottomBorder>$($fmt.BB)</bottomBorder>"
|
||||
}
|
||||
if ($fmt.Width) {
|
||||
X "`t`t<width>$($fmt.Width)</width>"
|
||||
}
|
||||
if ($fmt.Height) {
|
||||
X "`t`t<height>$($fmt.Height)</height>"
|
||||
}
|
||||
if ($fmt.HA) {
|
||||
X "`t`t<horizontalAlignment>$($fmt.HA)</horizontalAlignment>"
|
||||
}
|
||||
if ($fmt.VA) {
|
||||
X "`t`t<verticalAlignment>$($fmt.VA)</verticalAlignment>"
|
||||
}
|
||||
if ($fmt.Wrap -eq $true) {
|
||||
X "`t`t<textPlacement>Wrap</textPlacement>"
|
||||
}
|
||||
if ($fmt.FillType) {
|
||||
X "`t`t<fillType>$($fmt.FillType)</fillType>"
|
||||
}
|
||||
|
||||
X "`t</format>"
|
||||
}
|
||||
|
||||
# 7k. Close document
|
||||
X '</document>'
|
||||
|
||||
# --- 8. Write output ---
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText((Join-Path (Get-Location) $OutputPath), $xml.ToString(), $enc)
|
||||
|
||||
# --- 9. Summary ---
|
||||
|
||||
Write-Host "[OK] Compiled: $OutputPath"
|
||||
Write-Host " Areas: $($namedItems.Count), Rows: $totalRowCount, Columns: $totalColumns"
|
||||
Write-Host " Fonts: $($fontEntries.Count), Lines: $(if ($hasBorders) { 1 } else { 0 }), Formats: $($formatRegistry.Count)"
|
||||
Write-Host " Merges: $($merges.Count)"
|
||||
@@ -18,6 +18,7 @@
|
||||
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в обработку БСП |
|
||||
| `/mxl-info` | `<TemplatePath>` | Анализ структуры табличного документа (области, параметры, колонки) |
|
||||
| `/mxl-validate` | `<TemplatePath>` | Валидация табличного документа (индексы, ссылки, границы) |
|
||||
| `/mxl-compile` | `<JsonPath> <OutputPath>` | Компиляция табличного документа из JSON-определения |
|
||||
|
||||
Навыки удаления (`epf-remove-*`) не вызываются Claude автоматически — только по явной команде пользователя.
|
||||
|
||||
@@ -142,7 +143,8 @@ src/
|
||||
├── epf-bsp-init/ # SKILL.md (шаблоны кода, без скриптов)
|
||||
├── epf-bsp-add-command/ # SKILL.md (шаблоны кода, без скриптов)
|
||||
├── mxl-info/ # SKILL.md + scripts/mxl-info.ps1
|
||||
└── mxl-validate/ # SKILL.md + scripts/mxl-validate.ps1
|
||||
├── mxl-validate/ # SKILL.md + scripts/mxl-validate.ps1
|
||||
└── mxl-compile/ # SKILL.md + scripts/mxl-compile.ps1
|
||||
docs/
|
||||
├── 1c-xml-format-spec.md # Спецификация XML-формата выгрузки
|
||||
├── 1c-help-spec.md # Спецификация встроенной справки
|
||||
|
||||
Reference in New Issue
Block a user