mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-29 16:11:01 +03:00
Add mxl-info and mxl-validate skills for SpreadsheetDocument analysis
- /mxl-info: extracts compact template structure (areas, parameters, column sets) from Template.xml. Supports -WithText for cell content, -Format json, and output truncation protection. - /mxl-validate: 12 structural checks (format/font/line indices, column bounds per column set, named area ranges, merge bounds, columnsID references). Exit code 1 on errors. Tested on 3 real templates: label (simple), invoice (medium), УКД (complex — 7 column sets, Rectangle areas). Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
fdf661192d
commit
76a0dd80be
@@ -0,0 +1,103 @@
|
|||||||
|
---
|
||||||
|
name: mxl-info
|
||||||
|
description: Analyze SpreadsheetDocument (MXL) template structure — areas, parameters, column sets
|
||||||
|
argument-hint: <TemplatePath> or <ProcessorName> <TemplateName>
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Glob
|
||||||
|
---
|
||||||
|
|
||||||
|
# /mxl-info — Template Structure Analyzer
|
||||||
|
|
||||||
|
Reads a SpreadsheetDocument Template.xml and outputs a compact summary: named areas, parameters, column sets. Replaces the need to read thousands of XML lines.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/mxl-info <TemplatePath>
|
||||||
|
/mxl-info <ProcessorName> <TemplateName>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
|---------------|:--------:|---------|------------------------------------------|
|
||||||
|
| TemplatePath | no | — | Direct path to Template.xml |
|
||||||
|
| ProcessorName | no | — | Processor name (alternative to path) |
|
||||||
|
| TemplateName | no | — | Template name (alternative to path) |
|
||||||
|
| SrcDir | no | `src` | Source directory |
|
||||||
|
| Format | no | `text` | Output format: `text` or `json` |
|
||||||
|
| WithText | no | false | Include static text and template content |
|
||||||
|
| MaxParams | no | 10 | Max parameters listed per area |
|
||||||
|
| Limit | no | 150 | Max output lines (truncation protection) |
|
||||||
|
| Offset | no | 0 | Skip N lines (for pagination) |
|
||||||
|
|
||||||
|
Specify either `-TemplatePath` or both `-ProcessorName` and `-TemplateName`.
|
||||||
|
|
||||||
|
## Command
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/mxl-info/scripts/mxl-info.ps1 -TemplatePath "<path>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with processor/template names:
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/mxl-info/scripts/mxl-info.ps1 -ProcessorName "<Name>" -TemplateName "<Template>" [-SrcDir "<dir>"]
|
||||||
|
```
|
||||||
|
|
||||||
|
Additional flags:
|
||||||
|
```powershell
|
||||||
|
... -WithText # include cell text content
|
||||||
|
... -Format json # JSON output for programmatic use
|
||||||
|
... -MaxParams 20 # show more parameters per area
|
||||||
|
... -Offset 150 # pagination: skip first 150 lines
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output (text mode)
|
||||||
|
|
||||||
|
```
|
||||||
|
=== TemplName ===
|
||||||
|
Rows: 40, Columns: 33
|
||||||
|
Column sets: 1 (default only)
|
||||||
|
|
||||||
|
--- Named areas ---
|
||||||
|
Заголовок Rows rows 1-4 (1 params)
|
||||||
|
Строка Rows rows 14-14 (8 params)
|
||||||
|
Итого Rows rows 16-17 (1 params)
|
||||||
|
|
||||||
|
--- Parameters by area ---
|
||||||
|
Заголовок: ТекстЗаголовка
|
||||||
|
Строка: НомерСтроки, Товар, Количество, Цена, Сумма, ... (+3)
|
||||||
|
Итого: Всего
|
||||||
|
|
||||||
|
--- Stats ---
|
||||||
|
Merges: 43
|
||||||
|
Drawings: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
With `-WithText`, adds a section showing static text (labels, headers) and template strings:
|
||||||
|
|
||||||
|
```
|
||||||
|
--- Text content ---
|
||||||
|
ШапкаТаблицы:
|
||||||
|
Text: "№", "Товар", "Ед. изм.", "Кол-во", "Цена", "Сумма"
|
||||||
|
Строка:
|
||||||
|
Templates: "[НомерСтроки]", "[Товар] ([Артикул])"
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- **Before writing fill code**: run `/mxl-info` to understand the template structure, then write BSL code based on area names and parameter lists
|
||||||
|
- **With `-WithText`**: when you need context about what labels/headers surround the parameters
|
||||||
|
- **With `-Format json`**: when you need structured data for programmatic processing
|
||||||
|
- **For existing templates**: analyze uploaded or configuration templates without reading raw XML
|
||||||
|
|
||||||
|
## Truncation Protection
|
||||||
|
|
||||||
|
Output is limited to 150 lines by default. If exceeded:
|
||||||
|
```
|
||||||
|
[TRUNCATED] Shown 150 of 220 lines. Use -Offset 150 to continue.
|
||||||
|
```
|
||||||
|
|
||||||
|
Use `-Offset N` and `-Limit N` to paginate through large outputs.
|
||||||
@@ -0,0 +1,417 @@
|
|||||||
|
param(
|
||||||
|
[string]$TemplatePath,
|
||||||
|
[string]$ProcessorName,
|
||||||
|
[string]$TemplateName,
|
||||||
|
[string]$SrcDir = "src",
|
||||||
|
[ValidateSet("text", "json")]
|
||||||
|
[string]$Format = "text",
|
||||||
|
[switch]$WithText,
|
||||||
|
[int]$MaxParams = 10,
|
||||||
|
[int]$Limit = 150,
|
||||||
|
[int]$Offset = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
# --- Resolve template path ---
|
||||||
|
|
||||||
|
if (-not $TemplatePath) {
|
||||||
|
if (-not $ProcessorName -or -not $TemplateName) {
|
||||||
|
Write-Error "Specify -TemplatePath or both -ProcessorName and -TemplateName"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$TemplatePath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $SrcDir $ProcessorName) "Templates") $TemplateName) "Ext") "Template.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $TemplatePath)) {
|
||||||
|
Write-Error "File not found: $TemplatePath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Load XML ---
|
||||||
|
|
||||||
|
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||||
|
$xmlDoc.PreserveWhitespace = $false
|
||||||
|
$xmlDoc.Load((Resolve-Path $TemplatePath).Path)
|
||||||
|
|
||||||
|
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||||
|
$nsMgr.AddNamespace("d", "http://v8.1c.ru/8.2/data/spreadsheet")
|
||||||
|
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||||
|
$nsMgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
|
||||||
|
$root = $xmlDoc.DocumentElement
|
||||||
|
|
||||||
|
# --- Column sets ---
|
||||||
|
|
||||||
|
$columnSets = @()
|
||||||
|
$defaultColCount = 0
|
||||||
|
|
||||||
|
foreach ($cols in $root.SelectNodes("d:columns", $nsMgr)) {
|
||||||
|
$sizeNode = $cols.SelectSingleNode("d:size", $nsMgr)
|
||||||
|
$idNode = $cols.SelectSingleNode("d:id", $nsMgr)
|
||||||
|
$size = if ($sizeNode) { [int]$sizeNode.InnerText } else { 0 }
|
||||||
|
|
||||||
|
if ($idNode) {
|
||||||
|
$columnSets += @{ Id = $idNode.InnerText; Size = $size }
|
||||||
|
} else {
|
||||||
|
$defaultColCount = $size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Rows: collect row data ---
|
||||||
|
|
||||||
|
$rowNodes = $root.SelectNodes("d:rowsItem", $nsMgr)
|
||||||
|
$totalRows = $rowNodes.Count
|
||||||
|
|
||||||
|
$heightNode = $root.SelectSingleNode("d:height", $nsMgr)
|
||||||
|
$docHeight = if ($heightNode) { [int]$heightNode.InnerText } else { $totalRows }
|
||||||
|
|
||||||
|
# --- Named items ---
|
||||||
|
|
||||||
|
$namedAreas = @()
|
||||||
|
$namedDrawings = @()
|
||||||
|
|
||||||
|
foreach ($ni in $root.SelectNodes("d:namedItem", $nsMgr)) {
|
||||||
|
$niType = $ni.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
$name = $ni.SelectSingleNode("d:name", $nsMgr).InnerText
|
||||||
|
|
||||||
|
if ($niType -like "*NamedItemCells*") {
|
||||||
|
$area = $ni.SelectSingleNode("d:area", $nsMgr)
|
||||||
|
$areaType = $area.SelectSingleNode("d:type", $nsMgr).InnerText
|
||||||
|
$beginRow = [int]$area.SelectSingleNode("d:beginRow", $nsMgr).InnerText
|
||||||
|
$endRow = [int]$area.SelectSingleNode("d:endRow", $nsMgr).InnerText
|
||||||
|
$beginCol = [int]$area.SelectSingleNode("d:beginColumn", $nsMgr).InnerText
|
||||||
|
$endCol = [int]$area.SelectSingleNode("d:endColumn", $nsMgr).InnerText
|
||||||
|
$colsId = $null
|
||||||
|
$colsIdNode = $area.SelectSingleNode("d:columnsID", $nsMgr)
|
||||||
|
if ($colsIdNode) { $colsId = $colsIdNode.InnerText }
|
||||||
|
|
||||||
|
$namedAreas += @{
|
||||||
|
Name = $name
|
||||||
|
AreaType = $areaType
|
||||||
|
BeginRow = $beginRow
|
||||||
|
EndRow = $endRow
|
||||||
|
BeginCol = $beginCol
|
||||||
|
EndCol = $endCol
|
||||||
|
ColumnsID = $colsId
|
||||||
|
}
|
||||||
|
} elseif ($niType -like "*NamedItemDrawing*") {
|
||||||
|
$drawId = $ni.SelectSingleNode("d:drawingID", $nsMgr).InnerText
|
||||||
|
$namedDrawings += @{ Name = $name; DrawingID = $drawId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Scan rows for parameters and text ---
|
||||||
|
|
||||||
|
# Build row index map: rowIndex -> XmlNode
|
||||||
|
$rowMap = @{}
|
||||||
|
foreach ($ri in $rowNodes) {
|
||||||
|
$idx = [int]$ri.SelectSingleNode("d:index", $nsMgr).InnerText
|
||||||
|
$rowMap[$idx] = $ri
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-CellData {
|
||||||
|
param($rowNode, [System.Xml.XmlNamespaceManager]$ns, [bool]$includeText)
|
||||||
|
|
||||||
|
$row = $rowNode.SelectSingleNode("d:row", $ns)
|
||||||
|
if (-not $row) { return @() }
|
||||||
|
|
||||||
|
$results = @()
|
||||||
|
foreach ($cGroup in $row.SelectNodes("d:c", $ns)) {
|
||||||
|
$cell = $cGroup.SelectSingleNode("d:c", $ns)
|
||||||
|
if (-not $cell) { continue }
|
||||||
|
|
||||||
|
$param = $cell.SelectSingleNode("d:parameter", $ns)
|
||||||
|
$detail = $cell.SelectSingleNode("d:detailParameter", $ns)
|
||||||
|
$tl = $cell.SelectSingleNode("d:tl", $ns)
|
||||||
|
|
||||||
|
if ($param) {
|
||||||
|
$entry = @{ Kind = "Parameter"; Value = $param.InnerText }
|
||||||
|
if ($detail) { $entry.Detail = $detail.InnerText }
|
||||||
|
$results += $entry
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($includeText -and $tl) {
|
||||||
|
$content = $tl.SelectSingleNode("v8:item/v8:content", $ns)
|
||||||
|
if ($content -and $content.InnerText) {
|
||||||
|
$text = $content.InnerText
|
||||||
|
# Detect if this is a Template (has [...] placeholders)
|
||||||
|
if ($text -match '\[.+\]') {
|
||||||
|
$results += @{ Kind = "Template"; Value = $text }
|
||||||
|
} else {
|
||||||
|
$results += @{ Kind = "Text"; Value = $text }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $results
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-AreaCellData {
|
||||||
|
param(
|
||||||
|
[hashtable]$area,
|
||||||
|
[hashtable]$rowMap,
|
||||||
|
[System.Xml.XmlNamespaceManager]$ns,
|
||||||
|
[bool]$includeText
|
||||||
|
)
|
||||||
|
|
||||||
|
$params = @()
|
||||||
|
$texts = @()
|
||||||
|
$templates = @()
|
||||||
|
|
||||||
|
$startRow = $area.BeginRow
|
||||||
|
$endRow = $area.EndRow
|
||||||
|
if ($startRow -eq -1) { $startRow = 0 }
|
||||||
|
if ($endRow -eq -1) { $endRow = $docHeight - 1 }
|
||||||
|
|
||||||
|
for ($r = $startRow; $r -le $endRow; $r++) {
|
||||||
|
if ($rowMap.ContainsKey($r)) {
|
||||||
|
$cells = Get-CellData -rowNode $rowMap[$r] -ns $ns -includeText $includeText
|
||||||
|
foreach ($c in $cells) {
|
||||||
|
switch ($c.Kind) {
|
||||||
|
"Parameter" { $params += $c.Value }
|
||||||
|
"Text" { $texts += $c.Value }
|
||||||
|
"Template" { $templates += $c.Value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{ Params = $params; Texts = $texts; Templates = $templates }
|
||||||
|
}
|
||||||
|
|
||||||
|
# Collect data for each area
|
||||||
|
$areaData = @()
|
||||||
|
$coveredRows = @{}
|
||||||
|
|
||||||
|
foreach ($area in $namedAreas) {
|
||||||
|
$data = Get-AreaCellData -area $area -rowMap $rowMap -ns $nsMgr -includeText $WithText
|
||||||
|
$areaData += @{
|
||||||
|
Area = $area
|
||||||
|
Params = $data.Params
|
||||||
|
Texts = $data.Texts
|
||||||
|
Templates = $data.Templates
|
||||||
|
}
|
||||||
|
|
||||||
|
# Track covered rows
|
||||||
|
$sr = $area.BeginRow
|
||||||
|
$er = $area.EndRow
|
||||||
|
if ($sr -ne -1 -and $er -ne -1) {
|
||||||
|
for ($r = $sr; $r -le $er; $r++) {
|
||||||
|
$coveredRows[$r] = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find parameters outside named areas
|
||||||
|
$outsideParams = @()
|
||||||
|
$outsideTexts = @()
|
||||||
|
$outsideTemplates = @()
|
||||||
|
|
||||||
|
foreach ($r in $rowMap.Keys | Sort-Object) {
|
||||||
|
if (-not $coveredRows.ContainsKey($r)) {
|
||||||
|
$cells = Get-CellData -rowNode $rowMap[$r] -ns $nsMgr -includeText $WithText
|
||||||
|
foreach ($c in $cells) {
|
||||||
|
switch ($c.Kind) {
|
||||||
|
"Parameter" { $outsideParams += $c.Value }
|
||||||
|
"Text" { $outsideTexts += $c.Value }
|
||||||
|
"Template" { $outsideTemplates += $c.Value }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Counts ---
|
||||||
|
|
||||||
|
$mergeCount = $root.SelectNodes("d:merge", $nsMgr).Count
|
||||||
|
$drawingNodes = $root.SelectNodes("d:drawing", $nsMgr)
|
||||||
|
$drawingCount = $drawingNodes.Count
|
||||||
|
|
||||||
|
# --- Output ---
|
||||||
|
|
||||||
|
function Truncate-List {
|
||||||
|
param([string[]]$items, [int]$max)
|
||||||
|
if ($items.Count -le $max) {
|
||||||
|
return ($items -join ", ")
|
||||||
|
}
|
||||||
|
$shown = ($items[0..($max - 1)] -join ", ")
|
||||||
|
$remaining = $items.Count - $max
|
||||||
|
return "$shown, ... (+$remaining)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine template name from path
|
||||||
|
$templateName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($TemplatePath)))
|
||||||
|
|
||||||
|
if ($Format -eq "json") {
|
||||||
|
$result = @{
|
||||||
|
name = $templateName
|
||||||
|
rows = $docHeight
|
||||||
|
columns = $defaultColCount
|
||||||
|
columnSets = @($columnSets)
|
||||||
|
areas = @()
|
||||||
|
outsideParams = @($outsideParams)
|
||||||
|
mergeCount = $mergeCount
|
||||||
|
drawingCount = $drawingCount
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($ad in $areaData) {
|
||||||
|
$areaObj = @{
|
||||||
|
name = $ad.Area.Name
|
||||||
|
type = $ad.Area.AreaType
|
||||||
|
beginRow = $ad.Area.BeginRow
|
||||||
|
endRow = $ad.Area.EndRow
|
||||||
|
beginCol = $ad.Area.BeginCol
|
||||||
|
endCol = $ad.Area.EndCol
|
||||||
|
params = @($ad.Params)
|
||||||
|
}
|
||||||
|
if ($ad.Area.ColumnsID) { $areaObj.columnsID = $ad.Area.ColumnsID }
|
||||||
|
if ($WithText) {
|
||||||
|
$areaObj.texts = @($ad.Texts)
|
||||||
|
$areaObj.templates = @($ad.Templates)
|
||||||
|
}
|
||||||
|
$result.areas += $areaObj
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($WithText) {
|
||||||
|
$result.outsideTexts = @($outsideTexts)
|
||||||
|
$result.outsideTemplates = @($outsideTemplates)
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($nd in $namedDrawings) {
|
||||||
|
$result.areas += @{
|
||||||
|
name = $nd.Name
|
||||||
|
type = "Drawing"
|
||||||
|
drawingID = $nd.DrawingID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$result | ConvertTo-Json -Depth 5
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Text format output ---
|
||||||
|
|
||||||
|
$lines = @()
|
||||||
|
|
||||||
|
$lines += "=== $templateName ==="
|
||||||
|
$lines += " Rows: $docHeight, Columns: $defaultColCount"
|
||||||
|
|
||||||
|
if ($columnSets.Count -eq 0) {
|
||||||
|
$lines += " Column sets: 1 (default only)"
|
||||||
|
} else {
|
||||||
|
$lines += " Column sets: $($columnSets.Count + 1) (default + $($columnSets.Count) additional)"
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines += ""
|
||||||
|
$lines += "--- Named areas ---"
|
||||||
|
|
||||||
|
foreach ($ad in $areaData) {
|
||||||
|
$a = $ad.Area
|
||||||
|
$paramCount = $ad.Params.Count
|
||||||
|
$rowRange = ""
|
||||||
|
|
||||||
|
switch ($a.AreaType) {
|
||||||
|
"Rows" { $rowRange = "rows $($a.BeginRow)-$($a.EndRow)" }
|
||||||
|
"Columns" { $rowRange = "cols $($a.BeginCol)-$($a.EndCol)" }
|
||||||
|
"Rectangle" { $rowRange = "rows $($a.BeginRow)-$($a.EndRow), cols $($a.BeginCol)-$($a.EndCol)" }
|
||||||
|
}
|
||||||
|
|
||||||
|
$colsInfo = ""
|
||||||
|
if ($a.ColumnsID) {
|
||||||
|
$colsInfo = " [colset]"
|
||||||
|
}
|
||||||
|
|
||||||
|
$paramInfo = "($paramCount params)"
|
||||||
|
$nameStr = $a.Name.PadRight(25)
|
||||||
|
$typeStr = $a.AreaType.PadRight(12)
|
||||||
|
$lines += " $nameStr $typeStr $rowRange $paramInfo$colsInfo"
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($nd in $namedDrawings) {
|
||||||
|
$nameStr = $nd.Name.PadRight(25)
|
||||||
|
$lines += " $nameStr Drawing drawingID=$($nd.DrawingID)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Parameters by area
|
||||||
|
$hasParams = ($areaData | Where-Object { $_.Params.Count -gt 0 }) -or ($outsideParams.Count -gt 0)
|
||||||
|
|
||||||
|
if ($hasParams) {
|
||||||
|
$lines += ""
|
||||||
|
$lines += "--- Parameters by area ---"
|
||||||
|
foreach ($ad in $areaData) {
|
||||||
|
if ($ad.Params.Count -gt 0) {
|
||||||
|
$paramStr = Truncate-List -items $ad.Params -max $MaxParams
|
||||||
|
$lines += " $($ad.Area.Name): $paramStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($outsideParams.Count -gt 0) {
|
||||||
|
$paramStr = Truncate-List -items $outsideParams -max $MaxParams
|
||||||
|
$lines += " (outside areas): $paramStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# WithText sections
|
||||||
|
if ($WithText) {
|
||||||
|
$hasText = ($areaData | Where-Object { $_.Texts.Count -gt 0 -or $_.Templates.Count -gt 0 }) -or ($outsideTexts.Count -gt 0) -or ($outsideTemplates.Count -gt 0)
|
||||||
|
|
||||||
|
if ($hasText) {
|
||||||
|
$lines += ""
|
||||||
|
$lines += "--- Text content ---"
|
||||||
|
foreach ($ad in $areaData) {
|
||||||
|
if ($ad.Texts.Count -gt 0 -or $ad.Templates.Count -gt 0) {
|
||||||
|
$lines += " $($ad.Area.Name):"
|
||||||
|
if ($ad.Texts.Count -gt 0) {
|
||||||
|
$textItems = $ad.Texts | ForEach-Object { "`"$_`"" }
|
||||||
|
$textStr = Truncate-List -items $textItems -max $MaxParams
|
||||||
|
$lines += " Text: $textStr"
|
||||||
|
}
|
||||||
|
if ($ad.Templates.Count -gt 0) {
|
||||||
|
$tplItems = $ad.Templates | ForEach-Object { "`"$_`"" }
|
||||||
|
$tplStr = Truncate-List -items $tplItems -max $MaxParams
|
||||||
|
$lines += " Templates: $tplStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($outsideTexts.Count -gt 0 -or $outsideTemplates.Count -gt 0) {
|
||||||
|
$lines += " (outside areas):"
|
||||||
|
if ($outsideTexts.Count -gt 0) {
|
||||||
|
$textItems = $outsideTexts | ForEach-Object { "`"$_`"" }
|
||||||
|
$textStr = Truncate-List -items $textItems -max $MaxParams
|
||||||
|
$lines += " Text: $textStr"
|
||||||
|
}
|
||||||
|
if ($outsideTemplates.Count -gt 0) {
|
||||||
|
$tplItems = $outsideTemplates | ForEach-Object { "`"$_`"" }
|
||||||
|
$tplStr = Truncate-List -items $tplItems -max $MaxParams
|
||||||
|
$lines += " Templates: $tplStr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$lines += ""
|
||||||
|
$lines += "--- Stats ---"
|
||||||
|
$lines += " Merges: $mergeCount"
|
||||||
|
$lines += " Drawings: $drawingCount"
|
||||||
|
|
||||||
|
# --- Truncation protection ---
|
||||||
|
|
||||||
|
$totalLines = $lines.Count
|
||||||
|
|
||||||
|
if ($Offset -gt 0) {
|
||||||
|
if ($Offset -ge $totalLines) {
|
||||||
|
Write-Host "[INFO] Offset $Offset exceeds total lines ($totalLines). Nothing to show."
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
$lines = $lines[$Offset..($totalLines - 1)]
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($lines.Count -gt $Limit) {
|
||||||
|
$shown = $lines[0..($Limit - 1)]
|
||||||
|
foreach ($l in $shown) { Write-Host $l }
|
||||||
|
$remaining = $totalLines - $Offset - $Limit
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "[TRUNCATED] Shown $Limit of $totalLines lines. Use -Offset $($Offset + $Limit) to continue."
|
||||||
|
} else {
|
||||||
|
foreach ($l in $lines) { Write-Host $l }
|
||||||
|
}
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
---
|
||||||
|
name: mxl-validate
|
||||||
|
description: Validate SpreadsheetDocument (MXL) template for structural correctness
|
||||||
|
argument-hint: <TemplatePath> or <ProcessorName> <TemplateName>
|
||||||
|
allowed-tools:
|
||||||
|
- Bash
|
||||||
|
- Read
|
||||||
|
- Glob
|
||||||
|
---
|
||||||
|
|
||||||
|
# /mxl-validate — Template Validator
|
||||||
|
|
||||||
|
Checks Template.xml for structural errors that the 1C platform may silently ignore (potentially causing data loss or template corruption).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
/mxl-validate <TemplatePath>
|
||||||
|
/mxl-validate <ProcessorName> <TemplateName>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Parameters
|
||||||
|
|
||||||
|
| Parameter | Required | Default | Description |
|
||||||
|
|---------------|:--------:|---------|------------------------------------------|
|
||||||
|
| TemplatePath | no | — | Direct path to Template.xml |
|
||||||
|
| ProcessorName | no | — | Processor name (alternative to path) |
|
||||||
|
| TemplateName | no | — | Template name (alternative to path) |
|
||||||
|
| SrcDir | no | `src` | Source directory |
|
||||||
|
| MaxErrors | no | 20 | Stop after N errors |
|
||||||
|
|
||||||
|
Specify either `-TemplatePath` or both `-ProcessorName` and `-TemplateName`.
|
||||||
|
|
||||||
|
## Command
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -TemplatePath "<path>"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or with processor/template names:
|
||||||
|
```powershell
|
||||||
|
powershell.exe -NoProfile -File .claude/skills/mxl-validate/scripts/mxl-validate.ps1 -ProcessorName "<Name>" -TemplateName "<Template>" [-SrcDir "<dir>"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Checks Performed
|
||||||
|
|
||||||
|
| # | Check | Severity |
|
||||||
|
|---|---|---|
|
||||||
|
| 1 | `<height>` >= max row index + 1 | ERROR |
|
||||||
|
| 2 | `<vgRows>` <= `<height>` | WARN |
|
||||||
|
| 3 | Cell format indices (`<f>`) within format palette | ERROR |
|
||||||
|
| 4 | Row/column `<formatIndex>` within format palette | ERROR |
|
||||||
|
| 5 | Cell column indices (`<i>`) within column count (per column set) | ERROR |
|
||||||
|
| 6 | Row `<columnsID>` references existing column set | ERROR |
|
||||||
|
| 7 | Merge/namedItem `<columnsID>` references existing column set | ERROR |
|
||||||
|
| 8 | Named area row/column ranges within document bounds | ERROR |
|
||||||
|
| 9 | Merge ranges within document bounds | ERROR |
|
||||||
|
| 10 | Font indices in formats within font palette | ERROR |
|
||||||
|
| 11 | Border/line indices in formats within line palette | ERROR |
|
||||||
|
| 12 | Drawing `pictureIndex` references existing picture | ERROR |
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
```
|
||||||
|
=== Validation: TemplateName ===
|
||||||
|
|
||||||
|
[OK] height (40) >= max row index + 1 (40), rowsItem count=34
|
||||||
|
[OK] Font refs: max=3, palette size=4
|
||||||
|
[ERROR] Row 15: cell format index 38 > format palette size (37)
|
||||||
|
[OK] Column indices: max in default set=32, default column count=33
|
||||||
|
---
|
||||||
|
Errors: 1, Warnings: 0
|
||||||
|
```
|
||||||
|
|
||||||
|
Exit code: 0 = all checks passed, 1 = errors found.
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
- **After generating a template**: run validator to catch structural errors before building EPF
|
||||||
|
- **After editing Template.xml**: verify indices and references are still valid
|
||||||
|
- **On errors**: fix the reported issues and re-run until all checks pass
|
||||||
|
|
||||||
|
## Error Protection
|
||||||
|
|
||||||
|
Stops after 20 errors by default (configurable with `-MaxErrors`). Summary line always shows total counts.
|
||||||
@@ -0,0 +1,395 @@
|
|||||||
|
param(
|
||||||
|
[string]$TemplatePath,
|
||||||
|
[string]$ProcessorName,
|
||||||
|
[string]$TemplateName,
|
||||||
|
[string]$SrcDir = "src",
|
||||||
|
[int]$MaxErrors = 20
|
||||||
|
)
|
||||||
|
|
||||||
|
$ErrorActionPreference = "Stop"
|
||||||
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||||
|
|
||||||
|
# --- Resolve template path ---
|
||||||
|
|
||||||
|
if (-not $TemplatePath) {
|
||||||
|
if (-not $ProcessorName -or -not $TemplateName) {
|
||||||
|
Write-Error "Specify -TemplatePath or both -ProcessorName and -TemplateName"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
$TemplatePath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $SrcDir $ProcessorName) "Templates") $TemplateName) "Ext") "Template.xml"
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path $TemplatePath)) {
|
||||||
|
Write-Error "File not found: $TemplatePath"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Load XML ---
|
||||||
|
|
||||||
|
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||||
|
$xmlDoc.PreserveWhitespace = $false
|
||||||
|
$xmlDoc.Load((Resolve-Path $TemplatePath).Path)
|
||||||
|
|
||||||
|
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||||
|
$nsMgr.AddNamespace("d", "http://v8.1c.ru/8.2/data/spreadsheet")
|
||||||
|
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||||
|
$nsMgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
|
||||||
|
$root = $xmlDoc.DocumentElement
|
||||||
|
|
||||||
|
# --- Counters ---
|
||||||
|
|
||||||
|
$errors = 0
|
||||||
|
$warnings = 0
|
||||||
|
$stopped = $false
|
||||||
|
|
||||||
|
function Report-OK {
|
||||||
|
param([string]$msg)
|
||||||
|
Write-Host "[OK] $msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
function Report-Error {
|
||||||
|
param([string]$msg)
|
||||||
|
$script:errors++
|
||||||
|
Write-Host "[ERROR] $msg"
|
||||||
|
if ($script:errors -ge $MaxErrors) {
|
||||||
|
$script:stopped = $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Report-Warn {
|
||||||
|
param([string]$msg)
|
||||||
|
$script:warnings++
|
||||||
|
Write-Host "[WARN] $msg"
|
||||||
|
}
|
||||||
|
|
||||||
|
$templateName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($TemplatePath)))
|
||||||
|
Write-Host "=== Validation: $templateName ==="
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# --- Collect palettes ---
|
||||||
|
|
||||||
|
$lineNodes = $root.SelectNodes("d:line", $nsMgr)
|
||||||
|
$lineCount = $lineNodes.Count
|
||||||
|
|
||||||
|
$fontNodes = @()
|
||||||
|
foreach ($node in $root.ChildNodes) {
|
||||||
|
if ($node.LocalName -eq "font") { $fontNodes += $node }
|
||||||
|
}
|
||||||
|
$fontCount = $fontNodes.Count
|
||||||
|
|
||||||
|
$formatNodes = @()
|
||||||
|
foreach ($node in $root.ChildNodes) {
|
||||||
|
if ($node.LocalName -eq "format") { $formatNodes += $node }
|
||||||
|
}
|
||||||
|
$formatCount = $formatNodes.Count
|
||||||
|
|
||||||
|
$pictureNodes = $root.SelectNodes("d:picture", $nsMgr)
|
||||||
|
$pictureCount = $pictureNodes.Count
|
||||||
|
|
||||||
|
# --- Collect column sets ---
|
||||||
|
|
||||||
|
$columnSets = @{} # id -> size
|
||||||
|
$defaultColCount = 0
|
||||||
|
|
||||||
|
foreach ($cols in $root.SelectNodes("d:columns", $nsMgr)) {
|
||||||
|
$sizeNode = $cols.SelectSingleNode("d:size", $nsMgr)
|
||||||
|
$idNode = $cols.SelectSingleNode("d:id", $nsMgr)
|
||||||
|
$size = if ($sizeNode) { [int]$sizeNode.InnerText } else { 0 }
|
||||||
|
|
||||||
|
if ($idNode) {
|
||||||
|
$columnSets[$idNode.InnerText] = $size
|
||||||
|
} else {
|
||||||
|
$defaultColCount = $size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 1: height vs actual rows ---
|
||||||
|
|
||||||
|
$rowNodes = $root.SelectNodes("d:rowsItem", $nsMgr)
|
||||||
|
$heightNode = $root.SelectSingleNode("d:height", $nsMgr)
|
||||||
|
$docHeight = if ($heightNode) { [int]$heightNode.InnerText } else { 0 }
|
||||||
|
|
||||||
|
# Find max row index (not all rows have rowsItem - implicit rows are skipped)
|
||||||
|
$maxRowIndex = -1
|
||||||
|
foreach ($ri in $rowNodes) {
|
||||||
|
$idxNode = $ri.SelectSingleNode("d:index", $nsMgr)
|
||||||
|
if ($idxNode) {
|
||||||
|
$idx = [int]$idxNode.InnerText
|
||||||
|
if ($idx -gt $maxRowIndex) { $maxRowIndex = $idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectedMinHeight = $maxRowIndex + 1
|
||||||
|
if ($docHeight -ge $expectedMinHeight) {
|
||||||
|
Report-OK "height ($docHeight) >= max row index + 1 ($expectedMinHeight), rowsItem count=$($rowNodes.Count)"
|
||||||
|
} else {
|
||||||
|
Report-Error "height=$docHeight but max row index=$maxRowIndex (need at least $expectedMinHeight)"
|
||||||
|
}
|
||||||
|
# --- Check 2: vgRows <= height ---
|
||||||
|
|
||||||
|
$vgRowsNode = $root.SelectSingleNode("d:vgRows", $nsMgr)
|
||||||
|
if ($vgRowsNode) {
|
||||||
|
$vgRows = [int]$vgRowsNode.InnerText
|
||||||
|
if ($vgRows -le $docHeight) {
|
||||||
|
Report-OK "vgRows ($vgRows) <= height ($docHeight)"
|
||||||
|
} else {
|
||||||
|
Report-Warn "vgRows ($vgRows) > height ($docHeight)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Build row data for checks ---
|
||||||
|
|
||||||
|
$maxFormatRef = 0
|
||||||
|
$maxFontRef = 0
|
||||||
|
$maxLineRef = 0
|
||||||
|
|
||||||
|
# Check format palette references in formats (font, border indices)
|
||||||
|
foreach ($fmt in $formatNodes) {
|
||||||
|
$fontIdx = $fmt.SelectSingleNode("d:font", $nsMgr)
|
||||||
|
if ($fontIdx) {
|
||||||
|
$val = [int]$fontIdx.InnerText
|
||||||
|
if ($val -gt $maxFontRef) { $maxFontRef = $val }
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($border in @("d:leftBorder", "d:topBorder", "d:rightBorder", "d:bottomBorder", "d:drawingBorder")) {
|
||||||
|
$borderNode = $fmt.SelectSingleNode($border, $nsMgr)
|
||||||
|
if ($borderNode) {
|
||||||
|
$val = [int]$borderNode.InnerText
|
||||||
|
if ($val -gt $maxLineRef) { $maxLineRef = $val }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 10: font indices in formats ---
|
||||||
|
|
||||||
|
if ($fontCount -gt 0) {
|
||||||
|
if ($maxFontRef -lt $fontCount) {
|
||||||
|
Report-OK "Font refs: max=$maxFontRef, palette size=$fontCount"
|
||||||
|
} else {
|
||||||
|
Report-Error "Font index $maxFontRef exceeds palette size ($fontCount)"
|
||||||
|
}
|
||||||
|
} elseif ($maxFontRef -gt 0) {
|
||||||
|
Report-Error "Font index $maxFontRef referenced but no fonts defined"
|
||||||
|
} else {
|
||||||
|
Report-OK "No font references"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 11: line/border indices in formats ---
|
||||||
|
|
||||||
|
if ($lineCount -gt 0) {
|
||||||
|
if ($maxLineRef -lt $lineCount) {
|
||||||
|
Report-OK "Line/border refs: max=$maxLineRef, palette size=$lineCount"
|
||||||
|
} else {
|
||||||
|
Report-Error "Line index $maxLineRef exceeds palette size ($lineCount)"
|
||||||
|
}
|
||||||
|
} elseif ($maxLineRef -gt 0) {
|
||||||
|
Report-Error "Line index $maxLineRef referenced but no lines defined"
|
||||||
|
} else {
|
||||||
|
Report-OK "No line/border references"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 3, 4, 5, 6: row/cell checks ---
|
||||||
|
|
||||||
|
$maxCellFormatRef = 0
|
||||||
|
$maxRowFormatRef = 0
|
||||||
|
$maxDefaultColIdx = 0
|
||||||
|
$rowIndex = 0
|
||||||
|
|
||||||
|
foreach ($ri in $rowNodes) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
|
||||||
|
$idxNode = $ri.SelectSingleNode("d:index", $nsMgr)
|
||||||
|
$rowIndex = if ($idxNode) { [int]$idxNode.InnerText } else { $rowIndex }
|
||||||
|
|
||||||
|
$row = $ri.SelectSingleNode("d:row", $nsMgr)
|
||||||
|
if (-not $row) { continue }
|
||||||
|
|
||||||
|
# Row formatIndex
|
||||||
|
$rowFmtNode = $row.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||||
|
if ($rowFmtNode) {
|
||||||
|
$val = [int]$rowFmtNode.InnerText
|
||||||
|
if ($val -gt $maxRowFormatRef) { $maxRowFormatRef = $val }
|
||||||
|
if ($val -gt $formatCount) {
|
||||||
|
Report-Error "Row ${rowIndex}: formatIndex=$val > format palette size ($formatCount)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check columnsID
|
||||||
|
$rowColsId = $null
|
||||||
|
$colsIdNode = $row.SelectSingleNode("d:columnsID", $nsMgr)
|
||||||
|
if ($colsIdNode) {
|
||||||
|
$rowColsId = $colsIdNode.InnerText
|
||||||
|
if (-not $columnSets.ContainsKey($rowColsId)) {
|
||||||
|
Report-Error "Row ${rowIndex}: columnsID '$($rowColsId.Substring(0,8))...' not found in column sets"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine column count for this row
|
||||||
|
$rowColCount = $defaultColCount
|
||||||
|
if ($rowColsId -and $columnSets.ContainsKey($rowColsId)) {
|
||||||
|
$rowColCount = $columnSets[$rowColsId]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Cell checks
|
||||||
|
foreach ($cGroup in $row.SelectNodes("d:c", $nsMgr)) {
|
||||||
|
$iNode = $cGroup.SelectSingleNode("d:i", $nsMgr)
|
||||||
|
if ($iNode) {
|
||||||
|
$colIdx = [int]$iNode.InnerText
|
||||||
|
# Track max index for default column set only
|
||||||
|
if (-not $rowColsId -and $colIdx -gt $maxDefaultColIdx) {
|
||||||
|
$maxDefaultColIdx = $colIdx
|
||||||
|
}
|
||||||
|
# Check against row's column count
|
||||||
|
if ($rowColCount -gt 0 -and $colIdx -ge $rowColCount) {
|
||||||
|
Report-Error "Row ${rowIndex}: column index $colIdx >= column count ($rowColCount)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$cell = $cGroup.SelectSingleNode("d:c", $nsMgr)
|
||||||
|
if ($cell) {
|
||||||
|
$fNode = $cell.SelectSingleNode("d:f", $nsMgr)
|
||||||
|
if ($fNode) {
|
||||||
|
$val = [int]$fNode.InnerText
|
||||||
|
if ($val -gt $maxCellFormatRef) { $maxCellFormatRef = $val }
|
||||||
|
if ($val -gt $formatCount) {
|
||||||
|
Report-Error "Row ${rowIndex}: cell format index $val > format palette size ($formatCount)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$rowIndex++
|
||||||
|
}
|
||||||
|
|
||||||
|
# Summary checks for format refs
|
||||||
|
if (-not $stopped) {
|
||||||
|
if ($maxCellFormatRef -le $formatCount -and $maxRowFormatRef -le $formatCount) {
|
||||||
|
Report-OK "Format refs: max cell=$maxCellFormatRef, max row=$maxRowFormatRef, palette size=$formatCount"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check column format indices
|
||||||
|
foreach ($cols in $root.SelectNodes("d:columns", $nsMgr)) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
foreach ($ci in $cols.SelectNodes("d:columnsItem", $nsMgr)) {
|
||||||
|
$col = $ci.SelectSingleNode("d:column", $nsMgr)
|
||||||
|
if ($col) {
|
||||||
|
$fmtNode = $col.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||||
|
if ($fmtNode) {
|
||||||
|
$val = [int]$fmtNode.InnerText
|
||||||
|
if ($val -gt $formatCount) {
|
||||||
|
$colIdx = $ci.SelectSingleNode("d:index", $nsMgr).InnerText
|
||||||
|
Report-Error "Column ${colIdx}: formatIndex=$val > format palette size ($formatCount)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 5: column index summary ---
|
||||||
|
|
||||||
|
if (-not $stopped) {
|
||||||
|
Report-OK "Column indices: max in default set=$maxDefaultColIdx, default column count=$defaultColCount"
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 7, 8: named areas ---
|
||||||
|
|
||||||
|
foreach ($ni in $root.SelectNodes("d:namedItem", $nsMgr)) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
|
||||||
|
$niType = $ni.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||||
|
$name = $ni.SelectSingleNode("d:name", $nsMgr).InnerText
|
||||||
|
|
||||||
|
if ($niType -like "*NamedItemCells*") {
|
||||||
|
$area = $ni.SelectSingleNode("d:area", $nsMgr)
|
||||||
|
$beginRow = [int]$area.SelectSingleNode("d:beginRow", $nsMgr).InnerText
|
||||||
|
$endRow = [int]$area.SelectSingleNode("d:endRow", $nsMgr).InnerText
|
||||||
|
|
||||||
|
# Check row bounds (skip -1 which means "all")
|
||||||
|
if ($beginRow -ne -1 -and $beginRow -ge $docHeight) {
|
||||||
|
Report-Error "Area '$name': beginRow=$beginRow >= height=$docHeight"
|
||||||
|
}
|
||||||
|
if ($endRow -ne -1 -and $endRow -ge $docHeight) {
|
||||||
|
Report-Error "Area '$name': endRow=$endRow >= height=$docHeight"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check columnsID reference
|
||||||
|
$colsIdNode = $area.SelectSingleNode("d:columnsID", $nsMgr)
|
||||||
|
if ($colsIdNode) {
|
||||||
|
$colsId = $colsIdNode.InnerText
|
||||||
|
if (-not $columnSets.ContainsKey($colsId)) {
|
||||||
|
Report-Error "Area '$name': columnsID '$($colsId.Substring(0,8))...' not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 9: merge bounds ---
|
||||||
|
|
||||||
|
foreach ($merge in $root.SelectNodes("d:merge", $nsMgr)) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
|
||||||
|
$r = [int]$merge.SelectSingleNode("d:r", $nsMgr).InnerText
|
||||||
|
$c = [int]$merge.SelectSingleNode("d:c", $nsMgr).InnerText
|
||||||
|
$wNode = $merge.SelectSingleNode("d:w", $nsMgr)
|
||||||
|
$hNode = $merge.SelectSingleNode("d:h", $nsMgr)
|
||||||
|
|
||||||
|
# r=-1 means all rows, skip bound check
|
||||||
|
if ($r -ne -1 -and $r -ge $docHeight) {
|
||||||
|
Report-Error "Merge at row=${r}, col=${c}: row >= height ($docHeight)"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($hNode -and $r -ne -1) {
|
||||||
|
$h = [int]$hNode.InnerText
|
||||||
|
if (($r + $h) -ge $docHeight) {
|
||||||
|
Report-Error "Merge at row=${r}: extends to row $($r + $h) >= height ($docHeight)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check columnsID in merge
|
||||||
|
$colsIdNode = $merge.SelectSingleNode("d:columnsID", $nsMgr)
|
||||||
|
if ($colsIdNode) {
|
||||||
|
$colsId = $colsIdNode.InnerText
|
||||||
|
if (-not $columnSets.ContainsKey($colsId)) {
|
||||||
|
Report-Error "Merge at row=${r}, col=${c}: columnsID '$($colsId.Substring(0,8))...' not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Check 12: drawing picture indices ---
|
||||||
|
|
||||||
|
foreach ($drawing in $root.SelectNodes("d:drawing", $nsMgr)) {
|
||||||
|
if ($stopped) { break }
|
||||||
|
|
||||||
|
$picIdxNode = $drawing.SelectSingleNode("d:pictureIndex", $nsMgr)
|
||||||
|
if ($picIdxNode) {
|
||||||
|
$picIdx = [int]$picIdxNode.InnerText
|
||||||
|
if ($picIdx -gt $pictureCount) {
|
||||||
|
$drawId = $drawing.SelectSingleNode("d:id", $nsMgr).InnerText
|
||||||
|
Report-Error "Drawing id=${drawId}: pictureIndex=$picIdx > picture count ($pictureCount)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# --- Summary ---
|
||||||
|
|
||||||
|
# :finish label equivalent
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "---"
|
||||||
|
|
||||||
|
if ($stopped) {
|
||||||
|
Write-Host "Stopped after $MaxErrors errors. Fix and re-run."
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors -eq 0 -and $warnings -eq 0) {
|
||||||
|
Write-Host "All checks passed."
|
||||||
|
} else {
|
||||||
|
Write-Host "Errors: $errors, Warnings: $warnings"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($errors -gt 0) {
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
@@ -16,6 +16,8 @@
|
|||||||
| `/epf-dump` | `<EpfFile>` | Разобрать EPF в XML (документация команды 1cv8.exe) |
|
| `/epf-dump` | `<EpfFile>` | Разобрать EPF в XML (документация команды 1cv8.exe) |
|
||||||
| `/epf-bsp-init` | `<ProcessorName> <Вид>` | Добавить регистрацию БСП (СведенияОВнешнейОбработке) |
|
| `/epf-bsp-init` | `<ProcessorName> <Вид>` | Добавить регистрацию БСП (СведенияОВнешнейОбработке) |
|
||||||
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в обработку БСП |
|
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в обработку БСП |
|
||||||
|
| `/mxl-info` | `<TemplatePath>` | Анализ структуры табличного документа (области, параметры, колонки) |
|
||||||
|
| `/mxl-validate` | `<TemplatePath>` | Валидация табличного документа (индексы, ссылки, границы) |
|
||||||
|
|
||||||
Навыки удаления (`epf-remove-*`) не вызываются Claude автоматически — только по явной команде пользователя.
|
Навыки удаления (`epf-remove-*`) не вызываются Claude автоматически — только по явной команде пользователя.
|
||||||
|
|
||||||
@@ -138,11 +140,14 @@ src/
|
|||||||
├── epf-dump/ # SKILL.md (только документация)
|
├── epf-dump/ # SKILL.md (только документация)
|
||||||
├── epf-add-help/ # SKILL.md + scripts/add-help.ps1
|
├── epf-add-help/ # SKILL.md + scripts/add-help.ps1
|
||||||
├── epf-bsp-init/ # SKILL.md (шаблоны кода, без скриптов)
|
├── epf-bsp-init/ # SKILL.md (шаблоны кода, без скриптов)
|
||||||
└── epf-bsp-add-command/ # SKILL.md (шаблоны кода, без скриптов)
|
├── epf-bsp-add-command/ # SKILL.md (шаблоны кода, без скриптов)
|
||||||
|
├── mxl-info/ # SKILL.md + scripts/mxl-info.ps1
|
||||||
|
└── mxl-validate/ # SKILL.md + scripts/mxl-validate.ps1
|
||||||
docs/
|
docs/
|
||||||
├── 1c-xml-format-spec.md # Спецификация XML-формата выгрузки
|
├── 1c-xml-format-spec.md # Спецификация XML-формата выгрузки
|
||||||
├── 1c-help-spec.md # Спецификация встроенной справки
|
├── 1c-help-spec.md # Спецификация встроенной справки
|
||||||
└── build-spec.md # Спецификация команд сборки/разборки
|
├── build-spec.md # Спецификация команд сборки/разборки
|
||||||
|
└── 1c-spreadsheet-spec.md # Спецификация табличного документа (MXL)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Спецификации
|
## Спецификации
|
||||||
@@ -150,6 +155,7 @@ docs/
|
|||||||
- [XML-формат выгрузки обработок](docs/1c-xml-format-spec.md) — полное описание структуры XML-файлов, namespace'ов, элементов форм
|
- [XML-формат выгрузки обработок](docs/1c-xml-format-spec.md) — полное описание структуры XML-файлов, namespace'ов, элементов форм
|
||||||
- [Встроенная справка](docs/1c-help-spec.md) — Help.xml, HTML-страницы, кнопка справки на форме
|
- [Встроенная справка](docs/1c-help-spec.md) — Help.xml, HTML-страницы, кнопка справки на форме
|
||||||
- [Сборка и разборка EPF](docs/build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
|
- [Сборка и разборка EPF](docs/build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
|
||||||
|
- [Табличный документ (MXL)](docs/1c-spreadsheet-spec.md) — XML-формат SpreadsheetDocument, совместимость версий
|
||||||
|
|
||||||
## Технические детали
|
## Технические детали
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user