mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-21 09:45: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
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
[CmdletBinding(PositionalBinding=$false)]
|
[CmdletBinding(PositionalBinding=$false)]
|
||||||
param(
|
param(
|
||||||
@@ -107,6 +107,9 @@ $typeNameMap = @{
|
|||||||
"ExternalDataSource"="Внешний источник данных"; "Table"="Таблица внешнего источника"
|
"ExternalDataSource"="Внешний источник данных"; "Table"="Таблица внешнего источника"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Долгие виды сокращаем (ПВХ, РС/РН/РБ/РР): в списке на сорок реквизитов повторяющийся префикс
|
||||||
|
# съедает колонку и прячет отличающуюся часть — имя объекта. Аббревиатуры приняты как ВВОД в
|
||||||
|
# meta-compile/meta-edit (словарь typeSynonyms), поэтому строку из вывода можно подать обратно.
|
||||||
$refTypeMap = @{
|
$refTypeMap = @{
|
||||||
"CatalogRef"="СправочникСсылка"; "DocumentRef"="ДокументСсылка"
|
"CatalogRef"="СправочникСсылка"; "DocumentRef"="ДокументСсылка"
|
||||||
"EnumRef"="ПеречислениеСсылка"; "ChartOfAccountsRef"="ПланСчетовСсылка"
|
"EnumRef"="ПеречислениеСсылка"; "ChartOfAccountsRef"="ПланСчетовСсылка"
|
||||||
@@ -146,11 +149,32 @@ $objectTypeMap = @{
|
|||||||
"CatalogObject"="СправочникОбъект"; "DocumentObject"="ДокументОбъект"
|
"CatalogObject"="СправочникОбъект"; "DocumentObject"="ДокументОбъект"
|
||||||
"ChartOfAccountsObject"="ПланСчетовОбъект"
|
"ChartOfAccountsObject"="ПланСчетовОбъект"
|
||||||
"ChartOfCharacteristicTypesObject"="ПВХОбъект"
|
"ChartOfCharacteristicTypesObject"="ПВХОбъект"
|
||||||
|
"ChartOfCalculationTypesObject"="ПВРОбъект"
|
||||||
"BusinessProcessObject"="БизнесПроцессОбъект"; "TaskObject"="ЗадачаОбъект"
|
"BusinessProcessObject"="БизнесПроцессОбъект"; "TaskObject"="ЗадачаОбъект"
|
||||||
"ExchangePlanObject"="ПланОбменаОбъект"
|
"ExchangePlanObject"="ПланОбменаОбъект"
|
||||||
"InformationRegisterRecordSet"="НаборЗаписейРС"
|
"InformationRegisterRecordSet"="НаборЗаписейРС"
|
||||||
"AccumulationRegisterRecordSet"="НаборЗаписейРН"
|
"AccumulationRegisterRecordSet"="НаборЗаписейРН"
|
||||||
"AccountingRegisterRecordSet"="НаборЗаписейРБ"
|
"AccountingRegisterRecordSet"="НаборЗаписейРБ"
|
||||||
|
"CalculationRegisterRecordSet"="НаборЗаписейРР"
|
||||||
|
"SequenceRecordSet"="НаборЗаписейПоследовательности"
|
||||||
|
"RecalculationRecordSet"="НаборЗаписейПерерасчета"
|
||||||
|
# Менеджеры встречаются в источниках подписок. Сиблинга-аббревиатуры у них нет
|
||||||
|
# (единственный ориентир — КонстантаМенеджерЗначения), поэтому имена полные.
|
||||||
|
"CatalogManager"="СправочникМенеджер"; "DocumentManager"="ДокументМенеджер"
|
||||||
|
"DocumentJournalManager"="ЖурналДокументовМенеджер"
|
||||||
|
"EnumManager"="ПеречислениеМенеджер"
|
||||||
|
"ConstantValueManager"="КонстантаМенеджерЗначения"
|
||||||
|
"InformationRegisterManager"="РегистрСведенийМенеджер"
|
||||||
|
"AccumulationRegisterManager"="РегистрНакопленияМенеджер"
|
||||||
|
"AccountingRegisterManager"="РегистрБухгалтерииМенеджер"
|
||||||
|
"CalculationRegisterManager"="РегистрРасчетаМенеджер"
|
||||||
|
"ChartOfAccountsManager"="ПланСчетовМенеджер"
|
||||||
|
"ChartOfCharacteristicTypesManager"="ПВХМенеджер"
|
||||||
|
"ChartOfCalculationTypesManager"="ПВРМенеджер"
|
||||||
|
"ExchangePlanManager"="ПланОбменаМенеджер"
|
||||||
|
"BusinessProcessManager"="БизнесПроцессМенеджер"
|
||||||
|
"TaskManager"="ЗадачаМенеджер"
|
||||||
|
"DataProcessorManager"="ОбработкаМенеджер"; "ReportManager"="ОтчетМенеджер"
|
||||||
}
|
}
|
||||||
|
|
||||||
$numberPeriodMap = @{
|
$numberPeriodMap = @{
|
||||||
@@ -173,22 +197,142 @@ function Get-MLText($node) {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
# Корень конфигурации — ближайший каталог выше объекта, где лежит Configuration.xml (или
|
||||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
# 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) {
|
function Format-SingleTypeSet([string]$raw) {
|
||||||
$raw = $raw -replace '^d\d+p\d+:', 'cfg:'
|
$raw = $raw -replace '^d\d+p\d+:', 'cfg:'
|
||||||
if ($raw -match '^cfg:DefinedType\.(.+)$') { return "ОпределяемыйТип.$($Matches[1])" }
|
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
||||||
if ($raw -match '^cfg:Characteristic\.(.+)$') { return "Характеристика.$($Matches[1])" }
|
$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:AnyRef') { return "ЛюбаяСсылка" }
|
||||||
if ($raw -eq 'cfg:AnyIBRef') { return "ЛюбаяСсылкаИБ" }
|
if ($raw -eq 'cfg:AnyIBRef') { return "ЛюбаяСсылкаИБ" }
|
||||||
if ($raw -match '^cfg:(\w+Ref)$' -and $refTypeMap.ContainsKey($Matches[1])) {
|
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] }
|
if ($raw -match '^cfg:(.+)$') { return $Matches[1] }
|
||||||
return $raw
|
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) {
|
function Format-Type($typeNode) {
|
||||||
if (-not $typeNode) { return "" }
|
if (-not $typeNode) { return "" }
|
||||||
$types = @()
|
$types = @()
|
||||||
@@ -261,6 +405,12 @@ function Format-SingleType([string]$raw, $parentNode) {
|
|||||||
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
if ($raw -match '^cfg:DefinedType\.(.+)$') {
|
||||||
return "ОпределяемыйТип.$($Matches[1])"
|
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
|
# Strip cfg: prefix for unknown
|
||||||
if ($raw -match '^cfg:(.+)$') {
|
if ($raw -match '^cfg:(.+)$') {
|
||||||
return $Matches[1]
|
return $Matches[1]
|
||||||
@@ -582,6 +732,25 @@ function Format-SourceType([string]$raw) {
|
|||||||
return $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) {
|
function Get-HTTPEndpoints($childObjs) {
|
||||||
$result = @()
|
$result = @()
|
||||||
foreach ($tpl in $childObjs.SelectNodes("md:URLTemplate", $ns)) {
|
foreach ($tpl in $childObjs.SelectNodes("md:URLTemplate", $ns)) {
|
||||||
@@ -646,16 +815,9 @@ function Test-ExternalObjectRoot([string]$xmlPath) {
|
|||||||
function Get-ObjectSupportStatus([string]$objUuid) {
|
function Get-ObjectSupportStatus([string]$objUuid) {
|
||||||
try {
|
try {
|
||||||
if (Test-ExternalObjectRoot $ObjectPath) { return $null }
|
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)
|
$root = Get-ConfigRootDir
|
||||||
$binPath = $null
|
$binPath = if ($root) { Join-Path (Join-Path $root "Ext") "ParentConfigurations.bin" } else { $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
|
|
||||||
}
|
|
||||||
if (-not $binPath -or -not (Test-Path $binPath)) { return "не на поддержке" }
|
if (-not $binPath -or -not (Test-Path $binPath)) { return "не на поддержке" }
|
||||||
$bytes = [System.IO.File]::ReadAllBytes($binPath)
|
$bytes = [System.IO.File]::ReadAllBytes($binPath)
|
||||||
if ($bytes.Length -le 32) { return "снято с поддержки (правки свободны)" }
|
if ($bytes.Length -le 32) { return "снято с поддержки (правки свободны)" }
|
||||||
@@ -996,9 +1158,13 @@ if (-not $drillDone) {
|
|||||||
if ($mdType -eq "DefinedType") {
|
if ($mdType -eq "DefinedType") {
|
||||||
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
||||||
if ($typeNode2) {
|
if ($typeNode2) {
|
||||||
|
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||||
|
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||||
|
# «Типы (N)» врал.
|
||||||
$types = @()
|
$types = @()
|
||||||
foreach ($t in $typeNode2.SelectNodes("v8:Type", $ns)) {
|
foreach ($t in $typeNode2.SelectNodes("v8:Type|v8:TypeSet", $ns)) {
|
||||||
$types += Format-SingleType $t.InnerText $typeNode2
|
if ($t.get_LocalName() -eq 'TypeSet') { $types += Format-SingleTypeSet $t.InnerText }
|
||||||
|
else { $types += Format-SingleType $t.InnerText $typeNode2 }
|
||||||
}
|
}
|
||||||
if ($types.Count -gt 0) {
|
if ($types.Count -gt 0) {
|
||||||
Out "Типы ($($types.Count)): $($types -join ', ')"
|
Out "Типы ($($types.Count)): $($types -join ', ')"
|
||||||
@@ -1061,8 +1227,17 @@ if (-not $drillDone) {
|
|||||||
}
|
}
|
||||||
$source = $props.SelectSingleNode("md:Source", $ns)
|
$source = $props.SelectSingleNode("md:Source", $ns)
|
||||||
if ($source) {
|
if ($source) {
|
||||||
$srcCount = $source.SelectNodes("v8:Type", $ns).Count
|
# brief называет множества, а явные типы считает: по одному числу не видно, что
|
||||||
if ($srcCount -gt 0) { $esParts += "Источники: $srcCount" }
|
# подписка срабатывает на целый класс документов или на состав определяемого типа.
|
||||||
|
$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 " | ") }
|
if ($esParts.Count -gt 0) { Out ($esParts -join " | ") }
|
||||||
}
|
}
|
||||||
@@ -1185,9 +1360,13 @@ if (-not $drillDone) {
|
|||||||
if ($mdType -eq "DefinedType") {
|
if ($mdType -eq "DefinedType") {
|
||||||
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
$typeNode2 = $props.SelectSingleNode("md:Type", $ns)
|
||||||
if ($typeNode2) {
|
if ($typeNode2) {
|
||||||
|
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||||
|
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||||
|
# «Типы (N)» врал.
|
||||||
$types = @()
|
$types = @()
|
||||||
foreach ($t in $typeNode2.SelectNodes("v8:Type", $ns)) {
|
foreach ($t in $typeNode2.SelectNodes("v8:Type|v8:TypeSet", $ns)) {
|
||||||
$types += Format-SingleType $t.InnerText $typeNode2
|
if ($t.get_LocalName() -eq 'TypeSet') { $types += Format-SingleTypeSet $t.InnerText }
|
||||||
|
else { $types += Format-SingleType $t.InnerText $typeNode2 }
|
||||||
}
|
}
|
||||||
if ($types.Count -gt 0) {
|
if ($types.Count -gt 0) {
|
||||||
Out "Типы ($($types.Count)):"
|
Out "Типы ($($types.Count)):"
|
||||||
@@ -1250,16 +1429,18 @@ if (-not $drillDone) {
|
|||||||
}
|
}
|
||||||
$source = $props.SelectSingleNode("md:Source", $ns)
|
$source = $props.SelectSingleNode("md:Source", $ns)
|
||||||
if ($source) {
|
if ($source) {
|
||||||
$srcTypes = @()
|
$src = Get-SubscriptionSourceList $source
|
||||||
foreach ($t in $source.SelectNodes("v8:Type", $ns)) {
|
$srcTypes = @($src.Types); $srcSets = @($src.Sets)
|
||||||
$srcTypes += Format-SourceType $t.InnerText
|
$total = $srcTypes.Count + $srcSets.Count
|
||||||
}
|
if ($total -gt 0) {
|
||||||
if ($srcTypes.Count -gt 0) {
|
Out "Источники ($total):"
|
||||||
if ($Mode -eq "full") {
|
# full печатает всё: режим для этого и нужен, а длину держит постраничник.
|
||||||
Out "Источники ($($srcTypes.Count)):"
|
# В overview явные типы сворачиваем в счётчик — их бывает больше тысячи.
|
||||||
foreach ($s in $srcTypes) { Out " $s" }
|
$listTypes = ($Mode -eq "full") -or ($total -le $script:composedTypeThreshold)
|
||||||
} else {
|
if ($listTypes) { foreach ($s in $srcTypes) { Out " $s" } }
|
||||||
Out "Источники ($($srcTypes.Count))"
|
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 ---
|
# --- Pagination and output ---
|
||||||
$totalLines = $script:lines.Count
|
$totalLines = $script:lines.Count
|
||||||
$outLines = $script:lines
|
$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
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
@@ -180,6 +180,9 @@ type_name_map = {
|
|||||||
"ExternalDataSource": "Внешний источник данных", "Table": "Таблица внешнего источника",
|
"ExternalDataSource": "Внешний источник данных", "Table": "Таблица внешнего источника",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Долгие виды сокращаем (ПВХ, РС/РН/РБ/РР): в списке на сорок реквизитов повторяющийся префикс
|
||||||
|
# съедает колонку и прячет отличающуюся часть — имя объекта. Аббревиатуры приняты как ВВОД в
|
||||||
|
# meta-compile/meta-edit (словарь typeSynonyms), поэтому строку из вывода можно подать обратно.
|
||||||
ref_type_map = {
|
ref_type_map = {
|
||||||
"CatalogRef": "СправочникСсылка", "DocumentRef": "ДокументСсылка",
|
"CatalogRef": "СправочникСсылка", "DocumentRef": "ДокументСсылка",
|
||||||
"EnumRef": "ПеречислениеСсылка", "ChartOfAccountsRef": "ПланСчетовСсылка",
|
"EnumRef": "ПеречислениеСсылка", "ChartOfAccountsRef": "ПланСчетовСсылка",
|
||||||
@@ -219,11 +222,32 @@ object_type_map = {
|
|||||||
"CatalogObject": "СправочникОбъект", "DocumentObject": "ДокументОбъект",
|
"CatalogObject": "СправочникОбъект", "DocumentObject": "ДокументОбъект",
|
||||||
"ChartOfAccountsObject": "ПланСчетовОбъект",
|
"ChartOfAccountsObject": "ПланСчетовОбъект",
|
||||||
"ChartOfCharacteristicTypesObject": "ПВХОбъект",
|
"ChartOfCharacteristicTypesObject": "ПВХОбъект",
|
||||||
|
"ChartOfCalculationTypesObject": "ПВРОбъект",
|
||||||
"BusinessProcessObject": "БизнесПроцессОбъект", "TaskObject": "ЗадачаОбъект",
|
"BusinessProcessObject": "БизнесПроцессОбъект", "TaskObject": "ЗадачаОбъект",
|
||||||
"ExchangePlanObject": "ПланОбменаОбъект",
|
"ExchangePlanObject": "ПланОбменаОбъект",
|
||||||
"InformationRegisterRecordSet": "НаборЗаписейРС",
|
"InformationRegisterRecordSet": "НаборЗаписейРС",
|
||||||
"AccumulationRegisterRecordSet": "НаборЗаписейРН",
|
"AccumulationRegisterRecordSet": "НаборЗаписейРН",
|
||||||
"AccountingRegisterRecordSet": "НаборЗаписейРБ",
|
"AccountingRegisterRecordSet": "НаборЗаписейРБ",
|
||||||
|
"CalculationRegisterRecordSet": "НаборЗаписейРР",
|
||||||
|
"SequenceRecordSet": "НаборЗаписейПоследовательности",
|
||||||
|
"RecalculationRecordSet": "НаборЗаписейПерерасчета",
|
||||||
|
# Менеджеры встречаются в источниках подписок. Сиблинга-аббревиатуры у них нет
|
||||||
|
# (единственный ориентир — КонстантаМенеджерЗначения), поэтому имена полные.
|
||||||
|
"CatalogManager": "СправочникМенеджер", "DocumentManager": "ДокументМенеджер",
|
||||||
|
"DocumentJournalManager": "ЖурналДокументовМенеджер",
|
||||||
|
"EnumManager": "ПеречислениеМенеджер",
|
||||||
|
"ConstantValueManager": "КонстантаМенеджерЗначения",
|
||||||
|
"InformationRegisterManager": "РегистрСведенийМенеджер",
|
||||||
|
"AccumulationRegisterManager": "РегистрНакопленияМенеджер",
|
||||||
|
"AccountingRegisterManager": "РегистрБухгалтерииМенеджер",
|
||||||
|
"CalculationRegisterManager": "РегистрРасчетаМенеджер",
|
||||||
|
"ChartOfAccountsManager": "ПланСчетовМенеджер",
|
||||||
|
"ChartOfCharacteristicTypesManager": "ПВХМенеджер",
|
||||||
|
"ChartOfCalculationTypesManager": "ПВРМенеджер",
|
||||||
|
"ExchangePlanManager": "ПланОбменаМенеджер",
|
||||||
|
"BusinessProcessManager": "БизнесПроцессМенеджер",
|
||||||
|
"TaskManager": "ЗадачаМенеджер",
|
||||||
|
"DataProcessorManager": "ОбработкаМенеджер", "ReportManager": "ОтчетМенеджер",
|
||||||
}
|
}
|
||||||
|
|
||||||
number_period_map = {
|
number_period_map = {
|
||||||
@@ -251,30 +275,143 @@ def get_ml_text(node):
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
|
# Корень конфигурации — ближайший каталог выше объекта, где лежит Configuration.xml (или
|
||||||
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
|
# 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):
|
def format_single_type_set(raw):
|
||||||
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
|
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
|
||||||
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
||||||
if m:
|
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)
|
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
|
||||||
if m:
|
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":
|
if raw == "cfg:AnyRef":
|
||||||
return "ЛюбаяСсылка"
|
return "ЛюбаяСсылка"
|
||||||
if raw == "cfg:AnyIBRef":
|
if raw == "cfg:AnyIBRef":
|
||||||
return "ЛюбаяСсылкаИБ"
|
return "ЛюбаяСсылкаИБ"
|
||||||
m = re.match(r'^cfg:(\w+Ref)$', raw)
|
m = re.match(r'^cfg:(\w+Ref)$', raw)
|
||||||
if m and m.group(1) in ref_type_map:
|
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)
|
m = re.match(r'^cfg:(.+)$', raw)
|
||||||
if m:
|
if m:
|
||||||
return m.group(1)
|
return m.group(1)
|
||||||
return raw
|
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):
|
def format_type(type_node_el):
|
||||||
if type_node_el is None:
|
if type_node_el is None:
|
||||||
return ""
|
return ""
|
||||||
@@ -347,6 +484,12 @@ def format_single_type(raw, parent_node):
|
|||||||
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
|
||||||
if m:
|
if m:
|
||||||
return f"ОпределяемыйТип.{m.group(1)}"
|
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
|
# Strip cfg: prefix
|
||||||
m = re.match(r'^cfg:(.+)$', raw)
|
m = re.match(r'^cfg:(.+)$', raw)
|
||||||
if m:
|
if m:
|
||||||
@@ -656,6 +799,26 @@ def format_source_type(raw):
|
|||||||
return 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):
|
def get_http_endpoints(child_objs):
|
||||||
result = []
|
result = []
|
||||||
for tpl in find_all(child_objs, "md:URLTemplate"):
|
for tpl in find_all(child_objs, "md:URLTemplate"):
|
||||||
@@ -1053,8 +1216,14 @@ if not drill_done:
|
|||||||
if md_type == "DefinedType":
|
if md_type == "DefinedType":
|
||||||
type_node2 = find(props, "md:Type")
|
type_node2 = find(props, "md:Type")
|
||||||
if type_node2 is not None:
|
if type_node2 is not None:
|
||||||
|
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||||
|
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||||
|
# «Типы (N)» врал.
|
||||||
types = []
|
types = []
|
||||||
for t in find_all(type_node2, "v8:Type"):
|
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))
|
types.append(format_single_type(inner_text(t), type_node2))
|
||||||
if types:
|
if types:
|
||||||
out(f"Типы ({len(types)}): {', '.join(types)}")
|
out(f"Типы ({len(types)}): {', '.join(types)}")
|
||||||
@@ -1114,9 +1283,15 @@ if not drill_done:
|
|||||||
es_parts.append(f"Обработчик: {h_name}")
|
es_parts.append(f"Обработчик: {h_name}")
|
||||||
source = find(props, "md:Source")
|
source = find(props, "md:Source")
|
||||||
if source is not None:
|
if source is not None:
|
||||||
src_count = len(find_all(source, "v8:Type"))
|
# brief называет множества, а явные типы считает: по одному числу не видно, что
|
||||||
if src_count > 0:
|
# подписка срабатывает на целый класс документов или на состав определяемого типа.
|
||||||
es_parts.append(f"Источники: {src_count}")
|
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:
|
if es_parts:
|
||||||
out(" | ".join(es_parts))
|
out(" | ".join(es_parts))
|
||||||
|
|
||||||
@@ -1221,8 +1396,14 @@ if not drill_done:
|
|||||||
if md_type == "DefinedType":
|
if md_type == "DefinedType":
|
||||||
type_node2 = find(props, "md:Type")
|
type_node2 = find(props, "md:Type")
|
||||||
if type_node2 is not None:
|
if type_node2 is not None:
|
||||||
|
# Оба узла, одним XPath и в порядке документа: v8:TypeSet внутри определяемого
|
||||||
|
# типа платформа допускает, а чтение только v8:Type МОЛЧА теряло его — счётчик
|
||||||
|
# «Типы (N)» врал.
|
||||||
types = []
|
types = []
|
||||||
for t in find_all(type_node2, "v8:Type"):
|
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))
|
types.append(format_single_type(inner_text(t), type_node2))
|
||||||
if types:
|
if types:
|
||||||
out(f"Типы ({len(types)}):")
|
out(f"Типы ({len(types)}):")
|
||||||
@@ -1283,16 +1464,20 @@ if not drill_done:
|
|||||||
out(f"Обработчик: {h_name}")
|
out(f"Обработчик: {h_name}")
|
||||||
source = find(props, "md:Source")
|
source = find(props, "md:Source")
|
||||||
if source is not None:
|
if source is not None:
|
||||||
src_types = []
|
src_types, src_sets = get_subscription_source_list(source)
|
||||||
for t in find_all(source, "v8:Type"):
|
total = len(src_types) + len(src_sets)
|
||||||
src_types.append(format_source_type(inner_text(t)))
|
if total > 0:
|
||||||
if src_types:
|
out(f"Источники ({total}):")
|
||||||
if mode == "full":
|
# full печатает всё: режим для этого и нужен, а длину держит постраничник.
|
||||||
out(f"Источники ({len(src_types)}):")
|
# В overview явные типы сворачиваем в счётчик — их бывает больше тысячи.
|
||||||
|
list_types = mode == "full" or total <= COMPOSED_TYPE_THRESHOLD
|
||||||
|
if list_types:
|
||||||
for s in src_types:
|
for s in src_types:
|
||||||
out(f" {s}")
|
out(f" {s}")
|
||||||
else:
|
for s in src_sets:
|
||||||
out(f"Источники ({len(src_types)})")
|
out(f" {s}")
|
||||||
|
if not list_types and src_types:
|
||||||
|
out(f" и ещё явных типов: {len(src_types)} (-Mode full)")
|
||||||
|
|
||||||
# HTTPService
|
# HTTPService
|
||||||
if md_type == "HTTPService":
|
if md_type == "HTTPService":
|
||||||
@@ -1542,6 +1727,17 @@ if not drill_done:
|
|||||||
out("")
|
out("")
|
||||||
out(f"Полный состав типов: {hints}")
|
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 ────────────────────────────────────
|
# ── Pagination and output ────────────────────────────────────
|
||||||
|
|
||||||
total_lines = len(lines)
|
total_lines = len(lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user