Expand /skd-edit to 22 operations with modify, clear, structure, and dataSetLink

Add 7 new operations: modify-field, modify-filter, modify-dataParameter,
clear-selection, clear-order, clear-filter, add-dataSetLink, set-structure.
Add selection dedup for add-field/add-calculated-field and order dedup
for add-order. Fix SetAttribute output leak.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-02-11 18:50:13 +03:00
parent 69213930b3
commit a9b7a90672
2 changed files with 671 additions and 17 deletions
+93 -2
View File
@@ -11,7 +11,7 @@ allowed-tools:
# /skd-edit — точечное редактирование СКД (Template.xml)
Атомарные операции модификации существующей схемы компоновки данных: добавление и удаление полей, итогов, фильтров, параметров, настроек варианта, замена запроса.
Атомарные операции модификации существующей схемы компоновки данных: добавление, удаление и модификация полей, итогов, фильтров, параметров, настроек варианта, управление структурой, замена запроса.
## Параметры и команда
@@ -36,7 +36,7 @@ powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 -Te
-Operation add-field -Value "Цена: decimal(15,2) ;; Количество: decimal(15,3) ;; Сумма: decimal(15,2)"
```
Работает для всех операций кроме `set-query`. Каждое значение обрабатывается последовательно.
Работает для всех операций кроме `set-query` и `set-structure`. Каждое значение обрабатывается последовательно.
## Операции
@@ -119,6 +119,15 @@ Shorthand-формат: `"Имя [Заголовок]: тип @роль #огр
-Operation add-selection -Value "Auto"
```
### add-dataSetLink — добавить связь наборов данных
```powershell
-Operation add-dataSetLink -Value "Набор1 > Набор2 on Поле1 = Поле2"
-Operation add-dataSetLink -Value "Набор1 > Набор2 on Поле1 = Поле2 [param Связь]"
```
Формат: `"Источник > Приёмник on ВыражениеИсточника = ВыражениеПриёмника [param ИмяПараметра]"`. Параметр связи опционален.
### set-query — заменить текст запроса
```powershell
@@ -136,6 +145,66 @@ Shorthand-формат: `"Имя [Заголовок]: тип @роль #огр
Если параметр уже существует — заменяет значение. Поддерживаемые параметры: Заголовок/Title, ВыводитьЗаголовок/OutputTitle, ВертикальноеРасположениеОбщихИтогов/VerticalOverallPlacement, ГоризонтальноеРасположениеОбщихИтогов/HorizontalOverallPlacement, РасположениеРеквизитов/AttributePlacement, РасположениеГруппировки/GroupPlacement, РасположениеПолейГруппировки/GroupFieldsPlacement, РасположениеИтогов/OverallPlacement, РасположениеОтбора/FilterOutput, ВыводитьОтбор/OutputFilter.
### set-structure — установить структуру варианта
```powershell
-Operation set-structure -Value "Организация > Номенклатура > details"
-Operation set-structure -Value "details"
```
Формат: `"ПолеГруппировки1 > ПолеГруппировки2 > details"`. `details`/`детали` — группа детальных записей (пустой groupBy). Заменяет всю существующую структуру варианта. Не поддерживает пакетный режим.
### modify-field — изменить существующее поле в наборе данных
```powershell
-Operation modify-field -Value "Цена [Цена USD]: decimal(10,4) @dimension"
-Operation modify-field -Value "Цена: decimal(10,2)"
```
Использует тот же shorthand что и `add-field`. Находит поле по dataPath, объединяет с переданными свойствами (непустые переопределяют), сохраняет позицию. Если тип/заголовок/роли/ограничения не указаны в shorthand — берутся из существующего поля.
### modify-filter — изменить существующий фильтр в варианте
```powershell
-Operation modify-filter -Value "Организация >= bar @off"
-Operation modify-filter -Value "Дата = 2025-01-01T00:00:00 @user @quickAccess"
```
Находит фильтр по полю (left), обновляет оператор, значение и флаги. Используется тот же shorthand что и `add-filter`.
### modify-dataParameter — изменить существующий параметр данных
```powershell
-Operation modify-dataParameter -Value "Период = ThisYear"
-Operation modify-dataParameter -Value "Организация @off @user"
```
Находит dataParameter по имени, обновляет значение и флаги. Используется тот же shorthand что и `add-dataParameter`.
### clear-selection — очистить выборку варианта
```powershell
-Operation clear-selection -Value "*"
```
Удаляет все элементы из `<dcsset:selection>`. Value игнорируется.
### clear-order — очистить сортировку варианта
```powershell
-Operation clear-order -Value "*"
```
Удаляет все элементы из `<dcsset:order>`. Value игнорируется.
### clear-filter — очистить фильтры варианта
```powershell
-Operation clear-filter -Value "*"
```
Удаляет все элементы из `<dcsset:filter>`. Value игнорируется.
### remove-field — удалить поле из набора данных
```powershell
@@ -207,6 +276,28 @@ powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
# Заменить запрос
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation set-query -Value "ВЫБРАТЬ 1 КАК Тест"
# Установить структуру группировок
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation set-structure -Value "Организация > Номенклатура > details"
# Модифицировать поле
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation modify-field -Value "Цена [Цена USD]: decimal(10,4) @dimension"
# Модифицировать фильтр
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation modify-filter -Value "Организация >= bar @off"
# Очистить выборку и установить заново
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation clear-selection -Value "*"
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation add-selection -Value "Auto"
# Добавить связь наборов данных
powershell.exe -NoProfile -File .claude\skills\skd-edit\scripts\skd-edit.ps1 `
-TemplatePath test-tmp\edit-test.xml -Operation add-dataSetLink -Value "Набор1 > Набор2 on Поле1 = Поле2"
```
## Верификация
+578 -15
View File
@@ -5,8 +5,10 @@
[Parameter(Mandatory)]
[ValidateSet(
"add-field","add-total","add-calculated-field","add-parameter","add-filter",
"add-dataParameter","add-order","add-selection",
"set-query","set-outputParameter",
"add-dataParameter","add-order","add-selection","add-dataSetLink",
"set-query","set-outputParameter","set-structure",
"modify-field","modify-filter","modify-dataParameter",
"clear-selection","clear-order","clear-filter",
"remove-field","remove-total","remove-calculated-field","remove-parameter","remove-filter")]
[string]$Operation,
@@ -145,6 +147,66 @@ function Parse-FieldShorthand {
return $result
}
function Read-FieldProperties($fieldEl) {
$props = @{
dataPath = ""; field = ""; title = ""; type = ""
roles = @(); restrict = @()
}
foreach ($ch in $fieldEl.ChildNodes) {
if ($ch.NodeType -ne 'Element') { continue }
switch ($ch.LocalName) {
"dataPath" { $props.dataPath = $ch.InnerText.Trim() }
"field" { $props.field = $ch.InnerText.Trim() }
"title" {
# Extract text from LocalStringType
foreach ($item in $ch.ChildNodes) {
if ($item.NodeType -eq 'Element' -and $item.LocalName -eq 'item') {
foreach ($gc in $item.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'content') {
$props.title = $gc.InnerText.Trim()
}
}
}
}
}
"valueType" {
# Read type info — store the raw element for now, we'll use type from parsed if overridden
$typeEl = $null
foreach ($gc in $ch.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'Type') {
$typeEl = $gc; break
}
}
if ($typeEl) {
$props["_rawTypeText"] = $typeEl.InnerText.Trim()
}
}
"role" {
foreach ($gc in $ch.ChildNodes) {
if ($gc.NodeType -eq 'Element') {
if ($gc.LocalName -eq 'periodNumber') {
$props.roles += "period"
} elseif ($gc.InnerText.Trim() -eq 'true') {
$props.roles += $gc.LocalName
}
}
}
}
"useRestriction" {
$revMap = @{ "field" = "noField"; "condition" = "noFilter"; "group" = "noGroup"; "order" = "noOrder" }
foreach ($gc in $ch.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.InnerText.Trim() -eq 'true') {
$mapped = $revMap[$gc.LocalName]
if ($mapped) { $props.restrict += $mapped }
}
}
}
}
}
return $props
}
function Parse-TotalShorthand {
param([string]$s)
@@ -339,6 +401,58 @@ function Parse-OrderShorthand {
return @{ field = $field; direction = $dir }
}
function Parse-DataSetLinkShorthand {
param([string]$s)
$result = @{ source = ""; dest = ""; sourceExpr = ""; destExpr = ""; parameter = "" }
# Extract optional [param ParamName]
if ($s -match '\[param\s+([^\]]+)\]') {
$result.parameter = $Matches[1].Trim()
$s = $s -replace '\s*\[param\s+[^\]]+\]', ''
}
# Pattern: "Source > Dest on FieldA = FieldB"
if ($s -match '^(.+?)\s*>\s*(.+?)\s+on\s+(.+?)\s*=\s*(.+)$') {
$result.source = $Matches[1].Trim()
$result.dest = $Matches[2].Trim()
$result.sourceExpr = $Matches[3].Trim()
$result.destExpr = $Matches[4].Trim()
} else {
Write-Error "Invalid dataSetLink shorthand: $s. Expected: 'Source > Dest on FieldA = FieldB [param Name]'"
exit 1
}
return $result
}
function Parse-StructureShorthand {
param([string]$s)
$segments = $s -split '\s*>\s*'
$result = @()
$innermost = $null
for ($i = $segments.Count - 1; $i -ge 0; $i--) {
$seg = $segments[$i].Trim()
$group = @{ type = "group" }
if ($seg -match '^(?i)(details|детали)$') {
$group["groupBy"] = @()
} else {
$group["groupBy"] = @($seg)
}
if ($null -ne $innermost) {
$group["children"] = @($innermost)
}
$innermost = $group
}
if ($innermost) { $result += $innermost }
return ,$result
}
function Parse-OutputParamShorthand {
param([string]$s)
$idx = $s.IndexOf('=')
@@ -705,6 +819,70 @@ function Build-OrderItemFragment {
return $lines -join "`r`n"
}
function Build-DataSetLinkFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dataSetLink>"
$lines += "$i`t<sourceDataSet>$(Esc-Xml $parsed.source)</sourceDataSet>"
$lines += "$i`t<destinationDataSet>$(Esc-Xml $parsed.dest)</destinationDataSet>"
$lines += "$i`t<sourceExpression>$(Esc-Xml $parsed.sourceExpr)</sourceExpression>"
$lines += "$i`t<destinationExpression>$(Esc-Xml $parsed.destExpr)</destinationExpression>"
if ($parsed.parameter) {
$lines += "$i`t<parameter>$(Esc-Xml $parsed.parameter)</parameter>"
}
$lines += "$i</dataSetLink>"
return $lines -join "`r`n"
}
function Build-StructureItemFragment {
param($item, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dcsset:item xsi:type=`"dcsset:StructureItemGroup`">"
# groupItems
$groupBy = $item["groupBy"]
if (-not $groupBy -or $groupBy.Count -eq 0) {
$lines += "$i`t<dcsset:groupItems/>"
} else {
$lines += "$i`t<dcsset:groupItems>"
foreach ($field in $groupBy) {
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:GroupItemField`">"
$lines += "$i`t`t`t<dcsset:field>$(Esc-Xml $field)</dcsset:field>"
$lines += "$i`t`t`t<dcsset:groupType>Items</dcsset:groupType>"
$lines += "$i`t`t`t<dcsset:periodAdditionType>None</dcsset:periodAdditionType>"
$lines += "$i`t`t`t<dcsset:periodAdditionBegin xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</dcsset:periodAdditionBegin>"
$lines += "$i`t`t`t<dcsset:periodAdditionEnd xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</dcsset:periodAdditionEnd>"
$lines += "$i`t`t</dcsset:item>"
}
$lines += "$i`t</dcsset:groupItems>"
}
# order (Auto)
$lines += "$i`t<dcsset:order>"
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:OrderItemAuto`"/>"
$lines += "$i`t</dcsset:order>"
# selection (Auto)
$lines += "$i`t<dcsset:selection>"
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:SelectedItemAuto`"/>"
$lines += "$i`t</dcsset:selection>"
# Recursive children
if ($item["children"]) {
foreach ($child in $item["children"]) {
$childXml = Build-StructureItemFragment -item $child -indent "$i`t"
$lines += $childXml
}
}
$lines += "$i</dcsset:item>"
return $lines -join "`r`n"
}
function Build-OutputParamFragment {
param($parsed, [string]$indent)
@@ -794,6 +972,18 @@ function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
}
}
function Clear-ContainerChildren($container) {
$toRemove = @()
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Element') {
$toRemove += $child
}
}
foreach ($el in $toRemove) {
Remove-NodeWithWhitespace $el
}
}
function Remove-NodeWithWhitespace($node) {
$parent = $node.ParentNode
$prev = $node.PreviousSibling
@@ -849,6 +1039,52 @@ function Find-ElementByChildValue($container, [string]$elemName, [string]$childN
return $null
}
function Set-OrCreateChildElement($parent, [string]$localName, [string]$nsUri, [string]$value, [string]$indent) {
$existing = $null
foreach ($ch in $parent.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq $localName -and $ch.NamespaceURI -eq $nsUri) {
$existing = $ch
break
}
}
if ($existing) {
$existing.InnerText = $value
} else {
$prefix = $parent.GetPrefixOfNamespace($nsUri)
$qualName = if ($prefix) { "${prefix}:$localName" } else { $localName }
$fragXml = "$indent<$qualName>$(Esc-Xml $value)</$qualName>"
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $parent $node $null $indent
}
}
}
function Set-OrCreateChildElementWithAttr($parent, [string]$localName, [string]$nsUri, [string]$value, [string]$xsiType, [string]$indent) {
$existing = $null
foreach ($ch in $parent.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq $localName -and $ch.NamespaceURI -eq $nsUri) {
$existing = $ch
break
}
}
if ($existing) {
$existing.InnerText = $value
if ($xsiType) {
$existing.SetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance", $xsiType) | Out-Null
}
} else {
$prefix = $parent.GetPrefixOfNamespace($nsUri)
$qualName = if ($prefix) { "${prefix}:$localName" } else { $localName }
$typeAttr = if ($xsiType) { " xsi:type=`"$xsiType`"" } else { "" }
$fragXml = "$indent<$qualName$typeAttr>$(Esc-Xml $value)</$qualName>"
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $parent $node $null $indent
}
}
}
function Resolve-DataSet {
$schNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
$root = $xmlDoc.DocumentElement
@@ -1007,7 +1243,7 @@ $corNs = "http://v8.1c.ru/8.1/data-composition-system/core"
# --- 7. Batch value splitting ---
if ($Operation -eq "set-query") {
if ($Operation -eq "set-query" -or $Operation -eq "set-structure") {
$values = @($Value)
} else {
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
@@ -1045,13 +1281,18 @@ switch ($Operation) {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Ensure-SettingsChild $settings "selection" @()
$selIndent = Get-ContainerChildIndent $selection
$selXml = Build-SelectionItemFragment -fieldName $parsed.dataPath -indent $selIndent
$selNodes = Import-Fragment $xmlDoc $selXml
foreach ($node in $selNodes) {
Insert-BeforeElement $selection $node $null $selIndent
$existingSel = Find-ElementByChildValue $selection "item" "field" $parsed.dataPath $setNs
if ($existingSel) {
Write-Host "[INFO] Field `"$($parsed.dataPath)`" already in selection — skipped"
} else {
$selIndent = Get-ContainerChildIndent $selection
$selXml = Build-SelectionItemFragment -fieldName $parsed.dataPath -indent $selIndent
$selNodes = Import-Fragment $xmlDoc $selXml
foreach ($node in $selNodes) {
Insert-BeforeElement $selection $node $null $selIndent
}
Write-Host "[OK] Field `"$($parsed.dataPath)`" added to selection of variant `"$varName`""
}
Write-Host "[OK] Field `"$($parsed.dataPath)`" added to selection of variant `"$varName`""
}
}
}
@@ -1126,13 +1367,18 @@ switch ($Operation) {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Ensure-SettingsChild $settings "selection" @()
$selIndent = Get-ContainerChildIndent $selection
$selXml = Build-SelectionItemFragment -fieldName $parsed.dataPath -indent $selIndent
$selNodes = Import-Fragment $xmlDoc $selXml
foreach ($node in $selNodes) {
Insert-BeforeElement $selection $node $null $selIndent
$existingSel = Find-ElementByChildValue $selection "item" "field" $parsed.dataPath $setNs
if ($existingSel) {
Write-Host "[INFO] Field `"$($parsed.dataPath)`" already in selection — skipped"
} else {
$selIndent = Get-ContainerChildIndent $selection
$selXml = Build-SelectionItemFragment -fieldName $parsed.dataPath -indent $selIndent
$selNodes = Import-Fragment $xmlDoc $selXml
foreach ($node in $selNodes) {
Insert-BeforeElement $selection $node $null $selIndent
}
Write-Host "[OK] Field `"$($parsed.dataPath)`" added to selection of variant `"$varName`""
}
Write-Host "[OK] Field `"$($parsed.dataPath)`" added to selection of variant `"$varName`""
}
}
}
@@ -1226,6 +1472,27 @@ switch ($Operation) {
$orderEl = Ensure-SettingsChild $settings "order" @("filter","selection")
$orderIndent = Get-ContainerChildIndent $orderEl
# Duplicate check
if ($parsed.field -eq "Auto") {
$isDup = $false
foreach ($ch in $orderEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'item') {
$typeAttr = $ch.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
if ($typeAttr -and $typeAttr.Contains("OrderItemAuto")) { $isDup = $true; break }
}
}
if ($isDup) {
Write-Host "[WARN] OrderItemAuto already exists in variant `"$varName`" — skipped"
continue
}
} else {
$existingOrd = Find-ElementByChildValue $orderEl "item" "field" $parsed.field $setNs
if ($existingOrd) {
Write-Host "[WARN] Order `"$($parsed.field)`" already exists in variant `"$varName`" — skipped"
continue
}
}
$fragXml = Build-OrderItemFragment -parsed $parsed -indent $orderIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
@@ -1300,6 +1567,302 @@ switch ($Operation) {
}
}
"set-structure" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
# Remove all existing structure items (dcsset:item elements)
$toRemove = @()
foreach ($ch in $settings.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'item' -and $ch.NamespaceURI -eq $setNs) {
$toRemove += $ch
}
}
foreach ($el in $toRemove) {
Remove-NodeWithWhitespace $el
}
# Parse structure shorthand
$structItems = Parse-StructureShorthand $Value
$settingsIndent = Get-ChildIndent $settings
# Find insertion point — before outputParameters/dataParameters/conditionalAppearance/order/filter/selection or at end
$refNode = Find-FirstElement $settings @("outputParameters","dataParameters","conditionalAppearance","order","filter","selection","item") $setNs
if (-not $refNode) { $refNode = $null }
foreach ($structItem in $structItems) {
$fragXml = Build-StructureItemFragment -item $structItem -indent $settingsIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $settings $node $refNode $settingsIndent
}
}
Write-Host "[OK] Structure set in variant `"$varName`": $Value"
}
"add-dataSetLink" {
foreach ($val in $values) {
$parsed = Parse-DataSetLinkShorthand $val
$root = $xmlDoc.DocumentElement
$childIndent = Get-ChildIndent $root
$fragXml = Build-DataSetLinkFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
# Insert after last dataSetLink, or before calculatedField/totalField/parameter/...
$lastLink = Find-LastElement $root "dataSetLink" $schNs
if ($lastLink) {
$refNode = $lastLink.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = Find-FirstElement $root @("calculatedField","totalField","parameter","template","groupTemplate","settingsVariant") $schNs
}
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
$desc = "$($parsed.source) > $($parsed.dest) on $($parsed.sourceExpr) = $($parsed.destExpr)"
if ($parsed.parameter) { $desc += " [param $($parsed.parameter)]" }
Write-Host "[OK] DataSetLink `"$desc`" added"
}
}
"clear-selection" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Find-FirstElement $settings @("selection") $setNs
if ($selection) {
Clear-ContainerChildren $selection
Write-Host "[OK] Selection cleared in variant `"$varName`""
} else {
Write-Host "[INFO] No selection section in variant `"$varName`""
}
}
"clear-order" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$orderEl = Find-FirstElement $settings @("order") $setNs
if ($orderEl) {
Clear-ContainerChildren $orderEl
Write-Host "[OK] Order cleared in variant `"$varName`""
} else {
Write-Host "[INFO] No order section in variant `"$varName`""
}
}
"clear-filter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$filterEl = Find-FirstElement $settings @("filter") $setNs
if ($filterEl) {
Clear-ContainerChildren $filterEl
Write-Host "[OK] Filter cleared in variant `"$varName`""
} else {
Write-Host "[INFO] No filter section in variant `"$varName`""
}
}
"modify-filter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-FilterShorthand $val
$filterEl = Find-FirstElement $settings @("filter") $setNs
if (-not $filterEl) {
Write-Host "[WARN] No filter section in variant `"$varName`""
continue
}
$filterItem = Find-ElementByChildValue $filterEl "item" "left" $parsed.field $setNs
if (-not $filterItem) {
Write-Host "[WARN] Filter for `"$($parsed.field)`" not found in variant `"$varName`""
continue
}
$itemIndent = Get-ChildIndent $filterItem
# Update comparisonType
Set-OrCreateChildElement $filterItem "comparisonType" $setNs $parsed.op $itemIndent
# Update right value
if ($null -ne $parsed.value) {
$vt = if ($parsed["valueType"]) { $parsed["valueType"] } else { "xs:string" }
Set-OrCreateChildElementWithAttr $filterItem "right" $setNs "$($parsed.value)" $vt $itemIndent
}
# Update use
if ($parsed.use -eq $false) {
Set-OrCreateChildElement $filterItem "use" $setNs "false" $itemIndent
} else {
# If explicitly not @off, remove use=false if exists
$useEl = $null
foreach ($ch in $filterItem.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $setNs) {
$useEl = $ch; break
}
}
if ($useEl -and $useEl.InnerText -eq 'false') {
Remove-NodeWithWhitespace $useEl
}
}
# Update viewMode
if ($parsed.viewMode) {
Set-OrCreateChildElement $filterItem "viewMode" $setNs $parsed.viewMode $itemIndent
}
# Update userSettingID
if ($parsed.userSettingID) {
$uid = if ($parsed.userSettingID -eq "auto") { [System.Guid]::NewGuid().ToString() } else { $parsed.userSettingID }
Set-OrCreateChildElement $filterItem "userSettingID" $setNs $uid $itemIndent
}
Write-Host "[OK] Filter `"$($parsed.field)`" modified in variant `"$varName`""
}
}
"modify-dataParameter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-DataParamShorthand $val
$dpEl = Find-FirstElement $settings @("dataParameters") $setNs
if (-not $dpEl) {
Write-Host "[WARN] No dataParameters section in variant `"$varName`""
continue
}
$dpItem = Find-ElementByChildValue $dpEl "item" "parameter" $parsed.parameter $corNs
if (-not $dpItem) {
Write-Host "[WARN] DataParameter `"$($parsed.parameter)`" not found in variant `"$varName`""
continue
}
$itemIndent = Get-ChildIndent $dpItem
# Update value
if ($null -ne $parsed.value) {
# Remove existing value element first
$existingVal = $null
foreach ($ch in $dpItem.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'value' -and $ch.NamespaceURI -eq $corNs) {
$existingVal = $ch; break
}
}
if ($existingVal) {
Remove-NodeWithWhitespace $existingVal
}
# Build new value fragment
$valLines = @()
if ($parsed.value -is [hashtable] -and $parsed.value.variant) {
$valLines += "$itemIndent<dcscor:value xsi:type=`"v8:StandardPeriod`">"
$valLines += "$itemIndent`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $parsed.value.variant)</v8:variant>"
$valLines += "$itemIndent</dcscor:value>"
} elseif ("$($parsed.value)" -match '^\d{4}-\d{2}-\d{2}T') {
$valLines += "$itemIndent<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
} elseif ("$($parsed.value)" -eq "true" -or "$($parsed.value)" -eq "false") {
$valLines += "$itemIndent<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
} else {
$valLines += "$itemIndent<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
}
$valXml = $valLines -join "`r`n"
$valNodes = Import-Fragment $xmlDoc $valXml
foreach ($node in $valNodes) {
Insert-BeforeElement $dpItem $node $null $itemIndent
}
}
# Update use
if ($parsed.use -eq $false) {
Set-OrCreateChildElement $dpItem "use" $corNs "false" $itemIndent
} else {
$useEl = $null
foreach ($ch in $dpItem.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $corNs) {
$useEl = $ch; break
}
}
if ($useEl -and $useEl.InnerText -eq 'false') {
Remove-NodeWithWhitespace $useEl
}
}
# Update viewMode
if ($parsed.viewMode) {
Set-OrCreateChildElement $dpItem "viewMode" $setNs $parsed.viewMode $itemIndent
}
# Update userSettingID
if ($parsed.userSettingID) {
$uid = if ($parsed.userSettingID -eq "auto") { [System.Guid]::NewGuid().ToString() } else { $parsed.userSettingID }
Set-OrCreateChildElement $dpItem "userSettingID" $setNs $uid $itemIndent
}
Write-Host "[OK] DataParameter `"$($parsed.parameter)`" modified in variant `"$varName`""
}
}
"modify-field" {
$dsNode = Resolve-DataSet
$dsName = Get-DataSetName $dsNode
foreach ($val in $values) {
$parsed = Parse-FieldShorthand $val
$fieldName = $parsed.dataPath
# Find existing field
$fieldEl = Find-ElementByChildValue $dsNode "field" "dataPath" $fieldName $schNs
if (-not $fieldEl) {
Write-Host "[WARN] Field `"$fieldName`" not found in dataset `"$dsName`""
continue
}
# Read existing properties
$existing = Read-FieldProperties $fieldEl
# Merge: parsed overrides existing for non-empty values
$merged = @{
dataPath = $existing.dataPath
field = $existing.field
title = if ($parsed.title) { $parsed.title } else { $existing.title }
type = if ($parsed.type) { $parsed.type } else { $existing.type }
roles = if ($parsed.roles -and $parsed.roles.Count -gt 0) { $parsed.roles } else { $existing.roles }
restrict = if ($parsed.restrict -and $parsed.restrict.Count -gt 0) { $parsed.restrict } else { $existing.restrict }
}
# Remember position (NextSibling after whitespace)
$nextSib = $fieldEl.NextSibling
while ($nextSib -and ($nextSib.NodeType -eq 'Whitespace' -or $nextSib.NodeType -eq 'SignificantWhitespace')) {
$nextSib = $nextSib.NextSibling
}
# Remove old field
$childIndent = Get-ChildIndent $dsNode
Remove-NodeWithWhitespace $fieldEl
# Build new field fragment with merged data
$fragXml = Build-FieldFragment -parsed $merged -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
# Insert at saved position
foreach ($node in $nodes) {
Insert-BeforeElement $dsNode $node $nextSib $childIndent
}
Write-Host "[OK] Field `"$fieldName`" modified in dataset `"$dsName`""
}
}
"remove-field" {
$dsNode = Resolve-DataSet
$dsName = Get-DataSetName $dsNode