Merge branch 'dev'

This commit is contained in:
Nick Shirokov
2026-02-15 17:12:53 +03:00
93 changed files with 23090 additions and 199 deletions
+58
View File
@@ -0,0 +1,58 @@
---
name: cf-edit
description: Точечное редактирование конфигурации 1С. Используй когда нужно изменить свойства конфигурации, добавить или удалить объект из состава, настроить роли по умолчанию
argument-hint: -ConfigPath <path> -Operation <op> -Value <value>
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /cf-edit — редактирование конфигурации 1С
Точечное редактирование Configuration.xml: свойства, состав ChildObjects, роли по умолчанию.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
| `Operation` | Операция (см. таблицу) |
| `Value` | Значение для операции (batch через `;;`) |
| `DefinitionFile` | JSON-файл с массивом операций |
| `NoValidate` | Пропустить авто-валидацию |
```powershell
powershell.exe -NoProfile -File .claude\skills\cf-edit\scripts\cf-edit.ps1 -ConfigPath '<path>' -Operation modify-property -Value 'Version=1.0.0.1'
```
## Операции
| Операция | Формат Value | Описание |
|----------|-------------|----------|
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
| `add-childObject` | `Type.Name` (batch `;;`) | Добавить объект в ChildObjects |
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
| `set-defaultRoles` | Имена через `;;` | Заменить список ролей по умолчанию |
Подробнее: `reference.md` в каталоге навыка.
## Примеры
```powershell
# Изменить версию и поставщика
... -ConfigPath test-tmp/cf -Operation modify-property -Value "Version=1.0.0.1 ;; Vendor=Фирма 1С"
# Добавить объекты
... -ConfigPath test-tmp/cf -Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ"
# Удалить объект
... -ConfigPath test-tmp/cf -Operation remove-childObject -Value "Catalog.Устаревший"
# Роли по умолчанию
... -ConfigPath test-tmp/cf -Operation add-defaultRole -Value "ПолныеПрава"
... -ConfigPath test-tmp/cf -Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
```
+63
View File
@@ -0,0 +1,63 @@
# cf-edit — справочник операций
## modify-property
Свойства для редактирования:
### Скалярные
`Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress`
### LocalString (многоязычные)
`Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress`
### Enum
| Свойство | Допустимые значения |
|----------|---------------------|
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_27`, `DontUse` |
| `ConfigurationExtensionCompatibilityMode` | то же |
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
| `ScriptVariant` | `Russian`, `English` |
| `DataLockControlMode` | `Managed`, `Automatic`, `AutomaticAndManaged` |
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
| `InterfaceCompatibilityMode` | `Taxi`, `TaxiEnableVersion8_2`, `Version8_2` |
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
### Ref
`DefaultLanguage` — значение вида `Language.Русский`
### Формат batch
`"Version=1.0.0.1 ;; Vendor=Фирма 1С ;; Synonym=Тестовая конфигурация"`
## add-childObject / remove-childObject
Формат: `Type.Name` — XML-тип и имя объекта через точку.
При добавлении объект вставляется в каноническую позицию:
1. Находит последний элемент того же типа → вставляет после
2. Если тип отсутствует → находит последний элемент предшествующего типа → вставляет после
3. Внутри одного типа — алфавитный порядок
Batch: `"Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"`
## add-defaultRole / remove-defaultRole / set-defaultRoles
Имя роли: `ПолныеПрава` или `Role.ПолныеПрава` (префикс `Role.` добавляется автоматически).
`set-defaultRoles` полностью заменяет список ролей.
## DefinitionFile (JSON)
```json
[
{ "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" },
{ "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" },
{ "operation": "add-defaultRole", "value": "ПолныеПрава" }
]
```
## Авто-валидация
После сохранения автоматически запускается `cf-validate` (если не указан `-NoValidate`).
+523
View File
@@ -0,0 +1,523 @@
# cf-edit v1.0 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$ConfigPath,
[string]$DefinitionFile,
[ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles")]
[string]$Operation,
[string]$Value,
[switch]$NoValidate
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Mode validation ---
if ($DefinitionFile -and $Operation) { Write-Error "Cannot use both -DefinitionFile and -Operation"; exit 1 }
if (-not $DefinitionFile -and -not $Operation) { Write-Error "Either -DefinitionFile or -Operation is required"; exit 1 }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ConfigPath -PathType Container) {
$candidate = Join-Path $ConfigPath "Configuration.xml"
if (Test-Path $candidate) { $ConfigPath = $candidate }
else { Write-Error "No Configuration.xml in directory"; exit 1 }
}
if (-not (Test-Path $ConfigPath)) { Write-Error "File not found: $ConfigPath"; exit 1 }
$resolvedPath = (Resolve-Path $ConfigPath).Path
# --- Load XML with PreserveWhitespace ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
$script:addCount = 0
$script:removeCount = 0
$script:modifyCount = 0
function Info([string]$msg) { Write-Host "[INFO] $msg" }
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
# --- Detect structure ---
$root = $script:xmlDoc.DocumentElement
$script:mdNs = "http://v8.1c.ru/8.3/MDClasses"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
$script:cfgEl = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration") {
$script:cfgEl = $child; break
}
}
if (-not $script:cfgEl) { Write-Error "No <Configuration> element found"; exit 1 }
$script:propsEl = $null
$script:childObjsEl = $null
foreach ($child in $script:cfgEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Properties") { $script:propsEl = $child }
if ($child.LocalName -eq "ChildObjects") { $script:childObjsEl = $child }
}
$script:objName = ""
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Name") {
$script:objName = $child.InnerText.Trim(); break
}
}
Info "Configuration: $($script:objName)"
# --- Canonical type order for ChildObjects (44 types) ---
$script:typeOrder = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# --- XML manipulation helpers (from subsystem-edit pattern) ---
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0; $current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Remove-NodeWithWhitespace($node) {
$parent = $node.ParentNode
$prev = $node.PreviousSibling
$next = $node.NextSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($prev) | Out-Null
} elseif ($next -and ($next.NodeType -eq 'Whitespace' -or $next.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($next) | Out-Null
}
$parent.RemoveChild($node) | Out-Null
}
function Expand-SelfClosingElement($container, $parentIndent) {
if (-not $container.HasChildNodes -or $container.IsEmpty) {
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
function Import-Fragment([string]$xmlString) {
$wrapper = "<_W xmlns=`"$($script:mdNs)`" xmlns:xsi=`"$($script:xsiNs)`" xmlns:v8=`"$($script:v8Ns)`" xmlns:xr=`"$($script:xrNs)`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`">$xmlString</_W>"
$frag = New-Object System.Xml.XmlDocument
$frag.PreserveWhitespace = $true
$frag.LoadXml($wrapper)
$nodes = @()
foreach ($child in $frag.DocumentElement.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$nodes += $script:xmlDoc.ImportNode($child, $true)
}
}
return ,$nodes
}
# --- Parse batch value (split by ;;) ---
function Parse-BatchValue([string]$val) {
$items = @()
foreach ($part in $val.Split(";;")) {
$trimmed = $part.Trim()
if ($trimmed) { $items += $trimmed }
}
return ,$items
}
# --- LocalString properties ---
$mlProps = @("Synonym","BriefInformation","DetailedInformation","Copyright","VendorInformationAddress","ConfigurationInformationAddress")
# Scalar properties
$scalarProps = @("Name","Version","Vendor","Comment","NamePrefix","UpdateCatalogAddress")
# Ref properties
$refProps = @("DefaultLanguage")
# --- Operation: modify-property ---
function Do-ModifyProperty([string]$batchVal) {
$items = Parse-BatchValue $batchVal
foreach ($item in $items) {
$eqIdx = $item.IndexOf("=")
if ($eqIdx -lt 1) {
Write-Error "Invalid property format '$item', expected 'Key=Value'"
exit 1
}
$propName = $item.Substring(0, $eqIdx).Trim()
$propValue = $item.Substring($eqIdx + 1).Trim()
# Find property element
$propEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
$propEl = $child; break
}
}
if (-not $propEl) {
Write-Error "Property '$propName' not found in Properties"
exit 1
}
if ($mlProps -contains $propName) {
# LocalString
if (-not $propValue) {
$propEl.InnerXml = ""
} else {
$indent = Get-ChildIndent $script:propsEl
$escaped = [System.Security.SecurityElement]::Escape($propValue)
$mlXml = "`r`n$indent`t<v8:item>`r`n$indent`t`t<v8:lang>ru</v8:lang>`r`n$indent`t`t<v8:content>$escaped</v8:content>`r`n$indent`t</v8:item>`r`n$indent"
$propEl.InnerXml = $mlXml
}
} elseif ($scalarProps -contains $propName -or $refProps -contains $propName) {
# Simple text
if (-not $propValue) { $propEl.InnerXml = "" }
else { $propEl.InnerText = $propValue }
} else {
# Enum or other — just set text
$propEl.InnerText = $propValue
}
$script:modifyCount++
Info "Set $propName = `"$propValue`""
}
}
# --- Operation: add-childObject ---
function Do-AddChildObject([string]$batchVal) {
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found"; exit 1 }
$items = Parse-BatchValue $batchVal
$cfgIndent = Get-ChildIndent $script:cfgEl
# Expand self-closing if needed
if (-not $script:childObjsEl.HasChildNodes -or $script:childObjsEl.IsEmpty) {
Expand-SelfClosingElement $script:childObjsEl $cfgIndent
}
$childIndent = Get-ChildIndent $script:childObjsEl
foreach ($item in $items) {
$dotIdx = $item.IndexOf(".")
if ($dotIdx -lt 1) {
Write-Error "Invalid format '$item', expected 'Type.Name'"
exit 1
}
$typeName = $item.Substring(0, $dotIdx)
$objNameVal = $item.Substring($dotIdx + 1)
# Check type is valid
$typeIdx = $script:typeOrder.IndexOf($typeName)
if ($typeIdx -lt 0) {
Write-Error "Unknown type '$typeName'"
exit 1
}
# Dedup check
$existing = $false
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName -and $child.InnerText -eq $objNameVal) {
$existing = $true; break
}
}
if ($existing) {
Warn "Already exists: $typeName.$objNameVal"
continue
}
# Find insertion point: after last element of same type, or after last element of preceding type
$insertBefore = $null
$lastSameType = $null
$lastPrecedingType = $null
$currentTypeIdx = -1
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTypeIdx = $script:typeOrder.IndexOf($child.LocalName)
if ($childTypeIdx -lt 0) { continue }
if ($child.LocalName -eq $typeName) {
# Same type — check alphabetical order
if ($child.InnerText -gt $objNameVal -and -not $insertBefore) {
# Insert before this element (alphabetical)
$insertBefore = $child
}
$lastSameType = $child
} elseif ($childTypeIdx -lt $typeIdx) {
$lastPrecedingType = $child
} elseif ($childTypeIdx -gt $typeIdx -and -not $insertBefore) {
# First element of a later type — insert before it
$insertBefore = $child
}
}
# Create element
$newEl = $script:xmlDoc.CreateElement($typeName, $script:mdNs)
$newEl.InnerText = $objNameVal
if ($insertBefore) {
Insert-BeforeElement $script:childObjsEl $newEl $insertBefore $childIndent
} else {
# Append at end (or after last same/preceding type)
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
}
$script:addCount++
Info "Added: $typeName.$objNameVal"
}
}
# --- Operation: remove-childObject ---
function Do-RemoveChildObject([string]$batchVal) {
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found"; exit 1 }
$items = Parse-BatchValue $batchVal
foreach ($item in $items) {
$dotIdx = $item.IndexOf(".")
if ($dotIdx -lt 1) {
Write-Error "Invalid format '$item', expected 'Type.Name'"
exit 1
}
$typeName = $item.Substring(0, $dotIdx)
$objNameVal = $item.Substring($dotIdx + 1)
$found = $false
foreach ($child in @($script:childObjsEl.ChildNodes)) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName -and $child.InnerText -eq $objNameVal) {
Remove-NodeWithWhitespace $child
$script:removeCount++
Info "Removed: $typeName.$objNameVal"
$found = $true
break
}
}
if (-not $found) { Warn "Not found: $typeName.$objNameVal" }
}
}
# --- Operation: add-defaultRole ---
function Do-AddDefaultRole([string]$batchVal) {
$items = Parse-BatchValue $batchVal
# Find DefaultRoles element
$rolesEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "DefaultRoles") {
$rolesEl = $child; break
}
}
if (-not $rolesEl) { Write-Error "No <DefaultRoles> element found in Properties"; exit 1 }
$propsIndent = Get-ChildIndent $script:propsEl
if (-not $rolesEl.HasChildNodes -or $rolesEl.IsEmpty) {
Expand-SelfClosingElement $rolesEl $propsIndent
}
$roleIndent = Get-ChildIndent $rolesEl
foreach ($item in $items) {
$roleName = $item
if (-not $roleName.StartsWith("Role.")) { $roleName = "Role.$roleName" }
# Dedup
$existing = $false
foreach ($child in $rolesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.InnerText.Trim() -eq $roleName) {
$existing = $true; break
}
}
if ($existing) {
Warn "DefaultRole already exists: $roleName"
continue
}
$fragXml = "<xr:Item xsi:type=`"xr:MDObjectRef`">$roleName</xr:Item>"
$nodes = Import-Fragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $rolesEl $nodes[0] $null $roleIndent
$script:addCount++
Info "Added DefaultRole: $roleName"
}
}
}
# --- Operation: remove-defaultRole ---
function Do-RemoveDefaultRole([string]$batchVal) {
$items = Parse-BatchValue $batchVal
$rolesEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "DefaultRoles") {
$rolesEl = $child; break
}
}
if (-not $rolesEl) { Write-Error "No <DefaultRoles> element found"; exit 1 }
foreach ($item in $items) {
$roleName = $item
if (-not $roleName.StartsWith("Role.")) { $roleName = "Role.$roleName" }
$found = $false
foreach ($child in @($rolesEl.ChildNodes)) {
if ($child.NodeType -eq 'Element' -and $child.InnerText.Trim() -eq $roleName) {
Remove-NodeWithWhitespace $child
$script:removeCount++
Info "Removed DefaultRole: $roleName"
$found = $true
break
}
}
if (-not $found) { Warn "DefaultRole not found: $roleName" }
}
}
# --- Operation: set-defaultRoles ---
function Do-SetDefaultRoles([string]$batchVal) {
$items = Parse-BatchValue $batchVal
$rolesEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "DefaultRoles") {
$rolesEl = $child; break
}
}
if (-not $rolesEl) { Write-Error "No <DefaultRoles> element found"; exit 1 }
# Clear all existing children
while ($rolesEl.HasChildNodes) {
$rolesEl.RemoveChild($rolesEl.FirstChild) | Out-Null
}
if ($items.Count -eq 0) {
$script:modifyCount++
Info "Cleared DefaultRoles"
return
}
$propsIndent = Get-ChildIndent $script:propsEl
$roleIndent = "$propsIndent`t"
# Add closing whitespace
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$propsIndent")
$rolesEl.AppendChild($closeWs) | Out-Null
foreach ($item in $items) {
$roleName = $item
if (-not $roleName.StartsWith("Role.")) { $roleName = "Role.$roleName" }
$fragXml = "<xr:Item xsi:type=`"xr:MDObjectRef`">$roleName</xr:Item>"
$nodes = Import-Fragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $rolesEl $nodes[0] $null $roleIndent
}
}
$script:modifyCount++
Info "Set DefaultRoles: $($items.Count) roles"
}
# --- Execute operations ---
$operations = @()
if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$ops = $jsonText | ConvertFrom-Json
if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op }
} else {
$operations += $ops
}
} else {
$operations += @{ operation = $Operation; value = $Value }
}
foreach ($op in $operations) {
$opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" }
$opValue = if ($op.value) { "$($op.value)" } else { "$Value" }
switch ($opName) {
"modify-property" { Do-ModifyProperty $opValue }
"add-childObject" { Do-AddChildObject $opValue }
"remove-childObject" { Do-RemoveChildObject $opValue }
"add-defaultRole" { Do-AddDefaultRole $opValue }
"remove-defaultRole" { Do-RemoveDefaultRole $opValue }
"set-defaultRoles" { Do-SetDefaultRoles $opValue }
default { Write-Error "Unknown operation: $opName"; exit 1 }
}
}
# --- Save ---
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath"
# --- Auto-validate ---
if (-not $NoValidate) {
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\cf-validate") "scripts\cf-validate.ps1"
$validateScript = [System.IO.Path]::GetFullPath($validateScript)
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- Running cf-validate ---"
& powershell.exe -NoProfile -File $validateScript -ConfigPath $resolvedPath
}
}
# --- Summary ---
Write-Host ""
Write-Host "=== cf-edit summary ==="
Write-Host " Configuration: $($script:objName)"
Write-Host " Added: $($script:addCount)"
Write-Host " Removed: $($script:removeCount)"
Write-Host " Modified: $($script:modifyCount)"
exit 0
+50
View File
@@ -0,0 +1,50 @@
---
name: cf-info
description: Анализ структуры конфигурации 1С — свойства, состав, счётчики объектов. Используй для обзора конфигурации — какие объекты есть, сколько их, какие настройки
argument-hint: <ConfigPath> [-Mode overview|brief|full]
allowed-tools:
- Bash
- Read
- Glob
---
# /cf-info — Структура конфигурации 1С
Читает Configuration.xml из выгрузки конфигурации и выводит компактное описание структуры.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
| `Mode` | Режим: `overview` (default), `brief`, `full` |
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell
powershell.exe -NoProfile -File .claude\skills\cf-info\scripts\cf-info.ps1 -ConfigPath "<путь>"
```
## Три режима
| Режим | Что показывает |
|---|---|
| `overview` *(default)* | Заголовок + ключевые свойства + таблица счётчиков объектов по типам |
| `brief` | Одна строка: Имя — "Синоним" vВерсия \| N объектов \| совместимость |
| `full` | Все свойства по категориям + полный список ChildObjects + DefaultRoles + мобильные функциональности |
## Примеры
```powershell
# Обзор пустой конфигурации
... -ConfigPath upload/cfempty
# Краткая сводка реальной конфигурации
... -ConfigPath upload/acc_8.3.24 -Mode brief
# Полная информация
... -ConfigPath upload/acc_8.3.24 -Mode full
# С пагинацией
... -ConfigPath upload/acc_8.3.24 -Mode full -Limit 50 -Offset 100
```
+387
View File
@@ -0,0 +1,387 @@
# cf-info v1.0 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)][string]$ConfigPath,
[ValidateSet("overview","brief","full")]
[string]$Mode = "overview",
[int]$Limit = 150,
[int]$Offset = 0,
[string]$OutFile
)
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Output helper (always collect, paginate at the end) ---
$script:lines = @()
function Out([string]$text) { $script:lines += $text }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
# Directory -> find Configuration.xml
if (Test-Path $ConfigPath -PathType Container) {
$candidate = Join-Path $ConfigPath "Configuration.xml"
if (Test-Path $candidate) {
$ConfigPath = $candidate
} else {
Write-Host "[ERROR] No Configuration.xml found in directory: $ConfigPath"
exit 1
}
}
if (-not (Test-Path $ConfigPath)) {
Write-Host "[ERROR] File not found: $ConfigPath"
exit 1
}
# --- Load XML ---
[xml]$xmlDoc = Get-Content -Path $ConfigPath -Encoding UTF8
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$mdRoot = $xmlDoc.SelectSingleNode("/md:MetaDataObject", $ns)
if (-not $mdRoot) {
Write-Host "[ERROR] Not a valid 1C metadata XML file (no MetaDataObject root)"
exit 1
}
$cfgNode = $mdRoot.SelectSingleNode("md:Configuration", $ns)
if (-not $cfgNode) {
Write-Host "[ERROR] No <Configuration> element found"
exit 1
}
$version = $mdRoot.GetAttribute("version")
$propsNode = $cfgNode.SelectSingleNode("md:Properties", $ns)
$childObjNode = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
# --- Helpers ---
function Get-MLText($node) {
if (-not $node) { return "" }
$item = $node.SelectSingleNode("v8:item/v8:content", $ns)
if ($item -and $item.InnerText) { return $item.InnerText }
return ""
}
function Get-PropText([string]$propName) {
$n = $propsNode.SelectSingleNode("md:$propName", $ns)
if ($n -and $n.InnerText) { return $n.InnerText }
return ""
}
function Get-PropML([string]$propName) {
$n = $propsNode.SelectSingleNode("md:$propName", $ns)
return (Get-MLText $n)
}
# --- Type name maps (canonical order, 44 types) ---
$typeOrder = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
$typeRuNames = @{
"Language"="Языки"; "Subsystem"="Подсистемы"; "StyleItem"="Элементы стиля"; "Style"="Стили"
"CommonPicture"="Общие картинки"; "SessionParameter"="Параметры сеанса"; "Role"="Роли"
"CommonTemplate"="Общие макеты"; "FilterCriterion"="Критерии отбора"; "CommonModule"="Общие модули"
"CommonAttribute"="Общие реквизиты"; "ExchangePlan"="Планы обмена"; "XDTOPackage"="XDTO-пакеты"
"WebService"="Веб-сервисы"; "HTTPService"="HTTP-сервисы"; "WSReference"="WS-ссылки"
"EventSubscription"="Подписки на события"; "ScheduledJob"="Регламентные задания"
"SettingsStorage"="Хранилища настроек"; "FunctionalOption"="Функциональные опции"
"FunctionalOptionsParameter"="Параметры ФО"; "DefinedType"="Определяемые типы"
"CommonCommand"="Общие команды"; "CommandGroup"="Группы команд"; "Constant"="Константы"
"CommonForm"="Общие формы"; "Catalog"="Справочники"; "Document"="Документы"
"DocumentNumerator"="Нумераторы"; "Sequence"="Последовательности"; "DocumentJournal"="Журналы документов"
"Enum"="Перечисления"; "Report"="Отчёты"; "DataProcessor"="Обработки"
"InformationRegister"="Регистры сведений"; "AccumulationRegister"="Регистры накопления"
"ChartOfCharacteristicTypes"="ПВХ"; "ChartOfAccounts"="Планы счетов"
"AccountingRegister"="Регистры бухгалтерии"; "ChartOfCalculationTypes"="ПВР"
"CalculationRegister"="Регистры расчёта"; "BusinessProcess"="Бизнес-процессы"
"Task"="Задачи"; "IntegrationService"="Сервисы интеграции"
}
# --- Count objects in ChildObjects ---
$objectCounts = [ordered]@{}
$totalObjects = 0
if ($childObjNode) {
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
if (-not $objectCounts.Contains($typeName)) {
$objectCounts[$typeName] = 0
}
$objectCounts[$typeName] = $objectCounts[$typeName] + 1
$totalObjects++
}
}
# --- Read key properties ---
$cfgName = Get-PropText "Name"
$cfgSynonym = Get-PropML "Synonym"
$cfgVersion = Get-PropText "Version"
$cfgVendor = Get-PropText "Vendor"
$cfgCompat = Get-PropText "CompatibilityMode"
$cfgExtCompat = Get-PropText "ConfigurationExtensionCompatibilityMode"
$cfgDefaultRun = Get-PropText "DefaultRunMode"
$cfgScript = Get-PropText "ScriptVariant"
$cfgDefaultLang = Get-PropText "DefaultLanguage"
$cfgDataLock = Get-PropText "DataLockControlMode"
$dash = [char]0x2014
$cfgModality = Get-PropText "ModalityUseMode"
$cfgIntfCompat = Get-PropText "InterfaceCompatibilityMode"
$cfgAutoNum = Get-PropText "ObjectAutonumerationMode"
$cfgSyncCalls = Get-PropText "SynchronousPlatformExtensionAndAddInCallUseMode"
$cfgDbSpaces = Get-PropText "DatabaseTablespacesUseMode"
$cfgWindowMode = Get-PropText "MainClientApplicationWindowMode"
# --- BRIEF mode ---
if ($Mode -eq "brief") {
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
$compatPart = if ($cfgCompat) { " | $cfgCompat" } else { "" }
Out "Конфигурация: ${cfgName}${synPart}${verPart} | $totalObjects объектов${compatPart}"
}
# --- OVERVIEW mode ---
if ($Mode -eq "overview") {
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ==="
Out ""
# Key properties
Out "Формат: $version"
if ($cfgVendor) { Out "Поставщик: $cfgVendor" }
if ($cfgVersion) { Out "Версия: $cfgVersion" }
Out "Совместимость: $cfgCompat"
Out "Режим запуска: $cfgDefaultRun"
Out "Язык скриптов: $cfgScript"
Out "Язык: $cfgDefaultLang"
Out "Блокировки: $cfgDataLock"
Out "Модальность: $cfgModality"
Out "Интерфейс: $cfgIntfCompat"
Out ""
# Object counts table
Out "--- Состав ($totalObjects объектов) ---"
Out ""
$maxTypeLen = 0
foreach ($typeName in $typeOrder) {
if ($objectCounts.Contains($typeName)) {
$ruName = $typeRuNames[$typeName]
if ($ruName.Length -gt $maxTypeLen) { $maxTypeLen = $ruName.Length }
}
}
if ($maxTypeLen -lt 10) { $maxTypeLen = 10 }
foreach ($typeName in $typeOrder) {
if ($objectCounts.Contains($typeName)) {
$count = $objectCounts[$typeName]
$ruName = $typeRuNames[$typeName]
$padded = $ruName.PadRight($maxTypeLen)
Out " $padded $count"
}
}
}
# --- FULL mode ---
if ($Mode -eq "full") {
$synPart = if ($cfgSynonym) { " $dash `"$cfgSynonym`"" } else { "" }
$verPart = if ($cfgVersion) { " v$cfgVersion" } else { "" }
Out "=== Конфигурация: ${cfgName}${synPart}${verPart} ==="
Out ""
# --- Section: Identification ---
Out "--- Идентификация ---"
Out "UUID: $($cfgNode.GetAttribute('uuid'))"
Out "Имя: $cfgName"
if ($cfgSynonym) { Out "Синоним: $cfgSynonym" }
$cfgComment = Get-PropText "Comment"
if ($cfgComment) { Out "Комментарий: $cfgComment" }
$cfgPrefix = Get-PropText "NamePrefix"
if ($cfgPrefix) { Out "Префикс: $cfgPrefix" }
if ($cfgVendor) { Out "Поставщик: $cfgVendor" }
if ($cfgVersion) { Out "Версия: $cfgVersion" }
$cfgUpdateAddr = Get-PropText "UpdateCatalogAddress"
if ($cfgUpdateAddr) { Out "Каталог обн.: $cfgUpdateAddr" }
Out ""
# --- Section: Modes ---
Out "--- Режимы работы ---"
Out "Формат: $version"
Out "Совместимость: $cfgCompat"
Out "Совм. расширений: $cfgExtCompat"
Out "Режим запуска: $cfgDefaultRun"
Out "Язык скриптов: $cfgScript"
Out "Блокировки: $cfgDataLock"
Out "Автонумерация: $cfgAutoNum"
Out "Модальность: $cfgModality"
Out "Синхр. вызовы: $cfgSyncCalls"
Out "Интерфейс: $cfgIntfCompat"
Out "Табл. пространства: $cfgDbSpaces"
Out "Режим окна: $cfgWindowMode"
Out ""
# --- Section: Language, roles, purposes ---
Out "--- Назначение ---"
Out "Язык по умолч.: $cfgDefaultLang"
# UsePurposes
$purposeNode = $propsNode.SelectSingleNode("md:UsePurposes", $ns)
if ($purposeNode) {
$purposes = @()
foreach ($val in $purposeNode.SelectNodes("v8:Value", $ns)) {
$purposes += $val.InnerText
}
if ($purposes.Count -gt 0) { Out "Назначения: $($purposes -join ', ')" }
}
# DefaultRoles
$rolesNode = $propsNode.SelectSingleNode("md:DefaultRoles", $ns)
if ($rolesNode) {
$roles = @()
foreach ($item in $rolesNode.SelectNodes("xr:Item", $ns)) {
$roles += $item.InnerText
}
if ($roles.Count -gt 0) {
Out "Роли по умолч.: $($roles.Count)"
foreach ($r in $roles) { Out " - $r" }
}
}
# Booleans
$useMF = Get-PropText "UseManagedFormInOrdinaryApplication"
$useOF = Get-PropText "UseOrdinaryFormInManagedApplication"
Out "Управл.формы в обычн.: $useMF"
Out "Обычн.формы в управл.: $useOF"
Out ""
# --- Section: Storages & default forms ---
Out "--- Хранилища и формы по умолчанию ---"
$storageProps = @("CommonSettingsStorage","ReportsUserSettingsStorage","ReportsVariantsStorage","FormDataSettingsStorage","DynamicListsUserSettingsStorage","URLExternalDataStorage")
foreach ($sp in $storageProps) {
$val = Get-PropText $sp
if ($val) { Out " ${sp}: $val" }
}
$formProps = @("DefaultReportForm","DefaultReportVariantForm","DefaultReportSettingsForm","DefaultReportAppearanceTemplate","DefaultDynamicListSettingsForm","DefaultSearchForm","DefaultDataHistoryChangeHistoryForm","DefaultDataHistoryVersionDataForm","DefaultDataHistoryVersionDifferencesForm","DefaultCollaborationSystemUsersChoiceForm","DefaultConstantsForm","DefaultInterface","DefaultStyle")
foreach ($fp in $formProps) {
$val = Get-PropText $fp
if ($val) { Out " ${fp}: $val" }
}
Out ""
# --- Section: Info ---
$cfgBrief = Get-PropML "BriefInformation"
$cfgDetail = Get-PropML "DetailedInformation"
$cfgCopyright = Get-PropML "Copyright"
$cfgVendorAddr = Get-PropML "VendorInformationAddress"
$cfgInfoAddr = Get-PropML "ConfigurationInformationAddress"
if ($cfgBrief -or $cfgDetail -or $cfgCopyright -or $cfgVendorAddr -or $cfgInfoAddr) {
Out "--- Информация ---"
if ($cfgBrief) { Out "Краткая: $cfgBrief" }
if ($cfgDetail) { Out "Подробная: $cfgDetail" }
if ($cfgCopyright) { Out "Copyright: $cfgCopyright" }
if ($cfgVendorAddr) { Out "Сайт поставщика: $cfgVendorAddr" }
if ($cfgInfoAddr) { Out "Адрес информ.: $cfgInfoAddr" }
Out ""
}
# --- Section: Mobile functionalities ---
$mobileFunc = $propsNode.SelectSingleNode("md:UsedMobileApplicationFunctionalities", $ns)
if ($mobileFunc) {
$enabledFuncs = @()
$disabledFuncs = @()
foreach ($func in $mobileFunc.SelectNodes("app:functionality", $ns)) {
$fName = $func.SelectSingleNode("app:functionality", $ns)
$fUse = $func.SelectSingleNode("app:use", $ns)
if ($fName -and $fUse) {
if ($fUse.InnerText -eq "true") {
$enabledFuncs += $fName.InnerText
} else {
$disabledFuncs += $fName.InnerText
}
}
}
$totalFunc = $enabledFuncs.Count + $disabledFuncs.Count
Out "--- Мобильные функциональности ($totalFunc, включено: $($enabledFuncs.Count)) ---"
if ($enabledFuncs.Count -gt 0) {
foreach ($f in $enabledFuncs) { Out " [+] $f" }
}
foreach ($f in $disabledFuncs) { Out " [-] $f" }
Out ""
}
# --- Section: InternalInfo ---
$internalInfo = $cfgNode.SelectSingleNode("md:InternalInfo", $ns)
if ($internalInfo) {
$contained = $internalInfo.SelectNodes("xr:ContainedObject", $ns)
Out "--- InternalInfo ($($contained.Count) ContainedObject) ---"
foreach ($co in $contained) {
$classId = $co.SelectSingleNode("xr:ClassId", $ns).InnerText
$objectId = $co.SelectSingleNode("xr:ObjectId", $ns).InnerText
Out " $classId -> $objectId"
}
Out ""
}
# --- Section: ChildObjects (full list) ---
Out "--- Состав ($totalObjects объектов) ---"
Out ""
foreach ($typeName in $typeOrder) {
if (-not $objectCounts.Contains($typeName)) { continue }
$count = $objectCounts[$typeName]
$ruName = $typeRuNames[$typeName]
Out " $ruName ($typeName): $count"
# Collect names for this type
$names = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName) {
$names += $child.InnerText
}
}
foreach ($n in $names) { Out " $n" }
}
}
# --- Pagination and output ---
$total = $script:lines.Count
if ($Offset -gt 0 -or $Limit -lt $total) {
$start = [Math]::Min($Offset, $total)
$end = [Math]::Min($start + $Limit, $total)
$page = $script:lines[$start..($end - 1)]
$result = ($page -join "`n")
if ($end -lt $total) {
$result += "`n`n... ($end of $total lines, use -Offset $end to continue)"
}
} else {
$result = ($script:lines -join "`n")
}
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "`nWritten to: $OutFile"
}
+58
View File
@@ -0,0 +1,58 @@
---
name: cf-init
description: Создать пустую конфигурацию 1С (scaffold XML-исходников). Используй когда нужно начать новую конфигурацию с нуля
argument-hint: <Name> [-Synonym <name>] [-OutputDir src]
allowed-tools:
- Bash
- Read
- Glob
---
# /cf-init — Создание пустой конфигурации 1С
Создаёт scaffold исходников пустой конфигурации 1С: `Configuration.xml`, `Languages/Русский.xml`.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `Name` | Имя конфигурации (обязат.) |
| `Synonym` | Синоним (= Name если не указан) |
| `OutputDir` | Каталог для создания (default: `src`) |
| `Version` | Версия конфигурации |
| `Vendor` | Поставщик |
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
```powershell
powershell.exe -NoProfile -File .claude\skills\cf-init\scripts\cf-init.ps1 -Name "МояКонфигурация"
```
## Что создаётся
```
<OutputDir>/
├── Configuration.xml # Корневой файл — все свойства
└── Languages/
└── Русский.xml # Язык по умолчанию
```
## Примеры
```powershell
# Базовая конфигурация
... -Name МояКонфигурация -Synonym "Моя конфигурация" -OutputDir test-tmp/cf
# С версией и поставщиком
... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2
# Другой режим совместимости
... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
```
## Верификация
```
/cf-init TestConfig -OutputDir test-tmp/cf
/cf-info test-tmp/cf — проверить созданное
/cf-validate test-tmp/cf — валидировать
```
+215
View File
@@ -0,0 +1,215 @@
# cf-init v1.0 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$Name,
[string]$Synonym = $Name,
[string]$OutputDir = "src",
[string]$Version,
[string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24"
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve output dir ---
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir
}
# --- Check existing ---
$cfgFile = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $cfgFile) {
Write-Error "Configuration.xml already exists: $cfgFile"
exit 1
}
# --- Generate UUIDs ---
$uuidCfg = [guid]::NewGuid().ToString()
$uuidLang = [guid]::NewGuid().ToString()
# 7 ContainedObject ObjectIds
$co1 = [guid]::NewGuid().ToString()
$co2 = [guid]::NewGuid().ToString()
$co3 = [guid]::NewGuid().ToString()
$co4 = [guid]::NewGuid().ToString()
$co5 = [guid]::NewGuid().ToString()
$co6 = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString()
# --- Mobile functionalities ---
$mobileFuncs = @(
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
@("Calendars","false"), @("PushNotifications","false"), @("LocalNotifications","false"),
@("InAppPurchases","false"), @("PersonalComputerFileExchange","false"), @("Ads","false"),
@("NumberDialing","false"), @("CallProcessing","false"), @("CallLog","false"),
@("AutoSendSMS","false"), @("ReceiveSMS","false"), @("SMSLog","false"),
@("Camera","false"), @("Microphone","false"), @("MusicLibrary","false"),
@("PictureAndVideoLibraries","false"), @("AudioPlaybackAndVibration","false"),
@("BackgroundAudioPlaybackAndVibration","false"), @("InstallPackages","false"),
@("OSBackup","true"), @("ApplicationUsageStatistics","false"),
@("BarcodeScanning","false"), @("BackgroundAudioRecording","false"),
@("AllFilesAccess","false"), @("Videoconferences","false"), @("NFC","false"),
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
)
$mobileXml = ""
foreach ($mf in $mobileFuncs) {
$mobileXml += "`r`n`t`t`t`t<app:functionality>`r`n`t`t`t`t`t<app:functionality>$($mf[0])</app:functionality>`r`n`t`t`t`t`t<app:use>$($mf[1])</app:use>`r`n`t`t`t`t</app:functionality>"
}
# --- Synonym XML ---
$synonymXml = ""
if ($Synonym) {
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
}
# --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- Configuration.xml ---
$cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="$uuidCfg">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>$co1</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>$co2</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>$co3</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>$co4</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>$co5</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>$co6</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>$co7</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
<Synonym>$synonymXml</Synonym>
<Comment/>
<NamePrefix/>
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/>
<Vendor>$vendorXml</Vendor>
<Version>$versionXml</Version>
<UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage/>
<ReportsUserSettingsStorage/>
<ReportsVariantsStorage/>
<FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage/>
<URLExternalDataStorage/>
<Content/>
<DefaultReportForm/>
<DefaultReportVariantForm/>
<DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm/>
<DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>$mobileXml
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface/>
<DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>$CompatibilityMode</CompatibilityMode>
<DefaultConstantsForm/>
</Properties>
<ChildObjects>
<Language>Русский</Language>
</ChildObjects>
</Configuration>
</MetaDataObject>
"@
# --- Languages/Русский.xml ---
$langXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="$uuidLang">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
"@
# --- Create directories ---
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$langDir = Join-Path $OutputDir "Languages"
if (-not (Test-Path $langDir)) {
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
}
# --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
# --- Output ---
Write-Host "[OK] Создана конфигурация: $Name"
Write-Host " Каталог: $OutputDir"
Write-Host " Configuration.xml: $cfgFile"
Write-Host " Languages: $langFile"
+70
View File
@@ -0,0 +1,70 @@
---
name: cf-validate
description: Валидация конфигурации 1С. Используй после создания или модификации конфигурации для проверки корректности
argument-hint: <ConfigPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /cf-validate — валидация конфигурации 1С
Проверяет Configuration.xml на структурные ошибки: XML well-formedness, InternalInfo, свойства, enum-значения, ChildObjects, DefaultLanguage, файлы языков, каталоги объектов.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `ConfigPath` | Путь к Configuration.xml или каталогу выгрузки |
| `MaxErrors` | Остановиться после N ошибок (default: 30) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell
powershell.exe -NoProfile -File .claude\skills\cf-validate\scripts\cf-validate.ps1 -ConfigPath "<путь>"
```
## Выполняемые проверки
| # | Проверка | Серьёзность |
|---|----------|-------------|
| 1 | XML well-formedness, MetaDataObject/Configuration, version 2.17/2.20 | ERROR |
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId, уникальность | ERROR |
| 3 | Properties: Name непустой, Synonym, DefaultLanguage, DefaultRunMode | ERROR/WARN |
| 4 | Properties: enum-значения (11 свойств) | ERROR |
| 5 | ChildObjects: валидные имена типов (44 типа), нет дубликатов, порядок типов | ERROR/WARN |
| 6 | DefaultLanguage ссылается на существующий Language в ChildObjects | ERROR |
| 7 | Файлы языков Languages/<name>.xml существуют | WARN |
| 8 | Каталоги объектов из ChildObjects существуют (spot-check) | WARN |
## Вывод
```
=== Validation: Configuration.МояКонфигурация ===
[OK] 1. Root structure: MetaDataObject/Configuration, version 2.17
[OK] 2. InternalInfo: 7 ContainedObject, all ClassIds valid
[OK] 3. Properties: Name="МояКонфигурация", Synonym present
[OK] 4. Property values: 11 enum properties checked
[OK] 5. ChildObjects: 1 types, 1 objects, order correct
[OK] 6. DefaultLanguage "Language.Русский" found in ChildObjects
[OK] 7. Language files: 1/1 exist
[OK] 8. Object directories: spot-check passed
=== Result: 0 errors, 0 warnings ===
```
Exit code: 0 = OK, 1 = errors.
## Примеры
```powershell
# Пустая конфигурация
... -ConfigPath upload/cfempty
# Реальная конфигурация
... -ConfigPath C:\WS\tasks\cfsrc\acc_8.3.24
# С лимитом ошибок
... -ConfigPath test-tmp/cf -MaxErrors 10
```
@@ -0,0 +1,538 @@
# cf-validate v1.0 — Validate 1C configuration root structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ConfigPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ConfigPath -PathType Container) {
$candidate = Join-Path $ConfigPath "Configuration.xml"
if (Test-Path $candidate) {
$ConfigPath = $candidate
} else {
Write-Host "[ERROR] No Configuration.xml found in directory: $ConfigPath"
exit 1
}
}
if (-not (Test-Path $ConfigPath)) {
Write-Host "[ERROR] File not found: $ConfigPath"
exit 1
}
$resolvedPath = (Resolve-Path $ConfigPath).Path
$configDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
Out-Line "[OK] $msg"
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
# 7 fixed ClassIds for Configuration
$validClassIds = @(
"9cd510cd-abfc-11d4-9434-004095e12fc7", # managed application module
"9fcd25a0-4822-11d4-9414-008048da11f9", # ordinary application module
"e3687481-0a87-462c-a166-9f34594f9bba", # session module
"9de14907-ec23-4a07-96f0-85521cb6b53b", # external connection module
"51f2d5d8-ea4d-4064-8892-82951750031e", # command interface
"e68182ea-4237-4383-967f-90c1e3370bc7", # main section command interface
"fb282519-d103-4dd3-bc12-cb271d631dfc" # home page / client app interface
)
# 44 types in canonical order
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# Type -> directory mapping
$childTypeDirMap = @{
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"
"CommonTemplate"="CommonTemplates"; "FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"
"CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"; "XDTOPackage"="XDTOPackages"
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FunctionalOption"="FunctionalOptions"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"; "DefinedType"="DefinedTypes"
"CommonCommand"="CommonCommands"; "CommandGroup"="CommandGroups"; "Constant"="Constants"
"CommonForm"="CommonForms"; "Catalog"="Catalogs"; "Document"="Documents"
"DocumentNumerator"="DocumentNumerators"; "Sequence"="Sequences"
"DocumentJournal"="DocumentJournals"; "Enum"="Enums"; "Report"="Reports"
"DataProcessor"="DataProcessors"; "InformationRegister"="InformationRegisters"
"AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
"CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"IntegrationService"="IntegrationServices"
}
# Valid enum values for Configuration properties
$validEnumValues = @{
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
"ScriptVariant" = @("Russian","English")
"DataLockControlMode" = @("Automatic","Managed","AutomaticAndManaged")
"ObjectAutonumerationMode" = @("NotAutoFree","AutoFree")
"ModalityUseMode" = @("DontUse","Use","UseWithWarnings")
"SynchronousPlatformExtensionAndAddInCallUseMode" = @("DontUse","Use","UseWithWarnings")
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
"DatabaseTablespacesUseMode" = @("DontUse","Use")
"MainClientApplicationWindowMode" = @("Normal","Fullscreen","Kiosk")
"CompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: Configuration (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
}
# Must have Configuration child
$cfgNode = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration" -and $child.NamespaceURI -eq $expectedNs) {
$cfgNode = $child; break
}
}
if (-not $cfgNode) {
Report-Error "1. No <Configuration> element found inside MetaDataObject"
& $finalize
exit 1
}
# UUID
$cfgUuid = $cfgNode.GetAttribute("uuid")
if (-not $cfgUuid) {
Report-Error "1. Missing uuid on <Configuration>"
$check1Ok = $false
} elseif ($cfgUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$cfgUuid' on <Configuration>"
$check1Ok = $false
}
# Get name early for header
$propsNode = $cfgNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
$script:output.Insert(0, "=== Validation: Configuration.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/Configuration, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $cfgNode.SelectSingleNode("md:InternalInfo", $ns)
$check2Ok = $true
if (-not $internalInfo) {
Report-Error "2. InternalInfo: missing"
} else {
$contained = $internalInfo.SelectNodes("xr:ContainedObject", $ns)
if ($contained.Count -ne 7) {
Report-Warn "2. InternalInfo: expected 7 ContainedObject, found $($contained.Count)"
}
$foundClassIds = @{}
foreach ($co in $contained) {
$classId = $co.SelectSingleNode("xr:ClassId", $ns)
$objectId = $co.SelectSingleNode("xr:ObjectId", $ns)
if (-not $classId -or -not $classId.InnerText) {
Report-Error "2. ContainedObject missing ClassId"
$check2Ok = $false
continue
}
$cid = $classId.InnerText
if ($validClassIds -notcontains $cid) {
Report-Error "2. Unknown ClassId: $cid"
$check2Ok = $false
}
if ($foundClassIds.ContainsKey($cid)) {
Report-Error "2. Duplicate ClassId: $cid"
$check2Ok = $false
}
$foundClassIds[$cid] = $true
if (-not $objectId -or -not $objectId.InnerText) {
Report-Error "2. ContainedObject missing ObjectId for ClassId $cid"
$check2Ok = $false
} elseif ($objectId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ObjectId '$($objectId.InnerText)' for ClassId $cid"
$check2Ok = $false
}
}
# Check missing ClassIds
$missingIds = @($validClassIds | Where-Object { -not $foundClassIds.ContainsKey($_) })
if ($missingIds.Count -gt 0) {
Report-Warn "2. Missing ClassIds: $($missingIds.Count) of 7"
}
if ($check2Ok) {
Report-OK "2. InternalInfo: $($contained.Count) ContainedObject, all ClassIds valid"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Properties — Name, Synonym, DefaultLanguage, DefaultRunMode ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Properties: Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Properties: Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
}
# Synonym
$synNode = $propsNode.SelectSingleNode("md:Synonym", $ns)
$synPresent = $false
if ($synNode) {
$synItem = $synNode.SelectSingleNode("v8:item", $ns)
if ($synItem) {
$synContent = $synItem.SelectSingleNode("v8:content", $ns)
if ($synContent -and $synContent.InnerText) { $synPresent = $true }
}
}
# DefaultLanguage
$defLangNode = $propsNode.SelectSingleNode("md:DefaultLanguage", $ns)
$defLang = if ($defLangNode -and $defLangNode.InnerText) { $defLangNode.InnerText } else { "" }
if (-not $defLang) {
Report-Error "3. Properties: DefaultLanguage is missing or empty"
$check3Ok = $false
}
# DefaultRunMode
$defRunNode = $propsNode.SelectSingleNode("md:DefaultRunMode", $ns)
if (-not $defRunNode -or -not $defRunNode.InnerText) {
Report-Warn "3. Properties: DefaultRunMode is missing or empty"
}
if ($check3Ok) {
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
Report-OK "3. Properties: Name=`"$objName`", $synInfo, DefaultLanguage=$defLang"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: Property values — enum properties ---
if ($propsNode) {
$enumChecked = 0
$check4Ok = $true
foreach ($propName in $validEnumValues.Keys) {
$propNode = $propsNode.SelectSingleNode("md:$propName", $ns)
if ($propNode -and $propNode.InnerText) {
$val = $propNode.InnerText
$allowed = $validEnumValues[$propName]
if ($allowed -notcontains $val) {
Report-Error "4. Property '$propName' has invalid value '$val'"
$check4Ok = $false
}
$enumChecked++
}
}
if ($check4Ok) {
Report-OK "4. Property values: $enumChecked enum properties checked"
}
} else {
Report-Warn "4. No Properties block to check"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: ChildObjects — valid types, no duplicates, order ---
$childObjNode = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
if (-not $childObjNode) {
Report-Error "5. ChildObjects block missing"
} else {
$check5Ok = $true
$totalCount = 0
$typeCounts = @{}
$duplicates = @{}
$typeFirstIndex = @{} # type -> first position index
$lastTypeOrder = -1
$orderOk = $true
$idx = 0
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
$objNameVal = $child.InnerText
# Valid type?
$typeIdx = $childObjectTypes.IndexOf($typeName)
if ($typeIdx -lt 0) {
Report-Error "5. Unknown type '$typeName' in ChildObjects"
$check5Ok = $false
} else {
# Check order
if (-not $typeFirstIndex.ContainsKey($typeName)) {
$typeFirstIndex[$typeName] = $typeIdx
if ($typeIdx -lt $lastTypeOrder) {
Report-Warn "5. Type '$typeName' is out of canonical order (after type at position $lastTypeOrder)"
$orderOk = $false
}
$lastTypeOrder = $typeIdx
}
}
# Count and dedup
if (-not $typeCounts.ContainsKey($typeName)) { $typeCounts[$typeName] = @{} }
if ($typeCounts[$typeName].ContainsKey($objNameVal)) {
if (-not $duplicates.ContainsKey("$typeName.$objNameVal")) {
Report-Error "5. Duplicate: $typeName.$objNameVal"
$duplicates["$typeName.$objNameVal"] = $true
$check5Ok = $false
}
} else {
$typeCounts[$typeName][$objNameVal] = $true
}
$totalCount++
$idx++
}
$typeCount = $typeCounts.Count
if ($check5Ok) {
$orderInfo = if ($orderOk) { ", order correct" } else { "" }
Report-OK "5. ChildObjects: $typeCount types, $totalCount objects${orderInfo}"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: DefaultLanguage references existing Language in ChildObjects ---
if ($defLang -and $childObjNode) {
# DefaultLanguage is like "Language.Русский"
$langName = $defLang
if ($langName.StartsWith("Language.")) {
$langName = $langName.Substring(9)
}
$found = $false
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language" -and $child.InnerText -eq $langName) {
$found = $true; break
}
}
if ($found) {
Report-OK "6. DefaultLanguage `"$defLang`" found in ChildObjects"
} else {
Report-Error "6. DefaultLanguage `"$defLang`" not found in ChildObjects"
}
} else {
if (-not $defLang) {
Report-Warn "6. Cannot check DefaultLanguage (empty)"
} else {
Report-Warn "6. Cannot check DefaultLanguage (no ChildObjects)"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: Language files exist ---
if ($childObjNode) {
$langNames = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language") {
$langNames += $child.InnerText
}
}
if ($langNames.Count -gt 0) {
$existCount = 0
foreach ($ln in $langNames) {
$langFile = Join-Path (Join-Path $configDir "Languages") "$ln.xml"
if (Test-Path $langFile) {
$existCount++
} else {
Report-Warn "7. Language file missing: Languages/$ln.xml"
}
}
if ($existCount -eq $langNames.Count) {
Report-OK "7. Language files: $existCount/$($langNames.Count) exist"
}
} else {
Report-Warn "7. No Language entries in ChildObjects"
}
} else {
Report-Warn "7. Cannot check language files (no ChildObjects)"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Object directories exist (spot-check) ---
if ($childObjNode) {
$dirsToCheck = @{}
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
if ($typeName -eq "Language") { continue } # Already checked
if ($childTypeDirMap.ContainsKey($typeName)) {
$dirName = $childTypeDirMap[$typeName]
if (-not $dirsToCheck.ContainsKey($dirName)) {
$dirsToCheck[$dirName] = 0
}
$dirsToCheck[$dirName] = $dirsToCheck[$dirName] + 1
}
}
$missingDirs = @()
foreach ($dir in $dirsToCheck.Keys) {
$dirPath = Join-Path $configDir $dir
if (-not (Test-Path $dirPath -PathType Container)) {
$missingDirs += "$dir ($($dirsToCheck[$dir]) objects)"
}
}
if ($missingDirs.Count -eq 0) {
Report-OK "8. Object directories: $($dirsToCheck.Count) directories, all exist"
} else {
foreach ($md in $missingDirs) {
Report-Warn "8. Missing directory: $md"
}
}
} else {
Report-OK "8. Object directories: N/A"
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0
+58
View File
@@ -0,0 +1,58 @@
---
name: cfe-borrow
description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты"
allowed-tools:
- Bash
- Read
- Glob
---
# /cfe-borrow — Заимствование объектов из конфигурации
Заимствует объекты из основной конфигурации в расширение. Создаёт XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`, добавляет запись в ChildObjects расширения.
## Предусловие
Расширение должно быть создано (`/cfe-init`) и содержать валидный `Configuration.xml`.
## Параметры
| Параметр | Описание |
|----------|----------|
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
| `Object` | Что заимствовать (обязат.), batch через `;;` |
## Формат -Object
- `Catalog.Контрагенты` — справочник
- `CommonModule.РаботаСФайлами` — общий модуль
- `Document.РеализацияТоваров` — документ
- `Enum.ВидыОплат` — перечисление
- `Catalog.X ;; CommonModule.Y ;; Enum.Z` — несколько объектов
Поддерживаются все 44 типа объектов конфигурации.
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-borrow\scripts\cfe-borrow.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
```
## Примеры
```powershell
# Заимствовать один объект
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
# Несколько объектов за раз
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
```
## Верификация
```
/cfe-validate <ExtensionPath>
```
@@ -0,0 +1,587 @@
# cfe-borrow v1.0 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$ExtensionPath,
[Parameter(Mandatory)][string]$ConfigPath,
[Parameter(Mandatory)][string]$Object
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
function Info([string]$msg) { Write-Host "[INFO] $msg" }
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
# --- 1. Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Container) {
$candidate = Join-Path $ExtensionPath "Configuration.xml"
if (Test-Path $candidate) { $ExtensionPath = $candidate }
else { Write-Error "No Configuration.xml in extension directory: $ExtensionPath"; exit 1 }
}
if (-not (Test-Path $ExtensionPath)) { Write-Error "Extension file not found: $ExtensionPath"; exit 1 }
$extResolvedPath = (Resolve-Path $ExtensionPath).Path
$extDir = Split-Path $extResolvedPath -Parent
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ConfigPath -PathType Container) {
$candidate = Join-Path $ConfigPath "Configuration.xml"
if (Test-Path $candidate) { $ConfigPath = $candidate }
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
}
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
$cfgResolvedPath = (Resolve-Path $ConfigPath).Path
$cfgDir = Split-Path $cfgResolvedPath -Parent
# --- 2. Load extension Configuration.xml ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($extResolvedPath)
$script:mdNs = "http://v8.1c.ru/8.3/MDClasses"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
$root = $script:xmlDoc.DocumentElement
$script:cfgEl = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration") {
$script:cfgEl = $child; break
}
}
if (-not $script:cfgEl) { Write-Error "No <Configuration> element found in extension"; exit 1 }
$script:propsEl = $null
$script:childObjsEl = $null
foreach ($child in $script:cfgEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Properties") { $script:propsEl = $child }
if ($child.LocalName -eq "ChildObjects") { $script:childObjsEl = $child }
}
if (-not $script:propsEl) { Write-Error "No <Properties> element found in extension"; exit 1 }
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found in extension"; exit 1 }
# --- 3. Extract NamePrefix ---
$script:namePrefix = ""
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "NamePrefix") {
$script:namePrefix = $child.InnerText.Trim(); break
}
}
Info "Extension NamePrefix: $($script:namePrefix)"
# --- 4. Type mappings ---
$childTypeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"
"HTTPService"="HTTPServices"; "WSReference"="WSReferences"
"CommonAttribute"="CommonAttributes"; "Style"="Styles"
}
# --- 5. Canonical type order (44 types) ---
$script:typeOrder = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# --- 6. GeneratedType patterns per type ---
$script:generatedTypes = @{
"Catalog" = @(
@{ prefix = "CatalogObject"; category = "Object" }
@{ prefix = "CatalogRef"; category = "Ref" }
@{ prefix = "CatalogSelection"; category = "Selection" }
@{ prefix = "CatalogList"; category = "List" }
@{ prefix = "CatalogManager"; category = "Manager" }
)
"Document" = @(
@{ prefix = "DocumentObject"; category = "Object" }
@{ prefix = "DocumentRef"; category = "Ref" }
@{ prefix = "DocumentSelection"; category = "Selection" }
@{ prefix = "DocumentList"; category = "List" }
@{ prefix = "DocumentManager"; category = "Manager" }
)
"Enum" = @(
@{ prefix = "EnumRef"; category = "Ref" }
@{ prefix = "EnumManager"; category = "Manager" }
@{ prefix = "EnumList"; category = "List" }
)
"Constant" = @(
@{ prefix = "ConstantManager"; category = "Manager" }
@{ prefix = "ConstantValueManager"; category = "ValueManager" }
@{ prefix = "ConstantValueKey"; category = "ValueKey" }
)
"InformationRegister" = @(
@{ prefix = "InformationRegisterRecord"; category = "Record" }
@{ prefix = "InformationRegisterManager"; category = "Manager" }
@{ prefix = "InformationRegisterSelection"; category = "Selection" }
@{ prefix = "InformationRegisterList"; category = "List" }
@{ prefix = "InformationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "InformationRegisterRecordKey"; category = "RecordKey" }
@{ prefix = "InformationRegisterRecordManager"; category = "RecordManager" }
)
"AccumulationRegister" = @(
@{ prefix = "AccumulationRegisterRecord"; category = "Record" }
@{ prefix = "AccumulationRegisterManager"; category = "Manager" }
@{ prefix = "AccumulationRegisterSelection"; category = "Selection" }
@{ prefix = "AccumulationRegisterList"; category = "List" }
@{ prefix = "AccumulationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" }
)
"AccountingRegister" = @(
@{ prefix = "AccountingRegisterRecord"; category = "Record" }
@{ prefix = "AccountingRegisterManager"; category = "Manager" }
@{ prefix = "AccountingRegisterSelection"; category = "Selection" }
@{ prefix = "AccountingRegisterList"; category = "List" }
@{ prefix = "AccountingRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "AccountingRegisterRecordKey"; category = "RecordKey" }
)
"CalculationRegister" = @(
@{ prefix = "CalculationRegisterRecord"; category = "Record" }
@{ prefix = "CalculationRegisterManager"; category = "Manager" }
@{ prefix = "CalculationRegisterSelection"; category = "Selection" }
@{ prefix = "CalculationRegisterList"; category = "List" }
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
)
"ChartOfAccounts" = @(
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
@{ prefix = "ChartOfAccountsRef"; category = "Ref" }
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
@{ prefix = "ChartOfAccountsList"; category = "List" }
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
)
"ChartOfCharacteristicTypes" = @(
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
@{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" }
@{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" }
@{ prefix = "ChartOfCharacteristicTypesList"; category = "List" }
@{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" }
)
"ChartOfCalculationTypes" = @(
@{ prefix = "ChartOfCalculationTypesObject"; category = "Object" }
@{ prefix = "ChartOfCalculationTypesRef"; category = "Ref" }
@{ prefix = "ChartOfCalculationTypesSelection"; category = "Selection" }
@{ prefix = "ChartOfCalculationTypesList"; category = "List" }
@{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" }
@{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" }
@{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" }
@{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" }
)
"BusinessProcess" = @(
@{ prefix = "BusinessProcessObject"; category = "Object" }
@{ prefix = "BusinessProcessRef"; category = "Ref" }
@{ prefix = "BusinessProcessSelection"; category = "Selection" }
@{ prefix = "BusinessProcessList"; category = "List" }
@{ prefix = "BusinessProcessManager"; category = "Manager" }
)
"Task" = @(
@{ prefix = "TaskObject"; category = "Object" }
@{ prefix = "TaskRef"; category = "Ref" }
@{ prefix = "TaskSelection"; category = "Selection" }
@{ prefix = "TaskList"; category = "List" }
@{ prefix = "TaskManager"; category = "Manager" }
)
"ExchangePlan" = @(
@{ prefix = "ExchangePlanObject"; category = "Object" }
@{ prefix = "ExchangePlanRef"; category = "Ref" }
@{ prefix = "ExchangePlanSelection"; category = "Selection" }
@{ prefix = "ExchangePlanList"; category = "List" }
@{ prefix = "ExchangePlanManager"; category = "Manager" }
)
"DocumentJournal" = @(
@{ prefix = "DocumentJournalSelection"; category = "Selection" }
@{ prefix = "DocumentJournalList"; category = "List" }
@{ prefix = "DocumentJournalManager"; category = "Manager" }
)
"Report" = @(
@{ prefix = "ReportObject"; category = "Object" }
)
"DataProcessor" = @(
@{ prefix = "DataProcessorObject"; category = "Object" }
)
}
# Types that need ChildObjects element
$typesWithChildObjects = @(
"Catalog","Document","ExchangePlan","ChartOfAccounts",
"ChartOfCharacteristicTypes","ChartOfCalculationTypes",
"BusinessProcess","Task","Enum",
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister"
)
# CommonModule properties to copy from source
$commonModuleProps = @("Global","ClientManagedApplication","Server","ExternalConnection","ClientOrdinaryApplication","ServerCall")
# --- 7. XML manipulation helpers (from cf-edit) ---
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0; $current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Expand-SelfClosingElement($container, $parentIndent) {
if (-not $container.HasChildNodes -or $container.IsEmpty) {
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
# --- 8. Namespaces declaration for object XML ---
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
# --- 9. Parse -Object into items ---
$items = @()
foreach ($part in $Object.Split(";;")) {
$trimmed = $part.Trim()
if ($trimmed) { $items += $trimmed }
}
if ($items.Count -eq 0) {
Write-Error "No objects specified in -Object"
exit 1
}
# --- 10. Helper: read source object XML ---
function Read-SourceObject {
param([string]$typeName, [string]$objName)
$dirName = $childTypeDirMap[$typeName]
if (-not $dirName) {
Write-Error "Unknown type '$typeName'"
exit 1
}
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
if (-not (Test-Path $srcFile)) {
Write-Error "Source object not found: $srcFile"
exit 1
}
$srcDoc = New-Object System.Xml.XmlDocument
$srcDoc.PreserveWhitespace = $false
$srcDoc.Load($srcFile)
$srcNs = New-Object System.Xml.XmlNamespaceManager($srcDoc.NameTable)
$srcNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$srcNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
# Find the type element (e.g. <Catalog uuid="...">)
$srcRoot = $srcDoc.DocumentElement
$srcEl = $null
foreach ($c in $srcRoot.ChildNodes) {
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
}
if (-not $srcEl) {
Write-Error "No metadata element found in ${dirName}/${objName}.xml"
exit 1
}
# Extract uuid
$srcUuid = $srcEl.GetAttribute("uuid")
if (-not $srcUuid) {
Write-Error "No uuid attribute on source element in ${dirName}/${objName}.xml"
exit 1
}
# Extract properties for CommonModule
$srcProps = @{}
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
if ($propsNode) {
foreach ($propName in $commonModuleProps) {
$propNode = $propsNode.SelectSingleNode("md:${propName}", $srcNs)
if ($propNode) {
$srcProps[$propName] = $propNode.InnerText.Trim()
}
}
}
return @{
Uuid = $srcUuid
Properties = $srcProps
Element = $srcEl
NsManager = $srcNs
}
}
# --- 11. Helper: generate InternalInfo XML ---
function Build-InternalInfoXml {
param([string]$typeName, [string]$objName, [string]$indent)
$types = $script:generatedTypes[$typeName]
if (-not $types -or $types.Count -eq 0) {
return "${indent}<InternalInfo/>"
}
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("${indent}<InternalInfo>") | Out-Null
# ExchangePlan: ThisNode UUID before GeneratedTypes
if ($typeName -eq "ExchangePlan") {
$thisNodeUuid = [guid]::NewGuid().ToString()
$sb.AppendLine("${indent}`t<xr:ThisNode>${thisNodeUuid}</xr:ThisNode>") | Out-Null
}
foreach ($gt in $types) {
$fullName = "$($gt.prefix).${objName}"
$typeId = [guid]::NewGuid().ToString()
$valueId = [guid]::NewGuid().ToString()
$sb.AppendLine("${indent}`t<xr:GeneratedType name=`"${fullName}`" category=`"$($gt.category)`">") | Out-Null
$sb.AppendLine("${indent}`t`t<xr:TypeId>${typeId}</xr:TypeId>") | Out-Null
$sb.AppendLine("${indent}`t`t<xr:ValueId>${valueId}</xr:ValueId>") | Out-Null
$sb.AppendLine("${indent}`t</xr:GeneratedType>") | Out-Null
}
$sb.Append("${indent}</InternalInfo>") | Out-Null
return $sb.ToString()
}
# --- 12. Helper: build borrowed object XML ---
function Build-BorrowedObjectXml {
param(
[string]$typeName,
[string]$objName,
[string]$sourceUuid,
[hashtable]$sourceProps
)
$newUuid = [guid]::NewGuid().ToString()
$internalInfoXml = Build-InternalInfoXml $typeName $objName "`t`t"
$sb = New-Object System.Text.StringBuilder
$sb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
$sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null
# InternalInfo
$sb.AppendLine($internalInfoXml) | Out-Null
# Properties
$sb.AppendLine("`t`t<Properties>") | Out-Null
$sb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
$sb.AppendLine("`t`t`t<Name>${objName}</Name>") | Out-Null
$sb.AppendLine("`t`t`t<Comment/>") | Out-Null
$sb.AppendLine("`t`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
# CommonModule: extra properties from source
if ($typeName -eq "CommonModule") {
foreach ($propName in $commonModuleProps) {
$propVal = "false"
if ($sourceProps.ContainsKey($propName)) {
$propVal = $sourceProps[$propName]
}
$sb.AppendLine("`t`t`t<${propName}>${propVal}</${propName}>") | Out-Null
}
}
$sb.AppendLine("`t`t</Properties>") | Out-Null
# ChildObjects (for types that need it)
if ($typesWithChildObjects -contains $typeName) {
$sb.AppendLine("`t`t<ChildObjects/>") | Out-Null
}
$sb.AppendLine("`t</${typeName}>") | Out-Null
$sb.Append("</MetaDataObject>") | Out-Null
return $sb.ToString()
}
# --- 13. Helper: add object to extension ChildObjects ---
function Add-ToChildObjects {
param([string]$typeName, [string]$objName)
$cfgIndent = Get-ChildIndent $script:cfgEl
# Expand self-closing ChildObjects if needed
if (-not $script:childObjsEl.HasChildNodes -or $script:childObjsEl.IsEmpty) {
Expand-SelfClosingElement $script:childObjsEl $cfgIndent
}
$childIndent = Get-ChildIndent $script:childObjsEl
$typeIdx = $script:typeOrder.IndexOf($typeName)
if ($typeIdx -lt 0) {
Write-Error "Unknown type '$typeName' for ChildObjects ordering"
exit 1
}
# Dedup check
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName -and $child.InnerText -eq $objName) {
Warn "Already in ChildObjects: ${typeName}.${objName}"
return
}
}
# Find insertion point: after last element of same type, or before first element of later type
$insertBefore = $null
$lastSameType = $null
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTypeIdx = $script:typeOrder.IndexOf($child.LocalName)
if ($childTypeIdx -lt 0) { continue }
if ($child.LocalName -eq $typeName) {
# Same type -- check alphabetical order
if ($child.InnerText -gt $objName -and -not $insertBefore) {
$insertBefore = $child
}
$lastSameType = $child
} elseif ($childTypeIdx -gt $typeIdx -and -not $insertBefore) {
# First element of a later type -- insert before it
$insertBefore = $child
}
}
# Create element
$newEl = $script:xmlDoc.CreateElement($typeName, $script:mdNs)
$newEl.InnerText = $objName
if ($insertBefore) {
Insert-BeforeElement $script:childObjsEl $newEl $insertBefore $childIndent
} else {
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
}
Info "Added to ChildObjects: ${typeName}.${objName}"
}
# --- 14. Process each item ---
$borrowedFiles = @()
$borrowedCount = 0
foreach ($item in $items) {
$dotIdx = $item.IndexOf(".")
if ($dotIdx -lt 1) {
Write-Error "Invalid format '${item}', expected 'Type.Name'"
exit 1
}
$typeName = $item.Substring(0, $dotIdx)
$objName = $item.Substring($dotIdx + 1)
if (-not $childTypeDirMap.ContainsKey($typeName)) {
Write-Error "Unknown type '${typeName}'"
exit 1
}
$dirName = $childTypeDirMap[$typeName]
Info "Borrowing ${typeName}.${objName}..."
# Read source object
$src = Read-SourceObject $typeName $objName
Info " Source UUID: $($src.Uuid)"
# Build borrowed object XML
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
# Create directory in extension if needed
$targetDir = Join-Path $extDir $dirName
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
# Write borrowed object XML with UTF-8 BOM
$targetFile = Join-Path $targetDir "${objName}.xml"
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
Info " Created: $targetFile"
# Add to ChildObjects
Add-ToChildObjects $typeName $objName
$borrowedFiles += $targetFile
$borrowedCount++
}
# --- 15. Save modified Configuration.xml ---
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
Info "Saved: $extResolvedPath"
# --- 16. Summary ---
Write-Host ""
Write-Host "=== cfe-borrow summary ==="
Write-Host " Extension: $extDir"
Write-Host " Config: $cfgDir"
Write-Host " Borrowed: $borrowedCount object(s)"
foreach ($f in $borrowedFiles) {
Write-Host " - $f"
}
exit 0
+61
View File
@@ -0,0 +1,61 @@
---
name: cfe-diff
description: Анализ расширения конфигурации 1С (CFE) — обзор изменений и проверка переноса. Используй для понимания что изменено в расширении или для проверки перенесены ли изменения из расширения в конфигурацию
argument-hint: -ExtensionPath <path> -ConfigPath <path> [-Mode A|B]
allowed-tools:
- Bash
- Read
- Glob
---
# /cfe-diff — Анализ расширения конфигурации
Анализирует расширение в двух режимах: обзор изменений (Mode A) или проверка переноса (Mode B).
## Параметры
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `ExtensionPath` | Путь к расширению (обязат.) | — |
| `ConfigPath` | Путь к конфигурации (обязат.) | — |
| `Mode` | `A` (обзор) / `B` (проверка переноса) | `A` |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-diff\scripts\cfe-diff.ps1 -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
```
## Mode A — обзор расширения
Для каждого объекта показывает:
- `[BORROWED]` — заимствованный: перехватчики (`&Перед`, `&После`, `&ИзменениеИКонтроль`, `&Вместо`), собственные реквизиты/ТЧ/формы
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
Пример вывода:
```
[BORROWED] Catalog.Валюты
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке") — line 4 in ...
&Перед("ЗагрузитьКурсыВалют") — line 13 in ...
ChildObjects: 1 own attrs, 1 own TS, 3 own forms
[OWN] Catalog.Расш5_Справочник1
```
## Mode B — проверка переноса
Для каждого `&ИзменениеИКонтроль` извлекает блоки `#Вставка`/`#КонецВставки` из расширения и ищет их в соответствующем модуле конфигурации.
Статусы:
- `[TRANSFERRED]` — код найден в конфигурации
- `[NOT_TRANSFERRED]` — код не найден
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
## Примеры
```powershell
# Обзор — что изменено в расширении
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
# Проверка переноса — все ли #Вставка перенесены
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B
```
@@ -0,0 +1,392 @@
# cfe-diff v1.0 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[Parameter(Mandatory)]
[string]$ConfigPath,
[ValidateSet("A","B")]
[string]$Mode = "A"
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
}
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
$extCfg = Join-Path $ExtensionPath "Configuration.xml"
$srcCfg = Join-Path $ConfigPath "Configuration.xml"
if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 }
if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
# --- Type -> directory mapping ---
$childTypeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
"CommonAttribute"="CommonAttributes"
}
# --- Parse extension Configuration.xml ---
$extDoc = New-Object System.Xml.XmlDocument
$extDoc.PreserveWhitespace = $false
$extDoc.Load($extCfg)
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
Write-Host " NamePrefix: $namePrefix"
Write-Host ""
# --- Collect ChildObjects ---
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
if (-not $childObjNode) {
Write-Host "[WARN] No ChildObjects in extension"
exit 0
}
$objects = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Language") { continue }
$objects += @{ Type = $child.LocalName; Name = $child.InnerText }
}
if ($objects.Count -eq 0) {
Write-Host "No objects (besides Language) in extension."
exit 0
}
# --- Helper: check if object is borrowed ---
function Get-ObjectInfo {
param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
$dirName = $childTypeDirMap[$objType]
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($objFile)
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$objEl = $null
foreach ($c in $doc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
$info = @{
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
File = $objFile
Exists = $true
Type = $objType
Name = $objName
DirName = $dirName
ObjElement = $objEl
ObjNs = $objNs
}
return $info
}
# --- Helper: find .bsl files for object ---
function Get-BslFiles {
param([string]$objType, [string]$objName)
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
$dirName = $childTypeDirMap[$objType]
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
if (-not (Test-Path $objDir -PathType Container)) { return @() }
$bslFiles = @()
$extDir = Join-Path $objDir "Ext"
if (Test-Path $extDir) {
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
foreach ($item in $items) { $bslFiles += $item.FullName }
}
# Forms
$formsDir = Join-Path $objDir "Forms"
if (Test-Path $formsDir) {
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
}
return $bslFiles
}
# --- Helper: parse interceptors from .bsl ---
function Get-Interceptors {
param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$interceptors = @()
$i = 0
while ($i -lt $lines.Count) {
$line = $lines[$i].Trim()
if ($line -match '^&(Перед|После|ИзменениеИКонтроль|Вместо)\("([^"]+)"\)') {
$type = $Matches[1]
$method = $Matches[2]
$interceptors += @{ Type = $type; Method = $method; Line = $i + 1; File = $bslPath }
}
$i++
}
return $interceptors
}
# --- Helper: extract #Вставка blocks from .bsl ---
function Get-InsertionBlocks {
param([string]$bslPath)
if (-not (Test-Path $bslPath)) { return @() }
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
$blocks = @()
$inBlock = $false
$blockLines = @()
$startLine = 0
for ($i = 0; $i -lt $lines.Count; $i++) {
$line = $lines[$i].Trim()
if ($line -eq "#Вставка") {
$inBlock = $true
$blockLines = @()
$startLine = $i + 1
} elseif ($line -eq "#КонецВставки" -and $inBlock) {
$inBlock = $false
$blocks += @{
StartLine = $startLine
EndLine = $i + 1
Code = ($blockLines -join "`n").Trim()
File = $bslPath
}
} elseif ($inBlock) {
$blockLines += $lines[$i]
}
}
return $blocks
}
# ============================================================
# MODE A: Extension overview
# ============================================================
if ($Mode -eq "A") {
$borrowedList = @()
$ownList = @()
foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info) {
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
continue
}
if (-not $info.Exists) {
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
continue
}
if ($info.Borrowed) {
$borrowedList += $obj
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
# Find .bsl files and interceptors
$bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) {
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
$interceptors = Get-Interceptors $bsl
if ($interceptors.Count -gt 0) {
foreach ($ic in $interceptors) {
Write-Host " &$($ic.Type)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
}
} else {
Write-Host " $relPath (no interceptors)"
}
}
# Check for own attributes/forms in ChildObjects
if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) {
$ownAttrs = 0
$ownForms = 0
$ownTS = 0
$borrowedItems = 0
foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
if ($cProps) {
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
if ($cOb -and $cOb.InnerText -eq "Adopted") {
$borrowedItems++
continue
}
}
switch ($c.LocalName) {
"Attribute" { $ownAttrs++ }
"TabularSection" { $ownTS++ }
"Form" { $ownForms++ }
}
}
$parts = @()
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
if ($parts.Count -gt 0) {
Write-Host " ChildObjects: $($parts -join ', ')"
}
}
}
} else {
$ownList += $obj
Write-Host " [OWN] $($obj.Type).$($obj.Name)"
# Brief info for own objects
if ($info.ObjElement) {
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
if ($childObj) {
$attrs = 0; $forms = 0; $ts = 0
foreach ($c in $childObj.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
switch ($c.LocalName) {
"Attribute" { $attrs++ }
"TabularSection" { $ts++ }
"Form" { $forms++ }
}
}
$parts = @()
if ($attrs -gt 0) { $parts += "$attrs attrs" }
if ($ts -gt 0) { $parts += "$ts TS" }
if ($forms -gt 0) { $parts += "$forms forms" }
if ($parts.Count -gt 0) {
Write-Host " $($parts -join ', ')"
}
}
}
}
}
Write-Host ""
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
}
# ============================================================
# MODE B: Transfer check
# ============================================================
if ($Mode -eq "B") {
$transferred = 0
$notTransferred = 0
$needsReview = 0
foreach ($obj in $objects) {
$info = Get-ObjectInfo $obj.Type $obj.Name
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
# Find .bsl files with &ИзменениеИКонтроль
$bslFiles = Get-BslFiles $obj.Type $obj.Name
foreach ($bsl in $bslFiles) {
$interceptors = Get-Interceptors $bsl
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
if ($macInterceptors.Count -eq 0) { continue }
foreach ($ic in $macInterceptors) {
$methodName = $ic.Method
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
# Find #Вставка blocks in this file
$insertBlocks = Get-InsertionBlocks $bsl
if ($insertBlocks.Count -eq 0) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
$needsReview++
continue
}
# Find corresponding module in config
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
$dirName = $childTypeDirMap[$obj.Type]
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
if (-not (Test-Path $configBsl)) {
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
$needsReview++
continue
}
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
$allTransferred = $true
foreach ($block in $insertBlocks) {
$code = $block.Code
if (-not $code) { continue }
# Normalize whitespace for comparison
$codeNorm = $code -replace '\s+', ' '
$configNorm = $configContent -replace '\s+', ' '
if ($configNorm.Contains($codeNorm)) {
# Found in config
} else {
$allTransferred = $false
}
}
if ($allTransferred) {
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
$transferred++
} else {
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
$notTransferred++
}
}
}
}
Write-Host ""
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
}
+74
View File
@@ -0,0 +1,74 @@
---
name: cfe-init
description: Создать расширение конфигурации 1С (CFE) — scaffold XML-исходников. Используй когда нужно создать новое расширение для исправления, доработки или дополнения конфигурации
argument-hint: <Name> [-Purpose Patch|Customization|AddOn] [-CompatibilityMode Version8_3_24]
allowed-tools:
- Bash
- Read
- Glob
---
# /cfe-init — Создание расширения конфигурации 1С
Создаёт scaffold расширения: `Configuration.xml`, `Languages/Русский.xml`, опционально `Roles/`.
## Подготовка
Перед созданием расширения рекомендуется получить версию и режим совместимости базовой конфигурации:
```
/cf-info <ConfigPath> -Mode brief
```
Это даст `CompatibilityMode` (передать в `-CompatibilityMode`) и версию конфигурации (для `-Version`, например `<ВерсияКонфигурации>.1`).
## Параметры
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог для создания | `src` |
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
| `Version` | Версия расширения | — |
| `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
| `NoRole` | Без основной роли | false |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-init\scripts\cfe-init.ps1 -Name "МоёРасширение"
```
## Что создаётся
```
<OutputDir>/
├── Configuration.xml # Свойства расширения
├── Languages/
│ └── Русский.xml # Язык (заимствованный)
└── Roles/ # Если не -NoRole
└── <Prefix>ОсновнаяРоль.xml
```
## Примеры
```powershell
# Расширение-исправление для ERP
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
# Расширение-доработка с версией
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src
# Без роли, с явным префиксом
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src
```
## Верификация
```
/cfe-validate <OutputDir>
```
@@ -0,0 +1,208 @@
# cfe-init v1.0 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$Name,
[string]$Synonym = $Name,
[string]$NamePrefix,
[string]$OutputDir = "src",
[ValidateSet("Patch","Customization","AddOn")]
[string]$Purpose = "Customization",
[string]$Version,
[string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24",
[switch]$NoRole
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Default NamePrefix ---
if (-not $NamePrefix) {
$NamePrefix = "${Name}_"
}
# --- Resolve output dir ---
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir
}
# --- Check existing ---
$cfgFile = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $cfgFile) {
Write-Error "Configuration.xml already exists: $cfgFile"
exit 1
}
# --- Generate UUIDs ---
$uuidCfg = [guid]::NewGuid().ToString()
$uuidLang = [guid]::NewGuid().ToString()
$uuidRole = [guid]::NewGuid().ToString()
# 7 ContainedObject ObjectIds
$co1 = [guid]::NewGuid().ToString()
$co2 = [guid]::NewGuid().ToString()
$co3 = [guid]::NewGuid().ToString()
$co4 = [guid]::NewGuid().ToString()
$co5 = [guid]::NewGuid().ToString()
$co6 = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString()
# --- Synonym XML ---
$synonymXml = ""
if ($Synonym) {
$synonymXml = "`r`n`t`t`t`t<v8:item>`r`n`t`t`t`t`t<v8:lang>ru</v8:lang>`r`n`t`t`t`t`t<v8:content>$([System.Security.SecurityElement]::Escape($Synonym))</v8:content>`r`n`t`t`t`t</v8:item>`r`n`t`t`t"
}
# --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" }
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" }
# --- Role name ---
$roleName = "${NamePrefix}ОсновнаяРоль"
# --- DefaultRoles XML ---
$defaultRolesXml = ""
if (-not $NoRole) {
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t"
}
# --- ChildObjects ---
$childObjectsXml = "`r`n`t`t`t<Language>Русский</Language>"
if (-not $NoRole) {
$childObjectsXml += "`r`n`t`t`t<Role>$roleName</Role>"
}
$childObjectsXml += "`r`n`t`t"
# --- Configuration.xml ---
$cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="$uuidCfg">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>$co1</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>$co2</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>$co3</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>$co4</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>$co5</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>$co6</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>$co7</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>$([System.Security.SecurityElement]::Escape($Name))</Name>
<Synonym>$synonymXml</Synonym>
<Comment/>
<ConfigurationExtensionPurpose>$Purpose</ConfigurationExtensionPurpose>
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
<NamePrefix>$([System.Security.SecurityElement]::Escape($NamePrefix))</NamePrefix>
<ConfigurationExtensionCompatibilityMode>$CompatibilityMode</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles>$defaultRolesXml</DefaultRoles>
<Vendor>$vendorXml</Vendor>
<Version>$versionXml</Version>
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
</Properties>
<ChildObjects>$childObjectsXml</ChildObjects>
</Configuration>
</MetaDataObject>
"@
# --- Languages/Русский.xml (adopted format) ---
$langXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="$uuidLang">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Русский</Name>
<Comment/>
<ExtendedConfigurationObject>00000000-0000-0000-0000-000000000000</ExtendedConfigurationObject>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
"@
# --- Role XML ---
$roleXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Role uuid="$uuidRole">
<Properties>
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
<Synonym/>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
"@
# --- Create directories ---
if (-not (Test-Path $OutputDir)) {
New-Item -ItemType Directory -Path $OutputDir -Force | Out-Null
}
$langDir = Join-Path $OutputDir "Languages"
if (-not (Test-Path $langDir)) {
New-Item -ItemType Directory -Path $langDir -Force | Out-Null
}
# --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc)
$langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc)
# --- Role ---
if (-not $NoRole) {
$roleDir = Join-Path $OutputDir "Roles"
if (-not (Test-Path $roleDir)) {
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
}
$roleFile = Join-Path $roleDir "$roleName.xml"
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc)
}
# --- Output ---
Write-Host "[OK] Создано расширение: $Name"
Write-Host " Каталог: $OutputDir"
Write-Host " Назначение: $Purpose"
Write-Host " Префикс: $NamePrefix"
Write-Host " Configuration.xml: $cfgFile"
Write-Host " Languages: $langFile"
if (-not $NoRole) {
Write-Host " Role: $roleFile"
}
+78
View File
@@ -0,0 +1,78 @@
---
name: cfe-patch-method
description: Генерация перехватчика метода в расширении 1С (CFE). Используй когда нужно перехватить метод заимствованного объекта — вставить код до, после или вместо оригинального
argument-hint: -ExtensionPath <path> -ModulePath "Catalog.X.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
allowed-tools:
- Bash
- Read
- Glob
---
# /cfe-patch-method — Генерация перехватчика метода
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта расширения. Создаёт файл или дописывает в существующий.
## Предусловие
Объект должен быть заимствован в расширение (`/cfe-borrow`). Скрипт читает `NamePrefix` из `Configuration.xml` расширения для формирования имени процедуры.
## Параметры
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `ExtensionPath` | Путь к расширению (обязат.) | — |
| `ModulePath` | Путь к модулю (обязат.) | — |
| `MethodName` | Имя перехватываемого метода (обязат.) | — |
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` (обязат.) | — |
| `Context` | Директива контекста | `НаСервере` |
| `IsFunction` | Метод — функция (добавит `Возврат`) | false |
## Формат ModulePath
| ModulePath | Файл |
|------------|------|
| `Catalog.X.ObjectModule` | `Catalogs/X/Ext/ObjectModule.bsl` |
| `Catalog.X.ManagerModule` | `Catalogs/X/Ext/ManagerModule.bsl` |
| `Catalog.X.Form.Y` | `Catalogs/X/Forms/Y/Ext/Form/Module.bsl` |
| `CommonModule.X` | `CommonModules/X/Ext/Module.bsl` |
| `Document.X.ObjectModule` | `Documents/X/Ext/ObjectModule.bsl` |
| `Document.X.Form.Y` | `Documents/X/Forms/Y/Ext/Form/Module.bsl` |
Аналогично для Report, DataProcessor, InformationRegister и других типов.
## Типы перехвата
| InterceptorType | Декоратор | Назначение |
|-----------------|-----------|------------|
| `Before` | `&Перед` | Код до вызова оригинального метода |
| `After` | `&После` | Код после вызова оригинального метода |
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Копия тела метода с маркерами `#Вставка`/`#Удаление` |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-patch-method\scripts\cfe-patch-method.ps1 -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
```
## Примеры
```powershell
# Перехват &Перед на сервере
... -ExtensionPath src -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
# Перехват &После на клиенте
... -ExtensionPath src -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After -Context "НаКлиенте"
# ИзменениеИКонтроль для функции
... -ExtensionPath src -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType ModificationAndControl -IsFunction
```
## Генерируемый код (Before)
```bsl
&НаСервере
&Перед("ПриЗаписи")
Процедура Расш1_ПриЗаписи()
// TODO: код перед вызовом оригинального метода
КонецПроцедуры
```
@@ -0,0 +1,186 @@
# cfe-patch-method v1.0 — Generate method interceptor for 1C extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[Parameter(Mandatory)]
[string]$ModulePath,
[Parameter(Mandatory)]
[string]$MethodName,
[Parameter(Mandatory)]
[ValidateSet("Before","After","ModificationAndControl")]
[string]$InterceptorType,
[string]$Context = "НаСервере",
[switch]$IsFunction
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve extension path ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Leaf) {
$ExtensionPath = Split-Path $ExtensionPath -Parent
}
$cfgFile = Join-Path $ExtensionPath "Configuration.xml"
if (-not (Test-Path $cfgFile)) {
Write-Error "Configuration.xml not found in: $ExtensionPath"
exit 1
}
# --- Read NamePrefix from Configuration.xml ---
$cfgDoc = New-Object System.Xml.XmlDocument
$cfgDoc.PreserveWhitespace = $false
$cfgDoc.Load($cfgFile)
$cfgNs = New-Object System.Xml.XmlNamespaceManager($cfgDoc.NameTable)
$cfgNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$propsNode = $cfgDoc.SelectSingleNode("//md:Configuration/md:Properties", $cfgNs)
$prefixNode = if ($propsNode) { $propsNode.SelectSingleNode("md:NamePrefix", $cfgNs) } else { $null }
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "Расш_" }
# --- Map ModulePath to file path ---
# ModulePath formats:
# Catalog.X.ObjectModule -> Catalogs/X/Ext/ObjectModule.bsl
# Catalog.X.ManagerModule -> Catalogs/X/Ext/ManagerModule.bsl
# Catalog.X.Form.Y -> Catalogs/X/Forms/Y/Ext/Form/Module.bsl
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
# Document.X.ObjectModule -> Documents/X/Ext/ObjectModule.bsl
# Document.X.ManagerModule -> Documents/X/Ext/ManagerModule.bsl
# Document.X.Form.Y -> Documents/X/Forms/Y/Ext/Form/Module.bsl
$typeDirMap = @{
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
"CommonModule"="CommonModules"; "Report"="Reports"; "DataProcessor"="DataProcessors"
"ExchangePlan"="ExchangePlans"; "ChartOfAccounts"="ChartsOfAccounts"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
}
$parts = $ModulePath.Split(".")
if ($parts.Count -lt 2) {
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module or CommonModule.Name"
exit 1
}
$objType = $parts[0]
$objName = $parts[1]
if (-not $typeDirMap.ContainsKey($objType)) {
Write-Error "Unknown object type: $objType"
exit 1
}
$dirName = $typeDirMap[$objType]
$bslFile = $null
if ($objType -eq "CommonModule") {
# CommonModule.X -> CommonModules/X/Ext/Module.bsl
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Ext") "Module.bsl"
} elseif ($parts.Count -ge 4 -and $parts[2] -eq "Form") {
# Type.X.Form.Y -> Types/X/Forms/Y/Ext/Form/Module.bsl
$formName = $parts[3]
$bslFile = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) "Forms") $formName) "Ext") "Form") "Module.bsl"
} elseif ($parts.Count -ge 3) {
# Type.X.ObjectModule -> Types/X/Ext/ObjectModule.bsl
$moduleName = $parts[2]
$moduleFileName = switch ($moduleName) {
"ObjectModule" { "ObjectModule.bsl" }
"ManagerModule" { "ManagerModule.bsl" }
"RecordSetModule" { "RecordSetModule.bsl" }
"CommandModule" { "CommandModule.bsl" }
default { "$moduleName.bsl" }
}
$bslFile = Join-Path (Join-Path (Join-Path $ExtensionPath $dirName) $objName) (Join-Path "Ext" $moduleFileName)
} else {
Write-Error "Invalid ModulePath format: $ModulePath. Expected: Type.Name.Module, Type.Name.Form.FormName, or CommonModule.Name"
exit 1
}
# --- Map InterceptorType to decorator ---
$decorator = switch ($InterceptorType) {
"Before" { "&Перед" }
"After" { "&После" }
"ModificationAndControl" { "&ИзменениеИКонтроль" }
}
# --- Map Context to annotation ---
$contextAnnotation = switch ($Context) {
"НаСервере" { "&НаСервере" }
"НаКлиенте" { "&НаКлиенте" }
"НаСервереБезКонтекста" { "&НаСервереБезКонтекста" }
default { "&$Context" }
}
# --- Procedure name ---
$procName = "${namePrefix}${MethodName}"
# --- Generate BSL code ---
$keyword = if ($IsFunction) { "Функция" } else { "Процедура" }
$endKeyword = if ($IsFunction) { "КонецФункции" } else { "КонецПроцедуры" }
$bodyLines = @()
switch ($InterceptorType) {
"Before" {
$bodyLines += "`t// TODO: код перед вызовом оригинального метода"
}
"After" {
$bodyLines += "`t// TODO: код после вызова оригинального метода"
}
"ModificationAndControl" {
$bodyLines += "`t// Скопируйте тело оригинального метода и внесите изменения,"
$bodyLines += "`t// используя маркеры #Удаление / #КонецУдаления и #Вставка / #КонецВставки"
}
}
if ($IsFunction) {
$bodyLines += "`t"
$bodyLines += "`tВозврат Неопределено; // TODO: заменить на реальное возвращаемое значение"
}
$bslCode = @()
$bslCode += "$contextAnnotation"
$bslCode += "${decorator}(`"$MethodName`")"
$bslCode += "$keyword ${procName}()"
$bslCode += $bodyLines
$bslCode += "$endKeyword"
$bslText = ($bslCode -join "`r`n") + "`r`n"
# --- Check if file exists and append ---
$bslDir = Split-Path $bslFile -Parent
if (-not (Test-Path $bslDir)) {
New-Item -ItemType Directory -Path $bslDir -Force | Out-Null
}
$enc = New-Object System.Text.UTF8Encoding($true)
if (Test-Path $bslFile) {
# Append to existing file
$existing = [System.IO.File]::ReadAllText($bslFile, $enc)
$separator = "`r`n"
if ($existing -and -not $existing.EndsWith("`n")) {
$separator = "`r`n`r`n"
}
$newContent = $existing + $separator + $bslText
[System.IO.File]::WriteAllText($bslFile, $newContent, $enc)
Write-Host "[OK] Добавлен перехватчик в существующий файл"
} else {
[System.IO.File]::WriteAllText($bslFile, $bslText, $enc)
Write-Host "[OK] Создан файл модуля"
}
Write-Host " Файл: $bslFile"
Write-Host " Декоратор: $decorator(`"$MethodName`")"
Write-Host " Процедура: ${procName}()"
Write-Host " Контекст: $contextAnnotation"
+51
View File
@@ -0,0 +1,51 @@
---
name: cfe-validate
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
argument-hint: <ExtensionPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /cfe-validate — Валидация расширения конфигурации
Проверяет структурную корректность расширения: XML-формат, свойства, состав, заимствованные объекты. Аналог `/cf-validate`, но для расширений.
## Параметры
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `ExtensionPath` | Путь к каталогу или Configuration.xml расширения (обязат.) | — |
| `MaxErrors` | Лимит ошибок | 30 |
| `OutFile` | Записать результат в файл | — |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\cfe-validate\scripts\cfe-validate.ps1 -ExtensionPath src
```
## Проверки (9 шагов)
| # | Проверка | Уровень |
|---|----------|---------|
| 1 | XML well-formedness, MetaDataObject/Configuration, version | ERROR |
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId | ERROR |
| 3 | Extension properties: ObjectBelonging=Adopted, Name, Purpose, NamePrefix, KeepMapping | ERROR |
| 4 | Enum-значения: ConfigurationExtensionCompatibilityMode, DefaultRunMode, ScriptVariant, InterfaceCompatibilityMode | ERROR |
| 5 | ChildObjects: валидные типы (44), нет дубликатов, каноничный порядок | ERROR/WARN |
| 6 | DefaultLanguage ссылается на Language в ChildObjects | ERROR |
| 7 | Файлы языков существуют | WARN |
| 8 | Каталоги объектов существуют | WARN |
| 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN |
## Пример вывода
```
=== Validation: Extension.МоёРасширение ===
[OK] 1. Root structure: MetaDataObject/Configuration, version 2.17
[OK] 2. InternalInfo: 7 ContainedObject, all ClassIds valid
...
=== Result: 0 errors, 0 warnings ===
```
@@ -0,0 +1,607 @@
# cfe-validate v1.0 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ExtensionPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
}
if (Test-Path $ExtensionPath -PathType Container) {
$candidate = Join-Path $ExtensionPath "Configuration.xml"
if (Test-Path $candidate) {
$ExtensionPath = $candidate
} else {
Write-Host "[ERROR] No Configuration.xml found in directory: $ExtensionPath"
exit 1
}
}
if (-not (Test-Path $ExtensionPath)) {
Write-Host "[ERROR] File not found: $ExtensionPath"
exit 1
}
$resolvedPath = (Resolve-Path $ExtensionPath).Path
$configDir = Split-Path $resolvedPath -Parent
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
Out-Line "[OK] $msg"
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
# 7 fixed ClassIds for Configuration
$validClassIds = @(
"9cd510cd-abfc-11d4-9434-004095e12fc7",
"9fcd25a0-4822-11d4-9414-008048da11f9",
"e3687481-0a87-462c-a166-9f34594f9bba",
"9de14907-ec23-4a07-96f0-85521cb6b53b",
"51f2d5d8-ea4d-4064-8892-82951750031e",
"e68182ea-4237-4383-967f-90c1e3370bc7",
"fb282519-d103-4dd3-bc12-cb271d631dfc"
)
# 44 types in canonical order
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
# Type -> directory mapping
$childTypeDirMap = @{
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"
"CommonTemplate"="CommonTemplates"; "FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"
"CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"; "XDTOPackage"="XDTOPackages"
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
"SettingsStorage"="SettingsStorages"; "FunctionalOption"="FunctionalOptions"
"FunctionalOptionsParameter"="FunctionalOptionsParameters"; "DefinedType"="DefinedTypes"
"CommonCommand"="CommonCommands"; "CommandGroup"="CommandGroups"; "Constant"="Constants"
"CommonForm"="CommonForms"; "Catalog"="Catalogs"; "Document"="Documents"
"DocumentNumerator"="DocumentNumerators"; "Sequence"="Sequences"
"DocumentJournal"="DocumentJournals"; "Enum"="Enums"; "Report"="Reports"
"DataProcessor"="DataProcessors"; "InformationRegister"="InformationRegisters"
"AccumulationRegister"="AccumulationRegisters"
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"
"CalculationRegister"="CalculationRegisters"
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
"IntegrationService"="IntegrationServices"
}
# Valid enum values for extension properties
$validEnumValues = @{
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
"ScriptVariant" = @("Russian","English")
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: Extension (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("app", "http://v8.1c.ru/8.2/managed-application/core")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
}
# Must have Configuration child
$cfgNode = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration" -and $child.NamespaceURI -eq $expectedNs) {
$cfgNode = $child; break
}
}
if (-not $cfgNode) {
Report-Error "1. No <Configuration> element found inside MetaDataObject"
& $finalize
exit 1
}
# UUID
$cfgUuid = $cfgNode.GetAttribute("uuid")
if (-not $cfgUuid) {
Report-Error "1. Missing uuid on <Configuration>"
$check1Ok = $false
} elseif ($cfgUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$cfgUuid' on <Configuration>"
$check1Ok = $false
}
# Get name early for header
$propsNode = $cfgNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
$script:output.Insert(0, "=== Validation: Extension.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/Configuration, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $cfgNode.SelectSingleNode("md:InternalInfo", $ns)
$check2Ok = $true
if (-not $internalInfo) {
Report-Error "2. InternalInfo: missing"
} else {
$contained = $internalInfo.SelectNodes("xr:ContainedObject", $ns)
if ($contained.Count -ne 7) {
Report-Warn "2. InternalInfo: expected 7 ContainedObject, found $($contained.Count)"
}
$foundClassIds = @{}
foreach ($co in $contained) {
$classId = $co.SelectSingleNode("xr:ClassId", $ns)
$objectId = $co.SelectSingleNode("xr:ObjectId", $ns)
if (-not $classId -or -not $classId.InnerText) {
Report-Error "2. ContainedObject missing ClassId"
$check2Ok = $false
continue
}
$cid = $classId.InnerText
if ($validClassIds -notcontains $cid) {
Report-Error "2. Unknown ClassId: $cid"
$check2Ok = $false
}
if ($foundClassIds.ContainsKey($cid)) {
Report-Error "2. Duplicate ClassId: $cid"
$check2Ok = $false
}
$foundClassIds[$cid] = $true
if (-not $objectId -or -not $objectId.InnerText) {
Report-Error "2. ContainedObject missing ObjectId for ClassId $cid"
$check2Ok = $false
} elseif ($objectId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ObjectId '$($objectId.InnerText)' for ClassId $cid"
$check2Ok = $false
}
}
$missingIds = @($validClassIds | Where-Object { -not $foundClassIds.ContainsKey($_) })
if ($missingIds.Count -gt 0) {
Report-Warn "2. Missing ClassIds: $($missingIds.Count) of 7"
}
if ($check2Ok) {
Report-OK "2. InternalInfo: $($contained.Count) ContainedObject, all ClassIds valid"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Extension-specific properties ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# ObjectBelonging = Adopted
$obNode = $propsNode.SelectSingleNode("md:ObjectBelonging", $ns)
if (-not $obNode -or $obNode.InnerText -ne "Adopted") {
Report-Error "3. ObjectBelonging must be 'Adopted', got '$($obNode.InnerText)'"
$check3Ok = $false
}
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
}
# ConfigurationExtensionPurpose
$purposeNode = $propsNode.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
$validPurposes = @("Patch","Customization","AddOn")
if (-not $purposeNode -or -not $purposeNode.InnerText) {
Report-Error "3. ConfigurationExtensionPurpose is missing"
$check3Ok = $false
} elseif ($validPurposes -notcontains $purposeNode.InnerText) {
Report-Error "3. ConfigurationExtensionPurpose '$($purposeNode.InnerText)' invalid (expected: Patch, Customization, AddOn)"
$check3Ok = $false
}
# NamePrefix
$prefixNode = $propsNode.SelectSingleNode("md:NamePrefix", $ns)
if (-not $prefixNode -or -not $prefixNode.InnerText) {
Report-Warn "3. NamePrefix is empty"
}
# KeepMappingToExtendedConfigurationObjectsByIDs
$keepMapNode = $propsNode.SelectSingleNode("md:KeepMappingToExtendedConfigurationObjectsByIDs", $ns)
if (-not $keepMapNode) {
Report-Warn "3. KeepMappingToExtendedConfigurationObjectsByIDs is missing"
}
# DefaultLanguage
$defLangNode = $propsNode.SelectSingleNode("md:DefaultLanguage", $ns)
$defLang = if ($defLangNode -and $defLangNode.InnerText) { $defLangNode.InnerText } else { "" }
if ($check3Ok) {
$purposeVal = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
$prefixVal = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "(empty)" }
Report-OK "3. Extension properties: Name=`"$objName`", Purpose=$purposeVal, Prefix=$prefixVal"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: Enum property values ---
if ($propsNode) {
$enumChecked = 0
$check4Ok = $true
foreach ($propName in $validEnumValues.Keys) {
$propNode = $propsNode.SelectSingleNode("md:$propName", $ns)
if ($propNode -and $propNode.InnerText) {
$val = $propNode.InnerText
$allowed = $validEnumValues[$propName]
if ($allowed -notcontains $val) {
Report-Error "4. Property '$propName' has invalid value '$val'"
$check4Ok = $false
}
$enumChecked++
}
}
if ($check4Ok) {
Report-OK "4. Property values: $enumChecked enum properties checked"
}
} else {
Report-Warn "4. No Properties block to check"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: ChildObjects — valid types, no duplicates, order ---
$childObjNode = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
if (-not $childObjNode) {
Report-Error "5. ChildObjects block missing"
} else {
$check5Ok = $true
$totalCount = 0
$typeCounts = @{}
$duplicates = @{}
$typeFirstIndex = @{}
$lastTypeOrder = -1
$orderOk = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
$objNameVal = $child.InnerText
$typeIdx = $childObjectTypes.IndexOf($typeName)
if ($typeIdx -lt 0) {
Report-Error "5. Unknown type '$typeName' in ChildObjects"
$check5Ok = $false
} else {
if (-not $typeFirstIndex.ContainsKey($typeName)) {
$typeFirstIndex[$typeName] = $typeIdx
if ($typeIdx -lt $lastTypeOrder) {
Report-Warn "5. Type '$typeName' is out of canonical order (after type at position $lastTypeOrder)"
$orderOk = $false
}
$lastTypeOrder = $typeIdx
}
}
if (-not $typeCounts.ContainsKey($typeName)) { $typeCounts[$typeName] = @{} }
if ($typeCounts[$typeName].ContainsKey($objNameVal)) {
if (-not $duplicates.ContainsKey("$typeName.$objNameVal")) {
Report-Error "5. Duplicate: $typeName.$objNameVal"
$duplicates["$typeName.$objNameVal"] = $true
$check5Ok = $false
}
} else {
$typeCounts[$typeName][$objNameVal] = $true
}
$totalCount++
}
$typeCount = $typeCounts.Count
if ($check5Ok) {
$orderInfo = if ($orderOk) { ", order correct" } else { "" }
Report-OK "5. ChildObjects: $typeCount types, $totalCount objects${orderInfo}"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: DefaultLanguage references existing Language in ChildObjects ---
if ($defLang -and $childObjNode) {
$langName = $defLang
if ($langName.StartsWith("Language.")) {
$langName = $langName.Substring(9)
}
$found = $false
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language" -and $child.InnerText -eq $langName) {
$found = $true; break
}
}
if ($found) {
Report-OK "6. DefaultLanguage `"$defLang`" found in ChildObjects"
} else {
Report-Error "6. DefaultLanguage `"$defLang`" not found in ChildObjects"
}
} else {
if (-not $defLang) {
Report-Warn "6. Cannot check DefaultLanguage (empty)"
} else {
Report-Warn "6. Cannot check DefaultLanguage (no ChildObjects)"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: Language files exist ---
if ($childObjNode) {
$langNames = @()
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Language") {
$langNames += $child.InnerText
}
}
if ($langNames.Count -gt 0) {
$existCount = 0
foreach ($ln in $langNames) {
$langFile = Join-Path (Join-Path $configDir "Languages") "$ln.xml"
if (Test-Path $langFile) {
$existCount++
} else {
Report-Warn "7. Language file missing: Languages/$ln.xml"
}
}
if ($existCount -eq $langNames.Count) {
Report-OK "7. Language files: $existCount/$($langNames.Count) exist"
}
} else {
Report-Warn "7. No Language entries in ChildObjects"
}
} else {
Report-Warn "7. Cannot check language files (no ChildObjects)"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Object directories exist ---
if ($childObjNode) {
$dirsToCheck = @{}
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
if ($typeName -eq "Language") { continue }
if ($childTypeDirMap.ContainsKey($typeName)) {
$dirName = $childTypeDirMap[$typeName]
if (-not $dirsToCheck.ContainsKey($dirName)) {
$dirsToCheck[$dirName] = 0
}
$dirsToCheck[$dirName] = $dirsToCheck[$dirName] + 1
}
}
$missingDirs = @()
foreach ($dir in $dirsToCheck.Keys) {
$dirPath = Join-Path $configDir $dir
if (-not (Test-Path $dirPath -PathType Container)) {
$missingDirs += "$dir ($($dirsToCheck[$dir]) objects)"
}
}
if ($missingDirs.Count -eq 0) {
Report-OK "8. Object directories: $($dirsToCheck.Count) directories, all exist"
} else {
foreach ($md in $missingDirs) {
Report-Warn "8. Missing directory: $md"
}
}
} else {
Report-OK "8. Object directories: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: Borrowed objects validation ---
if ($childObjNode) {
$borrowedCount = 0
$borrowedOk = 0
$check9Ok = $true
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$typeName = $child.LocalName
$childName = $child.InnerText
if ($typeName -eq "Language") { continue }
if (-not $childTypeDirMap.ContainsKey($typeName)) { continue }
$dirName = $childTypeDirMap[$typeName]
$objFile = Join-Path (Join-Path $configDir $dirName) "$childName.xml"
if (-not (Test-Path $objFile)) { continue }
# Parse object XML
$objDoc = $null
try {
$objDoc = New-Object System.Xml.XmlDocument
$objDoc.PreserveWhitespace = $false
$objDoc.Load($objFile)
} catch {
Report-Warn "9. Cannot parse $dirName/$childName.xml: $($_.Exception.Message)"
continue
}
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$objNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
# Find the object element (Catalog, Document, etc.)
$objRoot = $objDoc.DocumentElement
$objEl = $null
foreach ($c in $objRoot.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { continue }
$objProps = $objEl.SelectSingleNode("md:Properties", $objNs)
if (-not $objProps) { continue }
$obNode = $objProps.SelectSingleNode("md:ObjectBelonging", $objNs)
if ($obNode -and $obNode.InnerText -eq "Adopted") {
$borrowedCount++
# Check ExtendedConfigurationObject
$extObj = $objProps.SelectSingleNode("md:ExtendedConfigurationObject", $objNs)
if (-not $extObj -or -not $extObj.InnerText) {
Report-Error "9. Borrowed ${typeName}.${childName}: missing ExtendedConfigurationObject"
$check9Ok = $false
} elseif ($extObj.InnerText -notmatch $guidPattern) {
Report-Error "9. Borrowed ${typeName}.${childName}: invalid ExtendedConfigurationObject UUID '$($extObj.InnerText)'"
$check9Ok = $false
} else {
$borrowedOk++
}
}
if ($script:stopped) { break }
}
if ($borrowedCount -eq 0) {
Report-OK "9. Borrowed objects: none found"
} elseif ($check9Ok) {
Report-OK "9. Borrowed objects: $borrowedOk/$borrowedCount validated"
}
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0
+1 -1
View File
@@ -204,5 +204,5 @@ allowed-tools:
- Добавить ещё команду: `/epf-bsp-add-command`
- Добавить форму: `/epf-add-form`
- Добавить макет: `/epf-add-template`
- Добавить макет: `/template-add`
- Собрать EPF: `/epf-build`
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: epf-build
description: Собрать внешнюю обработку 1С (EPF) из XML-исходников
description: Собрать внешнюю обработку 1С (EPF/ERF) из XML-исходников
argument-hint: <ProcessorName>
allowed-tools:
- Bash
@@ -11,7 +11,7 @@ allowed-tools:
# /epf-build — Сборка обработки
Собирает EPF-файл из XML-исходников с помощью платформы 1С.
Собирает EPF-файл из XML-исходников с помощью платформы 1С. Та же команда CLI работает и для внешних отчётов (ERF) — см. `/erf-build`.
## Usage
+2 -2
View File
@@ -1,6 +1,6 @@
---
name: epf-dump
description: Разобрать EPF-файл обработки 1С в XML-исходники
description: Разобрать EPF-файл обработки 1С (EPF/ERF) в XML-исходники
argument-hint: <EpfFile>
allowed-tools:
- Bash
@@ -11,7 +11,7 @@ allowed-tools:
# /epf-dump — Разборка обработки
Разбирает EPF-файл во XML-исходники с помощью платформы 1С (иерархический формат).
Разбирает EPF-файл во XML-исходники с помощью платформы 1С (иерархический формат). Та же команда CLI работает и для внешних отчётов (ERF) — см. `/erf-dump`.
## Usage
+2 -1
View File
@@ -50,5 +50,6 @@ pwsh -NoProfile -File .claude/skills/epf-init/scripts/init.ps1 -Name "<Name>" [-
## Дальнейшие шаги
- Добавить форму: `/epf-add-form`
- Добавить макет: `/epf-add-template`
- Добавить макет: `/template-add`
- Добавить справку: `/help-add`
- Собрать EPF: `/epf-build`
-47
View File
@@ -1,47 +0,0 @@
---
name: epf-remove-form
description: Удалить форму из внешней обработки 1С
argument-hint: <ProcessorName> <FormName>
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
---
# /epf-remove-form — Удаление формы
Удаляет форму и убирает её регистрацию из корневого XML обработки.
## Usage
```
/epf-remove-form <ProcessorName> <FormName>
```
| Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|--------------|-------------------------------------|
| ProcessorName | да | — | Имя обработки |
| FormName | да | — | Имя формы для удаления |
| SrcDir | нет | `src` | Каталог исходников |
## Команда
```powershell
pwsh -NoProfile -File .claude/skills/epf-remove-form/scripts/remove-form.ps1 -ProcessorName "<ProcessorName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"]
```
## Что удаляется
```
<SrcDir>/<ProcessorName>/Forms/<FormName>.xml # Метаданные формы
<SrcDir>/<ProcessorName>/Forms/<FormName>/ # Каталог формы (рекурсивно)
```
## Что модифицируется
- `<SrcDir>/<ProcessorName>.xml` — убирается `<Form>` из `ChildObjects`
- Если удаляемая форма была DefaultForm — очищается значение DefaultForm
@@ -1,46 +0,0 @@
---
name: epf-remove-template
description: Удалить макет из внешней обработки 1С
argument-hint: <ProcessorName> <TemplateName>
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
---
# /epf-remove-template — Удаление макета
Удаляет макет и убирает его регистрацию из корневого XML обработки.
## Usage
```
/epf-remove-template <ProcessorName> <TemplateName>
```
| Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|--------------|-------------------------------------|
| ProcessorName | да | — | Имя обработки |
| TemplateName | да | — | Имя макета для удаления |
| SrcDir | нет | `src` | Каталог исходников |
## Команда
```powershell
pwsh -NoProfile -File .claude/skills/epf-remove-template/scripts/remove-template.ps1 -ProcessorName "<ProcessorName>" -TemplateName "<TemplateName>" [-SrcDir "<SrcDir>"]
```
## Что удаляется
```
<SrcDir>/<ProcessorName>/Templates/<TemplateName>.xml # Метаданные макета
<SrcDir>/<ProcessorName>/Templates/<TemplateName>/ # Каталог макета (рекурсивно)
```
## Что модифицируется
- `<SrcDir>/<ProcessorName>.xml` — убирается `<Template>` из `ChildObjects`
+83
View File
@@ -0,0 +1,83 @@
---
name: erf-build
description: Собрать внешний отчёт 1С (ERF) из XML-исходников
argument-hint: <ReportName>
allowed-tools:
- Bash
- Read
- Glob
- Grep
---
# /erf-build — Сборка отчёта
Собирает ERF-файл из XML-исходников с помощью платформы 1С. Использует ту же команду CLI, что и `/epf-build`.
## Usage
```
/erf-build <ReportName> [SrcDir] [OutDir]
```
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|--------------------------------------|
| ReportName | да | — | Имя отчёта (имя корневого XML) |
| SrcDir | нет | `src` | Каталог исходников |
| OutDir | нет | `build` | Каталог для результата |
## Переменные окружения
| Переменная | Описание | Пример |
|------------|---------------------------------------|-----------------------------------------------|
| V8_PATH | Каталог bin платформы 1С | `C:\Program Files\1cv8\8.3.25.1257\bin` |
| V8_BASE | Путь к пустой файловой ИБ | `.\base` |
## Команды
### 1. Создать пустую ИБ (если нет)
```cmd
"%V8_PATH%\1cv8.exe" CREATEINFOBASE File="%V8_BASE%"
```
### 2. Сборка ERF из XML
```cmd
"%V8_PATH%\1cv8.exe" DESIGNER /F "%V8_BASE%" /DisableStartupDialogs /LoadExternalDataProcessorOrReportFromFiles "<SrcDir>\<ReportName>.xml" "<OutDir>\<ReportName>.erf" /Out "<OutDir>\build.log"
```
## Коды возврата
| Код | Описание |
|-----|-----------------------------|
| 0 | Успешная сборка |
| 1 | Ошибка (см. лог) |
## Автоопределение платформы (Windows)
Если `V8_PATH` не задан, можно найти автоматически:
```powershell
$v8 = Get-ChildItem "C:\Program Files\1cv8\*\bin\1cv8.exe" | Sort-Object -Descending | Select-Object -First 1
```
## Ссылочные типы и выбор базы
Пустая ИБ (`V8_BASE`) подходит для сборки, если формы используют только базовые типы (`xs:string`, `xs:boolean` и т.д.) или тип самого отчёта (`ExternalReportObject.Имя`).
Если отчёт использует ссылочные типы конфигурации (`CatalogRef.XXX`, `DocumentRef.XXX` и т.д.) — в реквизитах объекта, табличных частях или реквизитах форм — **сборка в пустой базе упадёт** с ошибкой XDTO. Платформа не может резолвить типы, отсутствующие в конфигурации базы.
**Решение**: собирать в базе с целевой конфигурацией. Если конфигурация неизвестна — спросить пользователя путь к базе.
## Пример полного цикла
```powershell
$env:V8_PATH = "C:\Program Files\1cv8\8.3.25.1257\bin"
$env:V8_BASE = ".\base"
# Создать ИБ
& "$env:V8_PATH\1cv8.exe" CREATEINFOBASE "File=$env:V8_BASE"
# Собрать
& "$env:V8_PATH\1cv8.exe" DESIGNER /F $env:V8_BASE /DisableStartupDialogs /LoadExternalDataProcessorOrReportFromFiles "src\МойОтчёт.xml" "build\МойОтчёт.erf" /Out "build\build.log"
```
+90
View File
@@ -0,0 +1,90 @@
---
name: erf-dump
description: Разобрать ERF-файл отчёта 1С в XML-исходники
argument-hint: <ErfFile>
allowed-tools:
- Bash
- Read
- Glob
- Grep
---
# /erf-dump — Разборка отчёта
Разбирает ERF-файл во XML-исходники с помощью платформы 1С (иерархический формат). Использует ту же команду CLI, что и `/epf-dump`.
## Usage
```
/erf-dump <ErfFile> [OutDir]
```
| Параметр | Обязательный | По умолчанию | Описание |
|----------|:------------:|--------------|-------------------------------------|
| ErfFile | да | — | Путь к ERF-файлу |
| OutDir | нет | `src` | Каталог для выгрузки исходников |
## Переменные окружения
| Переменная | Описание | Пример |
|------------|---------------------------------------|-----------------------------------------------|
| V8_PATH | Каталог bin платформы 1С | `C:\Program Files\1cv8\8.3.25.1257\bin` |
| V8_BASE | Путь к пустой файловой ИБ | `.\base` |
## Команды
### 1. Создать пустую ИБ (если нет)
```cmd
"%V8_PATH%\1cv8.exe" CREATEINFOBASE File="%V8_BASE%"
```
### 2. Разборка ERF в XML
```cmd
"%V8_PATH%\1cv8.exe" DESIGNER /F "%V8_BASE%" /DisableStartupDialogs /DumpExternalDataProcessorOrReportToFiles "<OutDir>" "<ErfFile>" -Format Hierarchical /Out "<OutDir>\dump.log"
```
## Коды возврата
| Код | Описание |
|-----|-----------------------------|
| 0 | Успешная разборка |
| 1 | Ошибка (см. лог) |
## Формат `-Format Hierarchical`
Ключ `-Format Hierarchical` создаёт структуру каталогов:
```
<OutDir>/
├── <Name>.xml # Корневой файл
└── <Name>/
├── Ext/
│ └── ObjectModule.bsl # Модуль объекта (если есть)
├── Forms/
│ ├── <FormName>.xml
│ └── <FormName>/
│ └── Ext/
│ ├── Form.xml
│ └── Form/
│ └── Module.bsl
└── Templates/
├── <TemplateName>.xml
└── <TemplateName>/
└── Ext/
└── Template.<ext>
```
## Пример полного цикла
```powershell
$env:V8_PATH = "C:\Program Files\1cv8\8.3.25.1257\bin"
$env:V8_BASE = ".\base"
# Создать ИБ
& "$env:V8_PATH\1cv8.exe" CREATEINFOBASE "File=$env:V8_BASE"
# Разобрать
& "$env:V8_PATH\1cv8.exe" DESIGNER /F $env:V8_BASE /DisableStartupDialogs /DumpExternalDataProcessorOrReportToFiles "src" "build\МойОтчёт.erf" -Format Hierarchical /Out "build\dump.log"
```
+68
View File
@@ -0,0 +1,68 @@
---
name: erf-init
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников)
argument-hint: <Name> [Synonym] [--with-skd]
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
---
# /erf-init — Создание нового отчёта
Генерирует минимальный набор XML-исходников для внешнего отчёта 1С: корневой файл метаданных и каталог отчёта.
## Usage
```
/erf-init <Name> [Synonym] [SrcDir] [--with-skd]
```
| Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|---------------------------------------|
| Name | да | — | Имя отчёта (латиница/кириллица) |
| Synonym | нет | = Name | Синоним (отображаемое имя) |
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\erf-init\scripts\init.ps1 -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD]
```
## Что создаётся
```
<SrcDir>/
├── <Name>.xml # Корневой файл метаданных (4 UUID)
└── <Name>/
└── Ext/
└── ObjectModule.bsl # Модуль объекта с 3 регионами
```
При `--WithSKD` дополнительно:
```
<SrcDir>/<Name>/
Templates/
├── ОсновнаяСхемаКомпоновкиДанных.xml # Метаданные макета
└── ОсновнаяСхемаКомпоновкиДанных/
└── Ext/
└── Template.xml # Пустая СКД
```
- Корневой XML содержит `MetaDataObject/ExternalReport` с пустыми `DefaultForm`, `MainDataCompositionSchema` и `ChildObjects`
- При `--WithSKD``MainDataCompositionSchema` заполняется ссылкой на макет, `ChildObjects` содержит `<Template>`
- ClassId фиксирован: `e41aff26-25cf-4bb6-b6c1-3f478a75f374`
- Файл создаётся в UTF-8 с BOM
## Дальнейшие шаги
- Добавить форму: `/form-add`
- Добавить макет: `/template-add`
- Добавить справку: `/help-add`
- Собрать ERF: `/erf-build`
+178
View File
@@ -0,0 +1,178 @@
# erf-init v1.0 — Init 1C external report scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$Name,
[string]$Synonym = $Name,
[string]$SrcDir = "src",
[switch]$WithSKD
)
$ErrorActionPreference = "Stop"
$uuid1 = [guid]::NewGuid().ToString()
$uuid2 = [guid]::NewGuid().ToString()
$uuid3 = [guid]::NewGuid().ToString()
$uuid4 = [guid]::NewGuid().ToString()
# --- Формируем Properties ---
$mainDCSValue = ""
$childObjectsContent = ""
if ($WithSKD) {
$mainDCSValue = "ExternalReport.$Name.Template.ОсновнаяСхемаКомпоновкиДанных"
$childObjectsContent = @"
<Template>ОсновнаяСхемаКомпоновкиДанных</Template>
"@
}
$mainDCSElement = if ($mainDCSValue) {
"<MainDataCompositionSchema>$mainDCSValue</MainDataCompositionSchema>"
} else {
"<MainDataCompositionSchema/>"
}
$childObjectsXml = if ($childObjectsContent) {
"<ChildObjects>$childObjectsContent</ChildObjects>"
} else {
"<ChildObjects/>"
}
$xml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<ExternalReport uuid="$uuid1">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
<xr:ObjectId>$uuid2</xr:ObjectId>
</xr:ContainedObject>
<xr:GeneratedType name="ExternalReportObject.$Name" category="Object">
<xr:TypeId>$uuid3</xr:TypeId>
<xr:ValueId>$uuid4</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>$Name</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>$Synonym</v8:content>
</v8:item>
</Synonym>
<Comment/>
<DefaultForm/>
<AuxiliaryForm/>
$mainDCSElement
<DefaultSettingsForm/>
<AuxiliarySettingsForm/>
<DefaultVariantForm/>
<VariantsStorage/>
<SettingsStorage/>
</Properties>
$childObjectsXml
</ExternalReport>
</MetaDataObject>
"@
$rootFile = Join-Path $SrcDir "$Name.xml"
$reportDir = Join-Path $SrcDir $Name
if (Test-Path $rootFile) {
Write-Error "Файл уже существует: $rootFile"
exit 1
}
if (-not (Test-Path $SrcDir)) {
New-Item -ItemType Directory -Path $SrcDir -Force | Out-Null
}
$extDir = Join-Path $reportDir "Ext"
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc)
# --- Модуль объекта ---
$moduleBsl = @"
#Область ОписаниеПеременных
#КонецОбласти
#Область ПрограммныйИнтерфейс
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
#КонецОбласти
"@
$modulePath = Join-Path $extDir "ObjectModule.bsl"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
Write-Host "[OK] Создан отчёт: $rootFile"
Write-Host " Каталог: $reportDir"
Write-Host " Модуль: $modulePath"
# --- СКД-макет (если --WithSKD) ---
if ($WithSKD) {
$templatesDir = Join-Path $reportDir "Templates"
$skdName = "ОсновнаяСхемаКомпоновкиДанных"
$skdMetaPath = Join-Path $templatesDir "$skdName.xml"
$skdExtDir = Join-Path (Join-Path $templatesDir $skdName) "Ext"
New-Item -ItemType Directory -Path $skdExtDir -Force | Out-Null
$skdUuid = [guid]::NewGuid().ToString()
$skdMetaXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Template uuid="$skdUuid">
<Properties>
<Name>$skdName</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Основная схема компоновки данных</v8:content>
</v8:item>
</Synonym>
<Comment/>
<TemplateType>DataCompositionSchema</TemplateType>
</Properties>
</Template>
</MetaDataObject>
"@
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc)
$skdContent = @"
<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dataSource>
<name>ИсточникДанных1</name>
<dataSourceType>Local</dataSourceType>
</dataSource>
</DataCompositionSchema>
"@
$skdFilePath = Join-Path $skdExtDir "Template.xml"
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc)
Write-Host " СКД: $skdMetaPath"
Write-Host " Тело: $skdFilePath"
}
+2 -2
View File
@@ -39,7 +39,7 @@ powershell.exe -NoProfile -File .claude\skills\form-add\scripts\form-add.ps1 -Ob
| Purpose | Допустимые типы объектов | Основной реквизит | DefaultForm-свойство |
|---------|-------------------------|-------------------|---------------------|
| Object | Document, Catalog, DataProcessor, Report, ChartOf*, ExchangePlan, BusinessProcess, Task | Объект (тип: *Object.Имя) | DefaultObjectForm (DefaultForm для DataProcessor/Report) |
| Object | Document, Catalog, DataProcessor, Report, ExternalDataProcessor, ExternalReport, ChartOf*, ExchangePlan, BusinessProcess, Task | Объект (тип: *Object.Имя) | DefaultObjectForm (DefaultForm для DataProcessor/Report/ExternalDataProcessor/ExternalReport) |
| List | Все кроме DataProcessor | Список (DynamicList) | DefaultListForm |
| Choice | Document, Catalog, ChartOf*, ExchangePlan, BusinessProcess, Task | Список (DynamicList) | DefaultChoiceForm |
| Record | InformationRegister | Запись (InformationRegisterRecordManager) | DefaultRecordForm |
@@ -62,7 +62,7 @@ powershell.exe -NoProfile -File .claude\skills\form-add\scripts\form-add.ps1 -Ob
## Поддерживаемые типы объектов
Document, Catalog, DataProcessor, Report, InformationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ExchangePlan, BusinessProcess, Task
Document, Catalog, DataProcessor, Report, ExternalDataProcessor, ExternalReport, InformationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ExchangePlan, BusinessProcess, Task
## Примеры
+4 -1
View File
@@ -41,6 +41,7 @@ if (-not $metaDataObject) {
$supportedTypes = @(
"Document", "Catalog", "DataProcessor", "Report",
"ExternalDataProcessor", "ExternalReport",
"InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
"ExchangePlan", "BusinessProcess", "Task"
)
@@ -89,7 +90,7 @@ switch ($Purpose) {
}
$objectLikeTypes = @("Document", "Catalog", "ChartOfAccounts", "ChartOfCharacteristicTypes", "ExchangePlan", "BusinessProcess", "Task")
$processorLikeTypes = @("DataProcessor", "Report")
$processorLikeTypes = @("DataProcessor", "Report", "ExternalDataProcessor", "ExternalReport")
switch ($Purpose) {
"Object" {
@@ -235,6 +236,8 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
"Catalog" = "CatalogObject"
"DataProcessor" = "DataProcessorObject"
"Report" = "ReportObject"
"ExternalDataProcessor" = "ExternalDataProcessorObject"
"ExternalReport" = "ExternalReportObject"
"ChartOfAccounts" = "ChartOfAccountsObject"
"ChartOfCharacteristicTypes" = "ChartOfCharacteristicTypesObject"
"ExchangePlan" = "ExchangePlanObject"
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: form-compile
description: Компиляция управляемой формы 1С (Form.xml) из компактного JSON-определения
description: Компиляция управляемой формы 1С из компактного JSON-определения. Используй когда нужно создать форму с нуля по описанию элементов
argument-hint: <JsonPath> <OutputPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: form-edit
description: Добавление элементов, реквизитов и команд в существующую управляемую форму 1С (Form.xml)
description: Добавление элементов, реквизитов и команд в существующую управляемую форму 1С. Используй когда нужно точечно модифицировать готовую форму
argument-hint: <FormPath> <JsonPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: form-info
description: Анализ структуры управляемой формы 1С (Form.xml) — элементы, реквизиты, команды, события
description: Анализ структуры управляемой формы 1С (Form.xml) — элементы, реквизиты, команды, события. Используй для понимания формы — при написании модуля формы, анализе обработчиков и элементов
argument-hint: <FormPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: form-patterns
description: Справочник паттернов компоновки управляемых форм 1С — архетипы, конвенции именования, продвинутые приёмы
description: Справочник паттернов компоновки управляемых форм 1С. Используй как справочник при проектировании форм — архетипы, конвенции, продвинутые приёмы
argument-hint: (no arguments)
allowed-tools: []
---
+47
View File
@@ -0,0 +1,47 @@
---
name: form-remove
description: Удалить форму из объекта 1С (обработка, отчёт, справочник, документ и др.)
argument-hint: <ObjectName> <FormName>
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
---
# /form-remove — Удаление формы
Удаляет форму и убирает её регистрацию из корневого XML объекта.
## Usage
```
/form-remove <ObjectName> <FormName>
```
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------|
| ObjectName | да | — | Имя объекта |
| FormName | да | — | Имя формы для удаления |
| SrcDir | нет | `src` | Каталог исходников |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\form-remove\scripts\remove-form.ps1 -ObjectName "<ObjectName>" -FormName "<FormName>" [-SrcDir "<SrcDir>"]
```
## Что удаляется
```
<SrcDir>/<ObjectName>/Forms/<FormName>.xml # Метаданные формы
<SrcDir>/<ObjectName>/Forms/<FormName>/ # Каталог формы (рекурсивно)
```
## Что модифицируется
- `<SrcDir>/<ObjectName>.xml` — убирается `<Form>` из `ChildObjects`
- Если удаляемая форма была DefaultForm — очищается значение DefaultForm
@@ -1,8 +1,9 @@
# epf-remove-form v1.0 — Remove form from 1C processor
# form-remove v1.1 — Remove form from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ProcessorName,
[Alias("ProcessorName")]
[string]$ObjectName,
[Parameter(Mandatory)]
[string]$FormName,
@@ -14,13 +15,13 @@ $ErrorActionPreference = "Stop"
# --- Проверки ---
$rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml"
$rootXmlPath = Join-Path $SrcDir "$ObjectName.xml"
if (-not (Test-Path $rootXmlPath)) {
Write-Error "Корневой файл обработки не найден: $rootXmlPath"
exit 1
}
$processorDir = Join-Path $SrcDir $ProcessorName
$processorDir = Join-Path $SrcDir $ObjectName
$formsDir = Join-Path $processorDir "Forms"
$formMetaPath = Join-Path $formsDir "$FormName.xml"
$formDir = Join-Path $formsDir $FormName
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: form-validate
description: Валидация структурной корректности управляемой формы 1С (Form.xml)
description: Валидация управляемой формы 1С. Используй после создания или модификации формы для проверки корректности
argument-hint: <FormPath>
allowed-tools:
- Bash
@@ -1,7 +1,7 @@
---
name: epf-add-help
description: Добавить встроенную справку к внешней обработке 1С
argument-hint: <ProcessorName>
name: help-add
description: Добавить встроенную справку к объекту 1С (обработка, отчёт, справочник, документ и др.)
argument-hint: <ObjectName>
allowed-tools:
- Bash
- Read
@@ -11,32 +11,32 @@ allowed-tools:
- Grep
---
# /epf-add-help — Добавление справки
# /help-add — Добавление справки
Добавляет встроенную справку к обработке: файл метаданных `Help.xml`, HTML-страницу и при необходимости обновляет метаданные форм.
Добавляет встроенную справку к объекту: файл метаданных `Help.xml`, HTML-страницу и при необходимости обновляет метаданные форм.
## Usage
```
/epf-add-help <ProcessorName> [Lang] [SrcDir]
/help-add <ObjectName> [Lang] [SrcDir]
```
| Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|--------------|-------------------------------------|
| ProcessorName | да | — | Имя обработки |
| Lang | нет | `ru` | Код языка справки |
| SrcDir | нет | `src` | Каталог исходников |
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------|
| ObjectName | да | — | Имя объекта |
| Lang | нет | `ru` | Код языка справки |
| SrcDir | нет | `src` | Каталог исходников |
## Команда
```powershell
powershell.exe -NoProfile -File .claude/skills/epf-add-help/scripts/add-help.ps1 -ProcessorName "<ProcessorName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"]
powershell.exe -NoProfile -File .claude\skills\help-add\scripts\add-help.ps1 -ObjectName "<ObjectName>" [-Lang "<Lang>"] [-SrcDir "<SrcDir>"]
```
## Что создаётся
```
<SrcDir>/<ProcessorName>/
<SrcDir>/<ObjectName>/
Ext/
Help.xml # Метаданные справки (namespace extrnprops)
Help/
@@ -49,13 +49,13 @@ powershell.exe -NoProfile -File .claude/skills/epf-add-help/scripts/add-help.ps1
## Что модифицируется
- Если в метаданных формы (`Forms/<FormName>.xml`) отсутствует `<IncludeHelpInContents>` — скрипт добавит `<IncludeHelpInContents>false</IncludeHelpInContents>` после `<FormType>`. Для форм, созданных через `/epf-add-form`, элемент уже есть.
- Если в метаданных формы (`Forms/<FormName>.xml`) отсутствует `<IncludeHelpInContents>` — скрипт добавит `<IncludeHelpInContents>false</IncludeHelpInContents>` после `<FormType>`. Для форм, созданных через `/form-add`, элемент уже есть.
## Кнопка справки на форме
После создания справки для её вызова нужна кнопка на форме. Добавь кнопку `Form.StandardCommand.Help` в AutoCommandBar формы (`Forms/<FormName>/Ext/Form.xml`).
### Текущая структура AutoCommandBar (созданная epf-add-form)
### Текущая структура AutoCommandBar (созданная form-add)
```xml
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
@@ -89,4 +89,4 @@ powershell.exe -NoProfile -File .claude/skills/epf-add-help/scripts/add-help.ps1
## Редактирование справки
После создания содержимое справки — обычный HTML. Отредактируй `Ext/Help/ru.html` в соответствии с назначением обработки. Поддерживается стандартная HTML-разметка: `<h1>`..`<h4>`, `<p>`, `<ul>`, `<ol>`, `<table>`, `<strong>`, `<em>`, `<a>`, `<pre>`.
После создания содержимое справки — обычный HTML. Отредактируй `Ext/Help/ru.html` в соответствии с назначением объекта. Поддерживается стандартная HTML-разметка: `<h1>`..`<h4>`, `<p>`, `<ul>`, `<ol>`, `<table>`, `<strong>`, `<em>`, `<a>`, `<pre>`.
@@ -1,8 +1,9 @@
# epf-add-help v1.0 — Add built-in help to 1C processor
# help-add v1.1 — Add built-in help to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ProcessorName,
[Alias("ProcessorName")]
[string]$ObjectName,
[string]$Lang = "ru",
@@ -13,7 +14,7 @@ $ErrorActionPreference = "Stop"
# --- Проверки ---
$processorDir = Join-Path $SrcDir $ProcessorName
$processorDir = Join-Path $SrcDir $ObjectName
$extDir = Join-Path $processorDir "Ext"
if (-not (Test-Path $extDir)) {
@@ -57,7 +58,7 @@ $helpHtml = @"
<link rel="stylesheet" type="text/css" href="v8help://service_book/service_style"/>
</head>
<body>
<h1>$ProcessorName</h1>
<h1>$ObjectName</h1>
<p>Описание обработки.</p>
</body>
</html>
@@ -112,6 +113,6 @@ if (Test-Path $formsDir) {
}
}
Write-Host "[OK] Создана справка: $ProcessorName"
Write-Host "[OK] Создана справка: $ObjectName"
Write-Host " Метаданные: $helpXmlPath"
Write-Host " Страница: $helpHtmlPath"
+18
View File
@@ -0,0 +1,18 @@
---
name: interface-edit
description: Настройка командного интерфейса подсистемы 1С. Используй когда нужно скрыть или показать команды, разместить в группах, настроить порядок
argument-hint: <CIPath> <Operation> <Value>
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /interface-edit — редактирование CommandInterface.xml
Операции: hide, show, place, order, subsystem-order, group-order. Подробнее: `.claude/skills/interface-edit/reference.md`
```powershell
powershell.exe -NoProfile -File '.claude\skills\interface-edit\scripts\interface-edit.ps1' -CIPath '<path>' -Operation hide -Value '<cmd>'
```
@@ -0,0 +1,48 @@
# /interface-edit — редактирование CommandInterface.xml
Точечное редактирование файла командного интерфейса подсистемы 1С.
## Параметры
| Параметр | Описание |
|----------|----------|
| CIPath | Путь к CommandInterface.xml |
| DefinitionFile | JSON-файл с массивом операций |
| Operation | Одна операция: hide, show, place, order, subsystem-order, group-order |
| Value | Значение для операции |
| CreateIfMissing | Создать файл если не существует |
| NoValidate | Пропустить авто-валидацию |
## Операции
| Операция | Значение | Описание |
|----------|----------|----------|
| hide | Cmd.Name или массив | Скрыть команду (CommandsVisibility, false) |
| show | Cmd.Name или массив | Показать команду (visibility, true) |
| place | {"command":"...","group":"CommandGroup.X"} | Разместить команду в группе |
| order | {"group":"...","commands":[...]} | Задать порядок команд в группе |
| subsystem-order | ["Subsystem.X.Subsystem.A",...] | Порядок дочерних подсистем |
| group-order | ["NavigationPanelOrdinary",...] | Порядок групп |
## Примеры
```powershell
# Скрыть команду
... -CIPath Subsystems/Продажи/Ext/CommandInterface.xml -Operation hide -Value "Catalog.Товары.StandardCommand.OpenList"
# Показать команду
... -Operation show -Value "Report.Продажи.Command.Отчёт"
# Разместить в группе
... -Operation place -Value '{"command":"Report.X.Command.Y","group":"CommandGroup.Отчеты"}'
# Задать порядок подсистем
... -Operation subsystem-order -Value '["Subsystem.X.Subsystem.A","Subsystem.X.Subsystem.B"]'
# Создать новый CI
... -CIPath <new-path> -Operation subsystem-order -Value '[...]' -CreateIfMissing
```
## Авто-валидация
После каждой операции автоматически запускается `/interface-validate`. Подавить: `-NoValidate`.
@@ -0,0 +1,490 @@
# interface-edit v1.0 — Edit 1C CommandInterface.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$CIPath,
[string]$DefinitionFile,
[ValidateSet("hide","show","place","order","subsystem-order","group-order")]
[string]$Operation,
[string]$Value,
[switch]$CreateIfMissing,
[switch]$NoValidate
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Mode validation ---
if ($DefinitionFile -and $Operation) { Write-Error "Cannot use both -DefinitionFile and -Operation"; exit 1 }
if (-not $DefinitionFile -and -not $Operation) { Write-Error "Either -DefinitionFile or -Operation is required"; exit 1 }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
$CIPath = Join-Path (Get-Location).Path $CIPath
}
$resolvedPath = $CIPath
# --- Namespaces ---
$script:ciNs = "http://v8.1c.ru/8.3/xcf/extrnprops"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$script:xsNs = "http://www.w3.org/2001/XMLSchema"
# --- Create if missing ---
if (-not (Test-Path $CIPath)) {
if ($CreateIfMissing) {
$parentDir = [System.IO.Path]::GetDirectoryName($CIPath)
if (-not (Test-Path $parentDir)) {
New-Item -ItemType Directory -Path $parentDir -Force | Out-Null
}
$emptyCI = @"
<?xml version="1.0" encoding="UTF-8"?>
<CommandInterface xmlns="$($script:ciNs)"
xmlns:xr="$($script:xrNs)"
xmlns:xs="$($script:xsNs)"
xmlns:xsi="$($script:xsiNs)"
version="2.17">
</CommandInterface>
"@
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($CIPath, $emptyCI, $utf8Bom)
Write-Host "[INFO] Created new CommandInterface.xml: $CIPath"
} else {
Write-Error "File not found: $CIPath (use -CreateIfMissing to create)"
exit 1
}
}
$resolvedPath = (Resolve-Path $CIPath).Path
# --- Load XML ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
$script:addCount = 0
$script:removeCount = 0
$script:modifyCount = 0
function Info([string]$msg) { Write-Host "[INFO] $msg" }
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
# --- Detect structure ---
$root = $script:xmlDoc.DocumentElement
if ($root.LocalName -ne "CommandInterface") {
Write-Error "Expected <CommandInterface> root element, got <$($root.LocalName)>"
exit 1
}
# Section canonical order
$script:sectionOrder = @("CommandsVisibility","CommandsPlacement","CommandsOrder","SubsystemsOrder","GroupsOrder")
# --- XML manipulation helpers ---
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0; $current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Remove-NodeWithWhitespace($node) {
$parent = $node.ParentNode
$prev = $node.PreviousSibling
$next = $node.NextSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($prev) | Out-Null
} elseif ($next -and ($next.NodeType -eq 'Whitespace' -or $next.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($next) | Out-Null
}
$parent.RemoveChild($node) | Out-Null
}
function Import-CIFragment([string]$xmlString) {
$wrapper = "<_W xmlns=`"$($script:ciNs)`" xmlns:xr=`"$($script:xrNs)`" xmlns:xsi=`"$($script:xsiNs)`" xmlns:xs=`"$($script:xsNs)`">$xmlString</_W>"
$frag = New-Object System.Xml.XmlDocument
$frag.PreserveWhitespace = $true
$frag.LoadXml($wrapper)
$nodes = @()
foreach ($child in $frag.DocumentElement.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$nodes += $script:xmlDoc.ImportNode($child, $true)
}
}
return ,$nodes
}
# --- Ensure section exists, creating it in correct order if needed ---
function Ensure-Section([string]$sectionName) {
# Find existing
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $sectionName) {
return $child
}
}
# Create new section
$newSection = $script:xmlDoc.CreateElement($sectionName, $script:ciNs)
# Find the correct insertion point: before the first section that comes AFTER us in canonical order
$myIdx = [array]::IndexOf($script:sectionOrder, $sectionName)
$refNode = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childIdx = [array]::IndexOf($script:sectionOrder, $child.LocalName)
if ($childIdx -gt $myIdx) {
# Find the whitespace before this element to insert before it
$prev = $child.PreviousSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
$refNode = $prev
} else {
$refNode = $child
}
break
}
}
$rootIndent = Get-ChildIndent $root
# Add closing whitespace inside the new section
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$rootIndent")
$newSection.AppendChild($closeWs) | Out-Null
if ($refNode) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$rootIndent")
$root.InsertBefore($ws, $refNode) | Out-Null
$root.InsertBefore($newSection, $ws) | Out-Null
} else {
Insert-BeforeElement $root $newSection $null $rootIndent
}
return $newSection
}
# --- Parse value: string or JSON array ---
function Parse-ValueList([string]$val) {
$val = $val.Trim()
if ($val.StartsWith("[")) {
$arr = $val | ConvertFrom-Json
$result = @(); foreach ($item in $arr) { $result += "$item" }
return ,$result
}
return @($val)
}
# --- Find Command element by name in a section ---
function Find-CommandByName($section, [string]$cmdName) {
foreach ($child in $section.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Command") {
if ($child.GetAttribute("name") -eq $cmdName) { return $child }
}
}
return $null
}
# --- Operations ---
function Do-Hide([string[]]$commands) {
$section = Ensure-Section "CommandsVisibility"
$sectionIndent = Get-ChildIndent $section
foreach ($cmd in $commands) {
$existing = Find-CommandByName $section $cmd
if ($existing) {
# Check if already false
$commonEl = $null
foreach ($vis in $existing.ChildNodes) {
if ($vis.NodeType -eq 'Element' -and $vis.LocalName -eq "Visibility") {
foreach ($c in $vis.ChildNodes) {
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq "Common") { $commonEl = $c; break }
}
}
}
if ($commonEl -and $commonEl.InnerText.Trim() -eq "false") {
Warn "Already hidden: $cmd"
continue
}
# Change true -> false
if ($commonEl) {
$commonEl.InnerText = "false"
$script:modifyCount++
Info "Changed to hidden: $cmd"
continue
}
}
# Add new entry
$fragXml = "<Command name=`"$cmd`"><Visibility><xr:Common>false</xr:Common></Visibility></Command>"
$nodes = Import-CIFragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $section $nodes[0] $null $sectionIndent
$script:addCount++
Info "Hidden: $cmd"
}
}
}
function Do-Show([string[]]$commands) {
$section = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "CommandsVisibility") {
$section = $child; break
}
}
foreach ($cmd in $commands) {
if (-not $section) {
# No CommandsVisibility section — showing means adding with true
$section = Ensure-Section "CommandsVisibility"
}
$existing = Find-CommandByName $section $cmd
if ($existing) {
$commonEl = $null
foreach ($vis in $existing.ChildNodes) {
if ($vis.NodeType -eq 'Element' -and $vis.LocalName -eq "Visibility") {
foreach ($c in $vis.ChildNodes) {
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq "Common") { $commonEl = $c; break }
}
}
}
if ($commonEl -and $commonEl.InnerText.Trim() -eq "true") {
Warn "Already shown: $cmd"
continue
}
if ($commonEl -and $commonEl.InnerText.Trim() -eq "false") {
# Change false -> true
$commonEl.InnerText = "true"
$script:modifyCount++
Info "Changed to shown: $cmd"
continue
}
}
# Add new entry with true
$sectionIndent = Get-ChildIndent $section
$fragXml = "<Command name=`"$cmd`"><Visibility><xr:Common>true</xr:Common></Visibility></Command>"
$nodes = Import-CIFragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $section $nodes[0] $null $sectionIndent
$script:addCount++
Info "Shown: $cmd"
}
}
}
function Do-Place([string]$jsonVal) {
$def = $jsonVal | ConvertFrom-Json
$cmdName = "$($def.command)"
$groupName = "$($def.group)"
if (-not $cmdName -or -not $groupName) { Write-Error "place requires {command, group}"; exit 1 }
$section = Ensure-Section "CommandsPlacement"
$sectionIndent = Get-ChildIndent $section
# Check existing
$existing = Find-CommandByName $section $cmdName
if ($existing) {
# Update group
foreach ($child in $existing.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "CommandGroup") {
$child.InnerText = $groupName
$script:modifyCount++
Info "Updated placement: $cmdName -> $groupName"
return
}
}
}
# Add new
$fragXml = "<Command name=`"$cmdName`"><CommandGroup>$groupName</CommandGroup><Placement>Auto</Placement></Command>"
$nodes = Import-CIFragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $section $nodes[0] $null $sectionIndent
$script:addCount++
Info "Placed: $cmdName -> $groupName"
}
}
function Do-Order([string]$jsonVal) {
$def = $jsonVal | ConvertFrom-Json
$groupName = "$($def.group)"
$commands = @($def.commands | ForEach-Object { "$_" })
if (-not $groupName -or $commands.Count -eq 0) { Write-Error "order requires {group, commands:[...]}"; exit 1 }
$section = Ensure-Section "CommandsOrder"
$sectionIndent = Get-ChildIndent $section
# Remove existing entries for this group
$toRemove = @()
foreach ($child in $section.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -ne "Command") { continue }
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq "CommandGroup" -and $gc.InnerText.Trim() -eq $groupName) {
$toRemove += $child
break
}
}
}
foreach ($node in $toRemove) {
Remove-NodeWithWhitespace $node
$script:removeCount++
}
# Add new entries in order
foreach ($cmdName in $commands) {
$fragXml = "<Command name=`"$cmdName`"><CommandGroup>$groupName</CommandGroup></Command>"
$nodes = Import-CIFragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $section $nodes[0] $null $sectionIndent
$script:addCount++
}
}
Info "Set order for $groupName : $($commands.Count) commands"
}
function Do-SubsystemOrder([string]$jsonVal) {
$parsed = $jsonVal | ConvertFrom-Json
$subsystems = @(); foreach ($s in $parsed) { $subsystems += "$s" }
if ($subsystems.Count -eq 0) { Write-Error "subsystem-order requires array of subsystem paths"; exit 1 }
$section = Ensure-Section "SubsystemsOrder"
$sectionIndent = Get-ChildIndent $section
# Clear existing
$toRemove = @()
foreach ($child in @($section.ChildNodes)) {
if ($child.NodeType -eq 'Element') { $toRemove += $child }
}
foreach ($node in $toRemove) {
Remove-NodeWithWhitespace $node
$script:removeCount++
}
# Add new entries
foreach ($sub in $subsystems) {
$newEl = $script:xmlDoc.CreateElement("Subsystem", $script:ciNs)
$newEl.InnerText = $sub
Insert-BeforeElement $section $newEl $null $sectionIndent
$script:addCount++
}
Info "Set subsystem order: $($subsystems.Count) entries"
}
function Do-GroupOrder([string]$jsonVal) {
$parsed = $jsonVal | ConvertFrom-Json
$groups = @(); foreach ($g in $parsed) { $groups += "$g" }
if ($groups.Count -eq 0) { Write-Error "group-order requires array of group names"; exit 1 }
$section = Ensure-Section "GroupsOrder"
$sectionIndent = Get-ChildIndent $section
# Clear existing
$toRemove = @()
foreach ($child in @($section.ChildNodes)) {
if ($child.NodeType -eq 'Element') { $toRemove += $child }
}
foreach ($node in $toRemove) {
Remove-NodeWithWhitespace $node
$script:removeCount++
}
# Add new entries
foreach ($grp in $groups) {
$newEl = $script:xmlDoc.CreateElement("Group", $script:ciNs)
$newEl.InnerText = $grp
Insert-BeforeElement $section $newEl $null $sectionIndent
$script:addCount++
}
Info "Set group order: $($groups.Count) entries"
}
# --- Execute operations ---
$operations = @()
if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$ops = $jsonText | ConvertFrom-Json
if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op }
} else {
$operations += $ops
}
} else {
$operations += @{ operation = $Operation; value = $Value }
}
foreach ($op in $operations) {
$opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" }
$opValue = if ($op.value) { "$($op.value)" } else { "$Value" }
switch ($opName) {
"hide" { Do-Hide (Parse-ValueList $opValue) }
"show" { Do-Show (Parse-ValueList $opValue) }
"place" { Do-Place $opValue }
"order" { Do-Order $opValue }
"subsystem-order" { Do-SubsystemOrder $opValue }
"group-order" { Do-GroupOrder $opValue }
default { Write-Error "Unknown operation: $opName"; exit 1 }
}
}
# --- Save ---
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath"
# --- Auto-validate ---
if (-not $NoValidate) {
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\interface-validate") "scripts\interface-validate.ps1"
$validateScript = [System.IO.Path]::GetFullPath($validateScript)
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- Running interface-validate ---"
& powershell.exe -NoProfile -File $validateScript -CIPath $resolvedPath
}
}
# --- Summary ---
Write-Host ""
Write-Host "=== interface-edit summary ==="
Write-Host " Added: $($script:addCount)"
Write-Host " Removed: $($script:removeCount)"
Write-Host " Modified: $($script:modifyCount)"
exit 0
@@ -0,0 +1,82 @@
---
name: interface-validate
description: Валидация командного интерфейса 1С. Используй после настройки командного интерфейса подсистемы для проверки корректности
argument-hint: <CIPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /interface-validate — валидация CommandInterface.xml
Проверяет XML командного интерфейса из выгрузки конфигурации на структурные ошибки: корневой элемент, допустимые секции, порядок, формат ссылок на команды, дубликаты.
## Параметры
| Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|------------------------------------|
| CIPath | да | — | Путь к CommandInterface.xml |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
## Команда
```powershell
powershell.exe -NoProfile -File '.claude\skills\interface-validate\scripts\interface-validate.ps1' -CIPath '<path>'
```
## Проверки (13)
| # | Проверка | Серьёзность |
|----|--------------------------------------------------------------|-------------|
| 1 | XML well-formedness + root element (CommandInterface, version, namespace) | ERROR |
| 2 | Допустимые дочерние элементы (только 5 секций) | ERROR |
| 3 | Порядок секций корректен | ERROR |
| 4 | Нет дублирующихся секций | ERROR |
| 5 | CommandsVisibility — Command.name + Visibility/xr:Common | ERROR |
| 6 | CommandsVisibility — нет дубликатов по name | WARN |
| 7 | CommandsPlacement — Command.name + CommandGroup + Placement | ERROR |
| 8 | CommandsOrder — Command.name + CommandGroup | ERROR |
| 9 | SubsystemsOrder — Subsystem непустой, формат Subsystem.X | ERROR |
| 10 | SubsystemsOrder — нет дубликатов | WARN |
| 11 | GroupsOrder — Group непустой | ERROR |
| 12 | GroupsOrder — нет дубликатов | WARN |
| 13 | Формат ссылок на команды | WARN |
## Вывод
```
=== Validation: CommandInterface (Продажи) ===
[OK] 1. Root structure: CommandInterface, version 2.17, namespace valid
[OK] 2. Child elements: 5 valid sections
[OK] 3. Section order: correct
[OK] 4. No duplicate sections
[OK] 5. CommandsVisibility: 55 entries, all valid
[OK] 6. CommandsVisibility: no duplicates
[OK] 7. CommandsPlacement: 3 entries, all valid
[OK] 8. CommandsOrder: 12 entries, all valid
[OK] 9. SubsystemsOrder: 9 entries, all valid format
[OK] 10. SubsystemsOrder: no duplicates
[OK] 11. GroupsOrder: 7 entries, all valid
[OK] 12. GroupsOrder: no duplicates
[OK] 13. Command reference format: all valid
---
Errors: 0, Warnings: 0
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Примеры
```powershell
# CommandInterface подсистемы
... -CIPath upload/acc_8.3.24/Subsystems/Продажи/Ext/CommandInterface.xml
# Корневой CommandInterface конфигурации
... -CIPath upload/acc_8.3.24/Ext/CommandInterface.xml
# С лимитом ошибок
... -CIPath <path> -MaxErrors 10
```
@@ -0,0 +1,382 @@
# interface-validate v1.0 — Validate 1C CommandInterface.xml structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$CIPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
$CIPath = Join-Path (Get-Location).Path $CIPath
}
if (-not (Test-Path $CIPath)) {
Write-Host "[ERROR] File not found: $CIPath"
exit 1
}
$resolvedPath = (Resolve-Path $CIPath).Path
# --- Derive context name from path ---
$contextName = ""
$parentParts = $resolvedPath -split '[/\\]'
for ($i = 0; $i -lt $parentParts.Count; $i++) {
if ($parentParts[$i] -eq "Subsystems" -and ($i + 1) -lt $parentParts.Count) {
$contextName = $parentParts[$i + 1]
}
}
if (-not $contextName) { $contextName = "Root" }
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
$script:allCommandNames = @()
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
function Report-OK([string]$msg) { Out-Line "[OK] $msg" }
function Report-Error([string]$msg) {
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
}
function Report-Warn([string]$msg) {
$script:warnings++
Out-Line "[WARN] $msg"
}
Out-Line "=== Validation: CommandInterface ($contextName) ==="
Out-Line ""
# --- Valid section names and order ---
$validSections = @("CommandsVisibility","CommandsPlacement","CommandsOrder","SubsystemsOrder","GroupsOrder")
# Command reference patterns
$stdCmdPattern = '^[A-Za-z]+\.[^\s\.]+\.StandardCommand\.\w+$'
$customCmdPattern = '^[A-Za-z]+\.[^\s\.]+\.Command\.\w+$'
$commonCmdPattern = '^CommonCommand\.\w+$'
$uuidCmdPattern = '^0:[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
# --- 1. XML well-formedness + root structure ---
$xmlDoc = $null
try {
[xml]$xmlDoc = Get-Content -Path $resolvedPath -Encoding UTF8
} catch {
Report-Error "1. XML parse error: $($_.Exception.Message)"
$script:stopped = $true
}
if (-not $script:stopped) {
$root = $xmlDoc.DocumentElement
if ($root.LocalName -ne "CommandInterface") {
Report-Error "1. Root element: expected <CommandInterface>, got <$($root.LocalName)>"
$script:stopped = $true
} else {
$nsUri = $root.NamespaceURI
$version = $root.GetAttribute("version")
$expectedNs = "http://v8.1c.ru/8.3/xcf/extrnprops"
if ($nsUri -ne $expectedNs) {
Report-Error "1. Root namespace: expected $expectedNs, got $nsUri"
} elseif (-not $version) {
Report-Warn "1. Root structure: CommandInterface, namespace valid, but no version attribute"
} else {
Report-OK "1. Root structure: CommandInterface, version $version, namespace valid"
}
}
}
# --- Setup namespace manager ---
$ns = $null
if (-not $script:stopped) {
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("ci", "http://v8.1c.ru/8.3/xcf/extrnprops")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
}
# --- 2. Valid child elements ---
if (-not $script:stopped) {
$root = $xmlDoc.DocumentElement
$foundSections = @()
$invalidElements = @()
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -in $validSections) {
$foundSections += $child.LocalName
} else {
$invalidElements += $child.LocalName
}
}
if ($invalidElements.Count -gt 0) {
Report-Error "2. Invalid child elements: $($invalidElements -join ', ')"
} else {
Report-OK "2. Child elements: $($foundSections.Count) valid sections"
}
}
# --- 3. Section order ---
if (-not $script:stopped) {
$orderOk = $true
$lastIdx = -1
foreach ($sec in $foundSections) {
$idx = [array]::IndexOf($validSections, $sec)
if ($idx -lt $lastIdx) {
Report-Error "3. Section order: '$sec' appears after a later section (expected: CommandsVisibility -> CommandsPlacement -> CommandsOrder -> SubsystemsOrder -> GroupsOrder)"
$orderOk = $false
break
}
$lastIdx = $idx
}
if ($orderOk) { Report-OK "3. Section order: correct" }
}
# --- 4. No duplicate sections ---
if (-not $script:stopped) {
$dupes = $foundSections | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Error "4. Duplicate sections: $dupeNames"
} else {
Report-OK "4. No duplicate sections"
}
}
# --- 5. CommandsVisibility ---
if (-not $script:stopped) {
$visSection = $root.SelectSingleNode("ci:CommandsVisibility", $ns)
if ($visSection) {
$visOk = $true
$visNames = @()
$visCount = 0
foreach ($cmd in $visSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$visCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "5. CommandsVisibility: Command element without 'name' attribute"
$visOk = $false; continue
}
$visNames += $cmdName
$script:allCommandNames += $cmdName
$visibility = $cmd.SelectSingleNode("ci:Visibility", $ns)
if (-not $visibility) {
Report-Error "5. CommandsVisibility[$cmdName]: missing <Visibility>"
$visOk = $false; continue
}
$common = $visibility.SelectSingleNode("xr:Common", $ns)
if (-not $common) {
Report-Error "5. CommandsVisibility[$cmdName]: missing <xr:Common>"
$visOk = $false; continue
}
$val = $common.InnerText.Trim()
if ($val -ne "true" -and $val -ne "false") {
Report-Error "5. CommandsVisibility[$cmdName]: xr:Common='$val' (expected true/false)"
$visOk = $false
}
}
if ($visOk) { Report-OK "5. CommandsVisibility: $visCount entries, all valid" }
} else {
Report-OK "5. CommandsVisibility: not present"
$visNames = @()
}
}
# --- 6. CommandsVisibility duplicates ---
if (-not $script:stopped) {
if ($visNames.Count -gt 0) {
$dupes = $visNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "6. CommandsVisibility: duplicates: $dupeNames"
} else {
Report-OK "6. CommandsVisibility: no duplicates"
}
} else {
Report-OK "6. CommandsVisibility: no duplicates (empty)"
}
}
# --- 7. CommandsPlacement ---
if (-not $script:stopped) {
$plcSection = $root.SelectSingleNode("ci:CommandsPlacement", $ns)
if ($plcSection) {
$plcOk = $true
$plcCount = 0
foreach ($cmd in $plcSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$plcCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "7. CommandsPlacement: Command without 'name' attribute"
$plcOk = $false; continue
}
$script:allCommandNames += $cmdName
$grpEl = $cmd.SelectSingleNode("ci:CommandGroup", $ns)
if (-not $grpEl -or -not $grpEl.InnerText.Trim()) {
Report-Error "7. CommandsPlacement[$cmdName]: missing or empty <CommandGroup>"
$plcOk = $false; continue
}
$placementEl = $cmd.SelectSingleNode("ci:Placement", $ns)
if (-not $placementEl) {
Report-Error "7. CommandsPlacement[$cmdName]: missing <Placement>"
$plcOk = $false
} elseif ($placementEl.InnerText.Trim() -ne "Auto") {
Report-Warn "7. CommandsPlacement[$cmdName]: Placement='$($placementEl.InnerText.Trim())' (expected Auto)"
}
}
if ($plcOk) { Report-OK "7. CommandsPlacement: $plcCount entries, all valid" }
} else {
Report-OK "7. CommandsPlacement: not present"
}
}
# --- 8. CommandsOrder ---
if (-not $script:stopped) {
$ordSection = $root.SelectSingleNode("ci:CommandsOrder", $ns)
if ($ordSection) {
$ordOk = $true
$ordCount = 0
foreach ($cmd in $ordSection.ChildNodes) {
if ($cmd.NodeType -ne 'Element') { continue }
$ordCount++
$cmdName = $cmd.GetAttribute("name")
if (-not $cmdName) {
Report-Error "8. CommandsOrder: Command without 'name' attribute"
$ordOk = $false; continue
}
$script:allCommandNames += $cmdName
$grpEl = $cmd.SelectSingleNode("ci:CommandGroup", $ns)
if (-not $grpEl -or -not $grpEl.InnerText.Trim()) {
Report-Error "8. CommandsOrder[$cmdName]: missing or empty <CommandGroup>"
$ordOk = $false
}
}
if ($ordOk) { Report-OK "8. CommandsOrder: $ordCount entries, all valid" }
} else {
Report-OK "8. CommandsOrder: not present"
}
}
# --- 9. SubsystemsOrder format ---
if (-not $script:stopped) {
$subSection = $root.SelectSingleNode("ci:SubsystemsOrder", $ns)
$subNames = @()
if ($subSection) {
$subOk = $true
$subCount = 0
foreach ($sub in $subSection.ChildNodes) {
if ($sub.NodeType -ne 'Element') { continue }
$subCount++
$text = $sub.InnerText.Trim()
$subNames += $text
if (-not $text) {
Report-Error "9. SubsystemsOrder: empty <Subsystem> element"
$subOk = $false
} elseif ($text -notmatch '^Subsystem\.') {
Report-Error "9. SubsystemsOrder: '$text' - expected format Subsystem.X..."
$subOk = $false
}
}
if ($subOk) { Report-OK "9. SubsystemsOrder: $subCount entries, all valid format" }
} else {
Report-OK "9. SubsystemsOrder: not present"
}
}
# --- 10. SubsystemsOrder duplicates ---
if (-not $script:stopped) {
if ($subNames.Count -gt 0) {
$dupes = $subNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "10. SubsystemsOrder: duplicates: $dupeNames"
} else {
Report-OK "10. SubsystemsOrder: no duplicates"
}
} else {
Report-OK "10. SubsystemsOrder: no duplicates (empty)"
}
}
# --- 11. GroupsOrder entries ---
if (-not $script:stopped) {
$grpSection = $root.SelectSingleNode("ci:GroupsOrder", $ns)
$grpNames = @()
if ($grpSection) {
$grpOk = $true
$grpCount = 0
foreach ($grp in $grpSection.ChildNodes) {
if ($grp.NodeType -ne 'Element') { continue }
$grpCount++
$text = $grp.InnerText.Trim()
$grpNames += $text
if (-not $text) {
Report-Error "11. GroupsOrder: empty <Group> element"
$grpOk = $false
}
}
if ($grpOk) { Report-OK "11. GroupsOrder: $grpCount entries, all valid" }
} else {
Report-OK "11. GroupsOrder: not present"
}
}
# --- 12. GroupsOrder duplicates ---
if (-not $script:stopped) {
if ($grpNames.Count -gt 0) {
$dupes = $grpNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "12. GroupsOrder: duplicates: $dupeNames"
} else {
Report-OK "12. GroupsOrder: no duplicates"
}
} else {
Report-OK "12. GroupsOrder: no duplicates (empty)"
}
}
# --- 13. Command reference format ---
if (-not $script:stopped) {
if ($script:allCommandNames.Count -gt 0) {
$badRefs = @()
foreach ($ref in $script:allCommandNames) {
if ($ref -match $stdCmdPattern) { continue }
if ($ref -match $customCmdPattern) { continue }
if ($ref -match $commonCmdPattern) { continue }
if ($ref -match $uuidCmdPattern) { continue }
$badRefs += $ref
}
if ($badRefs.Count -eq 0) {
Report-OK "13. Command reference format: all $($script:allCommandNames.Count) valid"
} else {
$shown = $badRefs[0..([Math]::Min(4, $badRefs.Count - 1))]
Report-Warn "13. Command reference format: $($badRefs.Count) unrecognized: $($shown -join ', ')$(if($badRefs.Count -gt 5){' ...'})"
}
} else {
Report-OK "13. Command reference format: n/a (no commands)"
}
}
# --- Finalize ---
Out-Line "---"
Out-Line "Errors: $($script:errors), Warnings: $($script:warnings)"
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
if (-not [System.IO.Path]::IsPathRooted($OutFile)) {
$OutFile = Join-Path (Get-Location).Path $OutFile
}
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
if ($script:errors -gt 0) { exit 1 } else { exit 0 }
+184
View File
@@ -0,0 +1,184 @@
---
name: meta-compile
description: Создать исходники объекта метаданных 1С (справочник, документ, регистр, перечисление, константа, общий модуль, обработка, HTTP-сервис и др.) в выгрузке конфигурации. Используй когда пользователь просит добавить или создать объект конфигурации
argument-hint: <JsonPath> <OutputDir>
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /meta-compile — генерация объектов метаданных из JSON DSL
Принимает JSON-определение объекта метаданных → генерирует XML + модули в структуре выгрузки конфигурации + регистрирует в Configuration.xml.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `JsonPath` | Путь к JSON-определению объекта |
| `OutputDir` | Корневая директория выгрузки конфигурации (где `Catalogs/`, `Documents/` и т.д.) |
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-compile\scripts\meta-compile.ps1 -JsonPath "<json>" -OutputDir "<ConfigDir>"
```
`OutputDir` — директория, содержащая подпапки типов (`Catalogs/`, `Documents/`, ...) и `Configuration.xml`.
## Поддерживаемые типы (23)
### Ссылочные
Catalog (Справочник), Document (Документ), Enum (Перечисление), ExchangePlan (ПланОбмена), ChartOfAccounts (ПланСчетов), ChartOfCharacteristicTypes (ПВХ), ChartOfCalculationTypes (ПВР), BusinessProcess (БизнесПроцесс), Task (Задача)
### Регистры
InformationRegister (РегистрСведений), AccumulationRegister (РегистрНакопления), AccountingRegister (РегистрБухгалтерии), CalculationRegister (РегистрРасчёта)
### Отчёты/Обработки
Report (Отчёт), DataProcessor (Обработка)
### Сервисные
Constant (Константа), DefinedType (ОпределяемыйТип), CommonModule (ОбщийМодуль), ScheduledJob (РегламентноеЗадание), EventSubscription (ПодпискаНаСобытие), DocumentJournal (ЖурналДокументов), HTTPService (HTTPСервис), WebService (ВебСервис)
## JSON DSL — краткий справочник
Полная спецификация: `docs/meta-dsl-spec.md`.
### Корневая структура
```json
{
"type": "Catalog",
"name": "Номенклатура",
"synonym": "авто из name",
...type-specific...,
"attributes": [...],
"tabularSections": {...}
}
```
### Реквизиты — shorthand
```
"ИмяРеквизита" — String без квалификаторов
"ИмяРеквизита: Тип" — с типом
"ИмяРеквизита: Тип | req, index" — с флагами
```
Типы: `String(100)`, `Number(15,2)`, `Boolean`, `Date`, `DateTime`, `CatalogRef.Xxx`, `DocumentRef.Xxx`, `EnumRef.Xxx`, `ChartOfAccountsRef.Xxx`, `ChartOfCharacteristicTypesRef.Xxx`, `ChartOfCalculationTypesRef.Xxx`, `ExchangePlanRef.Xxx`, `BusinessProcessRef.Xxx`, `TaskRef.Xxx`, `DefinedType.Xxx`.
Русские синонимы типов: `Строка`, `Число`, `Булево`, `Дата`, `СправочникСсылка.Xxx`, `ДокументСсылка.Xxx`, `ПланСчетовСсылка.Xxx`.
Флаги: `req`, `index`, `indexAdditional`, `nonneg`, `master`, `mainFilter`, `denyIncomplete`, `useInTotals`.
## Примеры
### Справочник
```json
{ "type": "Catalog", "name": "Валюты" }
```
### Перечисление
```json
{ "type": "Enum", "name": "Статусы", "values": ["Новый", "Закрыт"] }
```
### Константа
```json
{ "type": "Constant", "name": "ОсновнаяВалюта", "valueType": "CatalogRef.Валюты" }
```
### Определяемый тип
```json
{ "type": "DefinedType", "name": "ДенежныеСредства", "valueTypes": ["CatalogRef.БанковскиеСчета", "CatalogRef.Кассы"] }
```
### Общий модуль
```json
{ "type": "CommonModule", "name": "ОбменДаннымиСервер", "context": "server", "returnValuesReuse": "DuringRequest" }
```
Шорткаты context: `"server"` → Server+ServerCall, `"client"` → ClientManagedApplication, `"serverClient"` → Server+ClientManagedApplication.
### Регистр сведений
```json
{
"type": "InformationRegister", "name": "КурсыВалют", "periodicity": "Day",
"dimensions": ["Валюта: CatalogRef.Валюты | master, mainFilter, denyIncomplete"],
"resources": ["Курс: Number(15,4)", "Кратность: Number(10,0)"]
}
```
### План обмена
```json
{ "type": "ExchangePlan", "name": "ОбменССайтом", "attributes": ["АдресСервера: String(200)"] }
```
### Журнал документов
```json
{
"type": "DocumentJournal", "name": "Взаимодействия",
"registeredDocuments": ["Document.Встреча", "Document.ТелефонныйЗвонок"],
"columns": [{ "name": "Организация", "indexing": "Index", "references": ["Document.Встреча.Attribute.Организация"] }]
}
```
### HTTP-сервис
```json
{
"type": "HTTPService", "name": "API", "rootURL": "api",
"urlTemplates": { "Users": { "template": "/v1/users", "methods": { "Get": "GET", "Create": "POST" } } }
}
```
### Веб-сервис
```json
{
"type": "WebService", "name": "DataExchange", "namespace": "http://www.1c.ru/DataExchange",
"operations": { "TestConnection": { "returnType": "xs:boolean", "handler": "ПроверкаПодключения", "parameters": { "ErrorMessage": { "type": "xs:string", "direction": "Out" } } } }
}
```
### План счетов
```json
{
"type": "ChartOfAccounts", "name": "Хозрасчетный",
"extDimensionTypes": "ChartOfCharacteristicTypes.ВидыСубконто", "maxExtDimensionCount": 3,
"codeMask": "@@@.@@.@", "codeLength": 8,
"accountingFlags": ["Валютный", "Количественный"],
"extDimensionAccountingFlags": ["Суммовой", "Валютный"]
}
```
### Бизнес-процесс
```json
{ "type": "BusinessProcess", "name": "Задание", "attributes": ["Описание: String(200)"] }
```
## Что генерируется
- `{OutputDir}/{TypePlural}/{Name}.xml` — метаданные объекта
- `{OutputDir}/{TypePlural}/{Name}/Ext/ObjectModule.bsl` — модуль объекта (Catalog, Document, Report, DataProcessor, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task)
- `{OutputDir}/{TypePlural}/{Name}/Ext/RecordSetModule.bsl` — модуль набора записей (4 типа регистров)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Module.bsl` — модуль (CommonModule, HTTPService, WebService)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Content.xml` — состав плана обмена (ExchangePlan)
- `{OutputDir}/{TypePlural}/{Name}/Ext/Flowchart.xml` — карта маршрута (BusinessProcess)
- `Configuration.xml` — автоматическая регистрация в `<ChildObjects>`
## Верификация
```
/meta-info <OutputDir>/<TypePlural>/<Name>.xml — проверка структуры
```
File diff suppressed because it is too large Load Diff
+105
View File
@@ -0,0 +1,105 @@
---
name: meta-edit
description: Точечное редактирование объекта метаданных 1С. Используй когда нужно добавить, удалить или изменить реквизиты, табличные части, измерения, ресурсы или свойства существующего объекта конфигурации
argument-hint: <ObjectPath> -Operation <op> -Value "<val>" | -DefinitionFile <json> [-NoValidate]
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /meta-edit — точечное редактирование метаданных 1С
Атомарные операции модификации существующих XML объектов метаданных.
## Команда
### Inline mode (простые операции)
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -ObjectPath "<path>" -Operation <op> -Value "<val>"
```
### JSON mode (сложные/комбинированные)
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -DefinitionFile "<json>" -ObjectPath "<path>"
```
| Параметр | Описание |
|----------|----------|
| ObjectPath | XML-файл или директория объекта (обязательный, авторезолв `<dirName>.xml`) |
| Operation | Inline-операция (альтернатива DefinitionFile) |
| Value | Значение для inline-операции |
| DefinitionFile | JSON-файл с операциями (альтернатива Operation) |
| NoValidate | Не запускать meta-validate после правки |
## Операции — сводная таблица
Batch через `;;` во всех операциях. Подробный синтаксис — в файлах по ссылкам.
### Дочерние элементы — [child-operations.md](child-operations.md)
| Операция | Формат Value | Пример |
|----------|-------------|--------|
| `add-attribute` | `Имя: Тип \| флаги` | `"Сумма: Число(15,2) \| req, index"` |
| `add-ts` | `ТЧ: Рекв1: Тип1, Рекв2: Тип2` | `"Товары: Ном: CatalogRef.Ном, Кол: Число(15,3)"` |
| `add-dimension` | `Имя: Тип \| флаги` | `"Организация: CatalogRef.Организации \| master"` |
| `add-resource` | `Имя: Тип` | `"Сумма: Число(15,2)"` |
| `add-enumValue` | `Имя` | `"Значение1 ;; Значение2"` |
| `add-column` | `Имя: Тип` | `"Тип: EnumRef.ТипыДокументов"` |
| `add-form` / `add-template` / `add-command` | `Имя` | `"ФормаЭлемента"` |
| `add-ts-attribute` | `ТЧ.Имя: Тип` | `"Товары.Скидка: Число(15,2)"` |
| `remove-*` | `Имя` | `"СтарыйРеквизит ;; ЕщёОдин"` |
| `remove-ts-attribute` | `ТЧ.Имя` | `"Товары.УстаревшийРекв"` |
| `modify-attribute` | `Имя: ключ=значение` | `"СтароеИмя: name=НовоеИмя, type=Строка(500)"` |
| `modify-ts-attribute` | `ТЧ.Имя: ключ=значение` | `"Товары.Рекв: name=НовоеИмя"` |
| `modify-ts` | `ТЧ: ключ=значение` | `"Товары: synonym=Товарный состав"` |
Позиционная вставка: `"Склад: CatalogRef.Склады >> after Организация"`.
### Свойства объекта — [properties-reference.md](properties-reference.md)
| Операция | Формат Value | Пример |
|----------|-------------|--------|
| `modify-property` | `Ключ=Значение` | `"CodeLength=11 ;; DescriptionLength=150"` |
| `add-owner` | `MetaType.Name` | `"Catalog.Контрагенты ;; Catalog.Организации"` |
| `add-registerRecord` | `MetaType.Name` | `"AccumulationRegister.ОстаткиТоваров"` |
| `add-basedOn` | `MetaType.Name` | `"Document.ЗаказКлиента"` |
| `add-inputByString` | `Путь поля` | `"StandardAttribute.Description"` |
| `set-owners` / `set-registerRecords` / `set-basedOn` / `set-inputByString` | Замена всего списка | `"Catalog.Орг ;; Catalog.Контр"` |
| `remove-owner` / `remove-registerRecord` / ... | Удаление из списка | `"Catalog.Контрагенты"` |
### JSON DSL — [json-dsl.md](json-dsl.md)
Для комбинированных операций (add + remove + modify в одном файле), синонимы ключей/типов, таблица поддерживаемых объектов.
## Быстрые примеры
```powershell
# Добавить реквизиты
-Operation add-attribute -Value "Комментарий: Строка(200) ;; Сумма: Число(15,2) | index"
# Добавить ТЧ с реквизитами
-Operation add-ts -Value "Товары: Ном: CatalogRef.Ном | req, Кол: Число(15,3), Цена: Число(15,2)"
# Удалить реквизит
-Operation remove-attribute -Value "УстаревшийРеквизит"
# Переименовать + сменить тип
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=Строка(500)"
# Изменить свойства объекта
-Operation modify-property -Value "CodeLength=11 ;; DescriptionLength=150"
# Владельцы справочника
-Operation set-owners -Value "Catalog.Контрагенты ;; Catalog.Организации"
```
## Верификация
```
/meta-validate <ObjectPath> — валидация после редактирования
/meta-info <ObjectPath> — визуальная сводка
```
@@ -0,0 +1,102 @@
# Inline-операции над дочерними элементами
Подробный справочник операций `add-*` / `remove-*` / `modify-*` для дочерних элементов объекта метаданных.
## Общие правила
**Batch-режим** — несколько элементов через `;;`:
```
-Value "Комментарий: Строка(200) ;; Сумма: Число(15,2) | index"
```
**Shorthand-формат** реквизитов: `ИмяРеквизита: Тип | флаги`
Флаги: `req` (FillChecking=ShowError), `index` (Indexing=Index), `master` (Master=true, только dimensions), `mainFilter` (MainFilterOperand, только dimensions).
**Позиционная вставка**: `>> after ИмяЭлемента` или `<< before ИмяЭлемента`:
```powershell
-Operation add-attribute -Value "Склад: CatalogRef.Склады >> after Организация"
```
## add-attribute / add-dimension / add-resource / add-column
```powershell
-Operation add-attribute -Value "Комментарий: Строка(200)"
-Operation add-attribute -Value "Сумма: Число(15,2) | req, index"
-Operation add-attribute -Value "Ном: CatalogRef.Номенклатура | req ;; Кол: Число(15,3)"
-Operation add-dimension -Value "Организация: CatalogRef.Организации | master, mainFilter"
-Operation add-resource -Value "Сумма: Число(15,2)"
-Operation add-column -Value "Тип: EnumRef.ТипыДокументов"
```
## add-ts
Формат: `ИмяТЧ: Реквизит1: Тип1, Реквизит2: Тип2, ...`
```powershell
-Operation add-ts -Value "Товары: Ном: CatalogRef.Ном | req, Кол: Число(15,3), Цена: Число(15,2), Сумма: Число(15,2)"
```
## add-ts-attribute / remove-ts-attribute / modify-ts-attribute
Операции над реквизитами **внутри существующей ТЧ**. Формат: `ИмяТЧ.ОпределениеРеквизита` (dot-нотация).
```powershell
# Добавить реквизит в ТЧ
-Operation add-ts-attribute -Value "Товары.СтавкаНДС: EnumRef.СтавкиНДС"
-Operation add-ts-attribute -Value "Товары.Скидка: Число(15,2) ;; Товары.Бонус: Число(15,2)"
# Позиционная вставка в ТЧ
-Operation add-ts-attribute -Value "Товары.Скидка: Число(15,2) >> after Цена"
# Удалить реквизит из ТЧ
-Operation remove-ts-attribute -Value "Товары.УстаревшийРекв"
-Operation remove-ts-attribute -Value "Товары.Рекв1 ;; Товары.Рекв2"
# Изменить реквизит в ТЧ (rename, type change и т.д.)
-Operation modify-ts-attribute -Value "Товары.СтароеИмя: name=НовоеИмя, type=Строка(500)"
```
Batch через `;;` — можно указать разные ТЧ: `"Товары.А: Строка(50) ;; Услуги.Б: Число(10)"`.
## modify-ts
Изменение свойств **самой табличной части** (Synonym, FillChecking, Use и др.):
```powershell
-Operation modify-ts -Value "Товары: synonym=Товарный состав"
-Operation modify-ts -Value "Товары: fillChecking=ShowError"
```
Формат аналогичен `modify-attribute`: `ИмяТЧ: ключ=значение, ключ=значение`.
## add-enumValue / add-form / add-template / add-command
Просто имена (batch через `;;`):
```powershell
-Operation add-enumValue -Value "Значение1 ;; Значение2 ;; Значение3"
-Operation add-form -Value "ФормаЭлемента ;; ФормаСписка"
-Operation add-template -Value "ПечатнаяФорма"
-Operation add-command -Value "Команда1"
```
## remove-*
Имя элемента (или несколько через `;;`):
```powershell
-Operation remove-attribute -Value "СтарыйРеквизит ;; ЕщёОдин"
-Operation remove-ts -Value "УстаревшаяТЧ"
-Operation remove-enumValue -Value "НеиспользуемоеЗначение"
```
## modify-attribute / modify-dimension / modify-resource / modify-enumValue / modify-column
Формат: `ИмяЭлемента: ключ=значение, ключ=значение`
Ключи: `name` (rename), `type`, `synonym`, `indexing`, `fillChecking`, `use` и др.
```powershell
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=Строка(500)"
-Operation modify-attribute -Value "Комментарий: indexing=Index"
-Operation modify-enumValue -Value "СтароеЗначение: name=НовоеЗначение"
```
+135
View File
@@ -0,0 +1,135 @@
# JSON DSL — режим определений
Для сложных и комбинированных операций используйте JSON-файл вместо inline-режима.
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -DefinitionFile "<json>" -ObjectPath "<path>"
```
## add — добавить элементы
```json
{
"add": {
"attributes": [
{ "name": "Комментарий", "type": "Строка(200)" },
{ "name": "Сумма", "type": "Число(15,2)", "indexing": "Index" }
],
"tabularSections": [{
"name": "Товары",
"attrs": [
{ "name": "Номенклатура", "type": "CatalogRef.Номенклатура" },
{ "name": "Количество", "type": "Число(15,3)" }
]
}],
"forms": ["ФормаЭлемента"],
"templates": ["ПечатнаяФорма"]
}
}
```
Реквизиты можно задавать shorthand-строками: `"Сумма: Число(15,2) | req, index"`.
## remove — удалить элементы
```json
{
"remove": {
"attributes": ["СтарыйРеквизит"],
"tabularSections": ["УстаревшаяТЧ"]
}
}
```
## modify — изменить существующие
```json
{
"modify": {
"properties": {
"CodeLength": 11,
"Hierarchical": true,
"Owners": ["Catalog.Контрагенты", "Catalog.Организации"],
"RegisterRecords": ["AccumulationRegister.Продажи"],
"InputByString": ["StandardAttribute.Description"]
},
"attributes": {
"Комментарий": { "type": "Строка(500)" },
"СтароеИмя": { "name": "НовоеИмя" }
}
}
}
```
## modify — реквизиты внутри ТЧ
```json
{
"modify": {
"tabularSections": {
"Товары": {
"add": ["СтавкаНДС: EnumRef.СтавкиНДС", "Скидка: Число(15,2)"],
"remove": ["УстаревшийРекв"],
"modify": {
"СтароеИмя": { "name": "НовоеИмя", "type": "Строка(500)" }
}
}
}
}
}
```
## Комбинирование
Все три операции (`add`, `remove`, `modify`) можно указать в одном JSON-файле:
```json
{
"add": { "tabularSections": [{ "name": "НоваяТЧ", "attrs": ["Имя: Строка(100)"] }] },
"modify": {
"tabularSections": {
"СуществующаяТЧ": {
"add": ["НовыйРекв: Число(15,2)"],
"remove": ["СтарыйРекв"]
}
}
}
}
```
## Позиционная вставка
```json
{ "name": "Склад", "type": "CatalogRef.Склады", "after": "Организация" }
```
## Синонимы ключей (case-insensitive)
**Операции:** `add`/`добавить`, `remove`/`удалить`, `modify`/`изменить`
| Каноническое | Синонимы |
|-------------|----------|
| attributes | реквизиты, attrs |
| tabularSections | табличныеЧасти, тч, ts |
| dimensions | измерения, dims |
| resources | ресурсы, res |
| enumValues | значения, values |
| columns | графы, колонки |
| forms | формы |
| templates | макеты |
| commands | команды |
| properties | свойства |
## Синонимы типов
`Строка(200)`, `Число(15,2)`, `Булево`, `Дата`, `ДатаВремя`, `ХранилищеЗначения`, `СправочникСсылка.XXX`, `ДокументСсылка.XXX`, `ПеречислениеСсылка.XXX`, `ОпределяемыйТип.XXX`.
## Поддерживаемые типы объектов
| Тип объекта | Допустимые add-типы |
|-------------|-------------------|
| Catalog, Document, ExchangePlan, ChartOf*, BP, Task, Report, DP | attributes, tabularSections, forms, templates, commands |
| Enum | enumValues, forms, templates, commands |
| *Register (4 типа) | dimensions, resources, attributes, forms, templates, commands |
| DocumentJournal | columns, forms, templates, commands |
| Constant | forms |
@@ -0,0 +1,54 @@
# Свойства объекта и complex properties
Справочник операций для скалярных свойств объекта и свойств со вложенной XML-структурой (Owners, RegisterRecords, BasedOn, InputByString).
## modify-property
Изменение скалярных свойств объекта. Формат: `Ключ=Значение` (batch через `;;`):
```powershell
-Operation modify-property -Value "CodeLength=11 ;; DescriptionLength=150"
-Operation modify-property -Value "Hierarchical=true"
```
## Complex properties
Свойства со вложенной XML-структурой. Поддерживаются через inline `add-*` / `remove-*` / `set-*` и через JSON `modify.properties`.
| Свойство | Объекты | Inline-значение |
|----------|---------|-----------------|
| Owners | Catalog, ChartOfCharacteristicTypes | `Catalog.XXX` |
| RegisterRecords | Document | `AccumulationRegister.XXX` |
| BasedOn | Document, Catalog, BP, Task | `Document.XXX` |
| InputByString | Catalog, ChartOf*, Task | `StandardAttribute.Description` |
### add-owner / add-registerRecord / add-basedOn
Полное имя метаданных `MetaType.Name`:
```powershell
-Operation add-owner -Value "Catalog.Контрагенты ;; Catalog.Организации"
-Operation add-registerRecord -Value "AccumulationRegister.ОстаткиТоваров"
-Operation add-basedOn -Value "Document.ЗаказКлиента"
```
### add-inputByString
Пути полей (префикс `MetaType.Name.` добавляется автоматически):
```powershell
-Operation add-inputByString -Value "StandardAttribute.Description ;; StandardAttribute.Code"
```
### remove-owner / remove-registerRecord / remove-basedOn / remove-inputByString
```powershell
-Operation remove-owner -Value "Catalog.Контрагенты"
-Operation remove-inputByString -Value "Catalog.МойСпр.StandardAttribute.Code"
```
### set-owners / set-registerRecords / set-basedOn / set-inputByString
Заменяют **весь список** (в отличие от add/remove):
```powershell
-Operation set-owners -Value "Catalog.Организации ;; Catalog.Контрагенты"
-Operation set-registerRecords -Value "AccumulationRegister.Продажи ;; AccumulationRegister.ОстаткиТоваров"
-Operation set-inputByString -Value "StandardAttribute.Description ;; StandardAttribute.Code"
```
File diff suppressed because it is too large Load Diff
+87
View File
@@ -0,0 +1,87 @@
---
name: meta-info
description: Анализ структуры объекта метаданных 1С из XML-выгрузки — реквизиты, табличные части, формы, движения, типы. Используй для изучения структуры объектов и как подготовительный шаг при написании запросов и кода, работающего с объектами
argument-hint: <ObjectPath> [-Mode overview|brief|full] [-Name <элемент>]
allowed-tools:
- Bash
- Read
- Glob
---
# /meta-info — Структура объекта метаданных 1С
Читает XML объекта метаданных из выгрузки конфигурации 1С и выводит компактное описание структуры.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `ObjectPath` | Путь к XML-файлу объекта или каталогу (авто-резолв `<name>/<name>.xml`) |
| `Mode` | Режим: `overview` (default), `brief`, `full` |
| `Name` | Drill-down по имени элемента (реквизит, ТЧ, значение перечисления, шаблон URL, операция) |
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-info\scripts\meta-info.ps1 -ObjectPath "<путь>"
```
## Три режима
| Режим | Что показывает |
|---|---|
| `overview` *(default)* | Заголовок + ключевые свойства + структура без раскрытия деталей |
| `brief` | Всё одной-двумя строками: имена полей, счётчики |
| `full` | Всё раскрыто: колонки ТЧ, список источников подписки, движения, формы |
`-Name` — drill-down: раскрыть конкретный элемент объекта (ТЧ, реквизит, шаблон URL, операцию веб-сервиса).
## Поддерживаемые типы (23)
**Ссылочные:** Справочник, Документ, Перечисление, Бизнес-процесс, Задача, План обмена, План счетов, ПВХ, ПВР
**Регистры:** Регистр сведений, Регистр накопления, Регистр бухгалтерии, Регистр расчёта
**Сервисные:** Отчёт, Обработка, HTTP-сервис, Веб-сервис, Общий модуль, Регламентное задание, Подписка на событие
**Прочие:** Константа, Журнал документов, Определяемый тип
## Примеры
```powershell
# Справочник — overview
... -ObjectPath Catalogs/Валюты/Валюты.xml
# Документ — полная сводка с колонками ТЧ, движениями, формами
... -ObjectPath Documents/АвансовыйОтчет/АвансовыйОтчет.xml -Mode full
# Регистр сведений — краткая сводка
... -ObjectPath InformationRegisters/КурсыВалют/КурсыВалют.xml -Mode brief
# Drill-down в ТЧ документа
... -ObjectPath Documents/АвансовыйОтчет/АвансовыйОтчет.xml -Name Товары
# Drill-down в реквизит
... -ObjectPath Catalogs/Валюты/Валюты.xml -Name ОсновнаяВалюта
# Общий модуль — флаги контекста и повторное использование
... -ObjectPath CommonModules/ОбщегоНазначения/ОбщегоНазначения.xml
# HTTP-сервис — шаблоны URL и методы
... -ObjectPath HTTPServices/ExternalAPI/ExternalAPI.xml
# HTTP-сервис — drill-down в шаблон URL
... -ObjectPath HTTPServices/ExternalAPI/ExternalAPI.xml -Name АктуальныеЗадачи
# Веб-сервис — операции с параметрами
... -ObjectPath WebServices/EnterpriseDataUpload_1_0_1_1/EnterpriseDataUpload_1_0_1_1.xml
# Веб-сервис — drill-down в операцию
... -ObjectPath WebServices/EnterpriseDataUpload_1_0_1_1/EnterpriseDataUpload_1_0_1_1.xml -Name TestConnection
# Подписка на событие — full раскрывает список источников
... -ObjectPath EventSubscriptions/ПолныйРегистрацияУдаления/ПолныйРегистрацияУдаления.xml -Mode full
# Регламентное задание
... -ObjectPath ScheduledJobs/АвтоматическоеЗакрытиеМесяца/АвтоматическоеЗакрытиеМесяца.xml
# Определяемый тип
... -ObjectPath DefinedTypes/GLN/GLN.xml
```
File diff suppressed because it is too large Load Diff
+112
View File
@@ -0,0 +1,112 @@
---
name: meta-validate
description: Валидация объекта метаданных 1С. Используй после создания или модификации объекта конфигурации для проверки корректности
argument-hint: <ObjectPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /meta-validate — валидация объекта метаданных 1С
Проверяет XML объекта метаданных из выгрузки конфигурации на структурные ошибки: корневую структуру, InternalInfo, свойства, допустимые значения, StandardAttributes, ChildObjects, уникальность имён, табличные части, кросс-свойства, вложенные структуры HTTP/Web-сервисов.
## Использование
```
/meta-validate <ObjectPath>
```
## Параметры
| Параметр | Обязательный | По умолчанию | Описание |
|------------|:------------:|--------------|-------------------------------------------------|
| ObjectPath | да | — | Путь к XML-файлу или каталогу объекта |
| MaxErrors | нет | 30 | Остановиться после N ошибок |
| OutFile | нет | — | Записать результат в файл (UTF-8 BOM) |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>/<dirName>.xml`.
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\meta-validate\scripts\meta-validate.ps1 -ObjectPath "<путь>"
```
## Поддерживаемые типы (23)
**Ссылочные:** Catalog, Document, Enum, ExchangePlan, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task
**Регистры:** InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister
**Отчёты/Обработки:** Report, DataProcessor
**Сервисные:** CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService
**Прочие:** Constant, DocumentJournal, DefinedType
## Выполняемые проверки
| # | Проверка | Серьёзность |
|----|------------------------------------------|--------------|
| 1 | XML well-formedness + root structure | ERROR |
| 2 | InternalInfo / GeneratedType | ERROR / WARN |
| 3 | Properties — Name, Synonym | ERROR / WARN |
| 4 | Properties — enum-значения свойств | ERROR |
| 5 | StandardAttributes | ERROR / WARN |
| 6 | ChildObjects — допустимые элементы | ERROR |
| 7 | Attributes/Dimensions/Resources — UUID, Name, Type | ERROR |
| 8 | Уникальность имён | ERROR |
| 9 | TabularSections — внутренняя структура | ERROR / WARN |
| 10 | Кросс-свойства | ERROR / WARN |
| 11 | HTTPService/WebService — вложенная структура | ERROR |
## Вывод
```
=== Validation: Catalog.Номенклатура ===
[OK] 1. Root structure: MetaDataObject/Catalog, version 2.17
[OK] 2. InternalInfo: 5 GeneratedType (Object, Ref, Selection, List, Manager)
[OK] 3. Properties: Name="Номенклатура", Synonym present
[OK] 4. Property values: 12 enum properties checked
[ERROR] 5. StandardAttributes: missing "PredefinedDataName"
[OK] 6. ChildObjects types: Attribute(15), TabularSection(3), Form(4)
[OK] 7. Attributes/Dimensions: all valid
[WARN] 8. Name uniqueness: duplicate attribute "Комментарий" at positions 5, 12
[OK] 9. TabularSections: 3 sections, structure valid
[OK] 10. Cross-property consistency
[OK] 11. N/A (not HTTPService/WebService)
---
Errors: 1, Warnings: 1
```
Код возврата: 0 = все проверки пройдены, 1 = есть ошибки.
## Примеры
```powershell
# Справочник из выгрузки конфигурации
... -ObjectPath upload/acc_8.3.24/Catalogs/Банки/Банки.xml
# Авторезолв из директории
... -ObjectPath upload/acc_8.3.24/Documents/АвансовыйОтчет
# С лимитом ошибок
... -ObjectPath Catalogs/Номенклатура.xml -MaxErrors 10
# С записью в файл
... -ObjectPath Catalogs/Номенклатура.xml -OutFile result.txt
```
## Верификация
```
/meta-compile <JsonPath> <OutputDir> — генерация XML
/meta-validate <OutputDir>/<Type>/<Name>.xml — проверка результата
/meta-info <OutputDir>/<Type>/<Name>.xml — визуальная сводка
```
## Когда использовать
- **После `/meta-compile`**: проверить корректность сгенерированного XML
- **После ручного редактирования**: убедиться что структура не нарушена
- **После merge/импорта**: выявить конфликты и битые ссылки
- **При отладке**: найти структурные ошибки до сборки EPF
@@ -0,0 +1,988 @@
# meta-validate v1.0 — Validate 1C metadata object structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ObjectPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($ObjectPath)) {
$ObjectPath = Join-Path (Get-Location).Path $ObjectPath
}
if (Test-Path $ObjectPath -PathType Container) {
$dirName = Split-Path $ObjectPath -Leaf
$candidate = Join-Path $ObjectPath "$dirName.xml"
if (Test-Path $candidate) {
$ObjectPath = $candidate
} else {
$xmlFiles = @(Get-ChildItem $ObjectPath -Filter "*.xml" -File | Select-Object -First 1)
if ($xmlFiles.Count -gt 0) {
$ObjectPath = $xmlFiles[0].FullName
} else {
Write-Host "[ERROR] No XML file found in directory: $ObjectPath"
exit 1
}
}
}
if (-not (Test-Path $ObjectPath)) {
Write-Host "[ERROR] File not found: $ObjectPath"
exit 1
}
$resolvedPath = (Resolve-Path $ObjectPath).Path
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line {
param([string]$msg)
$script:output.AppendLine($msg) | Out-Null
}
function Report-OK {
param([string]$msg)
Out-Line "[OK] $msg"
}
function Report-Error {
param([string]$msg)
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) {
$script:stopped = $true
}
}
function Report-Warn {
param([string]$msg)
$script:warnings++
Out-Line "[WARN] $msg"
}
$finalize = {
Out-Line ""
Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ==="
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
$utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
}
# --- Reference tables ---
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
$validTypes = @(
"Catalog","Document","Enum","Constant",
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister",
"ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes",
"BusinessProcess","Task","ExchangePlan","DocumentJournal",
"Report","DataProcessor",
"CommonModule","ScheduledJob","EventSubscription",
"HTTPService","WebService","DefinedType"
)
# GeneratedType categories by type
$generatedTypeCategories = @{
"Catalog" = @("Object","Ref","Selection","List","Manager")
"Document" = @("Object","Ref","Selection","List","Manager")
"Enum" = @("Ref","Manager","List")
"Constant" = @("Manager","ValueManager","ValueKey")
"InformationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","RecordManager")
"AccumulationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey")
"AccountingRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","ExtDimensions")
"CalculationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","Recalcs")
"ChartOfAccounts" = @("Object","Ref","Selection","List","Manager","ExtDimensionTypes","ExtDimensionTypesRow")
"ChartOfCharacteristicTypes" = @("Object","Ref","Selection","List","Manager","Characteristic")
"ChartOfCalculationTypes" = @("Object","Ref","Selection","List","Manager","DisplacingCalculationTypes","DisplacingCalculationTypesRow","BaseCalculationTypes","BaseCalculationTypesRow","LeadingCalculationTypes","LeadingCalculationTypesRow")
"BusinessProcess" = @("Object","Ref","Selection","List","Manager","RoutePointRef")
"Task" = @("Object","Ref","Selection","List","Manager")
"ExchangePlan" = @("Object","Ref","Selection","List","Manager")
"DocumentJournal" = @("Selection","List","Manager")
"Report" = @("Object","Manager")
"DataProcessor" = @("Object","Manager")
"DefinedType" = @("DefinedType")
}
# Types that have NO InternalInfo / GeneratedType
$typesWithoutInternalInfo = @("CommonModule","ScheduledJob","EventSubscription")
# StandardAttributes by type
$standardAttributesByType = @{
"Catalog" = @("PredefinedDataName","Predefined","Ref","DeletionMark","IsFolder","Owner","Parent","Description","Code")
"Document" = @("Posted","Ref","DeletionMark","Date","Number")
"Enum" = @("Order","Ref")
"InformationRegister" = @("Active","LineNumber","Recorder","Period")
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period","RecordType")
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account")
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry","ActionPeriod","BegOfActionPeriod","EndOfActionPeriod","BegOfBasePeriod","EndOfBasePeriod")
"ChartOfAccounts" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","Order","Type","OffBalance")
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","IsFolder","ValueType")
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","ActionPeriodIsBasic")
"BusinessProcess" = @("Ref","DeletionMark","Date","Number","Started","Completed","HeadTask")
"Task" = @("Ref","DeletionMark","Date","Number","Executed","Description","RoutePoint","BusinessProcess")
"ExchangePlan" = @("Ref","DeletionMark","Code","Description","ThisNode","SentNo","ReceivedNo")
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
}
# Types that have StandardAttributes block
$typesWithStdAttrs = @(
"Catalog","Document","Enum",
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister",
"ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes",
"BusinessProcess","Task","ExchangePlan","DocumentJournal"
)
# ChildObjects rules: what child element types are valid for each metadata type
$childObjectRules = @{
"Catalog" = @("Attribute","TabularSection","Form","Template","Command")
"Document" = @("Attribute","TabularSection","Form","Template","Command")
"ExchangePlan" = @("Attribute","TabularSection","Form","Template","Command")
"ChartOfAccounts" = @("Attribute","TabularSection","Form","Template","Command","AccountingFlag","ExtDimensionAccountingFlag")
"ChartOfCharacteristicTypes" = @("Attribute","TabularSection","Form","Template","Command")
"ChartOfCalculationTypes" = @("Attribute","TabularSection","Form","Template","Command")
"BusinessProcess" = @("Attribute","TabularSection","Form","Template","Command")
"Task" = @("Attribute","TabularSection","Form","Template","Command","AddressingAttribute")
"Report" = @("Attribute","TabularSection","Form","Template","Command")
"DataProcessor" = @("Attribute","TabularSection","Form","Template","Command")
"Enum" = @("EnumValue","Form","Template","Command")
"InformationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
"AccumulationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
"AccountingRegister" = @("Dimension","Resource","Attribute","Form","Template","Command")
"CalculationRegister" = @("Dimension","Resource","Attribute","Form","Template","Command","Recalculation")
"DocumentJournal" = @("Column","Form","Template","Command")
"HTTPService" = @("URLTemplate")
"WebService" = @("Operation")
"Constant" = @("Form")
"DefinedType" = @()
"CommonModule" = @()
"ScheduledJob" = @()
"EventSubscription" = @()
}
# Valid enum property values
$validPropertyValues = @{
"CodeType" = @("String","Number")
"CodeAllowedLength" = @("Variable","Fixed")
"NumberType" = @("String","Number")
"NumberAllowedLength" = @("Variable","Fixed")
"Posting" = @("Allow","Deny")
"RealTimePosting" = @("Allow","Deny")
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
"DataLockControlMode" = @("Automatic","Managed")
"FullTextSearch" = @("Use","DontUse")
"DefaultPresentation" = @("AsDescription","AsCode")
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
"EditType" = @("InDialog","InList","BothWays")
"WriteMode" = @("Independent","RecorderSubordinate")
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
"RegisterType" = @("Balance","Turnovers")
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
"ReuseSessions" = @("DontUse","AutoUse")
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
"DataHistory" = @("Use","DontUse")
}
# --- 1. Parse XML ---
Out-Line ""
$xmlDoc = $null
try {
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $false
$xmlDoc.Load($resolvedPath)
} catch {
Out-Line "=== Validation: (parse failed) ==="
Out-Line ""
Report-Error "1. XML parse failed: $($_.Exception.Message)"
& $finalize
exit 1
}
# --- 2. Register namespaces ---
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$ns.AddNamespace("xs", "http://www.w3.org/2001/XMLSchema")
$ns.AddNamespace("cfg", "http://v8.1c.ru/8.1/data/enterprise/current-config")
$root = $xmlDoc.DocumentElement
# --- Check 1: Root structure ---
$check1Ok = $true
# Root must be MetaDataObject
if ($root.LocalName -ne "MetaDataObject") {
Report-Error "1. Root element is '$($root.LocalName)', expected 'MetaDataObject'"
& $finalize
exit 1
}
$expectedNs = "http://v8.1c.ru/8.3/MDClasses"
if ($root.NamespaceURI -ne $expectedNs) {
Report-Error "1. Root namespace is '$($root.NamespaceURI)', expected '$expectedNs'"
$check1Ok = $false
}
# Version attribute
$version = $root.GetAttribute("version")
if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
}
# Detect type element — exactly one child element in md namespace
$typeNode = $null
$mdType = ""
$childElements = @()
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.NamespaceURI -eq $expectedNs) {
$childElements += $child
}
}
if ($childElements.Count -eq 0) {
Report-Error "1. No metadata type element found inside MetaDataObject"
& $finalize
exit 1
} elseif ($childElements.Count -gt 1) {
Report-Error "1. Multiple type elements found: $($childElements | ForEach-Object { $_.LocalName })"
$check1Ok = $false
}
$typeNode = $childElements[0]
$mdType = $typeNode.LocalName
if ($validTypes -notcontains $mdType) {
Report-Error "1. Unrecognized metadata type: $mdType"
& $finalize
exit 1
}
# UUID on type element
$typeUuid = $typeNode.GetAttribute("uuid")
if (-not $typeUuid) {
Report-Error "1. Missing uuid on <$mdType> element"
$check1Ok = $false
} elseif ($typeUuid -notmatch $guidPattern) {
Report-Error "1. Invalid uuid '$typeUuid' on <$mdType>"
$check1Ok = $false
}
# Get object name early for header
$propsNode = $typeNode.SelectSingleNode("md:Properties", $ns)
$nameNode = if ($propsNode) { $propsNode.SelectSingleNode("md:Name", $ns) } else { $null }
$objName = if ($nameNode -and $nameNode.InnerText) { $nameNode.InnerText } else { "(unknown)" }
# Now emit header
$script:output.Insert(0, "=== Validation: $mdType.$objName ===$([Environment]::NewLine)") | Out-Null
if ($check1Ok) {
Report-OK "1. Root structure: MetaDataObject/$mdType, version $version"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 2: InternalInfo ---
$internalInfo = $typeNode.SelectSingleNode("md:InternalInfo", $ns)
if ($typesWithoutInternalInfo -contains $mdType) {
# These types should NOT have InternalInfo with GeneratedType
if ($internalInfo) {
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
if ($genTypes.Count -gt 0) {
Report-Warn "2. InternalInfo: $mdType should not have GeneratedType entries, found $($genTypes.Count)"
} else {
Report-OK "2. InternalInfo: absent or empty (correct for $mdType)"
}
} else {
Report-OK "2. InternalInfo: absent (correct for $mdType)"
}
} elseif ($generatedTypeCategories.ContainsKey($mdType)) {
$expectedCategories = $generatedTypeCategories[$mdType]
if (-not $internalInfo) {
Report-Error "2. InternalInfo: missing (expected $($expectedCategories.Count) GeneratedType)"
} else {
$genTypes = $internalInfo.SelectNodes("xr:GeneratedType", $ns)
$check2Ok = $true
$foundCategories = @()
foreach ($gt in $genTypes) {
$gtName = $gt.GetAttribute("name")
$gtCategory = $gt.GetAttribute("category")
$foundCategories += $gtCategory
# Validate name format: Prefix.ObjectName
if ($gtName -and $objName -ne "(unknown)") {
if (-not $gtName.EndsWith(".$objName")) {
Report-Error "2. GeneratedType name '$gtName' does not end with '.$objName'"
$check2Ok = $false
}
}
# Validate category
if ($expectedCategories -notcontains $gtCategory) {
Report-Warn "2. Unexpected GeneratedType category '$gtCategory' for $mdType"
}
# Validate TypeId and ValueId UUIDs
$typeId = $gt.SelectSingleNode("xr:TypeId", $ns)
$valueId = $gt.SelectSingleNode("xr:ValueId", $ns)
if ($typeId -and $typeId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid TypeId UUID in GeneratedType '$gtCategory'"
$check2Ok = $false
}
if ($valueId -and $valueId.InnerText -notmatch $guidPattern) {
Report-Error "2. Invalid ValueId UUID in GeneratedType '$gtCategory'"
$check2Ok = $false
}
}
# ExchangePlan: check for ThisNode
if ($mdType -eq "ExchangePlan") {
$thisNode = $internalInfo.SelectSingleNode("xr:ThisNode", $ns)
if (-not $thisNode) {
Report-Warn "2. ExchangePlan missing xr:ThisNode in InternalInfo"
} elseif ($thisNode.InnerText -notmatch $guidPattern) {
Report-Error "2. ExchangePlan xr:ThisNode has invalid UUID"
$check2Ok = $false
}
}
# Check count mismatch
$missingCats = @($expectedCategories | Where-Object { $foundCategories -notcontains $_ })
if ($missingCats.Count -gt 0) {
Report-Warn "2. Missing GeneratedType categories: $($missingCats -join ', ')"
}
if ($check2Ok) {
$catList = ($foundCategories | Sort-Object) -join ", "
Report-OK "2. InternalInfo: $($genTypes.Count) GeneratedType ($catList)"
}
}
} else {
Report-OK "2. InternalInfo: N/A for $mdType"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 3: Properties — Name, Synonym ---
if (-not $propsNode) {
Report-Error "3. Properties block missing"
} else {
$check3Ok = $true
# Name
if (-not $nameNode -or -not $nameNode.InnerText) {
Report-Error "3. Properties: Name is missing or empty"
$check3Ok = $false
} else {
$nameVal = $nameNode.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "3. Properties: Name '$nameVal' is not a valid 1C identifier"
$check3Ok = $false
}
if ($nameVal.Length -gt 80) {
Report-Warn "3. Properties: Name '$nameVal' is longer than 80 characters ($($nameVal.Length))"
}
}
# Synonym
$synNode = $propsNode.SelectSingleNode("md:Synonym", $ns)
$synPresent = $false
if ($synNode) {
$synItem = $synNode.SelectSingleNode("v8:item", $ns)
if ($synItem) {
$synContent = $synItem.SelectSingleNode("v8:content", $ns)
if ($synContent -and $synContent.InnerText) {
$synPresent = $true
}
}
}
if ($check3Ok) {
$synInfo = if ($synPresent) { "Synonym present" } else { "no Synonym" }
Report-OK "3. Properties: Name=`"$objName`", $synInfo"
}
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 4: Property values — enum properties ---
if ($propsNode) {
$enumChecked = 0
$check4Ok = $true
foreach ($propName in $validPropertyValues.Keys) {
$propNode = $propsNode.SelectSingleNode("md:$propName", $ns)
if ($propNode -and $propNode.InnerText) {
$val = $propNode.InnerText
$allowed = $validPropertyValues[$propName]
if ($allowed -notcontains $val) {
Report-Error "4. Property '$propName' has invalid value '$val' (allowed: $($allowed -join ', '))"
$check4Ok = $false
}
$enumChecked++
}
}
if ($check4Ok) {
Report-OK "4. Property values: $enumChecked enum properties checked"
}
} else {
Report-Warn "4. No Properties block to check"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 5: StandardAttributes ---
if ($typesWithStdAttrs -contains $mdType) {
$stdAttrNode = $propsNode.SelectSingleNode("md:StandardAttributes", $ns)
if (-not $stdAttrNode) {
# StandardAttributes block is optional for some types (e.g. Enum)
Report-OK "5. StandardAttributes: absent (optional for $mdType)"
} else {
$stdAttrs = $stdAttrNode.SelectNodes("xr:StandardAttribute", $ns)
$expectedStdAttrs = $standardAttributesByType[$mdType]
$check5Ok = $true
$foundNames = @()
foreach ($sa in $stdAttrs) {
$saName = $sa.GetAttribute("name")
if ($saName) {
$foundNames += $saName
if ($expectedStdAttrs -notcontains $saName) {
# AccountingRegister has dynamic ExtDimension{N}/ExtDimensionType{N} and optional PeriodAdjustment
$isDynamic = ($mdType -eq "AccountingRegister" -and ($saName -match '^ExtDimension\d+$' -or $saName -match '^ExtDimensionType\d+$' -or $saName -eq "PeriodAdjustment"))
# CalculationRegister has conditional period attrs
$isCalcDynamic = ($mdType -eq "CalculationRegister" -and $saName -in @("ActionPeriod","BegOfActionPeriod","EndOfActionPeriod","BegOfBasePeriod","EndOfBasePeriod"))
if (-not $isDynamic -and -not $isCalcDynamic) {
Report-Warn "5. Unexpected StandardAttribute '$saName' for $mdType"
}
}
} else {
Report-Error "5. StandardAttribute without 'name' attribute"
$check5Ok = $false
}
}
if ($expectedStdAttrs) {
$missingAttrs = @($expectedStdAttrs | Where-Object { $foundNames -notcontains $_ })
if ($missingAttrs.Count -gt 0) {
Report-Warn "5. Missing StandardAttributes: $($missingAttrs -join ', ')"
}
}
if ($check5Ok) {
Report-OK "5. StandardAttributes: $($stdAttrs.Count) entries"
}
}
} else {
Report-OK "5. StandardAttributes: N/A for $mdType"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 6: ChildObjects — allowed element types ---
$childObjNode = $typeNode.SelectSingleNode("md:ChildObjects", $ns)
$allowedChildren = $childObjectRules[$mdType]
if ($childObjNode) {
$check6Ok = $true
$childCounts = @{}
foreach ($child in $childObjNode.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
$childTag = $child.LocalName
if ($allowedChildren -notcontains $childTag) {
Report-Error "6. ChildObjects: disallowed element '$childTag' for $mdType"
$check6Ok = $false
}
if (-not $childCounts.ContainsKey($childTag)) {
$childCounts[$childTag] = 0
}
$childCounts[$childTag]++
}
if ($check6Ok) {
$summary = ($childCounts.GetEnumerator() | Sort-Object Name | ForEach-Object { "$($_.Name)($($_.Value))" }) -join ", "
if ($summary) {
Report-OK "6. ChildObjects types: $summary"
} else {
Report-OK "6. ChildObjects: empty (valid for $mdType)"
}
}
} elseif ($allowedChildren.Count -eq 0) {
Report-OK "6. ChildObjects: absent (correct for $mdType)"
} else {
# Some types may have no children — that's OK
Report-OK "6. ChildObjects: absent"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 7: Attributes/Dimensions/Resources/EnumValues/Columns — UUID, Name, Type ---
function Check-ChildElement {
param(
[System.Xml.XmlNode]$node,
[string]$kind,
[bool]$requireType
)
$uuid = $node.GetAttribute("uuid")
if (-not $uuid) {
Report-Error "7. $kind missing uuid"
return $false
} elseif ($uuid -notmatch $guidPattern) {
Report-Error "7. $kind has invalid uuid '$uuid'"
return $false
}
$elProps = $node.SelectSingleNode("md:Properties", $ns)
if (-not $elProps) {
Report-Error "7. $kind (uuid=$uuid) missing Properties"
return $false
}
$elName = $elProps.SelectSingleNode("md:Name", $ns)
if (-not $elName -or -not $elName.InnerText) {
Report-Error "7. $kind (uuid=$uuid) missing or empty Name"
return $false
}
$nameVal = $elName.InnerText
if ($nameVal -notmatch $identPattern) {
Report-Error "7. $kind '$nameVal' has invalid identifier"
return $false
}
if ($requireType) {
$typeEl = $elProps.SelectSingleNode("md:Type", $ns)
if (-not $typeEl) {
Report-Error "7. $kind '$nameVal' missing Type block"
return $false
}
$v8Types = $typeEl.SelectNodes("v8:Type", $ns)
$v8TypeSets = $typeEl.SelectNodes("v8:TypeSet", $ns)
if ($v8Types.Count -eq 0 -and $v8TypeSets.Count -eq 0) {
Report-Error "7. $kind '$nameVal' Type block has no v8:Type or v8:TypeSet"
return $false
}
}
return $true
}
if ($childObjNode) {
$check7Ok = $true
$check7Count = 0
$elementKinds = @("Attribute","Dimension","Resource","EnumValue","Column")
foreach ($kind in $elementKinds) {
$elements = $childObjNode.SelectNodes("md:$kind", $ns)
$requireType = ($kind -ne "EnumValue" -and $kind -ne "Column")
foreach ($el in $elements) {
if ($script:stopped) { break }
$ok = Check-ChildElement -node $el -kind $kind -requireType $requireType
if (-not $ok) { $check7Ok = $false }
$check7Count++
}
}
if ($check7Ok -and $check7Count -gt 0) {
Report-OK "7. Child elements: $check7Count items checked (UUID, Name, Type)"
} elseif ($check7Count -eq 0) {
Report-OK "7. Child elements: none to check"
}
} else {
Report-OK "7. Child elements: N/A (no ChildObjects)"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 8: Name uniqueness ---
function Check-Uniqueness {
param(
[System.Xml.XmlNodeList]$nodes,
[string]$kind
)
$names = @{}
$hasDupes = $false
foreach ($node in $nodes) {
$elProps = $node.SelectSingleNode("md:Properties", $ns)
if (-not $elProps) { continue }
$elName = $elProps.SelectSingleNode("md:Name", $ns)
if (-not $elName -or -not $elName.InnerText) { continue }
$nameVal = $elName.InnerText
if ($names.ContainsKey($nameVal)) {
Report-Error "8. Duplicate $kind name: '$nameVal'"
$hasDupes = $true
} else {
$names[$nameVal] = $true
}
}
return (-not $hasDupes)
}
if ($childObjNode) {
$check8Ok = $true
# Attributes
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns)
if ($attrs.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $attrs -kind "Attribute")) { $check8Ok = $false }
}
# TabularSections
$tss = $childObjNode.SelectNodes("md:TabularSection", $ns)
if ($tss.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $tss -kind "TabularSection")) { $check8Ok = $false }
}
# Dimensions
$dims = $childObjNode.SelectNodes("md:Dimension", $ns)
if ($dims.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $dims -kind "Dimension")) { $check8Ok = $false }
}
# Resources
$ress = $childObjNode.SelectNodes("md:Resource", $ns)
if ($ress.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $ress -kind "Resource")) { $check8Ok = $false }
}
# EnumValues
$evs = $childObjNode.SelectNodes("md:EnumValue", $ns)
if ($evs.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $evs -kind "EnumValue")) { $check8Ok = $false }
}
# Columns (DocumentJournal)
$cols = $childObjNode.SelectNodes("md:Column", $ns)
if ($cols.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $cols -kind "Column")) { $check8Ok = $false }
}
# URLTemplates (HTTPService)
$urlTs = $childObjNode.SelectNodes("md:URLTemplate", $ns)
if ($urlTs.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $urlTs -kind "URLTemplate")) { $check8Ok = $false }
}
# Operations (WebService)
$ops = $childObjNode.SelectNodes("md:Operation", $ns)
if ($ops.Count -gt 0) {
if (-not (Check-Uniqueness -nodes $ops -kind "Operation")) { $check8Ok = $false }
}
if ($check8Ok) {
Report-OK "8. Name uniqueness: all names unique"
}
} else {
Report-OK "8. Name uniqueness: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 9: TabularSections — internal structure ---
if ($childObjNode) {
$tsSections = $childObjNode.SelectNodes("md:TabularSection", $ns)
if ($tsSections.Count -gt 0) {
$check9Ok = $true
$tsCount = 0
foreach ($ts in $tsSections) {
if ($script:stopped) { break }
$tsCount++
# UUID
$tsUuid = $ts.GetAttribute("uuid")
if (-not $tsUuid -or $tsUuid -notmatch $guidPattern) {
Report-Error "9. TabularSection #${tsCount}: invalid or missing uuid"
$check9Ok = $false
}
# Name
$tsProps = $ts.SelectSingleNode("md:Properties", $ns)
$tsNameNode = if ($tsProps) { $tsProps.SelectSingleNode("md:Name", $ns) } else { $null }
$tsName = if ($tsNameNode) { $tsNameNode.InnerText } else { "(unnamed)" }
if (-not $tsNameNode -or -not $tsNameNode.InnerText) {
Report-Error "9. TabularSection #${tsCount}: missing or empty Name"
$check9Ok = $false
}
# InternalInfo with 2 GeneratedType (TabularSection + TabularSectionRow)
$tsIntInfo = $ts.SelectSingleNode("md:InternalInfo", $ns)
if ($tsIntInfo) {
$tsGens = $tsIntInfo.SelectNodes("xr:GeneratedType", $ns)
if ($tsGens.Count -lt 2) {
Report-Warn "9. TabularSection '$tsName': expected 2 GeneratedType, found $($tsGens.Count)"
}
}
# Attributes inside TS
$tsChildObj = $ts.SelectSingleNode("md:ChildObjects", $ns)
if ($tsChildObj) {
$tsAttrs = $tsChildObj.SelectNodes("md:Attribute", $ns)
$tsAttrNames = @{}
foreach ($ta in $tsAttrs) {
$taOk = Check-ChildElement -node $ta -kind "TabularSection '$tsName'.Attribute" -requireType $true
if (-not $taOk) { $check9Ok = $false }
# Check name uniqueness within TS
$taProps = $ta.SelectSingleNode("md:Properties", $ns)
$taName = if ($taProps) { $taProps.SelectSingleNode("md:Name", $ns) } else { $null }
if ($taName -and $taName.InnerText) {
if ($tsAttrNames.ContainsKey($taName.InnerText)) {
Report-Error "9. Duplicate attribute '$($taName.InnerText)' in TabularSection '$tsName'"
$check9Ok = $false
} else {
$tsAttrNames[$taName.InnerText] = $true
}
}
}
# StandardAttributes of TS: expect LineNumber
$tsStdAttr = $tsProps.SelectSingleNode("md:StandardAttributes", $ns)
if ($tsStdAttr) {
$tsStdAttrs = $tsStdAttr.SelectNodes("xr:StandardAttribute", $ns)
$hasLineNumber = $false
foreach ($tsa in $tsStdAttrs) {
if ($tsa.GetAttribute("name") -eq "LineNumber") { $hasLineNumber = $true }
}
if (-not $hasLineNumber) {
Report-Warn "9. TabularSection '$tsName': missing LineNumber StandardAttribute"
}
}
}
}
if ($check9Ok) {
Report-OK "9. TabularSections: $tsCount sections, structure valid"
}
} else {
Report-OK "9. TabularSections: none present"
}
} else {
Report-OK "9. TabularSections: N/A"
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 10: Cross-property consistency ---
$check10Ok = $true
$check10Issues = 0
if ($propsNode) {
# HierarchyType set but Hierarchical = false
$hierarchical = $propsNode.SelectSingleNode("md:Hierarchical", $ns)
$hierarchyType = $propsNode.SelectSingleNode("md:HierarchyType", $ns)
if ($hierarchical -and $hierarchyType -and $hierarchical.InnerText -eq "false" -and $hierarchyType.InnerText) {
Report-Warn "10. HierarchyType='$($hierarchyType.InnerText)' but Hierarchical=false"
$check10Issues++
}
# CommonModule: no context enabled
if ($mdType -eq "CommonModule") {
$contexts = @("Server","ClientManagedApplication","ClientOrdinaryApplication","ExternalConnection","ServerCall","Global")
$anyEnabled = $false
foreach ($ctx in $contexts) {
$ctxNode = $propsNode.SelectSingleNode("md:$ctx", $ns)
if ($ctxNode -and $ctxNode.InnerText -eq "true") {
$anyEnabled = $true
break
}
}
if (-not $anyEnabled) {
Report-Warn "10. CommonModule: no execution context enabled"
$check10Issues++
}
}
# EventSubscription: empty Handler
if ($mdType -eq "EventSubscription") {
$handler = $propsNode.SelectSingleNode("md:Handler", $ns)
if (-not $handler -or -not $handler.InnerText.Trim()) {
Report-Error "10. EventSubscription: empty Handler"
$check10Ok = $false
$check10Issues++
}
# Empty Source
$source = $propsNode.SelectSingleNode("md:Source", $ns)
$hasSource = $false
if ($source) {
$sourceTypes = $source.SelectNodes("v8:Type", $ns)
if ($sourceTypes.Count -gt 0) { $hasSource = $true }
}
if (-not $hasSource) {
Report-Warn "10. EventSubscription: no Source types specified"
$check10Issues++
}
}
# ScheduledJob: empty MethodName
if ($mdType -eq "ScheduledJob") {
$method = $propsNode.SelectSingleNode("md:MethodName", $ns)
if (-not $method -or -not $method.InnerText.Trim()) {
Report-Error "10. ScheduledJob: empty MethodName"
$check10Ok = $false
$check10Issues++
}
}
}
if ($check10Ok -and $check10Issues -eq 0) {
Report-OK "10. Cross-property consistency"
} elseif ($check10Ok) {
# Had warnings but no errors — already reported
}
if ($script:stopped) { & $finalize; exit 1 }
# --- Check 11: HTTPService/WebService nested structure ---
if ($mdType -eq "HTTPService" -and $childObjNode) {
$urlTemplates = $childObjNode.SelectNodes("md:URLTemplate", $ns)
$check11Ok = $true
$methodCount = 0
$validHTTPMethods = @("GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS","MERGE","CONNECT")
foreach ($ut in $urlTemplates) {
if ($script:stopped) { break }
$utProps = $ut.SelectSingleNode("md:Properties", $ns)
$utNameNode = if ($utProps) { $utProps.SelectSingleNode("md:Name", $ns) } else { $null }
$utName = if ($utNameNode) { $utNameNode.InnerText } else { "(unnamed)" }
# Template property
$tpl = if ($utProps) { $utProps.SelectSingleNode("md:Template", $ns) } else { $null }
if (-not $tpl -or -not $tpl.InnerText.Trim()) {
Report-Error "11. HTTPService URLTemplate '$utName': empty Template"
$check11Ok = $false
}
# Methods inside URLTemplate
$utChildObj = $ut.SelectSingleNode("md:ChildObjects", $ns)
if ($utChildObj) {
$methods = $utChildObj.SelectNodes("md:Method", $ns)
foreach ($m in $methods) {
$methodCount++
$mProps = $m.SelectSingleNode("md:Properties", $ns)
if ($mProps) {
$httpMethod = $mProps.SelectSingleNode("md:HTTPMethod", $ns)
if ($httpMethod -and $httpMethod.InnerText) {
if ($validHTTPMethods -notcontains $httpMethod.InnerText) {
Report-Error "11. HTTPService URLTemplate '$utName': invalid HTTPMethod '$($httpMethod.InnerText)'"
$check11Ok = $false
}
} else {
Report-Error "11. HTTPService URLTemplate '$utName': Method missing HTTPMethod"
$check11Ok = $false
}
}
}
}
}
if ($check11Ok) {
Report-OK "11. HTTPService: $($urlTemplates.Count) URLTemplate(s), $methodCount method(s)"
}
} elseif ($mdType -eq "WebService" -and $childObjNode) {
$operations = $childObjNode.SelectNodes("md:Operation", $ns)
$check11Ok = $true
$paramCount = 0
$validDirections = @("In","Out","InOut")
foreach ($op in $operations) {
if ($script:stopped) { break }
$opProps = $op.SelectSingleNode("md:Properties", $ns)
$opNameNode = if ($opProps) { $opProps.SelectSingleNode("md:Name", $ns) } else { $null }
$opName = if ($opNameNode) { $opNameNode.InnerText } else { "(unnamed)" }
# ReturnType — XDTOReturningValueType
$retType = if ($opProps) { $opProps.SelectSingleNode("md:XDTOReturningValueType", $ns) } else { $null }
if (-not $retType -or -not $retType.InnerText.Trim()) {
Report-Warn "11. WebService Operation '$opName': no XDTOReturningValueType"
}
# Parameters inside Operation
$opChildObj = $op.SelectSingleNode("md:ChildObjects", $ns)
if ($opChildObj) {
$params = $opChildObj.SelectNodes("md:Parameter", $ns)
foreach ($p in $params) {
$paramCount++
$pProps = $p.SelectSingleNode("md:Properties", $ns)
if ($pProps) {
$dir = $pProps.SelectSingleNode("md:TransferDirection", $ns)
if ($dir -and $dir.InnerText -and $validDirections -notcontains $dir.InnerText) {
Report-Error "11. WebService Operation '$opName': Parameter has invalid TransferDirection '$($dir.InnerText)'"
$check11Ok = $false
}
}
}
}
}
if ($check11Ok) {
Report-OK "11. WebService: $($operations.Count) operation(s), $paramCount parameter(s)"
}
} else {
Report-OK "11. HTTPService/WebService: N/A"
}
# --- Final output ---
& $finalize
if ($script:errors -gt 0) {
exit 1
}
exit 0
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: mxl-compile
description: Компиляция табличного документа (MXL) из JSON-определения
description: Компиляция табличного документа (MXL) из JSON-определения. Используй когда нужно создать макет печатной формы
argument-hint: <JsonPath> <OutputPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: mxl-decompile
description: Декомпиляция табличного документа (MXL) в JSON-определение
description: Декомпиляция табличного документа (MXL) в JSON-определение. Используй когда нужно получить редактируемое описание существующего макета
argument-hint: <TemplatePath> [OutputPath]
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: mxl-info
description: Анализ структуры макета табличного документа (MXL) — области, параметры, наборы колонок
description: Анализ структуры макета табличного документа (MXL) — области, параметры, наборы колонок. Используй при разработке печати — получить области и заполняемые параметры макета
argument-hint: <TemplatePath> или <ProcessorName> <TemplateName>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: mxl-validate
description: Валидация структурной корректности макета табличного документа (MXL)
description: Валидация макета табличного документа (MXL). Используй после создания или модификации макета для проверки корректности
argument-hint: <TemplatePath> или <ProcessorName> <TemplateName>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: role-compile
description: Создание роли 1С — метаданные и Rights.xml из описания прав
description: Создание роли 1С из описания прав. Используй когда нужно создать новую роль с набором прав на объекты
argument-hint: <JsonPath> <RolesDir>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: role-info
description: Компактная сводка прав роли 1С из Rights.xml — объекты, права, RLS, шаблоны ограничений
description: Компактная сводка прав роли 1С из Rights.xml — объекты, права, RLS, шаблоны ограничений. Используй для аудита прав — какие объекты и действия доступны, ограничения RLS
argument-hint: <RightsPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: role-validate
description: Валидация структурной корректности роли 1С (Rights.xml) — формат, права, RLS, шаблоны
description: Валидация роли 1С. Используй после создания или модификации роли для проверки корректности
argument-hint: <RightsPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: skd-compile
description: Компиляция схемы компоновки данных 1С (СКД) — Template.xml из компактного JSON-определения
description: Компиляция схемы компоновки данных 1С (СКД) из компактного JSON-определения. Используй когда нужно создать СКД с нуля
argument-hint: <JsonPath> <OutputPath>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: skd-edit
description: Точечное редактирование схемы компоновки данных 1С (СКД) — добавление/удаление полей, итогов, фильтров, параметров, вычисляемых полей
description: Точечное редактирование схемы компоновки данных 1С (СКД). Используй когда нужно модифицировать существующую СКД — добавить поля, итоги, фильтры, параметры
argument-hint: <TemplatePath> -Operation <op> -Value <value>
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: skd-info
description: Анализ структуры схемы компоновки данных 1С (СКД) — наборы, поля, параметры, варианты
description: Анализ структуры схемы компоновки данных 1С (СКД) — наборы, поля, параметры, варианты. Используй для понимания отчёта — источник данных (запрос), доступные поля, параметры
argument-hint: <TemplatePath> [-Mode overview|query|fields|links|calculated|resources|params|variant|templates|trace] [-Name <dataset|variant|field|group>]
allowed-tools:
- Bash
+1 -1
View File
@@ -1,6 +1,6 @@
---
name: skd-validate
description: Валидация структурной корректности схемы компоновки данных 1С (СКД) — Template.xml
description: Валидация схемы компоновки данных 1С (СКД). Используй после создания или модификации СКД для проверки корректности
argument-hint: <TemplatePath> [-MaxErrors 20]
allowed-tools:
- Bash
+65
View File
@@ -0,0 +1,65 @@
---
name: subsystem-compile
description: Создать подсистему 1С — XML-исходники из JSON-определения. Используй когда пользователь просит добавить подсистему (раздел) в конфигурацию
argument-hint: [-DefinitionFile <json> | -Value <json-string>] -OutputDir <ConfigDir> [-Parent <path>]
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /subsystem-compile — генерация подсистемы из JSON
Принимает JSON-определение подсистемы → генерирует XML + файловую структуру + регистрирует в родителе (Configuration.xml или родительская подсистема).
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `DefinitionFile` | Путь к JSON-файлу определения |
| `Value` | Инлайн JSON-строка (альтернатива DefinitionFile) |
| `OutputDir` | Корень выгрузки (где `Subsystems/`, `Configuration.xml`) |
| `Parent` | Путь к XML родительской подсистемы (для вложенных) |
| `NoValidate` | Пропустить авто-валидацию |
```powershell
powershell.exe -NoProfile -File '.claude\skills\subsystem-compile\scripts\subsystem-compile.ps1' -Value '<json>' -OutputDir '<ConfigDir>'
```
## JSON-определение
```json
{
"name": "МояПодсистема",
"synonym": "Моя подсистема",
"comment": "",
"includeInCommandInterface": true,
"useOneCommand": false,
"explanation": "Описание раздела",
"picture": "CommonPicture.МояКартинка",
"content": ["Catalog.Товары", "Document.Заказ"],
"children": ["ДочерняяА", "ДочерняяБ"]
}
```
Минимально: только `name`. Остальное — дефолты.
## Примеры
```powershell
# Минимальная подсистема
... -Value '{"name":"Тест"}' -OutputDir config/
# С составом и картинкой
... -Value '{"name":"Продажи","content":["Catalog.Товары","Report.Продажи"],"picture":"CommonPicture.Продажи"}' -OutputDir config/
# Вложенная подсистема
... -Value '{"name":"Дочерняя"}' -OutputDir config/ -Parent config/Subsystems/Продажи.xml
```
## Что генерируется
- `{OutputDir}/Subsystems/{Name}.xml` — определение подсистемы
- `{OutputDir}/Subsystems/{Name}/` — каталог (если есть children)
- `Configuration.xml` или родительская подсистема — регистрация в `<ChildObjects>`
@@ -0,0 +1,338 @@
# subsystem-compile v1.0 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
[string]$Value,
[Parameter(Mandatory)][string]$OutputDir,
[string]$Parent,
[switch]$NoValidate
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 1. Load JSON ---
if ($DefinitionFile -and $Value) {
Write-Error "Cannot use both -DefinitionFile and -Value"
exit 1
}
if (-not $DefinitionFile -and -not $Value) {
Write-Error "Either -DefinitionFile or -Value is required"
exit 1
}
if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
}
if (-not (Test-Path $DefinitionFile)) {
Write-Error "Definition file not found: $DefinitionFile"
exit 1
}
$json = Get-Content -Raw -Encoding UTF8 $DefinitionFile
} else {
$json = $Value
}
$def = $json | ConvertFrom-Json
if (-not $def.name) {
Write-Error "JSON must have 'name' field"
exit 1
}
$objName = "$($def.name)"
# Resolve OutputDir
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
$OutputDir = Join-Path (Get-Location).Path $OutputDir
}
# --- 2. XML helpers ---
$script:xml = New-Object System.Text.StringBuilder 8192
function X([string]$text) {
$script:xml.AppendLine($text) | Out-Null
}
function Esc-Xml([string]$s) {
return $s.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
function Split-CamelCase([string]$name) {
if (-not $name) { return $name }
$result = [regex]::Replace($name, '([a-z\u0430-\u044F\u0451])([A-Z\u0410-\u042F\u0401])', '$1 $2')
if ($result.Length -gt 1) {
$result = $result.Substring(0,1) + $result.Substring(1).ToLower()
}
return $result
}
function Emit-MLText([string]$indent, [string]$tag, [string]$text) {
if (-not $text) {
X "$indent<$tag/>"
return
}
X "$indent<$tag>"
X "$indent`t<v8:item>"
X "$indent`t`t<v8:lang>ru</v8:lang>"
X "$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
X "$indent`t</v8:item>"
X "$indent</$tag>"
}
function New-Guid-String {
return [System.Guid]::NewGuid().ToString()
}
# --- 3. Resolve defaults ---
$synonym = if ($def.synonym) { "$($def.synonym)" } else { Split-CamelCase $objName }
$comment = if ($def.comment) { "$($def.comment)" } else { "" }
$includeHelpInContents = "true"
$includeInCI = if ($null -ne $def.includeInCommandInterface) { "$($def.includeInCommandInterface)".ToLower() } else { "true" }
$useOneCommand = if ($null -ne $def.useOneCommand) { "$($def.useOneCommand)".ToLower() } else { "false" }
$explanation = if ($def.explanation) { "$($def.explanation)" } else { "" }
$picture = if ($def.picture) { "$($def.picture)" } else { "" }
$contentItems = @()
if ($def.content) {
foreach ($c in $def.content) { $contentItems += "$c" }
}
$children = @()
if ($def.children) {
foreach ($ch in $def.children) { $children += "$ch" }
}
# --- 4. Build XML ---
$uuid = New-Guid-String
$indent = "`t`t`t"
X '<?xml version="1.0" encoding="UTF-8"?>'
X '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">'
X "`t<Subsystem uuid=`"$uuid`">"
X "`t`t<Properties>"
# Name
X "`t`t`t<Name>$(Esc-Xml $objName)</Name>"
# Synonym
Emit-MLText "`t`t`t" "Synonym" $synonym
# Comment
if ($comment) {
X "`t`t`t<Comment>$(Esc-Xml $comment)</Comment>"
} else {
X "`t`t`t<Comment/>"
}
# Boolean properties
X "`t`t`t<IncludeHelpInContents>$includeHelpInContents</IncludeHelpInContents>"
X "`t`t`t<IncludeInCommandInterface>$includeInCI</IncludeInCommandInterface>"
X "`t`t`t<UseOneCommand>$useOneCommand</UseOneCommand>"
# Explanation
Emit-MLText "`t`t`t" "Explanation" $explanation
# Picture
if ($picture) {
X "`t`t`t<Picture>"
X "`t`t`t`t<xr:Ref>$picture</xr:Ref>"
X "`t`t`t`t<xr:LoadTransparent>false</xr:LoadTransparent>"
X "`t`t`t</Picture>"
} else {
X "`t`t`t<Picture/>"
}
# Content
if ($contentItems.Count -gt 0) {
X "`t`t`t<Content>"
foreach ($item in $contentItems) {
X "`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml $item)</xr:Item>"
}
X "`t`t`t</Content>"
} else {
X "`t`t`t<Content/>"
}
X "`t`t</Properties>"
# ChildObjects
if ($children.Count -gt 0) {
X "`t`t<ChildObjects>"
foreach ($ch in $children) {
X "`t`t`t<Subsystem>$(Esc-Xml $ch)</Subsystem>"
}
X "`t`t</ChildObjects>"
} else {
X "`t`t<ChildObjects/>"
}
X "`t</Subsystem>"
X '</MetaDataObject>'
# --- 5. Write files ---
# Determine target directory
if ($Parent) {
# Nested subsystem
if (-not [System.IO.Path]::IsPathRooted($Parent)) {
$Parent = Join-Path (Get-Location).Path $Parent
}
if (-not (Test-Path $Parent)) {
Write-Error "Parent subsystem not found: $Parent"
exit 1
}
$parentDir = [System.IO.Path]::GetDirectoryName($Parent)
$parentBaseName = [System.IO.Path]::GetFileNameWithoutExtension($Parent)
$subsDir = Join-Path (Join-Path $parentDir $parentBaseName) "Subsystems"
} else {
# Top-level subsystem
$subsDir = Join-Path $OutputDir "Subsystems"
}
if (-not (Test-Path $subsDir)) {
New-Item -ItemType Directory -Path $subsDir -Force | Out-Null
}
$targetXml = Join-Path $subsDir "$objName.xml"
# Write XML
$xmlContent = $script:xml.ToString()
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetXml, $xmlContent, $utf8Bom)
Write-Host "[OK] Created: $targetXml"
# Create subdirectory if children exist
if ($children.Count -gt 0) {
$childSubsDir = Join-Path (Join-Path $subsDir $objName) "Subsystems"
if (-not (Test-Path $childSubsDir)) {
New-Item -ItemType Directory -Path $childSubsDir -Force | Out-Null
Write-Host "[OK] Created directory: $childSubsDir"
}
}
# --- 6. Register in parent ---
$parentXmlPath = $null
if ($Parent) {
$parentXmlPath = $Parent
} else {
$configXml = Join-Path $OutputDir "Configuration.xml"
if (Test-Path $configXml) {
$parentXmlPath = $configXml
}
}
if ($parentXmlPath -and (Test-Path $parentXmlPath)) {
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $true
$doc.Load($parentXmlPath)
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
# Find ChildObjects
$childObjects = $null
if ($Parent) {
$childObjects = $doc.SelectSingleNode("//md:Subsystem/md:ChildObjects", $ns)
} else {
$childObjects = $doc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
}
if ($childObjects) {
# Check for self-closing tag
$isSelfClosing = (-not $childObjects.HasChildNodes) -or ($childObjects.IsEmpty)
# Check if already registered
$alreadyExists = $false
foreach ($child in $childObjects.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem" -and $child.InnerText -eq $objName) {
$alreadyExists = $true
break
}
}
if (-not $alreadyExists) {
$newEl = $doc.CreateElement("Subsystem", "http://v8.1c.ru/8.3/MDClasses")
$newEl.InnerText = $objName
if ($isSelfClosing) {
# Expand self-closing tag
$parentIndent = ""
$prev = $childObjects.PreviousSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
if ($prev.Value -match '(\t+)$') { $parentIndent = $Matches[1] }
}
$childIndent = "$parentIndent`t"
$ws1 = $doc.CreateWhitespace("`r`n$childIndent")
$ws2 = $doc.CreateWhitespace("`r`n$parentIndent")
$childObjects.AppendChild($ws1) | Out-Null
$childObjects.AppendChild($newEl) | Out-Null
$childObjects.AppendChild($ws2) | Out-Null
} else {
# Insert before trailing whitespace
$childIndent = "`t`t`t"
foreach ($child in $childObjects.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)') { $childIndent = $Matches[1]; break }
}
}
$trailing = $childObjects.LastChild
$ws = $doc.CreateWhitespace("`r`n$childIndent")
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$childObjects.InsertBefore($ws, $trailing) | Out-Null
$childObjects.InsertBefore($newEl, $trailing) | Out-Null
} else {
$childObjects.AppendChild($ws) | Out-Null
$childObjects.AppendChild($newEl) | Out-Null
}
}
# Save parent XML
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$doc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
[System.IO.File]::WriteAllText($parentXmlPath, $text, $utf8Bom)
Write-Host "[OK] Registered in: $parentXmlPath"
} else {
Write-Host "[SKIP] Already registered in: $parentXmlPath"
}
} else {
Write-Host "[WARN] ChildObjects not found in: $parentXmlPath"
}
} else {
Write-Host "[INFO] No parent XML to register in"
}
# --- 7. Auto-validate ---
if (-not $NoValidate) {
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\subsystem-validate") "scripts\subsystem-validate.ps1"
$validateScript = [System.IO.Path]::GetFullPath($validateScript)
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- Running subsystem-validate ---"
& powershell.exe -NoProfile -File $validateScript -SubsystemPath $targetXml
}
}
Write-Host ""
Write-Host "=== subsystem-compile summary ==="
Write-Host " Name: $objName"
Write-Host " UUID: $uuid"
Write-Host " Content: $($contentItems.Count) objects"
Write-Host " Children: $($children.Count)"
Write-Host " File: $targetXml"
exit 0
+57
View File
@@ -0,0 +1,57 @@
---
name: subsystem-edit
description: Точечное редактирование подсистемы 1С. Используй когда нужно добавить или удалить объекты из подсистемы, управлять дочерними подсистемами или изменить свойства
argument-hint: -SubsystemPath <path> -Operation <op> -Value <value>
allowed-tools:
- Bash
- Read
- Write
- Glob
---
# /subsystem-edit — редактирование подсистемы 1С
Точечное редактирование XML подсистемы: состав, дочерние подсистемы, свойства.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `SubsystemPath` | Путь к XML-файлу подсистемы |
| `DefinitionFile` | JSON-файл с массивом операций |
| `Operation` | Одна операция (альтернатива DefinitionFile) |
| `Value` | Значение для операции |
| `NoValidate` | Пропустить авто-валидацию |
```powershell
powershell.exe -NoProfile -File '.claude\skills\subsystem-edit\scripts\subsystem-edit.ps1' -SubsystemPath '<path>' -Operation add-content -Value 'Catalog.Товары'
```
## Операции
| Операция | Значение | Описание |
|----------|----------|----------|
| `add-content` | `"Catalog.X"` или `["Catalog.X","Document.Y"]` | Добавить объекты в Content |
| `remove-content` | `"Catalog.X"` или `["Catalog.X"]` | Удалить объекты из Content |
| `add-child` | `"ИмяПодсистемы"` | Добавить дочернюю подсистему в ChildObjects |
| `remove-child` | `"ИмяПодсистемы"` | Удалить дочернюю подсистему |
| `set-property` | `{"name":"prop","value":"val"}` | Изменить свойство (Synonym, IncludeInCommandInterface, UseOneCommand, etc.) |
## Примеры
```powershell
# Добавить объект в состав
... -SubsystemPath Subsystems/Продажи.xml -Operation add-content -Value "Document.Заказ"
# Добавить несколько объектов
... -SubsystemPath Subsystems/Продажи.xml -Operation add-content -Value '["Catalog.Товары","Report.Продажи"]'
# Удалить объект из состава
... -SubsystemPath Subsystems/Продажи.xml -Operation remove-content -Value "Report.Старый"
# Добавить дочернюю подсистему
... -SubsystemPath Subsystems/Продажи.xml -Operation add-child -Value "НоваяДочерняя"
# Изменить свойство
... -SubsystemPath Subsystems/Продажи.xml -Operation set-property -Value '{"name":"IncludeInCommandInterface","value":"false"}'
```
@@ -0,0 +1,403 @@
# subsystem-edit v1.0 — Edit existing 1C subsystem XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$SubsystemPath,
[string]$DefinitionFile,
[ValidateSet("add-content","remove-content","add-child","remove-child","set-property")]
[string]$Operation,
[string]$Value,
[switch]$NoValidate
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Mode validation ---
if ($DefinitionFile -and $Operation) { Write-Error "Cannot use both -DefinitionFile and -Operation"; exit 1 }
if (-not $DefinitionFile -and -not $Operation) { Write-Error "Either -DefinitionFile or -Operation is required"; exit 1 }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($SubsystemPath)) {
$SubsystemPath = Join-Path (Get-Location).Path $SubsystemPath
}
if (Test-Path $SubsystemPath -PathType Container) {
$dirName = Split-Path $SubsystemPath -Leaf
$candidate = Join-Path $SubsystemPath "$dirName.xml"
if (Test-Path $candidate) { $SubsystemPath = $candidate }
else { Write-Error "No $dirName.xml found in directory"; exit 1 }
}
if (-not (Test-Path $SubsystemPath)) { Write-Error "File not found: $SubsystemPath"; exit 1 }
$resolvedPath = (Resolve-Path $SubsystemPath).Path
# --- Load XML with PreserveWhitespace ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
$script:addCount = 0
$script:removeCount = 0
$script:modifyCount = 0
function Info([string]$msg) { Write-Host "[INFO] $msg" }
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
# --- Detect structure ---
$root = $script:xmlDoc.DocumentElement
$script:mdNs = "http://v8.1c.ru/8.3/MDClasses"
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
$script:sub = $null
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem") {
$script:sub = $child; break
}
}
if (-not $script:sub) { Write-Error "No <Subsystem> element found"; exit 1 }
$script:propsEl = $null
$script:childObjsEl = $null
foreach ($child in $script:sub.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq "Properties") { $script:propsEl = $child }
if ($child.LocalName -eq "ChildObjects") { $script:childObjsEl = $child }
}
$script:objName = ""
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Name") {
$script:objName = $child.InnerText.Trim(); break
}
}
Info "Subsystem: $($script:objName)"
# --- XML manipulation helpers (from meta-edit pattern) ---
function Import-Fragment([string]$xmlString) {
$wrapper = "<_W xmlns=`"$($script:mdNs)`" xmlns:xsi=`"$($script:xsiNs)`" xmlns:v8=`"$($script:v8Ns)`" xmlns:xr=`"$($script:xrNs)`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`">$xmlString</_W>"
$frag = New-Object System.Xml.XmlDocument
$frag.PreserveWhitespace = $true
$frag.LoadXml($wrapper)
$nodes = @()
foreach ($child in $frag.DocumentElement.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$nodes += $script:xmlDoc.ImportNode($child, $true)
}
}
return ,$nodes
}
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0; $current = $container
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
if ($refNode) {
$container.InsertBefore($ws, $refNode) | Out-Null
$container.InsertBefore($newNode, $ws) | Out-Null
} else {
$trailing = $container.LastChild
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
$container.InsertBefore($ws, $trailing) | Out-Null
$container.InsertBefore($newNode, $trailing) | Out-Null
} else {
$container.AppendChild($ws) | Out-Null
$container.AppendChild($newNode) | Out-Null
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
function Remove-NodeWithWhitespace($node) {
$parent = $node.ParentNode
$prev = $node.PreviousSibling
$next = $node.NextSibling
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($prev) | Out-Null
} elseif ($next -and ($next.NodeType -eq 'Whitespace' -or $next.NodeType -eq 'SignificantWhitespace')) {
$parent.RemoveChild($next) | Out-Null
}
$parent.RemoveChild($node) | Out-Null
}
function Expand-SelfClosingElement($container, $parentIndent) {
# If the element is self-closing (empty), add whitespace for children
if (-not $container.HasChildNodes -or $container.IsEmpty) {
$childIndent = "$parentIndent`t"
# The element is self-closing; we need to add something to make it non-empty
# Adding a whitespace node will force opening+closing tags
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
# --- Parse value: string or JSON array ---
function Parse-ValueList([string]$val) {
$val = $val.Trim()
if ($val.StartsWith("[")) {
$arr = $val | ConvertFrom-Json
$result = @(); foreach ($item in $arr) { $result += "$item" }
return ,$result
}
return @($val)
}
# --- Operations ---
function Do-AddContent([string[]]$items) {
$contentEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Content") {
$contentEl = $child; break
}
}
if (-not $contentEl) { Write-Error "No <Content> element found"; exit 1 }
# Get existing items for dedup
$existing = @()
foreach ($child in $contentEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Item") {
$existing += $child.InnerText.Trim()
}
}
# Determine indentation
$propsIndent = Get-ChildIndent $script:propsEl
$contentIndent = "$propsIndent`t"
# Expand self-closing if needed
if (-not $contentEl.HasChildNodes -or $contentEl.IsEmpty) {
Expand-SelfClosingElement $contentEl $propsIndent
$contentIndent = "$propsIndent`t"
} else {
$contentIndent = Get-ChildIndent $contentEl
}
foreach ($item in $items) {
if ($item -in $existing) {
Warn "Content already contains: $item"
continue
}
$fragXml = "<xr:Item xsi:type=`"xr:MDObjectRef`">$item</xr:Item>"
$nodes = Import-Fragment $fragXml
if ($nodes.Count -gt 0) {
Insert-BeforeElement $contentEl $nodes[0] $null $contentIndent
$script:addCount++
Info "Added content: $item"
}
}
}
function Do-RemoveContent([string[]]$items) {
$contentEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Content") {
$contentEl = $child; break
}
}
if (-not $contentEl) { Write-Error "No <Content> element found"; exit 1 }
foreach ($item in $items) {
$found = $false
foreach ($child in @($contentEl.ChildNodes)) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Item" -and $child.InnerText.Trim() -eq $item) {
Remove-NodeWithWhitespace $child
$script:removeCount++
Info "Removed content: $item"
$found = $true
break
}
}
if (-not $found) { Warn "Content item not found: $item" }
}
}
function Do-AddChild([string]$childName) {
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found"; exit 1 }
# Dedup check
foreach ($child in $script:childObjsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem" -and $child.InnerText.Trim() -eq $childName) {
Warn "ChildObjects already contains: $childName"
return
}
}
$subIndent = Get-ChildIndent $script:sub
if (-not $script:childObjsEl.HasChildNodes -or $script:childObjsEl.IsEmpty) {
Expand-SelfClosingElement $script:childObjsEl $subIndent
}
$childIndent = Get-ChildIndent $script:childObjsEl
$newEl = $script:xmlDoc.CreateElement("Subsystem", $script:mdNs)
$newEl.InnerText = $childName
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
$script:addCount++
Info "Added child subsystem: $childName"
}
function Do-RemoveChild([string]$childName) {
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found"; exit 1 }
$found = $false
foreach ($child in @($script:childObjsEl.ChildNodes)) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem" -and $child.InnerText.Trim() -eq $childName) {
Remove-NodeWithWhitespace $child
$script:removeCount++
Info "Removed child subsystem: $childName"
$found = $true
break
}
}
if (-not $found) { Warn "Child subsystem not found: $childName" }
}
function Do-SetProperty([string]$jsonVal) {
$propDef = $jsonVal | ConvertFrom-Json
$propName = "$($propDef.name)"
$propValue = "$($propDef.value)"
$propEl = $null
foreach ($child in $script:propsEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
$propEl = $child; break
}
}
if (-not $propEl) {
Write-Error "Property '$propName' not found in Properties"
exit 1
}
$boolProps = @("IncludeInCommandInterface","UseOneCommand","IncludeHelpInContents")
if ($propName -in $boolProps) {
$propEl.InnerText = $propValue.ToLower()
$script:modifyCount++
Info "Set $propName = $propValue"
return
}
$mlProps = @("Synonym","Explanation")
if ($propName -in $mlProps) {
if (-not $propValue) {
# Clear - make self-closing
$propEl.InnerXml = ""
$script:modifyCount++
Info "Cleared $propName"
} else {
$indent = Get-ChildIndent $script:propsEl
$mlXml = "`r`n$indent`t<v8:item>`r`n$indent`t`t<v8:lang>ru</v8:lang>`r`n$indent`t`t<v8:content>$([System.Security.SecurityElement]::Escape($propValue))</v8:content>`r`n$indent`t</v8:item>`r`n$indent"
$propEl.InnerXml = $mlXml
$script:modifyCount++
Info "Set $propName = `"$propValue`""
}
return
}
if ($propName -eq "Comment") {
if (-not $propValue) { $propEl.InnerXml = "" }
else { $propEl.InnerText = $propValue }
$script:modifyCount++
Info "Set Comment = `"$propValue`""
return
}
if ($propName -eq "Picture") {
if (-not $propValue) {
$propEl.InnerXml = ""
} else {
$indent = Get-ChildIndent $script:propsEl
$picXml = "`r`n$indent`t<xr:Ref>$propValue</xr:Ref>`r`n$indent`t<xr:LoadTransparent>false</xr:LoadTransparent>`r`n$indent"
$propEl.InnerXml = $picXml
}
$script:modifyCount++
Info "Set Picture = `"$propValue`""
return
}
# Generic text property
$propEl.InnerText = $propValue
$script:modifyCount++
Info "Set $propName = `"$propValue`""
}
# --- Execute operations ---
$operations = @()
if ($DefinitionFile) {
if (-not [System.IO.Path]::IsPathRooted($DefinitionFile)) {
$DefinitionFile = Join-Path (Get-Location).Path $DefinitionFile
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$ops = $jsonText | ConvertFrom-Json
if ($ops -is [System.Array]) {
foreach ($op in $ops) { $operations += $op }
} else {
$operations += $ops
}
} else {
$operations += @{ operation = $Operation; value = $Value }
}
foreach ($op in $operations) {
$opName = if ($op.operation) { "$($op.operation)" } else { "$Operation" }
$opValue = if ($op.value) { "$($op.value)" } else { "$Value" }
switch ($opName) {
"add-content" { Do-AddContent (Parse-ValueList $opValue) }
"remove-content" { Do-RemoveContent (Parse-ValueList $opValue) }
"add-child" { Do-AddChild $opValue }
"remove-child" { Do-RemoveChild $opValue }
"set-property" { Do-SetProperty $opValue }
default { Write-Error "Unknown operation: $opName"; exit 1 }
}
}
# --- Save ---
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$script:xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$bytes = $memStream.ToArray()
$memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath"
# --- Auto-validate ---
if (-not $NoValidate) {
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\subsystem-validate") "scripts\subsystem-validate.ps1"
$validateScript = [System.IO.Path]::GetFullPath($validateScript)
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- Running subsystem-validate ---"
& powershell.exe -NoProfile -File $validateScript -SubsystemPath $resolvedPath
}
}
# --- Summary ---
Write-Host ""
Write-Host "=== subsystem-edit summary ==="
Write-Host " Subsystem: $($script:objName)"
Write-Host " Added: $($script:addCount)"
Write-Host " Removed: $($script:removeCount)"
Write-Host " Modified: $($script:modifyCount)"
exit 0
+61
View File
@@ -0,0 +1,61 @@
---
name: subsystem-info
description: Анализ структуры подсистемы 1С из XML-выгрузки — состав, дочерние подсистемы, командный интерфейс, дерево иерархии. Используй для изучения структуры подсистем и навигации по конфигурации
argument-hint: <SubsystemPath> [-Mode overview|content|ci|tree] [-Name <элемент>]
allowed-tools:
- Bash
- Read
- Glob
---
# /subsystem-info — Структура подсистемы 1С
Читает XML подсистемы из выгрузки конфигурации 1С и выводит компактное описание структуры.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `SubsystemPath` | Путь к XML-файлу подсистемы, каталогу подсистемы или каталогу `Subsystems/` (для tree) |
| `Mode` | Режим: `overview` (default), `content`, `ci`, `tree` |
| `Name` | Drill-down: тип объекта в content, секция в ci, имя подсистемы в tree |
| `Limit` / `Offset` | Пагинация (по умолчанию 150 строк) |
| `OutFile` | Записать результат в файл (UTF-8 BOM) |
```powershell
powershell.exe -NoProfile -File .claude\skills\subsystem-info\scripts\subsystem-info.ps1 -SubsystemPath "<путь>"
```
## Четыре режима
| Режим | Что показывает |
|---|---|
| `overview` *(default)* | Компактная сводка: свойства, состав (сгруппирован по типам), дочерние подсистемы, наличие CI |
| `content` | Список Content с группировкой по типу объекта. `-Name Catalog` — только каталоги |
| `ci` | Разбор CommandInterface.xml: видимость, размещение, порядок команд/подсистем/групп |
| `tree` | Рекурсивное дерево иерархии подсистем с маркерами [CI], [OneCmd], [Скрыт] |
## Примеры
```powershell
# Обзор подсистемы
... -SubsystemPath Subsystems/Продажи.xml
# Состав подсистемы
... -SubsystemPath Subsystems/Администрирование.xml -Mode content
# Только документы в составе
... -SubsystemPath Subsystems/Продажи.xml -Mode content -Name Document
# Командный интерфейс подсистемы
... -SubsystemPath Subsystems/Продажи.xml -Mode ci
# Дерево подсистем от корня
... -SubsystemPath Subsystems -Mode tree
# Дерево от конкретной подсистемы
... -SubsystemPath Subsystems/Администрирование.xml -Mode tree
# Дерево только для одной подсистемы
... -SubsystemPath Subsystems -Mode tree -Name Администрирование
```
@@ -0,0 +1,483 @@
# subsystem-info v1.0 — Compact summary of 1C subsystem structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)][string]$SubsystemPath,
[ValidateSet("overview","content","ci","tree")]
[string]$Mode = "overview",
[string]$Name,
[int]$Limit = 150,
[int]$Offset = 0,
[string]$OutFile
)
$ErrorActionPreference = 'Stop'
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Output helper ---
$script:lines = @()
function Out([string]$text) { $script:lines += $text }
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($SubsystemPath)) {
$SubsystemPath = Join-Path (Get-Location).Path $SubsystemPath
}
# --- Helper: get LocalString text ---
function Get-MLText($node) {
if (-not $node -or -not $node.HasChildNodes) { return "" }
foreach ($item in $node.ChildNodes) {
if ($item.NodeType -ne 'Element') { continue }
$lang = ""; $content = ""
foreach ($c in $item.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
if ($c.LocalName -eq "lang") { $lang = $c.InnerText }
if ($c.LocalName -eq "content") { $content = $c.InnerText }
}
if ($lang -eq "ru" -and $content) { return $content }
}
# fallback: first item
foreach ($item in $node.ChildNodes) {
if ($item.NodeType -ne 'Element') { continue }
foreach ($c in $item.ChildNodes) {
if ($c.NodeType -ne 'Element') { continue }
if ($c.LocalName -eq "content" -and $c.InnerText) { return $c.InnerText }
}
}
return ""
}
# --- Helper: load subsystem XML ---
function Load-SubsystemXml([string]$xmlPath) {
[xml]$doc = Get-Content -Path $xmlPath -Encoding UTF8
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$sub = $doc.SelectSingleNode("/md:MetaDataObject/md:Subsystem", $ns)
if (-not $sub) {
Write-Host "[ERROR] Not a valid subsystem XML: $xmlPath"
exit 1
}
return @{ Doc=$doc; Ns=$ns; Sub=$sub }
}
# --- Helper: get content items ---
function Get-ContentItems($props, $ns) {
$items = @()
$contentNode = $props.SelectSingleNode("md:Content", $ns)
if (-not $contentNode -or -not $contentNode.HasChildNodes) { return $items }
foreach ($item in $contentNode.SelectNodes("xr:Item", $ns)) {
$items += $item.InnerText
}
return $items
}
# --- Helper: get child subsystem names ---
function Get-ChildNames($sub, $ns) {
$names = @()
$co = $sub.SelectSingleNode("md:ChildObjects", $ns)
if (-not $co -or -not $co.HasChildNodes) { return $names }
foreach ($child in $co.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem") {
$names += $child.InnerText
}
}
return $names
}
# --- Helper: group content by type ---
function Group-ContentByType($items) {
$groups = [ordered]@{}
foreach ($item in $items) {
if ($item -match '^([^.]+)\.(.+)$') {
$type = $Matches[1]
$name = $Matches[2]
} elseif ($item -match '^[0-9a-fA-F]{8}-') {
$type = "[UUID]"
$name = $item
} else {
$type = "[Other]"
$name = $item
}
if (-not $groups.Contains($type)) { $groups[$type] = @() }
$groups[$type] += $name
}
return $groups
}
# --- Helper: find subsystem dir from XML path ---
function Get-SubsystemDir([string]$xmlPath) {
$dir = [System.IO.Path]::GetDirectoryName($xmlPath)
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($xmlPath)
return Join-Path $dir $baseName
}
# ============================================================
# Mode: tree
# ============================================================
if ($Mode -eq "tree") {
$isDir = Test-Path $SubsystemPath -PathType Container
$rootDir = $null
$rootXml = $null
if ($isDir) {
# Subsystems/ directory — show all top-level subsystems
$rootDir = $SubsystemPath
} else {
# Specific subsystem XML — show tree from this subsystem
if (-not (Test-Path $SubsystemPath)) {
Write-Host "[ERROR] File not found: $SubsystemPath"
exit 1
}
$rootXml = $SubsystemPath
}
# Box-drawing chars (PS 5.1 compatible)
$script:T_BRANCH = [char]0x251C + [char]0x2500 + [char]0x2500 + " " # ├──
$script:T_LAST = [char]0x2514 + [char]0x2500 + [char]0x2500 + " " # └──
$script:T_PIPE = [char]0x2502 + " " # │
$script:T_ARROW = [char]0x2192 # →
function Get-TreeLine([string]$xmlPath) {
$parsed = Load-SubsystemXml $xmlPath
$sub = $parsed.Sub; $ns = $parsed.Ns
$props = $sub.SelectSingleNode("md:Properties", $ns)
$name = $props.SelectSingleNode("md:Name", $ns).InnerText
$markers = @()
$subDir = Get-SubsystemDir $xmlPath
$ciPath = Join-Path (Join-Path $subDir "Ext") "CommandInterface.xml"
if (Test-Path $ciPath) { $markers += "CI" }
$useOne = $props.SelectSingleNode("md:UseOneCommand", $ns)
if ($useOne -and $useOne.InnerText -eq "true") { $markers += "OneCmd" }
$inclCI = $props.SelectSingleNode("md:IncludeInCommandInterface", $ns)
if ($inclCI -and $inclCI.InnerText -eq "false") { $markers += "Скрыт" }
$markerStr = if ($markers.Count -gt 0) { " [$($markers -join ', ')]" } else { "" }
$contentItems = @(Get-ContentItems $props $ns)
$childNames = @(Get-ChildNames $sub $ns)
$childStr = if ($childNames.Count -gt 0) { ", $($childNames.Count) дочерних" } else { "" }
return @{
Label = "$name$markerStr ($($contentItems.Count) объектов$childStr)"
SubDir = $subDir
ChildNames = $childNames
}
}
function Build-TreeEntry([string]$xmlPath, [string]$prefix, [bool]$isLast, [bool]$isRoot) {
$info = Get-TreeLine $xmlPath
$connector = if ($isRoot) { "" } elseif ($isLast) { $script:T_LAST } else { $script:T_BRANCH }
Out "$prefix$connector$($info.Label)"
if ($info.ChildNames.Count -gt 0) {
$childPrefix = if ($isRoot) { "" } elseif ($isLast) { "$prefix " } else { "$prefix$($script:T_PIPE)" }
$subsDir = Join-Path $info.SubDir "Subsystems"
for ($i = 0; $i -lt $info.ChildNames.Count; $i++) {
$childXml = Join-Path $subsDir "$($info.ChildNames[$i]).xml"
$childIsLast = ($i -eq $info.ChildNames.Count - 1)
if (Test-Path $childXml) {
Build-TreeEntry $childXml $childPrefix $childIsLast $false
} else {
$conn2 = if ($childIsLast) { $script:T_LAST } else { $script:T_BRANCH }
Out "$childPrefix$conn2$($info.ChildNames[$i]) [NOT FOUND]"
}
}
}
}
if ($rootDir) {
$label = Split-Path $rootDir -Leaf
Out "Дерево подсистем от: $label/"
Out ""
$xmlFiles = @(Get-ChildItem $rootDir -Filter "*.xml" -File | Sort-Object Name)
if ($Name) {
$xmlFiles = @($xmlFiles | Where-Object { $_.BaseName -eq $Name })
if ($xmlFiles.Count -eq 0) {
Write-Host "[ERROR] Subsystem '$Name' not found in $rootDir"
exit 1
}
}
for ($i = 0; $i -lt $xmlFiles.Count; $i++) {
Build-TreeEntry $xmlFiles[$i].FullName "" ($i -eq $xmlFiles.Count - 1) $true
}
} else {
Build-TreeEntry $rootXml "" $true $true
}
} elseif ($Mode -eq "ci") {
# ============================================================
# Mode: ci — CommandInterface.xml
# ============================================================
if (Test-Path $SubsystemPath -PathType Container) {
Write-Host "[ERROR] ci mode requires a subsystem .xml file, not a directory"
exit 1
}
if (-not (Test-Path $SubsystemPath)) {
Write-Host "[ERROR] File not found: $SubsystemPath"
exit 1
}
$parsed = Load-SubsystemXml $SubsystemPath
$sub = $parsed.Sub; $ns = $parsed.Ns
$props = $sub.SelectSingleNode("md:Properties", $ns)
$subName = $props.SelectSingleNode("md:Name", $ns).InnerText
$subDir = Get-SubsystemDir $SubsystemPath
$ciPath = Join-Path (Join-Path $subDir "Ext") "CommandInterface.xml"
if (-not (Test-Path $ciPath)) {
Out "Командный интерфейс: $subName"
Out ""
Out "Файл CommandInterface.xml не найден."
Out "Путь: $ciPath"
} else {
[xml]$ciDoc = Get-Content -Path $ciPath -Encoding UTF8
$ciNs = New-Object System.Xml.XmlNamespaceManager($ciDoc.NameTable)
$ciNs.AddNamespace("ci", "http://v8.1c.ru/8.3/xcf/extrnprops")
$ciNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ciRoot = $ciDoc.DocumentElement
Out "Командный интерфейс: $subName"
Out ""
# --- CommandsVisibility ---
$visSection = $ciRoot.SelectSingleNode("ci:CommandsVisibility", $ciNs)
if ($visSection) {
$hidden = @(); $shown = @()
foreach ($cmd in $visSection.SelectNodes("ci:Command", $ciNs)) {
$cmdName = $cmd.GetAttribute("name")
$vis = $cmd.SelectSingleNode("ci:Visibility/xr:Common", $ciNs)
if ($vis -and $vis.InnerText -eq "false") { $hidden += $cmdName }
else { $shown += $cmdName }
}
$total = $hidden.Count + $shown.Count
if (-not $Name -or $Name -eq "visibility") {
Out "Видимость ($total):"
if ($hidden.Count -gt 0) {
Out " СКРЫТО ($($hidden.Count)):"
foreach ($h in $hidden) { Out " $h" }
}
if ($shown.Count -gt 0) {
Out " ПОКАЗАНО ($($shown.Count)):"
foreach ($s in $shown) { Out " $s" }
}
Out ""
}
}
# --- CommandsPlacement ---
$placeSection = $ciRoot.SelectSingleNode("ci:CommandsPlacement", $ciNs)
if ($placeSection) {
$placements = @()
foreach ($cmd in $placeSection.SelectNodes("ci:Command", $ciNs)) {
$cmdName = $cmd.GetAttribute("name")
$grp = $cmd.SelectSingleNode("ci:CommandGroup", $ciNs)
$pl = $cmd.SelectSingleNode("ci:Placement", $ciNs)
$grpText = if ($grp) { $grp.InnerText } else { "?" }
$plText = if ($pl) { $pl.InnerText } else { "?" }
$placements += @{ Name=$cmdName; Group=$grpText; Placement=$plText }
}
if ((-not $Name -or $Name -eq "placement") -and $placements.Count -gt 0) {
Out "Размещение ($($placements.Count)):"
$arrow = [char]0x2192
foreach ($p in $placements) {
Out " $($p.Name) $arrow $($p.Group) ($($p.Placement))"
}
Out ""
}
}
# --- CommandsOrder ---
$orderSection = $ciRoot.SelectSingleNode("ci:CommandsOrder", $ciNs)
if ($orderSection) {
$orderGroups = [ordered]@{}
foreach ($cmd in $orderSection.SelectNodes("ci:Command", $ciNs)) {
$cmdName = $cmd.GetAttribute("name")
$grp = $cmd.SelectSingleNode("ci:CommandGroup", $ciNs)
$grpText = if ($grp) { $grp.InnerText } else { "?" }
if (-not $orderGroups.Contains($grpText)) { $orderGroups[$grpText] = @() }
$orderGroups[$grpText] += $cmdName
}
$totalOrder = 0
foreach ($k in $orderGroups.Keys) { $totalOrder += $orderGroups[$k].Count }
if ((-not $Name -or $Name -eq "order") -and $totalOrder -gt 0) {
Out "Порядок команд ($totalOrder):"
foreach ($grpName in $orderGroups.Keys) {
Out " [$grpName]:"
foreach ($c in $orderGroups[$grpName]) { Out " $c" }
}
Out ""
}
}
# --- SubsystemsOrder ---
$subOrderSection = $ciRoot.SelectSingleNode("ci:SubsystemsOrder", $ciNs)
if ($subOrderSection) {
$subOrder = @()
foreach ($s in $subOrderSection.SelectNodes("ci:Subsystem", $ciNs)) {
$subOrder += $s.InnerText
}
if ((-not $Name -or $Name -eq "subsystems") -and $subOrder.Count -gt 0) {
Out "Порядок подсистем ($($subOrder.Count)):"
for ($i = 0; $i -lt $subOrder.Count; $i++) {
Out " $($i+1). $($subOrder[$i])"
}
Out ""
}
}
# --- GroupsOrder ---
$grpOrderSection = $ciRoot.SelectSingleNode("ci:GroupsOrder", $ciNs)
if ($grpOrderSection) {
$grpOrder = @()
foreach ($g in $grpOrderSection.SelectNodes("ci:Group", $ciNs)) {
$grpOrder += $g.InnerText
}
if ((-not $Name -or $Name -eq "groups") -and $grpOrder.Count -gt 0) {
Out "Порядок групп ($($grpOrder.Count)):"
foreach ($g in $grpOrder) { Out " $g" }
}
}
}
} else {
# ============================================================
# Mode: overview / content — requires a subsystem XML file
# ============================================================
if (Test-Path $SubsystemPath -PathType Container) {
$dirName = Split-Path $SubsystemPath -Leaf
$candidate = Join-Path $SubsystemPath "$dirName.xml"
if (Test-Path $candidate) {
$SubsystemPath = $candidate
} else {
Write-Host "[ERROR] No $dirName.xml found in directory. Use -Mode tree for directory listing."
exit 1
}
}
if (-not (Test-Path $SubsystemPath)) {
Write-Host "[ERROR] File not found: $SubsystemPath"
exit 1
}
$parsed = Load-SubsystemXml $SubsystemPath
$sub = $parsed.Sub; $ns = $parsed.Ns
$props = $sub.SelectSingleNode("md:Properties", $ns)
$subName = $props.SelectSingleNode("md:Name", $ns).InnerText
$synonym = Get-MLText $props.SelectSingleNode("md:Synonym", $ns)
$comment = $props.SelectSingleNode("md:Comment", $ns)
$commentText = if ($comment -and $comment.InnerText) { $comment.InnerText } else { "" }
$inclHelp = $props.SelectSingleNode("md:IncludeHelpInContents", $ns).InnerText
$inclCI = $props.SelectSingleNode("md:IncludeInCommandInterface", $ns).InnerText
$useOneCmd = $props.SelectSingleNode("md:UseOneCommand", $ns).InnerText
$explanation = Get-MLText $props.SelectSingleNode("md:Explanation", $ns)
# Picture
$picNode = $props.SelectSingleNode("md:Picture", $ns)
$picText = ""
if ($picNode -and $picNode.HasChildNodes) {
$picRef = $picNode.SelectSingleNode("xr:Ref", $ns)
if ($picRef -and $picRef.InnerText) { $picText = $picRef.InnerText }
}
# Content
$contentItems = @(Get-ContentItems $props $ns)
$groups = Group-ContentByType $contentItems
# Children
$childNames = @(Get-ChildNames $sub $ns)
# CI presence
$subDir = Get-SubsystemDir $SubsystemPath
$ciPath = Join-Path (Join-Path $subDir "Ext") "CommandInterface.xml"
$hasCI = Test-Path $ciPath
if ($Mode -eq "overview") {
Out "Подсистема: $subName"
if ($synonym -and $synonym -ne $subName) { Out "Синоним: $synonym" }
if ($commentText) { Out "Комментарий: $commentText" }
Out "ВключатьВКомандныйИнтерфейс: $inclCI"
Out "ИспользоватьОднуКоманду: $useOneCmd"
if ($explanation) { Out "Пояснение: $explanation" }
if ($picText) { Out "Картинка: $picText" }
# Content summary
if ($contentItems.Count -gt 0) {
$parts = @()
foreach ($type in $groups.Keys) {
$parts += "$type`: $($groups[$type].Count)"
}
Out "Состав: $($contentItems.Count) объектов ($($parts -join ', '))"
} else {
Out "Состав: пусто"
}
# Children
if ($childNames.Count -gt 0) {
Out "Дочерние подсистемы ($($childNames.Count)): $($childNames -join ', ')"
}
# CI
if ($hasCI) {
Out "Командный интерфейс: есть"
}
} elseif ($Mode -eq "content") {
Out "Состав подсистемы $subName ($($contentItems.Count) объектов):"
Out ""
if ($Name) {
# Filter by type
if ($groups.Contains($Name)) {
$filtered = $groups[$Name]
Out "$Name ($($filtered.Count)):"
foreach ($n in $filtered) { Out " $n" }
} else {
Out "[INFO] Тип '$Name' не найден в составе."
Out "Доступные типы: $($groups.Keys -join ', ')"
}
} else {
foreach ($type in $groups.Keys) {
Out "$type ($($groups[$type].Count)):"
foreach ($n in $groups[$type]) { Out " $n" }
Out ""
}
}
}
}
# --- Pagination and output ---
$totalLines = $script:lines.Count
$outLines = $script:lines
if ($Offset -gt 0) {
if ($Offset -ge $totalLines) {
Write-Host "[INFO] Offset $Offset exceeds total lines ($totalLines). Nothing to show."
exit 0
}
$outLines = $outLines[$Offset..($totalLines - 1)]
}
if ($Limit -gt 0 -and $outLines.Count -gt $Limit) {
$shown = $outLines[0..($Limit - 1)]
$remaining = $totalLines - $Offset - $Limit
$shown += ""
$shown += "[ОБРЕЗАНО] Показано $Limit из $totalLines строк. Используйте -Offset $($Offset + $Limit) для продолжения."
$outLines = $shown
}
if ($OutFile) {
if (-not [System.IO.Path]::IsPathRooted($OutFile)) {
$OutFile = Join-Path (Get-Location).Path $OutFile
}
$utf8 = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllLines($OutFile, $outLines, $utf8)
Write-Host "Output written to $OutFile"
} else {
foreach ($l in $outLines) { Write-Host $l }
}
@@ -0,0 +1,41 @@
---
name: subsystem-validate
description: Валидация подсистемы 1С. Используй после создания или модификации подсистемы для проверки корректности
argument-hint: <SubsystemPath> [-MaxErrors 30]
allowed-tools:
- Bash
- Read
- Glob
---
# /subsystem-validate — валидация подсистемы 1С
Проверяет структурную корректность XML-файла подсистемы из выгрузки конфигурации.
## Параметры и команда
| Параметр | Описание |
|----------|----------|
| `SubsystemPath` | Путь к XML-файлу подсистемы |
| `MaxErrors` | Максимум ошибок до остановки (по умолчанию 30) |
| `OutFile` | Записать результат в файл |
```powershell
powershell.exe -NoProfile -File '.claude\skills\subsystem-validate\scripts\subsystem-validate.ps1' -SubsystemPath '<путь>'
```
## Проверки (13)
1. XML well-formedness + root structure (MetaDataObject/Subsystem)
2. Properties — 9 обязательных свойств
3. Name — непустой, валидный идентификатор
4. Synonym — непустой (хотя бы один v8:item)
5. Булевы свойства — содержат true/false
6. Content — формат xr:Item, xsi:type
7. Content — нет дубликатов
8. ChildObjects — элементы непустые
9. ChildObjects — нет дубликатов
10. ChildObjects → файлы существуют
11. CommandInterface.xml — well-formedness
12. Picture — формат ссылки
13. UseOneCommand=true → ровно 1 элемент в Content
@@ -0,0 +1,314 @@
# subsystem-validate v1.0 — Validate 1C subsystem XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$SubsystemPath,
[int]$MaxErrors = 30,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Resolve path ---
if (-not [System.IO.Path]::IsPathRooted($SubsystemPath)) {
$SubsystemPath = Join-Path (Get-Location).Path $SubsystemPath
}
if (Test-Path $SubsystemPath -PathType Container) {
$dirName = Split-Path $SubsystemPath -Leaf
$candidate = Join-Path $SubsystemPath "$dirName.xml"
if (Test-Path $candidate) { $SubsystemPath = $candidate }
else {
Write-Host "[ERROR] No $dirName.xml found in directory: $SubsystemPath"
exit 1
}
}
if (-not (Test-Path $SubsystemPath)) {
Write-Host "[ERROR] File not found: $SubsystemPath"
exit 1
}
$resolvedPath = (Resolve-Path $SubsystemPath).Path
# --- Output infrastructure ---
$script:errors = 0
$script:warnings = 0
$script:stopped = $false
$script:output = New-Object System.Text.StringBuilder 8192
function Out-Line([string]$msg) { $script:output.AppendLine($msg) | Out-Null }
function Report-OK([string]$msg) { Out-Line "[OK] $msg" }
function Report-Error([string]$msg) {
$script:errors++
Out-Line "[ERROR] $msg"
if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
}
function Report-Warn([string]$msg) {
$script:warnings++
Out-Line "[WARN] $msg"
}
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
# --- 1. XML well-formedness + root structure ---
$xmlDoc = $null
try {
[xml]$xmlDoc = Get-Content -Path $resolvedPath -Encoding UTF8
} catch {
Report-Error "1. XML parse error: $($_.Exception.Message)"
$script:stopped = $true
}
if (-not $script:stopped) {
$ns = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ns.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
$ns.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
$root = $xmlDoc.DocumentElement
$sub = $xmlDoc.SelectSingleNode("/md:MetaDataObject/md:Subsystem", $ns)
$version = $root.GetAttribute("version")
if (-not $sub) {
Report-Error "1. Root structure: expected MetaDataObject/Subsystem, not found"
$script:stopped = $true
} else {
$uuid = $sub.GetAttribute("uuid")
if ($uuid -and $uuid -match $guidPattern) {
Report-OK "1. Root structure: MetaDataObject/Subsystem, uuid=$uuid, version $version"
} else {
Report-Error "1. Root structure: invalid or missing uuid"
}
}
}
# --- Properties checks ---
if (-not $script:stopped) {
$props = $sub.SelectSingleNode("md:Properties", $ns)
if (-not $props) {
Report-Error "2. Properties: <Properties> element not found"
$script:stopped = $true
}
}
$subName = ""
if (-not $script:stopped) {
# --- 2. Required properties ---
$requiredProps = @("Name","Synonym","Comment","IncludeHelpInContents","IncludeInCommandInterface","UseOneCommand","Explanation","Picture","Content")
$missing = @()
foreach ($p in $requiredProps) {
$el = $props.SelectSingleNode("md:$p", $ns)
if (-not $el) { $missing += $p }
}
if ($missing.Count -eq 0) {
Report-OK "2. Properties: all 9 required properties present"
} else {
Report-Error "2. Properties: missing: $($missing -join ', ')"
}
# --- 3. Name ---
$nameEl = $props.SelectSingleNode("md:Name", $ns)
$subName = if ($nameEl) { $nameEl.InnerText.Trim() } else { "" }
Out-Line ""
Out-Line "=== Validation: Subsystem.$subName ==="
# Re-insert header at position 0
$headerLine = "=== Validation: Subsystem.$subName ==="
$script:output.Insert(0, "$headerLine`r`n`r`n") | Out-Null
if ($subName -and $subName -match $identPattern) {
Report-OK "3. Name: `"$subName`" - valid identifier"
} elseif (-not $subName) {
Report-Error "3. Name: empty"
} else {
Report-Error "3. Name: `"$subName`" - invalid identifier"
}
# --- 4. Synonym ---
$synEl = $props.SelectSingleNode("md:Synonym", $ns)
if ($synEl -and $synEl.HasChildNodes) {
$items = $synEl.SelectNodes("v8:item", $ns)
if ($items.Count -gt 0) {
$firstContent = ""
foreach ($item in $items) {
$c = $item.SelectSingleNode("v8:content", $ns)
if ($c -and $c.InnerText) { $firstContent = $c.InnerText; break }
}
Report-OK "4. Synonym: `"$firstContent`" ($($items.Count) lang(s))"
} else {
Report-Warn "4. Synonym: element exists but no v8:item children"
}
} else {
Report-Warn "4. Synonym: empty or missing"
}
# --- 5. Boolean properties ---
$boolProps = @("IncludeHelpInContents","IncludeInCommandInterface","UseOneCommand")
$boolOk = $true
$boolVals = @{}
foreach ($bp in $boolProps) {
$el = $props.SelectSingleNode("md:$bp", $ns)
if ($el) {
$val = $el.InnerText.Trim()
$boolVals[$bp] = $val
if ($val -ne "true" -and $val -ne "false") {
Report-Error "5. Boolean property $bp = `"$val`" (expected true/false)"
$boolOk = $false
}
}
}
if ($boolOk) { Report-OK "5. Boolean properties: valid" }
# --- 6. Content items format ---
$contentEl = $props.SelectSingleNode("md:Content", $ns)
$contentItems = @()
if ($contentEl -and $contentEl.HasChildNodes) {
$xrItems = $contentEl.SelectNodes("xr:Item", $ns)
$contentOk = $true
foreach ($item in $xrItems) {
$typeAttr = $item.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
$text = $item.InnerText.Trim()
$contentItems += $text
if ($typeAttr -ne "xr:MDObjectRef") {
Report-Error "6. Content item `"$text`": xsi:type=`"$typeAttr`" (expected xr:MDObjectRef)"
$contentOk = $false
}
if ($text -notmatch '^[A-Za-z]+\..+$' -and $text -notmatch $guidPattern) {
Report-Error "6. Content item `"$text`": invalid format (expected Type.Name or UUID)"
$contentOk = $false
}
}
if ($contentOk) { Report-OK "6. Content: $($xrItems.Count) items, all valid MDObjectRef format" }
} else {
Report-OK "6. Content: empty (no items)"
}
# --- 7. Content duplicates ---
if ($contentItems.Count -gt 0) {
$dupes = $contentItems | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Warn "7. Content: duplicates found: $dupeNames"
} else {
Report-OK "7. Content: no duplicates"
}
} else {
Report-OK "7. Content: no duplicates (empty)"
}
# --- 8. ChildObjects entries non-empty ---
$childObjs = $sub.SelectSingleNode("md:ChildObjects", $ns)
$childNames = @()
if ($childObjs -and $childObjs.HasChildNodes) {
$childOk = $true
foreach ($child in $childObjs.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -ne "Subsystem") {
Report-Error "8. ChildObjects: unexpected element <$($child.LocalName)>"
$childOk = $false
} elseif (-not $child.InnerText.Trim()) {
Report-Error "8. ChildObjects: empty <Subsystem> element"
$childOk = $false
} else {
$childNames += $child.InnerText.Trim()
}
}
if ($childOk) { Report-OK "8. ChildObjects: $($childNames.Count) entries, all non-empty" }
} else {
Report-OK "8. ChildObjects: empty (leaf subsystem)"
}
# --- 9. ChildObjects duplicates ---
if ($childNames.Count -gt 0) {
$dupes = $childNames | Group-Object | Where-Object { $_.Count -gt 1 }
if ($dupes) {
$dupeNames = ($dupes | ForEach-Object { $_.Name }) -join ", "
Report-Error "9. ChildObjects: duplicates: $dupeNames"
} else {
Report-OK "9. ChildObjects: no duplicates"
}
} else {
Report-OK "9. ChildObjects: no duplicates (empty)"
}
# --- 10. ChildObjects files exist ---
if ($childNames.Count -gt 0) {
$parentDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($resolvedPath)
$subsDir = Join-Path (Join-Path $parentDir $baseName) "Subsystems"
$missingFiles = @()
foreach ($cn in $childNames) {
$childXml = Join-Path $subsDir "$cn.xml"
if (-not (Test-Path $childXml)) { $missingFiles += $cn }
}
if ($missingFiles.Count -eq 0) {
Report-OK "10. ChildObjects files: all $($childNames.Count) files exist"
} else {
Report-Warn "10. ChildObjects files: missing: $($missingFiles -join ', ')"
}
} else {
Report-OK "10. ChildObjects files: n/a (no children)"
}
# --- 11. CommandInterface.xml ---
$parentDir2 = [System.IO.Path]::GetDirectoryName($resolvedPath)
$baseName2 = [System.IO.Path]::GetFileNameWithoutExtension($resolvedPath)
$ciPath = Join-Path (Join-Path (Join-Path $parentDir2 $baseName2) "Ext") "CommandInterface.xml"
if (Test-Path $ciPath) {
try {
[xml]$ciDoc = Get-Content -Path $ciPath -Encoding UTF8
Report-OK "11. CommandInterface: exists, well-formed"
} catch {
Report-Warn "11. CommandInterface: exists but NOT well-formed: $($_.Exception.Message)"
}
} else {
Report-OK "11. CommandInterface: not present"
}
# --- 12. Picture format ---
$picEl = $props.SelectSingleNode("md:Picture", $ns)
if ($picEl -and $picEl.HasChildNodes) {
$picRef = $picEl.SelectSingleNode("xr:Ref", $ns)
if ($picRef -and $picRef.InnerText) {
$refText = $picRef.InnerText
if ($refText -match '^CommonPicture\.') {
Report-OK "12. Picture: $refText"
} else {
Report-Warn "12. Picture: `"$refText`" (expected CommonPicture.XXX)"
}
} else {
Report-Warn "12. Picture: has children but no xr:Ref content"
}
} else {
Report-OK "12. Picture: empty (not set)"
}
# --- 13. UseOneCommand constraint ---
$useOne = $boolVals["UseOneCommand"]
if ($useOne -eq "true") {
if ($contentItems.Count -eq 1) {
Report-OK "13. UseOneCommand: true, Content has exactly 1 item"
} else {
Report-Warn "13. UseOneCommand: true but Content has $($contentItems.Count) items (expected 1)"
}
} else {
Report-OK "13. UseOneCommand: false (no constraint)"
}
}
# --- Finalize ---
Out-Line "---"
Out-Line "Errors: $($script:errors), Warnings: $($script:warnings)"
$result = $script:output.ToString()
Write-Host $result
if ($OutFile) {
if (-not [System.IO.Path]::IsPathRooted($OutFile)) {
$OutFile = Join-Path (Get-Location).Path $OutFile
}
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($OutFile, $result, $utf8Bom)
Write-Host "Written to: $OutFile"
}
if ($script:errors -gt 0) { exit 1 } else { exit 0 }
@@ -1,7 +1,7 @@
---
name: epf-add-template
description: Добавить макет к внешней обработке 1С
argument-hint: <ProcessorName> <TemplateName> <TemplateType>
name: template-add
description: Добавить макет к объекту 1С (обработка, отчёт, справочник, документ и др.)
argument-hint: <ObjectName> <TemplateName> <TemplateType>
allowed-tools:
- Bash
- Read
@@ -11,28 +11,29 @@ allowed-tools:
- Grep
---
# /epf-add-template — Добавление макета
# /template-add — Добавление макета
Создаёт макет указанного типа и регистрирует его в корневом XML обработки.
Создаёт макет указанного типа и регистрирует его в корневом XML объекта.
## Usage
```
/epf-add-template <ProcessorName> <TemplateName> <TemplateType>
/template-add <ObjectName> <TemplateName> <TemplateType>
```
| Параметр | Обязательный | По умолчанию | Описание |
|---------------|:------------:|-----------------|--------------------------------------------------|
| ProcessorName | да | — | Имя обработки |
| ObjectName | да | — | Имя объекта |
| TemplateName | да | — | Имя макета |
| TemplateType | да | — | Тип: HTML, Text, SpreadsheetDocument, BinaryData, DataCompositionSchema |
| Synonym | нет | = TemplateName | Синоним макета |
| SrcDir | нет | `src` | Каталог исходников |
| --SetMainSKD | нет | — | Принудительно установить MainDataCompositionSchema |
## Команда
```powershell
pwsh -NoProfile -File .claude/skills/epf-add-template/scripts/add-template.ps1 -ProcessorName "<ProcessorName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"]
powershell.exe -NoProfile -File .claude\skills\template-add\scripts\add-template.ps1 -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-SetMainSKD]
```
## Маппинг типов
@@ -58,10 +59,16 @@ pwsh -NoProfile -File .claude/skills/epf-add-template/scripts/add-template.ps1 -
Если пользователь указал имя макета без префикса, но контекст — печатная форма, **добавь префикс `ПФ_MXL_` автоматически** и сообщи об этом.
## MainDataCompositionSchema (авто)
При добавлении макета типа `DataCompositionSchema` к `ExternalReport` или `Report`:
- Если `MainDataCompositionSchema` пуст — автоматически заполняется ссылкой на макет
- Используй `--SetMainSKD` чтобы перезаписать существующее значение
## Что создаётся
```
<SrcDir>/<ProcessorName>/Templates/
<SrcDir>/<ObjectName>/Templates/
├── <TemplateName>.xml # Метаданные макета (1 UUID)
└── <TemplateName>/
└── Ext/
@@ -70,4 +77,5 @@ pwsh -NoProfile -File .claude/skills/epf-add-template/scripts/add-template.ps1 -
## Что модифицируется
- `<SrcDir>/<ProcessorName>.xml` — добавляется `<Template>` в конец `ChildObjects`
- `<SrcDir>/<ObjectName>.xml` — добавляется `<Template>` в конец `ChildObjects`
- Для ExternalReport/Report: может обновляться `MainDataCompositionSchema`
@@ -1,8 +1,9 @@
# epf-add-template v1.0 — Add template to 1C processor
# template-add v1.1 — Add template to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ProcessorName,
[Alias("ProcessorName")]
[string]$ObjectName,
[Parameter(Mandatory)]
[string]$TemplateName,
@@ -13,7 +14,9 @@ param(
[string]$Synonym = $TemplateName,
[string]$SrcDir = "src"
[string]$SrcDir = "src",
[switch]$SetMainSKD
)
$ErrorActionPreference = "Stop"
@@ -32,13 +35,13 @@ $tmpl = $typeMap[$TemplateType]
# --- Проверки ---
$rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml"
$rootXmlPath = Join-Path $SrcDir "$ObjectName.xml"
if (-not (Test-Path $rootXmlPath)) {
Write-Error "Корневой файл обработки не найден: $rootXmlPath"
exit 1
}
$processorDir = Join-Path $SrcDir $ProcessorName
$processorDir = Join-Path $SrcDir $ObjectName
$templatesDir = Join-Path $processorDir "Templates"
$templateMetaPath = Join-Path $templatesDir "$TemplateName.xml"
@@ -172,6 +175,36 @@ if ($childObjects.ChildNodes.Count -eq 0) {
}
}
# --- 4. MainDataCompositionSchema (для ExternalReport / Report) ---
$mainDCSUpdated = $false
if ($TemplateType -eq "DataCompositionSchema") {
# Определяем корневой элемент объекта
$reportLikeTypes = @("ExternalReport", "Report")
$objectTypeNode = $null
$objectTypeName = $null
foreach ($rt in $reportLikeTypes) {
$node = $xmlDoc.SelectSingleNode("//md:$rt", $nsMgr)
if ($node) {
$objectTypeNode = $node
$objectTypeName = $rt
break
}
}
if ($objectTypeNode) {
$mainDCS = $xmlDoc.SelectSingleNode("//md:${objectTypeName}/md:Properties/md:MainDataCompositionSchema", $nsMgr)
if ($mainDCS) {
$isEmpty = [string]::IsNullOrWhiteSpace($mainDCS.InnerText)
if ($isEmpty -or $SetMainSKD) {
$objName = $xmlDoc.SelectSingleNode("//md:${objectTypeName}/md:Properties/md:Name", $nsMgr).InnerText
$mainDCS.InnerText = "$objectTypeName.$objName.Template.$TemplateName"
$mainDCSUpdated = $true
}
}
}
}
# Сохранить с BOM
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom
@@ -186,3 +219,6 @@ $stream.Close()
Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)"
Write-Host " Метаданные: $templateMetaPath"
Write-Host " Содержимое: $templateFilePath"
if ($mainDCSUpdated) {
Write-Host " MainDataCompositionSchema: $($mainDCS.InnerText)"
}
+47
View File
@@ -0,0 +1,47 @@
---
name: template-remove
description: Удалить макет из объекта 1С (обработка, отчёт, справочник, документ и др.)
argument-hint: <ObjectName> <TemplateName>
disable-model-invocation: true
allowed-tools:
- Bash
- Read
- Write
- Edit
- Glob
- Grep
---
# /template-remove — Удаление макета
Удаляет макет и убирает его регистрацию из корневого XML объекта.
## Usage
```
/template-remove <ObjectName> <TemplateName>
```
| Параметр | Обязательный | По умолчанию | Описание |
|--------------|:------------:|--------------|-------------------------------------|
| ObjectName | да | — | Имя объекта |
| TemplateName | да | — | Имя макета для удаления |
| SrcDir | нет | `src` | Каталог исходников |
## Команда
```powershell
powershell.exe -NoProfile -File .claude\skills\template-remove\scripts\remove-template.ps1 -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" [-SrcDir "<SrcDir>"]
```
## Что удаляется
```
<SrcDir>/<ObjectName>/Templates/<TemplateName>.xml # Метаданные макета
<SrcDir>/<ObjectName>/Templates/<TemplateName>/ # Каталог макета (рекурсивно)
```
## Что модифицируется
- `<SrcDir>/<ObjectName>.xml` — убирается `<Template>` из `ChildObjects`
- Для ExternalReport/Report: если удалённый макет был указан в `MainDataCompositionSchema` — значение очищается
@@ -1,8 +1,9 @@
# epf-remove-template v1.0 — Remove template from 1C processor
# template-remove v1.1 — Remove template from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ProcessorName,
[Alias("ProcessorName")]
[string]$ObjectName,
[Parameter(Mandatory)]
[string]$TemplateName,
@@ -14,13 +15,13 @@ $ErrorActionPreference = "Stop"
# --- Проверки ---
$rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml"
$rootXmlPath = Join-Path $SrcDir "$ObjectName.xml"
if (-not (Test-Path $rootXmlPath)) {
Write-Error "Корневой файл обработки не найден: $rootXmlPath"
exit 1
}
$processorDir = Join-Path $SrcDir $ProcessorName
$processorDir = Join-Path $SrcDir $ObjectName
$templatesDir = Join-Path $processorDir "Templates"
$templateMetaPath = Join-Path $templatesDir "$TemplateName.xml"
$templateDir = Join-Path $templatesDir $TemplateName
@@ -65,6 +66,13 @@ foreach ($node in $templateNodes) {
}
}
# Очистить MainDataCompositionSchema если указывала на этот макет
$mainDCS = $xmlDoc.SelectSingleNode("//md:MainDataCompositionSchema", $nsMgr)
if ($mainDCS -and $mainDCS.InnerText -match "Template\.$([regex]::Escape($TemplateName))$") {
$mainDCS.InnerText = ""
Write-Host "[OK] Очищён MainDataCompositionSchema"
}
# Сохранить с BOM
$encBom = New-Object System.Text.UTF8Encoding($true)
$settings = New-Object System.Xml.XmlWriterSettings
+57 -15
View File
@@ -2,7 +2,7 @@
> **Work in progress** — проект в стадии активной разработки. Набор навыков и операций расширяется.
Набор [Claude Code Skills](https://docs.anthropic.com/en/docs/claude-code/skills) для работы с артефактами 1С:Предприятия 8.3. Позволяет создавать и модифицировать обработки, макеты печатных форм и другие объекты из XML-исходников, не запоминая детали формата.
Набор [Claude Code Skills](https://docs.anthropic.com/en/docs/claude-code/skills) для работы с артефактами 1С:Предприятия 8.3. Позволяет создавать и модифицировать обработки, отчёты, объекты конфигурации, макеты печатных форм и другие объекты из XML-исходников, не запоминая детали формата.
## Быстрый старт
@@ -21,24 +21,32 @@
| Группа | Навыки | Описание | Гайд |
|--------|--------|----------|------|
| Внешние обработки (EPF) | 10 навыков `/epf-*` | Создание, модификация, сборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
| Внешние обработки (EPF) | 6 навыков `/epf-*` | Создание, сборка, разборка обработок из XML-исходников | [Подробнее](docs/epf-guide.md) |
| Внешние отчёты (ERF) | 3 навыка `/erf-*` | Создание, сборка, разборка внешних отчётов | [Подробнее](docs/epf-guide.md#внешние-отчёты-erf) |
| Универсальные операции | `/template-add`, `/template-remove`, `/help-add`, `/form-remove` | Добавление/удаление макетов, форм, справки для любых объектов | [Подробнее](docs/epf-guide.md#универсальные-навыки) |
| Табличный документ (MXL) | 4 навыка `/mxl-*` | Анализ, создание, компиляция макетов печатных форм | [Подробнее](docs/mxl-guide.md) |
| Управляемые формы (Form) | 6 навыков `/form-*` | Создание, анализ, генерация, модификация, валидация управляемых форм | [Подробнее](docs/form-guide.md) |
| Роли (Role) | 3 навыка `/role-*` | Анализ прав роли, создание из JSON DSL, валидация | [Подробнее](docs/role-guide.md) |
| Схема компоновки (СКД) | 4 навыка `/skd-*` | Анализ, генерация из JSON DSL, точечное редактирование, валидация схем компоновки данных | [Подробнее](docs/skd-guide.md) |
| Метаданные конфигурации | 4 навыка `/meta-*` | Создание, анализ, редактирование, валидация объектов метаданных (23 типа) | [Подробнее](docs/meta-guide.md) |
| Корневая конфигурация | 4 навыка `/cf-*` | Создание, анализ, редактирование, валидация корневых файлов конфигурации | [Подробнее](docs/cf-guide.md) |
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | — |
| Командный интерфейс (CI) | 2 навыка `/interface-*` | Редактирование и валидация CommandInterface.xml подсистем | — |
| Утилиты | `/img-grid` | Наложение сетки на изображение для определения пропорций колонок | — |
## Требования
- **Windows** с PowerShell 5.1+ (входит в Windows)
- **1С:Предприятие 8.3** — для сборки/разборки EPF (навыки генерации XML работают без платформы)
- **1С:Предприятие 8.3** — для сборки/разборки EPF/ERF (навыки генерации XML работают без платформы)
## Спецификации
- [XML-формат выгрузки обработок](docs/1c-xml-format-spec.md) — структура XML-файлов, namespace, элементы форм
- [XML-формат выгрузки обработок](docs/1c-epf-spec.md) — структура XML-файлов, namespace, элементы форм
- [XML-формат внешних отчётов](docs/1c-erf-spec.md) — отличия ERF от EPF, Properties, MainDataCompositionSchema
- [Управляемая форма](docs/1c-form-spec.md) — Form.xml, элементы, команды, реквизиты
- [Встроенная справка](docs/1c-help-spec.md) — Help.xml, HTML-страницы, кнопка справки на форме
- [Сборка и разборка EPF](docs/build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
- [Сборка и разборка EPF/ERF](docs/build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
- [Табличный документ (MXL)](docs/1c-spreadsheet-spec.md) — XML-формат SpreadsheetDocument, совместимость версий
- [MXL DSL](docs/mxl-dsl-spec.md) — JSON-формат описания макета для `/mxl-compile` и `/mxl-decompile`
- [Form DSL](docs/form-dsl-spec.md) — JSON-формат описания формы для `/form-compile`
@@ -46,30 +54,37 @@
- [Role DSL](docs/role-dsl-spec.md) — JSON-формат описания ролей для `/role-compile`
- [Схема компоновки данных (DCS)](docs/1c-dcs-spec.md) — XML-формат DataCompositionSchema, 930 схем проанализировано
- [SKD DSL](docs/skd-dsl-spec.md) — JSON-формат описания СКД для `/skd-compile`
- [Объекты конфигурации](docs/1c-config-objects-spec.md) — XML-формат объектов метаданных конфигурации (23 типа)
- [Подсистемы и командный интерфейс](docs/1c-subsystem-spec.md) — XML-формат подсистем, CommandInterface.xml, секции видимости/размещения/порядка
- [Корневая конфигурация](docs/1c-configuration-spec.md) — XML-формат Configuration.xml, ConfigDumpInfo.xml, Languages/, 44 типа ChildObjects
- [Расширения конфигурации (CFE)](docs/1c-extension-spec.md) — XML-формат выгрузки расширений конфигурации
## Структура репозитория
```
.claude/skills/ # Навыки Claude Code
├── epf-init/ # Создание обработки
├── epf-add-form/ # Добавление формы
├── epf-add-template/ # Добавление макета
├── epf-add-help/ # Добавление справки
├── epf-remove-form/ # Удаление формы
├── epf-remove-template/ # Удаление макета
├── epf-add-form/ # Добавление формы к обработке
├── epf-build/ # Сборка EPF
├── epf-dump/ # Разборка EPF
├── epf-bsp-init/ # Регистрация БСП
├── epf-bsp-add-command/ # Команда БСП
├── erf-init/ # Создание внешнего отчёта
├── erf-build/ # Сборка ERF
├── erf-dump/ # Разборка ERF
├── template-add/ # Добавление макета (универсальный)
├── template-remove/ # Удаление макета (универсальный)
├── form-add/ # Добавление формы (универсальный)
├── form-remove/ # Удаление формы (универсальный)
├── help-add/ # Добавление справки (универсальный)
├── mxl-info/ # Анализ макета
├── mxl-validate/ # Валидация макета
├── mxl-compile/ # Компиляция макета из JSON
├── mxl-decompile/ # Декомпиляция макета в JSON
├── form-add/ # Добавление формы к объекту конфигурации
├── form-info/ # Анализ структуры управляемой формы
├── form-compile/ # Компиляция формы из JSON
├── form-validate/ # Валидация формы
├── form-edit/ # Добавление элементов в форму
├── form-edit/ # Добавление элементов в форму
├── form-patterns/ # Справочник паттернов компоновки форм
├── role-info/ # Анализ прав роли
├── role-compile/ # Создание роли из JSON DSL
@@ -78,22 +93,49 @@
├── skd-compile/ # Компиляция СКД из JSON DSL
├── skd-edit/ # Точечное редактирование СКД (25 операций)
├── skd-validate/ # Валидация СКД
├── meta-info/ # Структура объекта метаданных
├── meta-compile/ # Создание объекта метаданных
├── meta-edit/ # Редактирование объекта метаданных
├── meta-validate/ # Валидация объекта метаданных
├── cf-info/ # Анализ структуры конфигурации
├── cf-init/ # Создание пустой конфигурации
├── cf-edit/ # Редактирование конфигурации
├── cf-validate/ # Валидация конфигурации
├── cfe-init/ # Создание расширения
├── cfe-borrow/ # Заимствование объектов
├── cfe-patch-method/ # Перехват методов
├── cfe-validate/ # Валидация расширения
├── cfe-diff/ # Анализ и сравнение
├── subsystem-info/ # Анализ структуры подсистемы
├── subsystem-compile/ # Создание подсистемы из JSON
├── subsystem-edit/ # Редактирование подсистемы
├── subsystem-validate/ # Валидация подсистемы
├── interface-edit/ # Редактирование CommandInterface.xml
├── interface-validate/ # Валидация CommandInterface.xml
└── img-grid/ # Сетка для анализа изображений
docs/
├── epf-guide.md # Гайд: внешние обработки
├── epf-guide.md # Гайд: внешние обработки и отчёты
├── mxl-guide.md # Гайд: табличный документ
├── form-guide.md # Гайд: управляемые формы
├── role-guide.md # Гайд: роли
├── skd-guide.md # Гайд: схема компоновки данных
├── 1c-xml-format-spec.md # Спецификация XML-формата
├── meta-guide.md # Гайд: объекты метаданных конфигурации
├── cf-guide.md # Гайд: корневые файлы конфигурации
├── cfe-guide.md # Гайд: расширения конфигурации (CFE)
├── 1c-epf-spec.md # Спецификация XML-формата (EPF)
├── 1c-erf-spec.md # Спецификация XML-формата (ERF)
├── 1c-form-spec.md # Спецификация управляемых форм
├── 1c-help-spec.md # Спецификация встроенной справки
├── 1c-config-objects-spec.md # Спецификация объектов конфигурации
├── build-spec.md # Спецификация сборки/разборки
├── 1c-spreadsheet-spec.md # Спецификация табличного документа
├── mxl-dsl-spec.md # Спецификация MXL DSL
├── form-dsl-spec.md # Спецификация Form DSL
├── meta-dsl-spec.md # Спецификация Meta DSL
├── 1c-role-spec.md # Спецификация ролей (Rights.xml)
├── 1c-dcs-spec.md # Спецификация СКД (DataCompositionSchema)
├── skd-dsl-spec.md # Спецификация SKD DSL
── role-dsl-spec.md # Спецификация Role DSL
── role-dsl-spec.md # Спецификация Role DSL
├── 1c-extension-spec.md # Спецификация расширений конфигурации (CFE)
└── 1c-subsystem-spec.md # Спецификация подсистем и командного интерфейса
```
File diff suppressed because it is too large Load Diff
+992
View File
@@ -0,0 +1,992 @@
# Спецификация корневой структуры конфигурации 1С
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
Версии формата: `2.17` (платформа 8.3.208.3.24), `2.20` (платформа 8.3.27+).
Источники: выгрузки Бухгалтерия предприятия (платформы 8.3.20, 8.3.24, 8.3.27), ERP 2 (8.3.24).
> **Связанные спецификации:**
> - Объекты метаданных — [1c-config-objects-spec.md](1c-config-objects-spec.md)
> - Подсистемы и командный интерфейс — [1c-subsystem-spec.md](1c-subsystem-spec.md)
> - Сводный индекс — [1c-specs-index.md](1c-specs-index.md)
---
## 1. Общая структура выгрузки
```
Configuration.xml # Корневой файл — свойства и состав конфигурации
ConfigDumpInfo.xml # Служебный файл — версии объектов
Ext/ # Корневой каталог модулей и интерфейса
Languages/ # Языки конфигурации
Subsystems/ # Подсистемы
Catalogs/ # Справочники
Documents/ # Документы
... # Каталоги всех типов объектов (см. раздел 2.4)
```
Полный перечень каталогов объектов и их формат — [1c-config-objects-spec.md § 1](1c-config-objects-spec.md#1-общая-структура-выгрузки).
---
## 2. Configuration.xml — корневой файл конфигурации
### 2.1. Общая структура
```xml
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:app="http://v8.1c.ru/8.2/managed-application/core"
... version="2.17">
<Configuration uuid="e0666db2-...">
<InternalInfo>...</InternalInfo>
<Properties>...</Properties>
<ChildObjects>...</ChildObjects>
</Configuration>
</MetaDataObject>
```
Атрибут `version` корневого элемента `MetaDataObject` определяет версию формата выгрузки.
### 2.2. InternalInfo
Содержит набор `xr:ContainedObject` — пары ClassId/ObjectId, идентифицирующие внутренние компоненты конфигурации (модули, интерфейс, справка и т.д.). Количество записей фиксировано (7 в типичной конфигурации).
```xml
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>f0ba0954-a66b-4085-9df1-b8a4283bdbd3</xr:ObjectId>
</xr:ContainedObject>
<!-- ещё 6 записей -->
</InternalInfo>
```
ClassId — фиксированные идентификаторы классов платформы. ObjectId — уникальные для каждой конфигурации.
### 2.3. Properties — свойства конфигурации
Свойства идут строго в фиксированном порядке. Пустые свойства записываются как самозакрывающийся элемент (`<Comment/>`) или с пробелом (`<Comment />`).
#### Идентификация и общие
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Name` | `xs:string` | Имя конфигурации (идентификатор) |
| `Synonym` | `LocalString` | Отображаемое имя |
| `Comment` | `xs:string` | Комментарий |
| `NamePrefix` | `xs:string` | Префикс имён объектов |
| `Vendor` | `xs:string` | Поставщик |
| `Version` | `xs:string` | Версия конфигурации (напр. `3.0.181.31`) |
| `UpdateCatalogAddress` | `xs:string` | URL каталога обновлений |
| `BriefInformation` | `LocalString` | Краткая информация |
| `DetailedInformation` | `LocalString` | Подробная информация |
| `Copyright` | `LocalString` | Авторские права |
| `VendorInformationAddress` | `LocalString` | Адрес сайта поставщика |
| `ConfigurationInformationAddress` | `LocalString` | Адрес информации о конфигурации |
#### Режимы работы и совместимость
| Свойство | Тип | Описание |
|----------|-----|----------|
| `ConfigurationExtensionCompatibilityMode` | enum | Совместимость расширений (`Version8_3_24`, ...) |
| `DefaultRunMode` | enum | Режим запуска (`ManagedApplication`) |
| `ScriptVariant` | enum | Язык скриптов (`Russian` / `English`) |
| `CompatibilityMode` | enum | Режим совместимости (`Version8_3_24`, ...) |
| `DataLockControlMode` | enum | Управление блокировками (`Managed` / `Automatic`) |
| `ObjectAutonumerationMode` | enum | Автонумерация (`NotAutoFree` / `AutoFree`) |
| `ModalityUseMode` | enum | Модальность (`DontUse` / `Use` / `UseWithWarnings`) |
| `SynchronousPlatformExtensionAndAddInCallUseMode` | enum | Синхр. вызовы (`DontUse` / `Use`) |
| `InterfaceCompatibilityMode` | enum | Совместимость интерфейса (`Taxi` / `TaxiEnableVersion8_2`) |
| `DatabaseTablespacesUseMode` | enum | Табличные пространства (`DontUse` / `Use`) |
| `MainClientApplicationWindowMode` | enum | Режим окна (`Normal` / `Fullscreen` / `Kiosk`) |
#### Назначение и использование
| Свойство | Тип | Описание |
|----------|-----|----------|
| `UsePurposes` | list | Назначения: `PlatformApplication`, `MobilePlatformApplication` |
| `DefaultRoles` | list | Роли по умолчанию: `<xr:Item xsi:type="xr:MDObjectRef">Role.XXX</xr:Item>` |
| `DefaultLanguage` | ref | Язык по умолчанию: `Language.Русский` |
| `IncludeHelpInContents` | `xs:boolean` | Включить справку в оглавление |
| `UseManagedFormInOrdinaryApplication` | `xs:boolean` | Управл. формы в обычном приложении |
| `UseOrdinaryFormInManagedApplication` | `xs:boolean` | Обычные формы в управл. приложении |
| `Content` | list | Состав конфигурации (обычно пуст — используется при расширениях) |
| `StandaloneConfigurationRestrictionRoles` | list | Роли ограничения автономной конфигурации |
#### Хранилища настроек
| Свойство | Тип | Описание |
|----------|-----|----------|
| `CommonSettingsStorage` | ref | Хранилище общих настроек |
| `ReportsUserSettingsStorage` | ref | Хранилище пользовательских настроек отчётов |
| `ReportsVariantsStorage` | ref | Хранилище вариантов отчётов (напр. `SettingsStorage.XXX`) |
| `FormDataSettingsStorage` | ref | Хранилище данных форм |
| `DynamicListsUserSettingsStorage` | ref | Хранилище настроек динамических списков |
| `URLExternalDataStorage` | ref | Хранилище внешних данных URL |
#### Формы по умолчанию
| Свойство | Тип | Описание |
|----------|-----|----------|
| `DefaultReportForm` | ref | Форма отчёта по умолчанию (напр. `CommonForm.ФормаОтчета`) |
| `DefaultReportVariantForm` | ref | Форма варианта отчёта |
| `DefaultReportSettingsForm` | ref | Форма настроек отчёта |
| `DefaultReportAppearanceTemplate` | ref | Шаблон оформления отчёта |
| `DefaultDynamicListSettingsForm` | ref | Форма настроек динамического списка |
| `DefaultSearchForm` | ref | Форма поиска |
| `DefaultDataHistoryChangeHistoryForm` | ref | Форма истории изменений |
| `DefaultDataHistoryVersionDataForm` | ref | Форма данных версии |
| `DefaultDataHistoryVersionDifferencesForm` | ref | Форма различий версий |
| `DefaultCollaborationSystemUsersChoiceForm` | ref | Форма выбора пользователей |
| `DefaultConstantsForm` | ref | Форма констант |
| `DefaultInterface` | ref | Интерфейс по умолчанию (устаревший) |
| `DefaultStyle` | ref | Стиль по умолчанию (устаревший) |
#### Полнотекстовый поиск
| Свойство | Тип | Описание |
|----------|-----|----------|
| `AdditionalFullTextSearchDictionaries` | `xs:string` | Дополнительные словари |
#### Мобильные настройки
| Свойство | Тип | Описание |
|----------|-----|----------|
| `RequiredMobileApplicationPermissions` | list | Обязательные разрешения |
| `UsedMobileApplicationFunctionalities` | list | Используемые функциональности (см. ниже) |
| `MobileApplicationURLs` | list | URL мобильного приложения |
| `AllowedIncomingShareRequestTypes` | list | Разрешённые типы входящих share-запросов |
**UsedMobileApplicationFunctionalities** — список из `app:functionality` с подэлементами `app:functionality` (имя) и `app:use` (boolean):
```xml
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<!-- ... -->
</UsedMobileApplicationFunctionalities>
```
Известные функциональности: `Biometrics`, `Location`, `BackgroundLocation`, `BluetoothPrinters`, `WiFiPrinters`, `Contacts`, `Calendars`, `PushNotifications`, `LocalNotifications`, `InAppPurchases`, `PersonalComputerFileExchange`, `Ads`, `NumberDialing`, `CallProcessing`, `CallLog`, `AutoSendSMS`, `ReceiveSMS`, `SMSLog`, `Camera`, `Microphone`, `MusicLibrary`, `PictureAndVideoLibraries`, `AudioPlaybackAndVibration`, `BackgroundAudioPlaybackAndVibration`, `InstallPackages`, `OSBackup`, `ApplicationUsageStatistics`, `BarcodeScanning`, `BackgroundAudioRecording`, `AllFilesAccess`, `Videoconferences`, `NFC`, `DocumentScanning`, `SpeechToText`, `Geofences`, `IncomingShareRequests`, `AllIncomingShareRequestsTypesProcessing`, `TextToSpeech` (v2.20+).
### 2.4. ChildObjects — состав конфигурации
Перечисляет все объекты метаданных, сгруппированные по типу. Имя XML-элемента = тип объекта, текстовое содержимое = имя объекта. Порядок типов фиксирован:
```xml
<ChildObjects>
<Language>Русский</Language>
<Subsystem>Администрирование</Subsystem>
<Subsystem>Продажи</Subsystem>
<StyleItem>АктуальнаяПодпискаЦвет</StyleItem>
<!-- Style (только ERP и аналогичные) -->
<CommonPicture>AppStore</CommonPicture>
<SessionParameter>АвторизованныйПользователь</SessionParameter>
<Role>ПолныеПрава</Role>
<CommonTemplate>fresh</CommonTemplate>
<FilterCriterion>ДокументыПоВидуОплаты</FilterCriterion>
<CommonModule>АвтоматическиеСкидки</CommonModule>
<CommonAttribute>КомментарийЯзык1</CommonAttribute>
<ExchangePlan>ОбновлениеИнформационнойБазы</ExchangePlan>
<XDTOPackage>AgentScripts</XDTOPackage>
<WebService>EnterpriseDataExchange_1_0_1_2</WebService>
<HTTPService>RegApi</HTTPService>
<WSReference>WSСборОтчетностиРосстата</WSReference>
<EventSubscription>ВстраиваниеОбщихФорм</EventSubscription>
<ScheduledJob>АвтоматическаяВыгрузкаЧеков</ScheduledJob>
<SettingsStorage>БуферыОбменаНовостей</SettingsStorage>
<FunctionalOption>ИспользоватьВалюту</FunctionalOption>
<FunctionalOptionsParameter>Организация</FunctionalOptionsParameter>
<DefinedType>ОписаниеТаблицОбъекта</DefinedType>
<CommonCommand>АвтономнаяРабота</CommonCommand>
<CommandGroup>Документы</CommandGroup>
<Constant>АдресОбработкиОповещений</Constant>
<CommonForm>ФормаОтчета</CommonForm>
<Catalog>Банки</Catalog>
<Document>АвансовыйОтчет</Document>
<DocumentNumerator>ПерсонифицированныйУчет</DocumentNumerator>
<Sequence>ДокументыОрганизаций</Sequence>
<DocumentJournal>ЖурналДокументовЕГАИС</DocumentJournal>
<Enum>АвтоОперацииСПодотчетником</Enum>
<Report>АктСверки</Report>
<DataProcessor>АвансовыйОтчет</DataProcessor>
<InformationRegister>АвторизованныеПодключения</InformationRegister>
<AccumulationRegister>ВозвратыТоваров</AccumulationRegister>
<ChartOfCharacteristicTypes>ВидыСубконтоХозрасчетные</ChartOfCharacteristicTypes>
<ChartOfAccounts>Хозрасчетный</ChartOfAccounts>
<AccountingRegister>Хозрасчетный</AccountingRegister>
<ChartOfCalculationTypes>Начисления</ChartOfCalculationTypes>
<!-- CalculationRegister (только ERP и аналогичные) -->
<BusinessProcess>Задание</BusinessProcess>
<Task>ЗадачаИсполнителя</Task>
<IntegrationService>ОбменСообщениями</IntegrationService>
</ChildObjects>
```
#### Порядок типов в ChildObjects
| № | XML-элемент | Каталог | Описание |
|---|-------------|---------|----------|
| 1 | `Language` | `Languages/` | Языки |
| 2 | `Subsystem` | `Subsystems/` | Подсистемы |
| 3 | `StyleItem` | `StyleItems/` | Элементы стиля |
| 4 | `Style` | `Styles/` | Стили (устаревший тип) |
| 5 | `CommonPicture` | `CommonPictures/` | Общие картинки |
| 6 | `SessionParameter` | `SessionParameters/` | Параметры сеанса |
| 7 | `Role` | `Roles/` | Роли |
| 8 | `CommonTemplate` | `CommonTemplates/` | Общие макеты |
| 9 | `FilterCriterion` | `FilterCriteria/` | Критерии отбора |
| 10 | `CommonModule` | `CommonModules/` | Общие модули |
| 11 | `CommonAttribute` | `CommonAttributes/` | Общие реквизиты |
| 12 | `ExchangePlan` | `ExchangePlans/` | Планы обмена |
| 13 | `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты |
| 14 | `WebService` | `WebServices/` | Веб-сервисы |
| 15 | `HTTPService` | `HTTPServices/` | HTTP-сервисы |
| 16 | `WSReference` | `WSReferences/` | WS-ссылки |
| 17 | `EventSubscription` | `EventSubscriptions/` | Подписки на события |
| 18 | `ScheduledJob` | `ScheduledJobs/` | Регламентные задания |
| 19 | `SettingsStorage` | `SettingsStorages/` | Хранилища настроек |
| 20 | `FunctionalOption` | `FunctionalOptions/` | Функциональные опции |
| 21 | `FunctionalOptionsParameter` | `FunctionalOptionsParameters/` | Параметры ФО |
| 22 | `DefinedType` | `DefinedTypes/` | Определяемые типы |
| 23 | `CommonCommand` | `CommonCommands/` | Общие команды |
| 24 | `CommandGroup` | `CommandGroups/` | Группы команд |
| 25 | `Constant` | `Constants/` | Константы |
| 26 | `CommonForm` | `CommonForms/` | Общие формы |
| 27 | `Catalog` | `Catalogs/` | Справочники |
| 28 | `Document` | `Documents/` | Документы |
| 29 | `DocumentNumerator` | `DocumentNumerators/` | Нумераторы документов |
| 30 | `Sequence` | `Sequences/` | Последовательности |
| 31 | `DocumentJournal` | `DocumentJournals/` | Журналы документов |
| 32 | `Enum` | `Enums/` | Перечисления |
| 33 | `Report` | `Reports/` | Отчёты |
| 34 | `DataProcessor` | `DataProcessors/` | Обработки |
| 35 | `InformationRegister` | `InformationRegisters/` | Регистры сведений |
| 36 | `AccumulationRegister` | `AccumulationRegisters/` | Регистры накопления |
| 37 | `ChartOfCharacteristicTypes` | `ChartsOfCharacteristicTypes/` | Планы видов характеристик |
| 38 | `ChartOfAccounts` | `ChartsOfAccounts/` | Планы счетов |
| 39 | `AccountingRegister` | `AccountingRegisters/` | Регистры бухгалтерии |
| 40 | `ChartOfCalculationTypes` | `ChartsOfCalculationTypes/` | Планы видов расчёта |
| 41 | `CalculationRegister` | `CalculationRegisters/` | Регистры расчёта |
| 42 | `BusinessProcess` | `BusinessProcesses/` | Бизнес-процессы |
| 43 | `Task` | `Tasks/` | Задачи |
| 44 | `IntegrationService` | `IntegrationServices/` | Сервисы интеграции |
Внутри одного типа объекты отсортированы по имени (алфавитный порядок). Типы, для которых нет объектов, в ChildObjects не записываются.
---
## 3. ConfigDumpInfo.xml — служебный файл выгрузки
Содержит информацию о версиях всех объектов конфигурации. Используется платформой для определения изменений при загрузке.
### 3.1. Общая структура
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ConfigDumpInfo xmlns="http://v8.1c.ru/8.3/xcf/dumpinfo"
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
format="Hierarchical" version="2.17">
<ConfigVersions>
<Metadata name="..." id="..." configVersion="...">
<Metadata name="..." id="..."/>
...
</Metadata>
...
</ConfigVersions>
</ConfigDumpInfo>
```
### 3.2. Атрибуты корневого элемента
| Атрибут | Описание |
|---------|----------|
| `format` | Формат выгрузки (`Hierarchical`) |
| `version` | Версия формата (`2.17` / `2.20`) — совпадает с Configuration.xml |
### 3.3. Структура записей Metadata
Каждый `<Metadata>` описывает один объект или его компоненту:
| Атрибут | Описание |
|---------|----------|
| `name` | Полное имя в dot-нотации (напр. `Catalog.Банки.Attribute.Код`) |
| `id` | UUID объекта (с суффиксом `.N` для модулей/форм/справки) |
| `configVersion` | Хеш версии (32 hex-символа + `00000000`), только у записей с файлами |
**Правила:**
- Корневой объект содержит вложенные `<Metadata>` для реквизитов, измерений, ресурсов (без `configVersion`, т.к. они не имеют отдельных файлов)
- Формы, модули, справка — отдельные `<Metadata>` верхнего уровня с `configVersion`
- Суффиксы id для модулей: `.0` — форма, `.1` — модуль набора записей, `.2` — модуль менеджера, `.5` — справка, `.6` — модуль набора записей (альт.), `.7` — модуль менеджера (альт.)
```xml
<!-- Объект с вложенными реквизитами -->
<Metadata name="AccountingRegister.Хозрасчетный" id="7b248429-..." configVersion="eda4...">
<Metadata name="AccountingRegister.Хозрасчетный.Attribute.Содержание" id="17c87c43-..."/>
<Metadata name="AccountingRegister.Хозрасчетный.Resource.Сумма" id="3656a8da-..."/>
<Metadata name="AccountingRegister.Хозрасчетный.Dimension.Организация" id="4d42e16e-..."/>
</Metadata>
<!-- Форма — отдельная запись -->
<Metadata name="AccountingRegister.Хозрасчетный.Form.ФормаСписка" id="5a682c7f-..." configVersion="1362..."/>
<Metadata name="AccountingRegister.Хозрасчетный.Form.ФормаСписка.Form" id="5a682c7f-....0" configVersion="e384..."/>
<!-- Модули — отдельные записи -->
<Metadata name="AccountingRegister.Хозрасчетный.ManagerModule" id="7b248429-....7" configVersion="0387..."/>
```
---
## 4. Ext/ — корневой каталог конфигурации
Каталог `Ext/` содержит файлы, относящиеся к конфигурации в целом (не к отдельным объектам).
### 4.1. Модули (BSL)
| Файл | Описание |
|------|----------|
| `ManagedApplicationModule.bsl` | Модуль управляемого приложения |
| `OrdinaryApplicationModule.bsl` | Модуль обычного приложения |
| `SessionModule.bsl` | Модуль сеанса |
| `ExternalConnectionModule.bsl` | Модуль внешнего соединения |
Все модули — текстовые файлы в кодировке UTF-8 с BOM, содержащие код на языке 1С (BSL).
### 4.2. Командный интерфейс
| Файл | Описание |
|------|----------|
| `CommandInterface.xml` | Корневой командный интерфейс (порядок подсистем, видимость) |
| `MainSectionCommandInterface.xml` | Командный интерфейс главного раздела |
| `ClientApplicationInterface.xml` | Интерфейс клиентского приложения (расположение панелей) |
**CommandInterface.xml** — описывает порядок подсистем и видимость команд для главного окна:
```xml
<CommandInterface xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" ... version="2.17">
<SubsystemsOrder>
<Subsystem>Subsystem.Руководителю</Subsystem>
<Subsystem>Subsystem.БанкИКасса</Subsystem>
...
</SubsystemsOrder>
</CommandInterface>
```
Подробнее: [1c-subsystem-spec.md § 4](1c-subsystem-spec.md#4-формат-командного-интерфейса-commandinterfacexml).
**ClientApplicationInterface.xml** — расположение панелей (top/left/bottom/right):
```xml
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" ...>
<top>
<group id="...">
<group><panel id="..."><uuid>...</uuid></panel></group>
...
</group>
</top>
<left>...</left>
</ClientApplicationInterface>
```
### 4.3. Начальная страница
| Файл | Описание |
|------|----------|
| `HomePageWorkArea.xml` | Рабочая область начальной страницы |
```xml
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" ... version="2.17">
<WorkingAreaTemplate>TwoColumnsVariableWidth</WorkingAreaTemplate>
<LeftColumn>
<Item>
<Form>CommonForm.НачалоРаботы</Form>
<Height>100</Height>
<Visibility>
<xr:Common>true</xr:Common>
<xr:Value name="Role.ОператорОтправки...">false</xr:Value>
</Visibility>
</Item>
...
</LeftColumn>
<RightColumn>...</RightColumn>
</HomePageWorkArea>
```
Шаблон рабочей области: `TwoColumnsVariableWidth`, `OneColumn` и др.
### 4.4. Картинки
| Файл | Описание |
|------|----------|
| `Splash.xml` + `Splash/Picture.png` | Заставка при запуске |
| `MainSectionPicture.xml` + `MainSectionPicture/Picture.svg` | Картинка главного раздела |
Формат XML-описания картинки:
```xml
<ExtPicture xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" ... version="2.17">
<Picture>
<xr:Abs>Picture.png</xr:Abs>
<xr:LoadTransparent>false</xr:LoadTransparent>
</Picture>
</ExtPicture>
```
### 4.5. Бинарные файлы
| Файл | Описание |
|------|----------|
| `ParentConfigurations.bin` | Информация о родительских конфигурациях (поставки) |
| `MobileClientSignature.bin` | Подпись мобильного клиента |
---
## 5. Языки (Languages)
Языки — простейший тип объекта конфигурации. Каталог `Languages/`, один XML-файл на язык.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" ... version="2.17">
<Language uuid="db4a9ccb-9ef5-4b3c-8577-b6fe5db1b62e">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
```
| Свойство | Описание |
|----------|----------|
| `Name` | Имя (идентификатор) |
| `Synonym` | Отображаемое имя |
| `Comment` | Комментарий |
| `LanguageCode` | Код языка (`ru`, `en`, и т.д.) |
Язык, указанный в `Properties.DefaultLanguage` конфигурации как `Language.Русский`, является основным.
---
## 6. Дополнительные типы объектов
Ниже описаны типы, не покрытые в [1c-config-objects-spec.md](1c-config-objects-spec.md). Все объекты следуют стандартной структуре `MetaDataObject / <Type> / Properties` с обязательными `Name`, `Synonym`, `Comment`.
### 6.1. CommonPicture — общая картинка
Каталог: `CommonPictures/`. Файлы: `<Имя>.xml` + `<Имя>/Ext/Picture/` (файлы картинок).
```xml
<CommonPicture uuid="...">
<Properties>
<Name>AppStore</Name>
<Synonym>...</Synonym>
<Comment/>
<AvailabilityForChoice>false</AvailabilityForChoice>
<AvailabilityForAppearance>false</AvailabilityForAppearance>
</Properties>
</CommonPicture>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `AvailabilityForChoice` | `xs:boolean` | Доступность для выбора в интерфейсе |
| `AvailabilityForAppearance` | `xs:boolean` | Доступность для оформления |
### 6.2. CommonTemplate — общий макет
Каталог: `CommonTemplates/`. Файлы: `<Имя>.xml` (метаданные) + `<Имя>/Ext/Template.xml` (содержимое).
```xml
<CommonTemplate uuid="...">
<Properties>
<Name>fresh</Name>
<Synonym>...</Synonym>
<Comment/>
<TemplateType>BinaryData</TemplateType>
</Properties>
</CommonTemplate>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `TemplateType` | enum | Тип макета: `SpreadsheetDocument`, `BinaryData`, `HTMLDocument`, `TextDocument`, `ActiveDocument`, `DataCompositionSchema`, `DataCompositionAppearanceTemplate`, `GraphicalSchema`, `AddIn` |
### 6.3. CommonAttribute — общий реквизит
Каталог: `CommonAttributes/`. Один XML-файл на объект.
```xml
<CommonAttribute uuid="...">
<Properties>
<Name>КомментарийЯзык1</Name>
<Synonym>...</Synonym>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<!-- Свойства аналогичны Attribute объекта: PasswordMode, Format, EditFormat, ... -->
<AutoUse>DontUse</AutoUse>
<DataSeparation>DontUse</DataSeparation>
<SeparatedDataUse>IndependentlyAndSimultaneously</SeparatedDataUse>
<DataSeparationValue/>
<DataSeparationUse/>
<ConditionalSeparation/>
<UsersSeparation>DontUse</UsersSeparation>
<AuthenticationSeparation>DontUse</AuthenticationSeparation>
<ConfigurationExtensionsSeparation>DontUse</ConfigurationExtensionsSeparation>
<Content>...</Content>
</Properties>
</CommonAttribute>
```
Специфичные свойства (помимо стандартных реквизитных): `AutoUse`, `DataSeparation`, `SeparatedDataUse`, `DataSeparationValue`, `DataSeparationUse`, `Content` (список объектов, к которым применяется).
### 6.4. CommonForm — общая форма
Каталог: `CommonForms/`. Файлы: `<Имя>.xml` (метаданные) + `<Имя>/Ext/Form.xml` + `<Имя>/Ext/Form/Module.bsl`.
```xml
<CommonForm uuid="...">
<Properties>
<Name>АварийныйРежимИСМП</Name>
<Synonym>...</Synonym>
<Comment/>
<FormType>Managed</FormType>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
</UsePurposes>
<UseStandardCommands>false</UseStandardCommands>
<ExtendedPresentation/>
<Explanation/>
</Properties>
</CommonForm>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `FormType` | enum | `Managed` / `Ordinary` |
| `IncludeHelpInContents` | `xs:boolean` | Включить справку в оглавление |
| `UsePurposes` | list | Назначения |
| `UseStandardCommands` | `xs:boolean` | Использовать стандартные команды |
| `ExtendedPresentation` | `LocalString` | Расширенное представление |
| `Explanation` | `LocalString` | Пояснение |
### 6.5. CommonCommand — общая команда
Каталог: `CommonCommands/`. Файлы: `<Имя>.xml` + `<Имя>/Ext/CommandModule.bsl`.
```xml
<CommonCommand uuid="...">
<Properties>
<Name>АвтономнаяРабота</Name>
<Synonym>...</Synonym>
<Comment/>
<Group>NavigationPanelOrdinary</Group>
<Representation>Auto</Representation>
<ToolTip/>
<Picture/>
<Shortcut/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<CommandParameterType/>
<ParameterUseMode>Single</ParameterUseMode>
<ModifiesData>false</ModifiesData>
<OnMainServerUnavalableBehavior>Auto</OnMainServerUnavalableBehavior>
</Properties>
</CommonCommand>
```
Подробнее: [1c-subsystem-spec.md § 6](1c-subsystem-spec.md#6-формат-общей-команды-commoncommand).
### 6.6. SessionParameter — параметр сеанса
Каталог: `SessionParameters/`. Один XML-файл на объект.
```xml
<SessionParameter uuid="...">
<Properties>
<Name>АвторизованныйПользователь</Name>
<Synonym>...</Synonym>
<Comment/>
<Type>
<v8:Type>cfg:CatalogRef.ВнешниеПользователи</v8:Type>
<v8:Type>cfg:CatalogRef.Пользователи</v8:Type>
</Type>
</Properties>
</SessionParameter>
```
Единственное специфичное свойство — `Type` (составной тип).
### 6.7. FunctionalOption — функциональная опция
Каталог: `FunctionalOptions/`. Один XML-файл на объект.
```xml
<FunctionalOption uuid="...">
<Properties>
<Name>АвансыВключаютсяВДоходыВПериодеПолучения</Name>
<Synonym>...</Synonym>
<Comment/>
<Location>InformationRegister.НастройкиУчетаНДФЛ.Resource.АвансыВключаютсяВДоходыВПериодеПолучения</Location>
<PrivilegedGetMode>true</PrivilegedGetMode>
<Content/>
</Properties>
</FunctionalOption>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Location` | ref | Где хранится значение (ссылка на реквизит/ресурс/константу) |
| `PrivilegedGetMode` | `xs:boolean` | Привилегированный режим получения |
| `Content` | list | Состав (объекты, зависящие от опции) |
### 6.8. FunctionalOptionsParameter — параметр функциональных опций
Каталог: `FunctionalOptionsParameters/`. Один XML-файл.
```xml
<FunctionalOptionsParameter uuid="...">
<Properties>
<Name>ДополнительныеОтчетыИОбработкиОбъектНазначения</Name>
<Synonym>...</Synonym>
<Comment/>
<Use>
<xr:Item xsi:type="xr:MDObjectRef">InformationRegister.Назначение...Dimension.ОбъектНазначения</xr:Item>
</Use>
</Properties>
</FunctionalOptionsParameter>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Use` | list | Список измерений/реквизитов, используемых как параметр |
### 6.9. Sequence — последовательность документов
Каталог: `Sequences/`. Один XML-файл.
```xml
<Sequence uuid="...">
<InternalInfo>
<xr:GeneratedType name="SequenceRecord.ДокументыОрганизаций" category="Record">...</xr:GeneratedType>
<xr:GeneratedType name="SequenceManager.ДокументыОрганизаций" category="Manager">...</xr:GeneratedType>
<xr:GeneratedType name="SequenceRecordSet.ДокументыОрганизаций" category="RecordSet">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ДокументыОрганизаций</Name>
<Synonym>...</Synonym>
<Comment/>
<MoveBoundaryOnPosting>DontMove</MoveBoundaryOnPosting>
<Documents>
<xr:Item xsi:type="xr:MDObjectRef">Document.АвансовыйОтчет</xr:Item>
...
</Documents>
</Properties>
<ChildObjects>
<Dimension>Организация</Dimension>
</ChildObjects>
</Sequence>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `MoveBoundaryOnPosting` | enum | Перемещение границы при проведении: `DontMove` / `Move` |
| `Documents` | list | Список документов, входящих в последовательность |
ChildObjects могут содержать `Dimension` (измерения последовательности).
### 6.10. SettingsStorage — хранилище настроек
Каталог: `SettingsStorages/`. Файлы: `<Имя>.xml` + `<Имя>/` (формы, модули).
```xml
<SettingsStorage uuid="...">
<InternalInfo>
<xr:GeneratedType name="SettingsStorageManager.БуферыОбменаНовостей" category="Manager">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>БуферыОбменаНовостей</Name>
<Synonym>...</Synonym>
<Comment/>
<DefaultSaveForm/>
<DefaultLoadForm/>
<AuxiliarySaveForm/>
<AuxiliaryLoadForm/>
</Properties>
<ChildObjects>
<Form>ФормаУправленияБуферамиОбмена</Form>
</ChildObjects>
</SettingsStorage>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `DefaultSaveForm` | ref | Форма сохранения по умолчанию |
| `DefaultLoadForm` | ref | Форма загрузки по умолчанию |
| `AuxiliarySaveForm` | ref | Вспомогательная форма сохранения |
| `AuxiliaryLoadForm` | ref | Вспомогательная форма загрузки |
ChildObjects: `Form` (формы), `Template` (макеты).
### 6.11. FilterCriterion — критерий отбора
Каталог: `FilterCriteria/`. Один XML-файл.
```xml
<FilterCriterion uuid="...">
<InternalInfo>
<xr:GeneratedType name="FilterCriterionManager.ДокументыПоВидуОплаты" category="Manager">...</xr:GeneratedType>
<xr:GeneratedType name="FilterCriterionList.ДокументыПоВидуОплаты" category="List">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ДокументыПоВидуОплаты</Name>
<Synonym>...</Synonym>
<Comment/>
<Type>
<v8:Type>cfg:CatalogRef.ВидыОплатОрганизаций</v8:Type>
</Type>
<UseStandardCommands>true</UseStandardCommands>
<Content>
<xr:Item xsi:type="xr:MDObjectRef">Document.ОплатаПлатежнойКартой.Attribute.ВидОплаты</xr:Item>
...
</Content>
</Properties>
</FilterCriterion>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Type` | type-def | Тип значения критерия (обычно ссылочный) |
| `UseStandardCommands` | `xs:boolean` | Использовать стандартные команды |
| `Content` | list | Реквизиты объектов, по которым выполняется отбор |
ChildObjects: `Form` (формы), `Command` (команды).
### 6.12. DocumentNumerator — нумератор документов
Каталог: `DocumentNumerators/`. Один XML-файл.
```xml
<DocumentNumerator uuid="...">
<Properties>
<Name>ПерсонифицированныйУчет</Name>
<Synonym>...</Synonym>
<Comment/>
<NumberType>String</NumberType>
<NumberLength>11</NumberLength>
<NumberAllowedLength>Variable</NumberAllowedLength>
<NumberPeriodicity>Year</NumberPeriodicity>
<CheckUnique>true</CheckUnique>
</Properties>
</DocumentNumerator>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `NumberType` | enum | Тип номера: `String` / `Number` |
| `NumberLength` | `xs:decimal` | Длина номера |
| `NumberAllowedLength` | enum | Допустимая длина: `Variable` / `Fixed` |
| `NumberPeriodicity` | enum | Периодичность: `Nonperiodical` / `Year` / `Quarter` / `Month` / `Day` |
| `CheckUnique` | `xs:boolean` | Контроль уникальности |
### 6.13. IntegrationService — сервис интеграции
Каталог: `IntegrationServices/`. Файлы: `<Имя>.xml` + `<Имя>/Ext/Module.bsl`.
```xml
<IntegrationService uuid="...">
<InternalInfo>
<xr:GeneratedType name="IntegrationServiceManager.ОбменСообщениями" category="Manager">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ОбменСообщениями</Name>
<Synonym>...</Synonym>
<Comment/>
<ExternalIntegrationServiceAddress/>
</Properties>
<ChildObjects>
<IntegrationServiceChannel uuid="...">
<InternalInfo>...</InternalInfo>
<Properties>
<Name>input_from_SM_normal_priority</Name>
<Synonym/>
<Comment/>
<ExternalIntegrationServiceChannelName>e1c::FreshBus::Main::...</ExternalIntegrationServiceChannelName>
<MessageDirection>Receive</MessageDirection>
<ReceiveMessageProcessing>ОбработатьСообщениеОбычныйПриоритет</ReceiveMessageProcessing>
<Transactioned>false</Transactioned>
</Properties>
</IntegrationServiceChannel>
</ChildObjects>
</IntegrationService>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `ExternalIntegrationServiceAddress` | `xs:string` | Адрес внешнего сервиса интеграции |
ChildObjects содержат `IntegrationServiceChannel` (каналы) — inline-определения с uuid, InternalInfo и Properties (Name, ExternalIntegrationServiceChannelName, MessageDirection: `Send`/`Receive`, ReceiveMessageProcessing, Transactioned).
### 6.14. XDTOPackage — XDTO-пакет
Каталог: `XDTOPackages/`. Файлы: `<Имя>.xml` (метаданные) + `<Имя>/Ext/Package.xdto` (схема).
```xml
<XDTOPackage uuid="...">
<Properties>
<Name>AgentScripts</Name>
<Synonym>...</Synonym>
<Comment/>
<Namespace>http://v8.1c.ru/agent/scripts/1.0</Namespace>
</Properties>
</XDTOPackage>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Namespace` | `xs:string` | URI пространства имён XDTO-пакета |
### 6.15. WSReference — WS-ссылка
Каталог: `WSReferences/`. Файлы: `<Имя>.xml` + `<Имя>/Ext/WSDefinition.wsdl`.
```xml
<WSReference uuid="...">
<InternalInfo>
<xr:GeneratedType name="WSReferenceManager.WSСборОтчетностиРосстата" category="Manager">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>WSСборОтчетностиРосстата</Name>
<Synonym>...</Synonym>
<Comment/>
<LocationURL>file://C:/TEMP/ECCOwsdl.xml</LocationURL>
</Properties>
</WSReference>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `LocationURL` | `xs:string` | URL WSDL-описания сервиса |
### 6.16. StyleItem — элемент стиля
Каталог: `StyleItems/`. Один XML-файл.
```xml
<StyleItem uuid="...">
<Properties>
<Name>АктуальнаяПодпискаЦвет</Name>
<Synonym>...</Synonym>
<Comment/>
<Type>Color</Type>
<Value xsi:type="v8ui:Color">#009646</Value>
</Properties>
</StyleItem>
```
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Type` | enum | Тип элемента стиля: `Color`, `Font`, `Border` |
| `Value` | varies | Значение: цвет `#RRGGBB`, шрифт (`v8ui:FontInfo`), рамка (`v8ui:BorderInfo`) |
### 6.17. Style — стиль (устаревший)
Каталог: `Styles/`. Файлы: `<Имя>.xml` + `<Имя>/Ext/Style.xml`.
```xml
<Style uuid="...">
<Properties>
<Name>Основной</Name>
<Synonym>...</Synonym>
<Comment/>
</Properties>
</Style>
```
Присутствует только в конфигурациях с поддержкой устаревших стилей (ERP). Не имеет специфичных свойств в метаданных; содержимое в `Ext/Style.xml`.
---
## 7. Различия версий 2.17 → 2.20
### 7.1. Атрибут version
```xml
<!-- 2.17 -->
<MetaDataObject ... version="2.17">
<!-- 2.20 -->
<MetaDataObject ... version="2.20">
```
### 7.2. Configuration.xml — Properties
Набор свойств Properties **идентичен** в обеих версиях. Отличия только в значениях:
| Свойство | Изменение |
|----------|-----------|
| `CompatibilityMode` | `Version8_3_24``Version8_3_24` / `Version8_3_27` (зависит от конфигурации) |
| `ConfigurationExtensionCompatibilityMode` | аналогично |
| `UsedMobileApplicationFunctionalities` | В v2.20 добавлена функциональность `TextToSpeech` |
### 7.3. Configuration.xml — ChildObjects
Набор типов объектов в ChildObjects **идентичен** между версиями 2.17 и 2.20.
### 7.4. ConfigDumpInfo.xml
Атрибут `version` меняется на `2.20`. Структура записей не изменилась.
### 7.5. Ext/ файлы
В файлах `CommandInterface.xml`, `HomePageWorkArea.xml` атрибут `version` меняется на `2.20`. Структура не изменилась.
### 7.6. Форматирование XML
В v2.20 пустые элементы записываются без пробела: `<Comment/>` вместо `<Comment />`. Это косметическое отличие, не влияющее на парсинг.
---
## 8. Пространства имён XML
Полный набор namespace, используемых в Configuration.xml:
| Префикс | URI | Назначение |
|---------|-----|------------|
| *(default)* | `http://v8.1c.ru/8.3/MDClasses` | Метаданные объектов |
| `v8` | `http://v8.1c.ru/8.1/data/core` | Ядро данных (типы, LocalString) |
| `xr` | `http://v8.1c.ru/8.3/xcf/readable` | Человекочитаемые ссылки |
| `xs` | `http://www.w3.org/2001/XMLSchema` | XML Schema типы |
| `xsi` | `http://www.w3.org/2001/XMLSchema-instance` | Атрибуты xsi:type, xsi:nil |
| `app` | `http://v8.1c.ru/8.2/managed-application/core` | Управляемое приложение |
| `cfg` | `http://v8.1c.ru/8.1/data/enterprise/current-config` | Ссылочные типы конфигурации |
| `v8ui` | `http://v8.1c.ru/8.1/data/ui` | UI-элементы (цвета, шрифты) |
| `style` | `http://v8.1c.ru/8.1/data/ui/style` | Стили |
| `sys` | `http://v8.1c.ru/8.1/data/ui/fonts/system` | Системные шрифты |
| `web` | `http://v8.1c.ru/8.1/data/ui/colors/web` | Web-цвета |
| `win` | `http://v8.1c.ru/8.1/data/ui/colors/windows` | Windows-цвета |
| `xen` | `http://v8.1c.ru/8.3/xcf/enums` | Перечисления формата |
| `xpr` | `http://v8.1c.ru/8.3/xcf/predef` | Предопределённые элементы |
| `ent` | `http://v8.1c.ru/8.1/data/enterprise` | Предприятие |
| `cmi` | `http://v8.1c.ru/8.2/managed-application/cmi` | Командный интерфейс |
| `lf` | `http://v8.1c.ru/8.2/managed-application/logform` | Логические формы |
@@ -3,6 +3,8 @@
Формат: XML-выгрузка внешней обработки (ExternalDataProcessor) из конфигуратора 1С:Предприятие 8.3.
Версия формата: `2.17`.
> **Связанная спецификация**: Для внешних отчётов (ExternalReport / ERF) см. [1c-erf-spec.md](1c-erf-spec.md). Формат отчётов основан на формате обработок с дополнительными свойствами для СКД и вариантов.
## 1. Структура каталогов
```
@@ -28,11 +30,12 @@
```
Обработка может содержать:
- 0..N реквизитов объекта (описаны в корневом XML)
- 0..N табличных частей (описаны в корневом XML)
- 0..N форм (каталог `Forms/`)
- 0..N макетов (каталог `Templates/`)
- 0..1 модуль объекта (`Ext/ObjectModule.bsl`)
- 0..1 встроенная справка (`Ext/Help.xml` + `Ext/Help/<язык>.html`), см. [1c-help-spec.md](1c-help-spec.md)
- 0..N табличных частей (описаны в корневом XML)
## 2. Пространства имён XML
@@ -132,6 +135,8 @@ xmlns="http://v8.1c.ru/8.3/xcf/logform"
<AuxiliaryForm/>
</Properties>
<ChildObjects>
<!-- Реквизиты объекта (опционально) -->
<Attribute uuid="<UUID>">...</Attribute>
<!-- Табличные части (опционально) -->
<TabularSection uuid="<UUID>">...</TabularSection>
<!-- Формы -->
@@ -151,8 +156,102 @@ xmlns="http://v8.1c.ru/8.3/xcf/logform"
| `ObjectId`, `TypeId`, `ValueId` | Уникальные UUID, генерируются при создании |
| `DefaultForm` | Полный путь: `ExternalDataProcessor.<Имя>.Form.<ИмяФормы>` |
| `<Form>`, `<Template>` | Только имена (без путей), соответствуют именам подкаталогов в `Forms/` и `Templates/` |
| `<Attribute>` | Реквизиты объекта обработки (полное описание с типами) |
| `<TabularSection>` | Полное описание табличных частей объекта (включая реквизиты ТЧ с типами) |
### Порядок элементов в ChildObjects
Порядок дочерних объектов **фиксирован**:
1. `<Attribute>` — реквизиты объекта (0..N)
2. `<TabularSection>` — табличные части (0..N)
3. `<Form>` — формы (0..N)
4. `<Template>` — макеты (0..N)
### Реквизиты объекта
Если обработка имеет реквизиты объекта, они описываются в `<ChildObjects>` корневого файла:
```xml
<Attribute uuid="<UUID>">
<Properties>
<Name><ИмяРеквизита></Name>
<Synonym/>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
</Properties>
</Attribute>
```
#### Свойства реквизита объекта (полный перечень)
Порядок фиксирован:
| Свойство | Тип | Описание | Значение по умолчанию |
|----------|-----|----------|----------------------|
| `Name` | string | Имя реквизита | — |
| `Synonym` | LocalString | Синоним (отображаемое имя) | — |
| `Comment` | string | Комментарий | пустой |
| `Type` | TypeDescription | Тип данных | — |
| `PasswordMode` | boolean | Режим пароля | `false` |
| `Format` | string | Формат вывода | пустой |
| `EditFormat` | string | Формат редактирования | пустой |
| `ToolTip` | LocalString | Подсказка | пустой |
| `MarkNegatives` | boolean | Выделять отрицательные | `false` |
| `Mask` | string | Маска ввода | пустой |
| `MultiLine` | boolean | Многострочный | `false` |
| `ExtendedEdit` | boolean | Расширенное редактирование | `false` |
| `MinValue` | any | Минимальное значение | `xsi:nil="true"` |
| `MaxValue` | any | Максимальное значение | `xsi:nil="true"` |
| `FillChecking` | enum | Проверка заполнения | `DontCheck` |
| `ChoiceFoldersAndItems` | enum | Выбор групп и элементов | `Items` |
| `ChoiceParameterLinks` | list | Связи параметров выбора | пустой |
| `ChoiceParameters` | list | Параметры выбора | пустой |
| `QuickChoice` | enum | Быстрый выбор | `Auto` |
| `CreateOnInput` | enum | Создание при вводе | `Auto` |
| `ChoiceForm` | string | Форма выбора | пустой |
| `LinkByType` | ref | Связь по типу | пустой |
| `ChoiceHistoryOnInput` | enum | История выбора при вводе | `Auto` |
#### Типы реквизитов
| v8:Type | Описание | Квалификаторы |
|---------|----------|---------------|
| `xs:string` | Строка | `v8:StringQualifiers`: `Length`, `AllowedLength` (Variable/Fixed) |
| `xs:boolean` | Булево | — |
| `xs:decimal` | Число | `v8:NumberQualifiers`: `Digits`, `FractionDigits`, `AllowedSign` (Any/Nonnegative) |
| `xs:dateTime` | Дата | `v8:DateQualifiers`: `DateFractions` (Date/Time/DateTime) |
| `cfg:CatalogRef.<Имя>` | Ссылка на справочник | — |
| `cfg:DocumentRef.<Имя>` | Ссылка на документ | — |
| `cfg:EnumRef.<Имя>` | Ссылка на перечисление | — |
> **Примечание**: Ссылочные типы (`cfg:CatalogRef.*` и т.д.) работают **только** при наличии в информационной базе конфигурации с соответствующими объектами.
### Табличные части объекта
Если обработка имеет табличные части, они описываются в `<ChildObjects>` корневого файла:
@@ -185,14 +284,38 @@ xmlns="http://v8.1c.ru/8.3/xcf/logform"
<Attribute uuid="<UUID>">
<Properties>
<Name><ИмяРеквизита></Name>
<Synonym/>
<Comment/>
<Type>...</Type>
<!-- Остальные свойства -->
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
</Properties>
</Attribute>
</ChildObjects>
</TabularSection>
```
> **Важно**: Реквизиты табличных частей имеют 2 дополнительных свойства по сравнению с реквизитами объекта: `FillFromFillingValue` и `FillValue`. Они вставляются между `MaxValue` и `FillChecking`.
## 4. Метаданные формы (`Forms/<Имя>.xml`)
```xml
@@ -247,6 +370,7 @@ xmlns="http://v8.1c.ru/8.3/xcf/logform"
| Значение `TemplateType` | Расширение файла тела | Описание |
|---|---|---|
| `SpreadsheetDocument` | `.xml` | Табличный документ (MXL в XML) |
| `DataCompositionSchema` | `.xml` | Схема компоновки данных (СКД), см. [1c-dcs-spec.md](1c-dcs-spec.md) |
| `HTMLDocument` | `.html` | HTML-документ |
| `TextDocument` | `.txt` | Текстовый документ |
| `BinaryData` | `.bin` | Двоичные данные |
@@ -640,9 +764,11 @@ xmlns="http://v8.1c.ru/8.3/xcf/logform"
## 10. Чеклист для создания новой обработки
1. Сгенерировать UUID для каждого объекта (обработка, формы, макеты, ТЧ)
1. Сгенерировать UUID для каждого объекта (обработка, реквизиты, формы, макеты, ТЧ)
2. Создать структуру каталогов (раздел 1)
3. Создать корневой XML (раздел 3) с правильными ChildObjects
3. Создать корневой XML (раздел 3) с правильными ChildObjects:
- Порядок: Attribute → TabularSection → Form → Template
- GeneratedType для ТЧ: `DataProcessorTabularSection.<Имя>.<ТЧ>` (не `ExternalDataProcessorTabularSection`!)
4. Для каждой формы:
- Создать `<Имя>.xml` (раздел 4)
- Создать `Form.xml` (раздел 6) — проверить пространство имён!
+628
View File
@@ -0,0 +1,628 @@
# Спецификация XML-формата выгрузки внешнего отчёта 1С
Формат: XML-выгрузка внешнего отчёта (ExternalReport) из конфигуратора 1С:Предприятие 8.3.
Версия формата: `2.17`.
> **Связь с другими спецификациями**:
> - Структура каталогов, пространства имён, формат форм и макетов — идентичны [спецификации внешней обработки (EPF)](1c-epf-spec.md).
> - Формат СКД-макетов — см. [спецификацию СКД](1c-dcs-spec.md).
> - Формат форм — см. [спецификацию форм](1c-form-spec.md).
> - Формат MXL-макетов — см. [спецификацию табличного документа](mxl-dsl-spec.md).
>
> Данный документ описывает **только** отличия внешнего отчёта от внешней обработки.
## 1. Структура каталогов
```
<ИмяОтчёта>.xml # Корневой файл метаданных
<ИмяОтчёта>/
Ext/
ObjectModule.bsl # Модуль объекта (опционально)
Help.xml # Метаданные справки (опционально)
Help/
ru.html # HTML-страница справки
Forms/
<ИмяФормы>.xml # Метаданные формы
<ИмяФормы>/
Ext/
Form.xml # Описание формы
Form/
Module.bsl # Модуль формы
Templates/
<ИмяМакета>.xml # Метаданные макета
<ИмяМакета>/
Ext/
Template.<расш> # Тело макета
```
Структура полностью совпадает с EPF. Отчёт может содержать:
- 0..N реквизитов объекта (описаны в корневом XML)
- 0..N табличных частей (описаны в корневом XML)
- 0..N форм (каталог `Forms/`)
- 0..N макетов (каталог `Templates/`) — включая СКД и MXL-макеты печатных форм
- 0..1 модуль объекта (`Ext/ObjectModule.bsl`)
- 0..1 встроенная справка (`Ext/Help.xml` + `Ext/Help/<язык>.html`)
## 2. Корневой файл отчёта (`<Имя>.xml`)
### 2.1. Общая структура
```xml
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="..." version="2.17">
<ExternalReport uuid="<UUID>">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
<xr:ObjectId><UUID></xr:ObjectId>
</xr:ContainedObject>
<xr:GeneratedType name="ExternalReportObject.<Имя>" category="Object">
<xr:TypeId><UUID></xr:TypeId>
<xr:ValueId><UUID></xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name><Имя></Name>
<Synonym>...</Synonym>
<Comment/>
<DefaultForm/>
<AuxiliaryForm/>
<MainDataCompositionSchema/>
<DefaultSettingsForm/>
<AuxiliarySettingsForm/>
<DefaultVariantForm/>
<VariantsStorage/>
<SettingsStorage/>
</Properties>
<ChildObjects>
<!-- Реквизиты объекта (опционально) -->
<Attribute uuid="<UUID>">...</Attribute>
<!-- Табличные части (опционально) -->
<TabularSection uuid="<UUID>">...</TabularSection>
<!-- Формы -->
<Form><ИмяФормы></Form>
<!-- Макеты -->
<Template><ИмяМакета></Template>
</ChildObjects>
</ExternalReport>
</MetaDataObject>
```
### 2.2. Отличия от EPF
| Элемент | EPF | ERF |
|---------|-----|-----|
| Корневой элемент | `<ExternalDataProcessor>` | `<ExternalReport>` |
| ClassId | `c3831ec8-d8d5-4f93-8a22-f9bfae07327f` | `e41aff26-25cf-4bb6-b6c1-3f478a75f374` |
| GeneratedType (Object) | `ExternalDataProcessorObject.<Имя>` | `ExternalReportObject.<Имя>` |
| GeneratedType (ТЧ) | `DataProcessorTabularSection.<Имя>.<ТЧ>` | `ReportTabularSection.<Имя>.<ТЧ>` |
| GeneratedType (строка ТЧ) | `DataProcessorTabularSectionRow.<Имя>.<ТЧ>` | `ReportTabularSectionRow.<Имя>.<ТЧ>` |
| Путь к форме | `ExternalDataProcessor.<Имя>.Form.<Форма>` | `ExternalReport.<Имя>.Form.<Форма>` |
| Путь к макету | `ExternalDataProcessor.<Имя>.Template.<Макет>` | `ExternalReport.<Имя>.Template.<Макет>` |
| Тип реквизита формы | `cfg:ExternalDataProcessorObject.<Имя>` | `cfg:ExternalReportObject.<Имя>` |
### 2.3. Свойства (Properties)
Свойства EPF (`Name`, `Synonym`, `Comment`, `DefaultForm`, `AuxiliaryForm`) сохраняются. Добавляются **6 свойств**, специфичных для отчёта:
| Свойство | Описание | Пример значения |
|----------|----------|-----------------|
| `MainDataCompositionSchema` | Основная СКД отчёта. Полный путь к макету-СКД | `ExternalReport.<Имя>.Template.ОсновнаяСхемаКомпоновкиДанных` |
| `DefaultSettingsForm` | Форма настроек отчёта | `ExternalReport.<Имя>.Form.ФормаНастроек` |
| `AuxiliarySettingsForm` | Дополнительная форма настроек | (обычно пустой) |
| `DefaultVariantForm` | Форма вариантов отчёта | `ExternalReport.<Имя>.Form.ФормаВарианта` |
| `VariantsStorage` | Хранилище вариантов отчёта | `SettingsStorage.ХранилищеВариантовОтчетов` |
| `SettingsStorage` | Хранилище настроек | (обычно пустой) |
**Порядок свойств фиксирован**: Name → Synonym → Comment → DefaultForm → AuxiliaryForm → MainDataCompositionSchema → DefaultSettingsForm → AuxiliarySettingsForm → DefaultVariantForm → VariantsStorage → SettingsStorage.
Если значение отсутствует, элемент остаётся пустым (самозакрывающимся):
```xml
<DefaultForm/>
<MainDataCompositionSchema>ExternalReport.МойОтчёт.Template.ОсновнаяСхемаКомпоновкиДанных</MainDataCompositionSchema>
<VariantsStorage/>
```
## 3. Реквизиты объекта (Attribute)
В отличие от EPF (где реквизиты не документированы), внешний отчёт часто имеет реквизиты объекта. Они размещаются в `<ChildObjects>` корневого файла **перед** `<TabularSection>`, `<Form>` и `<Template>`:
```xml
<Attribute uuid="<UUID>">
<Properties>
<Name>Реквизит1</Name>
<Synonym/>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
</Properties>
</Attribute>
```
### Типы реквизитов
Типы реквизитов объекта аналогичны типам реквизитов форм:
| v8:Type | Описание | Квалификаторы |
|---------|----------|---------------|
| `xs:string` | Строка | `v8:StringQualifiers`: `Length`, `AllowedLength` |
| `xs:boolean` | Булево | — |
| `xs:decimal` | Число | `v8:NumberQualifiers`: `Digits`, `FractionDigits`, `AllowedSign` |
| `xs:dateTime` | Дата | `v8:DateQualifiers`: `DateFractions` |
| `cfg:CatalogRef.<Имя>` | Ссылка на справочник | — |
| `cfg:DocumentRef.<Имя>` | Ссылка на документ | — |
| `cfg:EnumRef.<Имя>` | Ссылка на перечисление | — |
> **Примечание**: Ссылочные типы (`cfg:CatalogRef.*` и т.д.) в контексте внешнего отчёта работают **только** при наличии в информационной базе конфигурации с соответствующими объектами.
### Свойства реквизита объекта (полный перечень)
Порядок фиксирован:
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Name` | string | Имя реквизита |
| `Synonym` | LocalString | Синоним (отображаемое имя) |
| `Comment` | string | Комментарий |
| `Type` | TypeDescription | Тип данных (см. таблицу типов выше) |
| `PasswordMode` | boolean | Режим пароля (`false`) |
| `Format` | string | Формат вывода |
| `EditFormat` | string | Формат редактирования |
| `ToolTip` | LocalString | Подсказка |
| `MarkNegatives` | boolean | Выделять отрицательные (`false`) |
| `Mask` | string | Маска ввода |
| `MultiLine` | boolean | Многострочный (`false`) |
| `ExtendedEdit` | boolean | Расширенное редактирование (`false`) |
| `MinValue` | any | Минимальное значение (`xsi:nil="true"`) |
| `MaxValue` | any | Максимальное значение (`xsi:nil="true"`) |
| `FillChecking` | enum | Проверка заполнения (`DontCheck`) |
| `ChoiceFoldersAndItems` | enum | Выбор групп и элементов (`Items`) |
| `ChoiceParameterLinks` | list | Связи параметров выбора |
| `ChoiceParameters` | list | Параметры выбора |
| `QuickChoice` | enum | Быстрый выбор (`Auto`) |
| `CreateOnInput` | enum | Создание при вводе (`Auto`) |
| `ChoiceForm` | string | Форма выбора |
| `LinkByType` | ref | Связь по типу |
| `ChoiceHistoryOnInput` | enum | История выбора при вводе (`Auto`) |
## 4. Табличные части (TabularSection)
Структура полностью аналогична EPF, отличаются только имена GeneratedType:
```xml
<TabularSection uuid="<UUID>">
<InternalInfo>
<xr:GeneratedType name="ReportTabularSection.<ИмяОтчёта>.<ИмяТЧ>" category="TabularSection">
<xr:TypeId><UUID></xr:TypeId>
<xr:ValueId><UUID></xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ReportTabularSectionRow.<ИмяОтчёта>.<ИмяТЧ>" category="TabularSectionRow">
<xr:TypeId><UUID></xr:TypeId>
<xr:ValueId><UUID></xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name><ИмяТЧ></Name>
<Synonym/>
<Comment/>
<ToolTip/>
<FillChecking>DontCheck</FillChecking>
</Properties>
<ChildObjects>
<Attribute uuid="<UUID>">
<Properties>
<!-- Те же свойства, что у реквизитов объекта, -->
<!-- плюс два дополнительных: -->
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:nil="true"/>
<!-- ... остальные совпадают -->
</Properties>
</Attribute>
</ChildObjects>
</TabularSection>
```
**Важно**: Реквизиты табличной части имеют 2 дополнительных свойства по сравнению с реквизитами объекта:
- `FillFromFillingValue` — заполнять из значения заполнения (`false`)
- `FillValue` — значение заполнения (`xsi:nil="true"` или `xsi:type="xs:string"` и т.д.)
Эти свойства вставляются между `MaxValue` и `FillChecking`.
## 5. Порядок элементов в ChildObjects
Порядок дочерних объектов **фиксирован**:
1. `<Attribute>` — реквизиты объекта (0..N)
2. `<TabularSection>` — табличные части (0..N)
3. `<Form>` — формы (0..N)
4. `<Template>` — макеты (0..N)
## 6. Формы отчёта
### 6.1. Метаданные формы (`Forms/<Имя>.xml`)
Формат метаданных формы полностью совпадает с EPF — см. [спецификацию EPF, раздел 4](1c-epf-spec.md).
### 6.2. Специфика Form.xml для отчётов
Формы отчёта имеют дополнительные свойства в `<Form>`, которых нет у форм обработки:
| Свойство | Описание | Допустимые значения |
|----------|----------|---------------------|
| `ReportFormType` | Тип формы отчёта | `Main`, `Settings`, `Variant` |
| `ReportResult` | Имя реквизита-результата | Имя реквизита типа SpreadsheetDocument |
| `DetailsData` | Имя реквизита данных расшифровки | Имя строкового реквизита |
| `CustomSettingsFolder` | Группа пользовательских настроек | Имя элемента UsualGroup на форме |
Эти свойства размещаются в начале `<Form>`, после `<CommandBarLocation>` и до `<AutoCommandBar>`.
### 6.3. Основная форма отчёта (ReportFormType = Main)
Форма для отображения результата отчёта.
```xml
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
<CommandBarLocation>None</CommandBarLocation>
<ReportResult>Результат</ReportResult>
<DetailsData>ДанныеРасшифровки</DetailsData>
<ReportFormType>Main</ReportFormType>
<CustomSettingsFolder>КомпоновщикНастроекПользовательскиеНастройки</CustomSettingsFolder>
<AutoCommandBar name="" id="-1">
<Autofill>false</Autofill>
</AutoCommandBar>
<ChildItems>
<CommandBar name="ОсновнаяКоманднаяПанель" id="1">
<Title>...</Title>
<CommandSource>Form</CommandSource>
<ExtendedTooltip name="ОсновнаяКоманднаяПанельРасширеннаяПодсказка" id="2"/>
</CommandBar>
<UsualGroup name="КомпоновщикНастроекПользовательскиеНастройки" id="3">
<Title>...</Title>
<VerticalStretch>false</VerticalStretch>
<Group>Vertical</Group>
<ShowTitle>false</ShowTitle>
<ExtendedTooltip name="КомпоновщикНастроекПользовательскиеНастройкиРасширеннаяПодсказка" id="4"/>
</UsualGroup>
<SpreadSheetDocumentField name="Результат" id="5">
<DataPath>Результат</DataPath>
<DefaultItem>true</DefaultItem>
<TitleLocation>None</TitleLocation>
<Width>100</Width>
<ContextMenu name="РезультатКонтекстноеМеню" id="6"/>
<ExtendedTooltip name="РезультатРасширеннаяПодсказка" id="7"/>
</SpreadSheetDocumentField>
</ChildItems>
<Attributes>
<Attribute name="Отчет" id="1">
<Type>
<v8:Type>cfg:ExternalReportObject.<ИмяОтчёта></v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
<Attribute name="Результат" id="2">
<Title>...</Title>
<Type>
<v8:Type xmlns:mxl="http://v8.1c.ru/8.2/data/spreadsheet">mxl:SpreadsheetDocument</v8:Type>
</Type>
</Attribute>
<Attribute name="ДанныеРасшифровки" id="3">
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
</Attribute>
</Attributes>
</Form>
```
**Ключевые элементы основной формы отчёта:**
| Элемент | Описание |
|---------|----------|
| `SpreadSheetDocumentField` | Поле табличного документа для вывода результата. Привязано к реквизиту типа `mxl:SpreadsheetDocument` |
| Реквизит `Отчет` | Основной реквизит (`MainAttribute=true`), тип `cfg:ExternalReportObject.<Имя>` |
| Реквизит `Результат` | Табличный документ. Тип `mxl:SpreadsheetDocument` (требует дополнительный namespace `xmlns:mxl`) |
| Реквизит `ДанныеРасшифровки` | Строка неограниченной длины для данных расшифровки |
| Группа `КомпоновщикНастроекПользовательскиеНастройки` | Контейнер для пользовательских настроек СКД |
### 6.4. Форма настроек (ReportFormType = Settings)
Форма настроек отчёта для пользователя.
```xml
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
<CommandBarLocation>Bottom</CommandBarLocation>
<VerticalScroll>useIfNecessary</VerticalScroll>
<ReportFormType>Settings</ReportFormType>
<CustomSettingsFolder>КомпоновщикНастроекПользовательскиеНастройки</CustomSettingsFolder>
<AutoCommandBar name="" id="-1">
<HorizontalAlign>Right</HorizontalAlign>
</AutoCommandBar>
<ChildItems>
<UsualGroup name="КомпоновщикНастроекПользовательскиеНастройки" id="1">
<Title>...</Title>
<Group>Vertical</Group>
<Representation>None</Representation>
<ShowTitle>false</ShowTitle>
<ExtendedTooltip name="КомпоновщикНастроекПользовательскиеНастройкиРасширеннаяПодсказка" id="2"/>
</UsualGroup>
</ChildItems>
<Attributes>
<Attribute name="Отчет" id="1">
<Type>
<v8:Type>cfg:ExternalReportObject.<ИмяОтчёта></v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
</Attributes>
</Form>
```
### 6.5. Форма варианта (ReportFormType = Variant)
Форма для настройки варианта отчёта (структуры, группировок, фильтров).
```xml
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
<CommandBarLocation>Bottom</CommandBarLocation>
<CollapseItemsByImportanceVariant>DontUse</CollapseItemsByImportanceVariant>
<ReportFormType>Variant</ReportFormType>
<AutoCommandBar name="" id="-1">
<HorizontalAlign>Right</HorizontalAlign>
</AutoCommandBar>
<ChildItems>
<Table name="КомпоновщикНастроекНастройки" id="1">
<Representation>Tree</Representation>
<DataPath>Отчет.SettingsComposer.Settings</DataPath>
<!-- ... элементы управления деревом настроек -->
</Table>
<!-- Страницы настроек (параметры, поля, фильтры и т.д.) -->
</ChildItems>
<!-- ... -->
</Form>
```
Форма варианта обычно содержит сложную структуру с деревом настроек (`Table` с `Representation=Tree` и `DataPath=Отчет.SettingsComposer.Settings`) и множеством страниц для редактирования параметров, полей, фильтров, сортировки и условного оформления.
### 6.6. Элемент SpreadSheetDocumentField
Специфичный для отчётов элемент формы — поле табличного документа. Используется для отображения результата отчёта.
```xml
<SpreadSheetDocumentField name="Результат" id="5">
<DataPath>Результат</DataPath>
<DefaultItem>true</DefaultItem>
<TitleLocation>None</TitleLocation>
<Width>100</Width>
<ContextMenu name="РезультатКонтекстноеМеню" id="6"/>
<ExtendedTooltip name="РезультатРасширеннаяПодсказка" id="7"/>
</SpreadSheetDocumentField>
```
| Свойство | Описание |
|----------|----------|
| `DataPath` | Путь к реквизиту типа SpreadsheetDocument |
| `DefaultItem` | Элемент по умолчанию (`true`) |
| `TitleLocation` | Расположение заголовка (`None`) |
| `Width` | Ширина в символах |
## 7. Модуль объекта
### 7.1. Событие ПриКомпоновкеРезультата
Основное событие модуля объекта отчёта. Вызывается платформой при формировании результата. Позволяет перехватить стандартную обработку СКД.
```bsl
Процедура ПриКомпоновкеРезультата(ДокументРезультат, ДанныеРасшифровки, СтандартнаяОбработка)
// СтандартнаяОбработка = Ложь; // отключить стандартное формирование по СКД
// Собственная логика формирования отчёта
КонецПроцедуры
```
Параметры:
- `ДокументРезультат` — табличный документ для вывода результата
- `ДанныеРасшифровки` — данные расшифровки
- `СтандартнаяОбработка` — если установить `Ложь`, платформа не будет сама формировать отчёт по СКД
### 7.2. Директива условной компиляции
В ERP-отчётах модуль объекта часто обёрнут в директиву условной компиляции:
```bsl
#Если Сервер Или ТолстыйКлиентОбычноеПриложение Или ВнешнееСоединение Тогда
// ... весь код модуля ...
#КонецЕсли
```
### 7.3. Типичные процедуры модуля объекта отчёта
| Процедура | Описание |
|-----------|----------|
| `ПриКомпоновкеРезультата` | Событие формирования результата |
| `ИнициализироватьОтчет` | Инициализация отчёта (экспорт, для БСП) |
| `ОпределитьНастройкиФормы` | Определение настроек формы (экспорт, для БСП) |
| `ПередЗагрузкойНастроекВКомпоновщик` | Предобработка настроек (экспорт, для БСП) |
## 8. Макеты (Templates)
### 8.1. СКД-макет (DataCompositionSchema)
Основной макет отчёта. Обязателен, если указан `MainDataCompositionSchema`.
```xml
<!-- Метаданные: Templates/ОсновнаяСхемаКомпоновкиДанных.xml -->
<Template uuid="<UUID>">
<Properties>
<Name>ОсновнаяСхемаКомпоновкиДанных</Name>
<Synonym>...</Synonym>
<Comment/>
<TemplateType>DataCompositionSchema</TemplateType>
</Properties>
</Template>
```
Тело СКД: `Templates/ОсновнаяСхемаКомпоновкиДанных/Ext/Template.xml` — формат описан в [спецификации СКД](1c-dcs-spec.md).
### 8.2. MXL-макеты печатных форм (SpreadsheetDocument)
Отчёты часто содержат MXL-макеты для вывода печатных форм.
```xml
<!-- Метаданные: Templates/ПФ_MXL_КарточкаУчета.xml -->
<Template uuid="<UUID>">
<Properties>
<Name>ПФ_MXL_КарточкаУчета</Name>
<Synonym>...</Synonym>
<Comment/>
<TemplateType>SpreadsheetDocument</TemplateType>
</Properties>
</Template>
```
Тело макета: `Templates/ПФ_MXL_КарточкаУчета/Ext/Template.xml` — формат MXL.
Конвенция именования MXL-макетов: `ПФ_MXL_<НазваниеПечатнойФормы>`.
## 9. Сравнение с отчётом конфигурации (Report)
| Аспект | ExternalReport (ERF) | Report (в конфигурации) |
|--------|---------------------|------------------------|
| Корневой элемент | `<ExternalReport>` | `<Report>` |
| ClassId | `e41aff26-25cf-4bb6-b6c1-3f478a75f374` | (нет ContainedObject) |
| GeneratedType (Object) | `ExternalReportObject.<Имя>` | `ReportObject.<Имя>` |
| GeneratedType (Manager) | — | `ReportManager.<Имя>` |
| Тип реквизита формы | `cfg:ExternalReportObject.<Имя>` | `cfg:ReportObject.<Имя>` |
| Дополнительные свойства | — | `UseStandardCommands`, `IncludeHelpInContents`, `ExtendedPresentation`, `Explanation` |
| Формы по умолчанию | Могут ссылаться на свои формы | Могут ссылаться на `CommonForm.*` |
| Хранение | Файл `.erf` | В составе конфигурации |
## 10. Минимальный пример
### Пустой отчёт (без СКД, форм, модулей)
```xml
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"
xmlns:app="http://v8.1c.ru/8.2/managed-application/core"
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"
xmlns:style="http://v8.1c.ru/8.1/data/ui/style"
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
version="2.17">
<ExternalReport uuid="b38bc179-9b8a-4eb3-9422-96c6eded1ac3">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>e41aff26-25cf-4bb6-b6c1-3f478a75f374</xr:ClassId>
<xr:ObjectId>38f084a4-47ce-4e67-ab4b-ac6323b9da08</xr:ObjectId>
</xr:ContainedObject>
<xr:GeneratedType name="ExternalReportObject.МойОтчёт" category="Object">
<xr:TypeId>1fd37c7e-ade2-47ac-8dae-3fafeec96943</xr:TypeId>
<xr:ValueId>b85e1756-f044-4907-b4bd-75a57649c486</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>МойОтчёт</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Мой отчёт</v8:content>
</v8:item>
</Synonym>
<Comment/>
<DefaultForm/>
<AuxiliaryForm/>
<MainDataCompositionSchema/>
<DefaultSettingsForm/>
<AuxiliarySettingsForm/>
<DefaultVariantForm/>
<VariantsStorage/>
<SettingsStorage/>
</Properties>
<ChildObjects/>
</ExternalReport>
</MetaDataObject>
```
### Отчёт с СКД
Добавляется ссылка на СКД в `MainDataCompositionSchema` и макет-СКД в `ChildObjects`:
```xml
<Properties>
...
<MainDataCompositionSchema>ExternalReport.МойОтчёт.Template.ОсновнаяСхемаКомпоновкиДанных</MainDataCompositionSchema>
...
</Properties>
<ChildObjects>
<Template>ОсновнаяСхемаКомпоновкиДанных</Template>
</ChildObjects>
```
Плюс файлы:
- `Templates/ОсновнаяСхемаКомпоновкиДанных.xml` (метаданные, `TemplateType=DataCompositionSchema`)
- `Templates/ОсновнаяСхемаКомпоновкиДанных/Ext/Template.xml` (тело СКД)
## 11. Чеклист для создания внешнего отчёта
1. Сгенерировать UUID для каждого объекта (отчёт, реквизиты, ТЧ, формы, макеты)
2. Создать структуру каталогов (раздел 1)
3. Создать корневой XML (раздел 2) с:
- `ClassId = e41aff26-25cf-4bb6-b6c1-3f478a75f374`
- `GeneratedType name="ExternalReportObject.<Имя>"`
- Корректными путями в `MainDataCompositionSchema`, `DefaultForm` и др.
- Правильным порядком элементов в `ChildObjects`
4. Создать СКД-макет (раздел 8.1):
- Метаданные с `TemplateType=DataCompositionSchema`
- Тело СКД по [спецификации СКД](1c-dcs-spec.md)
5. При необходимости создать формы (раздел 6):
- Указать `ReportFormType` (`Main` / `Settings` / `Variant`)
- Основная форма: `ReportResult`, `DetailsData`, `SpreadSheetDocumentField`
- Основной реквизит: `cfg:ExternalReportObject.<Имя>`
6. При необходимости создать `ObjectModule.bsl` (раздел 7)
7. При необходимости создать MXL-макеты печатных форм (раздел 8.2)
8. Проверить:
- Все пути используют префикс `ExternalReport.<Имя>` (не `ExternalDataProcessor`)
- Тип основного реквизита формы: `cfg:ExternalReportObject.<Имя>`
- `MainDataCompositionSchema` соответствует реальному макету в `ChildObjects`
- Порядок в `ChildObjects`: Attribute → TabularSection → Form → Template
- Все UUID уникальны
+960
View File
@@ -0,0 +1,960 @@
# Спецификация формата выгрузки расширений конфигурации 1С (CFE)
Формат: XML-выгрузка расширения конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Расширения → Выгрузить расширение в файлы).
Версия формата: `2.17` (платформа 8.3.178.3.24).
> **Связанные спецификации:**
> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md)
> - Объекты метаданных — [1c-config-objects-spec.md](1c-config-objects-spec.md)
> - Подсистемы — [1c-subsystem-spec.md](1c-subsystem-spec.md)
> - Управляемые формы — [1c-form-spec.md](1c-form-spec.md)
> - Роли — [1c-role-spec.md](1c-role-spec.md)
> - Сводный индекс — [1c-specs-index.md](1c-specs-index.md)
---
## 1. Общая структура выгрузки расширения
```
Configuration.xml # Корневой файл — свойства и состав расширения
ConfigDumpInfo.xml # Служебный файл — версии объектов
Languages/ # Языки (всегда заимствованные)
Roles/ # Роли (собственные)
Subsystems/ # Подсистемы (собственные или заимствованные)
CommonModules/ # Общие модули
CommonPictures/ # Общие картинки
CommonCommands/ # Общие команды
Catalogs/ # Справочники
Documents/ # Документы
Enums/ # Перечисления
... # Другие типы объектов
```
### Ключевые отличия от конфигурации
| Аспект | Конфигурация | Расширение |
|--------|-------------|------------|
| Корневой `Ext/` | Есть (модули, интерфейс, справка) | **Нет** |
| `ObjectBelonging` в Properties | Нет | `Adopted` (всегда) |
| `ConfigurationExtensionPurpose` | Нет | `Patch` / `Customization` / `AddOn` |
| `KeepMappingToExtendedConfigurationObjectsByIDs` | Нет | `true` / `false` |
| `NamePrefix` | Пустой или нет | Префикс для собственных объектов |
| `CompatibilityMode` | Да | Нет (используется `ConfigurationExtensionCompatibilityMode`) |
| Свойства режимов работы | Полный набор | Сокращённый набор |
| Объекты в ChildObjects | Только собственные | Собственные **и заимствованные** |
---
## 2. Configuration.xml — корневой файл расширения
### 2.1. Общая структура
```xml
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:app="http://v8.1c.ru/8.2/managed-application/core"
... version="2.17">
<Configuration uuid="...">
<InternalInfo>...</InternalInfo>
<Properties>...</Properties>
<ChildObjects>...</ChildObjects>
</Configuration>
</MetaDataObject>
```
Пространства имён и корневой элемент идентичны конфигурации. Атрибут `version` соответствует версии формата выгрузки.
### 2.2. InternalInfo
Содержит 7 записей `xr:ContainedObject` — аналогично конфигурации. ClassId фиксированные, ObjectId уникальны для каждого расширения.
### 2.3. Properties — свойства расширения
Свойства идут в фиксированном порядке. Набор свойств **отличается** от конфигурации — часть свойств специфична для расширений, часть свойств конфигурации отсутствует.
#### Специфичные свойства расширения
| Свойство | Тип | Описание |
|----------|-----|----------|
| `ObjectBelonging` | enum | Всегда `Adopted` — расширение «принято» к основной конфигурации |
| `ConfigurationExtensionPurpose` | enum | Назначение расширения: `Patch` (исправление), `Customization` (адаптация), `AddOn` (дополнение) |
| `KeepMappingToExtendedConfigurationObjectsByIDs` | `xs:boolean` | Сохранять привязку к объектам по идентификаторам |
| `NamePrefix` | `xs:string` | Префикс имён собственных объектов (напр. `Расш1_`, `МоёРасш_`) |
| `ConfigurationExtensionCompatibilityMode` | enum | Режим совместимости расширения (`Version8_3_17`, `Version8_3_24`, ...) |
#### Общие свойства (совпадают с конфигурацией)
| Свойство | Тип | Описание |
|----------|-----|----------|
| `Name` | `xs:string` | Имя расширения (идентификатор) |
| `Synonym` | `LocalString` | Отображаемое имя |
| `Comment` | `xs:string` | Комментарий |
| `DefaultRunMode` | enum | Режим запуска (`ManagedApplication`) |
| `UsePurposes` | list | Назначения (`PlatformApplication`) |
| `ScriptVariant` | enum | Язык скриптов (`Russian` / `English`) |
| `DefaultRoles` | list | Роли по умолчанию |
| `Vendor` | `xs:string` | Поставщик |
| `Version` | `xs:string` | Версия расширения |
| `DefaultLanguage` | ref | Язык по умолчанию (`Language.Русский`) |
| `BriefInformation` | `LocalString` | Краткая информация |
| `DetailedInformation` | `LocalString` | Подробная информация |
| `Copyright` | `LocalString` | Авторские права |
| `VendorInformationAddress` | `LocalString` | Адрес поставщика |
| `ConfigurationInformationAddress` | `LocalString` | Адрес информации |
| `InterfaceCompatibilityMode` | enum | Совместимость интерфейса |
> **Примечание:** Свойства `DefaultRunMode`, `UsePurposes`, `DefaultRoles`, `DefaultLanguage`, `InterfaceCompatibilityMode` **опциональны** — могут отсутствовать в расширении (в отличие от конфигурации, где они обязательны).
#### Свойства конфигурации, отсутствующие в расширении
В расширениях **нет** следующих свойств:
- `CompatibilityMode` (заменено на `ConfigurationExtensionCompatibilityMode`)
- `DataLockControlMode`
- `ObjectAutonumerationMode`
- `ModalityUseMode`
- `SynchronousPlatformExtensionAndAddInCallUseMode`
- `DatabaseTablespacesUseMode`
- `MainClientApplicationWindowMode`
- `UpdateCatalogAddress`
- `IncludeHelpInContents`
- `UseManagedFormInOrdinaryApplication`
- `UseOrdinaryFormInManagedApplication`
- `Content`
- `StandaloneConfigurationRestrictionRoles`
### 2.4. Порядок свойств
```xml
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяРасширения</Name>
<Synonym>...</Synonym>
<Comment/>
<ConfigurationExtensionPurpose>Patch</ConfigurationExtensionPurpose>
<KeepMappingToExtendedConfigurationObjectsByIDs>true</KeepMappingToExtendedConfigurationObjectsByIDs>
<NamePrefix>Расш1_</NamePrefix>
<ConfigurationExtensionCompatibilityMode>Version8_3_17</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <!-- опционально -->
<UsePurposes>...</UsePurposes> <!-- опционально -->
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles>...</DefaultRoles> <!-- опционально -->
<Vendor/>
<Version/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <!-- опционально -->
<BriefInformation/>
<DetailedInformation/>
<Copyright/>
<VendorInformationAddress/>
<ConfigurationInformationAddress/>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <!-- опционально -->
</Properties>
```
### 2.5. ChildObjects — состав расширения
Содержит как **собственные** объекты расширения, так и **заимствованные** из основной конфигурации. Порядок типов аналогичен конфигурации.
```xml
<ChildObjects>
<Language>Русский</Language> <!-- заимствованный -->
<Subsystem>Расш1_МояПодсистема</Subsystem> <!-- собственный -->
<CommonPicture>Расш1_МояКартинка</CommonPicture> <!-- собственный -->
<Role>Расш1_ОсновнаяРоль</Role> <!-- собственный -->
<CommonModule>Расш1_МодульСервер</CommonModule> <!-- собственный -->
<CommonModule>ОбщийМодульКонфигурации</CommonModule> <!-- заимствованный -->
<Catalog>Контрагенты</Catalog> <!-- заимствованный -->
<Catalog>Расш1_Проекты</Catalog> <!-- собственный -->
<Enum>Расш1_ВидыДокументов</Enum> <!-- собственный -->
<InformationRegister>Расш1_ДатыРабот</InformationRegister> <!-- собственный -->
</ChildObjects>
```
В `ChildObjects` не видно различие между собственными и заимствованными — оно определяется по содержимому XML-файла объекта (свойство `ObjectBelonging` и наличие `ExtendedConfigurationObject`).
**Правило именования:** собственные объекты расширения обычно имеют `NamePrefix` в начале имени (напр. `Расш1_Справочник1`, `Расш1_Проекты`), заимствованные — имя объекта из основной конфигурации без префикса (напр. `Контрагенты`, `Валюты`).
---
## 3. ConfigDumpInfo.xml
Формат идентичен конфигурации:
```xml
<?xml version="1.0" encoding="UTF-8"?>
<ConfigDumpInfo xmlns="http://v8.1c.ru/8.3/xcf/dumpinfo"
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
format="Hierarchical" version="2.17">
<ConfigVersions>
<Metadata name="Configuration.ИмяРасширения" id="uuid" configVersion="sha1"/>
<Metadata name="Language.Русский" id="uuid" configVersion="sha1"/>
<Metadata name="Role.Расш1_ОсновнаяРоль" id="uuid" configVersion="sha1"/>
<!-- ... все объекты расширения ... -->
</ConfigVersions>
</ConfigDumpInfo>
```
Включает записи для **всех** объектов расширения (и собственных, и заимствованных). Атрибут `configVersion` — 40-символьный SHA1-хеш версии объекта.
---
## 4. Заимствованные и собственные объекты
Расширение может содержать два типа объектов:
### 4.1. Заимствованные объекты (Adopted)
Объекты, существующие в основной конфигурации, которые расширение модифицирует или дополняет.
**Признаки:**
- `<ObjectBelonging>Adopted</ObjectBelonging>` в Properties
- `<ExtendedConfigurationObject>uuid</ExtendedConfigurationObject>` — UUID объекта в основной конфигурации
- Минимальный набор свойств (только те, что изменяются)
```xml
<Catalog uuid="81de7e56-...">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Валюты" category="Object">...</xr:GeneratedType>
<!-- ... стандартные GeneratedType ... -->
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Валюты</Name>
<Comment/>
<ExtendedConfigurationObject>7aadbb67-...</ExtendedConfigurationObject>
</Properties>
<ChildObjects>
<!-- заимствованные и собственные реквизиты, формы -->
</ChildObjects>
</Catalog>
```
Заимствованные объекты **не содержат** полного набора свойств — только `ObjectBelonging`, `Name`, `Comment`, `ExtendedConfigurationObject` и те свойства, которые расширение изменяет (напр. `CodeLength`, `DefaultListForm`).
### 4.2. Собственные объекты (Own)
Объекты, созданные непосредственно в расширении.
**Признаки:**
- **Нет** элемента `ObjectBelonging`
- **Нет** элемента `ExtendedConfigurationObject`
- Полный набор свойств (как в объектах конфигурации)
- Имя обычно начинается с `NamePrefix` расширения
```xml
<Catalog uuid="7dcd4d14-...">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Расш5_Справочник1" category="Object">...</xr:GeneratedType>
<!-- ... -->
</InternalInfo>
<Properties>
<Name>Расш5_Справочник1</Name>
<Synonym/>
<Comment/>
<Hierarchical>false</Hierarchical>
<CodeLength>9</CodeLength>
<!-- ... полный набор свойств как в конфигурации ... -->
</Properties>
<ChildObjects/>
</Catalog>
```
Формат полностью совпадает с форматом объектов конфигурации (см. [1c-config-objects-spec.md](1c-config-objects-spec.md)).
---
## 5. Заимствованные дочерние элементы
Дочерние элементы заимствованных объектов (реквизиты, табличные части, значения перечислений) также маркируются как заимствованные или собственные.
### 5.1. Заимствованные реквизиты
```xml
<Attribute uuid="259e5f94-...">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ОсновнаяВалюта</Name>
<Comment/>
<ExtendedConfigurationObject>206abcd3-...</ExtendedConfigurationObject>
<Type>
<v8:Type>cfg:CatalogRef.Валюты</v8:Type>
</Type>
</Properties>
</Attribute>
```
Заимствованный реквизит содержит `ObjectBelonging: Adopted` и `ExtendedConfigurationObject`. Набор свойств минимальный (Name, Comment, ExtendedConfigurationObject, Type).
### 5.2. Собственные реквизиты в заимствованном объекте
```xml
<Attribute uuid="7fabdcb4-...">
<Properties>
<Name>Расш5_Реквизит1</Name>
<Synonym/>
<Comment/>
<Type>
<v8:Type>cfg:CatalogRef.Расш5_Справочник1</v8:Type>
</Type>
<PasswordMode>false</PasswordMode>
<!-- ... полный набор свойств ... -->
</Properties>
</Attribute>
```
Собственный реквизит **не имеет** `ObjectBelonging` и `ExtendedConfigurationObject`. Содержит полный набор свойств.
### 5.3. Заимствованные значения перечислений
```xml
<EnumValue uuid="9bc7380f-...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>НаценкаНаКурсДругойВалюты</Name>
<ExtendedConfigurationObject>c9ab3890-...</ExtendedConfigurationObject>
<Synonym>...</Synonym>
<Comment/>
</Properties>
</EnumValue>
```
### 5.4. Заимствованные формы
Метаданные формы (файл `.xml` в каталоге `Forms/`):
```xml
<Form uuid="8fcebcc1-...">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ФормаСписка</Name>
<Comment/>
<ExtendedConfigurationObject>5f91b00f-...</ExtendedConfigurationObject>
<FormType>Managed</FormType>
</Properties>
</Form>
```
Содержимое формы (расширение) хранится в `Forms/ФормаСписка/Ext/Form.xml`, модуль формы — в `Forms/ФормаСписка/Ext/Form/Module.bsl`.
---
## 6. Расширение свойств (xr:PropertyState и xr:ExtendedProperty)
Расширения могут изменять свойства заимствованных реквизитов. Для этого используются специальные XML-конструкции.
### 6.1. PropertyState — уведомление об изменении
Элемент `xr:PropertyState` в `InternalInfo` реквизита указывает, что свойство было изменено расширением.
```xml
<Attribute uuid="a1752169-...">
<InternalInfo>
<xr:PropertyState>
<xr:Property>Type</xr:Property>
<xr:State>Notify</xr:State>
</xr:PropertyState>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Наценка</Name>
<ExtendedConfigurationObject>87429f11-...</ExtendedConfigurationObject>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</v8:Digits>
<v8:FractionDigits>2</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</Type>
</Properties>
</Attribute>
```
Значения `xr:State`:
| Значение | Описание |
|----------|----------|
| `Notify` | Свойство изменено расширением, платформа выводит предупреждение |
| `MultiState` | Свойство расширено (тип отличается от основной конфигурации) |
### 6.2. ExtendedProperty — расширение типа
Когда расширение изменяет тип реквизита, используется конструкция `xr:ExtendedProperty`:
```xml
<Type xsi:type="xr:ExtendedProperty">
<xr:ExtendValue xsi:type="v8:TypeDescription">
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>60</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</xr:ExtendValue>
</Type>
```
Свойство `Type` получает атрибут `xsi:type="xr:ExtendedProperty"`, а значение оборачивается в `xr:ExtendValue`. При этом в `InternalInfo` указывается `<xr:State>MultiState</xr:State>`.
---
## 7. Модули в расширениях
### 7.1. Типы модулей
Расширения поддерживают те же типы модулей, что и конфигурация:
| Модуль | Файл | Для каких объектов |
|--------|------|--------------------|
| Модуль объекта | `Ext/ObjectModule.bsl` | Справочники, документы, обработки |
| Модуль менеджера | `Ext/ManagerModule.bsl` | Справочники, документы, регистры |
| Модуль набора записей | `Ext/RecordSetModule.bsl` | Регистры |
| Модуль формы | `Forms/Имя/Ext/Form/Module.bsl` | Формы |
| Общий модуль | `CommonModules/Имя/Ext/Module.bsl` | Общие модули |
| Модуль команды | `Commands/Имя/Ext/CommandModule.bsl` | Команды |
### 7.2. Декораторы перехвата
Модули расширений используют специальные **аннотации-декораторы** для перехвата вызовов процедур основной конфигурации:
| Декоратор | Описание |
|-----------|----------|
| `&Перед("ИмяПроцедуры")` | Выполняется **до** оригинальной процедуры |
| `&После("ИмяПроцедуры")` | Выполняется **после** оригинальной процедуры |
| `&Вместо("ИмяПроцедуры")` | **Заменяет** оригинальную процедуру |
| `&ИзменениеИКонтроль("ИмяПроцедуры")` | Копия с контролем изменений (diff-маркеры) |
#### Пример &Перед / &После
```bsl
&НаКлиенте
&Перед("ПодборИзКлассификатора")
Процедура Расш5_ПодборИзКлассификатораПеред(Команда)
// Код выполняется ДО оригинальной процедуры
КонецПроцедуры
&НаКлиенте
&После("ПодборИзКлассификатора")
Процедура Расш5_ПодборИзКлассификатораПосле(Команда)
// Код выполняется ПОСЛЕ оригинальной процедуры
КонецПроцедуры
```
#### Пример &Вместо
```bsl
&НаСервере
&Вместо("ЗаполнитьПодменюПараметрыПрописиВалюты")
Процедура Расш5_ЗаполнитьПодменюПараметрыПрописиВалюты()
// Полная замена оригинальной процедуры
// ПродолжитьВызов() — вызов оригинальной реализации
ПродолжитьВызов();
КонецПроцедуры
```
#### Пример &ИзменениеИКонтроль
```bsl
&ИзменениеИКонтроль("РеквизитыРедактируемыеВГрупповойОбработке")
Функция Расш5_РеквизитыРедактируемыеВГрупповойОбработке()
Результат = Новый Массив;
Результат.Добавить("СпособУстановкиКурса");
#Удаление
Результат.Добавить("ФормулаРасчетаКурса");
#КонецУдаления
#Вставка
Результат.Добавить("НоваяФормулаРасчетаКурса");
#КонецВставки
Возврат Результат;
КонецФункции
```
### 7.3. Diff-маркеры в коде
Внутри процедур с декоратором `&ИзменениеИКонтроль` используются diff-маркеры для отслеживания изменений:
| Маркер | Описание |
|--------|----------|
| `#Удаление` | Начало блока удалённого кода |
| `#КонецУдаления` | Конец блока удалённого кода |
| `#Вставка` | Начало блока вставленного кода |
| `#КонецВставки` | Конец блока вставленного кода |
Маркеры могут быть вложенными и чередующимися — типичный паттерн «удалить → вставить»:
```bsl
#Удаление
ИначеЕсли Выборка.Дата > Дата Тогда
#КонецУдаления
#Вставка
ИначеЕсли Выборка.Дата < Дата Тогда
#КонецВставки
```
### 7.4. Именование процедур расширения
Собственные процедуры расширения именуются с `NamePrefix`:
- `Расш5_ПрочитатьАрхивВнутр()` — при `NamePrefix = Расш5_`
- `МоёРасш_ПроверитьДату()` — при `NamePrefix = МоёРасш_`
- `Расш1_ПриОпределенииНастроек()` — при `NamePrefix = Расш1_`
---
## 8. Предопределённые элементы в расширениях
Расширения могут добавлять предопределённые элементы к заимствованным справочникам. Файл: `Каталог/Ext/Predefined.xml`.
```xml
<?xml version="1.0" encoding="UTF-8"?>
<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:type="CatalogPredefinedItems" version="2.17">
<Item id="9b751d8b-...">
<Name>НовыйЭлемент</Name>
<Code>000000001</Code>
<Description>Новый элемент</Description>
<IsFolder>false</IsFolder>
<ExtensionState>Native</ExtensionState>
</Item>
</PredefinedData>
```
### Отличие от Predefined.xml конфигурации
| Аспект | Конфигурация | Расширение |
|--------|-------------|------------|
| Пространство имён | `http://v8.1c.ru/8.3/xcf/predef` (то же) | То же |
| `ExtensionState` | Нет | `Native` — элемент создан расширением |
---
## 9. Языки (Languages/)
Язык в расширении **всегда** заимствованный:
```xml
<Language uuid="9453bb96-...">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Русский</Name>
<Comment/>
<ExtendedConfigurationObject>0663bf5b-...</ExtendedConfigurationObject>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
```
UUID в `ExtendedConfigurationObject` одинаков для всех расширений одной конфигурации — это UUID языка в основной конфигурации.
---
## 10. Роли (Roles/)
### 10.1. Собственная роль расширения (без прав)
Минимальная роль без `Ext/Rights.xml`:
```xml
<Role uuid="c630865b-...">
<Properties>
<Name>Расш1_ОсновнаяРоль</Name>
<Synonym/>
<Comment/>
</Properties>
</Role>
```
Собственные роли расширений в изученных примерах **не имеют** каталога `Ext/` с `Rights.xml`. Права могут задаваться через конфигуратор.
### 10.2. DefaultRoles
Ссылки на роли по умолчанию в Configuration.xml:
```xml
<DefaultRoles>
<xr:Item xsi:type="xr:MDObjectRef">Role.Расш1_ОсновнаяРоль</xr:Item>
</DefaultRoles>
```
---
## 11. Подсистемы (Subsystems/)
### 11.1. Собственная подсистема расширения
```xml
<Subsystem uuid="...">
<Properties>
<Name>Расш1_МояПодсистема</Name>
<Synonym>...</Synonym>
<Comment/>
<Picture>
<xr:Ref>CommonPicture.Расш1_МояКартинка</xr:Ref>
</Picture>
<IncludeHelpInContents>false</IncludeHelpInContents>
<IncludeInCommandInterface>true</IncludeInCommandInterface>
<Content>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Расш1_Проекты</xr:Item>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Расш1_Задачи</xr:Item>
<xr:Item xsi:type="xr:MDObjectRef">DataProcessor.Расш1_Обработка1</xr:Item>
<!-- ... -->
</Content>
</Properties>
<ChildObjects/>
</Subsystem>
```
### 11.2. Заимствованная подсистема
```xml
<Subsystem uuid="...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Закупки</Name>
<ExtendedConfigurationObject>f230c0c7-...</ExtendedConfigurationObject>
<Content>
<!-- пустой или с добавленными объектами -->
</Content>
</Properties>
<ChildObjects>
<Subsystem>Расш1_Документы</Subsystem> <!-- дочерняя подсистема -->
</ChildObjects>
</Subsystem>
```
Заимствованная подсистема может содержать элементы `Content` (добавленные расширением команды/объекты) и дочерние подсистемы в `ChildObjects`.
### 11.3. CommandInterface в расширении
Командный интерфейс подсистемы в расширении: `Subsystems/Имя/Ext/CommandInterface.xml`.
```xml
<CommandInterface version="2.17">
<CommandsVisibility>
<xr:Command>
<xr:CommandID>Document.ЗаказНаПеремещение.StandardCommand.OpenList</xr:CommandID>
<xr:Visibility>
<xr:Common>false</xr:Common>
<xr:Value name="Role.Расш1_ОсновнаяРоль">true</xr:Value>
</xr:Visibility>
</xr:Command>
</CommandsVisibility>
<CommandsOrder>
<xr:Group name="NavigationPanelOrdinary">
<xr:CommandID>...</xr:CommandID>
</xr:Group>
</CommandsOrder>
</CommandInterface>
```
---
## 12. Общие модули (CommonModules/)
### 12.1. Заимствованный общий модуль
```xml
<CommonModule uuid="a32b77fa-...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ZipАрхивы</Name>
<ExtendedConfigurationObject>b92e2bb8-...</ExtendedConfigurationObject>
</Properties>
</CommonModule>
```
Модуль расширения: `CommonModules/ZipАрхивы/Ext/Module.bsl` — содержит процедуры с декораторами перехвата.
### 12.2. Собственный общий модуль
Формат аналогичен конфигурации (без `ObjectBelonging` и `ExtendedConfigurationObject`), со всеми свойствами (Server, ExternalConnection, ClientManagedApplication и т.д.).
---
## 13. Другие типы заимствованных объектов
### 13.1. Константы
```xml
<Constant uuid="...">
<InternalInfo>
<xr:GeneratedType name="ConstantManager.ИмяКонстанты" category="ConstantManager">...</xr:GeneratedType>
<xr:GeneratedType name="ConstantValueManager.ИмяКонстанты" category="ConstantValueManager">...</xr:GeneratedType>
<xr:GeneratedType name="ConstantValueKey.ИмяКонстанты" category="ConstantValueKey">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяКонстанты</Name>
<ExtendedConfigurationObject>81d26b82-...</ExtendedConfigurationObject>
<Type>
<v8:Type>xs:boolean</v8:Type>
</Type>
</Properties>
</Constant>
```
### 13.2. Функциональные опции
```xml
<FunctionalOption uuid="...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяФункциональнойОпции</Name>
<ExtendedConfigurationObject>d2699502-...</ExtendedConfigurationObject>
<Location>Constant.ИмяКонстанты</Location>
</Properties>
</FunctionalOption>
```
### 13.3. Определяемые типы
Заимствованный (минимальный):
```xml
<DefinedType uuid="...">
<InternalInfo>
<xr:GeneratedType name="DefinedType.ИмяОпределяемогоТипа" category="DefinedType">...</xr:GeneratedType>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяОпределяемогоТипа</Name>
</Properties>
</DefinedType>
```
Собственный (с полным описанием типа):
```xml
<DefinedType uuid="...">
<Properties>
<Name>Расш1_Координата</Name>
<Synonym>...</Synonym>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>10</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</Type>
</Properties>
</DefinedType>
```
### 13.4. Элементы стиля
```xml
<StyleItem uuid="...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяЭлементаСтиля</Name>
<ExtendedConfigurationObject>3d428bdf-...</ExtendedConfigurationObject>
<Type>Font</Type>
</Properties>
</StyleItem>
```
Тип: `Color`, `Font`, `Border`.
### 13.5. Общие картинки
```xml
<CommonPicture uuid="...">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяКартинки</Name>
</Properties>
</CommonPicture>
```
Собственные картинки могут иметь каталог `Ext/` с `Picture.xml` и файлом изображения:
```
CommonPictures/Расш1_МояКартинка/
Ext/
Picture.xml # <Picture><xr:Abs>Picture.png</xr:Abs>...</Picture>
Picture/
Picture.png # Файл изображения
```
### 13.6. Общие команды
```xml
<CommonCommand uuid="...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяКоманды</Name>
<Group>FormCommandBarImportant</Group>
</Properties>
</CommonCommand>
```
### 13.7. Планы обмена
```xml
<ExchangePlan uuid="...">
<InternalInfo>
<xr:ThisNode>c335c2b8-...</xr:ThisNode>
<xr:GeneratedType .../>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяПланаОбмена</Name>
<ExtendedConfigurationObject>0c01b26a-...</ExtendedConfigurationObject>
</Properties>
<ChildObjects/>
</ExchangePlan>
```
### 13.8. Планы счетов
```xml
<ChartOfAccounts uuid="...">
<InternalInfo>
<xr:GeneratedType .../>
</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>Хозрасчетный</Name>
<ExtendedConfigurationObject>3796bdf5-...</ExtendedConfigurationObject>
</Properties>
<ChildObjects/>
</ChartOfAccounts>
```
### 13.9. Регистры сведений
Заимствованный (минимальный):
```xml
<InformationRegister uuid="...">
<InternalInfo>...</InternalInfo>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяРегистра</Name>
<ExtendedConfigurationObject>...</ExtendedConfigurationObject>
<InformationRegisterPeriodicity>Quarter</InformationRegisterPeriodicity>
<WriteMode>Independent</WriteMode>
</Properties>
<ChildObjects>
<Resource uuid="...">
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>ИмяРесурса</Name>
<ExtendedConfigurationObject>...</ExtendedConfigurationObject>
<Type>...</Type>
</Properties>
</Resource>
</ChildObjects>
</InformationRegister>
```
Собственный регистр — полный набор свойств, аналогично конфигурации.
---
## 14. Назначения расширений (ConfigurationExtensionPurpose)
| Значение | Русское название | Описание |
|----------|-----------------|----------|
| `Patch` | Исправление | Минимальные исправления ошибок. Наибольшие ограничения |
| `Customization` | Адаптация | Доработка под требования заказчика. Средний уровень ограничений |
| `AddOn` | Дополнение | Добавление новой функциональности. Минимальные ограничения |
Назначение влияет на то, какие модификации допускает платформа при подключении расширения.
---
## 15. Структура каталогов (сводка)
### 15.1. Собственный объект (полная структура)
```
Catalogs/Расш1_Проекты.xml # Метаданные (полные)
Catalogs/Расш1_Проекты/
Ext/
ObjectModule.bsl # Модуль объекта
ManagerModule.bsl # Модуль менеджера
Predefined.xml # Предопределённые (опц.)
Forms/
ФормаЭлемента.xml # Метаданные формы
ФормаЭлемента/
Ext/
Form.xml # Содержимое формы
Form/
Module.bsl # Модуль формы
ФормаСписка.xml
ФормаСписка/
Ext/
Form.xml
Form/
Module.bsl
Templates/
ПФ_MXL_Акт.xml # Метаданные макета
ПФ_MXL_Акт/
Ext/
Template.xml # Содержимое макета
Commands/
ИмяКоманды.xml # Метаданные команды
ИмяКоманды/
Ext/
CommandModule.bsl # Модуль команды
```
### 15.2. Заимствованный объект (минимальная структура)
```
Catalogs/Валюты.xml # Метаданные (сокращённые)
Catalogs/Валюты/
Ext/
ManagerModule.bsl # Расширение модуля менеджера
Predefined.xml # Предопред. элементы (опц.)
Forms/
ФормаСписка.xml # Метаданные (сокращённые)
ФормаСписка/
Ext/
Form.xml # Расширение формы
Form/
Module.bsl # Расширение модуля формы
```
### 15.3. Минимальное расширение (пустое)
```
Configuration.xml # Корневой файл
ConfigDumpInfo.xml # Версии объектов
Languages/
Русский.xml # Язык (заимствованный)
```
### 15.4. Типичное расширение с ролью
```
Configuration.xml
ConfigDumpInfo.xml
Languages/
Русский.xml
Roles/
Расш1_ОсновнаяРоль.xml
```
---
## 16. Отличия заимствованного объекта от обычного (сводная таблица)
| Аспект | Обычный (конфигурация) | Заимствованный (расширение) | Собственный (расширение) |
|--------|----------------------|--------------------------|------------------------|
| `ObjectBelonging` | Нет | `Adopted` | Нет |
| `ExtendedConfigurationObject` | Нет | UUID объекта конфигурации | Нет |
| Набор Properties | Полный | Минимальный + изменённые | Полный |
| InternalInfo | GeneratedType | GeneratedType + PropertyState | GeneratedType |
| Реквизиты в ChildObjects | Полные | Заимствованные + собственные | Полные |
| Модули | Полные | С декораторами перехвата | Полные |
| Формы | Полные | С расширениями (Ext/) | Полные |
+172
View File
@@ -0,0 +1,172 @@
# Сводный индекс спецификаций формата XML конфигурации 1С
Единая точка входа ко всем спецификациям XML-формата выгрузки 1С:Предприятие 8.3.
---
## 1. Корневые файлы конфигурации
| Файл | Описание | Спецификация |
|------|----------|--------------|
| `Configuration.xml` | Свойства и состав конфигурации | [1c-configuration-spec.md § 2](1c-configuration-spec.md#2-configurationxml--корневой-файл-конфигурации) |
| `ConfigDumpInfo.xml` | Версии объектов (служебный) | [1c-configuration-spec.md § 3](1c-configuration-spec.md#3-configdumpinfoxml--служебный-файл-выгрузки) |
| `Ext/` | Модули, интерфейс, начальная страница | [1c-configuration-spec.md § 4](1c-configuration-spec.md#4-ext--корневой-каталог-конфигурации) |
| `Languages/` | Языки конфигурации | [1c-configuration-spec.md § 5](1c-configuration-spec.md#5-языки-languages) |
---
## 2. Объекты метаданных
Все типы объектов, встречающиеся в `ChildObjects` корневого `Configuration.xml`. Порядок соответствует порядку в XML.
### Служебные и интерфейсные
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `Language` | `Languages/` | Языки | [1c-configuration-spec.md § 5](1c-configuration-spec.md#5-языки-languages) |
| `Subsystem` | `Subsystems/` | Подсистемы | [1c-subsystem-spec.md § 3](1c-subsystem-spec.md#3-формат-подсистемы) |
| `StyleItem` | `StyleItems/` | Элементы стиля | [1c-configuration-spec.md § 6.16](1c-configuration-spec.md#616-styleitem--элемент-стиля) |
| `Style` | `Styles/` | Стили (устаревший) | [1c-configuration-spec.md § 6.17](1c-configuration-spec.md#617-style--стиль-устаревший) |
| `CommandGroup` | `CommandGroups/` | Группы команд | [1c-subsystem-spec.md § 5](1c-subsystem-spec.md#5-формат-группы-команд-commandgroup) |
### Общие объекты
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `CommonPicture` | `CommonPictures/` | Общие картинки | [1c-configuration-spec.md § 6.1](1c-configuration-spec.md#61-commonpicture--общая-картинка) |
| `SessionParameter` | `SessionParameters/` | Параметры сеанса | [1c-configuration-spec.md § 6.6](1c-configuration-spec.md#66-sessionparameter--параметр-сеанса) |
| `Role` | `Roles/` | Роли | [1c-role-spec.md § Файл метаданных](1c-role-spec.md#файл-метаданных-rolesимяролиxml) |
| `CommonTemplate` | `CommonTemplates/` | Общие макеты | [1c-configuration-spec.md § 6.2](1c-configuration-spec.md#62-commontemplate--общий-макет) |
| `FilterCriterion` | `FilterCriteria/` | Критерии отбора | [1c-configuration-spec.md § 6.11](1c-configuration-spec.md#611-filtercriterion--критерий-отбора) |
| `CommonModule` | `CommonModules/` | Общие модули | [1c-config-objects-spec.md § 21](1c-config-objects-spec.md#21-общие-модули-commonmodules) |
| `CommonAttribute` | `CommonAttributes/` | Общие реквизиты | [1c-configuration-spec.md § 6.3](1c-configuration-spec.md#63-commonattribute--общий-реквизит) |
| `CommonCommand` | `CommonCommands/` | Общие команды | [1c-subsystem-spec.md § 6](1c-subsystem-spec.md#6-формат-общей-команды-commoncommand) |
| `CommonForm` | `CommonForms/` | Общие формы | [1c-configuration-spec.md § 6.4](1c-configuration-spec.md#64-commonform--общая-форма) |
### Интеграция и сервисы
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `ExchangePlan` | `ExchangePlans/` | Планы обмена | [1c-config-objects-spec.md § 15](1c-config-objects-spec.md#15-планы-обмена-exchangeplans) |
| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-configuration-spec.md § 6.14](1c-configuration-spec.md#614-xdtopackage--xdto-пакет) |
| `WebService` | `WebServices/` | Веб-сервисы | [1c-config-objects-spec.md § 25](1c-config-objects-spec.md#25-веб-сервисы-webservices) |
| `HTTPService` | `HTTPServices/` | HTTP-сервисы | [1c-config-objects-spec.md § 24](1c-config-objects-spec.md#24-http-сервисы-httpservices) |
| `WSReference` | `WSReferences/` | WS-ссылки | [1c-configuration-spec.md § 6.15](1c-configuration-spec.md#615-wsreference--ws-ссылка) |
| `IntegrationService` | `IntegrationServices/` | Сервисы интеграции | [1c-configuration-spec.md § 6.13](1c-configuration-spec.md#613-integrationservice--сервис-интеграции) |
### Поведение и параметризация
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `EventSubscription` | `EventSubscriptions/` | Подписки на события | [1c-config-objects-spec.md § 23](1c-config-objects-spec.md#23-подписки-на-события-eventsubscriptions) |
| `ScheduledJob` | `ScheduledJobs/` | Регламентные задания | [1c-config-objects-spec.md § 22](1c-config-objects-spec.md#22-регламентные-задания-scheduledjobs) |
| `SettingsStorage` | `SettingsStorages/` | Хранилища настроек | [1c-configuration-spec.md § 6.10](1c-configuration-spec.md#610-settingsstorage--хранилище-настроек) |
| `FunctionalOption` | `FunctionalOptions/` | Функциональные опции | [1c-configuration-spec.md § 6.7](1c-configuration-spec.md#67-functionaloption--функциональная-опция) |
| `FunctionalOptionsParameter` | `FunctionalOptionsParameters/` | Параметры ФО | [1c-configuration-spec.md § 6.8](1c-configuration-spec.md#68-functionaloptionsparameter--параметр-функциональных-опций) |
| `DefinedType` | `DefinedTypes/` | Определяемые типы | [1c-config-objects-spec.md § 20](1c-config-objects-spec.md#20-определяемые-типы-definedtypes) |
| `Constant` | `Constants/` | Константы | [1c-config-objects-spec.md § 17](1c-config-objects-spec.md#17-константы-constants) |
### Прикладные объекты
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `Catalog` | `Catalogs/` | Справочники | [1c-config-objects-spec.md § 7](1c-config-objects-spec.md#7-справочники-catalogs) |
| `Document` | `Documents/` | Документы | [1c-config-objects-spec.md § 8](1c-config-objects-spec.md#8-документы-documents) |
| `DocumentNumerator` | `DocumentNumerators/` | Нумераторы документов | [1c-configuration-spec.md § 6.12](1c-configuration-spec.md#612-documentnumerator--нумератор-документов) |
| `Sequence` | `Sequences/` | Последовательности | [1c-configuration-spec.md § 6.9](1c-configuration-spec.md#69-sequence--последовательность-документов) |
| `DocumentJournal` | `DocumentJournals/` | Журналы документов | [1c-config-objects-spec.md § 19](1c-config-objects-spec.md#19-журналы-документов-documentjournals) |
| `Enum` | `Enums/` | Перечисления | [1c-config-objects-spec.md § 16](1c-config-objects-spec.md#16-перечисления-enums) |
| `Report` | `Reports/` | Отчёты | [1c-config-objects-spec.md § 18](1c-config-objects-spec.md#18-отчёты-и-обработки) |
| `DataProcessor` | `DataProcessors/` | Обработки | [1c-config-objects-spec.md § 18](1c-config-objects-spec.md#18-отчёты-и-обработки) |
### Регистры
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `InformationRegister` | `InformationRegisters/` | Регистры сведений | [1c-config-objects-spec.md § 9.1](1c-config-objects-spec.md#91-регистры-сведений-informationregisters) |
| `AccumulationRegister` | `AccumulationRegisters/` | Регистры накопления | [1c-config-objects-spec.md § 9.4](1c-config-objects-spec.md#94-регистры-накопления-accumulationregisters) |
| `AccountingRegister` | `AccountingRegisters/` | Регистры бухгалтерии | [1c-config-objects-spec.md § 9.5](1c-config-objects-spec.md#95-регистры-бухгалтерии-accountingregisters) |
| `CalculationRegister` | `CalculationRegisters/` | Регистры расчёта | [1c-config-objects-spec.md § 9.6](1c-config-objects-spec.md#96-регистры-расчёта-calculationregisters) |
### Планы
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `ChartOfCharacteristicTypes` | `ChartsOfCharacteristicTypes/` | Планы видов характеристик | [1c-config-objects-spec.md § 11](1c-config-objects-spec.md#11-планы-видов-характеристик-chartsofcharacteristictypes) |
| `ChartOfAccounts` | `ChartsOfAccounts/` | Планы счетов | [1c-config-objects-spec.md § 10](1c-config-objects-spec.md#10-планы-счетов-chartsofaccounts) |
| `ChartOfCalculationTypes` | `ChartsOfCalculationTypes/` | Планы видов расчёта | [1c-config-objects-spec.md § 12](1c-config-objects-spec.md#12-планы-видов-расчёта-chartsofcalculationtypes) |
### Бизнес-процессы
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `BusinessProcess` | `BusinessProcesses/` | Бизнес-процессы | [1c-config-objects-spec.md § 13](1c-config-objects-spec.md#13-бизнес-процессы-businessprocesses) |
| `Task` | `Tasks/` | Задачи | [1c-config-objects-spec.md § 14](1c-config-objects-spec.md#14-задачи-tasks) |
---
## 3. Вложенные форматы
Форматы файлов, вложенных в каталоги объектов метаданных.
| Формат | Файл | Описание | Спецификация |
|--------|------|----------|--------------|
| Управляемая форма | `Ext/Form.xml` | Элементы, реквизиты, команды | [1c-form-spec.md § 1](1c-form-spec.md#1-корневой-элемент) |
| СКД (DataCompositionSchema) | `Ext/Template.xml` | Схема компоновки данных | [1c-dcs-spec.md § 2](1c-dcs-spec.md#2-общая-структура-datacompositionschema) |
| Табличный документ (MXL) | `Ext/Template.xml` | Печатная форма | [1c-spreadsheet-spec.md](1c-spreadsheet-spec.md#структура-документа) |
| Роль (Rights) | `Ext/Rights.xml` | Права доступа и RLS | [1c-role-spec.md](1c-role-spec.md#файл-прав-rolesимяролиextrightsxml) |
| Справка (Help) | `Ext/Help/` | Встроенная справка | [1c-help-spec.md § 1](1c-help-spec.md#1-структура-файлов) |
| Предопределённые элементы | `Predefined.xml` | Предопределённые справочники/ПВХ | [1c-config-objects-spec.md § 7.2](1c-config-objects-spec.md#72-предопределённые-элементы-predefinedxml) |
| Состав плана обмена | `Content.xml` | Объекты синхронизации | [1c-config-objects-spec.md § 15.4](1c-config-objects-spec.md#154-состав-плана-обмена-contentxml) |
| Карта маршрута | `Flowchart.xml` | Маршрут бизнес-процесса | [1c-config-objects-spec.md § 13.3](1c-config-objects-spec.md#133-карта-маршрута-flowchartxml) |
---
## 4. Расширения конфигурации (CFE)
| Тема | Описание | Спецификация |
|------|----------|--------------|
| Общая структура выгрузки | Каталоги, отличия от конфигурации | [1c-extension-spec.md § 1](1c-extension-spec.md#1-общая-структура-выгрузки-расширения) |
| Configuration.xml расширения | Свойства, назначение, ChildObjects | [1c-extension-spec.md § 2](1c-extension-spec.md#2-configurationxml--корневой-файл-расширения) |
| Заимствованные / собственные объекты | ObjectBelonging, ExtendedConfigurationObject | [1c-extension-spec.md § 4](1c-extension-spec.md#4-заимствованные-и-собственные-объекты) |
| Расширение свойств (xr:PropertyState) | MultiState, ExtendedProperty | [1c-extension-spec.md § 6](1c-extension-spec.md#6-расширение-свойств-xrpropertystate-и-xrextendedproperty) |
| Модули и декораторы перехвата | &Перед, &После, &Вместо, diff-маркеры | [1c-extension-spec.md § 7](1c-extension-spec.md#7-модули-в-расширениях) |
| Предопределённые элементы | ExtensionState: Native | [1c-extension-spec.md § 8](1c-extension-spec.md#8-предопределённые-элементы-в-расширениях) |
---
## 5. Внешние обработки и отчёты
| Формат | Описание | Спецификация |
|--------|----------|--------------|
| EPF (внешняя обработка) | ExternalDataProcessor | [1c-epf-spec.md § 1](1c-epf-spec.md#1-структура-каталогов) |
| ERF (внешний отчёт) | ExternalReport | [1c-erf-spec.md § 1](1c-erf-spec.md#1-структура-каталогов) |
---
## 6. Общие элементы формата
Общие для всех типов объектов структуры XML описаны в спецификации объектов:
| Тема | Спецификация |
|------|--------------|
| Корневой элемент MetaDataObject | [1c-config-objects-spec.md § 2](1c-config-objects-spec.md#2-общий-формат-xml) |
| Пространства имён XML | [1c-config-objects-spec.md § 2.2](1c-config-objects-spec.md#22-пространства-имён) |
| InternalInfo / GeneratedType | [1c-config-objects-spec.md § 3](1c-config-objects-spec.md#3-internalinfo--внутренняя-информация) |
| Общие свойства Properties | [1c-config-objects-spec.md § 4](1c-config-objects-spec.md#4-общие-элементы-properties) |
| Стандартные реквизиты | [1c-config-objects-spec.md § 5](1c-config-objects-spec.md#5-стандартные-реквизиты-standardattributes) |
| Дочерние объекты (Attribute, TabularSection, Form, Template, Command) | [1c-config-objects-spec.md § 6](1c-config-objects-spec.md#6-дочерние-объекты-childobjects) |
| Формат ссылок на объекты | [1c-config-objects-spec.md § 28](1c-config-objects-spec.md#28-формат-ссылок-на-объекты-метаданных) |
| Различия версий 2.17 → 2.20 | [1c-config-objects-spec.md § 26](1c-config-objects-spec.md#26-различия-версий-платформы) |
---
## 7. DSL-спецификации (компактный формат ввода)
| DSL | Описание | Спецификация |
|-----|----------|--------------|
| Meta DSL | JSON-формат для создания/редактирования объектов | [meta-dsl-spec.md](meta-dsl-spec.md) |
| Form DSL | JSON-формат для компиляции форм | [form-dsl-spec.md](form-dsl-spec.md) |
| SKD DSL | JSON-формат для компиляции СКД | [skd-dsl-spec.md](skd-dsl-spec.md) |
| MXL DSL | JSON-формат для компиляции табличных документов | [mxl-dsl-spec.md](mxl-dsl-spec.md) |
| Role DSL | JSON-формат для компиляции ролей | [role-dsl-spec.md](role-dsl-spec.md) |
File diff suppressed because it is too large Load Diff
+182
View File
@@ -0,0 +1,182 @@
# Корневые файлы конфигурации
Навыки группы `/cf-*` позволяют создавать, анализировать, редактировать и проверять корневые файлы конфигурации 1С — `Configuration.xml`, `ConfigDumpInfo.xml`, `Languages/`.
## Навыки
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/cf-info` | `<ConfigPath> [-Mode overview\|brief\|full]` | Анализ конфигурации: свойства, состав, счётчики объектов (3 режима) |
| `/cf-init` | `<Name> [-Synonym] [-OutputDir] [-Version] [-Vendor]` | Создание пустой конфигурации (scaffold XML-исходников) |
| `/cf-validate` | `<ConfigPath> [-MaxErrors 30]` | Валидация структурной корректности (8 проверок) |
| `/cf-edit` | `<ConfigPath> -Operation <op> -Value "<val>"` | Редактирование свойств, состава ChildObjects, ролей по умолчанию (6 операций) |
## Рабочий цикл
```
Описание (текст) → /cf-init → XML-исходники → /cf-validate
↕ /cf-edit → /cf-info
```
1. `/cf-init` создаёт scaffold пустой конфигурации (Configuration.xml, ConfigDumpInfo.xml, Languages/)
2. `/cf-edit` вносит изменения: свойства, объекты в ChildObjects, роли по умолчанию
3. `/cf-validate` проверяет корректность XML (структура, enum-значения, ссылки, каталоги)
4. `/cf-info` выводит компактную сводку для визуальной проверки
## cf-info — режимы вывода
### brief — одна строка
```
Конфигурация: БухгалтерияПредприятия — "Бухгалтерия предприятия" v3.0.181.31 | 2847 объектов | Version8_3_24
```
### overview (по умолчанию) — заголовок + ключевые свойства + счётчики
```
Конфигурация: ТестКонфигурация — "Тестовая конфигурация"
Version: 2.0.0.1
Vendor: TestCompany
Compatibility: Version8_3_24
DefaultLanguage: Language.Русский
Объекты (4 шт.):
Language 1
Role 1
Catalog 1
Document 1
```
### full — все свойства + полный список объектов
Выводит все свойства по категориям (скалярные, enum, ref), полный список ChildObjects поимённо, DefaultRoles и мобильные функциональности.
## cf-edit — операции
### Свойства
```powershell
# Скалярные и enum
-Operation modify-property -Value "Version=2.0.0.1 ;; Vendor=Фирма 1С ;; CompatibilityMode=Version8_3_27"
# Многоязычные (LocalString)
-Operation modify-property -Value "Synonym=Моя конфигурация ;; Copyright=ООО Фирма"
# Ссылка
-Operation modify-property -Value "DefaultLanguage=Language.Русский"
```
Поддерживаемые свойства:
| Категория | Свойства |
|-----------|----------|
| Скалярные | `Name`, `Version`, `Vendor`, `Comment`, `NamePrefix`, `UpdateCatalogAddress` |
| LocalString | `Synonym`, `BriefInformation`, `DetailedInformation`, `Copyright`, `VendorInformationAddress`, `ConfigurationInformationAddress` |
| Enum | `CompatibilityMode`, `ConfigurationExtensionCompatibilityMode`, `DefaultRunMode`, `ScriptVariant`, `DataLockControlMode`, `ObjectAutonumerationMode`, `ModalityUseMode`, `SynchronousPlatformExtensionAndAddInCallUseMode`, `InterfaceCompatibilityMode`, `DatabaseTablespacesUseMode`, `MainClientApplicationWindowMode` |
| Ref | `DefaultLanguage` |
### Состав объектов (ChildObjects)
```powershell
# Добавить (вставляется в каноническую позицию — по типу, затем по алфавиту)
-Operation add-childObject -Value "Catalog.Товары ;; Document.Заказ ;; Enum.ВидыОплат"
# Удалить
-Operation remove-childObject -Value "Catalog.Устаревший"
```
44 типа объектов поддерживаются в каноническом порядке: Language, Subsystem, StyleItem, CommonPicture, ... IntegrationService.
### Роли по умолчанию (DefaultRoles)
```powershell
# Добавить
-Operation add-defaultRole -Value "ПолныеПрава"
# Удалить
-Operation remove-defaultRole -Value "ПолныеПрава"
# Заменить список целиком
-Operation set-defaultRoles -Value "ПолныеПрава ;; Администратор"
```
### JSON mode — комбинированные операции
```json
[
{ "operation": "modify-property", "value": "Version=2.0.0.1 ;; Vendor=Test" },
{ "operation": "add-childObject", "value": "Catalog.Товары ;; Document.Заказ" },
{ "operation": "add-defaultRole", "value": "ПолныеПрава" }
]
```
## cf-validate — проверки
| # | Проверка | Уровень |
|---|----------|---------|
| 1 | XML well-formedness, MetaDataObject/Configuration, version | ERROR |
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId | ERROR |
| 3 | Properties: Name, Synonym, DefaultLanguage, DefaultRunMode | ERROR/WARN |
| 4 | Enum-значения (11 свойств) | ERROR |
| 5 | ChildObjects: валидные типы, нет дубликатов, порядок | ERROR/WARN |
| 6 | DefaultLanguage ссылается на существующий Language | ERROR |
| 7 | Файлы языков `Languages/<name>.xml` существуют | WARN |
| 8 | Каталоги объектов из ChildObjects существуют | WARN |
## Сценарии использования
### Обзор существующей конфигурации
```
> Покажи структуру конфигурации C:\WS\cfsrc\acc_8.3.24
```
Claude вызовет `/cf-info` и покажет: имя, синоним, версию, поставщика, количество объектов по типам.
### Создание новой конфигурации
```
> Создай пустую конфигурацию МойПроект, версия 1.0.0.1, поставщик "ООО Ромашка"
```
Claude вызовет `/cf-init``/cf-edit` (Version, Vendor) → `/cf-validate``/cf-info`.
### Добавление объектов в конфигурацию
```
> Добавь в конфигурацию src/ справочник Контрагенты, документ ЗаказКлиента и перечисление ВидыОплат
```
Claude вызовет `/cf-edit` с `add-childObject`, объекты встанут в каноническом порядке.
### Проверка конфигурации после изменений
```
> Проверь корректность конфигурации src/
```
Claude вызовет `/cf-validate` и покажет ошибки и предупреждения.
## Структура корневых файлов
```
<OutputDir>/
├── Configuration.xml # Свойства и состав конфигурации
├── ConfigDumpInfo.xml # Служебный (версии объектов)
├── Ext/ # Модули конфигурации
│ ├── ManagedApplicationModule.bsl
│ ├── SessionModule.bsl
│ └── ...
└── Languages/
└── Русский.xml # Язык конфигурации
```
## Связь с другими навыками
- `/meta-compile` — при создании объекта автоматически регистрирует его в `Configuration.xml` (вызывает логику `add-childObject`)
- `/subsystem-edit` — при добавлении объекта в подсистему объект уже должен быть в ChildObjects
- `/cf-edit` + `/meta-compile` — типичная связка: сначала добавить объект в конфигурацию, затем создать его исходники
## Спецификации
- [1c-configuration-spec.md](1c-configuration-spec.md) — XML-формат Configuration.xml, ConfigDumpInfo.xml, Languages/, свойства, 44 типа ChildObjects
+175
View File
@@ -0,0 +1,175 @@
# Расширения конфигурации (CFE)
Навыки группы `/cfe-*` позволяют создавать, заимствовать объекты, перехватывать методы, проверять и анализировать расширения конфигурации 1С.
## Навыки
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/cfe-init` | `<Name> [-Purpose Patch\|Customization\|AddOn] [-CompatibilityMode]` | Создание расширения (scaffold XML-исходников) |
| `/cfe-borrow` | `-ExtensionPath <path> -ConfigPath <path> -Object "Type.Name"` | Заимствование объектов из конфигурации |
| `/cfe-patch-method` | `-ExtensionPath <path> -ModulePath "Type.Name.Module" -MethodName "X" -InterceptorType Before` | Генерация перехватчика метода |
| `/cfe-validate` | `<ExtensionPath> [-MaxErrors 30]` | Валидация структурной корректности (9 проверок) |
| `/cfe-diff` | `-ExtensionPath <path> -ConfigPath <path> [-Mode A\|B]` | Анализ расширения и проверка переноса |
## Рабочий цикл
```
cf-info (версия, совместимость)
/cfe-init → scaffold расширения
/cfe-borrow → заимствование объектов из конфигурации
/cfe-patch-method → перехват методов
/cfe-validate → проверка корректности
/cfe-diff Mode A → обзор изменений
```
## Типичные сценарии
### Создание расширения для исправления бага
```
> Создай расширение для исправления бага в справочнике Контрагенты,
конфигурация ERP в C:\cfsrc\erp
```
Claude выполнит:
1. `/cf-info C:\cfsrc\erp -Mode brief` — получить версию и режим совместимости
2. `/cfe-init` — создать расширение с нужным `CompatibilityMode`
3. `/cfe-borrow` — заимствовать `Catalog.Контрагенты`
4. `/cfe-patch-method` — создать перехватчик нужного метода
5. `/cfe-validate` — проверить результат
### Анализ существующего расширения
```
> Покажи что изменено в расширении src/
```
Claude вызовет `/cfe-diff -Mode A` и покажет: заимствованные объекты, перехватчики, собственные объекты.
### Проверка переноса изменений
```
> Проверь, все ли изменения из расширения перенесены в конфигурацию
```
Claude вызовет `/cfe-diff -Mode B` — найдёт блоки `#Вставка` и проверит их наличие в конфигурации.
## cfe-init — создание расширения
Параметры:
| Параметр | Описание | По умолчанию |
|----------|----------|--------------|
| `Name` | Имя расширения (обязат.) | — |
| `Synonym` | Синоним | = Name |
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
| `OutputDir` | Каталог | `src` |
| `Purpose` | Назначение | `Customization` |
| `Version` | Версия | — |
| `Vendor` | Поставщик | — |
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
| `NoRole` | Без основной роли | false |
Создаёт:
```
<OutputDir>/
├── Configuration.xml # Свойства расширения
├── Languages/
│ └── Русский.xml # Язык (Adopted)
└── Roles/ # Если не -NoRole
└── <Prefix>ОсновнаяРоль.xml
```
Назначение расширения (`Purpose`):
- `Patch` — исправление ошибок (минимальные изменения, только перехватчики)
- `Customization` — доработка (реквизиты, формы, модули)
- `AddOn` — дополнение (полноценный функционал)
## cfe-borrow — заимствование объектов
Заимствует объекты из основной конфигурации в расширение. Создаёт минимальные XML-файлы с `ObjectBelonging=Adopted` и `ExtendedConfigurationObject`.
Формат `-Object`:
- `Catalog.Контрагенты` — справочник
- `CommonModule.РаботаСФайлами` — общий модуль
- `Enum.ВидыОплат` — перечисление
- `Document.Заказ ;; Catalog.Товары` — несколько объектов через `;;`
Поддерживаемые типы: Catalog, Document, Enum, CommonModule, Report, DataProcessor, ExchangePlan, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, и другие (44 типа).
## cfe-patch-method — перехват методов
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта.
Параметры:
| Параметр | Описание |
|----------|----------|
| `ModulePath` | `Catalog.X.ObjectModule`, `CommonModule.Y`, `Catalog.X.Form.Z` |
| `MethodName` | Имя перехватываемого метода |
| `InterceptorType` | `Before` / `After` / `ModificationAndControl` |
| `Context` | `НаСервере` / `НаКлиенте` / `НаСервереБезКонтекста` |
| `IsFunction` | Добавить `Возврат` |
Типы перехватчиков:
| Тип | Декоратор | Когда использовать |
|-----|-----------|-------------------|
| `Before` | `&Перед` | Выполнить код до вызова оригинального метода |
| `After` | `&После` | Выполнить код после вызова оригинального метода |
| `ModificationAndControl` | `&ИзменениеИКонтроль` | Полная замена тела метода с маркерами `#Вставка`/`#Удаление` |
Пример генерируемого кода (`Before`):
```bsl
&НаСервере
&Перед("ПриЗаписи")
Процедура Расш1_ПриЗаписи()
// TODO: код перед вызовом оригинального метода
КонецПроцедуры
```
## cfe-validate — проверки
| # | Проверка | Уровень |
|---|----------|---------|
| 1 | XML well-formedness, MetaDataObject/Configuration, version | ERROR |
| 2 | InternalInfo: 7 ContainedObject, валидные ClassId | ERROR |
| 3 | Extension properties: ObjectBelonging=Adopted, Name, Purpose, NamePrefix, KeepMapping | ERROR |
| 4 | Enum-значения (4 свойства) | ERROR |
| 5 | ChildObjects: валидные типы, нет дубликатов, порядок | ERROR/WARN |
| 6 | DefaultLanguage ссылается на существующий Language | ERROR |
| 7 | Файлы языков существуют | WARN |
| 8 | Каталоги объектов существуют | WARN |
| 9 | Заимствованные объекты: ObjectBelonging=Adopted, ExtendedConfigurationObject UUID | ERROR/WARN |
## cfe-diff — режимы
### Mode A — обзор расширения
Для каждого объекта показывает:
- `[BORROWED]` — заимствованный: перехватчики, собственные реквизиты/формы
- `[OWN]` — собственный: количество реквизитов, ТЧ, форм
### Mode B — проверка переноса
Для каждого `&ИзменениеИКонтроль` проверяет, перенесены ли блоки `#Вставка` в конфигурацию:
- `[TRANSFERRED]` — код найден в конфигурации
- `[NOT_TRANSFERRED]` — код не найден
- `[NEEDS_REVIEW]` — нет блоков `#Вставка` или модуль конфигурации не найден
## Связь с другими навыками
- `/cf-info` — получение версии и совместимости конфигурации перед `cfe-init`
- `/meta-compile` — создание собственных объектов расширения (реквизиты, ТЧ)
- `/form-compile`, `/form-edit` — создание и модификация форм расширения
- `/cfe-validate` — всегда проверяйте расширение после изменений
## Спецификации
- [1c-extension-spec.md](1c-extension-spec.md) — XML-формат выгрузки расширений конфигурации (CFE)
+66 -17
View File
@@ -1,23 +1,42 @@
# Внешние обработки (EPF)
# Внешние обработки и отчёты (EPF / ERF)
Навыки группы `/epf-*` позволяют создавать, модифицировать и собирать внешние обработки 1С:Предприятия 8.3 (`.epf`) из XML-исходников, не запоминая детали формата.
Навыки для создания, модификации и сборки внешних обработок (`.epf`) и внешних отчётов (`.erf`) 1С:Предприятия 8.3 из XML-исходников.
## Навыки
## Навыки обработок (EPF)
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/epf-init` | `<Name> [Synonym]` | Создать новую обработку (корневой XML + модуль объекта) |
| `/epf-add-form` | `<ProcessorName> <FormName> [Synonym]` | Добавить управляемую форму |
| `/epf-add-template` | `<ProcessorName> <TemplateName> <TemplateType>` | Добавить макет (HTML, Text, SpreadsheetDocument, BinaryData) |
| `/epf-add-help` | `<ProcessorName>` | Добавить встроенную справку (Help.xml + HTML) |
| `/epf-remove-form` | `<ProcessorName> <FormName>` | Удалить форму |
| `/epf-remove-template` | `<ProcessorName> <TemplateName>` | Удалить макет |
| `/epf-add-form` | `<ProcessorName> <FormName> [Synonym]` | Добавить управляемую форму к обработке |
| `/epf-build` | `<ProcessorName>` | Собрать EPF из XML (через 1cv8.exe) |
| `/epf-dump` | `<EpfFile>` | Разобрать EPF в XML (через 1cv8.exe) |
| `/epf-bsp-init` | `<ProcessorName> <Вид>` | Добавить регистрацию БСП (СведенияОВнешнейОбработке) |
| `/epf-bsp-add-command` | `<ProcessorName> <Идентификатор>` | Добавить команду в дополнительную обработку БСП |
Навыки удаления (`epf-remove-*`) не вызываются Claude автоматически — только по явной команде пользователя.
## Внешние отчёты (ERF)
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/erf-init` | `<ReportName> [Synonym] [--WithSKD]` | Создать новый отчёт (корневой XML + модуль объекта + опционально СКД) |
| `/erf-build` | `<ReportName>` | Собрать ERF из XML (через 1cv8.exe) |
| `/erf-dump` | `<ErfFile>` | Разобрать ERF в XML (через 1cv8.exe) |
Флаг `--WithSKD` создаёт макет `ОсновнаяСхемаКомпоновкиДанных` и привязывает его к `MainDataCompositionSchema`.
## Универсальные навыки
Работают с любыми объектами — обработками, отчётами, справочниками, документами и др.
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/template-add` | `<ObjectName> <TemplateName> <TemplateType>` | Добавить макет (HTML, Text, SpreadsheetDocument, BinaryData, DataCompositionSchema) |
| `/template-remove` | `<ObjectName> <TemplateName>` | Удалить макет |
| `/help-add` | `<ObjectName>` | Добавить встроенную справку (Help.xml + HTML) |
| `/form-remove` | `<ObjectName> <FormName>` | Удалить форму |
Для отчётов: при добавлении макета типа DataCompositionSchema автоматически заполняется `MainDataCompositionSchema` (если пуст).
Навыки удаления (`template-remove`, `form-remove`) не вызываются Claude автоматически — только по явной команде пользователя.
## Сценарии использования
@@ -43,13 +62,21 @@ Claude выполнит `/epf-init` и `/epf-add-form` с правильными
Claude создаст обработку, добавит макет SpreadsheetDocument, вызовет `/epf-bsp-init` с видом ПечатнаяФорма и назначением, сгенерирует `СведенияОВнешнейОбработке()` и процедуру `Печать()`.
### Внешний отчёт с СКД
```
> Создай внешний отчёт ОстаткиНаСкладе с СКД
```
Claude выполнит `/erf-init ОстаткиНаСкладе --WithSKD`, затем предложит заполнить схему компоновки через `/skd-compile`.
### Доработка существующей обработки
```
> Добавь справку с описанием как пользоваться обработкой
```
Claude вызовет `/epf-add-help` и предложит отредактировать HTML.
Claude вызовет `/help-add` и предложит отредактировать HTML.
```
> Добавь ещё одну команду печати — накладная
@@ -61,7 +88,7 @@ Claude вызовет `/epf-bsp-add-command`, добавит команду в `
> Собери
```
Claude вызовет `/epf-build`.
Claude вызовет `/epf-build` или `/erf-build` в зависимости от типа объекта.
### Примеры слеш-команд
@@ -70,9 +97,13 @@ Claude вызовет `/epf-build`.
```
> /epf-init МояОбработка "Моя обработка"
> /epf-add-form МояОбработка Форма
> /epf-add-template МояОбработка Макет HTML
> /epf-add-help МояОбработка
> /template-add МояОбработка Макет HTML
> /help-add МояОбработка
> /epf-build МояОбработка
> /erf-init МойОтчёт --WithSKD
> /template-add МойОтчёт ДопМакет SpreadsheetDocument
> /erf-build МойОтчёт
```
## Структура каталогов
@@ -87,7 +118,7 @@ src/
└── ObjectModule.bsl # Модуль объекта
```
После `/epf-add-form` и `/epf-add-template`:
После `/epf-add-form` и `/template-add`:
```
src/
@@ -109,6 +140,21 @@ src/
└── Template.html # Содержимое макета
```
После `/erf-init МойОтчёт --WithSKD`:
```
src/
├── МойОтчёт.xml # Корневой файл (ExternalReport)
└── МойОтчёт/
├── Ext/
│ └── ObjectModule.bsl # Модуль объекта
└── Templates/
├── ОсновнаяСхемаКомпоновкиДанных.xml
└── ОсновнаяСхемаКомпоновкиДанных/
└── Ext/
└── Template.xml # Пустая СКД
```
Первая добавленная форма автоматически становится основной (DefaultForm). Флаг `--main` нужен только для переназначения основной формы на другую.
## Технические детали
@@ -116,13 +162,16 @@ src/
- Все XML-файлы создаются в **UTF-8 с BOM** (как в реальных выгрузках 1С)
- PowerShell-скрипты используют `System.Xml.XmlDocument` для модификации корневого XML
- UUID генерируются через `[guid]::NewGuid()`
- ClassId обработки фиксирован: `c3831ec8-d8d5-4f93-8a22-f9bfae07327f`
- ClassId обработки: `c3831ec8-d8d5-4f93-8a22-f9bfae07327f`
- ClassId отчёта: `e41aff26-25cf-4bb6-b6c1-3f478a75f374`
- Порядок элементов в `ChildObjects`: TabularSections → Forms → Templates
- Первая форма автоматически назначается основной (DefaultForm)
- Навыки БСП (`epf-bsp-*`) не используют скрипты — Claude модифицирует код напрямую через Read/Edit
- Для отчётов: `/template-add` с типом DataCompositionSchema автоматически заполняет `MainDataCompositionSchema`
## Спецификации
- [XML-формат выгрузки обработок](1c-xml-format-spec.md) — структура XML-файлов, namespace, элементы форм
- [XML-формат выгрузки обработок](1c-epf-spec.md) — структура XML-файлов, namespace, элементы форм
- [XML-формат внешних отчётов](1c-erf-spec.md) — отличия ERF от EPF, Properties, MainDataCompositionSchema
- [Встроенная справка](1c-help-spec.md) — Help.xml, HTML-страницы, кнопка справки на форме
- [Сборка и разборка EPF](build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
- [Сборка и разборка EPF/ERF](build-spec.md) — команды `1cv8.exe`, параметры, коды возврата
File diff suppressed because it is too large Load Diff
+176
View File
@@ -0,0 +1,176 @@
# Объекты метаданных конфигурации
Навыки группы `/meta-*` позволяют создавать, анализировать, редактировать и проверять объекты метаданных конфигурации 1С — справочники, документы, регистры, перечисления и ещё 19 типов объектов из XML-выгрузки.
## Навыки
| Навык | Параметры | Описание |
|-------|-----------|----------|
| `/meta-info` | `<ObjectPath> [-Mode] [-Name]` | Анализ структуры объекта: реквизиты, ТЧ, формы, движения, типы (8 режимов) |
| `/meta-compile` | `<JsonPath> <OutputPath>` | Создание объекта метаданных из JSON DSL: реквизиты, ТЧ, свойства, формы |
| `/meta-edit` | `<ObjectPath> -Operation <op> -Value "<val>"` | Точечное редактирование: 30+ атомарных операций (add/remove/modify/set) |
| `/meta-validate` | `<ObjectPath> [-MaxErrors 20]` | Валидация структурной корректности: ~40 проверок |
## Рабочий цикл
```
Описание объекта (текст) → JSON DSL → /meta-compile → XML-исходники → /meta-validate
↕ /meta-edit → /meta-info
```
1. Claude формирует JSON-определение объекта (тип, реквизиты, ТЧ, свойства)
2. `/meta-compile` генерирует XML-исходники с корректными UUID, namespace, структурой ChildObjects
3. `/meta-edit` вносит точечные изменения: добавление/удаление реквизитов, ТЧ, владельцев, движений и т.д.
4. `/meta-validate` проверяет корректность XML
5. `/meta-info` выводит компактную сводку для визуальной проверки
## Поддерживаемые типы объектов (23 типа)
| Группа | Типы |
|--------|------|
| Прикладные | Catalog, Document, Enum, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes |
| Процессы | BusinessProcess, Task |
| Регистры | InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister |
| Отчёты/обработки | Report, DataProcessor |
| Интеграция | ExchangePlan, HTTPService, WebService |
| Журналы | DocumentJournal, Sequence |
| Прочие | Constant, CommonModule, SessionParameter, FunctionalOption, DefinedType |
## Inline mode — типовые операции
### Реквизиты
```powershell
# Добавить
-Operation add-attribute -Value "Комментарий: Строка(200) ;; Сумма: Число(15,2) | req, index"
# С позиционной вставкой
-Operation add-attribute -Value "Склад: CatalogRef.Склады >> after Организация"
# Удалить
-Operation remove-attribute -Value "УстаревшийРеквизит"
# Переименовать + сменить тип
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=Строка(500)"
```
### Табличные части
```powershell
# Создать ТЧ с реквизитами
-Operation add-ts -Value "Товары: Ном: CatalogRef.Ном | req, Кол: Число(15,3), Цена: Число(15,2)"
# Добавить реквизит в существующую ТЧ
-Operation add-ts-attribute -Value "Товары.СтавкаНДС: EnumRef.СтавкиНДС"
# Изменить свойства ТЧ
-Operation modify-ts -Value "Товары: synonym=Товарный состав"
```
### Свойства объекта
```powershell
# Скалярные свойства
-Operation modify-property -Value "CodeLength=11 ;; DescriptionLength=150"
# Владельцы справочника
-Operation set-owners -Value "Catalog.Контрагенты ;; Catalog.Организации"
# Движения документа
-Operation add-registerRecord -Value "AccumulationRegister.Продажи ;; AccumulationRegister.ОстаткиТоваров"
```
### Регистры
```powershell
-Operation add-dimension -Value "Организация: CatalogRef.Организации | master, mainFilter"
-Operation add-resource -Value "Сумма: Число(15,2)"
```
## JSON mode — комбинированные операции
Для сложных сценариев (несколько типов изменений в одном вызове) используйте JSON-файл:
```json
{
"add": {
"attributes": ["Комментарий: Строка(200)"],
"tabularSections": [{
"name": "Товары",
"attrs": ["Ном: CatalogRef.Ном | req", "Кол: Число(15,3)"]
}]
},
"remove": { "attributes": ["УстаревшийРеквизит"] },
"modify": {
"properties": { "DescriptionLength": 150 },
"attributes": { "СтароеИмя": { "name": "НовоеИмя" } }
}
}
```
JSON поддерживает русские синонимы ключей (`реквизиты`, `тч`, `измерения` и др.) и типов (`Строка`, `Число`, `СправочникСсылка` и др.).
## Сценарии использования
### Анализ существующего объекта
```
> Покажи структуру справочника Catalogs/Номенклатура
```
Claude вызовет `/meta-info` и покажет: реквизиты с типами, табличные части, формы, владельцев, ввод по строке.
### Создание нового объекта
```
> Создай справочник Контрагенты: код 9, наименование 150, реквизиты ИНН(12), КПП(9),
> иерархический с группами, владелец — Catalog.Организации
```
Claude сформирует JSON и вызовет `/meta-compile``/meta-validate``/meta-info`.
### Добавление реквизитов к существующему объекту
```
> Добавь в документ Documents/ЗаказКлиента реквизиты Склад (CatalogRef.Склады)
> и ТЧ Товары с реквизитами Номенклатура, Количество, Цена, Сумма
```
Claude вызовет `/meta-edit` дважды: `add-attribute` для реквизита и `add-ts` для ТЧ.
### Настройка движений документа
```
> Документ Documents/ПриходТоваров должен делать движения
> по AccumulationRegister.ОстаткиТоваров и AccumulationRegister.Партии
```
Claude вызовет `/meta-edit` с операцией `set-registerRecords`.
### Проверка объекта после изменений
```
> Проверь корректность Documents/ЗаказКлиента
```
Claude вызовет `/meta-validate` и покажет ошибки и предупреждения.
## Структура файлов объекта метаданных
```
<MetaType>/<ObjectName>/
├── <ObjectName>.xml # Основной XML (Properties, ChildObjects)
├── Ext/
│ └── ObjectModule.bsl # Модуль объекта (опционально)
├── Forms/
│ └── <FormName>/ # Формы объекта
├── Templates/
│ └── <TemplateName>/ # Макеты
└── Commands/
└── <CommandName>/ # Команды
```
## Спецификации
- [1c-config-objects-spec.md](1c-config-objects-spec.md) — XML-формат объектов метаданных, Properties, ChildObjects, типы
- [meta-dsl-spec.md](meta-dsl-spec.md) — JSON DSL для описания объектов (`/meta-compile`)
+1 -1
View File
@@ -100,7 +100,7 @@ Claude вызовет `/mxl-validate`, который проверит инде
Макет табличного документа — часть обработки. Типичный сценарий создания печатной формы:
1. `/epf-init` — создать обработку
2. `/epf-add-template` — добавить макет SpreadsheetDocument (с именем `ПФ_MXL_...`)
2. `/template-add` — добавить макет SpreadsheetDocument (с именем `ПФ_MXL_...`)
3. `/mxl-compile` — заполнить макет содержимым из JSON-определения
4. `/epf-bsp-init` — добавить регистрацию БСП с видом ПечатнаяФорма
5. `/epf-bsp-add-command` — добавить команду печати