feat(cfe-borrow): ссылки параметров выбора по uuid реквизита

Заимствование формы без основного реквизита копировало
<ChoiceParameterLinks>/<xr:Link> как есть, с текстовым путём
«Объект.Партнер». В расширении такой путь не разрешается — платформа
отвергала загрузку: «Неверный путь к полю - Объект.Партнер». Привязка
лежит в <xr:DataPath> внутри xr:Link, и общий стриппинг её не видел.

Конфигуратор ссылку не выбрасывает, а переводит путь в непрозрачную форму
«1/0:<uuid реквизита объекта>» — связь остаётся рабочей. Делаем так же;
реквизит, которого в источнике нет, недоступен и по uuid — такую связь
вырезаем вместе с опустевшим контейнером. Путь односегментный во всём
корпусе УТ (285 из 285), глубже не бывает.

Результат совпал с эталоном Конфигуратора вплоть до uuid, E2E на UT_DEMO:
«Load completed successfully». Регресс 12/12 на обоих рантаймах.
This commit is contained in:
Nick Shirokov
2026-08-12 15:53:59 +03:00
parent 3fafa099b2
commit 1bd0c7724e
22 changed files with 1112 additions and 2 deletions
@@ -1,4 +1,4 @@
# cfe-borrow v1.22 — Borrow objects from configuration into extension (CFE) (перенос UseAlways/Columns основного реквизита формы)
# cfe-borrow v1.23 — Borrow objects from configuration into extension (CFE) (ссылки параметров выбора по uuid)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$ExtensionPath,
@@ -38,6 +38,34 @@ function Strip-FormBindings {
return $xml
}
# Ссылки параметров выбора (<ChoiceParameterLinks>/<xr:Link>) — привязка особого рода: путь лежит
# в <xr:DataPath> и обычным стриппингом не снимается. Когда основной реквизит не заимствован,
# текстовый «Объект.X» в расширении не разрешается («Неверный путь к полю»), поэтому Конфигуратор
# переписывает его в непрозрачную форму «1/0:<uuid реквизита объекта>» — ссылка остаётся рабочей.
# Реквизит, которого в источнике нет, недоступен и по uuid: такую связь вырезаем целиком.
# Путь всегда односегментный (корпусная проверка: 285 из 285 у УТ), глубже — тоже вырезаем.
function Rewrite-ChoiceParameterLinks {
param([string]$xml, $attrUuids)
if ($xml -notmatch '<ChoiceParameterLinks>') { return $xml }
$xml = [regex]::Replace($xml, '(?s)\s*<xr:Link>.*?</xr:Link>', {
param($m)
$link = $m.Value
$dp = [regex]::Match($link, '<xr:DataPath[^>]*>Объект\.([^<]+)</xr:DataPath>')
if (-not $dp.Success) { return $link }
$attrName = $dp.Groups[1].Value
if ($attrUuids.ContainsKey($attrName)) {
return [regex]::Replace($link, '(<xr:DataPath[^>]*>)Объект\.[^<]+(</xr:DataPath>)', "`${1}1/0:$($attrUuids[$attrName])`${2}")
}
return ''
})
# Опустевший контейнер платформе не нужен
$xml = [regex]::Replace($xml, '(?s)\s*<ChoiceParameterLinks>\s*</ChoiceParameterLinks>', '')
return $xml
}
# --- 1. Resolve paths ---
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
@@ -449,6 +477,37 @@ if ($BorrowMainAttribute) {
}
# --- 10. Helper: read source object XML ---
# Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
# параметров выбора (см. Rewrite-ChoiceParameterLinks).
function Get-SourceAttributeUuids {
param([string]$typeName, [string]$objName)
$result = @{}
$dirName = $childTypeDirMap[$typeName]
if (-not $dirName) { return $result }
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
if (-not (Test-Path $srcFile)) { return $result }
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($srcFile)
$objEl = $null
foreach ($c in $doc.DocumentElement.ChildNodes) {
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
}
if (-not $objEl) { return $result }
$childObjects = $objEl.SelectSingleNode("*[local-name()='ChildObjects']")
if (-not $childObjects) { return $result }
foreach ($child in $childObjects.ChildNodes) {
if ($child.NodeType -ne 'Element') { continue }
if ($child.LocalName -notin @('Attribute','TabularSection')) { continue }
$uuid = $child.GetAttribute("uuid")
$nameNode = $child.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
if ($uuid -and $nameNode) { $result[$nameNode.InnerText.Trim()] = $uuid }
}
return $result
}
function Read-SourceObject {
param([string]$typeName, [string]$objName)
@@ -652,6 +711,11 @@ function Borrow-Form {
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
# uuid реквизитов объекта — только для формы без заимствованного основного реквизита:
# там ссылки параметров выбора переводятся на непрозрачную форму пути
$srcAttrUuids = @{}
if (-not $BorrowMainAttr) { $srcAttrUuids = Get-SourceAttributeUuids $typeName $objName }
# AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false
$autoCmdXml = ""
if ($srcAutoCmd) {
@@ -663,6 +727,7 @@ function Borrow-Form {
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
# Strip data-binding tags whose root attribute isn't borrowed
$autoCmdXml = Strip-FormBindings $autoCmdXml ([bool]$BorrowMainAttr)
if (-not $BorrowMainAttr) { $autoCmdXml = Rewrite-ChoiceParameterLinks $autoCmdXml $srcAttrUuids }
}
# ChildItems: copy full tree, clean up base-config references
@@ -675,6 +740,7 @@ function Borrow-Form {
# Strip data-binding tags whose root attribute isn't borrowed
# (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*/RowPicture*)
$childItemsXml = Strip-FormBindings $childItemsXml ([bool]$BorrowMainAttr)
if (-not $BorrowMainAttr) { $childItemsXml = Rewrite-ChoiceParameterLinks $childItemsXml $srcAttrUuids }
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
# Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID)