diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md index 1937ecad..9c5791a8 100644 --- a/.claude/skills/skd-compile/SKILL.md +++ b/.claude/skills/skd-compile/SKILL.md @@ -94,7 +94,14 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -V Составной тип (несколько типов значений) — массив в объектной форме: `"type": ["CatalogRef.A", "CatalogRef.B"]`. Квалификаторы (`(N)`, `(D,F)`) применяются к каждому элементу. -Роли: `@dimension`, `@account`, `@balance`, `@period`. +Роли (shorthand или объект): + +- `@`-флаги: `@dimension`, `@account`, `@balance`, `@period`, `@required`, `@autoOrder`, `@ignoreNullValues` +- KV: `balanceGroupName`, `balanceType` (`OpeningBalance`/`ClosingBalance`), `parentDimension`, `accountTypeExpression`, `expression`, `orderType` (`Asc`/`Desc`), `periodNumber`, `periodType` + +``` +"Сумма: decimal(15,2) @balance balanceGroupName=Сумма balanceType=OpeningBalance" +``` Ограничения: `#noField`, `#noFilter`, `#noGroup`, `#noOrder`. @@ -103,6 +110,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/skd-compile.ps1" -V Дополнительные ключи объектной формы: - `"presentationExpression": "<выражение>"` — что показывать вместо значения поля. Исходное значение остаётся «под капотом» для перехода/расшифровки. - `"appearance": { "<параметр>": "<значение>" }` — оформление колонки по умолчанию (применяется во всех вариантах настроек). Ключи — параметры платформы (`ГоризонтальноеПоложение`, `МинимальнаяШирина`, `Формат`, `Текст` и т.п.). +- `"orderExpression": { "expression": "<выражение>", "orderType": "Asc"/"Desc", "autoOrder": true/false }` — сортировка поля по выражению (например `ЕстьNULL(Поле.Порядок, 10000)`). ```json { "field": "Сумма", "title": "Сумма продажи", "type": "decimal(15,2)", diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index 00c37ad9..8c9e5ce1 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -1,4 +1,4 @@ -# skd-compile v1.26 — Compile 1C DCS from JSON +# skd-compile v1.103 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -36,6 +36,47 @@ if ($DefinitionFile) { $def = $json | ConvertFrom-Json +# --- Sentinel check: refuse to compile if JSON contains skd-decompile sentinels --- +# These mark places the decompiler couldn't reverse cleanly; user must resolve +# them manually before compile (see .warnings.md alongside the JSON). +$script:foundSentinels = @() +function Scan-Sentinels { + param($obj, [string]$path) + if ($null -eq $obj) { return } + if ($obj -is [System.Collections.IDictionary]) { + foreach ($k in @($obj.Keys)) { + if ($k -eq '__unsupported__') { + $u = $obj[$k] + $id = $u.id; $kind = $u.kind; $loc = $u.loc + $script:foundSentinels += " $id [$kind] at $path → $loc" + } else { + Scan-Sentinels -obj $obj[$k] -path "$path/$k" + } + } + } elseif ($obj -is [System.Management.Automation.PSCustomObject]) { + foreach ($p in $obj.PSObject.Properties) { + if ($p.Name -eq '__unsupported__') { + $u = $p.Value + $id = $u.id; $kind = $u.kind; $loc = $u.loc + $script:foundSentinels += " $id [$kind] at $path → $loc" + } else { + Scan-Sentinels -obj $p.Value -path "$path/$($p.Name)" + } + } + } elseif ($obj -is [System.Collections.IEnumerable] -and -not ($obj -is [string])) { + $i = 0 + foreach ($item in $obj) { Scan-Sentinels -obj $item -path "$path[$i]"; $i++ } + } +} +Scan-Sentinels -obj $def -path '' +if ($script:foundSentinels.Count -gt 0) { + [Console]::Error.WriteLine("skd-compile: JSON содержит __unsupported__ маркеры от skd-decompile.") + [Console]::Error.WriteLine("Это конструкции, которые декомпиляция не смогла обратить — нужно разрешить вручную перед компиляцией.") + [Console]::Error.WriteLine("См. .warnings.md рядом с JSON. Найдено:") + foreach ($s in $script:foundSentinels) { [Console]::Error.WriteLine($s) } + exit 4 +} + if (-not $def.dataSets -or $def.dataSets.Count -eq 0) { Write-Error "JSON must have at least one entry in 'dataSets'" exit 1 @@ -80,8 +121,21 @@ function Resolve-QueryValue { } function Emit-MLText { - param([string]$tag, $text, [string]$indent) - X "$indent<$tag xsi:type=`"v8:LocalStringType`">" + param([string]$tag, $text, [string]$indent, [switch]$NoXsiType) + # Empty value → self-closing tag (matches platform output) + if ($null -eq $text -or ($text -is [string] -and $text -eq '')) { + if ($NoXsiType) { + X "$indent<$tag/>" + } else { + X "$indent<$tag xsi:type=`"v8:LocalStringType`"/>" + } + return + } + if ($NoXsiType) { + X "$indent<$tag>" + } else { + X "$indent<$tag xsi:type=`"v8:LocalStringType`">" + } # Multi-lang: object form { ru: "...", en: "..." } → one per language if ($text -is [System.Management.Automation.PSCustomObject] -or $text -is [hashtable] -or $text -is [System.Collections.IDictionary]) { $props = if ($text -is [System.Management.Automation.PSCustomObject]) { $text.PSObject.Properties } else { $text.GetEnumerator() | ForEach-Object { @{ Name = $_.Key; Value = $_.Value } } } @@ -282,6 +336,14 @@ function Emit-SingleValueType { return } + # TypeSet (композитный тип-набор): голое имя без точки, типа DocumentRef / CatalogRef / + # EnumRef / ChartOfAccountsRef / etc. (все ссылки указанного класса). + # Эмитим вместо . + if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|BusinessProcessRef|TaskRef|ExchangePlanRef|InformationRegisterRef|AnyRef)$') { + X "$indentd5p1:$(Esc-Xml $typeStr)" + return + } + # Fallback — assume dot-qualified types are also config references if ($typeStr.Contains('.')) { X "$indentd5p1:$(Esc-Xml $typeStr)" @@ -299,6 +361,7 @@ function Parse-FieldShorthand { $result = @{ dataPath = ""; field = ""; title = ""; type = "" roles = @(); restrict = @(); appearance = [ordered]@{} + roleExtras = [ordered]@{} } # Extract @roles @@ -315,6 +378,11 @@ function Parse-FieldShorthand { } $s = [regex]::Replace($s, '\s*#\w+', '') + # Extract role kv=value (e.g. balanceGroupName=Сумма balanceType=OpeningBalance) + $kvMatches = [regex]::Matches($s, '(\w+)=(\S+)') + foreach ($m in $kvMatches) { $result.roleExtras[$m.Groups[1].Value] = $m.Groups[2].Value } + $s = [regex]::Replace($s, '\s*\w+=\S+', '') + # Split name: type $s = $s.Trim() if ($s.Contains(':')) { @@ -329,6 +397,55 @@ function Parse-FieldShorthand { return $result } +# Universal role spec parser: string / array / object / null +# Returns @{ tokens = @(...); extras = [ordered]@{...} } +function Parse-RoleSpec { + param($spec) + $tokens = @() + $extras = [ordered]@{} + + if ($null -ne $spec) { + if ($spec -is [string]) { + if ($spec -notmatch '\s' -and $spec -notmatch '=') { + $tokens += $spec + } else { + $s = $spec.Trim() + foreach ($m in [regex]::Matches($s, '@(\w+)')) { $tokens += $m.Groups[1].Value } + $s = [regex]::Replace($s, '\s*@\w+', '').Trim() + foreach ($m in [regex]::Matches($s, '(\w+)=(\S+)')) { $extras[$m.Groups[1].Value] = $m.Groups[2].Value } + } + } elseif ($spec -is [array] -or $spec -is [System.Collections.IList]) { + foreach ($t in $spec) { $tokens += "$t" } + } elseif ($spec.PSObject -and $spec.PSObject.Properties) { + foreach ($prop in $spec.PSObject.Properties) { + $val = $prop.Value + if ($val -is [bool]) { + if ($val) { $tokens += $prop.Name } + } elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [string]) { + $extras[$prop.Name] = "$val" + } + } + } elseif ($spec -is [hashtable] -or $spec -is [System.Collections.IDictionary]) { + foreach ($k in $spec.Keys) { + $val = $spec[$k] + if ($val -is [bool]) { + if ($val) { $tokens += "$k" } + } elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [string]) { + $extras["$k"] = "$val" + } + } + } + } + + # Deprecated alias: balanceGroup → balanceGroupName (старое имя в коде compile, в реальном XML — Name) + if ($extras.Contains('balanceGroup') -and -not $extras.Contains('balanceGroupName')) { + $extras['balanceGroupName'] = $extras['balanceGroup'] + $extras.Remove('balanceGroup') + } + + return @{ tokens = $tokens; extras = $extras } +} + # --- 6. Total field shorthand parser --- function Parse-TotalShorthand { @@ -611,6 +728,11 @@ $script:outputParamTypes = @{ "РасположениеРеквизитов" = "dcsset:DataCompositionAttributesPlacement" "ГоризонтальноеРасположениеОбщихИтогов" = "dcscor:DataCompositionTotalPlacement" "ВертикальноеРасположениеОбщихИтогов" = "dcscor:DataCompositionTotalPlacement" + "РасположениеОбщихИтогов" = "dcscor:DataCompositionTotalPlacement" + "РасположениеИтогов" = "dcscor:DataCompositionTotalPlacement" + "РасположениеГруппировки" = "dcsset:DataCompositionFieldGroupPlacement" + "РасположениеРесурсов" = "dcsset:DataCompositionResourcesPlacement" + "ТипМакета" = "dcsset:DataCompositionGroupTemplateType" } # --- 11. Emit sections --- @@ -626,6 +748,105 @@ function Emit-DataSources { } # === Fields === +function Has-JsonProp { + param($obj, [string]$name) + if ($null -eq $obj) { return $false } + if ($obj.PSObject -and $obj.PSObject.Properties) { + return $null -ne $obj.PSObject.Properties[$name] + } + if ($obj -is [System.Collections.IDictionary]) { return $obj.Contains($name) } + return $false +} + +function Emit-InputParameters { + param($ip, [string]$indent) + if ($null -eq $ip) { return } + $items = @($ip) + if ($items.Count -eq 0) { return } + X "$indent" + foreach ($item in $items) { + X "$indent`t" + if ((Has-JsonProp $item 'use') -and $null -ne $item.use -and -not $item.use) { + X "$indent`t`tfalse" + } + X "$indent`t`t$(Esc-Xml "$($item.parameter)")" + if (Has-JsonProp $item 'choiceParameters') { + $cp = $item.choiceParameters + $cpItems = if ($null -ne $cp) { @($cp) } else { @() } + if ($cpItems.Count -eq 0) { + X "$indent`t`t" + } else { + X "$indent`t`t" + foreach ($cpItem in $cpItems) { + X "$indent`t`t`t" + X "$indent`t`t`t`t$(Esc-Xml "$($cpItem.name)")" + foreach ($v in @($cpItem.values)) { + if ($v -is [bool]) { + $vStr = if ($v) { 'true' } else { 'false' } + X "$indent`t`t`t`t$vStr" + } elseif ($v -is [int] -or $v -is [long] -or $v -is [double] -or $v -is [decimal]) { + X "$indent`t`t`t`t$v" + } else { + X "$indent`t`t`t`t$(Esc-Xml "$v")" + } + } + X "$indent`t`t`t" + } + X "$indent`t`t" + } + } elseif (Has-JsonProp $item 'choiceParameterLinks') { + $cpl = $item.choiceParameterLinks + $cplItems = if ($null -ne $cpl) { @($cpl) } else { @() } + if ($cplItems.Count -eq 0) { + X "$indent`t`t" + } else { + X "$indent`t`t" + foreach ($cplItem in $cplItems) { + X "$indent`t`t`t" + X "$indent`t`t`t`t$(Esc-Xml "$($cplItem.name)")" + X "$indent`t`t`t`t$(Esc-Xml "$($cplItem.value)")" + $mode = if ($cplItem.mode) { "$($cplItem.mode)" } else { 'Auto' } + X "$indent`t`t`t`t$mode" + X "$indent`t`t`t" + } + X "$indent`t`t" + } + } elseif (Has-JsonProp $item 'value') { + # Simple typed value — определяем xsi:type из JSON-типа + $val = $item.value + # Явный кастомный type из decompile: {uri, name} → + $customType = $null + if (Has-JsonProp $item 'valueType') { + $vtSrc = $item.valueType + $uri = $null; $tName = $null + if ($vtSrc -is [PSCustomObject]) { + if ($vtSrc.PSObject.Properties['uri']) { $uri = "$($vtSrc.uri)" } + if ($vtSrc.PSObject.Properties['name']) { $tName = "$($vtSrc.name)" } + } elseif ($vtSrc -is [System.Collections.IDictionary]) { + if ($vtSrc.Contains('uri')) { $uri = "$($vtSrc['uri'])" } + if ($vtSrc.Contains('name')) { $tName = "$($vtSrc['name'])" } + } + if ($uri -and $tName) { $customType = @{ uri = $uri; name = $tName } } + } + if ($customType) { + X "$indent`t`t$(Esc-Xml "$val")" + } elseif ($val -is [bool]) { + $vStr = if ($val) { 'true' } else { 'false' } + X "$indent`t`t$vStr" + } elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [decimal]) { + X "$indent`t`t$val" + } elseif ($val -is [hashtable] -or $val -is [System.Collections.IDictionary] -or $val -is [PSCustomObject]) { + # Multilang dict {ru, en, ...} → LocalStringType + Emit-MLText -tag "dcscor:value" -text $val -indent "$indent`t`t" + } else { + X "$indent`t`t$(Esc-Xml "$val")" + } + } + X "$indent`t" + } + X "$indent" +} + function Emit-Field { param($fieldDef, [string]$indent) @@ -646,27 +867,22 @@ function Emit-Field { roles = @() restrict = @() appearance = [ordered]@{} + roleExtras = [ordered]@{} } - # Parse role + # Parse role (string shorthand / array / object — единый формат с /skd-edit set-field-role) if ($fieldDef.role) { - if ($fieldDef.role -is [string]) { - $f.roles = @($fieldDef.role) - } else { - # Object form — collect truthy keys - $roleObj = $fieldDef.role - foreach ($prop in $roleObj.PSObject.Properties) { - if ($prop.Value -eq $true) { $f.roles += $prop.Name } - } - } + $parsed = Parse-RoleSpec $fieldDef.role + $f.roles = $parsed.tokens + $f.roleExtras = $parsed.extras } # Parse restrictions if ($fieldDef.restrict) { $f.restrict = @($fieldDef.restrict) } - # Parse appearance + # Parse appearance (сохраняем значение как есть — может быть string или multilang dict) if ($fieldDef.appearance) { foreach ($prop in $fieldDef.appearance.PSObject.Properties) { - $f.appearance[$prop.Name] = "$($prop.Value)" + $f.appearance[$prop.Name] = $prop.Value } } if ($fieldDef.presentationExpression) { @@ -676,10 +892,31 @@ function Emit-Field { if ($fieldDef.attrRestrict) { $f["attrRestrict"] = @($fieldDef.attrRestrict) } - # role object extras - if ($fieldDef.role -and $fieldDef.role -isnot [string]) { - $f["roleObj"] = $fieldDef.role + # availableValues — array of {value, presentation} + if ($fieldDef.availableValues) { + $f["availableValues"] = $fieldDef.availableValues } + # orderExpression — {expression, orderType, autoOrder} + if ($fieldDef.orderExpression) { + $f["orderExpression"] = $fieldDef.orderExpression + } + # inputParameters — массив элементов, типизированных по форме value + if ($null -ne $fieldDef.inputParameters) { + $f["inputParameters"] = $fieldDef.inputParameters + } + # folder: true → DataSetFieldFolder (поле-папка для UI-группировки, только dataPath+title) + if ($fieldDef.folder -eq $true) { + $f["folder"] = $true + } + } + + # DataSetFieldFolder — только dataPath + title (для UI-группировки полей в композиторе) + if ($f["folder"]) { + X "$indent" + X "$indent`t$(Esc-Xml $f.dataPath)" + if ($f.title) { Emit-MLText -tag "title" -text $f.title -indent "$indent`t" } + X "$indent" + return } X "$indent" @@ -720,29 +957,50 @@ function Emit-Field { } # Role - if ($f.roles.Count -gt 0 -or $f["roleObj"]) { + $hasExtras = $f["roleExtras"] -and $f["roleExtras"].Count -gt 0 + if ($f.roles.Count -gt 0 -or $hasExtras) { X "$indent`t" foreach ($role in $f.roles) { if ($role -eq "period") { - # @period -> periodNumber + periodType (not ) - X "$indent`t`t1" - X "$indent`t`tMain" + # @period — sugar для periodNumber=1 + periodType=Main; extras могут переопределить. + $pnInExtras = $hasExtras -and $f["roleExtras"].Contains('periodNumber') + $ptInExtras = $hasExtras -and $f["roleExtras"].Contains('periodType') + if (-not $pnInExtras) { X "$indent`t`t1" } + if (-not $ptInExtras) { X "$indent`t`tMain" } } else { X "$indent`t`ttrue" } } - if ($f["roleObj"]) { - $ro = $f["roleObj"] - if ($ro.accountTypeExpression) { - X "$indent`t`t$(Esc-Xml "$($ro.accountTypeExpression)")" - } - if ($ro.balanceGroup) { - X "$indent`t`t$(Esc-Xml "$($ro.balanceGroup)")" + if ($hasExtras) { + foreach ($k in $f["roleExtras"].Keys) { + X "$indent`t`t$(Esc-Xml "$($f["roleExtras"][$k])")" } } X "$indent`t" } + # OrderExpression — после role, до valueType. Допустим массив (multi-sort). + if ($f["orderExpression"]) { + $oeRaw = $f["orderExpression"] + if ($oeRaw -is [System.Collections.IDictionary]) { + $oeList = @($oeRaw) + } elseif ($oeRaw -is [System.Collections.IList]) { + $oeList = $oeRaw + } else { + $oeList = @($oeRaw) + } + foreach ($oe in $oeList) { + $expr = if ($oe.expression) { "$($oe.expression)" } else { '' } + $oType = if ($oe.orderType) { "$($oe.orderType)" } else { 'Asc' } + $autoOrder = if ($null -ne $oe.autoOrder) { $(if ($oe.autoOrder) { 'true' } else { 'false' }) } else { 'false' } + X "$indent`t" + X "$indent`t`t$(Esc-Xml $expr)" + X "$indent`t`t$oType" + X "$indent`t`t$autoOrder" + X "$indent`t" + } + } + # ValueType if ($f.type) { X "$indent`t" @@ -750,19 +1008,41 @@ function Emit-Field { X "$indent`t" } + # AvailableValues — list of allowed values with optional multilang presentation + if ($f["availableValues"]) { + foreach ($av in $f["availableValues"]) { + X "$indent`t" + $avVal = $av.value + $avType = if ($av.valueType) { "$($av.valueType)" } else { '' } + if (-not $avType) { + if ($avVal -is [bool]) { $avType = 'xs:boolean' } + elseif ($avVal -is [int] -or $avVal -is [long] -or $avVal -is [double]) { $avType = 'xs:decimal' } + elseif ("$avVal" -match '^\d{4}-\d{2}-\d{2}T') { $avType = 'xs:dateTime' } + else { $avType = 'xs:string' } + } + $avStr = if ($avVal -is [bool]) { "$avVal".ToLower() } else { Esc-Xml "$avVal" } + X "$indent`t`t$avStr" + if ($av.presentation) { + Emit-MLText -tag "presentation" -text $av.presentation -indent "$indent`t`t" + } + X "$indent`t" + } + } + # Appearance if ($f.appearance -and $f.appearance.Count -gt 0) { X "$indent`t" foreach ($key in $f.appearance.Keys) { $val = $f.appearance[$key] - X "$indent`t`t" - X "$indent`t`t`t$(Esc-Xml $key)" - if ($key -eq "ГоризонтальноеПоложение") { - X "$indent`t`t`t$(Esc-Xml $val)" + # ГоризонтальноеПоложение требует специального xsi:type (v8ui:HorizontalAlign), не строка + if ($key -eq "ГоризонтальноеПоложение" -and -not ($val -is [hashtable] -or $val -is [System.Collections.IDictionary] -or $val -is [PSCustomObject])) { + X "$indent`t`t" + X "$indent`t`t`t$(Esc-Xml $key)" + X "$indent`t`t`t$(Esc-Xml "$val")" + X "$indent`t`t" } else { - X "$indent`t`t`t$(Esc-Xml $val)" + Emit-AppearanceValue -key $key -val $val -indent "$indent`t`t" } - X "$indent`t`t" } X "$indent`t" } @@ -772,12 +1052,17 @@ function Emit-Field { X "$indent`t$(Esc-Xml $f["presentationExpression"])" } + # InputParameters — в конце field + if ($f["inputParameters"]) { + Emit-InputParameters -ip $f["inputParameters"] -indent "$indent`t" + } + X "$indent" } # === DataSets === function Emit-DataSet { - param($ds, [string]$indent) + param($ds, [string]$indent, [string]$tagName = "dataSet") # Determine type if ($ds.items) { @@ -788,7 +1073,7 @@ function Emit-DataSet { $dsType = "DataSetQuery" } - X "$indent" + X "$indent<$tagName xsi:type=`"$dsType`">" X "$indent`t$(Esc-Xml "$($ds.name)")" # Fields @@ -815,12 +1100,12 @@ function Emit-DataSet { X "$indent`t$(Esc-Xml "$($ds.objectName)")" } elseif ($dsType -eq "DataSetUnion") { foreach ($item in $ds.items) { - # Union items are nested dataSets - Emit-DataSet -ds $item -indent "$indent`t" | Out-Null + # Union inner items are wrapped as + Emit-DataSet -ds $item -indent "$indent`t" -tagName "item" | Out-Null } } - X "$indent" + X "$indent" } function Emit-DataSets { @@ -845,6 +1130,15 @@ function Emit-DataSetLinks { if ($link.parameter) { X "`t`t$(Esc-Xml "$($link.parameter)")" } + if ($link.PSObject.Properties.Match('parameterListAllowed').Count -gt 0 -and $link.parameterListAllowed) { + X "`t`ttrue" + } + if ($link.PSObject.Properties.Match('startExpression').Count -gt 0 -and $null -ne $link.startExpression) { + X "`t`t$(Esc-Xml "$($link.startExpression)")" + } + if ($link.PSObject.Properties.Match('linkConditionExpression').Count -gt 0 -and $null -ne $link.linkConditionExpression) { + X "`t`t$(Esc-Xml "$($link.linkConditionExpression)")" + } X "`t" } } @@ -930,10 +1224,15 @@ function Emit-CalcFields { if ($appearance) { X "`t`t" foreach ($prop in $appearance.PSObject.Properties) { - X "`t`t`t" - X "`t`t`t`t$(Esc-Xml $prop.Name)" - X "`t`t`t`t$(Esc-Xml "$($prop.Value)")" - X "`t`t`t" + # ГоризонтальноеПоложение — особый xsi:type (если не multilang) + if ($prop.Name -eq "ГоризонтальноеПоложение" -and -not ($prop.Value -is [hashtable] -or $prop.Value -is [System.Collections.IDictionary] -or $prop.Value -is [PSCustomObject])) { + X "`t`t`t" + X "`t`t`t`t$(Esc-Xml $prop.Name)" + X "`t`t`t`t$(Esc-Xml "$($prop.Value)")" + X "`t`t`t" + } else { + Emit-AppearanceValue -key $prop.Name -val $prop.Value -indent "`t`t`t" + } } X "`t`t" } @@ -999,12 +1298,23 @@ function Emit-SingleParam { # Value — for valueListAllowed params Designer omits when empty $vla = [bool]$parsed.valueListAllowed + # Multi-value (массив значений по умолчанию для valueListAllowed-параметра) — эмитим + # каждый отдельным . Различаем массив значений от composite type (тоже array, + # но в parsed.type). + $valIsArray = ($parsed.value -is [array]) -or ($parsed.value -is [System.Collections.IList] -and $parsed.value -isnot [string]) if ($parsed.type -is [array] -or $parsed.type -is [System.Collections.IList]) { # Composite type — Designer writes xsi:nil for any empty composite; # non-empty composite values are uncommon and would need per-type tagging. if (Test-EmptyValue $parsed.value) { if (-not $vla) { X "`t`t" } } + } elseif ($parsed.nilValue -eq $true) { + # Принудительный xsi:nil даже когда тип известен (для bit-perfect round-trip). + if (-not $vla) { X "`t`t" } + } elseif ($valIsArray) { + foreach ($v in @($parsed.value)) { + Emit-ParamValue -type $parsed.type -val $v -indent "`t`t" -valueListAllowed $false + } } else { Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t" -valueListAllowed $vla } @@ -1015,10 +1325,11 @@ function Emit-SingleParam { $parsed.useRestriction = $true } - # UseRestriction - if ($parsed.useRestriction -eq $true -or ($p -isnot [string] -and $p.useRestriction -eq $true)) { - X "`t`ttrue" - } + # UseRestriction — платформа всегда эмитит этот тег у параметра (true/false) + $urEmit = $false + if ($parsed.useRestriction -eq $true) { $urEmit = $true } + elseif ($p -isnot [string] -and $p.useRestriction -eq $true) { $urEmit = $true } + X ("`t`t" + $(if ($urEmit) { 'true' } else { 'false' }) + "") # Expression if ($parsed.expression) { @@ -1042,12 +1353,20 @@ function Emit-SingleParam { if (Test-EmptyValue $av.value) { Emit-EmptyValue -type $parsed.type -indent "`t`t`t" -tagPrefix "" -valueListAllowed $false } else { - $avVal = "$($av.value)" - $avType = "xs:string" - if ($avVal -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') { - $avType = "dcscor:DesignTimeValue" + $av_v = $av.value + if ($av_v -is [bool]) { + $bv = "$av_v".ToLower() + X "`t`t`t$bv" + } elseif ($av_v -is [int] -or $av_v -is [long] -or $av_v -is [double]) { + X "`t`t`t$av_v" + } else { + $avVal = "$av_v" + $avType = "xs:string" + if ($avVal -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') { + $avType = "dcscor:DesignTimeValue" + } + X "`t`t`t$(Esc-Xml $avVal)" } - X "`t`t`t$(Esc-Xml $avVal)" } # `title` accepted as synonym of `presentation` — both map to the same UI label. $avPres = if ($av.presentation) { $av.presentation } elseif ($av.title) { $av.title } else { "" } @@ -1073,6 +1392,11 @@ function Emit-SingleParam { X "`t`t$(Esc-Xml $useVal)" } + # InputParameters на параметре (ФорматРедактирования и т.п.) + if ($null -ne $p -and $p -isnot [string] -and $p.inputParameters) { + Emit-InputParameters -ip $p.inputParameters -indent "`t`t" + } + X "`t" } @@ -1105,6 +1429,7 @@ function Emit-Parameters { if ($p.valueListAllowed -eq $true) { $parsed.valueListAllowed = $true } if ($p.hidden -eq $true) { $parsed.hidden = $true } if ($p.autoDates -eq $true) { $parsed.autoDates = $true } + if ($p.nilValue -eq $true) { $parsed.nilValue = $true } } # @autoDates implies use=Always + denyIncompleteValues=true by default @@ -1121,19 +1446,21 @@ function Emit-Parameters { # Track parameter for auto dataParameters $script:allParams += @{ name = $parsed.name; hidden = [bool]$parsed.hidden; type = "$($parsed.type)"; value = $parsed.value } - # @autoDates: auto-generate НачалоПериода and КонецПериода (canonical БСП pattern) + # @autoDates: auto-generate НачалоПериода and КонецПериода (canonical БСП pattern). + # type=dateTime + DateFractions=DateTime — иначе КонецПериода обрезается до 00:00:00 + # и запрос `Дата МЕЖДУ &НачалоПериода И &КонецПериода` теряет данные за последний день. if ($parsed.autoDates) { $paramName = $parsed.name $beginParsed = @{ name = "НачалоПериода"; title = "Начало периода" - type = "date"; value = "0001-01-01T00:00:00" + type = "dateTime"; value = "0001-01-01T00:00:00" useRestriction = $true expression = "&$paramName.ДатаНачала" } Emit-SingleParam -p $null -parsed $beginParsed $endParsed = @{ name = "КонецПериода"; title = "Конец периода" - type = "date"; value = "0001-01-01T00:00:00" + type = "dateTime"; value = "0001-01-01T00:00:00" useRestriction = $true expression = "&$paramName.ДатаОкончания" } @@ -1157,6 +1484,8 @@ function Emit-EmptyValue { if ($valueListAllowed) { return } $t = if ($null -eq $type) { "" } else { "$type" } + # Нормализация: убираем префикс xs: (валидный для valueType из decompile/DSL) + $tBare = if ($t -match '^xs:(.+)$') { $matches[1] } else { $t } $pf = $tagPrefix if ($t -eq "") { @@ -1167,13 +1496,13 @@ function Emit-EmptyValue { X "$indent`t0001-01-01T00:00:00" X "$indent`t0001-01-01T00:00:00" X "$indent" - } elseif ($t -match '^string') { + } elseif ($tBare -match '^string') { X "$indent<${pf}value xsi:type=`"xs:string`"/>" - } elseif ($t -match '^(date|time)') { + } elseif ($tBare -match '^(date|time)') { X "$indent<${pf}value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00" - } elseif ($t -match '^decimal') { + } elseif ($tBare -match '^decimal') { X "$indent<${pf}value xsi:type=`"xs:decimal`">0" - } elseif ($t -eq "boolean") { + } elseif ($tBare -eq "boolean") { X "$indent<${pf}value xsi:type=`"xs:boolean`">false" } else { # Ref types or unknown — safe nil @@ -1189,15 +1518,35 @@ function Emit-ParamValue { return } - $valStr = "$val" + # val может быть строкой (variant only) или объектом {variant, startDate?, endDate?}. + $valIsDict = ($val -is [hashtable]) -or ($val -is [System.Collections.IDictionary]) -or ($val -is [PSCustomObject]) + $variantStr = $null + $sdStr = $null + $edStr = $null + if ($valIsDict) { + if ($val -is [PSCustomObject]) { + if ($val.PSObject.Properties['variant']) { $variantStr = "$($val.variant)" } + if ($val.PSObject.Properties['startDate']) { $sdStr = "$($val.startDate)" } + if ($val.PSObject.Properties['endDate']) { $edStr = "$($val.endDate)" } + } else { + if ($val.Contains('variant')) { $variantStr = "$($val['variant'])" } + if ($val.Contains('startDate')) { $sdStr = "$($val['startDate'])" } + if ($val.Contains('endDate')) { $edStr = "$($val['endDate'])" } + } + } + $valStr = if ($variantStr) { $variantStr } else { "$val" } if ($type -eq "StandardPeriod") { - # val is a period variant string like "LastMonth" or "Custom". - # Always emit startDate/endDate to match how 1C Designer saves the schema. + # Platform-pattern: startDate/endDate эмитятся ТОЛЬКО для variant=Custom. + # Для всех остальных вариантов (ThisMonth, LastYear, Today, ...) — без дат. X "$indent" X "$indent`t$(Esc-Xml $valStr)" - X "$indent`t0001-01-01T00:00:00" - X "$indent`t0001-01-01T00:00:00" + if ($valStr -eq 'Custom') { + $sdOut = if ($sdStr) { $sdStr } else { '0001-01-01T00:00:00' } + $edOut = if ($edStr) { $edStr } else { '0001-01-01T00:00:00' } + X "$indent`t$(Esc-Xml $sdOut)" + X "$indent`t$(Esc-Xml $edOut)" + } X "$indent" } elseif ($type -match '^date') { X "$indent$(Esc-Xml $valStr)" @@ -1227,6 +1576,12 @@ function Emit-ParamValue { # Built-in style presets $script:areaStylePresets = @{ + none = @{ + font = $null; fontSize = $null; bold = $false; italic = $false + hAlign = $null; vAlign = $null; wrap = $false + bgColor = $null; textColor = $null + borderColor = $null; borders = $false + } data = @{ font = 'Arial'; fontSize = 10; bold = $false; italic = $false hAlign = $null; vAlign = $null; wrap = $false @@ -1296,18 +1651,34 @@ foreach ($stylesFile in $searchPaths) { function Emit-ColorValue { param([string]$color, [string]$indent) - if ($color.StartsWith('style:')) { - $styleName = $color.Substring(6) - X "$indentd8p1:$styleName" - } else { - X "$indent$(Esc-Xml $color)" + # Префиксы style:/web:/win: → соответствующий xmlns + dN:Name + $colorPrefixToUri = @{ + 'style:' = 'http://v8.1c.ru/8.1/data/ui/style' + 'web:' = 'http://v8.1c.ru/8.1/data/ui/colors/web' + 'win:' = 'http://v8.1c.ru/8.1/data/ui/colors/windows' } + foreach ($pfx in $colorPrefixToUri.Keys) { + if ($color.StartsWith($pfx)) { + $name = $color.Substring($pfx.Length) + $uri = $colorPrefixToUri[$pfx] + X "$indentd8p1:$name" + return + } + } + X "$indent$(Esc-Xml $color)" } function Emit-CellAppearance { param($style, [double]$width = 0, [bool]$vMerge = $false, [bool]$hMerge = $false, [double]$minHeight = 0, $extraItems = @()) - $ind = "`t`t`t`t`t" - X "`t`t`t`t" + $ind = "`t`t`t`t`t`t" + # Если ничего внутри appearance не будет — не эмитим блок вовсе + # (оригинал платформы для cells без атрибутов не пишет ). + $hasContent = $style.bgColor -or $style.textColor -or $style.borders -or $style.font -or ` + $style.hAlign -or $style.vAlign -or $style.wrap -or ` + ($width -gt 0) -or ($minHeight -gt 0) -or $vMerge -or $hMerge -or ` + ($extraItems -and @($extraItems).Count -gt 0) + if (-not $hasContent) { return } + X "`t`t`t`t`t" # Background color if ($style.bgColor) { X "$ind" @@ -1345,13 +1716,15 @@ function Emit-CellAppearance { } X "$ind" } - # Font - $boldStr = if ($style.bold) { "true" } else { "false" } - $italicStr = if ($style.italic) { "true" } else { "false" } - X "$ind" - X "$ind`tШрифт" - X "$ind`t" - X "$ind" + # Font (skip if style has no font configured — for "none" preset) + if ($style.font) { + $boldStr = if ($style.bold) { "true" } else { "false" } + $italicStr = if ($style.italic) { "true" } else { "false" } + X "$ind" + X "$ind`tШрифт" + X "$ind`t" + X "$ind" + } # Horizontal alignment if ($style.hAlign) { X "$ind" @@ -1407,7 +1780,35 @@ function Emit-CellAppearance { } # Extra appearance items (e.g. drilldown Расшифровка) foreach ($ei in $extraItems) { X $ei } - X "`t`t`t`t" + X "`t`t`t`t`t" +} + +# Cell может быть string ("text"/"{param}"/"|"/">"/null) или объектом {value, style}. +# Helpers извлекают значение и эффективный стиль ячейки. +function Get-CellValue { + param($cell) + if ($null -eq $cell) { return $null } + if ($cell -is [string]) { return $cell } + if ($cell -is [hashtable] -or $cell -is [System.Collections.IDictionary]) { + if ($cell.Contains('value')) { return $cell['value'] } + return $cell # multilang dict без обёртки + } + if ($cell.PSObject -and $cell.PSObject.Properties['value']) { return $cell.value } + # PSCustomObject без 'value' — это multilang dict ({ru, en, ...}), отдаём как есть + if ($cell -is [PSCustomObject]) { return $cell } + return $null +} + +function Get-CellStyleOrDefault { + param($cell, $defaultStyle) + if ($null -ne $cell -and -not ($cell -is [string]) -and $cell.PSObject -and $cell.PSObject.Properties['style']) { + $sName = "$($cell.style)" + if ($script:areaStylePresets.ContainsKey($sName)) { + return $script:areaStylePresets[$sName] + } + Write-Warning "Unknown cell style preset '$sName', falling back to template default" + } + return $defaultStyle } function Emit-AreaTemplateDSL { @@ -1420,7 +1821,11 @@ function Emit-AreaTemplateDSL { $style = $script:areaStylePresets[$styleName] $rows = @($t.rows) - $widths = if ($t.widths) { @($t.widths) } else { @() } + # PS-quirk: if-expression unwraps single-element @() результат + # (`$x = if (...) { @($arr) }` даёт скаляр при одном элементе). + # Используем обычный if вместо if-expression. + $widths = @() + if ($t.widths) { $widths = @($t.widths) } $minHeight = if ($t.minHeight) { [double]$t.minHeight } else { 0 } $colCount = if ($widths.Count -gt 0) { $widths.Count } else { $rows[0].Count } @@ -1429,10 +1834,8 @@ function Emit-AreaTemplateDSL { for ($r = $rows.Count - 1; $r -ge 1; $r--) { $vMerge[$r] = @{} for ($c = 0; $c -lt $colCount; $c++) { - $cellVal = $rows[$r][$c] - if ($cellVal -is [string] -and $cellVal -eq '|') { - $vMerge[$r][$c] = $true - } + $cellValStr = Get-CellValue $rows[$r][$c] + if ($cellValStr -eq '|') { $vMerge[$r][$c] = $true } } } if (-not $vMerge.ContainsKey(0)) { $vMerge[0] = @{} } @@ -1442,18 +1845,19 @@ function Emit-AreaTemplateDSL { for ($r = 0; $r -lt $rows.Count; $r++) { $hMerge[$r] = @{} for ($c = 0; $c -lt $colCount; $c++) { - $cellVal = $rows[$r][$c] - if ($cellVal -is [string] -and $cellVal -eq '>') { - $hMerge[$r][$c] = $true - } + $cellValStr = Get-CellValue $rows[$r][$c] + if ($cellValStr -eq '>') { $hMerge[$r][$c] = $true } } } - # Build drilldown map: param_name -> drilldown_value + # Build drilldown map: param_name -> drilldown_value (только для shortcut-формы — drilldown:string). + # Форма C (drilldown:object) — DetailsAreaTemplateParameter с произвольным именем, в map не идёт. $drilldownMap = @{} if ($t.parameters) { foreach ($tp in $t.parameters) { - if ($tp.drilldown) { $drilldownMap["$($tp.name)"] = "$($tp.drilldown)" } + if ($tp.drilldown -and ($tp.drilldown -is [string])) { + $drilldownMap["$($tp.name)"] = "$($tp.drilldown)" + } } } @@ -1464,20 +1868,29 @@ function Emit-AreaTemplateDSL { for ($r = 0; $r -lt $rows.Count; $r++) { X "`t`t`t" for ($c = 0; $c -lt $colCount; $c++) { - $cellVal = $rows[$r][$c] + $cellRaw = $rows[$r][$c] + $cellVal = Get-CellValue $cellRaw + $cellStyle = Get-CellStyleOrDefault $cellRaw $style $w = if ($c -lt $widths.Count) { [double]$widths[$c] } else { 0 } $isVMerged = $vMerge[$r][$c] -eq $true $isHMerged = $hMerge[$r][$c] -eq $true X "`t`t`t`t" if ($isVMerged) { # Vertically merged cell — only appearance with vMerge flag + width - Emit-CellAppearance $style $w $true + Emit-CellAppearance $cellStyle $w $true } elseif ($isHMerged) { # Horizontally merged cell — only appearance with hMerge flag + width - Emit-CellAppearance $style $w $false $true + Emit-CellAppearance $cellStyle $w $false $true } else { # Cell value - if ($null -ne $cellVal -and $cellVal -ne '') { + $cellIsDict = ($cellVal -is [hashtable]) -or ($cellVal -is [System.Collections.IDictionary]) -or ($cellVal -is [PSCustomObject]) + if ($cellIsDict) { + # Multilang static text — эмитим напрямую с lwsTitle-подобной структурой + X "`t`t`t`t`t" + Emit-MLText -tag "dcsat:value" -text $cellVal -indent "`t`t`t`t`t`t" + X "`t`t`t`t`t" + $cellExtraItems = @() + } elseif ($null -ne $cellVal -and $cellVal -ne '') { $cellStr = "$cellVal" # Unescape \| and \> if ($cellStr -eq '\|') { $cellStr = '|' } @@ -1488,14 +1901,26 @@ function Emit-AreaTemplateDSL { X "`t`t`t`t`t" X "`t`t`t`t`t`t$(Esc-Xml $paramName)" X "`t`t`t`t`t" - # Build drilldown appearance extra items + # Build drilldown appearance extra items. + # Приоритет: per-cell override (cell={value, drilldown}) → drilldownMap (shortcut form B). $cellExtraItems = @() - if ($drilldownMap.ContainsKey($paramName)) { - $ddVal = $drilldownMap[$paramName] - $cellExtraItems += "`t`t`t`t`t" - $cellExtraItems += "`t`t`t`t`t`tРасшифровка" - $cellExtraItems += "`t`t`t`t`t`tРасшифровка_$ddVal" - $cellExtraItems += "`t`t`t`t`t" + $cellDrillOverride = $null + if ($cellRaw -is [PSCustomObject] -and $cellRaw.PSObject.Properties['drilldown']) { + $cellDrillOverride = "$($cellRaw.drilldown)" + } elseif (($cellRaw -is [hashtable] -or $cellRaw -is [System.Collections.IDictionary]) -and $cellRaw.Contains('drilldown')) { + $cellDrillOverride = "$($cellRaw['drilldown'])" + } + $ddTarget = $null + if ($cellDrillOverride) { + $ddTarget = $cellDrillOverride + } elseif ($drilldownMap.ContainsKey($paramName)) { + $ddTarget = "Расшифровка_$($drilldownMap[$paramName])" + } + if ($ddTarget) { + $cellExtraItems += "`t`t`t`t`t`t" + $cellExtraItems += "`t`t`t`t`t`t`tРасшифровка" + $cellExtraItems += "`t`t`t`t`t`t`t$(Esc-Xml $ddTarget)" + $cellExtraItems += "`t`t`t`t`t`t" } } else { # Static text @@ -1507,7 +1932,7 @@ function Emit-AreaTemplateDSL { # Appearance $h = if ($r -eq 0) { $minHeight } else { 0 } if (-not $cellExtraItems) { $cellExtraItems = @() } - Emit-CellAppearance $style $w $false $false $h $cellExtraItems + Emit-CellAppearance $cellStyle $w $false $false $h $cellExtraItems $cellExtraItems = @() } X "`t`t`t`t" @@ -1519,27 +1944,62 @@ function Emit-AreaTemplateDSL { # Parameters (reuse existing logic) if ($t.parameters) { foreach ($tp in $t.parameters) { - X "`t`t" - X "`t`t`t$(Esc-Xml "$($tp.name)")" - X "`t`t`t$(Esc-Xml "$($tp.expression)")" - X "`t`t" - # Drilldown parameter - if ($tp.drilldown) { - $ddVal = "$($tp.drilldown)" - X "`t`t" - X "`t`t`tРасшифровка_$(Esc-Xml $ddVal)" - X "`t`t`t" - X "`t`t`t`tИмяРесурса" - X "`t`t`t`t`"$(Esc-Xml $ddVal)`"" - X "`t`t`t" - X "`t`t`tDrillDown" - X "`t`t" - } + Emit-AreaTemplateParameter -tp $tp -indent "`t`t" } } X "`t" } +# Эмиссия одного параметра шаблона. Различает три формы: +# A. { name, expression } → ExpressionAreaTemplateParameter +# B. { name, expression, drilldown: "X" } → Expression + Details(Расшифровка_X, ИмяРесурса, DrillDown) [shortcut] +# C. { name, drilldown: { field, expression, action? } } → DetailsAreaTemplateParameter с произвольным name +function Emit-AreaTemplateParameter { + param($tp, [string]$indent) + # Определяем форму C: drilldown — объект с полем field или expression. + $dd = $tp.drilldown + $ddIsObject = $false + if ($null -ne $dd) { + if ($dd -is [hashtable] -or $dd -is [System.Collections.IDictionary]) { $ddIsObject = $true } + elseif ($dd -is [PSCustomObject]) { $ddIsObject = $true } + } + if ($ddIsObject) { + # Форма C + $ddField = if ($dd -is [PSCustomObject]) { "$($dd.field)" } else { "$($dd['field'])" } + $ddExpr = if ($dd -is [PSCustomObject]) { "$($dd.expression)" } else { "$($dd['expression'])" } + $ddActV = $null + if ($dd -is [PSCustomObject] -and $dd.PSObject.Properties['action']) { $ddActV = "$($dd.action)" } + elseif (($dd -is [hashtable] -or $dd -is [System.Collections.IDictionary]) -and $dd.Contains('action')) { $ddActV = "$($dd['action'])" } + $ddAct = if ($ddActV) { $ddActV } else { 'DrillDown' } + X "$indent" + X "$indent`t$(Esc-Xml "$($tp.name)")" + X "$indent`t" + X "$indent`t`t$(Esc-Xml $ddField)" + X "$indent`t`t$(Esc-Xml $ddExpr)" + X "$indent`t" + X "$indent`t$(Esc-Xml $ddAct)" + X "$indent" + return + } + # Форма A или B + X "$indent" + X "$indent`t$(Esc-Xml "$($tp.name)")" + X "$indent`t$(Esc-Xml "$($tp.expression)")" + X "$indent" + if ($dd -and ($dd -is [string])) { + # Форма B: shortcut Расшифровка_ + ИмяРесурса + DrillDown + $ddVal = "$dd" + X "$indent" + X "$indent`tРасшифровка_$(Esc-Xml $ddVal)" + X "$indent`t" + X "$indent`t`tИмяРесурса" + X "$indent`t`t`"$(Esc-Xml $ddVal)`"" + X "$indent`t" + X "$indent`tDrillDown" + X "$indent" + } +} + # === Templates === function Emit-Templates { if (-not $def.templates) { return } @@ -1556,22 +2016,7 @@ function Emit-Templates { } if ($t.parameters) { foreach ($tp in $t.parameters) { - X "`t`t" - X "`t`t`t$(Esc-Xml "$($tp.name)")" - X "`t`t`t$(Esc-Xml "$($tp.expression)")" - X "`t`t" - # Drilldown parameter - if ($tp.drilldown) { - $ddVal = "$($tp.drilldown)" - X "`t`t" - X "`t`t`tРасшифровка_$(Esc-Xml $ddVal)" - X "`t`t`t" - X "`t`t`t`tИмяРесурса" - X "`t`t`t`t`"$(Esc-Xml $ddVal)`"" - X "`t`t`t" - X "`t`t`tDrillDown" - X "`t`t" - } + Emit-AreaTemplateParameter -tp $tp -indent "`t`t" } } X "`t" @@ -1579,6 +2024,19 @@ function Emit-Templates { } } +# === FieldTemplates === +# Привязка ') +# \u042d\u043c\u0438\u0441\u0441\u0438\u044f \u043e\u0434\u043d\u043e\u0433\u043e \u043f\u0430\u0440\u0430\u043c\u0435\u0442\u0440\u0430 \u0448\u0430\u0431\u043b\u043e\u043d\u0430. \u0420\u0430\u0437\u043b\u0438\u0447\u0430\u0435\u0442 \u0442\u0440\u0438 \u0444\u043e\u0440\u043c\u044b: +# A. {name, expression} \u2192 ExpressionAreaTemplateParameter +# B. {name, expression, drilldown: "X"} \u2192 Expression + Details(\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_X, \u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430, DrillDown) +# C. {name, drilldown: {field, expression, action?}} \u2192 DetailsAreaTemplateParameter \u0441 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u044b\u043c \u0438\u043c\u0435\u043d\u0435\u043c +def _emit_area_template_parameter(lines, tp, indent): + dd = tp.get('drilldown') + if isinstance(dd, dict): + # \u0424\u043e\u0440\u043c\u0430 C + dd_field = str(dd.get('field', '')) + dd_expr = str(dd.get('expression', '')) + dd_act = str(dd.get('action') or 'DrillDown') + lines.append(f'{indent}') + lines.append(f'{indent}\t{esc_xml(str(tp["name"]))}') + lines.append(f'{indent}\t') + lines.append(f'{indent}\t\t{esc_xml(dd_field)}') + lines.append(f'{indent}\t\t{esc_xml(dd_expr)}') + lines.append(f'{indent}\t') + lines.append(f'{indent}\t{esc_xml(dd_act)}') + lines.append(f'{indent}') + return + # \u0424\u043e\u0440\u043c\u0430 A \u0438\u043b\u0438 B + lines.append(f'{indent}') + lines.append(f'{indent}\t{esc_xml(str(tp["name"]))}') + lines.append(f'{indent}\t{esc_xml(str(tp.get("expression", "")))}') + lines.append(f'{indent}') + if dd and isinstance(dd, str): + # \u0424\u043e\u0440\u043c\u0430 B: shortcut \u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_ + \u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430 + DrillDown + dd_val = dd + lines.append(f'{indent}') + lines.append(f'{indent}\t\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_{esc_xml(dd_val)}') + lines.append(f'{indent}\t') + lines.append(f'{indent}\t\t\u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430') + lines.append(f'{indent}\t\t"{esc_xml(dd_val)}"') + lines.append(f'{indent}\t') + lines.append(f'{indent}\tDrillDown') + lines.append(f'{indent}') + + # === Templates === def emit_templates(lines, defn): @@ -1328,24 +1651,23 @@ def emit_templates(lines, defn): lines.append(f'\t\t{t["template"]}') if t.get('parameters'): for tp in t['parameters']: - lines.append('\t\t') - lines.append(f'\t\t\t{esc_xml(str(tp["name"]))}') - lines.append(f'\t\t\t{esc_xml(str(tp["expression"]))}') - lines.append('\t\t') - # Drilldown parameter - if tp.get('drilldown'): - dd_val = str(tp['drilldown']) - lines.append('\t\t') - lines.append(f'\t\t\t\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_{esc_xml(dd_val)}') - lines.append('\t\t\t') - lines.append('\t\t\t\t\u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430') - lines.append(f'\t\t\t\t"{esc_xml(dd_val)}"') - lines.append('\t\t\t') - lines.append('\t\t\tDrillDown') - lines.append('\t\t') + _emit_area_template_parameter(lines, tp, '\t\t') lines.append('\t') +# === FieldTemplates === +# Привязка