Files
cc-1c-skills/.github/skills/skd-edit/scripts/skd-edit.ps1
T
2026-05-07 18:27:38 +00:00

2956 lines
98 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# skd-edit v1.11 — Atomic 1C DCS editor
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
[Alias('Path')]
[string]$TemplatePath,
[Parameter(Mandatory)]
[ValidateSet(
"add-field","add-total","add-calculated-field","add-parameter","add-filter",
"add-dataParameter","add-order","add-selection","add-dataSetLink",
"add-dataSet","add-variant","add-conditionalAppearance","add-drilldown",
"set-query","patch-query","set-outputParameter","set-structure",
"modify-field","modify-filter","modify-dataParameter","modify-parameter",
"rename-parameter","reorder-parameters",
"clear-selection","clear-order","clear-filter",
"remove-field","remove-total","remove-calculated-field","remove-parameter","remove-filter")]
[string]$Operation,
[Parameter(Mandatory)]
[string]$Value,
[string]$DataSet,
[string]$Variant,
[switch]$NoSelection
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
# --- 1. Resolve path ---
if (-not $TemplatePath.EndsWith(".xml")) {
$candidate = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml"
if (Test-Path $candidate) {
$TemplatePath = $candidate
}
}
if (-not (Test-Path $TemplatePath)) {
Write-Error "File not found: $TemplatePath"
exit 1
}
$resolvedPath = (Resolve-Path $TemplatePath).Path
function Esc-Xml {
param([string]$s)
return $s.Replace('&','&amp;').Replace('<','&lt;').Replace('>','&gt;').Replace('"','&quot;')
}
function Resolve-QueryValue {
param([string]$val, [string]$baseDir)
if (-not $val.StartsWith("@")) { return $val }
$filePath = $val.Substring(1)
if ([System.IO.Path]::IsPathRooted($filePath)) {
$candidates = @($filePath)
} else {
$candidates = @(
(Join-Path $baseDir $filePath),
(Join-Path (Get-Location).Path $filePath)
)
}
foreach ($c in $candidates) {
if (Test-Path $c) {
return (Get-Content -Raw -Encoding UTF8 $c).TrimEnd()
}
}
Write-Error "Query file not found: $filePath (searched: $($candidates -join ', '))"
exit 1
}
$script:queryBaseDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
# --- 2. Type system (copied from skd-compile) ---
$script:typeSynonyms = New-Object System.Collections.Hashtable
$script:typeSynonyms["число"] = "decimal"
$script:typeSynonyms["строка"] = "string"
$script:typeSynonyms["булево"] = "boolean"
$script:typeSynonyms["дата"] = "date"
$script:typeSynonyms["датавремя"] = "dateTime"
$script:typeSynonyms["стандартныйпериод"] = "StandardPeriod"
$script:typeSynonyms["bool"] = "boolean"
$script:typeSynonyms["str"] = "string"
$script:typeSynonyms["int"] = "decimal"
$script:typeSynonyms["integer"] = "decimal"
$script:typeSynonyms["number"] = "decimal"
$script:typeSynonyms["num"] = "decimal"
$script:typeSynonyms["справочникссылка"] = "CatalogRef"
$script:typeSynonyms["документссылка"] = "DocumentRef"
$script:typeSynonyms["перечислениессылка"] = "EnumRef"
$script:typeSynonyms["плансчетовссылка"] = "ChartOfAccountsRef"
$script:typeSynonyms["планвидовхарактеристикссылка"] = "ChartOfCharacteristicTypesRef"
$script:outputParamTypes = @{
"Заголовок" = "mltext"
"ВыводитьЗаголовок" = "dcsset:DataCompositionTextOutputType"
"ВыводитьПараметрыДанных" = "dcsset:DataCompositionTextOutputType"
"ВыводитьОтбор" = "dcsset:DataCompositionTextOutputType"
"МакетОформления" = "xs:string"
"РасположениеПолейГруппировки" = "dcsset:DataCompositionGroupFieldsPlacement"
"РасположениеРеквизитов" = "dcsset:DataCompositionAttributesPlacement"
"ГоризонтальноеРасположениеОбщихИтогов" = "dcscor:DataCompositionTotalPlacement"
"ВертикальноеРасположениеОбщихИтогов" = "dcscor:DataCompositionTotalPlacement"
}
function Resolve-TypeStr {
param([string]$typeStr)
if (-not $typeStr) { return $typeStr }
if ($typeStr -match '^([^(]+)\((.+)\)$') {
$baseName = $Matches[1].Trim()
$params = $Matches[2]
$resolved = $script:typeSynonyms[$baseName.ToLower()]
if ($resolved) { return "$resolved($params)" }
return $typeStr
}
if ($typeStr.Contains('.')) {
$dotIdx = $typeStr.IndexOf('.')
$prefix = $typeStr.Substring(0, $dotIdx)
$suffix = $typeStr.Substring($dotIdx)
$resolved = $script:typeSynonyms[$prefix.ToLower()]
if ($resolved) { return "$resolved$suffix" }
return $typeStr
}
$resolved = $script:typeSynonyms[$typeStr.ToLower()]
if ($resolved) { return $resolved }
return $typeStr
}
# --- 3. Parsers ---
function Parse-FieldShorthand {
param([string]$s)
$result = @{
dataPath = ""; field = ""; title = ""; type = ""
roles = @(); restrict = @()
}
# Extract [Title]
if ($s -match '\[([^\]]+)\]') {
$result.title = $Matches[1]
$s = $s -replace '\s*\[[^\]]+\]', ''
}
# Extract @roles
$roleMatches = [regex]::Matches($s, '@(\w+)')
foreach ($m in $roleMatches) {
$result.roles += $m.Groups[1].Value
}
$s = [regex]::Replace($s, '\s*@\w+', '')
# Extract #restrictions
$restrictMatches = [regex]::Matches($s, '#(\w+)')
foreach ($m in $restrictMatches) {
$result.restrict += $m.Groups[1].Value
}
$s = [regex]::Replace($s, '\s*#\w+', '')
# Split name: type
$s = $s.Trim()
if ($s.Contains(':')) {
$parts = $s -split ':', 2
$result.dataPath = $parts[0].Trim()
$result.type = Resolve-TypeStr ($parts[1].Trim())
} else {
$result.dataPath = $s
}
$result.field = $result.dataPath
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)
$parts = $s -split ':', 2
$dataPath = $parts[0].Trim()
$funcPart = $parts[1].Trim()
if ($funcPart -match '^\w+\(') {
return @{ dataPath = $dataPath; expression = $funcPart }
} else {
return @{ dataPath = $dataPath; expression = "$funcPart($dataPath)" }
}
}
function Parse-CalcShorthand {
param([string]$s)
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
# an expression (e.g. index access) isn't interpreted as a title.
# - `#restrict` flags use a known-names pattern and are extracted globally —
# the docs put them after `=`, and the closed flag set avoids matching
# `#word` that happens to appear inside a string literal.
$restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
$restrict = @()
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
$restrict += $m.Groups[1].Value
}
$s = [regex]::Replace($s, "\s*$restrictPattern", '')
$eqIdx = $s.IndexOf('=')
if ($eqIdx -gt 0) {
$lhs = $s.Substring(0, $eqIdx)
$rhs = $s.Substring($eqIdx + 1).Trim()
} else {
$lhs = $s
$rhs = $null
}
$title = ""
if ($lhs -match '\[([^\]]+)\]') {
$title = $Matches[1]
$lhs = $lhs -replace '\s*\[[^\]]+\]', ''
}
$lhs = $lhs.Trim()
if ($null -ne $rhs) {
if ($lhs.Contains(':')) {
$colonIdx = $lhs.IndexOf(':')
$dataPath = $lhs.Substring(0, $colonIdx).Trim()
$type = Resolve-TypeStr ($lhs.Substring($colonIdx + 1).Trim())
return @{ dataPath = $dataPath; expression = $rhs; type = $type; title = $title; restrict = $restrict }
}
return @{ dataPath = $lhs; expression = $rhs; type = ""; title = $title; restrict = $restrict }
}
return @{ dataPath = $lhs; expression = ""; type = ""; title = $title; restrict = $restrict }
}
function Parse-ParamShorthand {
param([string]$s)
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null }
if ($s -match '@autoDates') {
$result.autoDates = $true
$s = $s -replace '\s*@autoDates', ''
}
# Extract optional [Title] (mirrors Parse-FieldShorthand)
if ($s -match '\[([^\]]*)\]') {
$result.title = $Matches[1].Trim()
$s = ($s -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
}
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') {
$result.name = $Matches[1].Trim()
$result.type = Resolve-TypeStr ($Matches[2].Trim())
if ($Matches[4]) {
$result.value = $Matches[4].Trim()
}
} else {
$result.name = $s.Trim()
}
return $result
}
function Parse-FilterShorthand {
param([string]$s)
# use is tristate: $null = not specified (modify-* won't touch),
# $false = @off (explicit), $true = @on (explicit). add-* writes <use>false</use> only when $false.
$result = @{ field = ""; op = "Equal"; value = $null; use = $null; userSettingID = $null; viewMode = $null }
if ($s -match '@user') {
$result.userSettingID = "auto"
$s = $s -replace '\s*@user', ''
}
if ($s -match '@off') {
$result.use = $false
$s = $s -replace '\s*@off', ''
}
if ($s -match '@on\b') {
$result.use = $true
$s = $s -replace '\s*@on\b', ''
}
if ($s -match '@quickAccess') {
$result.viewMode = "QuickAccess"
$s = $s -replace '\s*@quickAccess', ''
}
if ($s -match '@normal') {
$result.viewMode = "Normal"
$s = $s -replace '\s*@normal', ''
}
if ($s -match '@inaccessible') {
$result.viewMode = "Inaccessible"
$s = $s -replace '\s*@inaccessible', ''
}
$s = $s.Trim()
$opPatterns = @('<>', '>=', '<=', '=', '>', '<',
'notIn\b', 'in\b', 'inHierarchy\b', 'inListByHierarchy\b',
'notContains\b', 'contains\b', 'notBeginsWith\b', 'beginsWith\b',
'notFilled\b', 'filled\b')
$opJoined = $opPatterns -join '|'
if ($s -match "^(.+?)\s+($opJoined)\s*(.*)?$") {
$result.field = $Matches[1].Trim()
$opRaw = $Matches[2].Trim()
$valPart = if ($Matches[3]) { $Matches[3].Trim() } else { "" }
$opMap = @{
"=" = "Equal"; "<>" = "NotEqual"; ">" = "Greater"; ">=" = "GreaterOrEqual"
"<" = "Less"; "<=" = "LessOrEqual"; "in" = "InList"; "notIn" = "NotInList"
"inHierarchy" = "InHierarchy"; "inListByHierarchy" = "InListByHierarchy"
"contains" = "Contains"; "notContains" = "NotContains"
"beginsWith" = "BeginsWith"; "notBeginsWith" = "NotBeginsWith"
"filled" = "Filled"; "notFilled" = "NotFilled"
}
$mapped = $opMap[$opRaw]
if ($mapped) { $result.op = $mapped } else { $result.op = $opRaw }
if ($valPart -and $valPart -ne "_") {
if ($valPart -eq "true" -or $valPart -eq "false") {
$result.value = $valPart
$result["valueType"] = "xs:boolean"
} elseif ($valPart -match '^\d{4}-\d{2}-\d{2}T') {
$result.value = $valPart
$result["valueType"] = "xs:dateTime"
} elseif ($valPart -match '^\d+(\.\d+)?$') {
$result.value = $valPart
$result["valueType"] = "xs:decimal"
} elseif ($valPart -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
$result.value = $valPart
$result["valueType"] = "dcscor:DesignTimeValue"
} else {
$result.value = $valPart
$result["valueType"] = "xs:string"
}
}
} else {
$result.field = $s
}
return $result
}
function Parse-DataParamShorthand {
param([string]$s)
# use is tristate: $null = not specified (modify-* won't touch),
# $false = @off (explicit), $true = @on (explicit). add-* writes <use>false</use> only when $false.
$result = @{ parameter = ""; value = $null; use = $null; userSettingID = $null; viewMode = $null }
if ($s -match '@user') {
$result.userSettingID = "auto"
$s = $s -replace '\s*@user', ''
}
if ($s -match '@off') {
$result.use = $false
$s = $s -replace '\s*@off', ''
}
if ($s -match '@on\b') {
$result.use = $true
$s = $s -replace '\s*@on\b', ''
}
if ($s -match '@quickAccess') {
$result.viewMode = "QuickAccess"
$s = $s -replace '\s*@quickAccess', ''
}
if ($s -match '@normal') {
$result.viewMode = "Normal"
$s = $s -replace '\s*@normal', ''
}
$s = $s.Trim()
if ($s -match '^([^=]+)=\s*(.+)$') {
$result.parameter = $Matches[1].Trim()
$valStr = $Matches[2].Trim()
$periodVariants = @("Custom","Today","ThisWeek","ThisTenDays","ThisMonth","ThisQuarter","ThisHalfYear","ThisYear","FromBeginningOfThisWeek","FromBeginningOfThisTenDays","FromBeginningOfThisMonth","FromBeginningOfThisQuarter","FromBeginningOfThisHalfYear","FromBeginningOfThisYear","LastWeek","LastTenDays","LastMonth","LastQuarter","LastHalfYear","LastYear","NextDay","NextWeek","NextTenDays","NextMonth","NextQuarter","NextHalfYear","NextYear","TillEndOfThisWeek","TillEndOfThisTenDays","TillEndOfThisMonth","TillEndOfThisQuarter","TillEndOfThisHalfYear","TillEndOfThisYear")
if ($periodVariants -contains $valStr) {
$result.value = @{ variant = $valStr }
} elseif ($valStr -match '^\d{4}-\d{2}-\d{2}T') {
$result.value = $valStr
} elseif ($valStr -eq "true" -or $valStr -eq "false") {
$result.value = $valStr
} else {
$result.value = $valStr
}
} else {
$result.parameter = $s
}
return $result
}
function Parse-OrderShorthand {
param([string]$s)
$s = $s.Trim()
if ($s -eq "Auto") {
return @{ field = "Auto"; direction = "" }
}
$parts = $s -split '\s+', 2
$field = $parts[0]
$dir = "Asc"
if ($parts.Count -gt 1 -and $parts[1] -match '(?i)^desc$') { $dir = "Desc" }
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-DataSetShorthand {
param([string]$s)
$s = $s.Trim()
# "Name: QUERY" — split on first ": " only if prefix is a single word (no spaces)
if ($s -match '^(\S+):\s(.+)$') {
return @{ name = $Matches[1]; query = $Matches[2] }
}
return @{ name = ""; query = $s }
}
function Parse-VariantShorthand {
param([string]$s)
$presentation = ""
if ($s -match '\[([^\]]+)\]') {
$presentation = $Matches[1]
$s = $s -replace '\s*\[[^\]]+\]', ''
}
$name = $s.Trim()
if (-not $presentation) { $presentation = $name }
return @{ name = $name; presentation = $presentation }
}
function Parse-ConditionalAppearanceShorthand {
param([string]$s)
$result = @{ param = ""; value = ""; filter = $null; fields = @() }
# Extract " when ..." — condition part
$whenIdx = $s.IndexOf(' when ')
$forIdx = $s.IndexOf(' for ')
# Determine boundaries
$mainEnd = $s.Length
if ($whenIdx -ge 0 -and $forIdx -ge 0) {
$mainEnd = [Math]::Min($whenIdx, $forIdx)
} elseif ($whenIdx -ge 0) {
$mainEnd = $whenIdx
} elseif ($forIdx -ge 0) {
$mainEnd = $forIdx
}
# Parse "for" fields
if ($forIdx -ge 0) {
$forEnd = $s.Length
if ($whenIdx -gt $forIdx) { $forEnd = $whenIdx }
$forPart = $s.Substring($forIdx + 5, $forEnd - $forIdx - 5).Trim()
$result.fields = @($forPart -split '\s*,\s*' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
# Parse "when" filter (supports " or " for OrGroup)
if ($whenIdx -ge 0) {
$whenEnd = $s.Length
if ($forIdx -gt $whenIdx) { $whenEnd = $forIdx }
$whenPart = $s.Substring($whenIdx + 6, $whenEnd - $whenIdx - 6).Trim()
$orParts = $whenPart -split '\s+or\s+'
if ($orParts.Count -gt 1) {
$result.filter = @($orParts | ForEach-Object { Parse-FilterShorthand $_.Trim() })
} else {
$result.filter = Parse-FilterShorthand $whenPart
}
}
# Parse main part: "Param = Value"
$mainPart = $s.Substring(0, $mainEnd).Trim()
$eqIdx = $mainPart.IndexOf('=')
if ($eqIdx -gt 0) {
$result.param = $mainPart.Substring(0, $eqIdx).Trim()
$result.value = $mainPart.Substring($eqIdx + 1).Trim()
} else {
$result.param = $mainPart
}
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 '@name=(.+)') {
$group["name"] = $Matches[1].Trim()
$seg = ($seg -replace '\s*@name=.+', '').Trim()
}
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('=')
if ($idx -gt 0) {
return @{
key = $s.Substring(0, $idx).Trim()
value = $s.Substring($idx + 1).Trim()
}
}
return @{ key = $s.Trim(); value = "" }
}
# --- 4. Build-* functions (XML fragment generators) ---
function Build-ValueTypeXml {
param([string]$typeStr, [string]$indent)
if (-not $typeStr) { return "" }
$typeStr = Resolve-TypeStr $typeStr
$lines = @()
if ($typeStr -eq "boolean") {
$lines += "$indent<v8:Type>xs:boolean</v8:Type>"
return $lines -join "`r`n"
}
if ($typeStr -match '^string(\((\d+)\))?$') {
$len = if ($Matches[2]) { $Matches[2] } else { "0" }
$lines += "$indent<v8:Type>xs:string</v8:Type>"
$lines += "$indent<v8:StringQualifiers>"
$lines += "$indent`t<v8:Length>$len</v8:Length>"
$lines += "$indent`t<v8:AllowedLength>Variable</v8:AllowedLength>"
$lines += "$indent</v8:StringQualifiers>"
return $lines -join "`r`n"
}
if ($typeStr -match '^decimal\((\d+),(\d+)(,nonneg)?\)$') {
$digits = $Matches[1]
$fraction = $Matches[2]
$sign = if ($Matches[3]) { "Nonnegative" } else { "Any" }
$lines += "$indent<v8:Type>xs:decimal</v8:Type>"
$lines += "$indent<v8:NumberQualifiers>"
$lines += "$indent`t<v8:Digits>$digits</v8:Digits>"
$lines += "$indent`t<v8:FractionDigits>$fraction</v8:FractionDigits>"
$lines += "$indent`t<v8:AllowedSign>$sign</v8:AllowedSign>"
$lines += "$indent</v8:NumberQualifiers>"
return $lines -join "`r`n"
}
if ($typeStr -match '^(date|dateTime)$') {
$fractions = switch ($typeStr) {
"date" { "Date" }
"dateTime" { "DateTime" }
}
$lines += "$indent<v8:Type>xs:dateTime</v8:Type>"
$lines += "$indent<v8:DateQualifiers>"
$lines += "$indent`t<v8:DateFractions>$fractions</v8:DateFractions>"
$lines += "$indent</v8:DateQualifiers>"
return $lines -join "`r`n"
}
if ($typeStr -eq "StandardPeriod") {
$lines += "$indent<v8:Type>v8:StandardPeriod</v8:Type>"
return $lines -join "`r`n"
}
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef)\.') {
$lines += "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$(Esc-Xml $typeStr)</v8:Type>"
return $lines -join "`r`n"
}
if ($typeStr.Contains('.')) {
$lines += "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$(Esc-Xml $typeStr)</v8:Type>"
return $lines -join "`r`n"
}
$lines += "$indent<v8:Type>$(Esc-Xml $typeStr)</v8:Type>"
return $lines -join "`r`n"
}
function Build-MLTextXml {
param([string]$tag, [string]$text, [string]$indent)
$lines = @()
$lines += "$indent<$tag xsi:type=`"v8:LocalStringType`">"
$lines += "$indent`t<v8:item>"
$lines += "$indent`t`t<v8:lang>ru</v8:lang>"
$lines += "$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
$lines += "$indent`t</v8:item>"
$lines += "$indent</$tag>"
return $lines -join "`r`n"
}
function Build-RoleXml {
param([string[]]$roles, [string]$indent)
if (-not $roles -or $roles.Count -eq 0) { return "" }
$lines = @()
$lines += "$indent<role>"
foreach ($role in $roles) {
if ($role -eq "period") {
$lines += "$indent`t<dcscom:periodNumber>1</dcscom:periodNumber>"
$lines += "$indent`t<dcscom:periodType>Main</dcscom:periodType>"
} else {
$lines += "$indent`t<dcscom:$role>true</dcscom:$role>"
}
}
$lines += "$indent</role>"
return $lines -join "`r`n"
}
function Build-RestrictionXml {
param([string[]]$restrict, [string]$indent)
if (-not $restrict -or $restrict.Count -eq 0) { return "" }
$restrictMap = @{
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
"noGroup" = "group"; "noOrder" = "order"
}
$lines = @()
$lines += "$indent<useRestriction>"
foreach ($r in $restrict) {
$xmlName = $restrictMap["$r"]
if ($xmlName) {
$lines += "$indent`t<$xmlName>true</$xmlName>"
}
}
$lines += "$indent</useRestriction>"
return $lines -join "`r`n"
}
function Build-FieldFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<field xsi:type=`"DataSetFieldField`">"
$lines += "$i`t<dataPath>$(Esc-Xml $parsed.dataPath)</dataPath>"
$lines += "$i`t<field>$(Esc-Xml $parsed.field)</field>"
if ($parsed.title) {
$lines += (Build-MLTextXml -tag "title" -text $parsed.title -indent "$i`t")
}
if ($parsed.restrict -and $parsed.restrict.Count -gt 0) {
$lines += (Build-RestrictionXml -restrict $parsed.restrict -indent "$i`t")
}
$roleXml = Build-RoleXml -roles $parsed.roles -indent "$i`t"
if ($roleXml) { $lines += $roleXml }
if ($parsed.type) {
$lines += "$i`t<valueType>"
$lines += (Build-ValueTypeXml -typeStr $parsed.type -indent "$i`t`t")
$lines += "$i`t</valueType>"
}
$lines += "$i</field>"
return $lines -join "`r`n"
}
function Build-TotalFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<totalField>"
$lines += "$i`t<dataPath>$(Esc-Xml $parsed.dataPath)</dataPath>"
$lines += "$i`t<expression>$(Esc-Xml $parsed.expression)</expression>"
$lines += "$i</totalField>"
return $lines -join "`r`n"
}
function Build-CalcFieldFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<calculatedField>"
$lines += "$i`t<dataPath>$(Esc-Xml $parsed.dataPath)</dataPath>"
$lines += "$i`t<expression>$(Esc-Xml $parsed.expression)</expression>"
if ($parsed.title) {
$lines += (Build-MLTextXml -tag "title" -text $parsed.title -indent "$i`t")
}
if ($parsed.restrict -and $parsed.restrict.Count -gt 0) {
$lines += (Build-RestrictionXml -restrict $parsed.restrict -indent "$i`t")
}
if ($parsed.type) {
$lines += "$i`t<valueType>"
$lines += (Build-ValueTypeXml -typeStr $parsed.type -indent "$i`t`t")
$lines += "$i`t</valueType>"
}
$lines += "$i</calculatedField>"
return $lines -join "`r`n"
}
function Build-ParamFragment {
param($parsed, [string]$indent)
$i = $indent
$fragments = @()
$lines = @()
$lines += "$i<parameter>"
$lines += "$i`t<name>$(Esc-Xml $parsed.name)</name>"
if ($parsed.title) {
$lines += (Build-MLTextXml -tag "title" -text $parsed.title -indent "$i`t")
}
if ($parsed.type) {
$lines += "$i`t<valueType>"
$lines += (Build-ValueTypeXml -typeStr $parsed.type -indent "$i`t`t")
$lines += "$i`t</valueType>"
}
if ($null -ne $parsed.value) {
$valStr = "$($parsed.value)"
if ($parsed.type -eq "StandardPeriod") {
$lines += "$i`t<value xsi:type=`"v8:StandardPeriod`">"
$lines += "$i`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $valStr)</v8:variant>"
$lines += "$i`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
$lines += "$i`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
$lines += "$i`t</value>"
} elseif ($parsed.type -match '^date') {
$lines += "$i`t<value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</value>"
} elseif ($parsed.type -eq "boolean") {
$lines += "$i`t<value xsi:type=`"xs:boolean`">$(Esc-Xml $valStr)</value>"
} elseif ($parsed.type -match '^decimal') {
$lines += "$i`t<value xsi:type=`"xs:decimal`">$(Esc-Xml $valStr)</value>"
} else {
$lines += "$i`t<value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</value>"
}
}
$lines += "$i</parameter>"
$fragments += ($lines -join "`r`n")
if ($parsed.autoDates) {
$paramName = $parsed.name
# Canonical БСП pattern: title + valueType + value + useRestriction + expression
$bLines = @()
$bLines += "$i<parameter>"
$bLines += "$i`t<name>ДатаНачала</name>"
$bLines += (Build-MLTextXml -tag "title" -text "Начало периода" -indent "$i`t")
$bLines += "$i`t<valueType>"
$bLines += (Build-ValueTypeXml -typeStr "date" -indent "$i`t`t")
$bLines += "$i`t</valueType>"
$bLines += "$i`t<value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</value>"
$bLines += "$i`t<useRestriction>true</useRestriction>"
$bLines += "$i`t<expression>$(Esc-Xml "&$paramName.ДатаНачала")</expression>"
$bLines += "$i</parameter>"
$fragments += ($bLines -join "`r`n")
$eLines = @()
$eLines += "$i<parameter>"
$eLines += "$i`t<name>ДатаОкончания</name>"
$eLines += (Build-MLTextXml -tag "title" -text "Конец периода" -indent "$i`t")
$eLines += "$i`t<valueType>"
$eLines += (Build-ValueTypeXml -typeStr "date" -indent "$i`t`t")
$eLines += "$i`t</valueType>"
$eLines += "$i`t<value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</value>"
$eLines += "$i`t<useRestriction>true</useRestriction>"
$eLines += "$i`t<expression>$(Esc-Xml "&$paramName.ДатаОкончания")</expression>"
$eLines += "$i</parameter>"
$fragments += ($eLines -join "`r`n")
}
return ,$fragments
}
function Build-FilterItemFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
if ($parsed.use -eq $false) {
$lines += "$i`t<dcsset:use>false</dcsset:use>"
}
$lines += "$i`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-Xml $parsed.field)</dcsset:left>"
$lines += "$i`t<dcsset:comparisonType>$(Esc-Xml $parsed.op)</dcsset:comparisonType>"
if ($null -ne $parsed.value) {
$vt = if ($parsed["valueType"]) { $parsed["valueType"] } else { "xs:string" }
$lines += "$i`t<dcsset:right xsi:type=`"$vt`">$(Esc-Xml "$($parsed.value)")</dcsset:right>"
}
if ($parsed.viewMode) {
$lines += "$i`t<dcsset:viewMode>$(Esc-Xml $parsed.viewMode)</dcsset:viewMode>"
}
if ($parsed.userSettingID) {
$uid = if ($parsed.userSettingID -eq "auto") { [System.Guid]::NewGuid().ToString() } else { $parsed.userSettingID }
$lines += "$i`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
}
$lines += "$i</dcsset:item>"
return $lines -join "`r`n"
}
function Build-SelectionItemFragment {
param([string]$fieldName, [string]$indent)
$i = $indent
$lines = @()
if ($fieldName -eq "Auto") {
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemAuto`"/>"
} elseif ($fieldName -match '^Folder\((.+)\)$') {
$inner = $Matches[1]
$colonIdx = $inner.IndexOf(':')
if ($colonIdx -gt 0) {
$title = $inner.Substring(0, $colonIdx).Trim()
$items = $inner.Substring($colonIdx + 1) -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
} else {
$title = ""
$items = $inner -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
}
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemFolder`">"
if ($title) {
$lines += "$i`t<dcsset:lwsTitle>"
$lines += "$i`t`t<v8:item>"
$lines += "$i`t`t`t<v8:lang>ru</v8:lang>"
$lines += "$i`t`t`t<v8:content>$(Esc-Xml $title)</v8:content>"
$lines += "$i`t`t</v8:item>"
$lines += "$i`t</dcsset:lwsTitle>"
}
foreach ($item in $items) {
$lines += "$i`t<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
$lines += "$i`t`t<dcsset:field>$(Esc-Xml $item)</dcsset:field>"
$lines += "$i`t</dcsset:item>"
}
$lines += "$i`t<dcsset:placement>Auto</dcsset:placement>"
$lines += "$i</dcsset:item>"
} else {
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
$lines += "$i`t<dcsset:field>$(Esc-Xml $fieldName)</dcsset:field>"
$lines += "$i</dcsset:item>"
}
return $lines -join "`r`n"
}
function Build-DataParamFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
if ($parsed.use -eq $false) {
$lines += "$i`t<dcscor:use>false</dcscor:use>"
}
$lines += "$i`t<dcscor:parameter>$(Esc-Xml $parsed.parameter)</dcscor:parameter>"
if ($null -ne $parsed.value) {
if ($parsed.value -is [hashtable] -and $parsed.value.variant) {
$lines += "$i`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
$lines += "$i`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $parsed.value.variant)</v8:variant>"
$lines += "$i`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
$lines += "$i`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
$lines += "$i`t</dcscor:value>"
} elseif ("$($parsed.value)" -match '^\d{4}-\d{2}-\d{2}T') {
$lines += "$i`t<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
} elseif ("$($parsed.value)" -eq "true" -or "$($parsed.value)" -eq "false") {
$lines += "$i`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
} else {
$lines += "$i`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
}
}
if ($parsed.viewMode) {
$lines += "$i`t<dcsset:viewMode>$(Esc-Xml $parsed.viewMode)</dcsset:viewMode>"
}
if ($parsed.userSettingID) {
$uid = if ($parsed.userSettingID -eq "auto") { [System.Guid]::NewGuid().ToString() } else { $parsed.userSettingID }
$lines += "$i`t<dcsset:userSettingID>$(Esc-Xml $uid)</dcsset:userSettingID>"
}
$lines += "$i</dcscor:item>"
return $lines -join "`r`n"
}
function Build-OrderItemFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
if ($parsed.field -eq "Auto") {
$lines += "$i<dcsset:item xsi:type=`"dcsset:OrderItemAuto`"/>"
} else {
$lines += "$i<dcsset:item xsi:type=`"dcsset:OrderItemField`">"
$lines += "$i`t<dcsset:field>$(Esc-Xml $parsed.field)</dcsset:field>"
$lines += "$i`t<dcsset:orderType>$($parsed.direction)</dcsset:orderType>"
$lines += "$i</dcsset:item>"
}
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-DataSetQueryFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dataSet xsi:type=`"DataSetQuery`">"
$lines += "$i`t<name>$(Esc-Xml $parsed.name)</name>"
$lines += "$i`t<dataSource>$(Esc-Xml $parsed.dataSource)</dataSource>"
$lines += "$i`t<query>$(Esc-Xml $parsed.query)</query>"
$lines += "$i</dataSet>"
return $lines -join "`r`n"
}
function Build-VariantFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<settingsVariant>"
$lines += "$i`t<dcsset:name>$(Esc-Xml $parsed.name)</dcsset:name>"
$lines += (Build-MLTextXml -tag "dcsset:presentation" -text $parsed.presentation -indent "$i`t")
$lines += "$i`t<dcsset:settings xmlns:style=`"http://v8.1c.ru/8.1/data/ui/style`" xmlns:sys=`"http://v8.1c.ru/8.1/data/ui/fonts/system`" xmlns:web=`"http://v8.1c.ru/8.1/data/ui/colors/web`" xmlns:win=`"http://v8.1c.ru/8.1/data/ui/colors/windows`">"
$lines += "$i`t`t<dcsset:selection>"
$lines += "$i`t`t`t<dcsset:item xsi:type=`"dcsset:SelectedItemAuto`"/>"
$lines += "$i`t`t</dcsset:selection>"
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:StructureItemGroup`">"
$lines += "$i`t`t`t<dcsset:groupItems/>"
$lines += "$i`t`t`t<dcsset:order>"
$lines += "$i`t`t`t`t<dcsset:item xsi:type=`"dcsset:OrderItemAuto`"/>"
$lines += "$i`t`t`t</dcsset:order>"
$lines += "$i`t`t`t<dcsset:selection>"
$lines += "$i`t`t`t`t<dcsset:item xsi:type=`"dcsset:SelectedItemAuto`"/>"
$lines += "$i`t`t`t</dcsset:selection>"
$lines += "$i`t`t</dcsset:item>"
$lines += "$i`t</dcsset:settings>"
$lines += "$i</settingsVariant>"
return $lines -join "`r`n"
}
function Emit-FilterComparison {
param($f, [string]$indent)
$lines = @()
$lines += "$indent<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
$lines += "$indent`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-Xml $f.field)</dcsset:left>"
$lines += "$indent`t<dcsset:comparisonType>$(Esc-Xml $f.op)</dcsset:comparisonType>"
if ($null -ne $f.value) {
$vt = if ($f["valueType"]) { $f["valueType"] } else { "xs:string" }
$lines += "$indent`t<dcsset:right xsi:type=`"$vt`">$(Esc-Xml "$($f.value)")</dcsset:right>"
}
$lines += "$indent</dcsset:item>"
return $lines
}
function Build-ConditionalAppearanceItemFragment {
param($parsed, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dcsset:item>"
# selection
if ($parsed.fields -and $parsed.fields.Count -gt 0) {
$lines += "$i`t<dcsset:selection>"
foreach ($fld in $parsed.fields) {
$lines += "$i`t`t<dcsset:item>"
$lines += "$i`t`t`t<dcsset:field>$(Esc-Xml $fld)</dcsset:field>"
$lines += "$i`t`t</dcsset:item>"
}
$lines += "$i`t</dcsset:selection>"
} else {
$lines += "$i`t<dcsset:selection/>"
}
# filter
if ($parsed.filter) {
$lines += "$i`t<dcsset:filter>"
if ($parsed.filter -is [array]) {
# OrGroup
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:FilterItemGroup`">"
$lines += "$i`t`t`t<dcsset:groupType>OrGroup</dcsset:groupType>"
foreach ($f in $parsed.filter) {
$lines += Emit-FilterComparison $f "$i`t`t`t"
}
$lines += "$i`t`t</dcsset:item>"
} else {
$lines += Emit-FilterComparison $parsed.filter "$i`t`t"
}
$lines += "$i`t</dcsset:filter>"
} else {
$lines += "$i`t<dcsset:filter/>"
}
# appearance
$lines += "$i`t<dcsset:appearance>"
$val = $parsed.value
$lines += "$i`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
$lines += "$i`t`t`t<dcscor:parameter>$(Esc-Xml $parsed.param)</dcscor:parameter>"
if ($val -match '^(web|style|win):') {
$lines += "$i`t`t`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-Xml $val)</dcscor:value>"
} elseif ($val -eq "true" -or $val -eq "false") {
$lines += "$i`t`t`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml $val)</dcscor:value>"
} elseif ($parsed.param -eq "Формат" -or $parsed.param -eq "Текст" -or $parsed.param -eq "Заголовок") {
$lines += "$i`t`t`t<dcscor:value xsi:type=`"v8:LocalStringType`">"
$lines += "$i`t`t`t`t<v8:item>"
$lines += "$i`t`t`t`t`t<v8:lang>ru</v8:lang>"
$lines += "$i`t`t`t`t`t<v8:content>$(Esc-Xml $val)</v8:content>"
$lines += "$i`t`t`t`t</v8:item>"
$lines += "$i`t`t`t</dcscor:value>"
} else {
$lines += "$i`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $val)</dcscor:value>"
}
$lines += "$i`t`t</dcscor:item>"
$lines += "$i`t</dcsset:appearance>"
$lines += "$i</dcsset:item>"
return $lines -join "`r`n"
}
function Build-StructureItemFragment {
param($item, [string]$indent)
$i = $indent
$lines = @()
$lines += "$i<dcsset:item xsi:type=`"dcsset:StructureItemGroup`">"
# name
if ($item["name"]) {
$lines += "$i`t<dcsset:name>$(Esc-Xml $item["name"])</dcsset:name>"
}
# 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)
$i = $indent
$key = $parsed.key
$val = $parsed.value
$ptype = $script:outputParamTypes[$key]
if (-not $ptype) { $ptype = "xs:string" }
$lines = @()
$lines += "$i<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
$lines += "$i`t<dcscor:parameter>$(Esc-Xml $key)</dcscor:parameter>"
if ($ptype -eq "mltext") {
$lines += "$i`t<dcscor:value xsi:type=`"v8:LocalStringType`">"
$lines += "$i`t`t<v8:item>"
$lines += "$i`t`t`t<v8:lang>ru</v8:lang>"
$lines += "$i`t`t`t<v8:content>$(Esc-Xml $val)</v8:content>"
$lines += "$i`t`t</v8:item>"
$lines += "$i`t</dcscor:value>"
} else {
$lines += "$i`t<dcscor:value xsi:type=`"$ptype`">$(Esc-Xml $val)</dcscor:value>"
}
$lines += "$i</dcscor:item>"
return $lines -join "`r`n"
}
# --- 5. XML helpers ---
function Import-Fragment($doc, [string]$xmlString) {
$wrapper = @"
<_W xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:dcscom="http://v8.1c.ru/8.1/data-composition-system/common"
xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core"
xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui">$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 += $doc.ImportNode($child, $true)
}
}
return ,$nodes
}
function Get-ChildIndent($container) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
$text = $child.Value
if ($text -match '^\r?\n(\t+)$') { return $Matches[1] }
if ($text -match '^\r?\n(\t+)') { return $Matches[1] }
}
}
$depth = 0
$current = $container
while ($current -and $current -ne $xmlDoc.DocumentElement) {
$depth++
$current = $current.ParentNode
}
return "`t" * ($depth + 1)
}
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
$ws = $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 = $xmlDoc.CreateWhitespace("`r`n$parentIndent")
$container.AppendChild($closeWs) | Out-Null
}
}
}
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
$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 Find-FirstElement($container, [string[]]$localNames, [string]$nsUri) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Element') {
foreach ($name in $localNames) {
if ($child.LocalName -eq $name) {
if (-not $nsUri -or $child.NamespaceURI -eq $nsUri) {
return $child
}
}
}
}
}
return $null
}
function Find-LastElement($container, [string]$localName, [string]$nsUri) {
$last = $null
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $localName) {
if (-not $nsUri -or $child.NamespaceURI -eq $nsUri) {
$last = $child
}
}
}
return $last
}
function Find-ElementByChildValue($container, [string]$elemName, [string]$childName, [string]$childValue, [string]$nsUri) {
foreach ($child in $container.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -ne $elemName) { continue }
if ($nsUri -and $child.NamespaceURI -ne $nsUri) { continue }
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq $childName -and $gc.InnerText.Trim() -eq $childValue) {
return $child
}
}
}
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
if ($DataSet) {
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'dataSet' -and $child.NamespaceURI -eq $schNs) {
$nameEl = $null
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
$nameEl = $gc
break
}
}
if ($nameEl -and $nameEl.InnerText -eq $DataSet) {
return $child
}
}
}
Write-Error "DataSet '$DataSet' not found"
exit 1
}
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'dataSet' -and $child.NamespaceURI -eq $schNs) {
return $child
}
}
Write-Error "No dataSet found in DCS"
exit 1
}
function Resolve-VariantSettings {
$schNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
$setNs = "http://v8.1c.ru/8.1/data-composition-system/settings"
$root = $xmlDoc.DocumentElement
$sv = $null
if ($Variant) {
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'settingsVariant' -and $child.NamespaceURI -eq $schNs) {
$nameEl = $null
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $setNs) {
$nameEl = $gc
break
}
}
if ($nameEl -and $nameEl.InnerText -eq $Variant) {
$sv = $child
break
}
}
}
if (-not $sv) {
Write-Error "Variant '$Variant' not found"
exit 1
}
} else {
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'settingsVariant' -and $child.NamespaceURI -eq $schNs) {
$sv = $child
break
}
}
if (-not $sv) {
Write-Error "No settingsVariant found in DCS"
exit 1
}
}
foreach ($gc in $sv.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'settings' -and $gc.NamespaceURI -eq $setNs) {
return $gc
}
}
Write-Error "No <dcsset:settings> found in variant"
exit 1
}
function Ensure-SettingsChild($settings, [string]$childName, [string[]]$afterSiblings) {
$el = Find-FirstElement $settings @($childName) $setNs
if ($el) { return $el }
$indent = Get-ChildIndent $settings
$fragXml = "$indent<dcsset:$childName/>"
$nodes = Import-Fragment $xmlDoc $fragXml
$refNode = $null
foreach ($sibName in $afterSiblings) {
$sib = Find-FirstElement $settings @($sibName) $setNs
if ($sib) {
$refNode = $sib.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
break
}
}
foreach ($node in $nodes) {
Insert-BeforeElement $settings $node $refNode $indent
}
return Find-FirstElement $settings @($childName) $setNs
}
function Get-VariantName {
$schNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
$setNs = "http://v8.1c.ru/8.1/data-composition-system/settings"
$root = $xmlDoc.DocumentElement
if ($Variant) { return $Variant }
foreach ($child in $root.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'settingsVariant' -and $child.NamespaceURI -eq $schNs) {
foreach ($gc in $child.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $setNs) {
return $gc.InnerText
}
}
}
}
return "(unknown)"
}
function Get-DataSetName($dsNode) {
$schNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
foreach ($gc in $dsNode.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
return $gc.InnerText
}
}
return "(unknown)"
}
function Get-ContainerChildIndent($container) {
$hasElements = $false
foreach ($ch in $container.ChildNodes) {
if ($ch.NodeType -eq 'Element') { $hasElements = $true; break }
}
if ($hasElements) {
return Get-ChildIndent $container
} else {
$parentIndent = Get-ChildIndent $container.ParentNode
return $parentIndent + "`t"
}
}
# --- 6. Load XML ---
$xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true
$xmlDoc.Load($resolvedPath)
$schNs = "http://v8.1c.ru/8.1/data-composition-system/schema"
$setNs = "http://v8.1c.ru/8.1/data-composition-system/settings"
$corNs = "http://v8.1c.ru/8.1/data-composition-system/core"
# --- 7. Batch value splitting ---
if ($Operation -eq "set-query" -or $Operation -eq "set-structure" -or $Operation -eq "add-dataSet") {
$values = @($Value)
} elseif ($Operation -eq "patch-query") {
$values = @($Value -split ';;' | Where-Object { $_.Trim() })
} elseif ($Operation -eq "add-drilldown") {
if ($Value.Contains(';;')) {
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
} else {
$values = @($Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
} else {
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
}
# --- 8. Main logic ---
switch ($Operation) {
"add-field" {
$dsNode = Resolve-DataSet
$dsName = Get-DataSetName $dsNode
foreach ($val in $values) {
$parsed = Parse-FieldShorthand $val
$childIndent = Get-ChildIndent $dsNode
# Duplicate check
$existing = Find-ElementByChildValue $dsNode "field" "dataPath" $parsed.dataPath $schNs
if ($existing) {
Write-Host "[WARN] Field `"$($parsed.dataPath)`" already exists in dataset `"$dsName`" — skipped"
continue
}
$fragXml = Build-FieldFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
$refNode = Find-FirstElement $dsNode @("dataSource") $schNs
foreach ($node in $nodes) {
Insert-BeforeElement $dsNode $node $refNode $childIndent
}
Write-Host "[OK] Field `"$($parsed.dataPath)`" added to dataset `"$dsName`""
if (-not $NoSelection) {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Ensure-SettingsChild $settings "selection" @()
$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`""
}
}
}
}
"add-total" {
foreach ($val in $values) {
$parsed = Parse-TotalShorthand $val
$childIndent = Get-ChildIndent $xmlDoc.DocumentElement
# Duplicate check
$existing = Find-ElementByChildValue $xmlDoc.DocumentElement "totalField" "dataPath" $parsed.dataPath $schNs
if ($existing) {
Write-Host "[WARN] TotalField `"$($parsed.dataPath)`" already exists — skipped"
continue
}
$fragXml = Build-TotalFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
$root = $xmlDoc.DocumentElement
$lastTotal = Find-LastElement $root "totalField" $schNs
if ($lastTotal) {
$refNode = $lastTotal.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = Find-FirstElement $root @("parameter","template","groupTemplate","settingsVariant") $schNs
}
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
Write-Host "[OK] TotalField `"$($parsed.dataPath)`" = $($parsed.expression) added"
}
}
"add-calculated-field" {
foreach ($val in $values) {
$parsed = Parse-CalcShorthand $val
$childIndent = Get-ChildIndent $xmlDoc.DocumentElement
# Duplicate check
$existing = Find-ElementByChildValue $xmlDoc.DocumentElement "calculatedField" "dataPath" $parsed.dataPath $schNs
if ($existing) {
Write-Host "[WARN] CalculatedField `"$($parsed.dataPath)`" already exists — skipped"
continue
}
$fragXml = Build-CalcFieldFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
$root = $xmlDoc.DocumentElement
$lastCalc = Find-LastElement $root "calculatedField" $schNs
if ($lastCalc) {
$refNode = $lastCalc.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = Find-FirstElement $root @("totalField","parameter","template","groupTemplate","settingsVariant") $schNs
}
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
Write-Host "[OK] CalculatedField `"$($parsed.dataPath)`" = $($parsed.expression) added"
if (-not $NoSelection) {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Ensure-SettingsChild $settings "selection" @()
$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`""
}
}
}
}
"add-parameter" {
foreach ($val in $values) {
$parsed = Parse-ParamShorthand $val
$childIndent = Get-ChildIndent $xmlDoc.DocumentElement
# Duplicate check
$existing = Find-ElementByChildValue $xmlDoc.DocumentElement "parameter" "name" $parsed.name $schNs
if ($existing) {
Write-Host "[WARN] Parameter `"$($parsed.name)`" already exists — skipped"
continue
}
$fragments = Build-ParamFragment -parsed $parsed -indent $childIndent
$root = $xmlDoc.DocumentElement
$lastParam = Find-LastElement $root "parameter" $schNs
if ($lastParam) {
$refNode = $lastParam.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = Find-FirstElement $root @("template","groupTemplate","settingsVariant") $schNs
}
foreach ($fragXml in $fragments) {
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
}
Write-Host "[OK] Parameter `"$($parsed.name)`" added"
if ($parsed.autoDates) {
Write-Host "[OK] Auto-parameters `"ДатаНачала`", `"ДатаОкончания`" added"
}
}
}
"modify-parameter" {
foreach ($val in $values) {
# Parse: "ParamName [Title] key=value key=value"
# Extract optional [Title] first (mirrors Parse-FieldShorthand)
$titleVal = $null
if ($val -match '\[([^\]]*)\]') {
$titleVal = $Matches[1].Trim()
$val = ($val -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
}
$parts = $val -split '\s+', 2
$paramName = $parts[0].Trim()
$rest = if ($parts.Count -gt 1) { $parts[1].Trim() } else { "" }
# Find parameter element
$paramEl = Find-ElementByChildValue $xmlDoc.DocumentElement "parameter" "name" $paramName $schNs
if (-not $paramEl) {
Write-Host "[WARN] Parameter `"$paramName`" not found — skipped"
continue
}
$childIndent = Get-ChildIndent $paramEl
# Set/replace title (must come right after <name>, before <valueType>)
if ($null -ne $titleVal) {
$existingTitle = $null
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'title') {
$existingTitle = $ch; break
}
}
if ($existingTitle) {
Remove-NodeWithWhitespace $existingTitle
}
# Insert before first of (valueType, value, useRestriction, expression, availableAsField, ...)
$titleRef = $null
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -ne 'name') {
$titleRef = $ch; break
}
}
$titleFrag = Build-MLTextXml -tag "title" -text $titleVal -indent $childIndent
$titleNodes = Import-Fragment $xmlDoc $titleFrag
foreach ($node in $titleNodes) {
Insert-BeforeElement $paramEl $node $titleRef $childIndent
}
Write-Host "[OK] Parameter `"$paramName`": title set to `"$titleVal`""
}
# Separate availableValue=... from simple kv pairs
$simpleRest = $rest
$avPart = $null
$avIdx = $rest.IndexOf('availableValue=')
if ($avIdx -ge 0) {
$simpleRest = $rest.Substring(0, $avIdx).Trim()
$avPart = $rest.Substring($avIdx)
}
# Process simple key=value pairs (use, denyIncompleteValues, etc.)
if ($simpleRest) {
$kvPairs = [regex]::Matches($simpleRest, '(\w+)=(\S+)')
foreach ($kv in $kvPairs) {
$key = $kv.Groups[1].Value
$value = $kv.Groups[2].Value
$existing = $paramEl.SelectSingleNode($key)
if ($existing) {
$existing.InnerText = $value
Write-Host "[OK] Parameter `"$paramName`": $key updated to $value"
} else {
# Schema order: ...value, useRestriction, availableValue*, denyIncompleteValues, use
$refNode = $null
if ($key -eq "denyIncompleteValues") {
foreach ($child in $paramEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'use') {
$refNode = $child; break
}
}
}
$fragXml = "$childIndent<$key>$(Esc-Xml $value)</$key>"
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $paramEl $node $refNode $childIndent
}
Write-Host "[OK] Parameter `"$paramName`": $key=$value added"
}
}
}
# Process availableValue
if ($avPart) {
$avRest = $avPart -replace '^availableValue=', ''
# Parse: "Перечисление...X presentation=текст с пробелами"
$avParts = $avRest -split '\s+presentation=', 2
$avValue = $avParts[0].Trim()
$avPresentation = if ($avParts.Count -gt 1) { $avParts[1].Trim() } else { "" }
# Detect value type
$avType = "xs:string"
if ($avValue -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
$avType = "dcscor:DesignTimeValue"
}
$avLines = @()
$avLines += "$childIndent<availableValue>"
$avLines += "$childIndent`t<value xsi:type=`"$avType`">$(Esc-Xml $avValue)</value>"
if ($avPresentation) {
$avLines += "$childIndent`t<presentation xsi:type=`"v8:LocalStringType`">"
$avLines += "$childIndent`t`t<v8:item>"
$avLines += "$childIndent`t`t`t<v8:lang>ru</v8:lang>"
$avLines += "$childIndent`t`t`t<v8:content>$(Esc-Xml $avPresentation)</v8:content>"
$avLines += "$childIndent`t`t</v8:item>"
$avLines += "$childIndent`t</presentation>"
}
$avLines += "$childIndent</availableValue>"
$fragXml = $avLines -join "`r`n"
# Insert before first of (denyIncompleteValues, use) in document order
$refNode = $null
foreach ($child in $paramEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and ($child.LocalName -eq 'denyIncompleteValues' -or $child.LocalName -eq 'use')) {
$refNode = $child; break
}
}
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $paramEl $node $refNode $childIndent
}
Write-Host "[OK] Parameter `"$paramName`": availableValue added"
}
}
}
"rename-parameter" {
foreach ($val in $values) {
# Shorthand: "OldName => NewName"
if ($val -notmatch '^\s*(.+?)\s*=>\s*(.+?)\s*$') {
Write-Host "[WARN] rename-parameter expects 'OldName => NewName', got: $val"
continue
}
$oldName = $Matches[1].Trim()
$newName = $Matches[2].Trim()
if ($oldName -eq $newName) {
Write-Host "[WARN] rename-parameter: old and new names are equal — skipped"
continue
}
# 1. Rename <parameter><name>OldName</name>
$root = $xmlDoc.DocumentElement
$paramEl = Find-ElementByChildValue $root "parameter" "name" $oldName $schNs
if (-not $paramEl) {
Write-Host "[WARN] Parameter `"$oldName`" not found — skipped"
continue
}
foreach ($ch in $paramEl.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'name' -and $ch.NamespaceURI -eq $schNs) {
$ch.InnerText = $newName
break
}
}
# 2. Update <expression> in other <parameter> elements.
# Regex matches "&OldName" only when followed by a non-identifier char (or end),
# so "&Период" matches "&Период.ДатаНачала" but NOT "&ПериодОтчета".
$escOld = [regex]::Escape($oldName)
$exprRegex = "&$escOld(?=[^\w\u0400-\u04FF]|$)"
$exprUpdated = 0
foreach ($ch in $root.ChildNodes) {
if ($ch.NodeType -ne 'Element' -or $ch.LocalName -ne 'parameter' -or $ch.NamespaceURI -ne $schNs) { continue }
foreach ($gc in $ch.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'expression' -and $gc.NamespaceURI -eq $schNs) {
$oldExpr = $gc.InnerText
$newExpr = [regex]::Replace($oldExpr, $exprRegex, "&$newName")
if ($newExpr -ne $oldExpr) {
$gc.InnerText = $newExpr
$exprUpdated++
}
}
}
}
# 3. Update <dcscor:parameter>OldName</dcscor:parameter> in dataParameters of all variants.
# Note: <settingsVariant> is in schNs, but <settings> and <dataParameters> are in setNs.
# IMPORTANT: don't use $variant — it collides with script parameter [string]$Variant
# (PowerShell vars are case-insensitive, and the [string] type would coerce XmlNode to "").
$dpUpdated = 0
foreach ($variantNode in $root.ChildNodes) {
if ($variantNode.NodeType -ne 'Element' -or $variantNode.LocalName -ne 'settingsVariant' -or $variantNode.NamespaceURI -ne $schNs) { continue }
$settings = Find-FirstElement $variantNode @("settings") $setNs
if (-not $settings) { continue }
$dpEl = Find-FirstElement $settings @("dataParameters") $setNs
if (-not $dpEl) { continue }
foreach ($item in $dpEl.ChildNodes) {
if ($item.NodeType -ne 'Element' -or $item.LocalName -ne 'item') { continue }
foreach ($gc in $item.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'parameter' -and $gc.NamespaceURI -eq $corNs) {
if ($gc.InnerText.Trim() -eq $oldName) {
$gc.InnerText = $newName
$dpUpdated++
}
}
}
}
}
Write-Host "[OK] Parameter renamed: `"$oldName`" => `"$newName`" (expressions updated: $exprUpdated, dataParameters updated: $dpUpdated)"
}
}
"reorder-parameters" {
foreach ($val in $values) {
# Shorthand: "Name1, Name2, Name3" — partial list, listed names go first in order, rest preserve original order
$order = @($val -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
if ($order.Count -eq 0) {
Write-Host "[WARN] reorder-parameters: empty list — skipped"
continue
}
$root = $xmlDoc.DocumentElement
# Collect all <parameter> in document order with their child indent
$allParams = @()
foreach ($ch in $root.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'parameter' -and $ch.NamespaceURI -eq $schNs) {
$allParams += $ch
}
}
if ($allParams.Count -eq 0) {
Write-Host "[WARN] reorder-parameters: no parameters in schema"
continue
}
$childIndent = Get-ChildIndent $root
# Build name -> element map
$byName = @{}
foreach ($pe in $allParams) {
foreach ($gc in $pe.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
$byName[$gc.InnerText.Trim()] = $pe
break
}
}
}
# Build new order
$newOrder = @()
$used = @{}
foreach ($name in $order) {
if ($byName.ContainsKey($name)) {
$newOrder += $byName[$name]
$used[$name] = $true
} else {
Write-Host "[WARN] reorder-parameters: parameter `"$name`" not found — skipped"
}
}
foreach ($pe in $allParams) {
$peName = $null
foreach ($gc in $pe.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
$peName = $gc.InnerText.Trim(); break
}
}
if ($peName -and -not $used.ContainsKey($peName)) {
$newOrder += $pe
}
}
# Find anchor: element right after the last parameter in original order
$lastParam = $allParams[-1]
$anchor = $lastParam.NextSibling
# Remove all parameters with surrounding whitespace
foreach ($pe in $allParams) {
Remove-NodeWithWhitespace $pe
}
# Re-insert in new order before anchor
foreach ($pe in $newOrder) {
Insert-BeforeElement $root $pe $anchor $childIndent
}
Write-Host "[OK] Parameters reordered ($($allParams.Count) total, $($order.Count) explicit)"
}
}
"add-filter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-FilterShorthand $val
$filterEl = Ensure-SettingsChild $settings "filter" @("selection")
$filterIndent = Get-ContainerChildIndent $filterEl
$fragXml = Build-FilterItemFragment -parsed $parsed -indent $filterIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $filterEl $node $null $filterIndent
}
Write-Host "[OK] Filter `"$($parsed.field) $($parsed.op)`" added to variant `"$varName`""
}
}
"add-dataParameter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-DataParamShorthand $val
$dpEl = Ensure-SettingsChild $settings "dataParameters" @("outputParameters","conditionalAppearance","order","filter","selection")
$dpIndent = Get-ContainerChildIndent $dpEl
$fragXml = Build-DataParamFragment -parsed $parsed -indent $dpIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $dpEl $node $null $dpIndent
}
Write-Host "[OK] DataParameter `"$($parsed.parameter)`" added to variant `"$varName`""
}
}
"add-order" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-OrderShorthand $val
$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) {
Insert-BeforeElement $orderEl $node $null $orderIndent
}
$desc = if ($parsed.field -eq "Auto") { "Auto" } else { "$($parsed.field) $($parsed.direction)" }
Write-Host "[OK] Order `"$desc`" added to variant `"$varName`""
}
}
"add-selection" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$fieldName = $val.Trim()
$groupName = $null
# Extract @group=Name
if ($fieldName -match '\s*@group=(\S+)') {
$groupName = $Matches[1]
$fieldName = ($fieldName -replace '\s*@group=\S+', '').Trim()
}
if ($groupName) {
# Find named StructureItemGroup
$dcssetNs = "http://v8.1c.ru/8.1/data-composition-system/settings"
$xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
$nsMgr.AddNamespace("dcsset", $dcssetNs)
$nsMgr.AddNamespace("xsi", $xsiNs)
$groupEl = $settings.SelectSingleNode(".//dcsset:item[@xsi:type='dcsset:StructureItemGroup'][dcsset:name='$groupName']", $nsMgr)
if (-not $groupEl) {
Write-Host "[WARN] StructureItemGroup `"$groupName`" not found — adding to variant level"
$targetEl = $settings
} else {
$targetEl = $groupEl
}
} else {
$targetEl = $settings
}
$selection = Ensure-SettingsChild $targetEl "selection" @()
# Dedup: skip if SelectedItemAuto already exists
if ($fieldName -eq "Auto") {
$isDup = $false
foreach ($ch in $selection.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("SelectedItemAuto")) { $isDup = $true; break }
}
}
if ($isDup) {
$target = if ($groupName) { "group `"$groupName`"" } else { "variant `"$varName`"" }
Write-Host "[WARN] SelectedItemAuto already exists in $target — skipped"
continue
}
}
$selIndent = Get-ContainerChildIndent $selection
$selXml = Build-SelectionItemFragment -fieldName $fieldName -indent $selIndent
$selNodes = Import-Fragment $xmlDoc $selXml
foreach ($node in $selNodes) {
Insert-BeforeElement $selection $node $null $selIndent
}
$target = if ($groupName) { "group `"$groupName`"" } else { "variant `"$varName`"" }
Write-Host "[OK] Selection `"$fieldName`" added to $target"
}
}
"set-query" {
$dsNode = Resolve-DataSet
$dsName = Get-DataSetName $dsNode
$queryEl = Find-FirstElement $dsNode @("query") $schNs
if (-not $queryEl) {
Write-Error "No <query> element found in dataset '$dsName'"
exit 1
}
# InnerText setter handles XML escaping automatically
$queryEl.InnerText = Resolve-QueryValue $Value $script:queryBaseDir
Write-Host "[OK] Query replaced in dataset `"$dsName`""
}
"patch-query" {
$dsNode = Resolve-DataSet
$dsName = Get-DataSetName $dsNode
$queryEl = Find-FirstElement $dsNode @("query") $schNs
if (-not $queryEl) {
Write-Error "No <query> element found in dataset '$dsName'"
exit 1
}
foreach ($val in $values) {
$sepIdx = $val.IndexOf(" => ")
if ($sepIdx -lt 0) {
Write-Error "patch-query value must contain ' => ' separator: old => new"
exit 1
}
$oldStr = $val.Substring(0, $sepIdx)
$newStr = $val.Substring($sepIdx + 4)
$queryText = $queryEl.InnerText
if (-not $queryText.Contains($oldStr)) {
Write-Error "Substring not found in query of dataset '$dsName': $oldStr"
exit 1
}
$queryEl.InnerText = $queryText.Replace($oldStr, $newStr)
Write-Host "[OK] Query patched in dataset `"$dsName`": replaced '$oldStr'"
}
}
"set-outputParameter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-OutputParamShorthand $val
$outputEl = Ensure-SettingsChild $settings "outputParameters" @("conditionalAppearance","order","filter","selection")
$outputIndent = Get-ContainerChildIndent $outputEl
# Remove existing parameter with same key if present
$existingParam = Find-ElementByChildValue $outputEl "item" "parameter" $parsed.key $corNs
if ($existingParam) {
Remove-NodeWithWhitespace $existingParam
Write-Host "[OK] Replaced outputParameter `"$($parsed.key)`" in variant `"$varName`""
} else {
Write-Host "[OK] OutputParameter `"$($parsed.key)`" added to variant `"$varName`""
}
$fragXml = Build-OutputParamFragment -parsed $parsed -indent $outputIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $outputEl $node $null $outputIndent
}
}
}
"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"
}
}
"add-dataSet" {
$root = $xmlDoc.DocumentElement
$childIndent = Get-ChildIndent $root
$parsed = Parse-DataSetShorthand $Value
$parsed.query = Resolve-QueryValue $parsed.query $script:queryBaseDir
# Auto-name if empty
if (-not $parsed.name) {
$count = 0
foreach ($ch in $root.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'dataSet' -and $ch.NamespaceURI -eq $schNs) { $count++ }
}
$parsed.name = "НаборДанных$($count + 1)"
}
# Duplicate check
$existing = Find-ElementByChildValue $root "dataSet" "name" $parsed.name $schNs
if ($existing) {
Write-Host "[WARN] DataSet `"$($parsed.name)`" already exists — skipped"
} else {
# Get dataSource name from first existing <dataSource>
$dsSourceEl = Find-FirstElement $root @("dataSource") $schNs
$dsSourceName = "ИсточникДанных1"
if ($dsSourceEl) {
$nameEl = Find-FirstElement $dsSourceEl @("name") $schNs
if ($nameEl) { $dsSourceName = $nameEl.InnerText.Trim() }
}
$parsed["dataSource"] = $dsSourceName
$fragXml = Build-DataSetQueryFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
# Insert after last <dataSet>, or after <dataSource> if none
$lastDS = Find-LastElement $root "dataSet" $schNs
if ($lastDS) {
$refNode = $lastDS.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = Find-FirstElement $root @("dataSetLink","calculatedField","totalField","parameter","template","groupTemplate","settingsVariant") $schNs
}
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
Write-Host "[OK] DataSet `"$($parsed.name)`" added (dataSource=$dsSourceName)"
}
}
"add-variant" {
$root = $xmlDoc.DocumentElement
$childIndent = Get-ChildIndent $root
foreach ($val in $values) {
$parsed = Parse-VariantShorthand $val
# Duplicate check — search for settingsVariant with matching dcsset:name
$isDup = $false
foreach ($ch in $root.ChildNodes) {
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'settingsVariant' -and $ch.NamespaceURI -eq $schNs) {
foreach ($gc in $ch.ChildNodes) {
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $setNs -and $gc.InnerText -eq $parsed.name) {
$isDup = $true; break
}
}
if ($isDup) { break }
}
}
if ($isDup) {
Write-Host "[WARN] Variant `"$($parsed.name)`" already exists — skipped"
continue
}
$fragXml = Build-VariantFragment -parsed $parsed -indent $childIndent
$nodes = Import-Fragment $xmlDoc $fragXml
# Insert after last <settingsVariant>
$lastSV = Find-LastElement $root "settingsVariant" $schNs
if ($lastSV) {
$refNode = $lastSV.NextSibling
while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) {
$refNode = $refNode.NextSibling
}
} else {
$refNode = $null
}
foreach ($node in $nodes) {
Insert-BeforeElement $root $node $refNode $childIndent
}
Write-Host "[OK] Variant `"$($parsed.name)`" [`"$($parsed.presentation)`"] added"
}
}
"add-conditionalAppearance" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$parsed = Parse-ConditionalAppearanceShorthand $val
$caEl = Ensure-SettingsChild $settings "conditionalAppearance" @("outputParameters","order","filter","selection")
$caIndent = Get-ContainerChildIndent $caEl
$fragXml = Build-ConditionalAppearanceItemFragment -parsed $parsed -indent $caIndent
$nodes = Import-Fragment $xmlDoc $fragXml
foreach ($node in $nodes) {
Insert-BeforeElement $caEl $node $null $caIndent
}
$desc = "$($parsed.param) = $($parsed.value)"
if ($parsed.filter) { $desc += " when $($parsed.filter.field) $($parsed.filter.op)" }
if ($parsed.fields -and $parsed.fields.Count -gt 0) { $desc += " for $($parsed.fields -join ', ')" }
Write-Host "[OK] ConditionalAppearance `"$desc`" added to variant `"$varName`""
}
}
"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 (only when explicitly set via @off / @on)
if ($parsed.use -eq $false) {
Set-OrCreateChildElement $filterItem "use" $setNs "false" $itemIndent
} elseif ($parsed.use -eq $true) {
# @on: remove existing use=false if any
$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`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
$valLines += "$itemIndent`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
$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 (only when explicitly set via @off / @on)
if ($parsed.use -eq $false) {
Set-OrCreateChildElement $dpItem "use" $corNs "false" $itemIndent
} elseif ($parsed.use -eq $true) {
# @on: remove existing use=false if any
$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
foreach ($val in $values) {
$fieldName = $val.Trim()
$fieldEl = Find-ElementByChildValue $dsNode "field" "dataPath" $fieldName $schNs
if (-not $fieldEl) {
Write-Host "[WARN] Field `"$fieldName`" not found in dataset `"$dsName`""
continue
}
Remove-NodeWithWhitespace $fieldEl
Write-Host "[OK] Field `"$fieldName`" removed from dataset `"$dsName`""
# Also remove from selection in variant
try {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Find-FirstElement $settings @("selection") $setNs
if ($selection) {
$selItem = Find-ElementByChildValue $selection "item" "field" $fieldName $setNs
if ($selItem) {
Remove-NodeWithWhitespace $selItem
Write-Host "[OK] Field `"$fieldName`" removed from selection of variant `"$varName`""
}
}
} catch {
# No variant — that's fine
}
}
}
"remove-total" {
foreach ($val in $values) {
$dataPath = $val.Trim()
$root = $xmlDoc.DocumentElement
$totalEl = Find-ElementByChildValue $root "totalField" "dataPath" $dataPath $schNs
if (-not $totalEl) {
Write-Host "[WARN] TotalField `"$dataPath`" not found"
continue
}
Remove-NodeWithWhitespace $totalEl
Write-Host "[OK] TotalField `"$dataPath`" removed"
}
}
"remove-calculated-field" {
foreach ($val in $values) {
$dataPath = $val.Trim()
$root = $xmlDoc.DocumentElement
$calcEl = Find-ElementByChildValue $root "calculatedField" "dataPath" $dataPath $schNs
if (-not $calcEl) {
Write-Host "[WARN] CalculatedField `"$dataPath`" not found"
continue
}
Remove-NodeWithWhitespace $calcEl
Write-Host "[OK] CalculatedField `"$dataPath`" removed"
# Also remove from selection
try {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
$selection = Find-FirstElement $settings @("selection") $setNs
if ($selection) {
$selItem = Find-ElementByChildValue $selection "item" "field" $dataPath $setNs
if ($selItem) {
Remove-NodeWithWhitespace $selItem
Write-Host "[OK] Field `"$dataPath`" removed from selection of variant `"$varName`""
}
}
} catch { }
}
}
"remove-parameter" {
foreach ($val in $values) {
$paramName = $val.Trim()
$root = $xmlDoc.DocumentElement
$paramEl = Find-ElementByChildValue $root "parameter" "name" $paramName $schNs
if (-not $paramEl) {
Write-Host "[WARN] Parameter `"$paramName`" not found"
continue
}
Remove-NodeWithWhitespace $paramEl
Write-Host "[OK] Parameter `"$paramName`" removed"
}
}
"remove-filter" {
$settings = Resolve-VariantSettings
$varName = Get-VariantName
foreach ($val in $values) {
$fieldName = $val.Trim()
$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" $fieldName $setNs
if (-not $filterItem) {
Write-Host "[WARN] Filter for `"$fieldName`" not found in variant `"$varName`""
continue
}
Remove-NodeWithWhitespace $filterItem
Write-Host "[OK] Filter for `"$fieldName`" removed from variant `"$varName`""
}
}
"add-drilldown" {
# String-based manipulation — templates use dcsat namespace with inline xmlns
$rawText = [System.IO.File]::ReadAllText($resolvedPath, [System.Text.Encoding]::UTF8)
$nl = "`r`n"
$dcsatNsDecl = 'xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template"'
# Find all outer <template> blocks by nesting-aware scan
$tplStarts = [System.Collections.ArrayList]::new()
$nameRegex = [regex]'<template>\s*<name>([^<]+)</name>'
foreach ($m in $nameRegex.Matches($rawText)) {
[void]$tplStarts.Add(@{ pos = $m.Index; name = $m.Groups[1].Value })
}
# For each start, find closing </template> at nesting depth 0
$tplBlocks = [System.Collections.ArrayList]::new()
foreach ($ts in $tplStarts) {
$depth = 1
$scanPos = $ts.pos + 10 # skip past opening <template>
while ($depth -gt 0 -and $scanPos -lt $rawText.Length) {
$nextOpen = $rawText.IndexOf("<template", $scanPos)
$nextClose = $rawText.IndexOf("</template>", $scanPos)
if ($nextClose -lt 0) { break }
if ($nextOpen -ge 0 -and $nextOpen -lt $nextClose) {
$depth++
$scanPos = $nextOpen + 10
} else {
$depth--
if ($depth -eq 0) {
$endPos = $nextClose + "</template>".Length
[void]$tplBlocks.Add(@{ name = $ts.name; start = $ts.pos; text = $rawText.Substring($ts.pos, $endPos - $ts.pos) })
}
$scanPos = $nextClose + 11
}
}
}
if ($tplBlocks.Count -eq 0) {
Write-Host "[WARN] No named templates found in schema"
}
# Collect all insertions as (position, text) — apply in reverse order
$insertions = [System.Collections.ArrayList]::new()
foreach ($tplBlock in $tplBlocks) {
$tplName = $tplBlock.name
$tplText = $tplBlock.text
$tplStart = $tplBlock.start
# Build map: expression → paramName from ExpressionAreaTemplateParameter
$exprMap = @{}
$exprRegex = [regex]'(?s)<parameter[^>]*ExpressionAreaTemplateParameter[^>]*>\s*<dcsat:name>([^<]+)</dcsat:name>\s*<dcsat:expression>([^<]+)</dcsat:expression>\s*</parameter>'
foreach ($em in $exprRegex.Matches($tplText)) {
$pName = $em.Groups[1].Value
$pExpr = $em.Groups[2].Value
$exprMap[$pExpr] = $pName
}
foreach ($resource in $values) {
$drillName = "Расшифровка_$resource"
# Idempotency: check if already exists
if ($tplText.Contains($drillName)) {
Write-Host "[INFO] $drillName already exists in $tplName — skipped"
continue
}
# Find ExpressionAreaTemplateParameter by expression
$paramName = $null
if ($exprMap.ContainsKey($resource)) {
$paramName = $exprMap[$resource]
} else {
Write-Host "[WARN] Expression `"$resource`" not found in template $tplName — skipped"
continue
}
$cellCount = 0
# Step 1: Insert DetailsAreaTemplateParameter after last </parameter> in template
$lastParamEndTag = "</parameter>"
$lastParamPos = $tplText.LastIndexOf($lastParamEndTag)
if ($lastParamPos -ge 0) {
$insertPos = $tplStart + $lastParamPos + $lastParamEndTag.Length
# Detect indent from context
$prevNewline = $tplText.LastIndexOf("`n", $lastParamPos)
$indent = "`t`t"
if ($prevNewline -ge 0) {
$lineStart = $prevNewline + 1
$indentMatch = [regex]::Match($tplText.Substring($lineStart), '^(\s*)')
if ($indentMatch.Success) { $indent = $indentMatch.Groups[1].Value }
}
$detailsXml = "$nl$indent<parameter $dcsatNsDecl xsi:type=`"dcsat:DetailsAreaTemplateParameter`">" +
"$nl$indent`t<dcsat:name>$drillName</dcsat:name>" +
"$nl$indent`t<dcsat:fieldExpression>" +
"$nl$indent`t`t<dcsat:field>ИмяРесурса</dcsat:field>" +
"$nl$indent`t`t<dcsat:expression>`"$resource`"</dcsat:expression>" +
"$nl$indent`t</dcsat:fieldExpression>" +
"$nl$indent`t<dcsat:mainAction>DrillDown</dcsat:mainAction>" +
"$nl$indent</parameter>"
[void]$insertions.Add(@{ pos = $insertPos; text = $detailsXml })
}
# Step 2: Insert appearance binding in cells referencing this parameter
$cellTag = '<dcsat:value xsi:type="dcscor:Parameter">' + $paramName + '</dcsat:value>'
$searchStart = 0
while (($cellIdx = $tplText.IndexOf($cellTag, $searchStart)) -ge 0) {
$cellEnd = $tplText.IndexOf("</dcsat:tableCell>", $cellIdx)
if ($cellEnd -lt 0) { break }
$appEnd = $tplText.LastIndexOf("</dcsat:appearance>", $cellEnd)
if ($appEnd -lt $cellIdx) { $searchStart = $cellEnd + 1; continue }
# Detect indent for appearance items — insert after \n, before indent of </dcsat:appearance>
$appPrevNl = $tplText.LastIndexOf("`n", $appEnd)
$appIndent = "`t`t`t`t`t`t"
if ($appPrevNl -ge 0) {
$appLineStart = $appPrevNl + 1
$appIndentMatch = [regex]::Match($tplText.Substring($appLineStart), '^(\s*)')
if ($appIndentMatch.Success) { $appIndent = $appIndentMatch.Groups[1].Value }
}
$itemIndent = $appIndent + "`t"
$appearanceXml = "$itemIndent<dcscor:item>$nl" +
"$itemIndent`t<dcscor:parameter>Расшифровка</dcscor:parameter>$nl" +
"$itemIndent`t<dcscor:value xsi:type=`"dcscor:Parameter`">$drillName</dcscor:value>$nl" +
"$itemIndent</dcscor:item>$nl"
# Insert after \n (before indent of closing tag), not before the tag itself
$insertAt = if ($appPrevNl -ge 0) { $tplStart + $appPrevNl + 1 } else { $tplStart + $appEnd }
[void]$insertions.Add(@{ pos = $insertAt; text = $appearanceXml })
$cellCount++
$searchStart = $cellEnd + 1
}
Write-Host "[OK] $drillName$tplName (param + $cellCount cell(s))"
}
}
# Apply insertions in reverse order to preserve offsets.
# For same position: reverse insertion order so first resource ends up first in file.
$idx = 0; foreach ($ins in $insertions) { $ins.seq = $idx; $idx++ }
$sorted = $insertions | Sort-Object { $_.pos }, { $_.seq } -Descending
foreach ($ins in $sorted) {
$rawText = $rawText.Insert($ins.pos, $ins.text)
}
# Write directly — skip DOM save
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $rawText, $enc)
Write-Host "[OK] Saved $resolvedPath"
exit 0
}
}
# --- 9. Save ---
$content = $xmlDoc.OuterXml
$content = $content -replace '(?<=<\?xml[^?]*encoding=")utf-8(?=")', 'UTF-8'
$enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $content, $enc)
Write-Host "[OK] Saved $resolvedPath"