diff --git a/.claude/skills/skd-edit/SKILL.md b/.claude/skills/skd-edit/SKILL.md index 1a05278e..4c57e648 100644 --- a/.claude/skills/skd-edit/SKILL.md +++ b/.claude/skills/skd-edit/SKILL.md @@ -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 "*" +``` + +Удаляет все элементы из ``. Value игнорируется. + +### clear-order — очистить сортировку варианта + +```powershell +-Operation clear-order -Value "*" +``` + +Удаляет все элементы из ``. Value игнорируется. + +### clear-filter — очистить фильтры варианта + +```powershell +-Operation clear-filter -Value "*" +``` + +Удаляет все элементы из ``. 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" ``` ## Верификация diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index 091978e7..1229e9b5 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -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" + $lines += "$i`t$(Esc-Xml $parsed.source)" + $lines += "$i`t$(Esc-Xml $parsed.dest)" + $lines += "$i`t$(Esc-Xml $parsed.sourceExpr)" + $lines += "$i`t$(Esc-Xml $parsed.destExpr)" + if ($parsed.parameter) { + $lines += "$i`t$(Esc-Xml $parsed.parameter)" + } + $lines += "$i" + return $lines -join "`r`n" +} + +function Build-StructureItemFragment { + param($item, [string]$indent) + + $i = $indent + $lines = @() + $lines += "$i" + + # groupItems + $groupBy = $item["groupBy"] + if (-not $groupBy -or $groupBy.Count -eq 0) { + $lines += "$i`t" + } else { + $lines += "$i`t" + foreach ($field in $groupBy) { + $lines += "$i`t`t" + $lines += "$i`t`t`t$(Esc-Xml $field)" + $lines += "$i`t`t`tItems" + $lines += "$i`t`t`tNone" + $lines += "$i`t`t`t0001-01-01T00:00:00" + $lines += "$i`t`t`t0001-01-01T00:00:00" + $lines += "$i`t`t" + } + $lines += "$i`t" + } + + # order (Auto) + $lines += "$i`t" + $lines += "$i`t`t" + $lines += "$i`t" + + # selection (Auto) + $lines += "$i`t" + $lines += "$i`t`t" + $lines += "$i`t" + + # Recursive children + if ($item["children"]) { + foreach ($child in $item["children"]) { + $childXml = Build-StructureItemFragment -item $child -indent "$i`t" + $lines += $childXml + } + } + + $lines += "$i" + 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)" + $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)" + $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" + $valLines += "$itemIndent`t$(Esc-Xml $parsed.value.variant)" + $valLines += "$itemIndent" + } elseif ("$($parsed.value)" -match '^\d{4}-\d{2}-\d{2}T') { + $valLines += "$itemIndent$(Esc-Xml "$($parsed.value)")" + } elseif ("$($parsed.value)" -eq "true" -or "$($parsed.value)" -eq "false") { + $valLines += "$itemIndent$(Esc-Xml "$($parsed.value)")" + } else { + $valLines += "$itemIndent$(Esc-Xml "$($parsed.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