Auto-build: copilot (python) from c147fd5

This commit is contained in:
github-actions[bot]
2026-06-01 19:33:30 +00:00
commit 46e13a614c
264 changed files with 110269 additions and 0 deletions
+60
View File
@@ -0,0 +1,60 @@
---
name: meta-remove
description: Удалить объект метаданных из конфигурации 1С. Используй когда нужно удалить, убрать объект из конфигурации
argument-hint: <ConfigDir> -Object <Type.Name>
allowed-tools:
- Bash
- Read
- Glob
- AskUserQuestion
---
# /meta-remove — удаление объекта метаданных
Безопасно удаляет объект из XML-выгрузки конфигурации. Перед удалением проверяет ссылки на объект в реквизитах, коде и других метаданных. Если ссылки найдены — удаление блокируется.
## Использование
```
/meta-remove <ConfigDir> -Object <Type.Name>
```
## Параметры
| Параметр | Обязательный | Описание |
|------------|:------------:|-------------------------------------------------|
| ConfigDir | да | Корневая директория выгрузки (где Configuration.xml) |
| Object | да | Тип и имя объекта: `Catalog.Товары`, `Document.Заказ` и т.д. |
| DryRun | нет | Только показать что будет удалено, без изменений |
| KeepFiles | нет | Не удалять файлы, только дерегистрировать |
| Force | нет | Удалить несмотря на найденные ссылки |
## Команда
```powershell
python ".github/skills/meta-remove/scripts/meta-remove.py" -ConfigDir "<путь>" -Object "Catalog.Товары"
```
## Поддерживаемые типы
Catalog, Document, Enum, Constant, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, ExchangePlan, DocumentJournal, Report, DataProcessor, CommonModule, ScheduledJob, EventSubscription, HTTPService, WebService, DefinedType, Role, Subsystem, CommonForm, CommonTemplate, CommonPicture, CommonAttribute, SessionParameter, FunctionalOption, FunctionalOptionsParameter, Sequence, FilterCriterion, SettingsStorage, XDTOPackage, WSReference, StyleItem, Language
## Примеры
```powershell
# Проверка ссылок + dry run
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -DryRun
# Удалить объект без ссылок
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший"
# Принудительно удалить несмотря на ссылки
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Catalog.Устаревший" -Force
# Только дерегистрировать (файлы оставить)
... -ConfigDir C:\WS\tasks\cfsrc\acc_8.3.24 -Object "Report.Старый" -KeepFiles
# Удалить общий модуль
... -ConfigDir src -Object "CommonModule.МойМодуль"
```
@@ -0,0 +1,495 @@
# meta-remove v1.1 — Remove metadata object from 1C configuration dump
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$ConfigDir,
[Parameter(Mandatory)]
[string]$Object,
[switch]$DryRun,
[switch]$KeepFiles,
[switch]$Force
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- Type → plural directory mapping ---
$typePluralMap = @{
"Catalog" = "Catalogs"
"Document" = "Documents"
"Enum" = "Enums"
"Constant" = "Constants"
"InformationRegister" = "InformationRegisters"
"AccumulationRegister" = "AccumulationRegisters"
"AccountingRegister" = "AccountingRegisters"
"CalculationRegister" = "CalculationRegisters"
"ChartOfAccounts" = "ChartsOfAccounts"
"ChartOfCharacteristicTypes" = "ChartsOfCharacteristicTypes"
"ChartOfCalculationTypes" = "ChartsOfCalculationTypes"
"BusinessProcess" = "BusinessProcesses"
"Task" = "Tasks"
"ExchangePlan" = "ExchangePlans"
"DocumentJournal" = "DocumentJournals"
"Report" = "Reports"
"DataProcessor" = "DataProcessors"
"CommonModule" = "CommonModules"
"ScheduledJob" = "ScheduledJobs"
"EventSubscription" = "EventSubscriptions"
"HTTPService" = "HTTPServices"
"WebService" = "WebServices"
"DefinedType" = "DefinedTypes"
"Role" = "Roles"
"Subsystem" = "Subsystems"
"CommonForm" = "CommonForms"
"CommonTemplate" = "CommonTemplates"
"CommonPicture" = "CommonPictures"
"CommonAttribute" = "CommonAttributes"
"SessionParameter" = "SessionParameters"
"FunctionalOption" = "FunctionalOptions"
"FunctionalOptionsParameter" = "FunctionalOptionsParameters"
"Sequence" = "Sequences"
"FilterCriterion" = "FilterCriteria"
"SettingsStorage" = "SettingsStorages"
"XDTOPackage" = "XDTOPackages"
"WSReference" = "WSReferences"
"StyleItem" = "StyleItems"
"Language" = "Languages"
}
# --- Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ConfigDir)) {
$ConfigDir = Join-Path (Get-Location).Path $ConfigDir
}
if (-not (Test-Path $ConfigDir -PathType Container)) {
Write-Host "[ERROR] Config directory not found: $ConfigDir"
exit 1
}
$configXml = Join-Path $ConfigDir "Configuration.xml"
if (-not (Test-Path $configXml)) {
Write-Host "[ERROR] Configuration.xml not found in: $ConfigDir"
exit 1
}
# --- Parse object spec ---
$parts = $Object -split "\.", 2
if ($parts.Count -ne 2 -or -not $parts[0] -or -not $parts[1]) {
Write-Host "[ERROR] Invalid object format '$Object'. Expected: Type.Name (e.g. Catalog.Товары)"
exit 1
}
$objType = $parts[0]
$objName = $parts[1]
if (-not $typePluralMap.ContainsKey($objType)) {
Write-Host "[ERROR] Unknown type '$objType'. Supported: $($typePluralMap.Keys -join ', ')"
exit 1
}
$typePlural = $typePluralMap[$objType]
Write-Host "=== meta-remove: ${objType}.${objName} ==="
Write-Host ""
if ($DryRun) {
Write-Host "[DRY-RUN] No changes will be made"
Write-Host ""
}
$actions = 0
$errors = 0
# --- 1. Find object files ---
$typeDir = Join-Path $ConfigDir $typePlural
$objXml = Join-Path $typeDir "$objName.xml"
$objDir = Join-Path $typeDir $objName
$hasXml = Test-Path $objXml
$hasDir = Test-Path $objDir -PathType Container
if (-not $hasXml -and -not $hasDir) {
# Check if registered in Configuration.xml before proceeding
$cfgCheckDoc = New-Object System.Xml.XmlDocument
$cfgCheckDoc.PreserveWhitespace = $true
$cfgCheckDoc.Load($configXml)
$cfgCheckNs = New-Object System.Xml.XmlNamespaceManager($cfgCheckDoc.NameTable)
$cfgCheckNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$cfgCheckNode = $cfgCheckDoc.DocumentElement.SelectSingleNode("md:Configuration/md:ChildObjects", $cfgCheckNs)
$registeredInCfg = $false
if ($cfgCheckNode) {
foreach ($child in @($cfgCheckNode.ChildNodes)) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq $objType -and $child.InnerText.Trim() -eq $objName) {
$registeredInCfg = $true; break
}
}
}
if (-not $registeredInCfg) {
Write-Host "[ERROR] Object not found: $typePlural/$objName.xml and not registered in Configuration.xml"
exit 1
}
Write-Host "[WARN] Object files not found: $typePlural/$objName.xml"
Write-Host " Proceeding with deregistration only..."
} else {
if ($hasXml) { Write-Host "[FOUND] $typePlural/$objName.xml" }
if ($hasDir) {
$fileCount = @(Get-ChildItem $objDir -Recurse -File).Count
Write-Host "[FOUND] $typePlural/$objName/ ($fileCount files)"
}
}
# --- 2. Reference check ---
Write-Host ""
Write-Host "--- Reference check ---"
# Build search patterns based on object type
# Type → reference type name (used in XML <v8:Type> elements)
$typeRefNames = @{
"Catalog" = @("CatalogRef","CatalogObject")
"Document" = @("DocumentRef","DocumentObject")
"Enum" = @("EnumRef")
"ExchangePlan" = @("ExchangePlanRef","ExchangePlanObject")
"ChartOfAccounts" = @("ChartOfAccountsRef","ChartOfAccountsObject")
"ChartOfCharacteristicTypes" = @("ChartOfCharacteristicTypesRef","ChartOfCharacteristicTypesObject")
"ChartOfCalculationTypes" = @("ChartOfCalculationTypesRef","ChartOfCalculationTypesObject")
"BusinessProcess" = @("BusinessProcessRef","BusinessProcessObject")
"Task" = @("TaskRef","TaskObject")
}
# Type → Russian manager name (used in BSL code: Справочники.Товары)
$typeRuManager = @{
"Catalog" = "Справочники"
"Document" = "Документы"
"Enum" = "Перечисления"
"Constant" = "Константы"
"InformationRegister" = "РегистрыСведений"
"AccumulationRegister" = "РегистрыНакопления"
"AccountingRegister" = "РегистрыБухгалтерии"
"CalculationRegister" = "РегистрыРасчета"
"ChartOfAccounts" = "ПланыСчетов"
"ChartOfCharacteristicTypes" = "ПланыВидовХарактеристик"
"ChartOfCalculationTypes" = "ПланыВидовРасчета"
"BusinessProcess" = "БизнесПроцессы"
"Task" = "Задачи"
"ExchangePlan" = "ПланыОбмена"
"Report" = "Отчеты"
"DataProcessor" = "Обработки"
"DocumentJournal" = "ЖурналыДокументов"
"CommonModule" = $null
}
$searchPatterns = @()
# 1) XML type references: CatalogRef.Name, CatalogObject.Name
if ($typeRefNames.ContainsKey($objType)) {
foreach ($refName in $typeRefNames[$objType]) {
$searchPatterns += "$refName.$objName"
}
}
# 2) BSL code references: Справочники.Name, Catalogs.Name
$ruMgr = $typeRuManager[$objType]
if ($ruMgr) {
$searchPatterns += "$ruMgr.$objName"
}
# English manager = plural directory name
$searchPatterns += "$typePlural.$objName"
# 3) CommonModule: method calls in BSL (ModuleName.)
if ($objType -eq "CommonModule") {
$searchPatterns += "$objName."
}
# 4) ScheduledJob/EventSubscription handler references
if ($objType -eq "CommonModule") {
$searchPatterns += "<Handler>$objName."
$searchPatterns += "<MethodName>$objName."
}
# Exclude object's own files from search
$excludeDirs = @()
if ($hasDir) { $excludeDirs += $objDir }
$excludeFile = ""
if ($hasXml) { $excludeFile = $objXml }
# Search all XML and BSL files
$references = @()
$searchExtensions = @("*.xml", "*.bsl")
foreach ($ext in $searchExtensions) {
$files = @(Get-ChildItem $ConfigDir -Filter $ext -Recurse -File -ErrorAction SilentlyContinue)
foreach ($file in $files) {
# Skip own files
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
if ($excludeDirs.Count -gt 0) {
$skip = $false
foreach ($ed in $excludeDirs) {
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
}
if ($skip) { continue }
}
# Skip auto-cleaned files (Configuration.xml, ConfigDumpInfo.xml, Subsystems)
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
if ($relPath -eq "Configuration.xml" -or $relPath -eq "ConfigDumpInfo.xml" -or $relPath.StartsWith("Subsystems")) { continue }
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
foreach ($pat in $searchPatterns) {
if ($content.Contains($pat)) {
$references += @{ File = $relPath; Pattern = $pat }
break # one match per file is enough
}
}
}
}
# Also check for Type.Name references (subsystem content, doc journal, etc.) — but NOT in own files
$typeNameRef = "${objType}.${objName}"
$files = @(Get-ChildItem $ConfigDir -Filter "*.xml" -Recurse -File -ErrorAction SilentlyContinue)
foreach ($file in $files) {
if ($excludeFile -and $file.FullName -eq $excludeFile) { continue }
if ($excludeDirs.Count -gt 0) {
$skip = $false
foreach ($ed in $excludeDirs) {
if ($file.FullName.StartsWith($ed)) { $skip = $true; break }
}
if ($skip) { continue }
}
# Skip Configuration.xml and Subsystems — they will be cleaned automatically
$relPath = $file.FullName.Substring($ConfigDir.Length + 1)
if ($relPath -eq "Configuration.xml") { continue }
if ($relPath -eq "ConfigDumpInfo.xml") { continue }
if ($relPath.StartsWith("Subsystems")) { continue }
$content = [System.IO.File]::ReadAllText($file.FullName, [System.Text.Encoding]::UTF8)
if ($content.Contains($typeNameRef)) {
# Check it's not already in references
$alreadyFound = $false
foreach ($r in $references) {
if ($r.File -eq $relPath) { $alreadyFound = $true; break }
}
if (-not $alreadyFound) {
$references += @{ File = $relPath; Pattern = $typeNameRef }
}
}
}
if ($references.Count -gt 0) {
Write-Host "[WARN] Found $($references.Count) reference(s) to ${objType}.${objName}:"
Write-Host ""
$shown = 0
foreach ($ref in $references) {
Write-Host " $($ref.File)"
Write-Host " pattern: $($ref.Pattern)"
$shown++
if ($shown -ge 20) {
$remaining = $references.Count - $shown
if ($remaining -gt 0) {
Write-Host " ... and $remaining more"
}
break
}
}
Write-Host ""
if (-not $Force) {
Write-Host "[ERROR] Cannot remove: object has $($references.Count) reference(s)."
Write-Host " Use -Force to remove anyway, or fix references first."
exit 1
} else {
Write-Host "[WARN] -Force specified, proceeding despite references"
}
} else {
Write-Host "[OK] No references found"
}
# --- 3. Remove from Configuration.xml ChildObjects ---
Write-Host ""
Write-Host "--- Configuration.xml ---"
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true
$xmlDoc.Load($configXml)
$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")
$cfgNode = $xmlDoc.DocumentElement.SelectSingleNode("md:Configuration", $ns)
if (-not $cfgNode) {
Write-Host "[ERROR] Configuration element not found in Configuration.xml"
$errors++
} else {
$childObjects = $cfgNode.SelectSingleNode("md:ChildObjects", $ns)
if ($childObjects) {
$found = $false
foreach ($child in @($childObjects.ChildNodes)) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -eq $objType -and $child.InnerText.Trim() -eq $objName) {
$found = $true
if (-not $DryRun) {
# Remove preceding whitespace if present
$prev = $child.PreviousSibling
if ($prev -and $prev.NodeType -eq 'Whitespace') {
$childObjects.RemoveChild($prev) | Out-Null
}
$childObjects.RemoveChild($child) | Out-Null
}
Write-Host "[OK] Removed <$objType>$objName</$objType> from ChildObjects"
$actions++
break
}
}
if (-not $found) {
Write-Host "[WARN] <$objType>$objName</$objType> not found in ChildObjects"
}
}
# Save Configuration.xml
if ($actions -gt 0 -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($configXml, $false, $enc)
$xmlDoc.Save($sw)
$sw.Close()
Write-Host "[OK] Configuration.xml saved"
}
}
# --- 4. Remove from subsystem Content ---
Write-Host ""
Write-Host "--- Subsystems ---"
$subsystemsDir = Join-Path $ConfigDir "Subsystems"
$subsystemsFound = 0
$subsystemsCleaned = 0
function Remove-FromSubsystems {
param([string]$dir)
$xmlFiles = @(Get-ChildItem $dir -Filter "*.xml" -File -ErrorAction SilentlyContinue)
foreach ($xmlFile in $xmlFiles) {
$ssDoc = New-Object System.Xml.XmlDocument
$ssDoc.PreserveWhitespace = $true
try { $ssDoc.Load($xmlFile.FullName) } catch { continue }
$ssNs = New-Object System.Xml.XmlNamespaceManager($ssDoc.NameTable)
$ssNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
$ssNs.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
$ssNode = $ssDoc.DocumentElement.SelectSingleNode("md:Subsystem", $ssNs)
if (-not $ssNode) { continue }
$propsNode = $ssNode.SelectSingleNode("md:Properties", $ssNs)
if (-not $propsNode) { continue }
$contentNode = $propsNode.SelectSingleNode("md:Content", $ssNs)
if (-not $contentNode) { continue }
$ssNameNode = $propsNode.SelectSingleNode("md:Name", $ssNs)
$ssName = if ($ssNameNode) { $ssNameNode.InnerText } else { $xmlFile.BaseName }
# Content items are <v8:Value>Type.Name</v8:Value>
$targetRef = "${objType}.${objName}"
$modified = $false
foreach ($item in @($contentNode.ChildNodes)) {
if ($item.NodeType -ne 'Element') { continue }
$val = $item.InnerText.Trim()
# Content format: "Subsystem.X" or "Catalog.X" etc.
if ($val -eq $targetRef) {
$script:subsystemsFound++
if (-not $DryRun) {
$prev = $item.PreviousSibling
if ($prev -and $prev.NodeType -eq 'Whitespace') {
$contentNode.RemoveChild($prev) | Out-Null
}
$contentNode.RemoveChild($item) | Out-Null
$modified = $true
}
Write-Host "[OK] Removed from subsystem '$ssName'"
$script:subsystemsCleaned++
}
}
if ($modified -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($xmlFile.FullName, $false, $enc)
$ssDoc.Save($sw)
$sw.Close()
}
# Recurse into child subsystems
$childDir = Join-Path $dir ($xmlFile.BaseName)
$childSubsystems = Join-Path $childDir "Subsystems"
if (Test-Path $childSubsystems -PathType Container) {
Remove-FromSubsystems -dir $childSubsystems
}
}
}
if (Test-Path $subsystemsDir -PathType Container) {
Remove-FromSubsystems -dir $subsystemsDir
if ($subsystemsCleaned -eq 0) {
Write-Host "[OK] Not referenced in any subsystem"
}
} else {
Write-Host "[OK] No Subsystems directory"
}
# --- 5. Delete object files ---
Write-Host ""
Write-Host "--- Files ---"
if (-not $KeepFiles) {
if ($hasDir -and -not $DryRun) {
Remove-Item $objDir -Recurse -Force
Write-Host "[OK] Deleted directory: $typePlural/$objName/"
$actions++
} elseif ($hasDir) {
Write-Host "[DRY] Would delete directory: $typePlural/$objName/"
$actions++
}
if ($hasXml -and -not $DryRun) {
Remove-Item $objXml -Force
Write-Host "[OK] Deleted file: $typePlural/$objName.xml"
$actions++
} elseif ($hasXml) {
Write-Host "[DRY] Would delete file: $typePlural/$objName.xml"
$actions++
}
if (-not $hasXml -and -not $hasDir) {
Write-Host "[OK] No files to delete"
}
} else {
Write-Host "[SKIP] File deletion skipped (-KeepFiles)"
}
# --- Summary ---
Write-Host ""
$totalActions = $actions + $subsystemsCleaned
if ($DryRun) {
Write-Host "=== Dry run complete: $totalActions actions would be performed ==="
} else {
Write-Host "=== Done: $totalActions actions performed ($subsystemsCleaned subsystem references removed) ==="
}
if ($errors -gt 0) {
exit 1
}
exit 0
@@ -0,0 +1,485 @@
#!/usr/bin/env python3
# meta-remove v1.1 — Remove metadata object from 1C configuration dump
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
import sys
import shutil
from lxml import etree
# --- Type -> plural directory mapping ---
TYPE_PLURAL_MAP = {
"Catalog": "Catalogs",
"Document": "Documents",
"Enum": "Enums",
"Constant": "Constants",
"InformationRegister": "InformationRegisters",
"AccumulationRegister": "AccumulationRegisters",
"AccountingRegister": "AccountingRegisters",
"CalculationRegister": "CalculationRegisters",
"ChartOfAccounts": "ChartsOfAccounts",
"ChartOfCharacteristicTypes": "ChartsOfCharacteristicTypes",
"ChartOfCalculationTypes": "ChartsOfCalculationTypes",
"BusinessProcess": "BusinessProcesses",
"Task": "Tasks",
"ExchangePlan": "ExchangePlans",
"DocumentJournal": "DocumentJournals",
"Report": "Reports",
"DataProcessor": "DataProcessors",
"CommonModule": "CommonModules",
"ScheduledJob": "ScheduledJobs",
"EventSubscription": "EventSubscriptions",
"HTTPService": "HTTPServices",
"WebService": "WebServices",
"DefinedType": "DefinedTypes",
"Role": "Roles",
"Subsystem": "Subsystems",
"CommonForm": "CommonForms",
"CommonTemplate": "CommonTemplates",
"CommonPicture": "CommonPictures",
"CommonAttribute": "CommonAttributes",
"SessionParameter": "SessionParameters",
"FunctionalOption": "FunctionalOptions",
"FunctionalOptionsParameter": "FunctionalOptionsParameters",
"Sequence": "Sequences",
"FilterCriterion": "FilterCriteria",
"SettingsStorage": "SettingsStorages",
"XDTOPackage": "XDTOPackages",
"WSReference": "WSReferences",
"StyleItem": "StyleItems",
"Language": "Languages",
}
# Type -> reference type names (used in XML <v8:Type> elements)
TYPE_REF_NAMES = {
"Catalog": ["CatalogRef", "CatalogObject"],
"Document": ["DocumentRef", "DocumentObject"],
"Enum": ["EnumRef"],
"ExchangePlan": ["ExchangePlanRef", "ExchangePlanObject"],
"ChartOfAccounts": ["ChartOfAccountsRef", "ChartOfAccountsObject"],
"ChartOfCharacteristicTypes": ["ChartOfCharacteristicTypesRef", "ChartOfCharacteristicTypesObject"],
"ChartOfCalculationTypes": ["ChartOfCalculationTypesRef", "ChartOfCalculationTypesObject"],
"BusinessProcess": ["BusinessProcessRef", "BusinessProcessObject"],
"Task": ["TaskRef", "TaskObject"],
}
# Type -> Russian manager name (used in BSL code)
TYPE_RU_MANAGER = {
"Catalog": "\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a\u0438",
"Document": "\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b",
"Enum": "\u041f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u044f",
"Constant": "\u041a\u043e\u043d\u0441\u0442\u0430\u043d\u0442\u044b",
"InformationRegister": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u0439",
"AccumulationRegister": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u041d\u0430\u043a\u043e\u043f\u043b\u0435\u043d\u0438\u044f",
"AccountingRegister": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0411\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u0438",
"CalculationRegister": "\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u044b\u0420\u0430\u0441\u0447\u0435\u0442\u0430",
"ChartOfAccounts": "\u041f\u043b\u0430\u043d\u044b\u0421\u0447\u0435\u0442\u043e\u0432",
"ChartOfCharacteristicTypes": "\u041f\u043b\u0430\u043d\u044b\u0412\u0438\u0434\u043e\u0432\u0425\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a",
"ChartOfCalculationTypes": "\u041f\u043b\u0430\u043d\u044b\u0412\u0438\u0434\u043e\u0432\u0420\u0430\u0441\u0447\u0435\u0442\u0430",
"BusinessProcess": "\u0411\u0438\u0437\u043d\u0435\u0441\u041f\u0440\u043e\u0446\u0435\u0441\u0441\u044b",
"Task": "\u0417\u0430\u0434\u0430\u0447\u0438",
"ExchangePlan": "\u041f\u043b\u0430\u043d\u044b\u041e\u0431\u043c\u0435\u043d\u0430",
"Report": "\u041e\u0442\u0447\u0435\u0442\u044b",
"DataProcessor": "\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0438",
"DocumentJournal": "\u0416\u0443\u0440\u043d\u0430\u043b\u044b\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043e\u0432",
"CommonModule": None,
}
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
V8_NS = "http://v8.1c.ru/8.1/data/core"
NSMAP = {"md": MD_NS, "v8": V8_NS}
def localname(el):
return etree.QName(el.tag).localname
def save_xml_bom(tree, path):
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
if not xml_bytes.endswith(b"\n"):
xml_bytes += b"\n"
with open(path, "wb") as f:
f.write(b"\xef\xbb\xbf")
f.write(xml_bytes)
def main():
sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8")
parser = argparse.ArgumentParser(description="Remove metadata object from 1C configuration dump", allow_abbrev=False)
parser.add_argument("-ConfigDir", required=True)
parser.add_argument("-Object", required=True)
parser.add_argument("-DryRun", action="store_true")
parser.add_argument("-KeepFiles", action="store_true")
parser.add_argument("-Force", action="store_true")
args = parser.parse_args()
config_dir = args.ConfigDir
if not os.path.isabs(config_dir):
config_dir = os.path.join(os.getcwd(), config_dir)
if not os.path.isdir(config_dir):
print(f"[ERROR] Config directory not found: {config_dir}")
sys.exit(1)
config_xml = os.path.join(config_dir, "Configuration.xml")
if not os.path.isfile(config_xml):
print(f"[ERROR] Configuration.xml not found in: {config_dir}")
sys.exit(1)
# --- Parse object spec ---
parts = args.Object.split(".", 1)
if len(parts) != 2 or not parts[0] or not parts[1]:
print(f"[ERROR] Invalid object format '{args.Object}'. Expected: Type.Name (e.g. Catalog.\u0422\u043e\u0432\u0430\u0440\u044b)")
sys.exit(1)
obj_type = parts[0]
obj_name = parts[1]
if obj_type not in TYPE_PLURAL_MAP:
print(f"[ERROR] Unknown type '{obj_type}'. Supported: {', '.join(TYPE_PLURAL_MAP.keys())}")
sys.exit(1)
type_plural = TYPE_PLURAL_MAP[obj_type]
print(f"=== meta-remove: {obj_type}.{obj_name} ===")
print()
if args.DryRun:
print("[DRY-RUN] No changes will be made")
print()
actions = 0
errors = 0
# --- 1. Find object files ---
type_dir = os.path.join(config_dir, type_plural)
obj_xml = os.path.join(type_dir, f"{obj_name}.xml")
obj_dir = os.path.join(type_dir, obj_name)
has_xml = os.path.isfile(obj_xml)
has_dir = os.path.isdir(obj_dir)
if not has_xml and not has_dir:
# Check if registered in Configuration.xml before proceeding
cfg_check_tree = etree.parse(config_xml, etree.XMLParser(remove_blank_text=False))
cfg_check_root = cfg_check_tree.getroot()
child_objects = cfg_check_root.find(f"{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects")
registered_in_cfg = False
if child_objects is not None:
for child in child_objects:
if isinstance(child.tag, str) and etree.QName(child.tag).localname == obj_type and (child.text or "").strip() == obj_name:
registered_in_cfg = True
break
if not registered_in_cfg:
print(f"[ERROR] Object not found: {type_plural}/{obj_name}.xml and not registered in Configuration.xml")
sys.exit(1)
print(f"[WARN] Object files not found: {type_plural}/{obj_name}.xml")
print(" Proceeding with deregistration only...")
else:
if has_xml:
print(f"[FOUND] {type_plural}/{obj_name}.xml")
if has_dir:
file_count = sum(len(files) for _, _, files in os.walk(obj_dir))
print(f"[FOUND] {type_plural}/{obj_name}/ ({file_count} files)")
# --- 2. Reference check ---
print()
print("--- Reference check ---")
search_patterns = []
# 1) XML type references
if obj_type in TYPE_REF_NAMES:
for ref_name in TYPE_REF_NAMES[obj_type]:
search_patterns.append(f"{ref_name}.{obj_name}")
# 2) BSL code references
ru_mgr = TYPE_RU_MANAGER.get(obj_type)
if ru_mgr:
search_patterns.append(f"{ru_mgr}.{obj_name}")
search_patterns.append(f"{type_plural}.{obj_name}")
# 3) CommonModule: method calls
if obj_type == "CommonModule":
search_patterns.append(f"{obj_name}.")
# 4) ScheduledJob/EventSubscription handler references
if obj_type == "CommonModule":
search_patterns.append(f"<Handler>{obj_name}.")
search_patterns.append(f"<MethodName>{obj_name}.")
# Exclude object's own files
exclude_dirs = []
if has_dir:
exclude_dirs.append(obj_dir)
exclude_file = obj_xml if has_xml else ""
# Search all XML and BSL files
references = []
search_extensions = (".xml", ".bsl")
for root_path, dirs, files in os.walk(config_dir):
for fname in files:
ext = os.path.splitext(fname)[1].lower()
if ext not in search_extensions:
continue
full_path = os.path.join(root_path, fname)
# Skip own files
if exclude_file and os.path.normcase(full_path) == os.path.normcase(exclude_file):
continue
skip = False
for ed in exclude_dirs:
if os.path.normcase(full_path).startswith(os.path.normcase(ed + os.sep)) or os.path.normcase(full_path) == os.path.normcase(ed):
skip = True
break
if skip:
continue
# Get relative path
rel_path = os.path.relpath(full_path, config_dir)
rel_path_fwd = rel_path.replace("\\", "/")
# Skip auto-cleaned files
if rel_path_fwd == "Configuration.xml" or rel_path_fwd == "ConfigDumpInfo.xml" or rel_path_fwd.startswith("Subsystems"):
continue
try:
with open(full_path, "r", encoding="utf-8-sig") as fh:
content = fh.read()
except Exception:
continue
for pat in search_patterns:
if pat in content:
references.append({"File": rel_path, "Pattern": pat})
break
# Also check Type.Name references
type_name_ref = f"{obj_type}.{obj_name}"
already_found_files = {r["File"] for r in references}
for root_path, dirs, files in os.walk(config_dir):
for fname in files:
if not fname.lower().endswith(".xml"):
continue
full_path = os.path.join(root_path, fname)
if exclude_file and os.path.normcase(full_path) == os.path.normcase(exclude_file):
continue
skip = False
for ed in exclude_dirs:
if os.path.normcase(full_path).startswith(os.path.normcase(ed + os.sep)) or os.path.normcase(full_path) == os.path.normcase(ed):
skip = True
break
if skip:
continue
rel_path = os.path.relpath(full_path, config_dir)
rel_path_fwd = rel_path.replace("\\", "/")
if rel_path_fwd == "Configuration.xml" or rel_path_fwd == "ConfigDumpInfo.xml" or rel_path_fwd.startswith("Subsystems"):
continue
if rel_path in already_found_files:
continue
try:
with open(full_path, "r", encoding="utf-8-sig") as fh:
content = fh.read()
except Exception:
continue
if type_name_ref in content:
references.append({"File": rel_path, "Pattern": type_name_ref})
if references:
print(f"[WARN] Found {len(references)} reference(s) to {obj_type}.{obj_name}:")
print()
shown = 0
for ref in references:
print(f" {ref['File']}")
print(f" pattern: {ref['Pattern']}")
shown += 1
if shown >= 20:
remaining = len(references) - shown
if remaining > 0:
print(f" ... and {remaining} more")
break
print()
if not args.Force:
print(f"[ERROR] Cannot remove: object has {len(references)} reference(s).")
print(" Use -Force to remove anyway, or fix references first.")
sys.exit(1)
else:
print("[WARN] -Force specified, proceeding despite references")
else:
print("[OK] No references found")
# --- 3. Remove from Configuration.xml ChildObjects ---
print()
print("--- Configuration.xml ---")
xml_parser = etree.XMLParser(remove_blank_text=False)
tree = etree.parse(config_xml, xml_parser)
xml_root = tree.getroot()
cfg_node = xml_root.find(f"{{{MD_NS}}}Configuration")
if cfg_node is None:
print("[ERROR] Configuration element not found in Configuration.xml")
errors += 1
else:
child_objects = cfg_node.find(f"{{{MD_NS}}}ChildObjects")
if child_objects is not None:
found = False
for child in list(child_objects):
if not isinstance(child.tag, str):
continue
if localname(child) == obj_type and (child.text or "").strip() == obj_name:
found = True
if not args.DryRun:
# Remove preceding whitespace (tail of previous sibling or text of parent)
prev = child.getprevious()
if prev is not None:
if prev.tail and prev.tail.strip() == "":
prev.tail = prev.tail.rsplit("\n", 1)[0] + "\n" if "\n" in prev.tail else ""
if not prev.tail.strip():
# Keep just the last newline+indent before the next element
pass
child_objects.remove(child)
print(f"[OK] Removed <{obj_type}>{obj_name}</{obj_type}> from ChildObjects")
actions += 1
break
if not found:
print(f"[WARN] <{obj_type}>{obj_name}</{obj_type}> not found in ChildObjects")
# Save Configuration.xml
if actions > 0 and not args.DryRun:
save_xml_bom(tree, config_xml)
print("[OK] Configuration.xml saved")
# --- 4. Remove from subsystem Content ---
print()
print("--- Subsystems ---")
subsystems_dir = os.path.join(config_dir, "Subsystems")
subsystems_found = 0
subsystems_cleaned = 0
def remove_from_subsystems(dir_path):
nonlocal subsystems_found, subsystems_cleaned
if not os.path.isdir(dir_path):
return
for fname in os.listdir(dir_path):
if not fname.lower().endswith(".xml"):
continue
xml_file = os.path.join(dir_path, fname)
if not os.path.isfile(xml_file):
continue
ss_parser = etree.XMLParser(remove_blank_text=False)
try:
ss_tree = etree.parse(xml_file, ss_parser)
except Exception:
continue
ss_root = ss_tree.getroot()
ss_node = None
for child in ss_root:
if isinstance(child.tag, str) and localname(child) == "Subsystem":
ss_node = child
break
if ss_node is None:
continue
props_node = ss_node.find(f"{{{MD_NS}}}Properties")
if props_node is None:
continue
content_node = props_node.find(f"{{{MD_NS}}}Content")
if content_node is None:
continue
ss_name_node = props_node.find(f"{{{MD_NS}}}Name")
ss_name = ss_name_node.text if ss_name_node is not None and ss_name_node.text else os.path.splitext(fname)[0]
target_ref = f"{obj_type}.{obj_name}"
modified = False
for item in list(content_node):
if not isinstance(item.tag, str):
continue
val = (item.text or "").strip()
if val == target_ref:
subsystems_found += 1
if not args.DryRun:
content_node.remove(item)
modified = True
print(f"[OK] Removed from subsystem '{ss_name}'")
subsystems_cleaned += 1
if modified and not args.DryRun:
save_xml_bom(ss_tree, xml_file)
# Recurse into child subsystems
base_name = os.path.splitext(fname)[0]
child_dir = os.path.join(dir_path, base_name, "Subsystems")
if os.path.isdir(child_dir):
remove_from_subsystems(child_dir)
if os.path.isdir(subsystems_dir):
remove_from_subsystems(subsystems_dir)
if subsystems_cleaned == 0:
print("[OK] Not referenced in any subsystem")
else:
print("[OK] No Subsystems directory")
# --- 5. Delete object files ---
print()
print("--- Files ---")
if not args.KeepFiles:
if has_dir and not args.DryRun:
shutil.rmtree(obj_dir)
print(f"[OK] Deleted directory: {type_plural}/{obj_name}/")
actions += 1
elif has_dir:
print(f"[DRY] Would delete directory: {type_plural}/{obj_name}/")
actions += 1
if has_xml and not args.DryRun:
os.remove(obj_xml)
print(f"[OK] Deleted file: {type_plural}/{obj_name}.xml")
actions += 1
elif has_xml:
print(f"[DRY] Would delete file: {type_plural}/{obj_name}.xml")
actions += 1
if not has_xml and not has_dir:
print("[OK] No files to delete")
else:
print("[SKIP] File deletion skipped (-KeepFiles)")
# --- Summary ---
print()
total_actions = actions + subsystems_cleaned
if args.DryRun:
print(f"=== Dry run complete: {total_actions} actions would be performed ===")
else:
print(f"=== Done: {total_actions} actions performed ({subsystems_cleaned} subsystem references removed) ===")
if errors > 0:
sys.exit(1)
sys.exit(0)
if __name__ == "__main__":
main()