mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-21 17:55:53 +03:00
fix(meta-info): множества типов (v8:TypeSet) показываются как тип
Источники подписки на событие читались только из v8:Type. Если источник задан множеством — определяемым типом или целым классом «все документы» — строки «Источники» не было вовсе, и подписка выглядела так, будто ни на что не срабатывает. В erp_8.3.24 так задана треть подписок: 151 из 492 вообще без v8:Type, в acc_8.3.24 — 126 из 443. Тот же дефект в типах реквизитов, и там он чаще. Вхождений v8:TypeSet>cfg:DefinedType в Catalogs/Documents/InformationRegisters/ AccumulationRegisters ERP — 8841, из них 7575 (86%) указывают на определяемый тип ровно с одним членом. АвансовыйОтчет печатал «Сумма ОпределяемыйТип.ДенежнаяСуммаНеотрицательная», и было не видно, что это Число(15,2): имя выглядит как ссылочный тип. Голый метатип-категория («ДокументСсылка» без точки = любой документ) отличался от конкретного «ДокументСсылка.Заказ» только наличием точки посреди длинного имени. Правило одно на все места: имя множества стоит в строке типа, а раскрытие — ровно одной записью на уникальное множество, глоссарием в конце вывода. Раскрывать в строке нельзя дважды: псевдоним повторяется в объекте десятками раз (в АвансовомОтчете — 12), а в корпусе есть определяемые типы на 596 типов — вывод упёрся бы в постраничник и съел хвост объекта. Выше порога composedTypeThreshold вместо состава идут счётчик и готовая команда раскрытия. Голый метатип получает суффикс «(все)»: разница становится словом, а не пунктуацией. Характеристика ПВХ помечена как набор, определяемый данными, — из выгрузки её состав не берётся в принципе. Попутно того же класса: - объектные типы (DocumentObject.X, *RecordSet.X, *Manager.X) печатались по-английски в выводе определяемого типа и по-русски в источниках подписки — одно понятие двумя видами; в карту добавлены недостающие виды, включая ConstantValueManager и ChartOfCalculationTypesObject; - собственный вывод определяемого типа читал только v8:Type и молча терял v8:TypeSet — счётчик «Типы (N)» врал; - корень конфигурации ищется климбом до Configuration.xml, общим с проверкой поддержки, а не фиксированным «на два уровня выше»; - состав определяемого типа кэшируется по имени; - нечитаемый файл типа назван нечитаемым, а не отсутствующим. Проверено корпусом: 5129 прогонов по erp_8.3.24 и acc_8.3.24 в трёх режимах, каждое расхождение с эталоном до правки классифицировано, необъяснённых нет. Паритет портов — 5129 из 5129, 103912 строк совпали. Проблему и замеры по подпискам сообщил автор PR #96. Co-Authored-By: Шпаков Антон Александрович <shpakov.anton.job@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Шпаков Антон Александрович
Claude Opus 5
parent
77c797e6d0
commit
7cbc8e9b7f
@@ -1,4 +1,4 @@
|
||||
# meta-info v1.14 — Compact summary of 1C metadata object
|
||||
# meta-info v1.15 — Compact summary of 1C metadata object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -107,6 +107,9 @@ $typeNameMap = @{
|
||||
"ExternalDataSource"="Внешний источник данных"; "Table"="Таблица внешнего источника"
|
||||
}
|
||||
|
||||
# Долгие виды сокращаем (ПВХ, РС/РН/РБ/РР): в списке на сорок реквизитов повторяющийся префикс
|
||||
# съедает колонку и прячет отличающуюся часть — имя объекта. Аббревиатуры приняты как ВВОД в
|
||||
# meta-compile/meta-edit (словарь typeSynonyms), поэтому строку из вывода можно подать обратно.
|
||||
$refTypeMap = @{
|
||||
"CatalogRef"="СправочникСсылка"; "DocumentRef"="ДокументСсылка"
|
||||
"EnumRef"="ПеречислениеСсылка"; "ChartOfAccountsRef"="ПланСчетовСсылка"
|
||||
@@ -146,11 +149,32 @@ $objectTypeMap = @{
|
||||
"CatalogObject"="СправочникОбъект"; "DocumentObject"="ДокументОбъект"
|
||||
"ChartOfAccountsObject"="ПланСчетовОбъект"
|
||||
"ChartOfCharacteristicTypesObject"="ПВХОбъект"
|
||||
"ChartOfCalculationTypesObject"="ПВРОбъект"
|
||||
"BusinessProcessObject"="БизнесПроцессОбъект"; "TaskObject"="ЗадачаОбъект"
|
||||
"ExchangePlanObject"="ПланОбменаОбъект"
|
||||
"InformationRegisterRecordSet"="НаборЗаписейРС"
|
||||
"AccumulationRegisterRecordSet"="НаборЗаписейРН"
|
||||
"AccountingRegisterRecordSet"="НаборЗаписейРБ"
|
||||
"CalculationRegisterRecordSet"="НаборЗаписейРР"
|
||||
"SequenceRecordSet"="НаборЗаписейПоследовательности"
|
||||
"RecalculationRecordSet"="НаборЗаписейПерерасчета"
|
||||
# Менеджеры встречаются в источниках подписок. Сиблинга-аббревиатуры у них нет
|
||||
# (единственный ориентир — КонстантаМенеджерЗначения), поэтому имена полные.
|
||||
"CatalogManager"="СправочникМенеджер"; "DocumentManager"="ДокументМенеджер"
|
||||
"DocumentJournalManager"="ЖурналДокументовМенеджер"
|
||||
"EnumManager"="ПеречислениеМенеджер"
|
||||
"ConstantValueManager"="КонстантаМенеджерЗначения"
|
||||
"InformationRegisterManager"="РегистрСведенийМенеджер"
|
||||
"AccumulationRegisterManager"="РегистрНакопленияМенеджер"
|
||||
"AccountingRegisterManager"="РегистрБухгалтерииМенеджер"
|
||||
"CalculationRegisterManager"="РегистрРасчетаМенеджер"
|
||||
"ChartOfAccountsManager"="ПланСчетовМенеджер"
|
||||
"ChartOfCharacteristicTypesManager"="ПВХМенеджер"
|
||||
"ChartOfCalculationTypesManager"="ПВРМенеджер"
|
||||
"ExchangePlanManager"="ПланОбменаМенеджер"
|
||||
"BusinessProcessManager"="БизнесПроцессМенеджер"
|
||||
"TaskManager"="ЗадачаМенеджер"
|
||||
"DataProcessorManager"="ОбработкаМенеджер"; "ReportManager"="ОтчетМенеджер"
|
||||
}
|
||||
|
||||
$numberPeriodMap = @{
|
||||
@@ -173,22 +197,142 @@ function Get-MLText($node) {
|
||||
return ""
|
||||
}
|
||||
|
||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
||||
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
|
||||
# Корень конфигурации — ближайший каталог выше объекта, где лежит Configuration.xml (или
|
||||
# Ext/ParentConfigurations.bin у выгрузки на поддержке). Фиксированное «на два уровня выше» тут
|
||||
# неверно: объект бывает подан из другого места дерева, а у внешней обработки корня нет вовсе.
|
||||
function Find-ConfigRootDir {
|
||||
$d = [System.IO.Path]::GetDirectoryName($ObjectPath)
|
||||
for ($i = 0; $i -lt 8 -and $d; $i++) {
|
||||
if ((Test-Path (Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin")) -or
|
||||
(Test-Path (Join-Path $d "Configuration.xml"))) { return $d }
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
# Множество типов (v8:TypeSet) — индирекция: в строке типа стоит имя, а состав лежит в другом
|
||||
# файле (определяемый тип) или определяется данными (характеристика ПВХ). Раскрывать состав в
|
||||
# строке типа нельзя дважды: один псевдоним встречается в объекте десятками раз (в АвансовомОтчете
|
||||
# `ДенежнаяСумма*` — 12 раз), а в корпусе есть определяемые типы на 596 типов — вывод упёрся бы в
|
||||
# постраничник и съел хвост объекта. Поэтому имя стоит встроенно, а раскрытие идёт ровно одной
|
||||
# записью на уникальное множество, глоссарием в конце вывода.
|
||||
$script:typeSetGlossary = New-Object System.Collections.Specialized.OrderedDictionary
|
||||
$script:definedTypeCache = @{}
|
||||
$script:configRootProbed = $false
|
||||
$script:configRootDir = $null
|
||||
# Состав определяемого типа сам может содержать множество. Внутрь раскрытия не углубляемся: там
|
||||
# имя уже достаточно, а рекурсия по самоссылочному типу не завершилась бы.
|
||||
$script:inTypeSetExpansion = $false
|
||||
|
||||
function Get-ConfigRootDir {
|
||||
if (-not $script:configRootProbed) {
|
||||
$script:configRootProbed = $true
|
||||
$script:configRootDir = Find-ConfigRootDir
|
||||
}
|
||||
return $script:configRootDir
|
||||
}
|
||||
|
||||
function Resolve-DefinedType([string]$dtName) {
|
||||
if ($script:definedTypeCache.ContainsKey($dtName)) { return $script:definedTypeCache[$dtName] }
|
||||
# Broken отличаем от Found: файл на месте, но не разобрался — сказать «файла нет» было бы
|
||||
# враньём, а молчать — тем же тихим отказом, от которого лечим.
|
||||
$res = @{ Found = $false; Broken = $false; Members = @() }
|
||||
$root = Get-ConfigRootDir
|
||||
if ($root) {
|
||||
$dtPath = Join-Path (Join-Path $root "DefinedTypes") "$dtName.xml"
|
||||
if (Test-Path -LiteralPath $dtPath) {
|
||||
try {
|
||||
[xml]$dtDoc = Get-Content -LiteralPath $dtPath -Encoding UTF8
|
||||
$dtNs = New-Object System.Xml.XmlNamespaceManager($dtDoc.NameTable)
|
||||
$dtNs.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$dtNs.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
$tn = $dtDoc.SelectSingleNode("/md:MetaDataObject/md:DefinedType/md:Properties/md:Type", $dtNs)
|
||||
if ($tn) {
|
||||
$res.Found = $true
|
||||
$members = @()
|
||||
$script:inTypeSetExpansion = $true
|
||||
try {
|
||||
foreach ($c in $tn.ChildNodes) {
|
||||
if ($c.NodeType -ne 'Element') { continue }
|
||||
# .LocalName у XML-адаптера PS перекрывается одноимённым атрибутом — get_LocalName().
|
||||
switch ($c.get_LocalName()) {
|
||||
'Type' { $members += Format-SingleType $c.InnerText $tn }
|
||||
'TypeSet' { $members += Format-SingleTypeSet $c.InnerText }
|
||||
}
|
||||
}
|
||||
} finally { $script:inTypeSetExpansion = $false }
|
||||
$res.Members = $members
|
||||
}
|
||||
} catch { $res.Broken = $true }
|
||||
if (-not $res.Found) { $res.Broken = $true }
|
||||
}
|
||||
}
|
||||
$script:definedTypeCache[$dtName] = $res
|
||||
return $res
|
||||
}
|
||||
|
||||
function Register-TypeSet([string]$label, [string]$kind, [string]$name) {
|
||||
if ($script:inTypeSetExpansion) { return }
|
||||
if ($script:typeSetGlossary.Contains($label)) { return }
|
||||
$script:typeSetGlossary.Add($label, @{ Kind = $kind; Name = $name })
|
||||
}
|
||||
|
||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ объекты этого класса
|
||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой, но
|
||||
# отличать «все» от «один» по наличию точки посреди длинного имени — сигнал низкой различимости,
|
||||
# поэтому обобщённый метатип получает суффикс «(все)»: разница становится словом, а не пунктуацией.
|
||||
function Format-SingleTypeSet([string]$raw) {
|
||||
$raw = $raw -replace '^d\d+p\d+:', 'cfg:'
|
||||
if ($raw -match '^cfg:DefinedType\.(.+)$') { return "ОпределяемыйТип.$($Matches[1])" }
|
||||
if ($raw -match '^cfg:Characteristic\.(.+)$') { return "Характеристика.$($Matches[1])" }
|
||||
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
||||
$label = "ОпределяемыйТип.$($Matches[1])"
|
||||
Register-TypeSet $label 'DefinedType' $Matches[1]
|
||||
return $label
|
||||
}
|
||||
if ($raw -match '^cfg:Characteristic\.(.+)$') {
|
||||
$label = "Характеристика.$($Matches[1])"
|
||||
Register-TypeSet $label 'Characteristic' $Matches[1]
|
||||
return $label
|
||||
}
|
||||
if ($raw -eq 'cfg:AnyRef') { return "ЛюбаяСсылка" }
|
||||
if ($raw -eq 'cfg:AnyIBRef') { return "ЛюбаяСсылкаИБ" }
|
||||
if ($raw -match '^cfg:(\w+Ref)$' -and $refTypeMap.ContainsKey($Matches[1])) {
|
||||
return $refTypeMap[$Matches[1]]
|
||||
return "$($refTypeMap[$Matches[1]]) (все)"
|
||||
}
|
||||
if ($raw -match '^cfg:(\w+)$' -and $objectTypeMap.ContainsKey($Matches[1])) {
|
||||
return "$($objectTypeMap[$Matches[1]]) (все)"
|
||||
}
|
||||
if ($raw -match '^cfg:(.+)$') { return $Matches[1] }
|
||||
return $raw
|
||||
}
|
||||
|
||||
# Глоссарий множеств: одна запись на уникальное множество, в порядке первого упоминания.
|
||||
function Get-TypeSetGlossaryLines {
|
||||
$lines = @()
|
||||
foreach ($label in $script:typeSetGlossary.Keys) {
|
||||
$e = $script:typeSetGlossary[$label]
|
||||
if ($e.Kind -eq 'Characteristic') {
|
||||
$lines += " $label — набор определяется данными ПВХ"
|
||||
continue
|
||||
}
|
||||
$dt = Resolve-DefinedType $e.Name
|
||||
if (-not $dt.Found) {
|
||||
$lines += if ($dt.Broken) { " $label — файл типа не разобран" } else { " $label — файла типа нет в выгрузке" }
|
||||
continue
|
||||
}
|
||||
$members = @($dt.Members)
|
||||
if ($members.Count -eq 0) { $lines += " $label — состав пуст"; continue }
|
||||
if ($members.Count -le $script:composedTypeThreshold) {
|
||||
$lines += " $label → $($members -join ' | ')"
|
||||
} else {
|
||||
$lines += " $label — типов: $($members.Count)"
|
||||
$lines += " раскрыть: meta-info -ObjectPath DefinedTypes/$($e.Name).xml -Mode full"
|
||||
}
|
||||
}
|
||||
return $lines
|
||||
}
|
||||
|
||||
function Format-Type($typeNode) {
|
||||
if (-not $typeNode) { return "" }
|
||||
$types = @()
|
||||
@@ -261,6 +405,12 @@ function Format-SingleType([string]$raw, $parentNode) {
|
||||
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
||||
return "ОпределяемыйТип.$($Matches[1])"
|
||||
}
|
||||
# cfg:DocumentObject.Xxx / cfg:InformationRegisterRecordSet.Xxx — объектные типы.
|
||||
# Без карты они уходили в ветку «снять cfg:» и печатались по-английски, хотя в
|
||||
# источниках подписки те же типы печатались по-русски: одно понятие двумя видами.
|
||||
if ($raw -match '^cfg:(\w+)\.(.+)$' -and $objectTypeMap.ContainsKey($Matches[1])) {
|
||||
return "$($objectTypeMap[$Matches[1]]).$($Matches[2])"
|
||||
}
|
||||
# Strip cfg: prefix for unknown
|
||||
if ($raw -match '^cfg:(.+)$') {
|
||||
return $Matches[1]
|
||||
@@ -582,6 +732,25 @@ function Format-SourceType([string]$raw) {
|
||||
return $raw
|
||||
}
|
||||
|
||||
# Источники подписки задаются и списком типов (v8:Type), и множеством (v8:TypeSet — определяемый
|
||||
# тип или целый класс «все документы»). В корпусе множеством задана треть подписок (в erp_8.3.24
|
||||
# 151 из 492 — вообще без v8:Type), поэтому чтение только v8:Type оставляло их без строки
|
||||
# «Источники»: подписка выглядела так, будто ни на что не срабатывает. Оба узла читаем одним
|
||||
# XPath, в порядке документа — так же, как meta-decompile.
|
||||
#
|
||||
# Множества и явные типы возвращаем врозь: явных бывает много (в корпусе до 1353 в одной
|
||||
# подписке), а множеств — не больше восьми. Поэтому в сводке множества называем всегда, а
|
||||
# явные типы сворачиваем в счётчик: по одному числу не видно, что подписка бьёт по целому классу.
|
||||
function Get-SubscriptionSourceList($sourceNode) {
|
||||
$types = @()
|
||||
$sets = @()
|
||||
foreach ($t in $sourceNode.SelectNodes("v8:Type|v8:TypeSet", $ns)) {
|
||||
if ($t.get_LocalName() -eq 'TypeSet') { $sets += Format-SingleTypeSet $t.InnerText }
|
||||
else { $types += Format-SourceType $t.InnerText }
|
||||
}
|
||||
return [pscustomobject]@{ Types = $types; Sets = $sets }
|
||||
}
|
||||
|
||||
function Get-HTTPEndpoints($childObjs) {
|
||||
$result = @()
|
||||
foreach ($tpl in $childObjs.SelectNodes("md:URLTemplate", $ns)) {
|
||||
@@ -646,16 +815,9 @@ function Test-ExternalObjectRoot([string]$xmlPath) {
|
||||
function Get-ObjectSupportStatus([string]$objUuid) {
|
||||
try {
|
||||
if (Test-ExternalObjectRoot $ObjectPath) { return $null }
|
||||
# Walk up to the config root (dir with Configuration.xml or Ext/ParentConfigurations.bin).
|
||||
$d = [System.IO.Path]::GetDirectoryName($ObjectPath)
|
||||
$binPath = $null
|
||||
for ($i = 0; $i -lt 8 -and $d; $i++) {
|
||||
$cand = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin"
|
||||
if ((Test-Path $cand) -or (Test-Path (Join-Path $d "Configuration.xml"))) { $binPath = $cand; break }
|
||||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
# Корень конфигурации ищем тем же климбом, что и состав определяемых типов.
|
||||
$root = Get-ConfigRootDir
|
||||
$binPath = if ($root) { Join-Path (Join-Path $root "Ext") "ParentConfigurations.bin" } else { $null }
|
||||
if (-not $binPath -or -not (Test-Path $binPath)) { return "не на поддержке" }
|
||||
$bytes = [System.IO.File]::ReadAllBytes($binPath)
|
||||
if ($bytes.Length -le 32) { return "снято с поддержки (правки свободны)" }
|
||||
@@ -996,9 +1158,13 @@ if (-not $drillDone) {
|
||||
if ($mdType -eq "DefinedType") {
|
||||
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
||||
if ($typeNode2) {
|
||||
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||
# «Типы (N)» врал.
|
||||
$types = @()
|
||||
foreach ($t in $typeNode2.SelectNodes("v8:Type", $ns)) {
|
||||
$types += Format-SingleType $t.InnerText $typeNode2
|
||||
foreach ($t in $typeNode2.SelectNodes("v8:Type|v8:TypeSet", $ns)) {
|
||||
if ($t.get_LocalName() -eq 'TypeSet') { $types += Format-SingleTypeSet $t.InnerText }
|
||||
else { $types += Format-SingleType $t.InnerText $typeNode2 }
|
||||
}
|
||||
if ($types.Count -gt 0) {
|
||||
Out "Типы ($($types.Count)): $($types -join ', ')"
|
||||
@@ -1061,8 +1227,17 @@ if (-not $drillDone) {
|
||||
}
|
||||
$source = $props.SelectSingleNode("md:Source", $ns)
|
||||
if ($source) {
|
||||
$srcCount = $source.SelectNodes("v8:Type", $ns).Count
|
||||
if ($srcCount -gt 0) { $esParts += "Источники: $srcCount" }
|
||||
# brief называет множества, а явные типы считает: по одному числу не видно, что
|
||||
# подписка срабатывает на целый класс документов или на состав определяемого типа.
|
||||
$src = Get-SubscriptionSourceList $source
|
||||
$srcTypes = @($src.Types); $srcSets = @($src.Sets)
|
||||
$total = $srcTypes.Count + $srcSets.Count
|
||||
if ($total -gt 0) {
|
||||
$parts2 = @()
|
||||
if ($srcSets.Count -gt 0) { $parts2 += $srcSets }
|
||||
if ($srcTypes.Count -gt 0) { $parts2 += "явных типов: $($srcTypes.Count)" }
|
||||
$esParts += "Источники ($total): $($parts2 -join ', ')"
|
||||
}
|
||||
}
|
||||
if ($esParts.Count -gt 0) { Out ($esParts -join " | ") }
|
||||
}
|
||||
@@ -1185,9 +1360,13 @@ if (-not $drillDone) {
|
||||
if ($mdType -eq "DefinedType") {
|
||||
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
||||
if ($typeNode2) {
|
||||
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||
# «Типы (N)» врал.
|
||||
$types = @()
|
||||
foreach ($t in $typeNode2.SelectNodes("v8:Type", $ns)) {
|
||||
$types += Format-SingleType $t.InnerText $typeNode2
|
||||
foreach ($t in $typeNode2.SelectNodes("v8:Type|v8:TypeSet", $ns)) {
|
||||
if ($t.get_LocalName() -eq 'TypeSet') { $types += Format-SingleTypeSet $t.InnerText }
|
||||
else { $types += Format-SingleType $t.InnerText $typeNode2 }
|
||||
}
|
||||
if ($types.Count -gt 0) {
|
||||
Out "Типы ($($types.Count)):"
|
||||
@@ -1250,16 +1429,18 @@ if (-not $drillDone) {
|
||||
}
|
||||
$source = $props.SelectSingleNode("md:Source", $ns)
|
||||
if ($source) {
|
||||
$srcTypes = @()
|
||||
foreach ($t in $source.SelectNodes("v8:Type", $ns)) {
|
||||
$srcTypes += Format-SourceType $t.InnerText
|
||||
}
|
||||
if ($srcTypes.Count -gt 0) {
|
||||
if ($Mode -eq "full") {
|
||||
Out "Источники ($($srcTypes.Count)):"
|
||||
foreach ($s in $srcTypes) { Out " $s" }
|
||||
} else {
|
||||
Out "Источники ($($srcTypes.Count))"
|
||||
$src = Get-SubscriptionSourceList $source
|
||||
$srcTypes = @($src.Types); $srcSets = @($src.Sets)
|
||||
$total = $srcTypes.Count + $srcSets.Count
|
||||
if ($total -gt 0) {
|
||||
Out "Источники ($total):"
|
||||
# full печатает всё: режим для этого и нужен, а длину держит постраничник.
|
||||
# В overview явные типы сворачиваем в счётчик — их бывает больше тысячи.
|
||||
$listTypes = ($Mode -eq "full") -or ($total -le $script:composedTypeThreshold)
|
||||
if ($listTypes) { foreach ($s in $srcTypes) { Out " $s" } }
|
||||
foreach ($s in $srcSets) { Out " $s" }
|
||||
if (-not $listTypes -and $srcTypes.Count -gt 0) {
|
||||
Out " и ещё явных типов: $($srcTypes.Count) (-Mode full)"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1532,6 +1713,18 @@ if (-not $drillDone) {
|
||||
}
|
||||
}
|
||||
|
||||
# Глоссарий множеств — после всего остального: в строках типов стоят только имена, и это
|
||||
# единственное место, где имя превращается в состав. brief не глоссируем: там типов реквизитов
|
||||
# нет вовсе, а множества источников подписки он называет прямо в строке.
|
||||
if ($Mode -ne "brief" -and $script:typeSetGlossary.Count -gt 0) {
|
||||
$glossary = @(Get-TypeSetGlossaryLines)
|
||||
if ($glossary.Count -gt 0) {
|
||||
Out ""
|
||||
Out "Множества типов в составе:"
|
||||
foreach ($l in $glossary) { Out $l }
|
||||
}
|
||||
}
|
||||
|
||||
# --- Pagination and output ---
|
||||
$totalLines = $script:lines.Count
|
||||
$outLines = $script:lines
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-info v1.14 — Compact summary of 1C metadata object (Python port)
|
||||
# meta-info v1.15 — Compact summary of 1C metadata object (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -180,6 +180,9 @@ type_name_map = {
|
||||
"ExternalDataSource": "Внешний источник данных", "Table": "Таблица внешнего источника",
|
||||
}
|
||||
|
||||
# Долгие виды сокращаем (ПВХ, РС/РН/РБ/РР): в списке на сорок реквизитов повторяющийся префикс
|
||||
# съедает колонку и прячет отличающуюся часть — имя объекта. Аббревиатуры приняты как ВВОД в
|
||||
# meta-compile/meta-edit (словарь typeSynonyms), поэтому строку из вывода можно подать обратно.
|
||||
ref_type_map = {
|
||||
"CatalogRef": "СправочникСсылка", "DocumentRef": "ДокументСсылка",
|
||||
"EnumRef": "ПеречислениеСсылка", "ChartOfAccountsRef": "ПланСчетовСсылка",
|
||||
@@ -219,11 +222,32 @@ object_type_map = {
|
||||
"CatalogObject": "СправочникОбъект", "DocumentObject": "ДокументОбъект",
|
||||
"ChartOfAccountsObject": "ПланСчетовОбъект",
|
||||
"ChartOfCharacteristicTypesObject": "ПВХОбъект",
|
||||
"ChartOfCalculationTypesObject": "ПВРОбъект",
|
||||
"BusinessProcessObject": "БизнесПроцессОбъект", "TaskObject": "ЗадачаОбъект",
|
||||
"ExchangePlanObject": "ПланОбменаОбъект",
|
||||
"InformationRegisterRecordSet": "НаборЗаписейРС",
|
||||
"AccumulationRegisterRecordSet": "НаборЗаписейРН",
|
||||
"AccountingRegisterRecordSet": "НаборЗаписейРБ",
|
||||
"CalculationRegisterRecordSet": "НаборЗаписейРР",
|
||||
"SequenceRecordSet": "НаборЗаписейПоследовательности",
|
||||
"RecalculationRecordSet": "НаборЗаписейПерерасчета",
|
||||
# Менеджеры встречаются в источниках подписок. Сиблинга-аббревиатуры у них нет
|
||||
# (единственный ориентир — КонстантаМенеджерЗначения), поэтому имена полные.
|
||||
"CatalogManager": "СправочникМенеджер", "DocumentManager": "ДокументМенеджер",
|
||||
"DocumentJournalManager": "ЖурналДокументовМенеджер",
|
||||
"EnumManager": "ПеречислениеМенеджер",
|
||||
"ConstantValueManager": "КонстантаМенеджерЗначения",
|
||||
"InformationRegisterManager": "РегистрСведенийМенеджер",
|
||||
"AccumulationRegisterManager": "РегистрНакопленияМенеджер",
|
||||
"AccountingRegisterManager": "РегистрБухгалтерииМенеджер",
|
||||
"CalculationRegisterManager": "РегистрРасчетаМенеджер",
|
||||
"ChartOfAccountsManager": "ПланСчетовМенеджер",
|
||||
"ChartOfCharacteristicTypesManager": "ПВХМенеджер",
|
||||
"ChartOfCalculationTypesManager": "ПВРМенеджер",
|
||||
"ExchangePlanManager": "ПланОбменаМенеджер",
|
||||
"BusinessProcessManager": "БизнесПроцессМенеджер",
|
||||
"TaskManager": "ЗадачаМенеджер",
|
||||
"DataProcessorManager": "ОбработкаМенеджер", "ReportManager": "ОтчетМенеджер",
|
||||
}
|
||||
|
||||
number_period_map = {
|
||||
@@ -251,30 +275,143 @@ def get_ml_text(node):
|
||||
return ""
|
||||
|
||||
|
||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
||||
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
|
||||
# Корень конфигурации — ближайший каталог выше объекта, где лежит Configuration.xml (или
|
||||
# Ext/ParentConfigurations.bin у выгрузки на поддержке). Фиксированное «на два уровня выше» тут
|
||||
# неверно: объект бывает подан из другого места дерева, а у внешней обработки корня нет вовсе.
|
||||
def find_config_root_dir():
|
||||
d = os.path.dirname(object_path)
|
||||
for _ in range(8):
|
||||
if not d:
|
||||
break
|
||||
if (os.path.exists(os.path.join(d, "Ext", "ParentConfigurations.bin"))
|
||||
or os.path.exists(os.path.join(d, "Configuration.xml"))):
|
||||
return d
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return None
|
||||
|
||||
|
||||
# Множество типов (v8:TypeSet) — индирекция: в строке типа стоит имя, а состав лежит в другом
|
||||
# файле (определяемый тип) или определяется данными (характеристика ПВХ). Раскрывать состав в
|
||||
# строке типа нельзя дважды: один псевдоним встречается в объекте десятками раз (в АвансовомОтчете
|
||||
# `ДенежнаяСумма*` — 12 раз), а в корпусе есть определяемые типы на 596 типов — вывод упёрся бы в
|
||||
# постраничник и съел хвост объекта. Поэтому имя стоит встроенно, а раскрытие идёт ровно одной
|
||||
# записью на уникальное множество, глоссарием в конце вывода.
|
||||
type_set_glossary = {}
|
||||
defined_type_cache = {}
|
||||
_config_root_state = {"probed": False, "dir": None}
|
||||
# Состав определяемого типа сам может содержать множество. Внутрь раскрытия не углубляемся: там
|
||||
# имя уже достаточно, а рекурсия по самоссылочному типу не завершилась бы.
|
||||
_in_type_set_expansion = {"on": False}
|
||||
|
||||
|
||||
def get_config_root_dir():
|
||||
if not _config_root_state["probed"]:
|
||||
_config_root_state["probed"] = True
|
||||
_config_root_state["dir"] = find_config_root_dir()
|
||||
return _config_root_state["dir"]
|
||||
|
||||
|
||||
def resolve_defined_type(dt_name):
|
||||
if dt_name in defined_type_cache:
|
||||
return defined_type_cache[dt_name]
|
||||
# broken отличаем от found: файл на месте, но не разобрался — сказать «файла нет» было бы
|
||||
# враньём, а молчать — тем же тихим отказом, от которого лечим.
|
||||
res = {"found": False, "broken": False, "members": []}
|
||||
root = get_config_root_dir()
|
||||
if root:
|
||||
dt_path = os.path.join(root, "DefinedTypes", f"{dt_name}.xml")
|
||||
if os.path.isfile(dt_path):
|
||||
try:
|
||||
dt_root = etree.parse(dt_path, parser_xml).getroot()
|
||||
tn = find(dt_root, "/md:MetaDataObject/md:DefinedType/md:Properties/md:Type")
|
||||
if tn is not None:
|
||||
res["found"] = True
|
||||
members = []
|
||||
_in_type_set_expansion["on"] = True
|
||||
try:
|
||||
for c in tn.xpath("v8:Type|v8:TypeSet", namespaces=NS):
|
||||
if local_name(c) == "TypeSet":
|
||||
members.append(format_single_type_set(inner_text(c)))
|
||||
else:
|
||||
members.append(format_single_type(inner_text(c), tn))
|
||||
finally:
|
||||
_in_type_set_expansion["on"] = False
|
||||
res["members"] = members
|
||||
except Exception:
|
||||
res["broken"] = True
|
||||
if not res["found"]:
|
||||
res["broken"] = True
|
||||
defined_type_cache[dt_name] = res
|
||||
return res
|
||||
|
||||
|
||||
def register_type_set(label, kind, name):
|
||||
if _in_type_set_expansion["on"]:
|
||||
return
|
||||
if label in type_set_glossary:
|
||||
return
|
||||
type_set_glossary[label] = {"kind": kind, "name": name}
|
||||
|
||||
|
||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ объекты этого класса
|
||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой, но
|
||||
# отличать «все» от «один» по наличию точки посреди длинного имени — сигнал низкой различимости,
|
||||
# поэтому обобщённый метатип получает суффикс «(все)»: разница становится словом, а не пунктуацией.
|
||||
def format_single_type_set(raw):
|
||||
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
|
||||
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
||||
if m:
|
||||
return f"ОпределяемыйТип.{m.group(1)}"
|
||||
label = f"ОпределяемыйТип.{m.group(1)}"
|
||||
register_type_set(label, "DefinedType", m.group(1))
|
||||
return label
|
||||
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
|
||||
if m:
|
||||
return f"Характеристика.{m.group(1)}"
|
||||
label = f"Характеристика.{m.group(1)}"
|
||||
register_type_set(label, "Characteristic", m.group(1))
|
||||
return label
|
||||
if raw == "cfg:AnyRef":
|
||||
return "ЛюбаяСсылка"
|
||||
if raw == "cfg:AnyIBRef":
|
||||
return "ЛюбаяСсылкаИБ"
|
||||
m = re.match(r'^cfg:(\w+Ref)$', raw)
|
||||
if m and m.group(1) in ref_type_map:
|
||||
return ref_type_map[m.group(1)]
|
||||
return f"{ref_type_map[m.group(1)]} (все)"
|
||||
m = re.match(r'^cfg:(\w+)$', raw)
|
||||
if m and m.group(1) in object_type_map:
|
||||
return f"{object_type_map[m.group(1)]} (все)"
|
||||
m = re.match(r'^cfg:(.+)$', raw)
|
||||
if m:
|
||||
return m.group(1)
|
||||
return raw
|
||||
|
||||
|
||||
# Глоссарий множеств: одна запись на уникальное множество, в порядке первого упоминания.
|
||||
def get_type_set_glossary_lines():
|
||||
lines = []
|
||||
for label, e in type_set_glossary.items():
|
||||
if e["kind"] == "Characteristic":
|
||||
lines.append(f" {label} — набор определяется данными ПВХ")
|
||||
continue
|
||||
dt = resolve_defined_type(e["name"])
|
||||
if not dt["found"]:
|
||||
lines.append(f" {label} — файл типа не разобран" if dt["broken"]
|
||||
else f" {label} — файла типа нет в выгрузке")
|
||||
continue
|
||||
members = dt["members"]
|
||||
if len(members) == 0:
|
||||
lines.append(f" {label} — состав пуст")
|
||||
continue
|
||||
if len(members) <= COMPOSED_TYPE_THRESHOLD:
|
||||
lines.append(f" {label} → {' | '.join(members)}")
|
||||
else:
|
||||
lines.append(f" {label} — типов: {len(members)}")
|
||||
lines.append(f" раскрыть: meta-info -ObjectPath DefinedTypes/{e['name']}.xml -Mode full")
|
||||
return lines
|
||||
|
||||
|
||||
def format_type(type_node_el):
|
||||
if type_node_el is None:
|
||||
return ""
|
||||
@@ -347,6 +484,12 @@ def format_single_type(raw, parent_node):
|
||||
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
||||
if m:
|
||||
return f"ОпределяемыйТип.{m.group(1)}"
|
||||
# cfg:DocumentObject.Xxx / cfg:InformationRegisterRecordSet.Xxx — объектные типы.
|
||||
# Без карты они уходили в ветку «снять cfg:» и печатались по-английски, хотя в
|
||||
# источниках подписки те же типы печатались по-русски: одно понятие двумя видами.
|
||||
m = re.match(r'^cfg:(\w+)\.(.+)$', raw)
|
||||
if m and m.group(1) in object_type_map:
|
||||
return f"{object_type_map[m.group(1)]}.{m.group(2)}"
|
||||
# Strip cfg: prefix
|
||||
m = re.match(r'^cfg:(.+)$', raw)
|
||||
if m:
|
||||
@@ -656,6 +799,26 @@ def format_source_type(raw):
|
||||
return raw
|
||||
|
||||
|
||||
# Источники подписки задаются и списком типов (v8:Type), и множеством (v8:TypeSet — определяемый
|
||||
# тип или целый класс «все документы»). В корпусе множеством задана треть подписок (в erp_8.3.24
|
||||
# 151 из 492 — вообще без v8:Type), поэтому чтение только v8:Type оставляло их без строки
|
||||
# «Источники»: подписка выглядела так, будто ни на что не срабатывает. Оба узла читаем одним
|
||||
# XPath, в порядке документа — так же, как meta-decompile.
|
||||
#
|
||||
# Множества и явные типы возвращаем врозь: явных бывает много (в корпусе до 1353 в одной
|
||||
# подписке), а множеств — не больше восьми. Поэтому в сводке множества называем всегда, а
|
||||
# явные типы сворачиваем в счётчик: по одному числу не видно, что подписка бьёт по целому классу.
|
||||
def get_subscription_source_list(source_node):
|
||||
types = []
|
||||
sets = []
|
||||
for t in find_all(source_node, "v8:Type|v8:TypeSet"):
|
||||
if local_name(t) == "TypeSet":
|
||||
sets.append(format_single_type_set(inner_text(t)))
|
||||
else:
|
||||
types.append(format_source_type(inner_text(t)))
|
||||
return types, sets
|
||||
|
||||
|
||||
def get_http_endpoints(child_objs):
|
||||
result = []
|
||||
for tpl in find_all(child_objs, "md:URLTemplate"):
|
||||
@@ -1053,9 +1216,15 @@ if not drill_done:
|
||||
if md_type == "DefinedType":
|
||||
type_node2 = find(props, "md:Type")
|
||||
if type_node2 is not None:
|
||||
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||
# «Типы (N)» врал.
|
||||
types = []
|
||||
for t in find_all(type_node2, "v8:Type"):
|
||||
types.append(format_single_type(inner_text(t), type_node2))
|
||||
for t in find_all(type_node2, "v8:Type|v8:TypeSet"):
|
||||
if local_name(t) == "TypeSet":
|
||||
types.append(format_single_type_set(inner_text(t)))
|
||||
else:
|
||||
types.append(format_single_type(inner_text(t), type_node2))
|
||||
if types:
|
||||
out(f"Типы ({len(types)}): {', '.join(types)}")
|
||||
|
||||
@@ -1114,9 +1283,15 @@ if not drill_done:
|
||||
es_parts.append(f"Обработчик: {h_name}")
|
||||
source = find(props, "md:Source")
|
||||
if source is not None:
|
||||
src_count = len(find_all(source, "v8:Type"))
|
||||
if src_count > 0:
|
||||
es_parts.append(f"Источники: {src_count}")
|
||||
# brief называет множества, а явные типы считает: по одному числу не видно, что
|
||||
# подписка срабатывает на целый класс документов или на состав определяемого типа.
|
||||
src_types, src_sets = get_subscription_source_list(source)
|
||||
total = len(src_types) + len(src_sets)
|
||||
if total > 0:
|
||||
parts2 = list(src_sets)
|
||||
if src_types:
|
||||
parts2.append(f"явных типов: {len(src_types)}")
|
||||
es_parts.append(f"Источники ({total}): {', '.join(parts2)}")
|
||||
if es_parts:
|
||||
out(" | ".join(es_parts))
|
||||
|
||||
@@ -1221,9 +1396,15 @@ if not drill_done:
|
||||
if md_type == "DefinedType":
|
||||
type_node2 = find(props, "md:Type")
|
||||
if type_node2 is not None:
|
||||
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||
# «Типы (N)» врал.
|
||||
types = []
|
||||
for t in find_all(type_node2, "v8:Type"):
|
||||
types.append(format_single_type(inner_text(t), type_node2))
|
||||
for t in find_all(type_node2, "v8:Type|v8:TypeSet"):
|
||||
if local_name(t) == "TypeSet":
|
||||
types.append(format_single_type_set(inner_text(t)))
|
||||
else:
|
||||
types.append(format_single_type(inner_text(t), type_node2))
|
||||
if types:
|
||||
out(f"Типы ({len(types)}):")
|
||||
for t in types:
|
||||
@@ -1283,16 +1464,20 @@ if not drill_done:
|
||||
out(f"Обработчик: {h_name}")
|
||||
source = find(props, "md:Source")
|
||||
if source is not None:
|
||||
src_types = []
|
||||
for t in find_all(source, "v8:Type"):
|
||||
src_types.append(format_source_type(inner_text(t)))
|
||||
if src_types:
|
||||
if mode == "full":
|
||||
out(f"Источники ({len(src_types)}):")
|
||||
src_types, src_sets = get_subscription_source_list(source)
|
||||
total = len(src_types) + len(src_sets)
|
||||
if total > 0:
|
||||
out(f"Источники ({total}):")
|
||||
# full печатает всё: режим для этого и нужен, а длину держит постраничник.
|
||||
# В overview явные типы сворачиваем в счётчик — их бывает больше тысячи.
|
||||
list_types = mode == "full" or total <= COMPOSED_TYPE_THRESHOLD
|
||||
if list_types:
|
||||
for s in src_types:
|
||||
out(f" {s}")
|
||||
else:
|
||||
out(f"Источники ({len(src_types)})")
|
||||
for s in src_sets:
|
||||
out(f" {s}")
|
||||
if not list_types and src_types:
|
||||
out(f" и ещё явных типов: {len(src_types)} (-Mode full)")
|
||||
|
||||
# HTTPService
|
||||
if md_type == "HTTPService":
|
||||
@@ -1542,6 +1727,17 @@ if not drill_done:
|
||||
out("")
|
||||
out(f"Полный состав типов: {hints}")
|
||||
|
||||
# Глоссарий множеств — после всего остального: в строках типов стоят только имена, и это
|
||||
# единственное место, где имя превращается в состав. brief не глоссируем: там типов реквизитов
|
||||
# нет вовсе, а множества источников подписки он называет прямо в строке.
|
||||
if mode != "brief" and type_set_glossary:
|
||||
glossary = get_type_set_glossary_lines()
|
||||
if glossary:
|
||||
out("")
|
||||
out("Множества типов в составе:")
|
||||
for l in glossary:
|
||||
out(l)
|
||||
|
||||
# ── Pagination and output ────────────────────────────────────
|
||||
|
||||
total_lines = len(lines)
|
||||
|
||||
Reference in New Issue
Block a user