Add inline mode and complex properties to /meta-edit skill

- Inline mode (-Operation/-Value) as alternative to JSON -DefinitionFile
  for quick single operations: add/remove/modify attributes, TS, dims,
  resources, enum values, forms, templates, commands
- Batch syntax with ;; separator, positional insertion (>> after, << before)
- Complex property support: Owners, RegisterRecords, BasedOn, InputByString
  with add-*/remove-*/set-* inline ops and JSON modify.properties arrays
- Paren-aware comma splitting for types like Number(15,2) in modify values

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-02-14 16:01:53 +03:00
parent cb4b5ec935
commit 989f4e49d9
2 changed files with 596 additions and 65 deletions
+177 -58
View File
@@ -1,7 +1,7 @@
---
name: meta-edit
description: Точечное редактирование объекта метаданных 1С (добавление/удаление/модификация реквизитов, ТЧ, измерений, ресурсов, значений перечислений, свойств объекта)
argument-hint: <DefinitionFile> <ObjectPath> [-NoValidate]
description: Точечное редактирование объекта метаданных 1С (добавление/удаление/модификация реквизитов, ТЧ, измерений, ресурсов, значений перечислений, свойств объекта, владельцев, движений, ввода по строке)
argument-hint: <ObjectPath> -Operation <op> -Value "<val>" | -DefinitionFile <json> [-NoValidate]
allowed-tools:
- Bash
- Read
@@ -11,23 +11,127 @@ allowed-tools:
# /meta-edit — точечное редактирование метаданных 1С
Атомарные операции модификации существующих XML объектов метаданных: добавление, удаление и модификация реквизитов, табличных частей, измерений, ресурсов, значений перечислений, свойств объекта.
Атомарные операции модификации существующих XML объектов метаданных: добавление, удаление и модификация реквизитов, табличных частей, измерений, ресурсов, значений перечислений, свойств объекта, владельцев, движений по регистрам, оснований, ввода по строке.
## Параметры и команда
## Два режима работы
| Параметр | Обязательный | Описание |
|----------|:------------:|----------|
| DefinitionFile | да | JSON-файл с операциями |
| ObjectPath | да | XML-файл или директория объекта |
| NoValidate | нет | Не запускать meta-validate после правки |
### 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-файл или директория объекта (обязательный) |
| Operation | Inline-операция (альтернатива DefinitionFile) |
| Value | Значение для inline-операции |
| DefinitionFile | JSON-файл с операциями (альтернатива Operation) |
| NoValidate | Не запускать meta-validate после правки |
`ObjectPath` авторезолв: если указана директория — ищет `<dirName>.xml` в ней.
## JSON DSL — три операции
## Inline mode — операции
### Batch-режим
Несколько элементов через `;;`:
```
-Value "Комментарий: Строка(200) ;; Сумма: Число(15,2) | index"
```
### add-attribute / add-dimension / add-resource / add-column
Shorthand-формат: `Имя: Тип | флаги`
```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"
```
Позиционная вставка: `>> after ИмяЭлемента` или `<< before ИмяЭлемента`:
```powershell
-Operation add-attribute -Value "Склад: CatalogRef.Склады >> after Организация"
```
### add-ts
Формат: `ИмяТЧ: Реквизит1: Тип1, Реквизит2: Тип2, ...`
```powershell
-Operation add-ts -Value "Товары: Ном: CatalogRef.Ном | req, Кол: Число(15,3), Цена: Число(15,2), Сумма: Число(15,2)"
```
### add-enumValue / add-form / add-template / add-command
Просто имена (batch через `;;`):
```powershell
-Operation add-enumValue -Value "Значение1 ;; Значение2 ;; Значение3"
-Operation add-form -Value "ФормаЭлемента ;; ФормаСписка"
```
### 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-*
Имя элемента (или несколько через `;;`):
```powershell
-Operation remove-attribute -Value "СтарыйРеквизит ;; ЕщёОдин"
-Operation remove-owner -Value "Catalog.Контрагенты"
-Operation remove-inputByString -Value "Catalog.МойСпр.StandardAttribute.Code"
```
### 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"
```
### modify-property
Формат: `Ключ=Значение` (batch через `;;`):
```powershell
-Operation modify-property -Value "CodeLength=11 ;; DescriptionLength=150"
-Operation modify-property -Value "Hierarchical=true"
```
### 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"
```
## JSON DSL
### add — добавить элементы
@@ -67,7 +171,13 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
```json
{
"modify": {
"properties": { "CodeLength": 11, "Hierarchical": true },
"properties": {
"CodeLength": 11,
"Hierarchical": true,
"Owners": ["Catalog.Контрагенты", "Catalog.Организации"],
"RegisterRecords": ["AccumulationRegister.Продажи"],
"InputByString": ["StandardAttribute.Description"]
},
"реквизиты": {
"Комментарий": { "type": "Строка(500)" },
"СтароеИмя": { "name": "НовоеИмя" }
@@ -78,7 +188,7 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
### Комбинирование
Все три операции можно указать в одном JSON-файле.
Все три операции можно указать в одном JSON-файле. Для сложных сценариев (ТЧ с реквизитами + удаление + модификация) используйте JSON DSL.
### Синонимы ключей (case-insensitive)
@@ -103,14 +213,11 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
`Строка(200)`, `Число(15,2)`, `Булево`, `Дата`, `ДатаВремя`, `ХранилищеЗначения`, `СправочникСсылка.XXX`, `ДокументСсылка.XXX`, `ПеречислениеСсылка.XXX`, `ОпределяемыйТип.XXX`.
### Позиционная вставка (опционально)
### Позиционная вставка
```json
{ "name": "Склад", "type": "CatalogRef.Склады", "after": "Организация" }
{ "name": "Приоритет", "type": "Число(1)", "before": "Комментарий" }
```
JSON: `{ "name": "Склад", "type": "CatalogRef.Склады", "after": "Организация" }`
Без указания — в конец группы однотипных элементов.
Inline: `"Склад: CatalogRef.Склады >> after Организация"` или `"Склад: CatalogRef.Склады << before Комментарий"`
### Shorthand-формат реквизитов
@@ -118,6 +225,17 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
"ИмяРеквизита: Тип | req, index"
```
## Complex properties — Owners, RegisterRecords, BasedOn, InputByString
Свойства со вложенной XML-структурой (не скалярный InnerText). Поддерживаются через inline-операции `add-*` / `remove-*` / `set-*` и через JSON `modify.properties`.
| Свойство | Объекты | XML-тег | Inline-значение |
|----------|---------|---------|-----------------|
| Owners | Catalog, ChartOfCharacteristicTypes | `<xr:Item xsi:type="xr:MDObjectRef">` | `Catalog.XXX` |
| RegisterRecords | Document | `<xr:Item xsi:type="xr:MDObjectRef">` | `AccumulationRegister.XXX` |
| BasedOn | Document, Catalog, BP, Task | `<xr:Item xsi:type="xr:MDObjectRef">` | `Document.XXX` |
| InputByString | Catalog, ChartOf*, Task | `<xr:Field>` | `StandardAttribute.Description` |
## Поддерживаемые типы объектов
| Тип объекта | Допустимые add-типы |
@@ -130,58 +248,55 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
## Примеры
### Добавить реквизиты в справочник
### Inline: добавить реквизиты
```json
{
"add": {
"attributes": [
"Комментарий: String(200)",
{ "name": "Сумма", "type": "Number(15,2)", "indexing": "Index" }
]
}
}
```powershell
-Operation add-attribute -Value "Комментарий: String(200) ;; Сумма: Число(15,2) | index"
```
### Добавить ТЧ в документ
### Inline: добавить ТЧ с реквизитами
```powershell
-Operation add-ts -Value "Товары: Ном: CatalogRef.Ном | req, Кол: Число(15,3), Цена: Число(15,2)"
```
### Inline: удалить + изменить (два вызова)
```powershell
-Operation remove-attribute -Value "УстаревшийРеквизит"
-Operation modify-attribute -Value "СтароеИмя: name=НовоеИмя, type=String(500)"
```
### Inline: владельцы справочника
```powershell
-Operation set-owners -Value "Catalog.Контрагенты ;; Catalog.Организации"
```
### Inline: движения документа
```powershell
-Operation add-registerRecord -Value "AccumulationRegister.Продажи ;; AccumulationRegister.ОстаткиТоваров"
```
### JSON: комплексное редактирование
```json
{
"add": {
"attributes": ["Комментарий: String(200)"],
"tabularSections": [{
"name": "Товары",
"attrs": [
"Номенклатура: CatalogRef.Номенклатура | req",
"Количество: Number(15,3)",
"Цена: Number(15,2)",
"Сумма: Number(15,2)"
]
"attrs": ["Ном: CatalogRef.Ном | req", "Кол: Number(15,3)"]
}]
}
}
```
### Добавить измерения и ресурсы в регистр
```json
{
"add": {
"dimensions": ["Организация: CatalogRef.Организации | master, mainFilter"],
"resources": ["Сумма: Number(15,2)"]
}
}
```
### Удалить + изменить
```json
{
},
"remove": { "attributes": ["УстаревшийРеквизит"] },
"modify": {
"properties": { "DescriptionLength": 150 },
"attributes": {
"СтароеИмя": { "name": "НовоеИмя", "type": "String(500)" }
}
"properties": {
"DescriptionLength": 150,
"Owners": ["Catalog.Контрагенты", "Catalog.Организации"]
},
"attributes": { "СтароеИмя": { "name": "НовоеИмя" } }
}
}
```
@@ -201,3 +316,7 @@ powershell.exe -NoProfile -File .claude\skills\meta-edit\scripts\meta-edit.ps1 -
- **Изменение свойств** объекта (длина кода, иерархия и т.д.)
- **Добавление значений** перечислений
- **Добавление измерений/ресурсов** в регистры
- **Управление владельцами** справочников (Owners)
- **Управление движениями** документов (RegisterRecords)
- **Настройка ввода по строке** (InputByString)
- **Управление основаниями** (BasedOn)
+419 -7
View File
@@ -1,12 +1,27 @@
# meta-edit v1.0 — Edit existing 1C metadata object XML
# meta-edit v1.1 — Edit existing 1C metadata object XML (inline mode + complex properties)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[string]$DefinitionFile,
[Parameter(Mandatory)]
[string]$ObjectPath,
# Inline mode (alternative to DefinitionFile)
[ValidateSet(
"add-attribute", "add-ts", "add-dimension", "add-resource",
"add-enumValue", "add-column", "add-form", "add-template", "add-command",
"add-owner", "add-registerRecord", "add-basedOn", "add-inputByString",
"remove-attribute", "remove-ts", "remove-dimension", "remove-resource",
"remove-enumValue", "remove-column", "remove-form", "remove-template", "remove-command",
"remove-owner", "remove-registerRecord", "remove-basedOn", "remove-inputByString",
"modify-attribute", "modify-dimension", "modify-resource",
"modify-enumValue", "modify-column",
"modify-property",
"set-owners", "set-registerRecords", "set-basedOn", "set-inputByString"
)]
[string]$Operation,
[string]$Value,
[switch]$NoValidate
)
@@ -17,13 +32,26 @@ $ErrorActionPreference = "Stop"
# Section 1: Parameters + loading
# ============================================================
# --- Load JSON definition ---
if (-not (Test-Path $DefinitionFile)) {
Write-Error "Definition file not found: $DefinitionFile"
# --- Mode validation ---
if ($DefinitionFile -and $Operation) {
Write-Error "Cannot use both -DefinitionFile and -Operation"
exit 1
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$def = $jsonText | ConvertFrom-Json
if (-not $DefinitionFile -and -not $Operation) {
Write-Error "Either -DefinitionFile or -Operation is required"
exit 1
}
# --- Load JSON definition (DefinitionFile mode) ---
$def = $null
if ($DefinitionFile) {
if (-not (Test-Path $DefinitionFile)) {
Write-Error "Definition file not found: $DefinitionFile"
exit 1
}
$jsonText = Get-Content -Raw -Encoding UTF8 $DefinitionFile
$def = $jsonText | ConvertFrom-Json
}
# --- Resolve object path ---
if (Test-Path $ObjectPath -PathType Container) {
@@ -553,6 +581,14 @@ function Parse-AttributeShorthand {
flags = @(); fillChecking = ""; indexing = ""
after = ""; before = ""
}
# Extract positional markers: >> after Name, << before Name
if ($str -match '\s*>>\s*after\s+(\S+)\s*$') {
$parsed.after = $Matches[1]
$str = ($str -replace '\s*>>\s*after\s+\S+\s*$', '').Trim()
} elseif ($str -match '\s*<<\s*before\s+(\S+)\s*$') {
$parsed.before = $Matches[1]
$str = ($str -replace '\s*<<\s*before\s+\S+\s*$', '').Trim()
}
# Split by | for flags
$parts = $str -split '\|', 2
$mainPart = $parts[0].Trim()
@@ -1164,6 +1200,186 @@ function Resolve-ChildTypeKey([string]$key) {
return $null
}
# ============================================================
# Section 9.5: Inline mode converter
# ============================================================
function Split-ByCommaOutsideParens([string]$str) {
$result = @()
$depth = 0
$current = ""
foreach ($ch in $str.ToCharArray()) {
if ($ch -eq '(') { $depth++ }
elseif ($ch -eq ')') { $depth-- }
if ($ch -eq ',' -and $depth -eq 0) {
$result += $current
$current = ""
} else {
$current += $ch
}
}
if ($current) { $result += $current }
return ,$result
}
function Convert-InlineToDefinition([string]$operation, [string]$value) {
# Parse operation: "add-attribute" → ("add", "attribute")
$opParts = $operation -split '-', 2
$op = $opParts[0] # add, remove, modify, set
$target = $opParts[1] # attribute, ts, owner, owners, property, etc.
# Complex property targets
$complexTargetMap = @{
"owner" = "Owners"; "owners" = "Owners"
"registerRecord" = "RegisterRecords"; "registerRecords" = "RegisterRecords"
"basedOn" = "BasedOn"
"inputByString" = "InputByString"
}
if ($complexTargetMap.ContainsKey($target)) {
$propName = $complexTargetMap[$target]
$values = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
# For InputByString, auto-prefix with MetaType.Name.
if ($propName -eq "InputByString") {
$prefix = "$($script:objType).$($script:objName)."
$values = @($values | ForEach-Object {
if ($_ -notmatch '\.') {
"$prefix$_"
} elseif ($_ -notmatch '^(Catalog|Document|InformationRegister|AccumulationRegister|AccountingRegister|CalculationRegister|ChartOfCharacteristicTypes|ChartOfCalculationTypes|ChartOfAccounts|ExchangePlan|BusinessProcess|Task|Enum|Report|DataProcessor)\.') {
"$prefix$_"
} else { $_ }
})
}
$def = New-Object PSCustomObject
$complexAction = if ($op -eq "set") { "set" } else { $op }
$def | Add-Member -NotePropertyName "_complex" -NotePropertyValue @(
@{ action = $complexAction; property = $propName; values = $values }
)
return $def
}
# Target → JSON DSL child type
$targetMap = @{
"attribute" = "attributes"
"ts" = "tabularSections"
"dimension" = "dimensions"
"resource" = "resources"
"enumValue" = "enumValues"
"column" = "columns"
"form" = "forms"
"template" = "templates"
"command" = "commands"
"property" = "properties"
}
$childType = $targetMap[$target]
if (-not $childType) {
Write-Error "Unknown inline target: $target"
exit 1
}
$def = New-Object PSCustomObject
switch ($op) {
"add" {
$items = @()
if ($childType -eq "tabularSections") {
# TS format: "TSName: attr1_shorthand, attr2_shorthand, ..."
$tsValues = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
foreach ($tsVal in $tsValues) {
$colonIdx = $tsVal.IndexOf(':')
if ($colonIdx -gt 0) {
$tsName = $tsVal.Substring(0, $colonIdx).Trim()
$attrsPart = $tsVal.Substring($colonIdx + 1).Trim()
# Split attrs by comma (paren-aware), reassemble if part doesn't start with "Name:"
$rawParts = Split-ByCommaOutsideParens $attrsPart
$attrStrs = @()
$current = ""
foreach ($rp in $rawParts) {
$rp = $rp.Trim()
if ($current -and $rp -match '^[А-Яа-яЁёA-Za-z_]\w*\s*:') {
$attrStrs += $current
$current = $rp
} elseif ($current) {
$current += ", $rp"
} else {
$current = $rp
}
}
if ($current) { $attrStrs += $current }
$items += [PSCustomObject]@{ name = $tsName; attrs = $attrStrs }
} else {
# Just a name, no attrs
$items += $tsVal
}
}
} else {
# Batch split by ;;
$items = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
$addObj = New-Object PSCustomObject
$addObj | Add-Member -NotePropertyName $childType -NotePropertyValue $items
$def | Add-Member -NotePropertyName "add" -NotePropertyValue $addObj
}
"remove" {
$items = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$removeObj = New-Object PSCustomObject
$removeObj | Add-Member -NotePropertyName $childType -NotePropertyValue $items
$def | Add-Member -NotePropertyName "remove" -NotePropertyValue $removeObj
}
"modify" {
if ($childType -eq "properties") {
# "CodeLength=11 ;; DescriptionLength=150"
$kvPairs = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$propsObj = New-Object PSCustomObject
foreach ($kv in $kvPairs) {
$eqIdx = $kv.IndexOf('=')
if ($eqIdx -gt 0) {
$k = $kv.Substring(0, $eqIdx).Trim()
$v = $kv.Substring($eqIdx + 1).Trim()
$propsObj | Add-Member -NotePropertyName $k -NotePropertyValue $v
} else {
Warn "Invalid property format (expected Key=Value): $kv"
}
}
$modifyObj = New-Object PSCustomObject
$modifyObj | Add-Member -NotePropertyName "properties" -NotePropertyValue $propsObj
$def | Add-Member -NotePropertyName "modify" -NotePropertyValue $modifyObj
} else {
# "ElementName: key=val, key=val ;; Element2: key=val"
$elemDefs = @($value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
$childModObj = New-Object PSCustomObject
foreach ($elemDef in $elemDefs) {
$colonIdx = $elemDef.IndexOf(':')
if ($colonIdx -le 0) {
Warn "Invalid modify format (expected Name: key=val): $elemDef"
continue
}
$elemName = $elemDef.Substring(0, $colonIdx).Trim()
$changesPart = $elemDef.Substring($colonIdx + 1).Trim()
$changesObj = New-Object PSCustomObject
$changePairs = Split-ByCommaOutsideParens $changesPart
foreach ($cp in $changePairs) {
$cp = $cp.Trim()
$eqIdx = $cp.IndexOf('=')
if ($eqIdx -gt 0) {
$ck = $cp.Substring(0, $eqIdx).Trim()
$cv = $cp.Substring($eqIdx + 1).Trim()
$changesObj | Add-Member -NotePropertyName $ck -NotePropertyValue $cv
}
}
$childModObj | Add-Member -NotePropertyName $elemName -NotePropertyValue $changesObj
}
$modifyObj = New-Object PSCustomObject
$modifyObj | Add-Member -NotePropertyName $childType -NotePropertyValue $childModObj
$def | Add-Member -NotePropertyName "modify" -NotePropertyValue $modifyObj
}
}
}
return $def
}
# ============================================================
# Section 10: ADD operations
# ============================================================
@@ -1443,6 +1659,18 @@ function Modify-Properties($propsDef) {
return
}
# Complex property: Owners, RegisterRecords, BasedOn, InputByString
if ($script:complexPropertyMap.ContainsKey($propName)) {
$valuesList = @()
if ($propValue -is [array]) {
$valuesList = @($propValue | ForEach-Object { "$_" })
} else {
$valuesList = @("$propValue" -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
Set-ComplexProperty $propName $valuesList
return
}
# Handle boolean values
$valueStr = "$propValue"
if ($propValue -is [bool]) {
@@ -1647,12 +1875,196 @@ function Process-Modify($modifyDef) {
}
}
# ============================================================
# Section 12.5: Complex property helpers
# ============================================================
$script:complexPropertyMap = @{
"Owners" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"RegisterRecords" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"BasedOn" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"InputByString" = @{ tag = "xr:Field"; attr = $null }
}
function Find-PropertyElement([string]$propName) {
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
return $child
}
}
return $null
}
function Get-ComplexPropertyValues([System.Xml.XmlElement]$propEl) {
$values = @()
foreach ($child in $propEl.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$values += $child.InnerText.Trim()
}
}
return $values
}
function Add-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
$mapEntry = $script:complexPropertyMap[$propertyName]
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
Warn "Property element '$propertyName' not found in Properties"
return
}
# Get existing values to check duplicates
$existing = Get-ComplexPropertyValues $propEl
$indent = Get-ChildIndent $script:propertiesEl
$childIndent = "$indent`t"
# Check if element is self-closing (empty)
$isEmpty = $true
foreach ($ch in $propEl.ChildNodes) {
if ($ch.NodeType -eq 'Element') { $isEmpty = $false; break }
}
# If self-closing / empty, add closing whitespace
if ($isEmpty -and $propEl.ChildNodes.Count -eq 0) {
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$indent")
$propEl.AppendChild($closeWs) | Out-Null
}
foreach ($val in $values) {
if ($val -in $existing) {
Warn "$propertyName already contains '$val', skipping"
continue
}
$tag = $mapEntry.tag
$attrStr = $mapEntry.attr
if ($attrStr) {
$fragXml = "<$tag $attrStr>$(Esc-Xml $val)</$tag>"
} else {
$fragXml = "<$tag>$(Esc-Xml $val)</$tag>"
}
$nodes = Import-Fragment $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $propEl $node $null $childIndent
}
Info "Added $propertyName item: $val"
$script:addCount++
}
}
function Remove-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
Warn "Property element '$propertyName' not found in Properties"
return
}
foreach ($val in $values) {
$found = $false
foreach ($child in @($propEl.ChildNodes)) {
if ($child.NodeType -eq 'Element' -and $child.InnerText.Trim() -eq $val) {
Remove-NodeWithWhitespace $child
Info "Removed $propertyName item: $val"
$script:removeCount++
$found = $true
break
}
}
if (-not $found) {
Warn "$propertyName item '$val' not found, skipping"
}
}
# Collapse if empty
$hasElements = $false
foreach ($ch in $propEl.ChildNodes) {
if ($ch.NodeType -eq 'Element') { $hasElements = $true; break }
}
if (-not $hasElements) {
while ($propEl.HasChildNodes) {
$propEl.RemoveChild($propEl.FirstChild) | Out-Null
}
}
}
function Set-ComplexProperty([string]$propertyName, [string[]]$values) {
$mapEntry = $script:complexPropertyMap[$propertyName]
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
Warn "Property element '$propertyName' not found in Properties"
return
}
$indent = Get-ChildIndent $script:propertiesEl
$childIndent = "$indent`t"
# Remove all existing children
while ($propEl.HasChildNodes) {
$propEl.RemoveChild($propEl.FirstChild) | Out-Null
}
if ($values.Count -eq 0) {
# Leave self-closing
Info "Cleared $propertyName"
$script:modifyCount++
return
}
# Add closing whitespace
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$indent")
$propEl.AppendChild($closeWs) | Out-Null
# Add each value
foreach ($val in $values) {
$tag = $mapEntry.tag
$attrStr = $mapEntry.attr
if ($attrStr) {
$fragXml = "<$tag $attrStr>$(Esc-Xml $val)</$tag>"
} else {
$fragXml = "<$tag>$(Esc-Xml $val)</$tag>"
}
$nodes = Import-Fragment $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $propEl $node $null $childIndent
}
}
$count = $values.Count
Info "Set $propertyName`: $count items"
$script:modifyCount++
}
# ============================================================
# Section 13: Main processing
# ============================================================
# --- Inline mode conversion ---
if ($Operation) {
$def = Convert-InlineToDefinition $Operation $Value
}
if (-not $def) {
Write-Error "No definition loaded"
exit 1
}
# --- Process complex property operations ---
if ($def.PSObject.Properties.Match("_complex").Count -gt 0 -and $def._complex) {
foreach ($cop in $def._complex) {
switch ($cop.action) {
"add" { Add-ComplexPropertyItem $cop.property $cop.values }
"remove" { Remove-ComplexPropertyItem $cop.property $cop.values }
"set" { Set-ComplexProperty $cop.property $cop.values }
}
}
}
# --- Process standard operations ---
$def.PSObject.Properties | ForEach-Object {
$prop = $_
if ($prop.Name -eq "_complex") { return }
$opKey = Resolve-OperationKey $prop.Name
if (-not $opKey) {
Warn "Unknown operation: $($prop.Name)"