Auto-build: copilot (python) from 7fa279c

This commit is contained in:
github-actions[bot]
2026-05-17 11:22:33 +00:00
commit bbd2f7a8c1
207 changed files with 98901 additions and 0 deletions
@@ -0,0 +1,546 @@
# subsystem-edit v1.2 — Edit existing 1C subsystem XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][Alias('Path')][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
# --- Content type normalization (plural→singular, Russian→English) ---
$script:contentTypeMap = @{
"Catalogs"="Catalog"; "Documents"="Document"; "Enums"="Enum"; "Constants"="Constant"
"Reports"="Report"; "DataProcessors"="DataProcessor"
"InformationRegisters"="InformationRegister"; "AccumulationRegisters"="AccumulationRegister"
"AccountingRegisters"="AccountingRegister"; "CalculationRegisters"="CalculationRegister"
"ChartsOfAccounts"="ChartOfAccounts"; "ChartsOfCharacteristicTypes"="ChartOfCharacteristicTypes"
"ChartsOfCalculationTypes"="ChartOfCalculationTypes"
"BusinessProcesses"="BusinessProcess"; "Tasks"="Task"
"ExchangePlans"="ExchangePlan"; "DocumentJournals"="DocumentJournal"
"CommonModules"="CommonModule"; "CommonCommands"="CommonCommand"
"CommonForms"="CommonForm"; "CommonPictures"="CommonPicture"
"CommonTemplates"="CommonTemplate"; "CommonAttributes"="CommonAttribute"
"CommandGroups"="CommandGroup"; "Roles"="Role"
"SessionParameters"="SessionParameter"; "FilterCriteria"="FilterCriterion"
"XDTOPackages"="XDTOPackage"; "WebServices"="WebService"
"HTTPServices"="HTTPService"; "WSReferences"="WSReference"
"EventSubscriptions"="EventSubscription"; "ScheduledJobs"="ScheduledJob"
"SettingsStorages"="SettingsStorage"; "FunctionalOptions"="FunctionalOption"
"FunctionalOptionsParameters"="FunctionalOptionsParameter"
"DefinedTypes"="DefinedType"; "DocumentNumerators"="DocumentNumerator"
"Sequences"="Sequence"; "Subsystems"="Subsystem"
"StyleItems"="StyleItem"; "IntegrationServices"="IntegrationService"
# Russian singular
"Справочник"="Catalog"; "Каталог"="Catalog"; "Документ"="Document"
"Перечисление"="Enum"; "Константа"="Constant"
"Отчёт"="Report"; "Отчет"="Report"; "Обработка"="DataProcessor"
"РегистрСведений"="InformationRegister"; "РегистрНакопления"="AccumulationRegister"
"РегистрБухгалтерии"="AccountingRegister"
"РегистрРасчёта"="CalculationRegister"; "РегистрРасчета"="CalculationRegister"
"ПланСчетов"="ChartOfAccounts"; "ПланВидовХарактеристик"="ChartOfCharacteristicTypes"
"ПланВидовРасчёта"="ChartOfCalculationTypes"; "ПланВидовРасчета"="ChartOfCalculationTypes"
"БизнесПроцесс"="BusinessProcess"; "Задача"="Task"
"ПланОбмена"="ExchangePlan"; "ЖурналДокументов"="DocumentJournal"
"ОбщийМодуль"="CommonModule"; "ОбщаяКоманда"="CommonCommand"
"ОбщаяФорма"="CommonForm"; "ОбщаяКартинка"="CommonPicture"
"ОбщийМакет"="CommonTemplate"; "ОбщийРеквизит"="CommonAttribute"
"ГруппаКоманд"="CommandGroup"; "Роль"="Role"
"ПараметрСеанса"="SessionParameter"; "КритерийОтбора"="FilterCriterion"
"ПакетXDTO"="XDTOPackage"; "ВебСервис"="WebService"
"HTTPСервис"="HTTPService"; "WSСсылка"="WSReference"
"ПодпискаНаСобытие"="EventSubscription"; "РегламентноеЗадание"="ScheduledJob"
"ХранилищеНастроек"="SettingsStorage"; "ФункциональнаяОпция"="FunctionalOption"
"ПараметрФункциональныхОпций"="FunctionalOptionsParameter"
"ОпределяемыйТип"="DefinedType"; "Подсистема"="Subsystem"
"ЭлементСтиля"="StyleItem"; "СервисИнтеграции"="IntegrationService"
# Russian plural
"Справочники"="Catalog"; "Документы"="Document"; "Перечисления"="Enum"
"Константы"="Constant"; "Отчёты"="Report"; "Отчеты"="Report"
"Обработки"="DataProcessor"; "РегистрыСведений"="InformationRegister"
"РегистрыНакопления"="AccumulationRegister"; "РегистрыБухгалтерии"="AccountingRegister"
"РегистрыРасчёта"="CalculationRegister"; "РегистрыРасчета"="CalculationRegister"
"ПланыСчетов"="ChartOfAccounts"; "ПланыВидовХарактеристик"="ChartOfCharacteristicTypes"
"ПланыВидовРасчёта"="ChartOfCalculationTypes"; "ПланыВидовРасчета"="ChartOfCalculationTypes"
"БизнесПроцессы"="BusinessProcess"; "Задачи"="Task"
"ПланыОбмена"="ExchangePlan"; "ЖурналыДокументов"="DocumentJournal"
"ОбщиеМодули"="CommonModule"; "ОбщиеКоманды"="CommonCommand"
"ОбщиеФормы"="CommonForm"; "ОбщиеКартинки"="CommonPicture"
"ОбщиеМакеты"="CommonTemplate"; "ОбщиеРеквизиты"="CommonAttribute"
"ГруппыКоманд"="CommandGroup"; "Роли"="Role"
"ПараметрыСеанса"="SessionParameter"; "КритерииОтбора"="FilterCriterion"
"ПакетыXDTO"="XDTOPackage"; "ВебСервисы"="WebService"
"HTTPСервисы"="HTTPService"; "WSСсылки"="WSReference"
"ПодпискиНаСобытия"="EventSubscription"; "РегламентныеЗадания"="ScheduledJob"
"ХранилищаНастроек"="SettingsStorage"; "ФункциональныеОпции"="FunctionalOption"
"ОпределяемыеТипы"="DefinedType"; "Подсистемы"="Subsystem"
"ЭлементыСтиля"="StyleItem"; "СервисыИнтеграции"="IntegrationService"
}
function Normalize-ContentRef([string]$ref) {
if (-not $ref -or -not $ref.Contains('.')) { return $ref }
$dotIdx = $ref.IndexOf('.')
$typePart = $ref.Substring(0, $dotIdx)
$namePart = $ref.Substring($dotIdx + 1)
if ($script:contentTypeMap.ContainsKey($typePart)) {
$typePart = $script:contentTypeMap[$typePart]
}
return "$typePart.$namePart"
}
# --- 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"
$sibling = Join-Path (Split-Path $SubsystemPath) "$dirName.xml"
if (Test-Path $candidate) { $SubsystemPath = $candidate }
elseif (Test-Path $sibling) { $SubsystemPath = $sibling }
else { Write-Error "No $dirName.xml found in directory or as sibling"; exit 1 }
}
# File not found — check Dir/Name/Name.xml → Dir/Name.xml
if (-not (Test-Path $SubsystemPath)) {
$fn = [System.IO.Path]::GetFileNameWithoutExtension($SubsystemPath)
$pd = Split-Path $SubsystemPath
if ($fn -eq (Split-Path $pd -Leaf)) {
$c = Join-Path (Split-Path $pd) "$fn.xml"
if (Test-Path $c) { $SubsystemPath = $c }
}
}
if (-not (Test-Path $SubsystemPath)) { Write-Error "File not found: $SubsystemPath"; exit 1 }
$resolvedPath = (Resolve-Path $SubsystemPath).Path
$script:resolvedPath = $resolvedPath
# --- Load XML with PreserveWhitespace ---
$script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
$script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version")
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
$script:utf8Bom = New-Object System.Text.UTF8Encoding($true)
$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 Esc-Xml([string]$s) {
return $s.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
function New-Guid-String {
return [System.Guid]::NewGuid().ToString()
}
function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [string]$formatVersion, [System.Text.Encoding]$utf8Bom) {
$childUuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder 2048
[void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>')
[void]$sb.AppendLine("<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=`"$formatVersion`">")
[void]$sb.AppendLine("`t<Subsystem uuid=`"$childUuid`">")
[void]$sb.AppendLine("`t`t<Properties>")
[void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>")
[void]$sb.AppendLine("`t`t`t<Synonym/>")
[void]$sb.AppendLine("`t`t`t<Comment/>")
[void]$sb.AppendLine("`t`t`t<IncludeHelpInContents>true</IncludeHelpInContents>")
[void]$sb.AppendLine("`t`t`t<IncludeInCommandInterface>true</IncludeInCommandInterface>")
[void]$sb.AppendLine("`t`t`t<UseOneCommand>false</UseOneCommand>")
[void]$sb.AppendLine("`t`t`t<Explanation/>")
[void]$sb.AppendLine("`t`t`t<Picture/>")
[void]$sb.AppendLine("`t`t`t<Content/>")
[void]$sb.AppendLine("`t`t</Properties>")
[void]$sb.AppendLine("`t`t<ChildObjects/>")
[void]$sb.AppendLine("`t</Subsystem>")
[void]$sb.AppendLine('</MetaDataObject>')
[System.IO.File]::WriteAllText($childPath, $sb.ToString(), $utf8Bom)
}
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 ($rawItem in $items) {
$item = Normalize-ContentRef $rawItem
if ($item -ne $rawItem) { Write-Host "[NORM] Content: $rawItem -> $item" }
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"
# Write stub XML for the new child if it doesn't exist yet
$parentDir = [System.IO.Path]::GetDirectoryName($script:resolvedPath)
$parentBaseName = [System.IO.Path]::GetFileNameWithoutExtension($script:resolvedPath)
$childSubsDir = Join-Path (Join-Path $parentDir $parentBaseName) "Subsystems"
if (-not (Test-Path $childSubsDir)) {
New-Item -ItemType Directory -Path $childSubsDir -Force | Out-Null
Info "Created directory: $childSubsDir"
}
$childXml = Join-Path $childSubsDir "$childName.xml"
if (-not (Test-Path $childXml)) {
Write-ChildSubsystemStub $childXml $childName $script:formatVersion $script:utf8Bom
Info "Created stub: $childXml"
}
}
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
@@ -0,0 +1,618 @@
#!/usr/bin/env python3
# subsystem-edit v1.2 — Edit existing 1C subsystem XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
import os
import subprocess
import sys
import uuid
from lxml import etree
def new_uuid():
return str(uuid.uuid4())
def esc_xml(s):
return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;').replace('"', '&quot;')
def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content)
def write_child_subsystem_stub(child_path, child_name, format_version):
child_uuid = new_uuid()
lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append(
'<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" '
f'version="{format_version}">'
)
lines.append(f'\t<Subsystem uuid="{child_uuid}">')
lines.append('\t\t<Properties>')
lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>')
lines.append('\t\t\t<Synonym/>')
lines.append('\t\t\t<Comment/>')
lines.append('\t\t\t<IncludeHelpInContents>true</IncludeHelpInContents>')
lines.append('\t\t\t<IncludeInCommandInterface>true</IncludeInCommandInterface>')
lines.append('\t\t\t<UseOneCommand>false</UseOneCommand>')
lines.append('\t\t\t<Explanation/>')
lines.append('\t\t\t<Picture/>')
lines.append('\t\t\t<Content/>')
lines.append('\t\t</Properties>')
lines.append('\t\t<ChildObjects/>')
lines.append('\t</Subsystem>')
lines.append('</MetaDataObject>')
write_utf8_bom(child_path, '\n'.join(lines) + '\n')
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
V8_NS = "http://v8.1c.ru/8.1/data/core"
XS_NS = "http://www.w3.org/2001/XMLSchema"
NSMAP_WRAPPER = {
None: MD_NS,
"xsi": XSI_NS,
"v8": V8_NS,
"xr": XR_NS,
"xs": XS_NS,
}
CONTENT_TYPE_MAP = {
'Catalogs': 'Catalog', 'Documents': 'Document', 'Enums': 'Enum',
'Constants': 'Constant', 'Reports': 'Report', 'DataProcessors': 'DataProcessor',
'InformationRegisters': 'InformationRegister', 'AccumulationRegisters': 'AccumulationRegister',
'AccountingRegisters': 'AccountingRegister', 'CalculationRegisters': 'CalculationRegister',
'ChartsOfAccounts': 'ChartOfAccounts', 'ChartsOfCharacteristicTypes': 'ChartOfCharacteristicTypes',
'ChartsOfCalculationTypes': 'ChartOfCalculationTypes',
'BusinessProcesses': 'BusinessProcess', 'Tasks': 'Task',
'ExchangePlans': 'ExchangePlan', 'DocumentJournals': 'DocumentJournal',
'CommonModules': 'CommonModule', 'CommonCommands': 'CommonCommand',
'CommonForms': 'CommonForm', 'CommonPictures': 'CommonPicture',
'CommonTemplates': 'CommonTemplate', 'CommonAttributes': 'CommonAttribute',
'CommandGroups': 'CommandGroup', 'Roles': 'Role',
'SessionParameters': 'SessionParameter', 'FilterCriteria': 'FilterCriterion',
'XDTOPackages': 'XDTOPackage', 'WebServices': 'WebService',
'HTTPServices': 'HTTPService', 'WSReferences': 'WSReference',
'EventSubscriptions': 'EventSubscription', 'ScheduledJobs': 'ScheduledJob',
'SettingsStorages': 'SettingsStorage', 'FunctionalOptions': 'FunctionalOption',
'FunctionalOptionsParameters': 'FunctionalOptionsParameter',
'DefinedTypes': 'DefinedType', 'DocumentNumerators': 'DocumentNumerator',
'Sequences': 'Sequence', 'Subsystems': 'Subsystem',
'StyleItems': 'StyleItem', 'IntegrationServices': 'IntegrationService',
# Russian singular
'Справочник': 'Catalog', 'Каталог': 'Catalog', 'Документ': 'Document',
'Перечисление': 'Enum', 'Константа': 'Constant',
'Отчёт': 'Report', 'Отчет': 'Report', 'Обработка': 'DataProcessor',
'РегистрСведений': 'InformationRegister', 'РегистрНакопления': 'AccumulationRegister',
'РегистрБухгалтерии': 'AccountingRegister',
'РегистрРасчёта': 'CalculationRegister', 'РегистрРасчета': 'CalculationRegister',
'ПланСчетов': 'ChartOfAccounts', 'ПланВидовХарактеристик': 'ChartOfCharacteristicTypes',
'ПланВидовРасчёта': 'ChartOfCalculationTypes', 'ПланВидовРасчета': 'ChartOfCalculationTypes',
'БизнесПроцесс': 'BusinessProcess', 'Задача': 'Task',
'ПланОбмена': 'ExchangePlan', 'ЖурналДокументов': 'DocumentJournal',
'ОбщийМодуль': 'CommonModule', 'ОбщаяКоманда': 'CommonCommand',
'ОбщаяФорма': 'CommonForm', 'ОбщаяКартинка': 'CommonPicture',
'ОбщийМакет': 'CommonTemplate', 'ОбщийРеквизит': 'CommonAttribute',
'ГруппаКоманд': 'CommandGroup', 'Роль': 'Role',
'ПараметрСеанса': 'SessionParameter', 'КритерийОтбора': 'FilterCriterion',
'ПакетXDTO': 'XDTOPackage', 'ВебСервис': 'WebService',
'HTTPСервис': 'HTTPService', 'WSСсылка': 'WSReference',
'ПодпискаНаСобытие': 'EventSubscription', 'РегламентноеЗадание': 'ScheduledJob',
'ХранилищеНастроек': 'SettingsStorage', 'ФункциональнаяОпция': 'FunctionalOption',
'ПараметрФункциональныхОпций': 'FunctionalOptionsParameter',
'ОпределяемыйТип': 'DefinedType', 'Подсистема': 'Subsystem',
'ЭлементСтиля': 'StyleItem', 'СервисИнтеграции': 'IntegrationService',
# Russian plural
'Справочники': 'Catalog', 'Документы': 'Document', 'Перечисления': 'Enum',
'Константы': 'Constant', 'Отчёты': 'Report', 'Отчеты': 'Report',
'Обработки': 'DataProcessor', 'РегистрыСведений': 'InformationRegister',
'РегистрыНакопления': 'AccumulationRegister', 'РегистрыБухгалтерии': 'AccountingRegister',
'РегистрыРасчёта': 'CalculationRegister', 'РегистрыРасчета': 'CalculationRegister',
'ПланыСчетов': 'ChartOfAccounts', 'ПланыВидовХарактеристик': 'ChartOfCharacteristicTypes',
'ПланыВидовРасчёта': 'ChartOfCalculationTypes', 'ПланыВидовРасчета': 'ChartOfCalculationTypes',
'БизнесПроцессы': 'BusinessProcess', 'Задачи': 'Task',
'ПланыОбмена': 'ExchangePlan', 'ЖурналыДокументов': 'DocumentJournal',
'ОбщиеМодули': 'CommonModule', 'ОбщиеКоманды': 'CommonCommand',
'ОбщиеФормы': 'CommonForm', 'ОбщиеКартинки': 'CommonPicture',
'ОбщиеМакеты': 'CommonTemplate', 'ОбщиеРеквизиты': 'CommonAttribute',
'ГруппыКоманд': 'CommandGroup', 'Роли': 'Role',
'ПараметрыСеанса': 'SessionParameter', 'КритерииОтбора': 'FilterCriterion',
'ПакетыXDTO': 'XDTOPackage', 'ВебСервисы': 'WebService',
'HTTPСервисы': 'HTTPService', 'WSСсылки': 'WSReference',
'ПодпискиНаСобытия': 'EventSubscription', 'РегламентныеЗадания': 'ScheduledJob',
'ХранилищаНастроек': 'SettingsStorage', 'ФункциональныеОпции': 'FunctionalOption',
'ОпределяемыеТипы': 'DefinedType', 'Подсистемы': 'Subsystem',
'ЭлементыСтиля': 'StyleItem', 'СервисыИнтеграции': 'IntegrationService',
}
def normalize_content_ref(ref):
if not ref or '.' not in ref:
return ref
dot_idx = ref.index('.')
type_part = ref[:dot_idx]
name_part = ref[dot_idx + 1:]
if type_part in CONTENT_TYPE_MAP:
type_part = CONTENT_TYPE_MAP[type_part]
return f'{type_part}.{name_part}'
def localname(el):
return etree.QName(el.tag).localname
def info(msg):
print(f"[INFO] {msg}")
def warn(msg):
print(f"[WARN] {msg}")
def get_child_indent(container):
"""Detect indentation of children inside a container element."""
if container.text and "\n" in container.text:
after_nl = container.text.rsplit("\n", 1)[-1]
if after_nl and not after_nl.strip():
return after_nl
for child in container:
if child.tail and "\n" in child.tail:
after_nl = child.tail.rsplit("\n", 1)[-1]
if after_nl and not after_nl.strip():
return after_nl
# Fallback: count depth
depth = 0
current = container
while current is not None:
depth += 1
current = current.getparent()
return "\t" * depth
def insert_before_closing(container, new_el, child_indent):
"""Insert new_el before the closing tag of container, with proper indentation."""
children = list(container)
if len(children) == 0:
# Empty element: set text to newline+indent, tail of new_el to newline+parent_indent
parent_indent = child_indent[:-1] if len(child_indent) > 0 else ""
container.text = "\r\n" + child_indent
new_el.tail = "\r\n" + parent_indent
container.append(new_el)
else:
last = children[-1]
new_el.tail = last.tail
last.tail = "\r\n" + child_indent
container.append(new_el)
def remove_with_indent(el):
"""Remove element and clean up surrounding whitespace."""
parent = el.getparent()
prev = el.getprevious()
if prev is not None:
# Transfer el.tail to prev.tail
if el.tail and el.tail.strip() == "":
pass # just drop extra whitespace
prev.tail = el.tail if el.tail and el.tail.strip() else (prev.tail or "")
# Actually try to keep the prev's tail as the closing indent
# Better approach: set prev.tail to what el.tail was (newline+indent of next or closing)
if el.tail:
prev.tail = el.tail
else:
# First child: adjust parent.text
if el.tail:
parent.text = el.tail
parent.remove(el)
def expand_self_closing(container, parent_indent):
"""If container is self-closing (no children, no text), add closing whitespace."""
if len(container) == 0 and not (container.text and container.text.strip()):
container.text = "\r\n" + parent_indent
def import_fragment(xml_string, doc_root):
"""Parse an XML fragment in the MD namespace context and return elements."""
wrapper = (
f'<_W xmlns="{MD_NS}" xmlns:xsi="{XSI_NS}" xmlns:v8="{V8_NS}" '
f'xmlns:xr="{XR_NS}" xmlns:xs="{XS_NS}">{xml_string}</_W>'
)
frag = etree.fromstring(wrapper.encode("utf-8"))
nodes = []
for child in frag:
nodes.append(child)
return nodes
def parse_value_list(val):
"""Parse a string or JSON array into a list of strings."""
val = val.strip()
if val.startswith("["):
arr = json.loads(val)
return [str(item) for item in arr]
return [val]
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="Edit existing 1C subsystem XML", allow_abbrev=False)
parser.add_argument("-SubsystemPath", "-Path", required=True)
parser.add_argument("-DefinitionFile", default=None)
parser.add_argument("-Operation", default=None, choices=["add-content", "remove-content", "add-child", "remove-child", "set-property"])
parser.add_argument("-Value", default=None)
parser.add_argument("-NoValidate", action="store_true")
args = parser.parse_args()
# --- Mode validation ---
if args.DefinitionFile and args.Operation:
print("Cannot use both -DefinitionFile and -Operation", file=sys.stderr)
sys.exit(1)
if not args.DefinitionFile and not args.Operation:
print("Either -DefinitionFile or -Operation is required", file=sys.stderr)
sys.exit(1)
# --- Resolve path ---
subsystem_path = args.SubsystemPath
if not os.path.isabs(subsystem_path):
subsystem_path = os.path.join(os.getcwd(), subsystem_path)
if os.path.isdir(subsystem_path):
dir_name = os.path.basename(subsystem_path)
candidate = os.path.join(subsystem_path, f"{dir_name}.xml")
sibling = os.path.join(os.path.dirname(subsystem_path), f"{dir_name}.xml")
if os.path.isfile(candidate):
subsystem_path = candidate
elif os.path.isfile(sibling):
subsystem_path = sibling
else:
print(f"No {dir_name}.xml found in directory or as sibling", file=sys.stderr)
sys.exit(1)
if not os.path.isfile(subsystem_path):
fn = os.path.splitext(os.path.basename(subsystem_path))[0]
pd = os.path.dirname(subsystem_path)
if fn == os.path.basename(pd):
c = os.path.join(os.path.dirname(pd), f"{fn}.xml")
if os.path.isfile(c):
subsystem_path = c
if not os.path.isfile(subsystem_path):
print(f"File not found: {subsystem_path}", file=sys.stderr)
sys.exit(1)
resolved_path = os.path.abspath(subsystem_path)
# --- Load XML ---
xml_parser = etree.XMLParser(remove_blank_text=False)
tree = etree.parse(resolved_path, xml_parser)
xml_root = tree.getroot()
format_version = xml_root.get("version") or "2.17"
add_count = 0
remove_count = 0
modify_count = 0
# --- Detect structure ---
sub = None
for child in xml_root:
if isinstance(child.tag, str) and localname(child) == "Subsystem":
sub = child
break
if sub is None:
print("No <Subsystem> element found", file=sys.stderr)
sys.exit(1)
props_el = None
child_objs_el = None
for child in sub:
if not isinstance(child.tag, str):
continue
if localname(child) == "Properties":
props_el = child
if localname(child) == "ChildObjects":
child_objs_el = child
obj_name = ""
if props_el is not None:
for child in props_el:
if isinstance(child.tag, str) and localname(child) == "Name":
obj_name = (child.text or "").strip()
break
info(f"Subsystem: {obj_name}")
# --- Operations ---
def do_add_content(items):
nonlocal add_count
content_el = None
for child in props_el:
if isinstance(child.tag, str) and localname(child) == "Content":
content_el = child
break
if content_el is None:
print("No <Content> element found", file=sys.stderr)
sys.exit(1)
existing = set()
for child in content_el:
if isinstance(child.tag, str) and localname(child) == "Item":
existing.add((child.text or "").strip())
props_indent = get_child_indent(props_el)
if len(content_el) == 0 and not (content_el.text and content_el.text.strip()):
expand_self_closing(content_el, props_indent)
content_indent = get_child_indent(content_el)
for raw_item in items:
item = normalize_content_ref(raw_item)
if item != raw_item:
print(f'[NORM] Content: {raw_item} -> {item}')
if item in existing:
warn(f"Content already contains: {item}")
continue
frag_xml = f'<xr:Item xsi:type="xr:MDObjectRef">{item}</xr:Item>'
nodes = import_fragment(frag_xml, xml_root)
if nodes:
insert_before_closing(content_el, nodes[0], content_indent)
add_count += 1
info(f"Added content: {item}")
def do_remove_content(items):
nonlocal remove_count
content_el = None
for child in props_el:
if isinstance(child.tag, str) and localname(child) == "Content":
content_el = child
break
if content_el is None:
print("No <Content> element found", file=sys.stderr)
sys.exit(1)
for item in items:
found = False
for child in list(content_el):
if isinstance(child.tag, str) and localname(child) == "Item" and (child.text or "").strip() == item:
remove_with_indent(child)
remove_count += 1
info(f"Removed content: {item}")
found = True
break
if not found:
warn(f"Content item not found: {item}")
def do_add_child(child_name):
nonlocal add_count
if child_objs_el is None:
print("No <ChildObjects> element found", file=sys.stderr)
sys.exit(1)
for child in child_objs_el:
if isinstance(child.tag, str) and localname(child) == "Subsystem" and (child.text or "").strip() == child_name:
warn(f"ChildObjects already contains: {child_name}")
return
sub_indent = get_child_indent(sub)
if len(child_objs_el) == 0 and not (child_objs_el.text and child_objs_el.text.strip()):
expand_self_closing(child_objs_el, sub_indent)
ci = get_child_indent(child_objs_el)
new_el = etree.SubElement(child_objs_el, f"{{{MD_NS}}}Subsystem")
# Actually we need to use insert_before_closing pattern
child_objs_el.remove(new_el)
new_el = etree.Element(f"{{{MD_NS}}}Subsystem")
new_el.text = child_name
insert_before_closing(child_objs_el, new_el, ci)
add_count += 1
info(f"Added child subsystem: {child_name}")
# Write stub XML for the new child if it doesn't exist yet
parent_dir = os.path.dirname(resolved_path)
parent_base_name = os.path.splitext(os.path.basename(resolved_path))[0]
child_subs_dir = os.path.join(parent_dir, parent_base_name, 'Subsystems')
if not os.path.exists(child_subs_dir):
os.makedirs(child_subs_dir, exist_ok=True)
info(f"Created directory: {child_subs_dir}")
child_xml = os.path.join(child_subs_dir, f'{child_name}.xml')
if not os.path.exists(child_xml):
write_child_subsystem_stub(child_xml, child_name, format_version)
info(f"Created stub: {child_xml}")
def do_remove_child(child_name):
nonlocal remove_count
if child_objs_el is None:
print("No <ChildObjects> element found", file=sys.stderr)
sys.exit(1)
found = False
for child in list(child_objs_el):
if isinstance(child.tag, str) and localname(child) == "Subsystem" and (child.text or "").strip() == child_name:
remove_with_indent(child)
remove_count += 1
info(f"Removed child subsystem: {child_name}")
found = True
break
if not found:
warn(f"Child subsystem not found: {child_name}")
def do_set_property(json_val):
nonlocal modify_count
prop_def = json.loads(json_val)
prop_name = str(prop_def["name"])
prop_value = str(prop_def.get("value", ""))
prop_el = None
for child in props_el:
if isinstance(child.tag, str) and localname(child) == prop_name:
prop_el = child
break
if prop_el is None:
print(f"Property '{prop_name}' not found in Properties", file=sys.stderr)
sys.exit(1)
bool_props = ["IncludeInCommandInterface", "UseOneCommand", "IncludeHelpInContents"]
if prop_name in bool_props:
prop_el.text = prop_value.lower()
# Clear children
for ch in list(prop_el):
prop_el.remove(ch)
modify_count += 1
info(f"Set {prop_name} = {prop_value}")
return
ml_props = ["Synonym", "Explanation"]
if prop_name in ml_props:
if not prop_value:
# Clear - make self-closing
for ch in list(prop_el):
prop_el.remove(ch)
prop_el.text = None
modify_count += 1
info(f"Cleared {prop_name}")
else:
for ch in list(prop_el):
prop_el.remove(ch)
indent = get_child_indent(props_el)
item_el = etree.SubElement(prop_el, f"{{{V8_NS}}}item")
lang_el = etree.SubElement(item_el, f"{{{V8_NS}}}lang")
lang_el.text = "ru"
content_el = etree.SubElement(item_el, f"{{{V8_NS}}}content")
content_el.text = prop_value
# Set whitespace
prop_el.text = "\r\n" + indent + "\t"
item_el.text = "\r\n" + indent + "\t\t"
lang_el.tail = "\r\n" + indent + "\t\t"
content_el.tail = "\r\n" + indent + "\t"
item_el.tail = "\r\n" + indent
modify_count += 1
info(f'Set {prop_name} = "{prop_value}"')
return
if prop_name == "Comment":
for ch in list(prop_el):
prop_el.remove(ch)
if not prop_value:
prop_el.text = None
else:
prop_el.text = prop_value
modify_count += 1
info(f'Set Comment = "{prop_value}"')
return
if prop_name == "Picture":
for ch in list(prop_el):
prop_el.remove(ch)
if not prop_value:
prop_el.text = None
else:
indent = get_child_indent(props_el)
ref_el = etree.SubElement(prop_el, f"{{{XR_NS}}}Ref")
ref_el.text = prop_value
load_el = etree.SubElement(prop_el, f"{{{XR_NS}}}LoadTransparent")
load_el.text = "false"
prop_el.text = "\r\n" + indent + "\t"
ref_el.tail = "\r\n" + indent + "\t"
load_el.tail = "\r\n" + indent
modify_count += 1
info(f'Set Picture = "{prop_value}"')
return
# Generic text property
for ch in list(prop_el):
prop_el.remove(ch)
prop_el.text = prop_value
modify_count += 1
info(f'Set {prop_name} = "{prop_value}"')
# --- Execute operations ---
operations = []
if args.DefinitionFile:
def_file = args.DefinitionFile
if not os.path.isabs(def_file):
def_file = os.path.join(os.getcwd(), def_file)
with open(def_file, "r", encoding="utf-8-sig") as fh:
ops = json.loads(fh.read())
if isinstance(ops, list):
operations = ops
else:
operations = [ops]
else:
operations = [{"operation": args.Operation, "value": args.Value or ""}]
for op in operations:
op_name = op.get("operation", args.Operation or "")
op_value = op.get("value", args.Value or "")
if op_name == "add-content":
do_add_content(parse_value_list(op_value))
elif op_name == "remove-content":
do_remove_content(parse_value_list(op_value))
elif op_name == "add-child":
do_add_child(op_value)
elif op_name == "remove-child":
do_remove_child(op_value)
elif op_name == "set-property":
do_set_property(op_value)
else:
print(f"Unknown operation: {op_name}", file=sys.stderr)
sys.exit(1)
# --- Save ---
save_xml_bom(tree, resolved_path)
info(f"Saved: {resolved_path}")
# --- Auto-validate ---
if not args.NoValidate:
validate_script = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "subsystem-validate", "scripts", "subsystem-validate.py"))
if os.path.isfile(validate_script):
print()
print("--- Running subsystem-validate ---")
subprocess.run([sys.executable, validate_script, "-SubsystemPath", "-Path", resolved_path])
# --- Summary ---
print()
print("=== subsystem-edit summary ===")
print(f" Subsystem: {obj_name}")
print(f" Added: {add_count}")
print(f" Removed: {remove_count}")
print(f" Modified: {modify_count}")
sys.exit(0)
if __name__ == "__main__":
main()