mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-19 09:09:41 +03:00
d155086444
When working with existing configs dumped from newer platforms (8.3.27+), XML files use version="2.20" instead of "2.17". Skills now detect the version from the nearest Configuration.xml walking up the directory tree, falling back to "2.17" if not found. This prevents format version mismatch errors during LoadConfigFromFiles. Updated skills (11): meta-compile, form-compile, form-add, template-add, cfe-borrow, epf-add-form, help-add, role-compile, subsystem-compile, interface-edit. Also fixed form-validate to accept version 2.20. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1773 lines
72 KiB
PowerShell
1773 lines
72 KiB
PowerShell
# cfe-borrow v1.3 — 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,
|
|
[string]$BorrowMainAttribute
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
function Info([string]$msg) { Write-Host "[INFO] $msg" }
|
|
function Warn([string]$msg) { Write-Host "[WARN] $msg" }
|
|
|
|
# --- 1. Resolve paths ---
|
|
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
|
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
|
}
|
|
if (Test-Path $ExtensionPath -PathType Container) {
|
|
$candidate = Join-Path $ExtensionPath "Configuration.xml"
|
|
if (Test-Path $candidate) { $ExtensionPath = $candidate }
|
|
else { Write-Error "No Configuration.xml in extension directory: $ExtensionPath"; exit 1 }
|
|
}
|
|
if (-not (Test-Path $ExtensionPath)) { Write-Error "Extension file not found: $ExtensionPath"; exit 1 }
|
|
$extResolvedPath = (Resolve-Path $ExtensionPath).Path
|
|
$extDir = Split-Path $extResolvedPath -Parent
|
|
|
|
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
|
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
|
}
|
|
if (Test-Path $ConfigPath -PathType Container) {
|
|
$candidate = Join-Path $ConfigPath "Configuration.xml"
|
|
if (Test-Path $candidate) { $ConfigPath = $candidate }
|
|
else { Write-Error "No Configuration.xml in config directory: $ConfigPath"; exit 1 }
|
|
}
|
|
if (-not (Test-Path $ConfigPath)) { Write-Error "Config file not found: $ConfigPath"; exit 1 }
|
|
$cfgResolvedPath = (Resolve-Path $ConfigPath).Path
|
|
$cfgDir = Split-Path $cfgResolvedPath -Parent
|
|
|
|
# --- 2. Load extension Configuration.xml ---
|
|
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
|
$script:xmlDoc.PreserveWhitespace = $true
|
|
$script:xmlDoc.Load($extResolvedPath)
|
|
|
|
$script:mdNs = "http://v8.1c.ru/8.3/MDClasses"
|
|
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
|
|
$script:xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
|
|
$script:v8Ns = "http://v8.1c.ru/8.1/data/core"
|
|
|
|
$root = $script:xmlDoc.DocumentElement
|
|
|
|
$script:cfgEl = $null
|
|
foreach ($child in $root.ChildNodes) {
|
|
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Configuration") {
|
|
$script:cfgEl = $child; break
|
|
}
|
|
}
|
|
if (-not $script:cfgEl) { Write-Error "No <Configuration> element found in extension"; exit 1 }
|
|
|
|
$script:propsEl = $null
|
|
$script:childObjsEl = $null
|
|
foreach ($child in $script:cfgEl.ChildNodes) {
|
|
if ($child.NodeType -ne 'Element') { continue }
|
|
if ($child.LocalName -eq "Properties") { $script:propsEl = $child }
|
|
if ($child.LocalName -eq "ChildObjects") { $script:childObjsEl = $child }
|
|
}
|
|
|
|
if (-not $script:propsEl) { Write-Error "No <Properties> element found in extension"; exit 1 }
|
|
if (-not $script:childObjsEl) { Write-Error "No <ChildObjects> element found in extension"; exit 1 }
|
|
|
|
# --- 3. Extract NamePrefix ---
|
|
$script:namePrefix = ""
|
|
foreach ($child in $script:propsEl.ChildNodes) {
|
|
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "NamePrefix") {
|
|
$script:namePrefix = $child.InnerText.Trim(); break
|
|
}
|
|
}
|
|
Info "Extension NamePrefix: $($script:namePrefix)"
|
|
|
|
# --- 4. Type mappings ---
|
|
$childTypeDirMap = @{
|
|
"Catalog"="Catalogs"; "Document"="Documents"; "Enum"="Enums"
|
|
"CommonModule"="CommonModules"; "CommonPicture"="CommonPictures"
|
|
"CommonCommand"="CommonCommands"; "CommonTemplate"="CommonTemplates"
|
|
"ExchangePlan"="ExchangePlans"; "Report"="Reports"; "DataProcessor"="DataProcessors"
|
|
"InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
|
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"
|
|
"ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
|
|
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
|
|
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"
|
|
"Subsystem"="Subsystems"; "Role"="Roles"; "Constant"="Constants"
|
|
"FunctionalOption"="FunctionalOptions"; "DefinedType"="DefinedTypes"
|
|
"FunctionalOptionsParameter"="FunctionalOptionsParameters"
|
|
"CommonForm"="CommonForms"; "DocumentJournal"="DocumentJournals"
|
|
"SessionParameter"="SessionParameters"; "StyleItem"="StyleItems"
|
|
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"
|
|
"SettingsStorage"="SettingsStorages"; "FilterCriterion"="FilterCriteria"
|
|
"CommandGroup"="CommandGroups"; "DocumentNumerator"="DocumentNumerators"
|
|
"Sequence"="Sequences"; "IntegrationService"="IntegrationServices"
|
|
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"
|
|
"HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
|
"CommonAttribute"="CommonAttributes"; "Style"="Styles"
|
|
}
|
|
|
|
# --- 4b. Russian synonym → English type ---
|
|
$synonymMap = @{
|
|
"Справочник"="Catalog"; "Документ"="Document"; "Перечисление"="Enum"
|
|
"ОбщийМодуль"="CommonModule"; "ОбщаяКартинка"="CommonPicture"
|
|
"ОбщаяКоманда"="CommonCommand"; "ОбщийМакет"="CommonTemplate"
|
|
"ПланОбмена"="ExchangePlan"; "Отчет"="Report"; "Отчёт"="Report"
|
|
"Обработка"="DataProcessor"; "РегистрСведений"="InformationRegister"
|
|
"РегистрНакопления"="AccumulationRegister"
|
|
"ПланВидовХарактеристик"="ChartOfCharacteristicTypes"
|
|
"ПланСчетов"="ChartOfAccounts"; "РегистрБухгалтерии"="AccountingRegister"
|
|
"ПланВидовРасчета"="ChartOfCalculationTypes"; "РегистрРасчета"="CalculationRegister"
|
|
"БизнесПроцесс"="BusinessProcess"; "Задача"="Task"
|
|
"Подсистема"="Subsystem"; "Роль"="Role"; "Константа"="Constant"
|
|
"ФункциональнаяОпция"="FunctionalOption"; "ОпределяемыйТип"="DefinedType"
|
|
"ОбщаяФорма"="CommonForm"; "ЖурналДокументов"="DocumentJournal"
|
|
"ПараметрСеанса"="SessionParameter"; "ГруппаКоманд"="CommandGroup"
|
|
"ПодпискаНаСобытие"="EventSubscription"; "РегламентноеЗадание"="ScheduledJob"
|
|
"ОбщийРеквизит"="CommonAttribute"; "ПакетXDTO"="XDTOPackage"
|
|
"HTTPСервис"="HTTPService"; "СервисИнтеграции"="IntegrationService"
|
|
}
|
|
|
|
# --- 5. Canonical type order (44 types) ---
|
|
$script:typeOrder = @(
|
|
"Language","Subsystem","StyleItem","Style",
|
|
"CommonPicture","SessionParameter","Role","CommonTemplate",
|
|
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
|
|
"XDTOPackage","WebService","HTTPService","WSReference",
|
|
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
|
|
"FunctionalOptionsParameter","DefinedType","CommonCommand","CommandGroup",
|
|
"Constant","CommonForm","Catalog","Document",
|
|
"DocumentNumerator","Sequence","DocumentJournal","Enum",
|
|
"Report","DataProcessor","InformationRegister","AccumulationRegister",
|
|
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
|
|
"ChartOfCalculationTypes","CalculationRegister",
|
|
"BusinessProcess","Task","IntegrationService"
|
|
)
|
|
|
|
# --- 6. GeneratedType patterns per type ---
|
|
$script:generatedTypes = @{
|
|
"Catalog" = @(
|
|
@{ prefix = "CatalogObject"; category = "Object" }
|
|
@{ prefix = "CatalogRef"; category = "Ref" }
|
|
@{ prefix = "CatalogSelection"; category = "Selection" }
|
|
@{ prefix = "CatalogList"; category = "List" }
|
|
@{ prefix = "CatalogManager"; category = "Manager" }
|
|
)
|
|
"Document" = @(
|
|
@{ prefix = "DocumentObject"; category = "Object" }
|
|
@{ prefix = "DocumentRef"; category = "Ref" }
|
|
@{ prefix = "DocumentSelection"; category = "Selection" }
|
|
@{ prefix = "DocumentList"; category = "List" }
|
|
@{ prefix = "DocumentManager"; category = "Manager" }
|
|
)
|
|
"Enum" = @(
|
|
@{ prefix = "EnumRef"; category = "Ref" }
|
|
@{ prefix = "EnumManager"; category = "Manager" }
|
|
@{ prefix = "EnumList"; category = "List" }
|
|
)
|
|
"Constant" = @(
|
|
@{ prefix = "ConstantManager"; category = "Manager" }
|
|
@{ prefix = "ConstantValueManager"; category = "ValueManager" }
|
|
@{ prefix = "ConstantValueKey"; category = "ValueKey" }
|
|
)
|
|
"InformationRegister" = @(
|
|
@{ prefix = "InformationRegisterRecord"; category = "Record" }
|
|
@{ prefix = "InformationRegisterManager"; category = "Manager" }
|
|
@{ prefix = "InformationRegisterSelection"; category = "Selection" }
|
|
@{ prefix = "InformationRegisterList"; category = "List" }
|
|
@{ prefix = "InformationRegisterRecordSet"; category = "RecordSet" }
|
|
@{ prefix = "InformationRegisterRecordKey"; category = "RecordKey" }
|
|
@{ prefix = "InformationRegisterRecordManager"; category = "RecordManager" }
|
|
)
|
|
"AccumulationRegister" = @(
|
|
@{ prefix = "AccumulationRegisterRecord"; category = "Record" }
|
|
@{ prefix = "AccumulationRegisterManager"; category = "Manager" }
|
|
@{ prefix = "AccumulationRegisterSelection"; category = "Selection" }
|
|
@{ prefix = "AccumulationRegisterList"; category = "List" }
|
|
@{ prefix = "AccumulationRegisterRecordSet"; category = "RecordSet" }
|
|
@{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" }
|
|
)
|
|
"AccountingRegister" = @(
|
|
@{ prefix = "AccountingRegisterRecord"; category = "Record" }
|
|
@{ prefix = "AccountingRegisterManager"; category = "Manager" }
|
|
@{ prefix = "AccountingRegisterSelection"; category = "Selection" }
|
|
@{ prefix = "AccountingRegisterList"; category = "List" }
|
|
@{ prefix = "AccountingRegisterRecordSet"; category = "RecordSet" }
|
|
@{ prefix = "AccountingRegisterRecordKey"; category = "RecordKey" }
|
|
)
|
|
"CalculationRegister" = @(
|
|
@{ prefix = "CalculationRegisterRecord"; category = "Record" }
|
|
@{ prefix = "CalculationRegisterManager"; category = "Manager" }
|
|
@{ prefix = "CalculationRegisterSelection"; category = "Selection" }
|
|
@{ prefix = "CalculationRegisterList"; category = "List" }
|
|
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
|
|
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
|
|
)
|
|
"ChartOfAccounts" = @(
|
|
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
|
|
@{ prefix = "ChartOfAccountsRef"; category = "Ref" }
|
|
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
|
|
@{ prefix = "ChartOfAccountsList"; category = "List" }
|
|
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
|
|
)
|
|
"ChartOfCharacteristicTypes" = @(
|
|
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
|
|
@{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" }
|
|
@{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" }
|
|
@{ prefix = "ChartOfCharacteristicTypesList"; category = "List" }
|
|
@{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" }
|
|
)
|
|
"ChartOfCalculationTypes" = @(
|
|
@{ prefix = "ChartOfCalculationTypesObject"; category = "Object" }
|
|
@{ prefix = "ChartOfCalculationTypesRef"; category = "Ref" }
|
|
@{ prefix = "ChartOfCalculationTypesSelection"; category = "Selection" }
|
|
@{ prefix = "ChartOfCalculationTypesList"; category = "List" }
|
|
@{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" }
|
|
@{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" }
|
|
@{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" }
|
|
@{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" }
|
|
)
|
|
"BusinessProcess" = @(
|
|
@{ prefix = "BusinessProcessObject"; category = "Object" }
|
|
@{ prefix = "BusinessProcessRef"; category = "Ref" }
|
|
@{ prefix = "BusinessProcessSelection"; category = "Selection" }
|
|
@{ prefix = "BusinessProcessList"; category = "List" }
|
|
@{ prefix = "BusinessProcessManager"; category = "Manager" }
|
|
)
|
|
"Task" = @(
|
|
@{ prefix = "TaskObject"; category = "Object" }
|
|
@{ prefix = "TaskRef"; category = "Ref" }
|
|
@{ prefix = "TaskSelection"; category = "Selection" }
|
|
@{ prefix = "TaskList"; category = "List" }
|
|
@{ prefix = "TaskManager"; category = "Manager" }
|
|
)
|
|
"ExchangePlan" = @(
|
|
@{ prefix = "ExchangePlanObject"; category = "Object" }
|
|
@{ prefix = "ExchangePlanRef"; category = "Ref" }
|
|
@{ prefix = "ExchangePlanSelection"; category = "Selection" }
|
|
@{ prefix = "ExchangePlanList"; category = "List" }
|
|
@{ prefix = "ExchangePlanManager"; category = "Manager" }
|
|
)
|
|
"DocumentJournal" = @(
|
|
@{ prefix = "DocumentJournalSelection"; category = "Selection" }
|
|
@{ prefix = "DocumentJournalList"; category = "List" }
|
|
@{ prefix = "DocumentJournalManager"; category = "Manager" }
|
|
)
|
|
"Report" = @(
|
|
@{ prefix = "ReportObject"; category = "Object" }
|
|
@{ prefix = "ReportManager"; category = "Manager" }
|
|
)
|
|
"DataProcessor" = @(
|
|
@{ prefix = "DataProcessorObject"; category = "Object" }
|
|
@{ prefix = "DataProcessorManager"; category = "Manager" }
|
|
)
|
|
"DefinedType" = @(
|
|
@{ prefix = "DefinedType"; category = "DefinedType" }
|
|
)
|
|
}
|
|
|
|
# Types that need ChildObjects element
|
|
$typesWithChildObjects = @(
|
|
"Catalog","Document","ExchangePlan","ChartOfAccounts",
|
|
"ChartOfCharacteristicTypes","ChartOfCalculationTypes",
|
|
"BusinessProcess","Task","Enum",
|
|
"InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister"
|
|
)
|
|
|
|
# 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) {
|
|
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
|
|
if ($child.Value -match '^\r?\n(\t+)$') { return $Matches[1] }
|
|
if ($child.Value -match '^\r?\n(\t+)') { return $Matches[1] }
|
|
}
|
|
}
|
|
$depth = 0; $current = $container
|
|
while ($current -and $current -ne $script:xmlDoc.DocumentElement) { $depth++; $current = $current.ParentNode }
|
|
return "`t" * ($depth + 1)
|
|
}
|
|
|
|
function Insert-BeforeElement($container, $newNode, $refNode, $childIndent) {
|
|
$ws = $script:xmlDoc.CreateWhitespace("`r`n$childIndent")
|
|
if ($refNode) {
|
|
$container.InsertBefore($ws, $refNode) | Out-Null
|
|
$container.InsertBefore($newNode, $ws) | Out-Null
|
|
} else {
|
|
$trailing = $container.LastChild
|
|
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
|
$container.InsertBefore($ws, $trailing) | Out-Null
|
|
$container.InsertBefore($newNode, $trailing) | Out-Null
|
|
} else {
|
|
$container.AppendChild($ws) | Out-Null
|
|
$container.AppendChild($newNode) | Out-Null
|
|
$parentIndent = if ($childIndent.Length -gt 1) { $childIndent.Substring(0, $childIndent.Length - 1) } else { "" }
|
|
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
|
|
$container.AppendChild($closeWs) | Out-Null
|
|
}
|
|
}
|
|
}
|
|
|
|
function Expand-SelfClosingElement($container, $parentIndent) {
|
|
if (-not $container.HasChildNodes -or $container.IsEmpty) {
|
|
$closeWs = $script:xmlDoc.CreateWhitespace("`r`n$parentIndent")
|
|
$container.AppendChild($closeWs) | Out-Null
|
|
}
|
|
}
|
|
|
|
# --- 7b. Detect format version ---
|
|
|
|
function Detect-FormatVersion([string]$dir) {
|
|
$d = $dir
|
|
while ($d) {
|
|
$cfgPath = Join-Path $d "Configuration.xml"
|
|
if (Test-Path $cfgPath) {
|
|
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).Length))
|
|
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
|
}
|
|
$parent = Split-Path $d -Parent
|
|
if ($parent -eq $d) { break }
|
|
$d = $parent
|
|
}
|
|
return "2.17"
|
|
}
|
|
|
|
$script:formatVersion = Detect-FormatVersion $extDir
|
|
|
|
# --- 8. Namespaces declaration for object XML ---
|
|
$script:xmlnsDecl = '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" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
|
|
# --- 9. Parse -Object into items ---
|
|
$items = @()
|
|
foreach ($part in $Object.Split(";;")) {
|
|
$trimmed = $part.Trim()
|
|
if ($trimmed) { $items += $trimmed }
|
|
}
|
|
|
|
if ($items.Count -eq 0) {
|
|
Write-Error "No objects specified in -Object"
|
|
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)
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
if (-not $dirName) {
|
|
Write-Error "Unknown type '$typeName'"
|
|
exit 1
|
|
}
|
|
|
|
$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", "http://v8.1c.ru/8.3/MDClasses")
|
|
$srcNs.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
|
|
|
# Find the type element (e.g. <Catalog uuid="...">)
|
|
$srcRoot = $srcDoc.DocumentElement
|
|
$srcEl = $null
|
|
foreach ($c in $srcRoot.ChildNodes) {
|
|
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
|
|
}
|
|
if (-not $srcEl) {
|
|
Write-Error "No metadata element found in ${dirName}/${objName}.xml"
|
|
exit 1
|
|
}
|
|
|
|
# Extract uuid
|
|
$srcUuid = $srcEl.GetAttribute("uuid")
|
|
if (-not $srcUuid) {
|
|
Write-Error "No uuid attribute on source element in ${dirName}/${objName}.xml"
|
|
exit 1
|
|
}
|
|
|
|
# Extract properties for CommonModule
|
|
$srcProps = @{}
|
|
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
|
|
if ($propsNode) {
|
|
foreach ($propName in $commonModuleProps) {
|
|
$propNode = $propsNode.SelectSingleNode("md:${propName}", $srcNs)
|
|
if ($propNode) {
|
|
$srcProps[$propName] = $propNode.InnerText.Trim()
|
|
}
|
|
}
|
|
}
|
|
|
|
return @{
|
|
Uuid = $srcUuid
|
|
Properties = $srcProps
|
|
Element = $srcEl
|
|
NsManager = $srcNs
|
|
}
|
|
}
|
|
|
|
# --- 10b. Helper: read source form UUID ---
|
|
function Read-SourceFormUuid {
|
|
param([string]$typeName, [string]$objName, [string]$formName)
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
$srcFile = Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") "${formName}.xml"
|
|
if (-not (Test-Path $srcFile)) {
|
|
Write-Error "Source form not found: $srcFile"
|
|
exit 1
|
|
}
|
|
|
|
$srcDoc = New-Object System.Xml.XmlDocument
|
|
$srcDoc.PreserveWhitespace = $false
|
|
$srcDoc.Load($srcFile)
|
|
|
|
$srcEl = $null
|
|
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
|
|
if ($c.NodeType -eq 'Element') { $srcEl = $c; break }
|
|
}
|
|
if (-not $srcEl) {
|
|
Write-Error "No metadata element found in source form: $srcFile"
|
|
exit 1
|
|
}
|
|
|
|
$srcUuid = $srcEl.GetAttribute("uuid")
|
|
if (-not $srcUuid) {
|
|
Write-Error "No uuid attribute on source form element: $srcFile"
|
|
exit 1
|
|
}
|
|
|
|
return $srcUuid
|
|
}
|
|
|
|
# --- 10c. Helper: borrow a form ---
|
|
function Borrow-Form {
|
|
param([string]$typeName, [string]$objName, [string]$formName, [switch]$BorrowMainAttr)
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
|
|
|
# 1. Read source form UUID
|
|
$formUuid = Read-SourceFormUuid $typeName $objName $formName
|
|
Info " Source form UUID: $formUuid"
|
|
|
|
# 2. Read source Form.xml content
|
|
$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
|
|
}
|
|
$srcFormContent = [System.IO.File]::ReadAllText($srcFormXmlPath, $enc)
|
|
|
|
# 3. Generate form metadata XML (ФормаЭлемента.xml)
|
|
$newFormUuid = [guid]::NewGuid().ToString()
|
|
$formMetaSb = New-Object System.Text.StringBuilder
|
|
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
|
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
|
$formMetaSb.AppendLine("`t<Form uuid=`"${newFormUuid}`">") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t<InternalInfo/>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t<Properties>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t`t<Name>${formName}</Name>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t`t<Comment/>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t`t<ExtendedConfigurationObject>${formUuid}</ExtendedConfigurationObject>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t`t<FormType>Managed</FormType>") | Out-Null
|
|
$formMetaSb.AppendLine("`t`t</Properties>") | Out-Null
|
|
$formMetaSb.AppendLine("`t</Form>") | Out-Null
|
|
$formMetaSb.Append("</MetaDataObject>") | Out-Null
|
|
|
|
# 4. Create directories
|
|
$formMetaDir = Join-Path (Join-Path (Join-Path $extDir $dirName) $objName) "Forms"
|
|
if (-not (Test-Path $formMetaDir)) {
|
|
New-Item -ItemType Directory -Path $formMetaDir -Force | Out-Null
|
|
}
|
|
|
|
# Write form metadata
|
|
$formMetaFile = Join-Path $formMetaDir "${formName}.xml"
|
|
[System.IO.File]::WriteAllText($formMetaFile, $formMetaSb.ToString(), $enc)
|
|
Info " Created: $formMetaFile"
|
|
|
|
# 5. Generate Form.xml with BaseForm (visual elements only)
|
|
# Parse source Form.xml as XmlDocument
|
|
$srcFormDoc = New-Object System.Xml.XmlDocument
|
|
$srcFormDoc.PreserveWhitespace = $true
|
|
$srcFormDoc.Load($srcFormXmlPath)
|
|
$srcFormEl = $srcFormDoc.DocumentElement
|
|
|
|
$formVersion = $srcFormEl.GetAttribute("version")
|
|
if (-not $formVersion) { $formVersion = $script:formatVersion }
|
|
|
|
# Find direct children: form properties, AutoCommandBar, ChildItems
|
|
$srcAutoCmd = $null
|
|
$srcChildItems = $null
|
|
$formProps = @()
|
|
$reachedVisual = $false
|
|
foreach ($fc in $srcFormEl.ChildNodes) {
|
|
if ($fc.NodeType -ne 'Element') { continue }
|
|
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) {
|
|
$reachedVisual = $true; $srcAutoCmd = $fc; continue
|
|
}
|
|
if ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) {
|
|
$reachedVisual = $true; $srcChildItems = $fc; continue
|
|
}
|
|
if ($fc.LocalName -eq 'Events' -or $fc.LocalName -eq 'Attributes' -or $fc.LocalName -eq 'Commands' -or $fc.LocalName -eq 'Parameters' -or $fc.LocalName -eq 'CommandSet') {
|
|
$reachedVisual = $true; continue
|
|
}
|
|
if (-not $reachedVisual) {
|
|
$formProps += $fc.OuterXml
|
|
}
|
|
}
|
|
|
|
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
|
|
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
|
|
|
|
# AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false
|
|
$autoCmdXml = ""
|
|
if ($srcAutoCmd) {
|
|
$autoCmdXml = $srcAutoCmd.OuterXml
|
|
$autoCmdXml = [regex]::Replace($autoCmdXml, $nsStripPattern, '')
|
|
$autoCmdXml = [regex]::Replace($autoCmdXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
|
$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
|
|
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
|
|
$childItemsXml = ""
|
|
if ($srcChildItems) {
|
|
$childItemsXml = $srcChildItems.OuterXml
|
|
$childItemsXml = [regex]::Replace($childItemsXml, $nsStripPattern, '')
|
|
# Replace all CommandName values with 0
|
|
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
|
# 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)
|
|
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<TypeLink>\s*<xr:DataPath>Items\.[^<]*</xr:DataPath>.*?</TypeLink>', '')
|
|
# Strip element-level Events (base form handlers not in extension)
|
|
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Events>.*?</Events>', '')
|
|
|
|
# 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 = @()
|
|
foreach ($picName in $referencedPictures.Keys) {
|
|
if (-not (Test-ObjectBorrowed "CommonPicture" $picName)) {
|
|
$picSrcFile = Join-Path (Join-Path $cfgDir "CommonPictures") "${picName}.xml"
|
|
if (Test-Path $picSrcFile) {
|
|
$src = Read-SourceObject "CommonPicture" $picName
|
|
$borrowedXml = Build-BorrowedObjectXml "CommonPicture" $picName $src.Uuid $src.Properties
|
|
$targetDir = Join-Path $extDir "CommonPictures"
|
|
if (-not (Test-Path $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
$targetFile = Join-Path $targetDir "${picName}.xml"
|
|
$encBom = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
|
Add-ToChildObjects "CommonPicture" $picName
|
|
$autoBorrowedPics += $picName
|
|
$script:borrowedFiles += $targetFile
|
|
Info " Auto-borrowed: CommonPicture.${picName}"
|
|
} else {
|
|
Warn " CommonPicture.${picName} not found in source config — will strip from form"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Collect all borrowed CommonPictures (including previously borrowed)
|
|
$borrowedPicSet = @{}
|
|
$nsMgr2 = New-Object System.Xml.XmlNamespaceManager($script:xmlDoc.NameTable)
|
|
$nsMgr2.AddNamespace("md", $script:mdNs)
|
|
$picNodes = $script:xmlDoc.SelectNodes("//md:ChildObjects/md:CommonPicture", $nsMgr2)
|
|
foreach ($pn in $picNodes) { $borrowedPicSet[$pn.InnerText] = $true }
|
|
|
|
# Strip <Picture> blocks referencing non-borrowed CommonPictures
|
|
$picBlockPattern = '(?s)\s*<Picture>\s*<xr:Ref>CommonPicture\.(\w+)</xr:Ref>.*?</Picture>'
|
|
$picMatches = [regex]::Matches($childItemsXml, $picBlockPattern)
|
|
# Process in reverse order to preserve positions
|
|
for ($mi = $picMatches.Count - 1; $mi -ge 0; $mi--) {
|
|
$pm = $picMatches[$mi]
|
|
$cpName = $pm.Groups[1].Value
|
|
if (-not $borrowedPicSet.ContainsKey($cpName)) {
|
|
$childItemsXml = $childItemsXml.Remove($pm.Index, $pm.Length)
|
|
}
|
|
}
|
|
# 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.
|
|
$referencedStyles = @{}
|
|
$styleRefs1 = [regex]::Matches($childItemsXml, 'ref="style:(\w+)"[^>]*kind="StyleItem"')
|
|
foreach ($m in $styleRefs1) { $referencedStyles[$m.Groups[1].Value] = $true }
|
|
$styleRefs2 = [regex]::Matches($childItemsXml, '>style:(\w+)</\w+>')
|
|
foreach ($m in $styleRefs2) { $referencedStyles[$m.Groups[1].Value] = $true }
|
|
|
|
foreach ($styleName in $referencedStyles.Keys) {
|
|
if (-not (Test-ObjectBorrowed "StyleItem" $styleName)) {
|
|
$styleSrcFile = Join-Path (Join-Path $cfgDir "StyleItems") "${styleName}.xml"
|
|
if (Test-Path $styleSrcFile) {
|
|
$src = Read-SourceObject "StyleItem" $styleName
|
|
$borrowedXml = Build-BorrowedObjectXml "StyleItem" $styleName $src.Uuid $src.Properties
|
|
$targetDir = Join-Path $extDir "StyleItems"
|
|
if (-not (Test-Path $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
$targetFile = Join-Path $targetDir "${styleName}.xml"
|
|
$encBom = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
|
Add-ToChildObjects "StyleItem" $styleName
|
|
$script:borrowedFiles += $targetFile
|
|
Info " Auto-borrowed: StyleItem.${styleName}"
|
|
} else {
|
|
Warn " StyleItem.${styleName} not found in source config"
|
|
}
|
|
}
|
|
}
|
|
# Auto-borrow Enums + EnumValues referenced via DesignTimeRef in ChoiceParameters
|
|
# Collect Enum -> [EnumValue names] map
|
|
$dtRefs = [regex]::Matches($childItemsXml, 'xr:DesignTimeRef">Enum\.(\w+)\.EnumValue\.(\w+)')
|
|
$referencedEnumValues = @{}
|
|
foreach ($m in $dtRefs) {
|
|
$eName = $m.Groups[1].Value
|
|
$evName = $m.Groups[2].Value
|
|
if (-not $referencedEnumValues.ContainsKey($eName)) { $referencedEnumValues[$eName] = @{} }
|
|
$referencedEnumValues[$eName][$evName] = $true
|
|
}
|
|
|
|
foreach ($enumName in $referencedEnumValues.Keys) {
|
|
if (-not (Test-ObjectBorrowed "Enum" $enumName)) {
|
|
$enumSrcFile = Join-Path (Join-Path $cfgDir "Enums") "${enumName}.xml"
|
|
if (Test-Path $enumSrcFile) {
|
|
# Read source Enum to get UUID and EnumValue UUIDs
|
|
$srcParser = New-Object System.Xml.XmlDocument
|
|
$srcParser.PreserveWhitespace = $true
|
|
$srcParser.Load($enumSrcFile)
|
|
$srcEnumEl = $null
|
|
foreach ($cn in $srcParser.DocumentElement.ChildNodes) {
|
|
if ($cn.NodeType -eq 'Element') { $srcEnumEl = $cn; break }
|
|
}
|
|
$srcEnumUuid = $srcEnumEl.GetAttribute("uuid")
|
|
|
|
# Find source EnumValues by name
|
|
$enumValueXmls = @()
|
|
$neededValues = $referencedEnumValues[$enumName]
|
|
$srcNsMgr = New-Object System.Xml.XmlNamespaceManager($srcParser.NameTable)
|
|
$srcNsMgr.AddNamespace("md", $script:mdNs)
|
|
$srcEvNodes = $srcEnumEl.SelectNodes("md:ChildObjects/md:EnumValue", $srcNsMgr)
|
|
foreach ($evNode in $srcEvNodes) {
|
|
$evUuid = $evNode.GetAttribute("uuid")
|
|
$evNameNode = $evNode.SelectSingleNode("md:Properties/md:Name", $srcNsMgr)
|
|
if ($evNameNode -and $neededValues.ContainsKey($evNameNode.InnerText)) {
|
|
$newEvUuid = [guid]::NewGuid().ToString()
|
|
$enumValueXmls += @"
|
|
<EnumValue uuid="${newEvUuid}">
|
|
<InternalInfo/>
|
|
<Properties>
|
|
<ObjectBelonging>Adopted</ObjectBelonging>
|
|
<Name>$($evNameNode.InnerText)</Name>
|
|
<Comment/>
|
|
<ExtendedConfigurationObject>${evUuid}</ExtendedConfigurationObject>
|
|
</Properties>
|
|
</EnumValue>
|
|
"@
|
|
}
|
|
}
|
|
|
|
# Build borrowed Enum with EnumValues in ChildObjects
|
|
$src = Read-SourceObject "Enum" $enumName
|
|
$borrowedXml = Build-BorrowedObjectXml "Enum" $enumName $src.Uuid $src.Properties
|
|
if ($enumValueXmls.Count -gt 0) {
|
|
$evBlock = ($enumValueXmls -join "`r`n")
|
|
$borrowedXml = $borrowedXml -replace '<ChildObjects/>', "<ChildObjects>`r`n${evBlock}`r`n`t`t</ChildObjects>"
|
|
}
|
|
|
|
$targetDir = Join-Path $extDir "Enums"
|
|
if (-not (Test-Path $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
$targetFile = Join-Path $targetDir "${enumName}.xml"
|
|
$encBom = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
|
|
Add-ToChildObjects "Enum" $enumName
|
|
$script:borrowedFiles += $targetFile
|
|
Info " Auto-borrowed: Enum.${enumName} (with $($enumValueXmls.Count) EnumValue(s))"
|
|
} else {
|
|
Warn " Enum.${enumName} not found in source config"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# Extract the <Form ...> opening tag from source text (preserves namespace declarations)
|
|
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
|
|
$formTag = "<Form version=`"${formVersion}`">"
|
|
if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] }
|
|
if ($srcFormContent -match '(<Form[^>]*>)') { $formTag = $Matches[1] }
|
|
|
|
# Build output Form.xml
|
|
$formXmlSb = New-Object System.Text.StringBuilder
|
|
$formXmlSb.Append($xmlDecl) | Out-Null
|
|
$formXmlSb.Append("`r`n") | Out-Null
|
|
$formXmlSb.Append($formTag) | Out-Null
|
|
$formXmlSb.Append("`r`n") | Out-Null
|
|
|
|
# Part 1: form properties + AutoCommandBar + ChildItems
|
|
foreach ($propXml in $formProps) {
|
|
$propXml = [regex]::Replace($propXml, $nsStripPattern, '')
|
|
$formXmlSb.Append("`t$propXml`r`n") | Out-Null
|
|
}
|
|
if ($autoCmdXml) {
|
|
$formXmlSb.Append("`t$autoCmdXml") | Out-Null
|
|
$formXmlSb.Append("`r`n") | Out-Null
|
|
}
|
|
if ($childItemsXml) {
|
|
$formXmlSb.Append("`t$childItemsXml") | Out-Null
|
|
$formXmlSb.Append("`r`n") | 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
|
|
$formXmlSb.Append("`t<BaseForm version=`"${formVersion}`">") | Out-Null
|
|
$formXmlSb.Append("`r`n") | Out-Null
|
|
|
|
foreach ($propXml in $formProps) {
|
|
$propXml = [regex]::Replace($propXml, $nsStripPattern, '')
|
|
$formXmlSb.Append("`t`t$propXml`r`n") | Out-Null
|
|
}
|
|
if ($autoCmdXml) {
|
|
$acLines = $autoCmdXml -split "`r?`n"
|
|
for ($li = 0; $li -lt $acLines.Count; $li++) {
|
|
if ($li -eq 0) { $formXmlSb.Append("`t`t$($acLines[$li])") | Out-Null }
|
|
else { $formXmlSb.Append("`t$($acLines[$li])") | Out-Null }
|
|
$formXmlSb.Append("`r`n") | Out-Null
|
|
}
|
|
}
|
|
if ($childItemsXml) {
|
|
# Reindent ChildItems for BaseForm (+1 tab level)
|
|
$ciLines = $childItemsXml -split "`r?`n"
|
|
for ($li = 0; $li -lt $ciLines.Count; $li++) {
|
|
if ($li -eq 0) { $formXmlSb.Append("`t`t$($ciLines[$li])") | Out-Null }
|
|
else { $formXmlSb.Append("`t$($ciLines[$li])") | Out-Null }
|
|
$formXmlSb.Append("`r`n") | 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
|
|
$formXmlSb.Append("</Form>") | Out-Null
|
|
|
|
# Write Form.xml
|
|
$formXmlDir = Join-Path (Join-Path $formMetaDir $formName) "Ext"
|
|
if (-not (Test-Path $formXmlDir)) {
|
|
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
|
|
}
|
|
$formXmlFile = Join-Path $formXmlDir "Form.xml"
|
|
[System.IO.File]::WriteAllText($formXmlFile, $formXmlSb.ToString(), $enc)
|
|
Info " Created: $formXmlFile"
|
|
|
|
# 6. Create empty Module.bsl
|
|
$moduleDir = Join-Path $formXmlDir "Form"
|
|
if (-not (Test-Path $moduleDir)) {
|
|
New-Item -ItemType Directory -Path $moduleDir -Force | Out-Null
|
|
}
|
|
$moduleBslFile = Join-Path $moduleDir "Module.bsl"
|
|
[System.IO.File]::WriteAllText($moduleBslFile, "", $enc)
|
|
Info " Created: $moduleBslFile"
|
|
|
|
# 7. Register form in parent object ChildObjects
|
|
Register-FormInObject $typeName $objName $formName
|
|
|
|
return @($formMetaFile, $formXmlFile, $moduleBslFile)
|
|
}
|
|
|
|
# --- 10d. Helper: register form in parent object's ChildObjects ---
|
|
function Register-FormInObject {
|
|
param([string]$typeName, [string]$objName, [string]$formName)
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
|
|
|
if (-not (Test-Path $objFile)) {
|
|
Warn "Parent object file not found: $objFile — form not registered in ChildObjects"
|
|
return
|
|
}
|
|
|
|
$objDoc = New-Object System.Xml.XmlDocument
|
|
$objDoc.PreserveWhitespace = $true
|
|
$objDoc.Load($objFile)
|
|
|
|
$objNs = New-Object System.Xml.XmlNamespaceManager($objDoc.NameTable)
|
|
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
|
|
|
# Find the type element
|
|
$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 — form not registered"
|
|
return
|
|
}
|
|
|
|
# Find or create ChildObjects
|
|
$childObjs = $objEl.SelectSingleNode("md:ChildObjects", $objNs)
|
|
if (-not $childObjs) {
|
|
# Create ChildObjects element
|
|
$childObjs = $objDoc.CreateElement("ChildObjects", "http://v8.1c.ru/8.3/MDClasses")
|
|
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t`t")) | Out-Null
|
|
$objEl.AppendChild($childObjs) | Out-Null
|
|
$objEl.AppendChild($objDoc.CreateWhitespace("`r`n`t")) | Out-Null
|
|
}
|
|
|
|
# Check dedup
|
|
foreach ($c in $childObjs.ChildNodes) {
|
|
if ($c.NodeType -eq 'Element' -and $c.LocalName -eq "Form" -and $c.InnerText -eq $formName) {
|
|
Warn "Form '$formName' already in ChildObjects of ${typeName}.${objName}"
|
|
return
|
|
}
|
|
}
|
|
|
|
# Expand self-closing if needed
|
|
if (-not $childObjs.HasChildNodes -or $childObjs.IsEmpty) {
|
|
$closeWs = $objDoc.CreateWhitespace("`r`n`t`t")
|
|
$childObjs.AppendChild($closeWs) | Out-Null
|
|
}
|
|
|
|
# Add <Form>formName</Form>
|
|
$formEl = $objDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
|
$formEl.InnerText = $formName
|
|
|
|
$trailing = $childObjs.LastChild
|
|
$ws = $objDoc.CreateWhitespace("`r`n`t`t`t")
|
|
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
|
$childObjs.InsertBefore($ws, $trailing) | Out-Null
|
|
$childObjs.InsertBefore($formEl, $trailing) | Out-Null
|
|
} else {
|
|
$childObjs.AppendChild($ws) | Out-Null
|
|
$childObjs.AppendChild($formEl) | Out-Null
|
|
}
|
|
|
|
# Save object XML
|
|
$settings2 = New-Object System.Xml.XmlWriterSettings
|
|
$settings2.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
$settings2.Indent = $false
|
|
$settings2.NewLineHandling = [System.Xml.NewLineHandling]::None
|
|
|
|
$memStream2 = New-Object System.IO.MemoryStream
|
|
$writer2 = [System.Xml.XmlWriter]::Create($memStream2, $settings2)
|
|
$objDoc.Save($writer2)
|
|
$writer2.Flush(); $writer2.Close()
|
|
|
|
$bytes2 = $memStream2.ToArray()
|
|
$memStream2.Close()
|
|
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
|
|
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
|
|
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
|
|
|
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
|
|
Info " Registered form in: $objFile"
|
|
}
|
|
|
|
# --- 10e. Helper: check if object is already borrowed in extension ---
|
|
function Test-ObjectBorrowed {
|
|
param([string]$typeName, [string]$objName)
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
$objFile = Join-Path (Join-Path $extDir $dirName) "${objName}.xml"
|
|
return (Test-Path $objFile)
|
|
}
|
|
|
|
# --- 11. Helper: generate InternalInfo XML ---
|
|
function Build-InternalInfoXml {
|
|
param([string]$typeName, [string]$objName, [string]$indent)
|
|
|
|
$types = $script:generatedTypes[$typeName]
|
|
if (-not $types -or $types.Count -eq 0) {
|
|
return "${indent}<InternalInfo/>"
|
|
}
|
|
|
|
$sb = New-Object System.Text.StringBuilder
|
|
$sb.AppendLine("${indent}<InternalInfo>") | Out-Null
|
|
|
|
# ExchangePlan: ThisNode UUID before GeneratedTypes
|
|
if ($typeName -eq "ExchangePlan") {
|
|
$thisNodeUuid = [guid]::NewGuid().ToString()
|
|
$sb.AppendLine("${indent}`t<xr:ThisNode>${thisNodeUuid}</xr:ThisNode>") | Out-Null
|
|
}
|
|
|
|
foreach ($gt in $types) {
|
|
$fullName = "$($gt.prefix).${objName}"
|
|
$typeId = [guid]::NewGuid().ToString()
|
|
$valueId = [guid]::NewGuid().ToString()
|
|
$sb.AppendLine("${indent}`t<xr:GeneratedType name=`"${fullName}`" category=`"$($gt.category)`">") | Out-Null
|
|
$sb.AppendLine("${indent}`t`t<xr:TypeId>${typeId}</xr:TypeId>") | Out-Null
|
|
$sb.AppendLine("${indent}`t`t<xr:ValueId>${valueId}</xr:ValueId>") | Out-Null
|
|
$sb.AppendLine("${indent}`t</xr:GeneratedType>") | Out-Null
|
|
}
|
|
|
|
$sb.Append("${indent}</InternalInfo>") | Out-Null
|
|
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(
|
|
[string]$typeName,
|
|
[string]$objName,
|
|
[string]$sourceUuid,
|
|
[hashtable]$sourceProps
|
|
)
|
|
|
|
$newUuid = [guid]::NewGuid().ToString()
|
|
$internalInfoXml = Build-InternalInfoXml $typeName $objName "`t`t"
|
|
|
|
$sb = New-Object System.Text.StringBuilder
|
|
$sb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
|
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
|
$sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null
|
|
|
|
# InternalInfo
|
|
$sb.AppendLine($internalInfoXml) | Out-Null
|
|
|
|
# Properties
|
|
$sb.AppendLine("`t`t<Properties>") | Out-Null
|
|
$sb.AppendLine("`t`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
|
$sb.AppendLine("`t`t`t<Name>${objName}</Name>") | Out-Null
|
|
$sb.AppendLine("`t`t`t<Comment/>") | Out-Null
|
|
$sb.AppendLine("`t`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
|
|
|
|
# CommonModule: extra properties from source
|
|
if ($typeName -eq "CommonModule") {
|
|
foreach ($propName in $commonModuleProps) {
|
|
$propVal = "false"
|
|
if ($sourceProps.ContainsKey($propName)) {
|
|
$propVal = $sourceProps[$propName]
|
|
}
|
|
$sb.AppendLine("`t`t`t<${propName}>${propVal}</${propName}>") | Out-Null
|
|
}
|
|
}
|
|
|
|
$sb.AppendLine("`t`t</Properties>") | Out-Null
|
|
|
|
# ChildObjects (for types that need it)
|
|
if ($typesWithChildObjects -contains $typeName) {
|
|
$sb.AppendLine("`t`t<ChildObjects/>") | Out-Null
|
|
}
|
|
|
|
$sb.AppendLine("`t</${typeName}>") | Out-Null
|
|
$sb.Append("</MetaDataObject>") | Out-Null
|
|
|
|
return $sb.ToString()
|
|
}
|
|
|
|
# --- 13. Helper: add object to extension ChildObjects ---
|
|
function Add-ToChildObjects {
|
|
param([string]$typeName, [string]$objName)
|
|
|
|
$cfgIndent = Get-ChildIndent $script:cfgEl
|
|
|
|
# Expand self-closing ChildObjects if needed
|
|
if (-not $script:childObjsEl.HasChildNodes -or $script:childObjsEl.IsEmpty) {
|
|
Expand-SelfClosingElement $script:childObjsEl $cfgIndent
|
|
}
|
|
$childIndent = Get-ChildIndent $script:childObjsEl
|
|
|
|
$typeIdx = $script:typeOrder.IndexOf($typeName)
|
|
if ($typeIdx -lt 0) {
|
|
Write-Error "Unknown type '$typeName' for ChildObjects ordering"
|
|
exit 1
|
|
}
|
|
|
|
# Dedup check
|
|
foreach ($child in $script:childObjsEl.ChildNodes) {
|
|
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $typeName -and $child.InnerText -eq $objName) {
|
|
Warn "Already in ChildObjects: ${typeName}.${objName}"
|
|
return
|
|
}
|
|
}
|
|
|
|
# Find insertion point: after last element of same type, or before first element of later type
|
|
$insertBefore = $null
|
|
$lastSameType = $null
|
|
|
|
foreach ($child in $script:childObjsEl.ChildNodes) {
|
|
if ($child.NodeType -ne 'Element') { continue }
|
|
$childTypeIdx = $script:typeOrder.IndexOf($child.LocalName)
|
|
if ($childTypeIdx -lt 0) { continue }
|
|
|
|
if ($child.LocalName -eq $typeName) {
|
|
# Same type -- check alphabetical order
|
|
if ($child.InnerText -gt $objName -and -not $insertBefore) {
|
|
$insertBefore = $child
|
|
}
|
|
$lastSameType = $child
|
|
} elseif ($childTypeIdx -gt $typeIdx -and -not $insertBefore) {
|
|
# First element of a later type -- insert before it
|
|
$insertBefore = $child
|
|
}
|
|
}
|
|
|
|
# Create element
|
|
$newEl = $script:xmlDoc.CreateElement($typeName, $script:mdNs)
|
|
$newEl.InnerText = $objName
|
|
|
|
if ($insertBefore) {
|
|
Insert-BeforeElement $script:childObjsEl $newEl $insertBefore $childIndent
|
|
} else {
|
|
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
|
|
}
|
|
|
|
Info "Added to ChildObjects: ${typeName}.${objName}"
|
|
}
|
|
|
|
# --- 14. Process each item ---
|
|
$script:borrowedFiles = @()
|
|
$borrowedCount = 0
|
|
|
|
foreach ($item in $items) {
|
|
$dotIdx = $item.IndexOf(".")
|
|
if ($dotIdx -lt 1) {
|
|
Write-Error "Invalid format '${item}', expected 'Type.Name' or 'Type.Name.Form.FormName'"
|
|
exit 1
|
|
}
|
|
$typeName = $item.Substring(0, $dotIdx)
|
|
$remainder = $item.Substring($dotIdx + 1)
|
|
|
|
# Resolve Russian synonym to English type name
|
|
if ($synonymMap.ContainsKey($typeName)) { $typeName = $synonymMap[$typeName] }
|
|
|
|
if (-not $childTypeDirMap.ContainsKey($typeName)) {
|
|
Write-Error "Unknown type '${typeName}'"
|
|
exit 1
|
|
}
|
|
|
|
# Check for .Form. pattern: Type.ObjName.Form.FormName
|
|
$formName = $null
|
|
$formIdx = $remainder.IndexOf(".Form.")
|
|
if ($formIdx -gt 0) {
|
|
$objName = $remainder.Substring(0, $formIdx)
|
|
$formName = $remainder.Substring($formIdx + 6) # skip ".Form."
|
|
} else {
|
|
$objName = $remainder
|
|
}
|
|
|
|
$dirName = $childTypeDirMap[$typeName]
|
|
|
|
if ($formName) {
|
|
# --- Form borrowing ---
|
|
Info "Borrowing form ${typeName}.${objName}.Form.${formName}..."
|
|
|
|
# Auto-borrow parent object if not yet borrowed
|
|
if (-not (Test-ObjectBorrowed $typeName $objName)) {
|
|
Info " Parent object ${typeName}.${objName} not yet borrowed — borrowing first..."
|
|
|
|
$src = Read-SourceObject $typeName $objName
|
|
Info " Source UUID: $($src.Uuid)"
|
|
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
|
|
|
$targetDir = Join-Path $extDir $dirName
|
|
if (-not (Test-Path $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
$targetFile = Join-Path $targetDir "${objName}.xml"
|
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
|
Info " Created: $targetFile"
|
|
|
|
Add-ToChildObjects $typeName $objName
|
|
$script:borrowedFiles += $targetFile
|
|
}
|
|
|
|
# Borrow the form
|
|
$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}..."
|
|
|
|
$src = Read-SourceObject $typeName $objName
|
|
Info " Source UUID: $($src.Uuid)"
|
|
|
|
$borrowedXml = Build-BorrowedObjectXml $typeName $objName $src.Uuid $src.Properties
|
|
|
|
$targetDir = Join-Path $extDir $dirName
|
|
if (-not (Test-Path $targetDir)) {
|
|
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
|
|
}
|
|
|
|
$targetFile = Join-Path $targetDir "${objName}.xml"
|
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $enc)
|
|
Info " Created: $targetFile"
|
|
|
|
Add-ToChildObjects $typeName $objName
|
|
|
|
$script:borrowedFiles += $targetFile
|
|
$borrowedCount++
|
|
}
|
|
}
|
|
|
|
# --- 15. Save modified Configuration.xml ---
|
|
$settings = New-Object System.Xml.XmlWriterSettings
|
|
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
$settings.Indent = $false
|
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
|
|
|
$memStream = New-Object System.IO.MemoryStream
|
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
|
$script:xmlDoc.Save($writer)
|
|
$writer.Flush(); $writer.Close()
|
|
|
|
$bytes = $memStream.ToArray()
|
|
$memStream.Close()
|
|
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
|
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
|
|
|
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
|
|
Info "Saved: $extResolvedPath"
|
|
|
|
# --- 16. Summary ---
|
|
Write-Host ""
|
|
Write-Host "=== cfe-borrow summary ==="
|
|
Write-Host " Extension: $extDir"
|
|
Write-Host " Config: $cfgDir"
|
|
Write-Host " Borrowed: $borrowedCount object(s)"
|
|
foreach ($f in $script:borrowedFiles) {
|
|
Write-Host " - $f"
|
|
}
|
|
exit 0
|