mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-10 08:04:56 +03:00
feat(cfe-borrow): add -BorrowMainAttribute for borrowing object attributes with form
When adding a new attribute to a borrowed form, -BorrowMainAttribute
borrows the form's main attribute ("Объект") and all referenced object
attributes, tabular sections, and their transitive type dependencies.
Two modes: Form (default — only attributes referenced by form DataPath)
and All (all object attributes). Deep paths like Объект.A.B are resolved
transitively. Already-borrowed objects are not overwritten.
Also fixed: CommonPicture auto-borrow from AutoCommandBar, form-attribute
DataPath stripping (keep only Объект.* paths).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: cfe-borrow
|
||||
description: Заимствование объектов из конфигурации 1С в расширение (CFE). Используй когда нужно перехватить метод, изменить форму или добавить реквизит к существующему объекту конфигурации
|
||||
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты"
|
||||
argument-hint: -ExtensionPath <path> -ConfigPath <path> -Object "Catalog.Контрагенты.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -31,6 +31,7 @@ allowed-tools:
|
||||
| `ExtensionPath` | Путь к каталогу расширения (обязат.) |
|
||||
| `ConfigPath` | Путь к конфигурации-источнику (обязат.) |
|
||||
| `Object` | Что заимствовать (обязат.), batch через `;;` |
|
||||
| `BorrowMainAttribute` | Используй при добавлении нового реквизита на заимствованную форму. `Form` (по умолч.) — реквизиты с формы, `All` — все реквизиты объекта |
|
||||
|
||||
## Формат -Object
|
||||
|
||||
@@ -52,6 +53,21 @@ allowed-tools:
|
||||
3. **Module.bsl** — пустой файл `Forms/ИмяФормы/Ext/Form/Module.bsl`
|
||||
4. **Регистрация** — `<Form>` в ChildObjects родительского объекта
|
||||
|
||||
### Заимствование основного реквизита формы (-BorrowMainAttribute)
|
||||
|
||||
**Когда нужно**: пользователь хочет добавить новый реквизит в существующий объект конфигурации и вывести его на заимствованную форму. Без `-BorrowMainAttribute` форма заимствуется "пустой" — только визуальные элементы, без привязки к данным объекта. С `-BorrowMainAttribute` форма сохраняет привязки к реквизитам объекта (DataPath), что позволяет затем добавить на неё новые элементы через `/form-edit`.
|
||||
|
||||
**Два режима**:
|
||||
- `Form` (по умолчанию) — заимствует только те реквизиты объекта, которые уже выведены на форму. Оптимальный выбор для большинства случаев
|
||||
- `All` — заимствует все реквизиты и табличные части объекта. Используй если планируешь выводить на форму реквизиты, которых на ней ещё нет
|
||||
|
||||
**Типовой сценарий** (добавление реквизита + вывод на форму):
|
||||
1. `/cfe-borrow` с `-BorrowMainAttribute` — заимствовать форму с реквизитами
|
||||
2. `/meta-edit` — добавить новый реквизит в объект расширения
|
||||
3. `/form-edit` — вывести реквизит на заимствованную форму
|
||||
|
||||
**Защита существующих данных**: если зависимый объект уже заимствован с содержимым (реквизитами, формами) — скрипт не перезаписывает его, а добавляет только недостающее.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
@@ -69,6 +85,12 @@ powershell.exe -NoProfile -File .claude/skills/cfe-borrow/scripts/cfe-borrow.ps1
|
||||
|
||||
# Несколько объектов за раз
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||
|
||||
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||
|
||||
# Заимствовать форму с ВСЕМИ реквизитами объекта
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# cfe-borrow v1.1 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
[Parameter(Mandatory)][string]$ConfigPath,
|
||||
[Parameter(Mandatory)][string]$Object
|
||||
[Parameter(Mandatory)][string]$Object,
|
||||
[string]$BorrowMainAttribute
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -256,6 +257,9 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "DataProcessorObject"; category = "Object" }
|
||||
@{ prefix = "DataProcessorManager"; category = "Manager" }
|
||||
)
|
||||
"DefinedType" = @(
|
||||
@{ prefix = "DefinedType"; category = "DefinedType" }
|
||||
)
|
||||
}
|
||||
|
||||
# Types that need ChildObjects element
|
||||
@@ -269,6 +273,9 @@ $typesWithChildObjects = @(
|
||||
# CommonModule properties to copy from source
|
||||
$commonModuleProps = @("Global","ClientManagedApplication","Server","ExternalConnection","ClientOrdinaryApplication","ServerCall")
|
||||
|
||||
# Standard system fields to skip when collecting DataPath references
|
||||
$script:standardFields = @("Code","Description","Ref","Parent","DeletionMark","Predefined","IsFolder","LineNumber","RowsCount","PredefinedDataName")
|
||||
|
||||
# --- 7. XML manipulation helpers (from cf-edit) ---
|
||||
function Get-ChildIndent($container) {
|
||||
foreach ($child in $container.ChildNodes) {
|
||||
@@ -324,6 +331,23 @@ if ($items.Count -eq 0) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- 9b. Validate -BorrowMainAttribute ---
|
||||
if ($BorrowMainAttribute) {
|
||||
# PS treats -BorrowMainAttribute without value as "True"
|
||||
if ($BorrowMainAttribute -eq "True") { $BorrowMainAttribute = "Form" }
|
||||
if ($BorrowMainAttribute -notin @("Form","All")) {
|
||||
Write-Error "-BorrowMainAttribute accepts 'Form' or 'All' (default: Form)"
|
||||
exit 1
|
||||
}
|
||||
# Validate: only with .Form. pattern
|
||||
$hasForm = $false
|
||||
foreach ($item in $items) { if ($item -match '\.Form\.') { $hasForm = $true; break } }
|
||||
if (-not $hasForm) {
|
||||
Write-Error "-BorrowMainAttribute requires a form in -Object (e.g. 'Catalog.X.Form.Y')"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# --- 10. Helper: read source object XML ---
|
||||
function Read-SourceObject {
|
||||
param([string]$typeName, [string]$objName)
|
||||
@@ -421,7 +445,7 @@ function Read-SourceFormUuid {
|
||||
|
||||
# --- 10c. Helper: borrow a form ---
|
||||
function Borrow-Form {
|
||||
param([string]$typeName, [string]$objName, [string]$formName)
|
||||
param([string]$typeName, [string]$objName, [string]$formName, [switch]$BorrowMainAttr)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
@@ -509,8 +533,13 @@ function Borrow-Form {
|
||||
$autoCmdXml = $autoCmdXml -replace '<Autofill>true</Autofill>', '<Autofill>false</Autofill>'
|
||||
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
|
||||
# Strip DataPath in AutoCommandBar buttons (e.g. Объект.Ref — invalid in extension)
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<DataPath>[^<]*</DataPath>', '')
|
||||
# Strip DataPath in AutoCommandBar buttons
|
||||
if ($BorrowMainAttr) {
|
||||
# Keep only Объект.* DataPaths
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<DataPath>(?!Объект\.)[^<]*</DataPath>', '')
|
||||
} else {
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<DataPath>[^<]*</DataPath>', '')
|
||||
}
|
||||
}
|
||||
|
||||
# ChildItems: copy full tree, clean up base-config references
|
||||
@@ -520,12 +549,17 @@ function Borrow-Form {
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, $nsStripPattern, '')
|
||||
# Replace all CommandName values with 0
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
||||
# Strip DataPath (references base form attributes not in extension)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<DataPath>[^<]*</DataPath>', '')
|
||||
# Strip TitleDataPath (e.g. Объект.Товары.RowsCount — invalid without base attributes)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<TitleDataPath>[^<]*</TitleDataPath>', '')
|
||||
# Strip RowPictureDataPath (e.g. Список.СостояниеДокумента — invalid in extension)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '')
|
||||
# Strip DataPath, TitleDataPath, RowPictureDataPath
|
||||
if ($BorrowMainAttr) {
|
||||
# Keep only Объект.* DataPaths — strip form-attribute DataPaths (not borrowed)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<DataPath>(?!Объект\.)[^<]*</DataPath>', '')
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<TitleDataPath>(?!Объект\.)[^<]*</TitleDataPath>', '')
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '')
|
||||
} else {
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<DataPath>[^<]*</DataPath>', '')
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<TitleDataPath>[^<]*</TitleDataPath>', '')
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '')
|
||||
}
|
||||
# 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)
|
||||
@@ -533,10 +567,14 @@ function Borrow-Form {
|
||||
# Strip element-level Events (base form handlers not in extension)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Events>.*?</Events>', '')
|
||||
|
||||
# Collect CommonPicture references from ChildItems
|
||||
$picRefs = [regex]::Matches($childItemsXml, '<xr:Ref>CommonPicture\.(\w+)</xr:Ref>')
|
||||
# Collect CommonPicture references from ChildItems and AutoCommandBar
|
||||
$referencedPictures = @{}
|
||||
$picRefs = [regex]::Matches($childItemsXml, '<xr:Ref>CommonPicture\.(\w+)</xr:Ref>')
|
||||
foreach ($m in $picRefs) { $referencedPictures[$m.Groups[1].Value] = $true }
|
||||
if ($autoCmdXml) {
|
||||
$picRefs2 = [regex]::Matches($autoCmdXml, '<xr:Ref>CommonPicture\.(\w+)</xr:Ref>')
|
||||
foreach ($m in $picRefs2) { $referencedPictures[$m.Groups[1].Value] = $true }
|
||||
}
|
||||
|
||||
# Auto-borrow referenced CommonPictures (if not already borrowed)
|
||||
$autoBorrowedPics = @()
|
||||
@@ -555,7 +593,7 @@ function Borrow-Form {
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
||||
Add-ToChildObjects "CommonPicture" $picName
|
||||
$autoBorrowedPics += $picName
|
||||
$borrowedFiles += $targetFile
|
||||
$script:borrowedFiles += $targetFile
|
||||
Info " Auto-borrowed: CommonPicture.${picName}"
|
||||
} else {
|
||||
Warn " CommonPicture.${picName} not found in source config — will strip from form"
|
||||
@@ -584,6 +622,19 @@ function Borrow-Form {
|
||||
# Strip StdPicture blocks (except Print)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '')
|
||||
|
||||
# Same Picture strip for AutoCommandBar
|
||||
if ($autoCmdXml) {
|
||||
$acPicMatches = [regex]::Matches($autoCmdXml, $picBlockPattern)
|
||||
for ($mi = $acPicMatches.Count - 1; $mi -ge 0; $mi--) {
|
||||
$pm = $acPicMatches[$mi]
|
||||
$cpName = $pm.Groups[1].Value
|
||||
if (-not $borrowedPicSet.ContainsKey($cpName)) {
|
||||
$autoCmdXml = $autoCmdXml.Remove($pm.Index, $pm.Length)
|
||||
}
|
||||
}
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '(?s)\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '')
|
||||
}
|
||||
|
||||
# Auto-borrow StyleItems referenced in ChildItems
|
||||
# Pattern 1: <Font ref="style:XXX" kind="StyleItem"/>, <TitleFont ref="style:XXX" ... kind="StyleItem"/>
|
||||
# Pattern 2: <BackColor>style:XXX</BackColor>, <TextColor>style:XXX</TextColor>, etc.
|
||||
@@ -607,7 +658,7 @@ function Borrow-Form {
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
||||
Add-ToChildObjects "StyleItem" $styleName
|
||||
$borrowedFiles += $targetFile
|
||||
$script:borrowedFiles += $targetFile
|
||||
Info " Auto-borrowed: StyleItem.${styleName}"
|
||||
} else {
|
||||
Warn " StyleItem.${styleName} not found in source config"
|
||||
@@ -680,7 +731,7 @@ function Borrow-Form {
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
||||
Add-ToChildObjects "Enum" $enumName
|
||||
$borrowedFiles += $targetFile
|
||||
$script:borrowedFiles += $targetFile
|
||||
Info " Auto-borrowed: Enum.${enumName} (with $($enumValueXmls.Count) EnumValue(s))"
|
||||
} else {
|
||||
Warn " Enum.${enumName} not found in source config"
|
||||
@@ -715,7 +766,22 @@ function Borrow-Form {
|
||||
$formXmlSb.Append("`t$childItemsXml") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
$formXmlSb.Append("`t<Attributes/>") | Out-Null
|
||||
# Attributes: empty or with MainAttribute when BorrowMainAttr
|
||||
if ($BorrowMainAttr) {
|
||||
$objTypePrefix = ""
|
||||
$gtList = $script:generatedTypes[$typeName]
|
||||
if ($gtList) { foreach ($g in $gtList) { if ($g.category -eq "Object") { $objTypePrefix = $g.prefix; break } } }
|
||||
$mainAttrType = "cfg:${objTypePrefix}.${objName}"
|
||||
$formXmlSb.Append("`t<Attributes>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t<Attribute name=`"Объект`" id=`"1000001`">`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t</Attribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t</Attributes>") | Out-Null
|
||||
} else {
|
||||
$formXmlSb.Append("`t<Attributes/>") | Out-Null
|
||||
}
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
|
||||
# BaseForm: same content, indented one more level
|
||||
@@ -744,7 +810,18 @@ function Borrow-Form {
|
||||
}
|
||||
}
|
||||
|
||||
$formXmlSb.Append("`t`t<Attributes/>") | Out-Null
|
||||
# BaseForm Attributes: same as main section
|
||||
if ($BorrowMainAttr) {
|
||||
$formXmlSb.Append("`t`t<Attributes>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<Attribute name=`"Объект`" id=`"1000001`">`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t</Attribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t</Attributes>") | Out-Null
|
||||
} else {
|
||||
$formXmlSb.Append("`t`t<Attributes/>") | Out-Null
|
||||
}
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t</BaseForm>") | Out-Null
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
@@ -904,6 +981,540 @@ function Build-InternalInfoXml {
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
# --- 11b. Collect DataPath references from source Form.xml ---
|
||||
function Collect-FormDataPaths {
|
||||
param([string]$formXmlPath)
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
$content = [System.IO.File]::ReadAllText($formXmlPath, $enc)
|
||||
|
||||
$firstLevel = @{}
|
||||
$deepPaths = @()
|
||||
|
||||
$matches2 = [regex]::Matches($content, '<DataPath>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</DataPath>')
|
||||
foreach ($m in $matches2) {
|
||||
$path = $m.Groups[1].Value
|
||||
$segments = $path.Split(".")
|
||||
$seg0 = $segments[0]
|
||||
if ($script:standardFields -contains $seg0) { continue }
|
||||
$firstLevel[$seg0] = $true
|
||||
if ($segments.Count -ge 2) {
|
||||
$seg1 = $segments[1]
|
||||
if ($script:standardFields -contains $seg1) { continue }
|
||||
$deepPaths += @{ ObjectAttr = $seg0; SubAttr = $seg1 }
|
||||
}
|
||||
}
|
||||
|
||||
# Also collect from TitleDataPath
|
||||
$matches3 = [regex]::Matches($content, '<TitleDataPath>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</TitleDataPath>')
|
||||
foreach ($m in $matches3) {
|
||||
$path = $m.Groups[1].Value
|
||||
$segments = $path.Split(".")
|
||||
$seg0 = $segments[0]
|
||||
if ($script:standardFields -contains $seg0) { continue }
|
||||
$firstLevel[$seg0] = $true
|
||||
}
|
||||
|
||||
# Deduplicate deep paths
|
||||
$seen = @{}
|
||||
$uniqueDeep = @()
|
||||
foreach ($dp in $deepPaths) {
|
||||
$key = "$($dp.ObjectAttr).$($dp.SubAttr)"
|
||||
if (-not $seen.ContainsKey($key)) {
|
||||
$seen[$key] = $true
|
||||
$uniqueDeep += $dp
|
||||
}
|
||||
}
|
||||
|
||||
return @{ FirstLevel = $firstLevel; DeepPaths = $uniqueDeep }
|
||||
}
|
||||
|
||||
# --- 11c. Resolve source attributes and tabular sections ---
|
||||
function Resolve-SourceAttributes {
|
||||
param([string]$typeName, [string]$objName, $firstLevelNames)
|
||||
# $firstLevelNames: hashtable of names, or $null for "all"
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
|
||||
if (-not (Test-Path $srcFile)) {
|
||||
Write-Error "Source object not found: $srcFile"
|
||||
exit 1
|
||||
}
|
||||
|
||||
$srcDoc = New-Object System.Xml.XmlDocument
|
||||
$srcDoc.PreserveWhitespace = $false
|
||||
$srcDoc.Load($srcFile)
|
||||
|
||||
$srcNs = New-Object System.Xml.XmlNamespaceManager($srcDoc.NameTable)
|
||||
$srcNs.AddNamespace("md", $script:mdNs)
|
||||
$srcNs.AddNamespace("xr", $script:xrNs)
|
||||
$srcNs.AddNamespace("v8", $script:v8Ns)
|
||||
|
||||
$srcEl = $null
|
||||
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
|
||||
}
|
||||
if (-not $srcEl) { Write-Error "No metadata element in source: $srcFile"; exit 1 }
|
||||
|
||||
$childObjs = $srcEl.SelectSingleNode("md:ChildObjects", $srcNs)
|
||||
if (-not $childObjs) { return @{ Attributes = @(); TabularSections = @(); ExtraProps = @{} } }
|
||||
|
||||
$attrs = @()
|
||||
$tabSections = @()
|
||||
|
||||
foreach ($child in $childObjs.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
|
||||
if ($child.LocalName -eq 'Attribute') {
|
||||
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $srcNs)
|
||||
if (-not $nameNode) { continue }
|
||||
$attrName = $nameNode.InnerText
|
||||
if ($null -ne $firstLevelNames -and -not $firstLevelNames.ContainsKey($attrName)) { continue }
|
||||
|
||||
$uuid = $child.GetAttribute("uuid")
|
||||
$typeNode = $child.SelectSingleNode("md:Properties/md:Type", $srcNs)
|
||||
$typeXml = if ($typeNode) { $typeNode.OuterXml } else { "" }
|
||||
# Strip namespace declarations from Type
|
||||
$typeXml = [regex]::Replace($typeXml, '\s+xmlns(?::\w+)?="[^"]*"', '')
|
||||
|
||||
$attrs += @{ Name = $attrName; Uuid = $uuid; TypeXml = $typeXml }
|
||||
}
|
||||
elseif ($child.LocalName -eq 'TabularSection') {
|
||||
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $srcNs)
|
||||
if (-not $nameNode) { continue }
|
||||
$tsName = $nameNode.InnerText
|
||||
if ($null -ne $firstLevelNames -and -not $firstLevelNames.ContainsKey($tsName)) { continue }
|
||||
|
||||
$tsUuid = $child.GetAttribute("uuid")
|
||||
|
||||
# Extract GeneratedTypes from InternalInfo
|
||||
$tsGenTypes = @()
|
||||
$iiNode = $child.SelectSingleNode("md:InternalInfo", $srcNs)
|
||||
if ($iiNode) {
|
||||
$gtNodes = $iiNode.SelectNodes("xr:GeneratedType", $srcNs)
|
||||
foreach ($gt in $gtNodes) {
|
||||
$tsGenTypes += @{
|
||||
Name = $gt.GetAttribute("name")
|
||||
Category = $gt.GetAttribute("category")
|
||||
TypeId = $gt.SelectSingleNode("xr:TypeId", $srcNs).InnerText
|
||||
ValueId = $gt.SelectSingleNode("xr:ValueId", $srcNs).InnerText
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Extract ALL child attributes of TabularSection
|
||||
$tsAttrs = @()
|
||||
$tsChildObjs = $child.SelectSingleNode("md:ChildObjects", $srcNs)
|
||||
if ($tsChildObjs) {
|
||||
foreach ($tsChild in $tsChildObjs.ChildNodes) {
|
||||
if ($tsChild.NodeType -ne 'Element' -or $tsChild.LocalName -ne 'Attribute') { continue }
|
||||
$tsAttrName = $tsChild.SelectSingleNode("md:Properties/md:Name", $srcNs)
|
||||
if (-not $tsAttrName) { continue }
|
||||
$tsAttrUuid = $tsChild.GetAttribute("uuid")
|
||||
$tsTypeNode = $tsChild.SelectSingleNode("md:Properties/md:Type", $srcNs)
|
||||
$tsTypeXml = if ($tsTypeNode) { $tsTypeNode.OuterXml } else { "" }
|
||||
$tsTypeXml = [regex]::Replace($tsTypeXml, '\s+xmlns(?::\w+)?="[^"]*"', '')
|
||||
$tsAttrs += @{ Name = $tsAttrName.InnerText; Uuid = $tsAttrUuid; TypeXml = $tsTypeXml }
|
||||
}
|
||||
}
|
||||
|
||||
$tabSections += @{ Name = $tsName; Uuid = $tsUuid; GeneratedTypes = $tsGenTypes; Attributes = $tsAttrs }
|
||||
}
|
||||
}
|
||||
|
||||
# Extract extra Properties for main object enrichment (Hierarchical, CodeLength, etc.)
|
||||
$extraProps = @{}
|
||||
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
|
||||
if ($propsNode) {
|
||||
$propsToExtract = @("Hierarchical","FoldersOnTop","CodeLength","DescriptionLength","CodeType","CodeAllowedLength",
|
||||
"NumberType","NumberLength","NumberAllowedLength","NumberPeriodicity")
|
||||
foreach ($pName in $propsToExtract) {
|
||||
$pNode = $propsNode.SelectSingleNode("md:${pName}", $srcNs)
|
||||
if ($pNode) { $extraProps[$pName] = $pNode.InnerText }
|
||||
}
|
||||
}
|
||||
|
||||
return @{ Attributes = $attrs; TabularSections = $tabSections; ExtraProps = $extraProps }
|
||||
}
|
||||
|
||||
# --- 11d. Build adopted attribute XML ---
|
||||
function Build-AdoptedAttributeXml {
|
||||
param([string]$name, [string]$sourceUuid, [string]$typeXml, [string]$indent)
|
||||
|
||||
$newUuid = [guid]::NewGuid().ToString()
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("${indent}<Attribute uuid=`"${newUuid}`">") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<InternalInfo/>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<Properties>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<Name>${name}</Name>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<Comment/>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t${typeXml}") | Out-Null
|
||||
$sb.AppendLine("${indent}`t</Properties>") | Out-Null
|
||||
$sb.Append("${indent}</Attribute>") | Out-Null
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
# --- 11e. Build adopted tabular section XML ---
|
||||
function Build-AdoptedTabularSectionXml {
|
||||
param([string]$tsName, [string]$sourceUuid, $generatedTypes, $childAttrs, [string]$indent)
|
||||
|
||||
$newUuid = [guid]::NewGuid().ToString()
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("${indent}<TabularSection uuid=`"${newUuid}`">") | Out-Null
|
||||
|
||||
# InternalInfo with GeneratedTypes (new UUIDs, referencing source names)
|
||||
if ($generatedTypes -and $generatedTypes.Count -gt 0) {
|
||||
$sb.AppendLine("${indent}`t<InternalInfo>") | Out-Null
|
||||
foreach ($gt in $generatedTypes) {
|
||||
$newTid = [guid]::NewGuid().ToString()
|
||||
$newVid = [guid]::NewGuid().ToString()
|
||||
$sb.AppendLine("${indent}`t`t<xr:GeneratedType name=`"$($gt.Name)`" category=`"$($gt.Category)`">") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t`t<xr:TypeId>${newTid}</xr:TypeId>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t`t<xr:ValueId>${newVid}</xr:ValueId>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t</xr:GeneratedType>") | Out-Null
|
||||
}
|
||||
$sb.AppendLine("${indent}`t</InternalInfo>") | Out-Null
|
||||
} else {
|
||||
$sb.AppendLine("${indent}`t<InternalInfo/>") | Out-Null
|
||||
}
|
||||
|
||||
$sb.AppendLine("${indent}`t<Properties>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<Name>${tsName}</Name>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<Comment/>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t</Properties>") | Out-Null
|
||||
|
||||
# ChildObjects with all attributes
|
||||
if ($childAttrs -and $childAttrs.Count -gt 0) {
|
||||
$sb.AppendLine("${indent}`t<ChildObjects>") | Out-Null
|
||||
foreach ($ca in $childAttrs) {
|
||||
$caXml = Build-AdoptedAttributeXml $ca.Name $ca.Uuid $ca.TypeXml "${indent}`t`t"
|
||||
$sb.AppendLine($caXml) | Out-Null
|
||||
}
|
||||
$sb.AppendLine("${indent}`t</ChildObjects>") | Out-Null
|
||||
} else {
|
||||
$sb.AppendLine("${indent}`t<ChildObjects/>") | Out-Null
|
||||
}
|
||||
|
||||
$sb.Append("${indent}</TabularSection>") | Out-Null
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
# --- 11f. Collect reference types from attribute Type XML strings ---
|
||||
function Collect-ReferenceTypes {
|
||||
param([string[]]$typeXmls)
|
||||
|
||||
$result = @{}
|
||||
foreach ($typeXml in $typeXmls) {
|
||||
# cfg:CatalogRef.XXX, cfg:EnumRef.XXX, cfg:DocumentRef.XXX, etc.
|
||||
$refMatches = [regex]::Matches($typeXml, 'cfg:(\w+)Ref\.(\w+)')
|
||||
foreach ($m in $refMatches) {
|
||||
$refPrefix = $m.Groups[1].Value # e.g. "Catalog", "Enum", "Document"
|
||||
$objName = $m.Groups[2].Value
|
||||
$key = "${refPrefix}.${objName}"
|
||||
if (-not $result.ContainsKey($key)) {
|
||||
$result[$key] = @{ TypeName = $refPrefix; ObjName = $objName }
|
||||
}
|
||||
}
|
||||
# cfg:DefinedType.XXX (via v8:TypeSet or v8:Type)
|
||||
$dtMatches = [regex]::Matches($typeXml, 'cfg:DefinedType\.(\w+)')
|
||||
foreach ($m in $dtMatches) {
|
||||
$dtName = $m.Groups[1].Value
|
||||
$key = "DefinedType.${dtName}"
|
||||
if (-not $result.ContainsKey($key)) {
|
||||
$result[$key] = @{ TypeName = "DefinedType"; ObjName = $dtName }
|
||||
}
|
||||
}
|
||||
}
|
||||
return @($result.Values)
|
||||
}
|
||||
|
||||
# --- 11g. Merge adopted attributes into existing extension object XML ---
|
||||
function Merge-AttributesIntoObject {
|
||||
param([string]$typeName, [string]$objName, $attrsToAdd)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||
if (-not (Test-Path $objFile)) {
|
||||
Warn "Cannot merge attributes: $objFile not found"
|
||||
return
|
||||
}
|
||||
|
||||
$objDoc = New-Object System.Xml.XmlDocument
|
||||
$objDoc.PreserveWhitespace = $true
|
||||
$objDoc.Load($objFile)
|
||||
|
||||
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
|
||||
$objNs.AddNamespace("md", $script:mdNs)
|
||||
|
||||
$objEl = $null
|
||||
foreach ($c in $objDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||
}
|
||||
if (-not $objEl) { Warn "No type element in $objFile"; return }
|
||||
|
||||
$childObjs = $objEl.SelectSingleNode("md:ChildObjects", $objNs)
|
||||
if (-not $childObjs) {
|
||||
$childObjs = $objDoc.CreateElement("ChildObjects", $script:mdNs)
|
||||
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t`t")) | Out-Null
|
||||
$objEl.AppendChild($childObjs) | Out-Null
|
||||
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
||||
}
|
||||
|
||||
# Collect existing attribute names for dedup
|
||||
$existingNames = @{}
|
||||
foreach ($c in $childObjs.ChildNodes) {
|
||||
if ($c.NodeType -ne 'Element' -or $c.LocalName -ne 'Attribute') { continue }
|
||||
$nameNode = $c.SelectSingleNode("md:Properties/md:Name", $objNs)
|
||||
if ($nameNode) { $existingNames[$nameNode.InnerText] = $true }
|
||||
}
|
||||
|
||||
$added = 0
|
||||
foreach ($attr in $attrsToAdd) {
|
||||
if ($existingNames.ContainsKey($attr.Name)) { continue }
|
||||
$attrXml = Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t"
|
||||
|
||||
# Expand self-closing ChildObjects if needed
|
||||
if (-not $childObjs.HasChildNodes -or $childObjs.IsEmpty) {
|
||||
$closeWs = $objDoc.CreateWhitespace("`r`n`t`t")
|
||||
$childObjs.AppendChild($closeWs) | Out-Null
|
||||
}
|
||||
|
||||
$added++
|
||||
}
|
||||
|
||||
if ($added -gt 0) {
|
||||
# Build all adopted attributes as text and do string-level insertion
|
||||
$allAttrXml = ""
|
||||
foreach ($attr in $attrsToAdd) {
|
||||
if ($existingNames.ContainsKey($attr.Name)) { continue }
|
||||
$allAttrXml += "`r`n" + (Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t")
|
||||
}
|
||||
|
||||
# Save via text manipulation to avoid namespace issues with InnerXml
|
||||
$settings3 = New-Object System.Xml.XmlWriterSettings
|
||||
$settings3.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||
$settings3.Indent = $false
|
||||
$settings3.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||
$memStream3 = New-Object System.IO.MemoryStream
|
||||
$writer3 = [System.Xml.XmlWriter]::Create($memStream3, $settings3)
|
||||
$objDoc.Save($writer3)
|
||||
$writer3.Flush(); $writer3.Close()
|
||||
$bytes3 = $memStream3.ToArray()
|
||||
$memStream3.Close()
|
||||
$text3 = [System.Text.Encoding]::UTF8.GetString($bytes3)
|
||||
if ($text3.Length -gt 0 -and $text3[0] -eq [char]0xFEFF) { $text3 = $text3.Substring(1) }
|
||||
$text3 = $text3.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||
|
||||
# Insert attributes before </ChildObjects>
|
||||
$text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>"
|
||||
|
||||
$utf8Bom3 = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3)
|
||||
Info " Merged $added attribute(s) into: $objFile"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 11h. Borrow-MainAttribute orchestrator ---
|
||||
function Borrow-MainAttribute {
|
||||
param([string]$typeName, [string]$objName, [string]$formName, [string]$mode)
|
||||
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
Info "Borrowing main attribute for ${typeName}.${objName} (mode: $mode)..."
|
||||
|
||||
# Step 1: Collect DataPaths (Form mode) or take all (All mode)
|
||||
$firstLevelNames = $null
|
||||
$deepPaths = @()
|
||||
if ($mode -eq "Form") {
|
||||
$srcFormXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
if (-not (Test-Path $srcFormXmlPath)) {
|
||||
Write-Error "Source Form.xml not found: $srcFormXmlPath"
|
||||
exit 1
|
||||
}
|
||||
$dp = Collect-FormDataPaths $srcFormXmlPath
|
||||
$firstLevelNames = $dp.FirstLevel
|
||||
$deepPaths = $dp.DeepPaths
|
||||
Info " Collected $($firstLevelNames.Count) first-level DataPath references, $($deepPaths.Count) deep paths"
|
||||
} else {
|
||||
Info " Mode All: borrowing all attributes and tabular sections"
|
||||
}
|
||||
|
||||
# Step 2: Resolve source attributes
|
||||
$resolved = Resolve-SourceAttributes $typeName $objName $firstLevelNames
|
||||
$srcAttrs = $resolved.Attributes
|
||||
$srcTS = $resolved.TabularSections
|
||||
$extraProps = $resolved.ExtraProps
|
||||
Info " Resolved: $($srcAttrs.Count) attributes, $($srcTS.Count) tabular section(s)"
|
||||
|
||||
# Identify which FirstLevel names are TabularSections (for deep path filtering)
|
||||
$tsNames = @{}
|
||||
foreach ($ts in $srcTS) { $tsNames[$ts.Name] = $true }
|
||||
|
||||
# Step 3: Build the adopted content and insert into main object XML
|
||||
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
||||
|
||||
# Generate full object XML with attributes and TS
|
||||
$contentSb = New-Object System.Text.StringBuilder
|
||||
foreach ($attr in $srcAttrs) {
|
||||
$attrXml = Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t"
|
||||
$contentSb.AppendLine($attrXml) | Out-Null
|
||||
}
|
||||
foreach ($ts in $srcTS) {
|
||||
$tsXml = Build-AdoptedTabularSectionXml $ts.Name $ts.Uuid $ts.GeneratedTypes $ts.Attributes "`t`t`t"
|
||||
$contentSb.AppendLine($tsXml) | Out-Null
|
||||
}
|
||||
$adoptedContent = $contentSb.ToString().TrimEnd()
|
||||
|
||||
# Read existing object XML and inject
|
||||
$objContent = [System.IO.File]::ReadAllText($objFile, (New-Object System.Text.UTF8Encoding($true)))
|
||||
|
||||
# Inject extra properties after ExtendedConfigurationObject
|
||||
if ($extraProps.Count -gt 0) {
|
||||
$propsSb = New-Object System.Text.StringBuilder
|
||||
foreach ($pName in $extraProps.Keys) {
|
||||
$propsSb.Append("`r`n`t`t`t<${pName}>$($extraProps[$pName])</${pName}>") | Out-Null
|
||||
}
|
||||
$objContent = $objContent -replace '(</ExtendedConfigurationObject>)', "`$1$($propsSb.ToString())"
|
||||
}
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
if ($adoptedContent) {
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if ($objContent -match '<ChildObjects\s*/>') {
|
||||
$objContent = $objContent -replace '<ChildObjects\s*/>', "<ChildObjects>`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elseif ($objContent -match '(?s)<ChildObjects>(.*?)</ChildObjects>') {
|
||||
$existingInner = $Matches[1]
|
||||
$objContent = $objContent -replace '(?s)<ChildObjects>(.*?)</ChildObjects>', "<ChildObjects>${existingInner}`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
}
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($objFile, $objContent, $encBom)
|
||||
Info " Enriched object: $objFile"
|
||||
|
||||
# Step 4: Collect all reference types and borrow as shells
|
||||
$allTypeXmls = @()
|
||||
foreach ($a in $srcAttrs) { $allTypeXmls += $a.TypeXml }
|
||||
foreach ($ts in $srcTS) {
|
||||
foreach ($tsa in $ts.Attributes) { $allTypeXmls += $tsa.TypeXml }
|
||||
}
|
||||
$refTypes = Collect-ReferenceTypes $allTypeXmls
|
||||
Info " Reference types to borrow: $($refTypes.Count)"
|
||||
|
||||
foreach ($rt in $refTypes) {
|
||||
if (-not $childTypeDirMap.ContainsKey($rt.TypeName)) {
|
||||
Warn " Unknown reference type: $($rt.TypeName).$($rt.ObjName)"
|
||||
continue
|
||||
}
|
||||
if (Test-ObjectBorrowed $rt.TypeName $rt.ObjName) {
|
||||
Info " Already borrowed: $($rt.TypeName).$($rt.ObjName)"
|
||||
continue
|
||||
}
|
||||
$rtSrcFile = Join-Path (Join-Path $cfgDir $childTypeDirMap[$rt.TypeName]) "$($rt.ObjName).xml"
|
||||
if (-not (Test-Path $rtSrcFile)) {
|
||||
Warn " Source not found: $($rt.TypeName).$($rt.ObjName)"
|
||||
continue
|
||||
}
|
||||
$src = Read-SourceObject $rt.TypeName $rt.ObjName
|
||||
$borrowedXml = Build-BorrowedObjectXml $rt.TypeName $rt.ObjName $src.Uuid $src.Properties
|
||||
$targetDir = Join-Path $extDir $childTypeDirMap[$rt.TypeName]
|
||||
if (-not (Test-Path $targetDir)) {
|
||||
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
||||
}
|
||||
$targetFile = Join-Path $targetDir "$($rt.ObjName).xml"
|
||||
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
||||
Add-ToChildObjects $rt.TypeName $rt.ObjName
|
||||
$script:borrowedFiles += $targetFile
|
||||
Info " Auto-borrowed: $($rt.TypeName).$($rt.ObjName)"
|
||||
}
|
||||
|
||||
# Step 5: Handle deep paths (Form mode only)
|
||||
if ($mode -eq "Form" -and $deepPaths.Count -gt 0) {
|
||||
# Filter out deep paths where ObjectAttr is a TabularSection (those are TS column refs, not deep attribute refs)
|
||||
$realDeep = @()
|
||||
foreach ($dp in $deepPaths) {
|
||||
if (-not $tsNames.ContainsKey($dp.ObjectAttr)) { $realDeep += $dp }
|
||||
}
|
||||
|
||||
if ($realDeep.Count -gt 0) {
|
||||
Info " Processing $($realDeep.Count) deep path(s)..."
|
||||
|
||||
# Group by ObjectAttr → target catalog
|
||||
$deepByAttr = @{}
|
||||
foreach ($dp in $realDeep) {
|
||||
if (-not $deepByAttr.ContainsKey($dp.ObjectAttr)) { $deepByAttr[$dp.ObjectAttr] = @() }
|
||||
$deepByAttr[$dp.ObjectAttr] += $dp.SubAttr
|
||||
}
|
||||
|
||||
foreach ($attrName in $deepByAttr.Keys) {
|
||||
# Find the attribute's type to determine target catalog
|
||||
$attrInfo = $srcAttrs | Where-Object { $_.Name -eq $attrName } | Select-Object -First 1
|
||||
if (-not $attrInfo) { continue }
|
||||
|
||||
# Extract catalog name from type: cfg:CatalogRef.XXX
|
||||
$catMatch = [regex]::Match($attrInfo.TypeXml, 'cfg:(\w+)Ref\.(\w+)')
|
||||
if (-not $catMatch.Success) { continue }
|
||||
|
||||
$targetTypeName = $catMatch.Groups[1].Value
|
||||
$targetObjName = $catMatch.Groups[2].Value
|
||||
|
||||
# Ensure target is borrowed
|
||||
if (-not (Test-ObjectBorrowed $targetTypeName $targetObjName)) {
|
||||
$tSrc = Read-SourceObject $targetTypeName $targetObjName
|
||||
$tBorrowedXml = Build-BorrowedObjectXml $targetTypeName $targetObjName $tSrc.Uuid $tSrc.Properties
|
||||
$tTargetDir = Join-Path $extDir $childTypeDirMap[$targetTypeName]
|
||||
if (-not (Test-Path $tTargetDir)) {
|
||||
New-Item -ItemType Directory -Path $tTargetDir -Force | Out-Null
|
||||
}
|
||||
$tTargetFile = Join-Path $tTargetDir "${targetObjName}.xml"
|
||||
[System.IO.File]::WriteAllText($tTargetFile, $tBorrowedXml, $encBom)
|
||||
Add-ToChildObjects $targetTypeName $targetObjName
|
||||
$script:borrowedFiles += $tTargetFile
|
||||
Info " Auto-borrowed for deep path: ${targetTypeName}.${targetObjName}"
|
||||
}
|
||||
|
||||
# Resolve sub-attributes in target catalog
|
||||
$subNames = @{}
|
||||
foreach ($sn in $deepByAttr[$attrName]) { $subNames[$sn] = $true }
|
||||
$subResolved = Resolve-SourceAttributes $targetTypeName $targetObjName $subNames
|
||||
|
||||
if ($subResolved.Attributes.Count -gt 0) {
|
||||
Merge-AttributesIntoObject $targetTypeName $targetObjName $subResolved.Attributes
|
||||
|
||||
# Collect and borrow ref types from deep attributes
|
||||
$subTypeXmls = @()
|
||||
foreach ($sa in $subResolved.Attributes) { $subTypeXmls += $sa.TypeXml }
|
||||
$subRefTypes = Collect-ReferenceTypes $subTypeXmls
|
||||
foreach ($srt in $subRefTypes) {
|
||||
if (-not $childTypeDirMap.ContainsKey($srt.TypeName)) { continue }
|
||||
if (Test-ObjectBorrowed $srt.TypeName $srt.ObjName) { continue }
|
||||
$sSrcFile = Join-Path (Join-Path $cfgDir $childTypeDirMap[$srt.TypeName]) "$($srt.ObjName).xml"
|
||||
if (-not (Test-Path $sSrcFile)) { continue }
|
||||
$sSrc = Read-SourceObject $srt.TypeName $srt.ObjName
|
||||
$sBorrowedXml = Build-BorrowedObjectXml $srt.TypeName $srt.ObjName $sSrc.Uuid $sSrc.Properties
|
||||
$sTargetDir = Join-Path $extDir $childTypeDirMap[$srt.TypeName]
|
||||
if (-not (Test-Path $sTargetDir)) {
|
||||
New-Item -ItemType Directory -Path $sTargetDir -Force | Out-Null
|
||||
}
|
||||
$sTargetFile = Join-Path $sTargetDir "$($srt.ObjName).xml"
|
||||
[System.IO.File]::WriteAllText($sTargetFile, $sBorrowedXml, $encBom)
|
||||
Add-ToChildObjects $srt.TypeName $srt.ObjName
|
||||
$script:borrowedFiles += $sTargetFile
|
||||
Info " Auto-borrowed (deep): $($srt.TypeName).$($srt.ObjName)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Info " Main attribute borrowing complete"
|
||||
}
|
||||
|
||||
# --- 12. Helper: build borrowed object XML ---
|
||||
function Build-BorrowedObjectXml {
|
||||
param(
|
||||
@@ -1016,7 +1627,7 @@ function Add-ToChildObjects {
|
||||
}
|
||||
|
||||
# --- 14. Process each item ---
|
||||
$borrowedFiles = @()
|
||||
$script:borrowedFiles = @()
|
||||
$borrowedCount = 0
|
||||
|
||||
foreach ($item in $items) {
|
||||
@@ -1070,13 +1681,19 @@ foreach ($item in $items) {
|
||||
Info " Created: $targetFile"
|
||||
|
||||
Add-ToChildObjects $typeName $objName
|
||||
$borrowedFiles += $targetFile
|
||||
$script:borrowedFiles += $targetFile
|
||||
}
|
||||
|
||||
# Borrow the form
|
||||
$formFiles = Borrow-Form $typeName $objName $formName
|
||||
$borrowedFiles += $formFiles
|
||||
$hasBMA = [bool]$BorrowMainAttribute
|
||||
$formFiles = Borrow-Form $typeName $objName $formName -BorrowMainAttr:$hasBMA
|
||||
$script:borrowedFiles += $formFiles
|
||||
$borrowedCount++
|
||||
|
||||
# Borrow main attribute if requested
|
||||
if ($hasBMA) {
|
||||
Borrow-MainAttribute $typeName $objName $formName $BorrowMainAttribute
|
||||
}
|
||||
} else {
|
||||
# --- Object borrowing (existing logic) ---
|
||||
Info "Borrowing ${typeName}.${objName}..."
|
||||
@@ -1098,7 +1715,7 @@ foreach ($item in $items) {
|
||||
|
||||
Add-ToChildObjects $typeName $objName
|
||||
|
||||
$borrowedFiles += $targetFile
|
||||
$script:borrowedFiles += $targetFile
|
||||
$borrowedCount++
|
||||
}
|
||||
}
|
||||
@@ -1130,7 +1747,7 @@ Write-Host "=== cfe-borrow summary ==="
|
||||
Write-Host " Extension: $extDir"
|
||||
Write-Host " Config: $cfgDir"
|
||||
Write-Host " Borrowed: $borrowedCount object(s)"
|
||||
foreach ($f in $borrowedFiles) {
|
||||
foreach ($f in $script:borrowedFiles) {
|
||||
Write-Host " - $f"
|
||||
}
|
||||
exit 0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.1 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -221,6 +221,9 @@ GENERATED_TYPES = {
|
||||
{"prefix": "DataProcessorObject", "category": "Object"},
|
||||
{"prefix": "DataProcessorManager", "category": "Manager"},
|
||||
],
|
||||
"DefinedType": [
|
||||
{"prefix": "DefinedType", "category": "DefinedType"},
|
||||
],
|
||||
}
|
||||
|
||||
TYPES_WITH_CHILD_OBJECTS = [
|
||||
@@ -232,6 +235,12 @@ TYPES_WITH_CHILD_OBJECTS = [
|
||||
|
||||
COMMON_MODULE_PROPS = ["Global", "ClientManagedApplication", "Server", "ExternalConnection", "ClientOrdinaryApplication", "ServerCall"]
|
||||
|
||||
# Standard system fields to skip when collecting DataPath references
|
||||
STANDARD_FIELDS = [
|
||||
"Code", "Description", "Ref", "Parent", "DeletionMark",
|
||||
"Predefined", "IsFolder", "LineNumber", "RowsCount", "PredefinedDataName",
|
||||
]
|
||||
|
||||
XMLNS_DECL = (
|
||||
'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" '
|
||||
'xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" '
|
||||
@@ -318,6 +327,7 @@ def main():
|
||||
parser.add_argument("-ExtensionPath", required=True)
|
||||
parser.add_argument("-ConfigPath", required=True)
|
||||
parser.add_argument("-Object", required=True)
|
||||
parser.add_argument("-BorrowMainAttribute", nargs="?", const="Form", default=None)
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- 1. Resolve paths ---
|
||||
@@ -392,6 +402,9 @@ def main():
|
||||
break
|
||||
info(f"Extension NamePrefix: {name_prefix}")
|
||||
|
||||
# Module-level list for borrowed files (used by both main loop and borrow_main_attribute)
|
||||
borrowed_files = []
|
||||
|
||||
# --- Helper functions ---
|
||||
def read_source_object(type_name, obj_name):
|
||||
dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
|
||||
@@ -603,7 +616,456 @@ def main():
|
||||
save_xml_bom(obj_tree, obj_file)
|
||||
info(f" Registered form in: {obj_file}")
|
||||
|
||||
def borrow_form(type_name, obj_name, form_name):
|
||||
# --- 11b. Collect DataPath references from source Form.xml ---
|
||||
def collect_form_data_paths(form_xml_path):
|
||||
with open(form_xml_path, "r", encoding="utf-8-sig") as fh:
|
||||
content = fh.read()
|
||||
|
||||
first_level = {}
|
||||
deep_paths = []
|
||||
|
||||
for m in re.finditer(r'<DataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</DataPath>', content):
|
||||
path = m.group(1)
|
||||
segments = path.split(".")
|
||||
seg0 = segments[0]
|
||||
if seg0 in STANDARD_FIELDS:
|
||||
continue
|
||||
first_level[seg0] = True
|
||||
if len(segments) >= 2:
|
||||
seg1 = segments[1]
|
||||
if seg1 in STANDARD_FIELDS:
|
||||
continue
|
||||
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1})
|
||||
|
||||
# Also collect from TitleDataPath
|
||||
for m in re.finditer(r'<TitleDataPath>[^<]*\b\u041e\u0431\u044a\u0435\u043a\u0442\.(\w+(?:\.\w+)*)</TitleDataPath>', content):
|
||||
path = m.group(1)
|
||||
segments = path.split(".")
|
||||
seg0 = segments[0]
|
||||
if seg0 in STANDARD_FIELDS:
|
||||
continue
|
||||
first_level[seg0] = True
|
||||
|
||||
# Deduplicate deep paths
|
||||
seen = set()
|
||||
unique_deep = []
|
||||
for dp in deep_paths:
|
||||
key = f"{dp['ObjectAttr']}.{dp['SubAttr']}"
|
||||
if key not in seen:
|
||||
seen.add(key)
|
||||
unique_deep.append(dp)
|
||||
|
||||
return {"FirstLevel": first_level, "DeepPaths": unique_deep}
|
||||
|
||||
# --- 11c. Resolve source attributes and tabular sections ---
|
||||
def resolve_source_attributes(type_name, obj_name, first_level_names):
|
||||
# first_level_names: dict of names, or None for "all"
|
||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||
src_file = os.path.join(cfg_dir, dir_name, f"{obj_name}.xml")
|
||||
if not os.path.isfile(src_file):
|
||||
print(f"Source object not found: {src_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
src_parser = etree.XMLParser(remove_blank_text=True)
|
||||
src_tree = etree.parse(src_file, src_parser)
|
||||
src_root = src_tree.getroot()
|
||||
|
||||
ns_strip = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||
|
||||
src_el = None
|
||||
for c in src_root:
|
||||
if isinstance(c.tag, str):
|
||||
src_el = c
|
||||
break
|
||||
if src_el is None:
|
||||
print(f"No metadata element in source: {src_file}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
child_objs = src_el.find(f"{{{MD_NS}}}ChildObjects")
|
||||
if child_objs is None:
|
||||
return {"Attributes": [], "TabularSections": [], "ExtraProps": {}}
|
||||
|
||||
attrs = []
|
||||
tab_sections = []
|
||||
|
||||
for child in child_objs:
|
||||
if not isinstance(child.tag, str):
|
||||
continue
|
||||
ln = localname(child)
|
||||
|
||||
if ln == "Attribute":
|
||||
name_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
if name_node is None:
|
||||
continue
|
||||
attr_name = (name_node.text or "").strip()
|
||||
if first_level_names is not None and attr_name not in first_level_names:
|
||||
continue
|
||||
|
||||
attr_uuid = child.get("uuid", "")
|
||||
type_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Type")
|
||||
type_xml = ""
|
||||
if type_node is not None:
|
||||
type_xml = etree.tostring(type_node, encoding="unicode")
|
||||
type_xml = ns_strip.sub("", type_xml)
|
||||
|
||||
attrs.append({"Name": attr_name, "Uuid": attr_uuid, "TypeXml": type_xml})
|
||||
|
||||
elif ln == "TabularSection":
|
||||
name_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
if name_node is None:
|
||||
continue
|
||||
ts_name = (name_node.text or "").strip()
|
||||
if first_level_names is not None and ts_name not in first_level_names:
|
||||
continue
|
||||
|
||||
ts_uuid = child.get("uuid", "")
|
||||
|
||||
# Extract GeneratedTypes from InternalInfo
|
||||
ts_gen_types = []
|
||||
ii_node = child.find(f"{{{MD_NS}}}InternalInfo")
|
||||
if ii_node is not None:
|
||||
for gt in ii_node:
|
||||
if isinstance(gt.tag, str) and localname(gt) == "GeneratedType":
|
||||
gt_name = gt.get("name", "")
|
||||
gt_category = gt.get("category", "")
|
||||
tid_el = gt.find(f"{{{XR_NS}}}TypeId")
|
||||
vid_el = gt.find(f"{{{XR_NS}}}ValueId")
|
||||
ts_gen_types.append({
|
||||
"Name": gt_name,
|
||||
"Category": gt_category,
|
||||
"TypeId": (tid_el.text or "") if tid_el is not None else "",
|
||||
"ValueId": (vid_el.text or "") if vid_el is not None else "",
|
||||
})
|
||||
|
||||
# Extract ALL child attributes of TabularSection
|
||||
ts_attrs = []
|
||||
ts_child_objs = child.find(f"{{{MD_NS}}}ChildObjects")
|
||||
if ts_child_objs is not None:
|
||||
for ts_child in ts_child_objs:
|
||||
if not isinstance(ts_child.tag, str) or localname(ts_child) != "Attribute":
|
||||
continue
|
||||
ts_attr_name_el = ts_child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
if ts_attr_name_el is None:
|
||||
continue
|
||||
ts_attr_uuid = ts_child.get("uuid", "")
|
||||
ts_type_node = ts_child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Type")
|
||||
ts_type_xml = ""
|
||||
if ts_type_node is not None:
|
||||
ts_type_xml = etree.tostring(ts_type_node, encoding="unicode")
|
||||
ts_type_xml = ns_strip.sub("", ts_type_xml)
|
||||
ts_attrs.append({
|
||||
"Name": (ts_attr_name_el.text or "").strip(),
|
||||
"Uuid": ts_attr_uuid,
|
||||
"TypeXml": ts_type_xml,
|
||||
})
|
||||
|
||||
tab_sections.append({
|
||||
"Name": ts_name, "Uuid": ts_uuid,
|
||||
"GeneratedTypes": ts_gen_types, "Attributes": ts_attrs,
|
||||
})
|
||||
|
||||
# Extract extra Properties for main object enrichment
|
||||
extra_props = {}
|
||||
props_node = src_el.find(f"{{{MD_NS}}}Properties")
|
||||
if props_node is not None:
|
||||
props_to_extract = [
|
||||
"Hierarchical", "FoldersOnTop", "CodeLength", "DescriptionLength",
|
||||
"CodeType", "CodeAllowedLength", "NumberType", "NumberLength",
|
||||
"NumberAllowedLength", "NumberPeriodicity",
|
||||
]
|
||||
for p_name in props_to_extract:
|
||||
p_node = props_node.find(f"{{{MD_NS}}}{p_name}")
|
||||
if p_node is not None:
|
||||
extra_props[p_name] = (p_node.text or "").strip()
|
||||
|
||||
return {"Attributes": attrs, "TabularSections": tab_sections, "ExtraProps": extra_props}
|
||||
|
||||
# --- 11d. Build adopted attribute XML ---
|
||||
def build_adopted_attribute_xml(name, source_uuid, type_xml, indent):
|
||||
new_uuid_val = new_guid()
|
||||
lines = [
|
||||
f'{indent}<Attribute uuid="{new_uuid_val}">',
|
||||
f'{indent}\t<InternalInfo/>',
|
||||
f'{indent}\t<Properties>',
|
||||
f'{indent}\t\t<ObjectBelonging>Adopted</ObjectBelonging>',
|
||||
f'{indent}\t\t<Name>{name}</Name>',
|
||||
f'{indent}\t\t<Comment/>',
|
||||
f'{indent}\t\t<ExtendedConfigurationObject>{source_uuid}</ExtendedConfigurationObject>',
|
||||
f'{indent}\t\t{type_xml}',
|
||||
f'{indent}\t</Properties>',
|
||||
f'{indent}</Attribute>',
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
# --- 11e. Build adopted tabular section XML ---
|
||||
def build_adopted_tabular_section_xml(ts_name, source_uuid, generated_types, child_attrs, indent):
|
||||
new_uuid_val = new_guid()
|
||||
lines = [f'{indent}<TabularSection uuid="{new_uuid_val}">']
|
||||
|
||||
# InternalInfo with GeneratedTypes (new UUIDs, referencing source names)
|
||||
if generated_types:
|
||||
lines.append(f'{indent}\t<InternalInfo>')
|
||||
for gt in generated_types:
|
||||
new_tid = new_guid()
|
||||
new_vid = new_guid()
|
||||
lines.append(f'{indent}\t\t<xr:GeneratedType name="{gt["Name"]}" category="{gt["Category"]}">')
|
||||
lines.append(f'{indent}\t\t\t<xr:TypeId>{new_tid}</xr:TypeId>')
|
||||
lines.append(f'{indent}\t\t\t<xr:ValueId>{new_vid}</xr:ValueId>')
|
||||
lines.append(f'{indent}\t\t</xr:GeneratedType>')
|
||||
lines.append(f'{indent}\t</InternalInfo>')
|
||||
else:
|
||||
lines.append(f'{indent}\t<InternalInfo/>')
|
||||
|
||||
lines.append(f'{indent}\t<Properties>')
|
||||
lines.append(f'{indent}\t\t<ObjectBelonging>Adopted</ObjectBelonging>')
|
||||
lines.append(f'{indent}\t\t<Name>{ts_name}</Name>')
|
||||
lines.append(f'{indent}\t\t<Comment/>')
|
||||
lines.append(f'{indent}\t\t<ExtendedConfigurationObject>{source_uuid}</ExtendedConfigurationObject>')
|
||||
lines.append(f'{indent}\t</Properties>')
|
||||
|
||||
# ChildObjects with all attributes
|
||||
if child_attrs:
|
||||
lines.append(f'{indent}\t<ChildObjects>')
|
||||
for ca in child_attrs:
|
||||
ca_xml = build_adopted_attribute_xml(ca["Name"], ca["Uuid"], ca["TypeXml"], f"{indent}\t\t")
|
||||
lines.append(ca_xml)
|
||||
lines.append(f'{indent}\t</ChildObjects>')
|
||||
else:
|
||||
lines.append(f'{indent}\t<ChildObjects/>')
|
||||
|
||||
lines.append(f'{indent}</TabularSection>')
|
||||
return "\n".join(lines)
|
||||
|
||||
# --- 11f. Collect reference types from attribute Type XML strings ---
|
||||
def collect_reference_types(type_xmls):
|
||||
result = {}
|
||||
for type_xml in type_xmls:
|
||||
# cfg:CatalogRef.XXX, cfg:EnumRef.XXX, cfg:DocumentRef.XXX, etc.
|
||||
for m in re.finditer(r'cfg:(\w+)Ref\.(\w+)', type_xml):
|
||||
ref_prefix = m.group(1)
|
||||
obj_n = m.group(2)
|
||||
key = f"{ref_prefix}.{obj_n}"
|
||||
if key not in result:
|
||||
result[key] = {"TypeName": ref_prefix, "ObjName": obj_n}
|
||||
# cfg:DefinedType.XXX
|
||||
for m in re.finditer(r'cfg:DefinedType\.(\w+)', type_xml):
|
||||
dt_name = m.group(1)
|
||||
key = f"DefinedType.{dt_name}"
|
||||
if key not in result:
|
||||
result[key] = {"TypeName": "DefinedType", "ObjName": dt_name}
|
||||
return list(result.values())
|
||||
|
||||
# --- 11g. Merge adopted attributes into existing extension object XML ---
|
||||
def merge_attributes_into_object(type_name, obj_name, attrs_to_add):
|
||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
||||
if not os.path.isfile(obj_file):
|
||||
warn(f"Cannot merge attributes: {obj_file} not found")
|
||||
return
|
||||
|
||||
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
||||
obj_content = fh.read()
|
||||
|
||||
# Collect existing attribute names for dedup (text-based)
|
||||
existing_names = set()
|
||||
for m in re.finditer(r'<Name>(\w+)</Name>', obj_content):
|
||||
existing_names.add(m.group(1))
|
||||
|
||||
all_attr_xml = ""
|
||||
added = 0
|
||||
for attr in attrs_to_add:
|
||||
if attr["Name"] in existing_names:
|
||||
continue
|
||||
all_attr_xml += "\r\n" + build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t")
|
||||
added += 1
|
||||
|
||||
if added > 0:
|
||||
# Insert attributes — handle both <ChildObjects/> and <ChildObjects>...</ChildObjects>
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>{all_attr_xml}\r\n\t\t</ChildObjects>", obj_content)
|
||||
else:
|
||||
obj_content = obj_content.replace("</ChildObjects>", f"{all_attr_xml}\r\n\t\t</ChildObjects>")
|
||||
save_text_bom(obj_file, obj_content)
|
||||
info(f" Merged {added} attribute(s) into: {obj_file}")
|
||||
|
||||
# --- 11h. Borrow main attribute orchestrator ---
|
||||
def borrow_main_attribute(type_name, obj_name, form_name, mode):
|
||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||
info(f"Borrowing main attribute for {type_name}.{obj_name} (mode: {mode})...")
|
||||
|
||||
# Step 1: Collect DataPaths (Form mode) or take all (All mode)
|
||||
first_level_names = None
|
||||
deep_paths = []
|
||||
if mode == "Form":
|
||||
src_form_xml_path = os.path.join(cfg_dir, dir_name, obj_name, "Forms", form_name, "Ext", "Form.xml")
|
||||
if not os.path.isfile(src_form_xml_path):
|
||||
print(f"Source Form.xml not found: {src_form_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
dp = collect_form_data_paths(src_form_xml_path)
|
||||
first_level_names = dp["FirstLevel"]
|
||||
deep_paths = dp["DeepPaths"]
|
||||
info(f" Collected {len(first_level_names)} first-level DataPath references, {len(deep_paths)} deep paths")
|
||||
else:
|
||||
info(" Mode All: borrowing all attributes and tabular sections")
|
||||
|
||||
# Step 2: Resolve source attributes
|
||||
resolved = resolve_source_attributes(type_name, obj_name, first_level_names)
|
||||
src_attrs = resolved["Attributes"]
|
||||
src_ts = resolved["TabularSections"]
|
||||
extra_props = resolved["ExtraProps"]
|
||||
info(f" Resolved: {len(src_attrs)} attributes, {len(src_ts)} tabular section(s)")
|
||||
|
||||
# Identify which FirstLevel names are TabularSections (for deep path filtering)
|
||||
ts_names = {ts["Name"]: True for ts in src_ts}
|
||||
|
||||
# Step 3: Build the adopted content and insert into main object XML
|
||||
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
|
||||
|
||||
# Generate full object XML with attributes and TS
|
||||
content_parts = []
|
||||
for attr in src_attrs:
|
||||
attr_xml = build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t")
|
||||
content_parts.append(attr_xml)
|
||||
for ts in src_ts:
|
||||
ts_xml = build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t")
|
||||
content_parts.append(ts_xml)
|
||||
adopted_content = "\n".join(content_parts).rstrip()
|
||||
|
||||
# Read existing object XML and inject
|
||||
with open(obj_file, "r", encoding="utf-8-sig") as fh:
|
||||
obj_content = fh.read()
|
||||
|
||||
# Inject extra properties after ExtendedConfigurationObject
|
||||
if extra_props:
|
||||
props_xml = ""
|
||||
for p_name, p_val in extra_props.items():
|
||||
props_xml += f"\r\n\t\t\t<{p_name}>{p_val}</{p_name}>"
|
||||
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}")
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
if adopted_content:
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>\r\n{adopted_content}\r\n\t\t</ChildObjects>", obj_content)
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elif re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content):
|
||||
m = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||
existing_inner = m.group(1)
|
||||
obj_content = obj_content.replace(
|
||||
f"<ChildObjects>{existing_inner}</ChildObjects>",
|
||||
f"<ChildObjects>{existing_inner}\r\n{adopted_content}\r\n\t\t</ChildObjects>"
|
||||
)
|
||||
|
||||
save_text_bom(obj_file, obj_content)
|
||||
info(f" Enriched object: {obj_file}")
|
||||
|
||||
# Step 4: Collect all reference types and borrow as shells
|
||||
all_type_xmls = []
|
||||
for a in src_attrs:
|
||||
all_type_xmls.append(a["TypeXml"])
|
||||
for ts in src_ts:
|
||||
for tsa in ts["Attributes"]:
|
||||
all_type_xmls.append(tsa["TypeXml"])
|
||||
ref_types = collect_reference_types(all_type_xmls)
|
||||
info(f" Reference types to borrow: {len(ref_types)}")
|
||||
|
||||
for rt in ref_types:
|
||||
if rt["TypeName"] not in CHILD_TYPE_DIR_MAP:
|
||||
warn(f" Unknown reference type: {rt['TypeName']}.{rt['ObjName']}")
|
||||
continue
|
||||
if test_object_borrowed(rt["TypeName"], rt["ObjName"]):
|
||||
info(f" Already borrowed: {rt['TypeName']}.{rt['ObjName']}")
|
||||
continue
|
||||
rt_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]], f"{rt['ObjName']}.xml")
|
||||
if not os.path.isfile(rt_src_file):
|
||||
warn(f" Source not found: {rt['TypeName']}.{rt['ObjName']}")
|
||||
continue
|
||||
src = read_source_object(rt["TypeName"], rt["ObjName"])
|
||||
borrowed_xml = build_borrowed_object_xml(rt["TypeName"], rt["ObjName"], src["Uuid"], src["Properties"])
|
||||
target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]])
|
||||
os.makedirs(target_dir, exist_ok=True)
|
||||
target_file = os.path.join(target_dir, f"{rt['ObjName']}.xml")
|
||||
save_text_bom(target_file, borrowed_xml)
|
||||
add_to_child_objects(rt["TypeName"], rt["ObjName"])
|
||||
borrowed_files.append(target_file)
|
||||
info(f" Auto-borrowed: {rt['TypeName']}.{rt['ObjName']}")
|
||||
|
||||
# Step 5: Handle deep paths (Form mode only)
|
||||
if mode == "Form" and deep_paths:
|
||||
# Filter out deep paths where ObjectAttr is a TabularSection
|
||||
real_deep = [dp for dp in deep_paths if dp["ObjectAttr"] not in ts_names]
|
||||
|
||||
if real_deep:
|
||||
info(f" Processing {len(real_deep)} deep path(s)...")
|
||||
|
||||
# Group by ObjectAttr -> target catalog
|
||||
deep_by_attr = {}
|
||||
for dp in real_deep:
|
||||
if dp["ObjectAttr"] not in deep_by_attr:
|
||||
deep_by_attr[dp["ObjectAttr"]] = []
|
||||
deep_by_attr[dp["ObjectAttr"]].append(dp["SubAttr"])
|
||||
|
||||
for attr_name, sub_attr_names in deep_by_attr.items():
|
||||
# Find the attribute's type to determine target catalog
|
||||
attr_info = None
|
||||
for a in src_attrs:
|
||||
if a["Name"] == attr_name:
|
||||
attr_info = a
|
||||
break
|
||||
if not attr_info:
|
||||
continue
|
||||
|
||||
# Extract catalog name from type: cfg:CatalogRef.XXX
|
||||
cat_match = re.search(r'cfg:(\w+)Ref\.(\w+)', attr_info["TypeXml"])
|
||||
if not cat_match:
|
||||
continue
|
||||
|
||||
target_type_name = cat_match.group(1)
|
||||
target_obj_name = cat_match.group(2)
|
||||
|
||||
# Ensure target is borrowed
|
||||
if not test_object_borrowed(target_type_name, target_obj_name):
|
||||
t_src = read_source_object(target_type_name, target_obj_name)
|
||||
t_borrowed_xml = build_borrowed_object_xml(target_type_name, target_obj_name, t_src["Uuid"], t_src["Properties"])
|
||||
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
|
||||
os.makedirs(t_target_dir, exist_ok=True)
|
||||
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
|
||||
save_text_bom(t_target_file, t_borrowed_xml)
|
||||
add_to_child_objects(target_type_name, target_obj_name)
|
||||
borrowed_files.append(t_target_file)
|
||||
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
|
||||
|
||||
# Resolve sub-attributes in target catalog
|
||||
sub_names = {sn: True for sn in sub_attr_names}
|
||||
sub_resolved = resolve_source_attributes(target_type_name, target_obj_name, sub_names)
|
||||
|
||||
if sub_resolved["Attributes"]:
|
||||
merge_attributes_into_object(target_type_name, target_obj_name, sub_resolved["Attributes"])
|
||||
|
||||
# Collect and borrow ref types from deep attributes
|
||||
sub_type_xmls = [sa["TypeXml"] for sa in sub_resolved["Attributes"]]
|
||||
sub_ref_types = collect_reference_types(sub_type_xmls)
|
||||
for srt in sub_ref_types:
|
||||
if srt["TypeName"] not in CHILD_TYPE_DIR_MAP:
|
||||
continue
|
||||
if test_object_borrowed(srt["TypeName"], srt["ObjName"]):
|
||||
continue
|
||||
s_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]], f"{srt['ObjName']}.xml")
|
||||
if not os.path.isfile(s_src_file):
|
||||
continue
|
||||
s_src = read_source_object(srt["TypeName"], srt["ObjName"])
|
||||
s_borrowed_xml = build_borrowed_object_xml(srt["TypeName"], srt["ObjName"], s_src["Uuid"], s_src["Properties"])
|
||||
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
|
||||
os.makedirs(s_target_dir, exist_ok=True)
|
||||
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
|
||||
save_text_bom(s_target_file, s_borrowed_xml)
|
||||
add_to_child_objects(srt["TypeName"], srt["ObjName"])
|
||||
borrowed_files.append(s_target_file)
|
||||
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
|
||||
|
||||
info(" Main attribute borrowing complete")
|
||||
|
||||
def borrow_form(type_name, obj_name, form_name, borrow_main_attr=False):
|
||||
dir_name = CHILD_TYPE_DIR_MAP[type_name]
|
||||
|
||||
# 1. Read source form UUID
|
||||
@@ -671,7 +1133,7 @@ def main():
|
||||
|
||||
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||
|
||||
# AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false
|
||||
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
|
||||
auto_cmd_xml = ""
|
||||
if src_auto_cmd is not None:
|
||||
auto_cmd_xml = etree.tostring(src_auto_cmd, encoding="unicode")
|
||||
@@ -680,8 +1142,12 @@ def main():
|
||||
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
|
||||
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
||||
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
|
||||
# Strip DataPath in AutoCommandBar buttons (e.g. Объект.Ref — invalid in extension)
|
||||
auto_cmd_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', auto_cmd_xml)
|
||||
# Strip DataPath in AutoCommandBar buttons
|
||||
if borrow_main_attr:
|
||||
# Keep only Объект.* DataPaths
|
||||
auto_cmd_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', auto_cmd_xml)
|
||||
else:
|
||||
auto_cmd_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', auto_cmd_xml)
|
||||
|
||||
# ChildItems: copy full tree, clean up base-config references
|
||||
child_items_xml = ""
|
||||
@@ -696,12 +1162,16 @@ def main():
|
||||
child_items_xml = ns_strip_pattern.sub("", child_items_xml)
|
||||
# Replace all CommandName values with 0
|
||||
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
|
||||
# Strip DataPath
|
||||
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
|
||||
# Strip TitleDataPath
|
||||
child_items_xml = re.sub(r'\s*<TitleDataPath>[^<]*</TitleDataPath>', '', child_items_xml)
|
||||
# Strip RowPictureDataPath (e.g. Список.СостояниеДокумента — invalid in extension)
|
||||
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
|
||||
# Strip DataPath / TitleDataPath / RowPictureDataPath
|
||||
if borrow_main_attr:
|
||||
# Keep only Объект.* DataPaths — strip form-attribute DataPaths (not borrowed)
|
||||
child_items_xml = re.sub(r'\s*<DataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</DataPath>', '', child_items_xml)
|
||||
child_items_xml = re.sub(r'\s*<TitleDataPath>(?!\u041e\u0431\u044a\u0435\u043a\u0442\.)[^<]*</TitleDataPath>', '', child_items_xml)
|
||||
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
|
||||
else:
|
||||
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
|
||||
child_items_xml = re.sub(r'\s*<TitleDataPath>[^<]*</TitleDataPath>', '', child_items_xml)
|
||||
child_items_xml = re.sub(r'\s*<RowPictureDataPath>[^<]*</RowPictureDataPath>', '', child_items_xml)
|
||||
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
|
||||
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
|
||||
# Strip TypeLink blocks with human-readable DataPath (Items.XXX)
|
||||
@@ -709,10 +1179,15 @@ def main():
|
||||
# Strip element-level Events
|
||||
child_items_xml = re.sub(r'\s*<Events>.*?</Events>', '', child_items_xml, flags=re.DOTALL)
|
||||
|
||||
# Auto-borrow referenced CommonPictures
|
||||
pic_refs = re.findall(r'<xr:Ref>CommonPicture\.(\w+)</xr:Ref>', child_items_xml)
|
||||
referenced_pictures = {name: True for name in pic_refs}
|
||||
# Collect CommonPicture references from ChildItems and AutoCommandBar
|
||||
referenced_pictures = {}
|
||||
for name in re.findall(r'<xr:Ref>CommonPicture\.(\w+)</xr:Ref>', child_items_xml):
|
||||
referenced_pictures[name] = True
|
||||
if auto_cmd_xml:
|
||||
for name in re.findall(r'<xr:Ref>CommonPicture\.(\w+)</xr:Ref>', auto_cmd_xml):
|
||||
referenced_pictures[name] = True
|
||||
|
||||
# Auto-borrow referenced CommonPictures
|
||||
auto_borrowed_pics = []
|
||||
for pic_name in referenced_pictures:
|
||||
if not test_object_borrowed("CommonPicture", pic_name):
|
||||
@@ -726,6 +1201,7 @@ def main():
|
||||
save_text_bom(target_file, borrowed_xml)
|
||||
add_to_child_objects("CommonPicture", pic_name)
|
||||
auto_borrowed_pics.append(pic_name)
|
||||
borrowed_files.append(target_file)
|
||||
info(f" Auto-borrowed: CommonPicture.{pic_name}")
|
||||
else:
|
||||
warn(f" CommonPicture.{pic_name} not found in source config — will strip from form")
|
||||
@@ -746,6 +1222,15 @@ def main():
|
||||
# Strip StdPicture blocks (except Print)
|
||||
child_items_xml = re.sub(r'\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '', child_items_xml, flags=re.DOTALL)
|
||||
|
||||
# Same Picture strip for AutoCommandBar
|
||||
if auto_cmd_xml:
|
||||
ac_pic_matches = list(pic_block_pattern.finditer(auto_cmd_xml))
|
||||
for pm in reversed(ac_pic_matches):
|
||||
cp_name = pm.group(1)
|
||||
if cp_name not in borrowed_pic_set:
|
||||
auto_cmd_xml = auto_cmd_xml[:pm.start()] + auto_cmd_xml[pm.end():]
|
||||
auto_cmd_xml = re.sub(r'\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '', auto_cmd_xml, flags=re.DOTALL)
|
||||
|
||||
# Auto-borrow StyleItems referenced in ChildItems
|
||||
referenced_styles = set()
|
||||
for m in re.finditer(r'ref="style:(\w+)"[^>]*kind="StyleItem"', child_items_xml):
|
||||
@@ -764,6 +1249,7 @@ def main():
|
||||
target_file = os.path.join(target_dir, f"{style_name}.xml")
|
||||
save_text_bom(target_file, borrowed_xml)
|
||||
add_to_child_objects("StyleItem", style_name)
|
||||
borrowed_files.append(target_file)
|
||||
info(f" Auto-borrowed: StyleItem.{style_name}")
|
||||
else:
|
||||
warn(f" StyleItem.{style_name} not found in source config")
|
||||
@@ -827,6 +1313,7 @@ def main():
|
||||
target_file = os.path.join(target_dir, f"{enum_name}.xml")
|
||||
save_text_bom(target_file, borrowed_xml)
|
||||
add_to_child_objects("Enum", enum_name)
|
||||
borrowed_files.append(target_file)
|
||||
info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))")
|
||||
else:
|
||||
warn(f" Enum.{enum_name} not found in source config")
|
||||
@@ -856,7 +1343,26 @@ def main():
|
||||
parts.append(f"\t{auto_cmd_xml}\r\n")
|
||||
if child_items_xml:
|
||||
parts.append(f"\t{child_items_xml}\r\n")
|
||||
parts.append("\t<Attributes/>\r\n")
|
||||
|
||||
# Attributes: empty or with MainAttribute when borrow_main_attr
|
||||
if borrow_main_attr:
|
||||
obj_type_prefix = ""
|
||||
gt_list = GENERATED_TYPES.get(type_name, [])
|
||||
for g in gt_list:
|
||||
if g["category"] == "Object":
|
||||
obj_type_prefix = g["prefix"]
|
||||
break
|
||||
main_attr_type = f"cfg:{obj_type_prefix}.{obj_name}"
|
||||
parts.append("\t<Attributes>\r\n")
|
||||
parts.append('\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1000001">\r\n')
|
||||
parts.append(f"\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
|
||||
parts.append("\t\t\t<MainAttribute>true</MainAttribute>\r\n")
|
||||
parts.append("\t\t\t<SavedData>true</SavedData>\r\n")
|
||||
parts.append("\t\t</Attribute>\r\n")
|
||||
parts.append("\t</Attributes>")
|
||||
else:
|
||||
parts.append("\t<Attributes/>")
|
||||
parts.append("\r\n")
|
||||
|
||||
# BaseForm: same content, indented one more level
|
||||
parts.append(f'\t<BaseForm version="{form_version}">\r\n')
|
||||
@@ -881,7 +1387,18 @@ def main():
|
||||
parts.append(f"\t{line}")
|
||||
parts.append("\r\n")
|
||||
|
||||
parts.append("\t\t<Attributes/>\r\n")
|
||||
# BaseForm Attributes: same as main section
|
||||
if borrow_main_attr:
|
||||
parts.append("\t\t<Attributes>\r\n")
|
||||
parts.append('\t\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1000001">\r\n')
|
||||
parts.append(f"\t\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
|
||||
parts.append("\t\t\t\t<MainAttribute>true</MainAttribute>\r\n")
|
||||
parts.append("\t\t\t\t<SavedData>true</SavedData>\r\n")
|
||||
parts.append("\t\t\t</Attribute>\r\n")
|
||||
parts.append("\t\t</Attributes>")
|
||||
else:
|
||||
parts.append("\t\t<Attributes/>")
|
||||
parts.append("\r\n")
|
||||
parts.append("\t</BaseForm>\r\n")
|
||||
parts.append("</Form>")
|
||||
|
||||
@@ -914,8 +1431,19 @@ def main():
|
||||
print("No objects specified in -Object", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- 9b. Validate -BorrowMainAttribute ---
|
||||
borrow_main_attribute_mode = args.BorrowMainAttribute
|
||||
if borrow_main_attribute_mode is not None:
|
||||
if borrow_main_attribute_mode not in ("Form", "All"):
|
||||
print("-BorrowMainAttribute accepts 'Form' or 'All' (default: Form)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# Validate: only with .Form. pattern
|
||||
has_form = any(".Form." in item for item in items)
|
||||
if not has_form:
|
||||
print("-BorrowMainAttribute requires a form in -Object (e.g. 'Catalog.X.Form.Y')", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- 10. Process each item ---
|
||||
borrowed_files = []
|
||||
borrowed_count = 0
|
||||
|
||||
for item in items:
|
||||
@@ -963,9 +1491,14 @@ def main():
|
||||
add_to_child_objects(type_name, obj_name)
|
||||
borrowed_files.append(target_file)
|
||||
|
||||
form_files = borrow_form(type_name, obj_name, form_name)
|
||||
has_bma = borrow_main_attribute_mode is not None
|
||||
form_files = borrow_form(type_name, obj_name, form_name, borrow_main_attr=has_bma)
|
||||
borrowed_files.extend(form_files)
|
||||
borrowed_count += 1
|
||||
|
||||
# Borrow main attribute if requested
|
||||
if has_bma:
|
||||
borrow_main_attribute(type_name, obj_name, form_name, borrow_main_attribute_mode)
|
||||
else:
|
||||
# --- Object borrowing ---
|
||||
info(f"Borrowing {type_name}.{obj_name}...")
|
||||
|
||||
+65
-11
@@ -310,7 +310,47 @@ Enums/ # Перечисления
|
||||
|
||||
Собственный реквизит **не имеет** `ObjectBelonging` и `ExtendedConfigurationObject`. Содержит полный набор свойств.
|
||||
|
||||
### 5.3. Заимствованные значения перечислений
|
||||
### 5.3. Заимствование реквизитов объекта для работы с формой
|
||||
|
||||
Когда в расширении заимствуется форма объекта и требуется сохранить привязки к данным (`DataPath`), необходимо заимствовать реквизиты объекта, на которые форма ссылается. Это позволяет добавлять новые элементы на форму через расширение.
|
||||
|
||||
**Что заимствуется**:
|
||||
- Реквизиты объекта, на которые ссылается форма через `Объект.XXX` в `<DataPath>`
|
||||
- Табличные части целиком (все колонки), если форма обращается к `Объект.ТЧ`
|
||||
- Зависимые объекты по типам реквизитов (`CatalogRef.X` → заимствуется `Catalog.X` как оболочка)
|
||||
- Глубокие ссылки: `Объект.A.B` → реквизит B заимствуется внутри каталога A
|
||||
|
||||
**Обогащение Properties**: заимствованный объект с реквизитами содержит дополнительные свойства из источника (`Hierarchical`, `CodeLength`, `DescriptionLength`, `CodeType`, `CodeAllowedLength`), которые отсутствуют у простой оболочки.
|
||||
|
||||
**Заимствованная табличная часть** содержит `InternalInfo` с `GeneratedType` (категории `TabularSection` и `TabularSectionRow`):
|
||||
|
||||
```xml
|
||||
<TabularSection uuid="...">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="CatalogTabularSection.Номенклатура.ДрагоценныеМатериалы" category="TabularSection">
|
||||
<xr:TypeId>...</xr:TypeId>
|
||||
<xr:ValueId>...</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="CatalogTabularSectionRow.Номенклатура.ДрагоценныеМатериалы" category="TabularSectionRow">
|
||||
<xr:TypeId>...</xr:TypeId>
|
||||
<xr:ValueId>...</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<ObjectBelonging>Adopted</ObjectBelonging>
|
||||
<Name>ДрагоценныеМатериалы</Name>
|
||||
<Comment/>
|
||||
<ExtendedConfigurationObject>...</ExtendedConfigurationObject>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<!-- все реквизиты ТЧ — adopted -->
|
||||
</ChildObjects>
|
||||
</TabularSection>
|
||||
```
|
||||
|
||||
**Типы в реквизитах**: `v8:Type` для обычных типов (`cfg:CatalogRef.X`, `cfg:EnumRef.X`), `v8:TypeSet` для определяемых типов (`cfg:DefinedType.X`).
|
||||
|
||||
### 5.4. Заимствованные значения перечислений
|
||||
|
||||
```xml
|
||||
<EnumValue uuid="9bc7380f-...">
|
||||
@@ -324,7 +364,7 @@ Enums/ # Перечисления
|
||||
</EnumValue>
|
||||
```
|
||||
|
||||
### 5.4. Формы в расширениях
|
||||
### 5.5. Формы в расширениях
|
||||
|
||||
В расширении существуют **два принципиально разных сценария** работы с формами:
|
||||
|
||||
@@ -335,7 +375,7 @@ Enums/ # Перечисления
|
||||
|
||||
> **Как отличить:** Если файл метаданных формы (`.xml`) содержит `<ObjectBelonging>Adopted</ObjectBelonging>` — это заимствованная форма. Собственные формы не имеют `ObjectBelonging`.
|
||||
|
||||
#### 5.4.1. Метаданные заимствованной формы
|
||||
#### 5.5.1. Метаданные заимствованной формы
|
||||
|
||||
Файл `.xml` в каталоге `Forms/`:
|
||||
|
||||
@@ -354,7 +394,7 @@ Enums/ # Перечисления
|
||||
|
||||
Содержимое формы хранится в `Forms/ФормаСписка/Ext/Form.xml`, модуль формы — в `Forms/ФормаСписка/Ext/Form/Module.bsl`.
|
||||
|
||||
#### 5.4.2. Структура Form.xml заимствованной формы
|
||||
#### 5.5.2. Структура Form.xml заимствованной формы
|
||||
|
||||
Form.xml заимствованной формы — **двухчастный файл**: Part 1 (результирующая форма) и BaseForm (исходная форма). Существуют **два варианта** в зависимости от наличия модификаций модуля формы.
|
||||
|
||||
@@ -415,13 +455,27 @@ Form.xml заимствованной формы — **двухчастный ф
|
||||
|
||||
2. **AutoCommandBar** — присутствует всегда с `id="-1"`, но без `<ChildItems>` (кнопки удаляются). `<Autofill>` = `false`.
|
||||
|
||||
3. **Attributes** — пустой `<Attributes/>` в обеих секциях. Атрибуты базовой конфигурации **не включаются**. Реквизиты расширения (id ≥ 1000000) добавляются только в Part 1.
|
||||
3. **Attributes** — по умолчанию пустой `<Attributes/>` в обеих секциях. Атрибуты базовой конфигурации **не включаются**. Реквизиты расширения (id ≥ 1000000) добавляются только в Part 1.
|
||||
|
||||
**Вариант с заимствованным основным реквизитом**: если реквизиты объекта заимствованы (см. раздел 5.3), секция `<Attributes>` содержит основной реквизит формы:
|
||||
```xml
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1000001">
|
||||
<Type><v8:Type>cfg:CatalogObject.Номенклатура</v8:Type></Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
```
|
||||
Присутствует в обеих секциях (Part 1 и BaseForm). Тип зависит от родительского объекта: `CatalogObject`, `DocumentObject` и т.д.
|
||||
|
||||
4. **BaseForm** — последний элемент в `<Form>`, атрибут `version`. В BaseForm **нет** Events, Commands, Parameters.
|
||||
|
||||
5. **DataPath: удаление** (вариант B) — все `<DataPath>` из базовых элементов удаляются в обеих секциях. DataPath ссылается на реквизиты, не включённые в расширение.
|
||||
5. **DataPath** — два варианта в зависимости от наличия заимствованного основного реквизита:
|
||||
- **Без основного реквизита**: все `<DataPath>` удаляются в обеих секциях (ссылаются на реквизиты, не включённые в расширение).
|
||||
- **С основным реквизитом**: `<DataPath>`, начинающиеся с `Объект.` **сохраняются** (реквизиты заимствованы и привязки валидны). DataPath формовых реквизитов (не начинающиеся с `Объект.`) — удаляются.
|
||||
|
||||
6. **TitleDataPath: удаление** (вариант B) — все `<TitleDataPath>` удаляются (напр. `Объект.Товары.RowsCount` — путь недействителен без базовых атрибутов).
|
||||
6. **TitleDataPath** — аналогично DataPath: удаляются без основного реквизита, `Объект.*` сохраняются с ним.
|
||||
|
||||
7. **TypeLink: удаление** (вариант B) — блоки `<TypeLink>` с `<xr:DataPath>Items.*</xr:DataPath>` удаляются (человекочитаемые пути, которые нельзя преобразовать в UUID-формат Конфигуратора).
|
||||
|
||||
@@ -435,7 +489,7 @@ Form.xml заимствованной формы — **двухчастный ф
|
||||
|
||||
12. **Авто-заимствование Enums + EnumValues** — `<ChoiceParameters>` могут содержать `<Value xsi:type="xr:DesignTimeRef">Enum.XXX.EnumValue.YYY</Value>`. Перечисление `Enum.XXX` заимствуется вместе с конкретными `EnumValue` (borrowed с `ExtendedConfigurationObject` указывающим на UUID оригинального значения).
|
||||
|
||||
#### 5.4.3. Нумерация ID элементов
|
||||
#### 5.5.3. Нумерация ID элементов
|
||||
|
||||
| Диапазон | Принадлежность |
|
||||
|----------|---------------|
|
||||
@@ -445,7 +499,7 @@ Form.xml заимствованной формы — **двухчастный ф
|
||||
|
||||
> **Важно:** Визуальные элементы форм (элементы в `ChildItems`), добавленные расширением в тело базовой формы, могут использовать ID из обычного диапазона (продолжая нумерацию базовой формы). Диапазон 1000000+ гарантирован для `Attributes` и `Commands`.
|
||||
|
||||
#### 5.4.4. Атрибут callType — перехват событий и команд
|
||||
#### 5.5.4. Атрибут callType — перехват событий и команд
|
||||
|
||||
В заимствованных формах события и действия команд используют атрибут `callType` для определения момента перехвата:
|
||||
|
||||
@@ -509,7 +563,7 @@ Form.xml заимствованной формы — **двухчастный ф
|
||||
|
||||
> **Отличие от обычной формы:** В обычной форме (конфигурации или собственной форме расширения) у `<Event>` и `<Action>` **нет** атрибута `callType` — обработчик вызывается напрямую.
|
||||
|
||||
#### 5.4.5. Собственная форма на заимствованном объекте
|
||||
#### 5.5.5. Собственная форма на заимствованном объекте
|
||||
|
||||
Расширение может добавить к заимствованному объекту **собственную форму**, не существующую в базовой конфигурации. Такая форма:
|
||||
|
||||
@@ -543,7 +597,7 @@ Form.xml заимствованной формы — **двухчастный ф
|
||||
</Form>
|
||||
```
|
||||
|
||||
#### 5.4.6. Модуль заимствованной формы
|
||||
#### 5.5.6. Модуль заимствованной формы
|
||||
|
||||
Модуль формы (`Forms/Имя/Ext/Form/Module.bsl`) в заимствованной форме использует те же декораторы перехвата, что и другие модули расширений (см. раздел 7.2):
|
||||
|
||||
|
||||
+28
-1
@@ -7,7 +7,7 @@
|
||||
| Навык | Параметры | Описание |
|
||||
|-------|-----------|----------|
|
||||
| `/cfe-init` | `<Name> [-Purpose Patch\|Customization\|AddOn] [-CompatibilityMode]` | Создание расширения (scaffold XML-исходников) |
|
||||
| `/cfe-borrow` | `-ExtensionPath <path> -ConfigPath <path> -Object "Type.Name"` | Заимствование объектов из конфигурации |
|
||||
| `/cfe-borrow` | `-ExtensionPath <path> -ConfigPath <path> -Object "Type.Name" [-BorrowMainAttribute]` | Заимствование объектов из конфигурации |
|
||||
| `/cfe-patch-method` | `-ExtensionPath <path> -ModulePath "Type.Name.Module" -MethodName "X" -InterceptorType Before` | Генерация перехватчика метода |
|
||||
| `/cfe-validate` | `<ExtensionPath> [-MaxErrors 30]` | Валидация структурной корректности (9 проверок) |
|
||||
| `/cfe-diff` | `-ExtensionPath <path> -ConfigPath <path> [-Mode A\|B]` | Анализ расширения и проверка переноса |
|
||||
@@ -44,6 +44,21 @@ Claude выполнит:
|
||||
4. `/cfe-patch-method` — создать перехватчик нужного метода
|
||||
5. `/cfe-validate` — проверить результат
|
||||
|
||||
### Добавление реквизита в объект и вывод на форму
|
||||
|
||||
```
|
||||
> Добавь реквизит "ОсновнойПоставщик" (тип СправочникСсылка.Партнеры)
|
||||
в справочник Номенклатура и выведи на форму элемента.
|
||||
Конфигурация ERP в C:\cfsrc\erp
|
||||
```
|
||||
|
||||
Claude выполнит:
|
||||
1. `/cfe-init` — создать расширение
|
||||
2. `/cfe-borrow -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute` — заимствовать форму с реквизитами объекта
|
||||
3. `/meta-edit` — добавить новый реквизит `Расш1_ОсновнойПоставщик` в Номенклатура
|
||||
4. `/form-edit` — вывести реквизит на форму
|
||||
5. `/cfe-validate` — проверить результат
|
||||
|
||||
### Анализ существующего расширения
|
||||
|
||||
```
|
||||
@@ -103,6 +118,18 @@ Claude вызовет `/cfe-diff -Mode B` — найдёт блоки `#Вста
|
||||
|
||||
Поддерживаемые типы: Catalog, Document, Enum, CommonModule, Report, DataProcessor, ExchangePlan, InformationRegister, AccumulationRegister, AccountingRegister, CalculationRegister, ChartOfAccounts, ChartOfCharacteristicTypes, ChartOfCalculationTypes, BusinessProcess, Task, и другие (44 типа).
|
||||
|
||||
### Заимствование формы с реквизитами объекта (-BorrowMainAttribute)
|
||||
|
||||
При добавлении нового реквизита на заимствованную форму нужна опция `-BorrowMainAttribute`:
|
||||
- Без неё форма заимствуется "пустой" — только визуальные элементы, без привязки к данным
|
||||
- С ней — форма сохраняет `DataPath` привязки к реквизитам объекта (`Объект.XXX`)
|
||||
|
||||
Два режима:
|
||||
- `Form` (по умолчанию) — заимствует только реквизиты, выведенные на форму
|
||||
- `All` — заимствует все реквизиты объекта (включая не выведенные на форму)
|
||||
|
||||
Каскадно заимствует зависимые объекты по типам реквизитов (справочники, перечисления, определяемые типы) как оболочки. Если зависимый объект уже заимствован с содержимым — не перезаписывает его.
|
||||
|
||||
## cfe-patch-method — перехват методов
|
||||
|
||||
Генерирует `.bsl` файл с декоратором перехвата для заимствованного объекта.
|
||||
|
||||
Reference in New Issue
Block a user