mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-29 21:45:54 +03:00
Модули заимствованного объекта собирались из Ext/*.bsl без рекурсии и из Forms/**/Module.bsl; модуль общей формы лежит в CommonForms/X/Ext/Form/Module.bsl и не попадал никуда. После перехвата методов общей формы (cfe-patch-method v2.13) такой перехватчик не показывался в отчёте, а проверка переноса (-Mode B) его пропускала. Теперь модуль общей формы собирается вместе с остальными; режим A показывает и перехваты событий/команд из её Form.xml — как у форм владельца. Оба порта, v1.7; вывод портов совпадает. Кейсы common-form-interceptor и common-form-transfer (на прежней версии падали). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
559 lines
20 KiB
PowerShell
559 lines
20 KiB
PowerShell
# cfe-diff v1.7 — Analyze and compare 1C configuration extension (CFE)
|
||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||
[CmdletBinding(PositionalBinding=$false)]
|
||
param(
|
||
[Parameter(Mandatory, Position=0)]
|
||
[string]$ExtensionPath,
|
||
|
||
[Parameter(Mandatory)]
|
||
[string]$ConfigPath,
|
||
|
||
[ValidateSet("A","B")]
|
||
[string]$Mode = "A"
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||
|
||
# --- Resolve paths ---
|
||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||
}
|
||
if (-not [System.IO.Path]::IsPathRooted($ConfigPath)) {
|
||
$ConfigPath = Join-Path (Get-Location).Path $ConfigPath
|
||
}
|
||
if (Test-Path $ExtensionPath -PathType Leaf) { $ExtensionPath = Split-Path $ExtensionPath -Parent }
|
||
if (Test-Path $ConfigPath -PathType Leaf) { $ConfigPath = Split-Path $ConfigPath -Parent }
|
||
|
||
$extCfg = Join-Path $ExtensionPath "Configuration.xml"
|
||
$srcCfg = Join-Path $ConfigPath "Configuration.xml"
|
||
if (-not (Test-Path $extCfg)) { Write-Error "Extension Configuration.xml not found: $extCfg"; exit 1 }
|
||
if (-not (Test-Path $srcCfg)) { Write-Error "Config Configuration.xml not found: $srcCfg"; exit 1 }
|
||
|
||
# --- Type -> directory mapping ---
|
||
$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"; "ExternalDataSource"="ExternalDataSources"; "IntegrationService"="IntegrationServices"
|
||
"CommonAttribute"="CommonAttributes"; "Style"="Styles"; "XDTOPackage"="XDTOPackages"
|
||
"WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
||
"Bot"="Bots"
|
||
"PaletteColor"="PaletteColors"
|
||
}
|
||
|
||
# --- Parse extension Configuration.xml ---
|
||
$extDoc = New-Object System.Xml.XmlDocument
|
||
$extDoc.PreserveWhitespace = $false
|
||
$extDoc.Load($extCfg)
|
||
|
||
$ns = New-Object System.Xml.XmlNamespaceManager($extDoc.NameTable)
|
||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||
$ns.AddNamespace("xr", "http://v8.1c.ru/8.3/xcf/readable")
|
||
|
||
$extProps = $extDoc.SelectSingleNode("//md:Configuration/md:Properties", $ns)
|
||
$extNameNode = $extProps.SelectSingleNode("md:Name", $ns)
|
||
$extName = if ($extNameNode) { $extNameNode.InnerText } else { "?" }
|
||
$prefixNode = $extProps.SelectSingleNode("md:NamePrefix", $ns)
|
||
$namePrefix = if ($prefixNode -and $prefixNode.InnerText) { $prefixNode.InnerText } else { "" }
|
||
$purposeNode = $extProps.SelectSingleNode("md:ConfigurationExtensionPurpose", $ns)
|
||
$purpose = if ($purposeNode) { $purposeNode.InnerText } else { "?" }
|
||
|
||
Write-Host "=== cfe-diff Mode ${Mode}: $extName (${purpose}) ==="
|
||
Write-Host " NamePrefix: $namePrefix"
|
||
Write-Host ""
|
||
|
||
# --- Collect ChildObjects ---
|
||
$childObjNode = $extDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
|
||
if (-not $childObjNode) {
|
||
Write-Host "[WARN] No ChildObjects in extension"
|
||
exit 0
|
||
}
|
||
|
||
$objects = @()
|
||
foreach ($child in $childObjNode.ChildNodes) {
|
||
if ($child.NodeType -ne 'Element') { continue }
|
||
if ($child.LocalName -eq "Language") { continue }
|
||
$objects += @{ Type = $child.LocalName; Name = $child.InnerText }
|
||
}
|
||
|
||
if ($objects.Count -eq 0) {
|
||
Write-Host "No objects (besides Language) in extension."
|
||
exit 0
|
||
}
|
||
|
||
# --- Helper: check if object is borrowed ---
|
||
function Get-ObjectInfo {
|
||
param([string]$objType, [string]$objName)
|
||
|
||
if (-not $childTypeDirMap.ContainsKey($objType)) { return $null }
|
||
$dirName = $childTypeDirMap[$objType]
|
||
$objFile = Join-Path (Join-Path $ExtensionPath $dirName) "${objName}.xml"
|
||
|
||
if (-not (Test-Path $objFile)) { return @{ Borrowed = $false; File = $objFile; Exists = $false } }
|
||
|
||
$doc = New-Object System.Xml.XmlDocument
|
||
$doc.PreserveWhitespace = $false
|
||
$doc.Load($objFile)
|
||
|
||
$objNs = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||
$objNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||
|
||
$objEl = $null
|
||
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||
}
|
||
if (-not $objEl) { return @{ Borrowed = $false; File = $objFile; Exists = $true } }
|
||
|
||
$propsEl = $objEl.SelectSingleNode("md:Properties", $objNs)
|
||
$obNode = if ($propsEl) { $propsEl.SelectSingleNode("md:ObjectBelonging", $objNs) } else { $null }
|
||
|
||
$info = @{
|
||
Borrowed = ($obNode -and $obNode.InnerText -eq "Adopted")
|
||
File = $objFile
|
||
Exists = $true
|
||
Type = $objType
|
||
Name = $objName
|
||
DirName = $dirName
|
||
ObjElement = $objEl
|
||
ObjNs = $objNs
|
||
}
|
||
return $info
|
||
}
|
||
|
||
# --- Helper: find .bsl files for object ---
|
||
function Get-BslFiles {
|
||
param([string]$objType, [string]$objName)
|
||
|
||
if (-not $childTypeDirMap.ContainsKey($objType)) { return @() }
|
||
$dirName = $childTypeDirMap[$objType]
|
||
$objDir = Join-Path (Join-Path $ExtensionPath $dirName) $objName
|
||
|
||
if (-not (Test-Path $objDir -PathType Container)) { return @() }
|
||
|
||
$bslFiles = @()
|
||
$extDir = Join-Path $objDir "Ext"
|
||
if (Test-Path $extDir) {
|
||
$items = Get-ChildItem -Path $extDir -Filter "*.bsl" -ErrorAction SilentlyContinue
|
||
foreach ($item in $items) { $bslFiles += $item.FullName }
|
||
}
|
||
|
||
# Модуль общей формы — у неё самой, Ext/Form/Module.bsl (у прочих объектов такого файла нет)
|
||
$ownFormModule = Join-Path (Join-Path $extDir "Form") "Module.bsl"
|
||
if (Test-Path $ownFormModule) { $bslFiles += (Get-Item $ownFormModule).FullName }
|
||
|
||
# Forms
|
||
$formsDir = Join-Path $objDir "Forms"
|
||
if (Test-Path $formsDir) {
|
||
$formModules = Get-ChildItem -Path $formsDir -Recurse -Filter "Module.bsl" -ErrorAction SilentlyContinue
|
||
foreach ($fm in $formModules) { $bslFiles += $fm.FullName }
|
||
}
|
||
|
||
return $bslFiles
|
||
}
|
||
|
||
# Built-in language keywords in both spellings. The platform accepts either one in any module
|
||
# (pairs taken from the platform string tables), so a module written in English is ordinary
|
||
# source, not a broken one: we must read both and emit the spelling we read.
|
||
function Get-BslKeywords {
|
||
return @{
|
||
ru = @{
|
||
Async="Асинх"; Proc="Процедура"; EndProc="КонецПроцедуры"
|
||
Func="Функция"; EndFunc="КонецФункции"; Val="Знач"
|
||
Region="Область"; EndRegion="КонецОбласти"
|
||
If="Если"; Then="Тогда"; ElsIf="ИначеЕсли"; Else="Иначе"; EndIf="КонецЕсли"
|
||
# Platform spells the negation "Не"; we emit "НЕ" as this skill always has —
|
||
# the language is case-insensitive, and the lower case would churn every snapshot.
|
||
And="И"; Or="Или"; Not="НЕ"
|
||
Insert="Вставка"; EndInsert="КонецВставки"; Delete="Удаление"; EndDelete="КонецУдаления"
|
||
Before="Перед"; After="После"; Around="Вместо"; Control="ИзменениеИКонтроль"
|
||
Proceed="ПродолжитьВызов"; Return="Возврат"
|
||
# Not a keyword: name of the local the generated Instead-stub declares. Lives here so
|
||
# that the language of emitted text is decided in exactly one place.
|
||
ResultVar="Результат"
|
||
Directives=@("НаКлиенте", "НаСервере", "НаСервереБезКонтекста", "НаКлиентеНаСервереБезКонтекста", "НаКлиентеНаСервере")
|
||
}
|
||
en = @{
|
||
Async="Async"; Proc="Procedure"; EndProc="EndProcedure"
|
||
Func="Function"; EndFunc="EndFunction"; Val="Val"
|
||
Region="Region"; EndRegion="EndRegion"
|
||
If="If"; Then="Then"; ElsIf="ElsIf"; Else="Else"; EndIf="EndIf"
|
||
And="And"; Or="Or"; Not="Not"
|
||
Insert="Insert"; EndInsert="EndInsert"; Delete="Delete"; EndDelete="EndDelete"
|
||
Before="Before"; After="After"; Around="Around"; Control="ChangeAndValidate"
|
||
Proceed="ProceedWithCall"; Return="Return"
|
||
ResultVar="Result"
|
||
Directives=@("AtClient", "AtServer", "AtServerNoContext", "AtClientAtServerNoContext", "AtClientAtServer")
|
||
}
|
||
}
|
||
}
|
||
|
||
$script:bslKw = Get-BslKeywords
|
||
|
||
# Edit marker kind at the start of a line: Insert/Delete/EndInsert/EndDelete, $null if none.
|
||
# The marker owns the start of the line only — a trailing comment does not bother the platform
|
||
# and must not bother us either.
|
||
function Get-BslMarkerKind {
|
||
param([string]$line)
|
||
$kw = $script:bslKw
|
||
foreach ($k in @("EndInsert", "EndDelete", "Insert", "Delete")) {
|
||
if ($line -match ('^\s*#(?:' + $kw.ru[$k] + '|' + $kw.en[$k] + ')\b')) { return $k }
|
||
}
|
||
return $null
|
||
}
|
||
|
||
# Parse interceptor annotations present in a module. Type is normalized to the Russian spelling
|
||
# so everything downstream stays single-language; Raw keeps what the file actually says.
|
||
function Get-Interceptors {
|
||
param($lines)
|
||
$kw = $script:bslKw
|
||
$keys = @("Before", "After", "Control", "Around")
|
||
$alt = @()
|
||
$norm = @{}
|
||
foreach ($k in $keys) {
|
||
$alt += $kw.ru[$k]; $alt += $kw.en[$k]
|
||
$norm[$kw.ru[$k].ToLower()] = $kw.ru[$k]
|
||
$norm[$kw.en[$k].ToLower()] = $kw.ru[$k]
|
||
}
|
||
$re = '^&(' + ($alt -join '|') + ')\("([^"]+)"\)'
|
||
$result = @()
|
||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||
$t = $lines[$i].Trim()
|
||
if ($t -match $re) {
|
||
$raw = $Matches[1]
|
||
$result += @{ Type = $norm[$raw.ToLower()]; Raw = $raw; Method = $Matches[2]; Line = $i }
|
||
}
|
||
}
|
||
return $result
|
||
}
|
||
|
||
# --- Helper: read a .bsl and parse its interceptors ---
|
||
function Get-FileInterceptors {
|
||
param([string]$bslPath)
|
||
|
||
if (-not (Test-Path $bslPath)) { return @() }
|
||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||
$result = @()
|
||
foreach ($ic in (Get-Interceptors $lines)) {
|
||
$result += @{ Type = $ic.Type; Raw = $ic.Raw; Method = $ic.Method; Line = $ic.Line + 1; File = $bslPath }
|
||
}
|
||
return $result
|
||
}
|
||
|
||
# --- Helper: extract #Вставка blocks from .bsl ---
|
||
function Get-InsertionBlocks {
|
||
param([string]$bslPath)
|
||
|
||
if (-not (Test-Path $bslPath)) { return @() }
|
||
$lines = [System.IO.File]::ReadAllLines($bslPath, [System.Text.Encoding]::UTF8)
|
||
$blocks = @()
|
||
$inBlock = $false
|
||
$blockLines = @()
|
||
$startLine = 0
|
||
|
||
for ($i = 0; $i -lt $lines.Count; $i++) {
|
||
$line = $lines[$i]
|
||
$kind = Get-BslMarkerKind $line
|
||
if ($kind -eq "Insert") {
|
||
$inBlock = $true
|
||
$blockLines = @()
|
||
$startLine = $i + 1
|
||
} elseif ($kind -eq "EndInsert" -and $inBlock) {
|
||
$inBlock = $false
|
||
$blocks += @{
|
||
StartLine = $startLine
|
||
EndLine = $i + 1
|
||
Code = ($blockLines -join "`n").Trim()
|
||
File = $bslPath
|
||
}
|
||
} elseif ($inBlock) {
|
||
$blockLines += $lines[$i]
|
||
}
|
||
}
|
||
return $blocks
|
||
}
|
||
|
||
# --- Helper: analyze form for callType events and commands ---
|
||
function Get-FormInterceptors {
|
||
param([string]$formXmlPath)
|
||
|
||
if (-not (Test-Path $formXmlPath)) { return $null }
|
||
|
||
$formDoc = New-Object System.Xml.XmlDocument
|
||
$formDoc.PreserveWhitespace = $false
|
||
try { $formDoc.Load($formXmlPath) } catch { return $null }
|
||
|
||
$fNs = New-Object System.Xml.XmlNamespaceManager($formDoc.NameTable)
|
||
$fNs.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||
|
||
$fRoot = $formDoc.DocumentElement
|
||
$baseForm = $fRoot.SelectSingleNode("f:BaseForm", $fNs)
|
||
$isBorrowed = ($baseForm -ne $null)
|
||
|
||
$interceptors = @()
|
||
|
||
# Form-level events with callType
|
||
$eventsNode = $fRoot.SelectSingleNode("f:Events", $fNs)
|
||
if ($eventsNode) {
|
||
foreach ($evt in $eventsNode.SelectNodes("f:Event", $fNs)) {
|
||
$ct = $evt.GetAttribute("callType")
|
||
if ($ct) {
|
||
$interceptors += "Event:$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||
}
|
||
}
|
||
}
|
||
|
||
# Element-level events with callType (scan all elements recursively)
|
||
$childItems = $fRoot.SelectSingleNode("f:ChildItems", $fNs)
|
||
if ($childItems) {
|
||
foreach ($evtNode in $childItems.SelectNodes(".//*[f:Events/f:Event[@callType]]", $fNs)) {
|
||
$elName = $evtNode.GetAttribute("name")
|
||
foreach ($evt in $evtNode.SelectNodes("f:Events/f:Event[@callType]", $fNs)) {
|
||
$ct = $evt.GetAttribute("callType")
|
||
$interceptors += "Element:${elName}.$($evt.GetAttribute('name')) [$ct] -> $($evt.InnerText)"
|
||
}
|
||
}
|
||
}
|
||
|
||
# Commands with callType on Action
|
||
foreach ($cmd in $fRoot.SelectNodes("f:Commands/f:Command", $fNs)) {
|
||
$cmdName = $cmd.GetAttribute("name")
|
||
foreach ($action in $cmd.SelectNodes("f:Action[@callType]", $fNs)) {
|
||
$ct = $action.GetAttribute("callType")
|
||
$interceptors += "Command:$cmdName [$ct] -> $($action.InnerText)"
|
||
}
|
||
}
|
||
|
||
return @{
|
||
IsBorrowed = $isBorrowed
|
||
Interceptors = $interceptors
|
||
}
|
||
}
|
||
|
||
# ============================================================
|
||
# MODE A: Extension overview
|
||
# ============================================================
|
||
if ($Mode -eq "A") {
|
||
$borrowedList = @()
|
||
$ownList = @()
|
||
|
||
foreach ($obj in $objects) {
|
||
$info = Get-ObjectInfo $obj.Type $obj.Name
|
||
if (-not $info) {
|
||
Write-Host " [?] $($obj.Type).$($obj.Name) — unknown type"
|
||
continue
|
||
}
|
||
if (-not $info.Exists) {
|
||
Write-Host " [?] $($obj.Type).$($obj.Name) — file not found"
|
||
continue
|
||
}
|
||
|
||
if ($info.Borrowed) {
|
||
$borrowedList += $obj
|
||
|
||
Write-Host " [BORROWED] $($obj.Type).$($obj.Name)"
|
||
|
||
# Find .bsl files and interceptors
|
||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||
foreach ($bsl in $bslFiles) {
|
||
$relPath = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||
$interceptors = Get-FileInterceptors $bsl
|
||
if ($interceptors.Count -gt 0) {
|
||
foreach ($ic in $interceptors) {
|
||
Write-Host " &$($ic.Raw)(`"$($ic.Method)`") — line $($ic.Line) in $relPath"
|
||
}
|
||
} else {
|
||
Write-Host " $relPath (no interceptors)"
|
||
}
|
||
}
|
||
|
||
# Check for own attributes/forms in ChildObjects
|
||
if ($info.ObjElement) {
|
||
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
||
if ($childObj) {
|
||
$ownAttrs = 0
|
||
$ownForms = 0
|
||
$ownTS = 0
|
||
$borrowedItems = 0
|
||
$formNames = @()
|
||
foreach ($c in $childObj.ChildNodes) {
|
||
if ($c.NodeType -ne 'Element') { continue }
|
||
$cProps = $c.SelectSingleNode("md:Properties", $info.ObjNs)
|
||
if ($cProps) {
|
||
$cOb = $cProps.SelectSingleNode("md:ObjectBelonging", $info.ObjNs)
|
||
if ($cOb -and $cOb.InnerText -eq "Adopted") {
|
||
$borrowedItems++
|
||
continue
|
||
}
|
||
}
|
||
switch ($c.LocalName) {
|
||
"Attribute" { $ownAttrs++ }
|
||
"TabularSection" { $ownTS++ }
|
||
"Form" { $formNames += $c.InnerText; $ownForms++ }
|
||
}
|
||
}
|
||
$parts = @()
|
||
if ($ownAttrs -gt 0) { $parts += "$ownAttrs own attrs" }
|
||
if ($ownTS -gt 0) { $parts += "$ownTS own TS" }
|
||
if ($ownForms -gt 0) { $parts += "$ownForms own forms" }
|
||
if ($borrowedItems -gt 0) { $parts += "$borrowedItems borrowed items" }
|
||
if ($parts.Count -gt 0) {
|
||
Write-Host " ChildObjects: $($parts -join ', ')"
|
||
}
|
||
|
||
# Analyze forms
|
||
$borrowedFormCount = 0
|
||
$ownFormCount = 0
|
||
foreach ($fn in $formNames) {
|
||
$formXmlPath = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Forms") $fn) "Ext/Form.xml"
|
||
$fi = Get-FormInterceptors $formXmlPath
|
||
if (-not $fi) {
|
||
Write-Host " Form.$fn (?)"
|
||
continue
|
||
}
|
||
$formTag = if ($fi.IsBorrowed) { "borrowed"; $borrowedFormCount++ } else { "own"; $ownFormCount++ }
|
||
if ($fi.Interceptors.Count -gt 0) {
|
||
Write-Host " Form.$fn ($formTag):"
|
||
foreach ($ic in $fi.Interceptors) {
|
||
Write-Host " $ic"
|
||
}
|
||
} else {
|
||
Write-Host " Form.$fn ($formTag)"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# Общая форма — сама форма: перехваты событий и команд из её Ext/Form.xml
|
||
if ($obj.Type -eq "CommonForm") {
|
||
$cfFormXml = Join-Path (Join-Path (Join-Path (Join-Path $ExtensionPath $info.DirName) $info.Name) "Ext") "Form.xml"
|
||
$cfi = Get-FormInterceptors $cfFormXml
|
||
if ($cfi -and $cfi.Interceptors.Count -gt 0) {
|
||
Write-Host " Form:"
|
||
foreach ($ic in $cfi.Interceptors) { Write-Host " $ic" }
|
||
}
|
||
}
|
||
} else {
|
||
$ownList += $obj
|
||
Write-Host " [OWN] $($obj.Type).$($obj.Name)"
|
||
|
||
# Brief info for own objects
|
||
if ($info.ObjElement) {
|
||
$childObj = $info.ObjElement.SelectSingleNode("md:ChildObjects", $info.ObjNs)
|
||
if ($childObj) {
|
||
$attrs = 0; $forms = 0; $ts = 0
|
||
foreach ($c in $childObj.ChildNodes) {
|
||
if ($c.NodeType -ne 'Element') { continue }
|
||
switch ($c.LocalName) {
|
||
"Attribute" { $attrs++ }
|
||
"TabularSection" { $ts++ }
|
||
"Form" { $forms++ }
|
||
}
|
||
}
|
||
$parts = @()
|
||
if ($attrs -gt 0) { $parts += "$attrs attrs" }
|
||
if ($ts -gt 0) { $parts += "$ts TS" }
|
||
if ($forms -gt 0) { $parts += "$forms forms" }
|
||
if ($parts.Count -gt 0) {
|
||
Write-Host " $($parts -join ', ')"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "=== Summary: $($borrowedList.Count) borrowed, $($ownList.Count) own objects ==="
|
||
}
|
||
|
||
# ============================================================
|
||
# MODE B: Transfer check
|
||
# ============================================================
|
||
if ($Mode -eq "B") {
|
||
$transferred = 0
|
||
$notTransferred = 0
|
||
$needsReview = 0
|
||
|
||
foreach ($obj in $objects) {
|
||
$info = Get-ObjectInfo $obj.Type $obj.Name
|
||
if (-not $info -or -not $info.Exists -or -not $info.Borrowed) { continue }
|
||
|
||
# Find .bsl files with &ИзменениеИКонтроль
|
||
$bslFiles = Get-BslFiles $obj.Type $obj.Name
|
||
foreach ($bsl in $bslFiles) {
|
||
$interceptors = Get-FileInterceptors $bsl
|
||
$macInterceptors = @($interceptors | Where-Object { $_.Type -eq "ИзменениеИКонтроль" })
|
||
|
||
if ($macInterceptors.Count -eq 0) { continue }
|
||
|
||
foreach ($ic in $macInterceptors) {
|
||
$methodName = $ic.Method
|
||
$relBsl = $bsl.Replace($ExtensionPath, "").TrimStart("\", "/")
|
||
|
||
# Find #Вставка blocks in this file
|
||
$insertBlocks = Get-InsertionBlocks $bsl
|
||
|
||
if ($insertBlocks.Count -eq 0) {
|
||
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — no #Вставка blocks"
|
||
$needsReview++
|
||
continue
|
||
}
|
||
|
||
# Find corresponding module in config
|
||
if (-not $childTypeDirMap.ContainsKey($obj.Type)) { continue }
|
||
$dirName = $childTypeDirMap[$obj.Type]
|
||
$configBsl = $bsl.Replace($ExtensionPath, $ConfigPath)
|
||
|
||
if (-not (Test-Path $configBsl)) {
|
||
Write-Host " [NEEDS_REVIEW] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — config module not found"
|
||
$needsReview++
|
||
continue
|
||
}
|
||
|
||
$configContent = [System.IO.File]::ReadAllText($configBsl, [System.Text.Encoding]::UTF8)
|
||
|
||
$allTransferred = $true
|
||
foreach ($block in $insertBlocks) {
|
||
$code = $block.Code
|
||
if (-not $code) { continue }
|
||
|
||
# Normalize whitespace for comparison
|
||
$codeNorm = $code -replace '\s+', ' '
|
||
$configNorm = $configContent -replace '\s+', ' '
|
||
|
||
if ($configNorm.Contains($codeNorm)) {
|
||
# Found in config
|
||
} else {
|
||
$allTransferred = $false
|
||
}
|
||
}
|
||
|
||
if ($allTransferred) {
|
||
Write-Host " [TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — $($insertBlocks.Count) block(s)"
|
||
$transferred++
|
||
} else {
|
||
Write-Host " [NOT_TRANSFERRED] $($obj.Type).$($obj.Name) — &ИзменениеИКонтроль(`"$methodName`") — some blocks not found in config"
|
||
$notTransferred++
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
Write-Host ""
|
||
Write-Host "=== Transfer check: $transferred transferred, $notTransferred not transferred, $needsReview needs review ==="
|
||
}
|