Files

695 lines
29 KiB
PowerShell
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# xdto-info v1.0 — Analyze 1C XDTO package structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)]
[Alias('Path')]
[string]$PackagePath,
[string]$Package,
[string]$Namespace,
[string]$Name,
[ValidateSet("auto", "used-by")]
[string]$Mode = "auto",
[int]$Depth = 1,
[switch]$RequiredOnly,
[int]$Limit = 150,
[int]$Offset = 0,
[string]$OutFile
)
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
$XS_NS = "http://www.w3.org/2001/XMLSchema"
$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
# --- Output ---
$sb = New-Object System.Text.StringBuilder
function O([string]$line = "") { [void]$sb.AppendLine($line) }
function Fail([string]$msg) {
# Отрицательный результат поиска — не исключение: печатаем сообщение
# и выходим с кодом 1, без стектрейса PowerShell
Write-Host $msg
exit 1
}
function Flush-Output {
$text = $sb.ToString().TrimEnd()
if ($OutFile) {
$dir = [System.IO.Path]::GetDirectoryName($OutFile)
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
[System.IO.File]::WriteAllText($OutFile, $text + "`r`n", (New-Object System.Text.UTF8Encoding($true)))
Write-Host "✓ Записано: $OutFile"
} else {
Write-Host $text
}
}
# --- Path resolution -----------------------------------------------------------
# Путь может указывать на корень конфигурации (тогда работаем со всеми пакетами)
# либо на конкретный пакет.
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
$PackagePath = Join-Path (Get-Location).Path $PackagePath
}
if (-not (Test-Path $PackagePath)) { Fail "Путь не найден: $PackagePath" }
$configRoot = $null
$directPkgDir = $null
if (Test-Path (Join-Path $PackagePath "Configuration.xml")) {
$configRoot = $PackagePath
} elseif ((Split-Path $PackagePath -Leaf) -eq "XDTOPackages") {
$configRoot = Split-Path $PackagePath -Parent
} elseif (Test-Path (Join-Path (Join-Path $PackagePath "Ext") "Package.bin")) {
$directPkgDir = $PackagePath
$pkgRoot = Split-Path $PackagePath -Parent
$configRoot = Split-Path $pkgRoot -Parent
} elseif ((Test-Path $PackagePath -PathType Leaf) -and ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin")) {
$directPkgDir = Split-Path (Split-Path $PackagePath -Parent) -Parent
$configRoot = Split-Path (Split-Path $directPkgDir -Parent) -Parent
} elseif ($PackagePath.EndsWith(".xml")) {
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath),
[System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
if (Test-Path (Join-Path (Join-Path $stem "Ext") "Package.bin")) {
$directPkgDir = $stem
$configRoot = Split-Path (Split-Path $stem -Parent) -Parent
}
}
if (-not $configRoot -and -not $directPkgDir) { Fail "Не удалось определить пакет или конфигурацию по пути: $PackagePath" }
# Sort-Object в PowerShell сортирует по культуре, sorted() в Python — по кодам.
# Для паритета портов сортируем ординально в обоих.
function Sort-Ordinal($items) {
$arr = [string[]]@($items)
[array]::Sort($arr, [StringComparer]::Ordinal)
return ,$arr
}
# --- Package index -------------------------------------------------------------
function Read-Package([string]$pkgDir) {
$bin = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
if (-not (Test-Path $bin)) { return $null }
try {
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($bin)
} catch { return $null }
$root = $doc.DocumentElement
if ($root.get_LocalName() -ne "package") { return $null }
$info = [pscustomobject]@{
Name = [System.IO.Path]::GetFileName($pkgDir)
Dir = $pkgDir
Namespace = $root.GetAttribute("targetNamespace")
Root = $root
Imports = (New-Object System.Collections.ArrayList)
Types = @{}
GlobalProps= (New-Object System.Collections.ArrayList)
}
foreach ($n in $root.ChildNodes) {
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
switch ($n.get_LocalName()) {
"import" { [void]$info.Imports.Add($n.GetAttribute("namespace")) }
"objectType" { $info.Types[$n.GetAttribute("name")] = $n }
"valueType" { $info.Types[$n.GetAttribute("name")] = $n }
"property" { [void]$info.GlobalProps.Add($n) }
}
}
return $info
}
$packages = New-Object System.Collections.ArrayList
$byNamespace = @{}
if ($configRoot -and (Test-Path (Join-Path $configRoot "XDTOPackages"))) {
foreach ($dn in (Sort-Ordinal ((Get-ChildItem (Join-Path $configRoot "XDTOPackages") -Directory -ErrorAction SilentlyContinue).Name))) {
$p = Read-Package (Join-Path (Join-Path $configRoot "XDTOPackages") $dn)
if ($p) {
[void]$packages.Add($p)
if (-not $byNamespace.ContainsKey($p.Namespace)) { $byNamespace[$p.Namespace] = $p }
}
}
}
if ($directPkgDir -and $packages.Count -eq 0) {
$p = Read-Package $directPkgDir
if ($p) { [void]$packages.Add($p); $byNamespace[$p.Namespace] = $p }
}
if ($packages.Count -eq 0) { Fail "Пакеты XDTO не найдены: $PackagePath" }
# --- Type notation: XSD -> 1С ---------------------------------------------------
$XS_TO_1C = @{
"string" = "Строка"; "normalizedString" = "Строка"; "token" = "Строка"; "NCName" = "Строка"
"Name" = "Строка"; "QName" = "Строка"; "anyURI" = "Строка"; "language" = "Строка"
"ID" = "Строка"; "IDREF" = "Строка"; "NMTOKEN" = "Строка"
"decimal" = "Число"; "integer" = "Число"; "int" = "Число"; "long" = "Число"; "short" = "Число"
"byte" = "Число"; "float" = "Число"; "double" = "Число"
"nonNegativeInteger" = "Число"; "positiveInteger" = "Число"; "nonPositiveInteger" = "Число"
"negativeInteger" = "Число"; "unsignedInt" = "Число"; "unsignedLong" = "Число"
"unsignedShort" = "Число"; "unsignedByte" = "Число"
"date" = "Дата"; "dateTime" = "Дата"; "time" = "Дата"
"boolean" = "Булево"
"base64Binary" = "ДвоичныеДанные"; "hexBinary" = "ДвоичныеДанные"
"anyType" = "произвольный"; "anySimpleType" = "произвольный"
}
function Split-Ref([System.Xml.XmlElement]$el, [string]$raw) {
if (-not $raw) { return $null }
if ($raw.StartsWith("{")) {
$close = $raw.IndexOf("}")
if ($close -lt 0) { return $null }
return [pscustomobject]@{ Ns = $raw.Substring(1, $close - 1); Local = $raw.Substring($close + 1) }
}
$parts = $raw.Split(":")
if ($parts.Count -eq 2) {
return [pscustomobject]@{ Ns = $el.GetNamespaceOfPrefix($parts[0]); Local = $parts[1] }
}
return [pscustomobject]@{ Ns = $null; Local = $parts[0] }
}
function Get-Facets([System.Xml.XmlElement]$t) {
$res = @{}
foreach ($f in @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
$v = $t.GetAttribute($f)
if ($v) { $res[$f] = $v }
}
$pat = $null
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { $pat = $c.InnerText; break }
}
if ($pat) { $res["pattern"] = $pat }
return $res
}
function Get-Enumerations([System.Xml.XmlElement]$t) {
$vals = @()
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { $vals += $c.InnerText }
}
return $vals
}
# Разворачивает цепочку псевдонимов до примитива, собирая фасеты по пути.
# Возвращает @{ Base1C; Facets; Alias; Enum; Kind }
function Resolve-Scalar([System.Xml.XmlElement]$t, $pkg, [int]$guard = 0) {
$acc = @{ Base1C = $null; Facets = @{}; Alias = $null; Enum = @(); Kind = "scalar" }
if ($guard -gt 10 -or -not $t) { return $acc }
$variety = $t.GetAttribute("variety")
if ($variety -eq "List") {
$it = Split-Ref $t $t.GetAttribute("itemType")
$acc.Kind = "list"
$acc.Base1C = "список " + $(if ($it) { Format-RefName $it $pkg } else { "значений" })
return $acc
}
if ($variety -eq "Union" -or $t.GetAttribute("memberTypes")) {
$members = @()
foreach ($m in (($t.GetAttribute("memberTypes") -split "\s+") | Where-Object { $_ })) {
$q = Split-Ref $t $m
if ($q) { $members += (Format-RefName $q $pkg) }
}
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") {
$inner = Resolve-Scalar $c $pkg ($guard + 1)
$members += $inner.Base1C
}
}
$acc.Kind = "union"
$acc.Base1C = "одно из (" + (($members | Where-Object { $_ }) -join " | ") + ")"
return $acc
}
$acc.Facets = Get-Facets $t
$acc.Enum = Get-Enumerations $t
# базовый тип: атрибут base или вложенный анонимный typeDef
$baseQ = Split-Ref $t $t.GetAttribute("base")
$anonBase = $null
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break }
}
if (-not $baseQ -and $anonBase) {
$inner = Resolve-Scalar $anonBase $pkg ($guard + 1)
$acc.Base1C = $inner.Base1C
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
return $acc
}
if (-not $baseQ) { $acc.Base1C = "произвольный"; return $acc }
if ($baseQ.Ns -eq $XS_NS) {
$acc.Base1C = $(if ($XS_TO_1C.ContainsKey($baseQ.Local)) { $XS_TO_1C[$baseQ.Local] } else { "xs:$($baseQ.Local)" })
return $acc
}
# база — именованный тип значения: разворачиваем дальше
$target = Find-Type $baseQ $pkg
if ($target -and $target.Element.get_LocalName() -eq "valueType") {
$inner = Resolve-Scalar $target.Element $target.Package ($guard + 1)
$acc.Base1C = $inner.Base1C
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
if (-not $acc.Alias) { $acc.Alias = $baseQ.Local }
return $acc
}
$acc.Base1C = $baseQ.Local
return $acc
}
function Find-Type($q, $pkg) {
if (-not $q) { return $null }
$targetPkg = $null
if (-not $q.Ns -or ($pkg -and $q.Ns -eq $pkg.Namespace)) { $targetPkg = $pkg }
elseif ($byNamespace.ContainsKey($q.Ns)) { $targetPkg = $byNamespace[$q.Ns] }
if (-not $targetPkg) { return $null }
if (-not $targetPkg.Types.ContainsKey($q.Local)) { return $null }
return [pscustomobject]@{ Element = $targetPkg.Types[$q.Local]; Package = $targetPkg }
}
function Format-RefName($q, $pkg) {
if (-not $q) { return "" }
if ($q.Ns -eq $XS_NS) {
return $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
}
return $q.Local
}
function Format-Scalar($res) {
$t = $res.Base1C
$f = $res.Facets
if ($t -eq "Строка") {
if ($f.ContainsKey("length")) { $t = "Строка($($f['length']))" }
elseif ($f.ContainsKey("maxLength")) { $t = "Строка($($f['maxLength']))" }
} elseif ($t -eq "Число") {
if ($f.ContainsKey("totalDigits")) {
$frac = $(if ($f.ContainsKey("fractionDigits")) { $f["fractionDigits"] } else { "0" })
$t = "Число($($f['totalDigits']),$frac)"
}
}
return $t
}
function Format-Notes($res) {
$notes = @()
if ($res.Alias) { $notes += "← $($res.Alias)" }
if ($res.Facets.ContainsKey("pattern")) {
$p = $res.Facets["pattern"]
if ($p.Length -gt 40) { $p = $p.Substring(0, 40) + "…" }
$notes += "шаблон $p"
}
foreach ($k in @("minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
if ($res.Facets.ContainsKey($k)) { $notes += "$k $($res.Facets[$k])" }
}
return $notes
}
# --- Property rendering ---------------------------------------------------------
function Get-PropRows([System.Xml.XmlElement]$type, $pkg, [int]$depth, [int]$indent, $seen) {
$rows = New-Object System.Collections.ArrayList
foreach ($p in $type.ChildNodes) {
if ($p.NodeType -ne [System.Xml.XmlNodeType]::Element -or $p.get_LocalName() -ne "property") { continue }
$pname = $p.GetAttribute("name")
if (-not $pname) {
$refQ = Split-Ref $p $p.GetAttribute("ref")
$pname = $(if ($refQ) { $refQ.Local } else { "(без имени)" })
}
$lower = $p.GetAttribute("lowerBound")
$upper = $p.GetAttribute("upperBound")
$flags = @()
# В модели умолчание lowerBound = 1; помечаем обязательные, как в meta-info
if ($lower -ne "0") { $flags += "обязательный" }
if ($upper -eq "-1") { $flags += "список" }
elseif ($upper -and $upper -ne "1") { $flags += "до $upper" }
if ($p.GetAttribute("form") -eq "Text") { $flags += "значение элемента" }
$notes = @()
$typeText = ""
$children = $null
$childPkg = $pkg
# Именованный вложенный тип берётся так же, как корневой; окольный путь
# через свойство владельца нужен только анонимному — имени у него нет
$objName = $null
$objNs = $null
$anon = $null
foreach ($c in $p.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon = $c; break }
}
if ($anon) {
if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
$typeText = "объект (анонимный)"
$objName = "(анонимный)"
$children = $anon # анонимные раскрываем всегда: смотреть отдельно негде
} else {
$res = Resolve-Scalar $anon $pkg
$typeText = Format-Scalar $res
$notes += (Format-Notes $res)
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
}
} else {
$q = Split-Ref $p $p.GetAttribute("type")
if (-not $q) {
$typeText = "произвольный"
} elseif ($q.Ns -eq $XS_NS) {
$typeText = $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
} else {
$target = Find-Type $q $pkg
if (-not $target) {
$typeText = "объект $($q.Local)"
$notes += "(пакет не найден: $($q.Ns))"
} elseif ($target.Element.get_LocalName() -eq "objectType") {
$typeText = "объект $($q.Local)"
if ($target.Package.Namespace -ne $pkg.Namespace) { $typeText += " · $($target.Package.Name)" }
$objName = $q.Local
$objNs = $target.Package.Namespace
$children = $target.Element
$childPkg = $target.Package
} else {
$res = Resolve-Scalar $target.Element $target.Package
$typeText = Format-Scalar $res
$notes += "← $($q.Local)"
$n2 = Format-Notes $res | Where-Object { -not $_.StartsWith("←") }
$notes += $n2
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
}
}
}
[void]$rows.Add([pscustomobject]@{
Indent = $indent; Name = $pname; Type = $typeText
Flags = $flags; Notes = ($notes | Where-Object { $_ })
ObjName = $objName; ObjNs = $objNs
})
if ($children) {
$key = "$($childPkg.Namespace)#$($children.GetAttribute('name'))"
$isAnon = -not $children.GetAttribute("name")
if (-not $isAnon -and $seen.Contains($key)) {
[void]$rows.Add([pscustomobject]@{ Indent = $indent + 1; Name = "(раскрыт выше)"; Type = ""; Flags = @(); Notes = @() })
} elseif ($isAnon -or $depth -gt 1) {
$nextSeen = New-Object System.Collections.Generic.HashSet[string] (,[string[]]$seen)
if (-not $isAnon) { [void]$nextSeen.Add($key) }
$nextDepth = $(if ($isAnon) { $depth } else { $depth - 1 })
foreach ($r in (Get-PropRows $children $childPkg $nextDepth ($indent + 1) $nextSeen)) { [void]$rows.Add($r) }
}
}
}
return ,$rows
}
# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
function Select-Required($rows) {
$res = New-Object System.Collections.ArrayList
$cutFrom = -1
foreach ($r in $rows) {
if ($cutFrom -ge 0 -and $r.Indent -gt $cutFrom) { continue }
$cutFrom = -1
if ($r.Flags -notcontains "обязательный") { $cutFrom = $r.Indent; continue }
[void]$res.Add($r)
}
return ,$res
}
function Write-Rows($rows) {
if ($rows.Count -eq 0) { O " (нет свойств)"; return }
$shown = $rows
if ($Offset -gt 0 -or $rows.Count -gt $Limit) {
$end = [Math]::Min($Offset + $Limit, $rows.Count) - 1
if ($Offset -le $end) { $shown = $rows[$Offset..$end] } else { $shown = @() }
}
$wName = 0; $wType = 0
foreach ($r in $shown) {
$n = (" " * $r.Indent) + $r.Name
if ($n.Length -gt $wName) { $wName = $n.Length }
if ($r.Type.Length -gt $wType) { $wType = $r.Type.Length }
}
foreach ($r in $shown) {
$n = (" " * $r.Indent) + $r.Name
$line = " " + $n.PadRight($wName + 2) + $r.Type.PadRight($wType + 2)
if ($r.Flags.Count -gt 0) { $line += "[" + ($r.Flags -join ", ") + "] " }
if ($r.Notes.Count -gt 0) { $line += ($r.Notes -join ", ") }
O $line.TrimEnd()
}
if ($rows.Count -gt $shown.Count) {
O " … показано $($shown.Count) из $($rows.Count); листать через -Offset/-Limit"
}
}
# --- Modes ----------------------------------------------------------------------
function Show-PackageList {
O "=== Пакеты XDTO: $($packages.Count) ==="
O ""
$shown = $packages
if ($Offset -gt 0 -or $packages.Count -gt $Limit) {
$end = [Math]::Min($Offset + $Limit, $packages.Count) - 1
if ($Offset -le $end) { $shown = $packages[$Offset..$end] } else { $shown = @() }
}
$wn = 0
foreach ($p in $shown) { if ($p.Name.Length -gt $wn) { $wn = $p.Name.Length } }
foreach ($p in $shown) {
$cnt = $p.Types.Count
O (" " + $p.Name.PadRight($wn + 2) + "$cnt".PadLeft(4) + " " + $p.Namespace)
}
if ($packages.Count -gt $shown.Count) {
O ""
O " … показано $($shown.Count) из $($packages.Count); листать через -Offset/-Limit"
}
O ""
O "Колонки: имя пакета, число типов, namespace."
O "Следующий шаг: -Package <имя> или -Namespace <URI> — состав пакета; -Name <Тип> — поиск типа по всем пакетам"
}
function Show-PackageOverview($pkg) {
O "=== Пакет XDTO: $($pkg.Name) ==="
O "Namespace: $($pkg.Namespace)"
if ($pkg.Imports.Count -gt 0) {
O ""
O "Импорты ($($pkg.Imports.Count)):"
foreach ($i in $pkg.Imports) {
$dep = $(if ($byNamespace.ContainsKey($i)) { $byNamespace[$i].Name } else { "(пакет не найден)" })
O " $i$dep"
}
}
if ($pkg.GlobalProps.Count -eq 0) {
O ""
O "Точки входа: нет — пакет не объявляет корневых элементов документа"
} else {
O ""
O "Точки входа ($($pkg.GlobalProps.Count)) — корневые элементы документа:"
foreach ($gp in $pkg.GlobalProps) {
$q = Split-Ref $gp $gp.GetAttribute("type")
$tn = $(if ($q) { Format-RefName $q $pkg } else { "произвольный" })
$form = $(if ($gp.GetAttribute("form") -eq "Attribute") { " (атрибут)" } else { "" })
O (" <" + $gp.GetAttribute("name") + "> → " + $tn + $form)
}
}
$objs = @(); $vals = @()
foreach ($k in (Sort-Ordinal $pkg.Types.Keys)) {
if ($pkg.Types[$k].get_LocalName() -eq "objectType") { $objs += $k } else { $vals += $k }
}
if ($objs.Count -gt 0) {
O ""
O "Объектные типы ($($objs.Count)):"
foreach ($n in $objs) {
$cnt = 0
foreach ($c in $pkg.Types[$n].ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $cnt++ }
}
$base = Split-Ref $pkg.Types[$n] $pkg.Types[$n].GetAttribute("base")
$suffix = $(if ($base) { " ← $($base.Local)" } else { "" })
O (" " + $n.PadRight(40) + "свойств: $cnt" + $suffix)
}
}
if ($vals.Count -gt 0) {
O ""
O "Типы значений ($($vals.Count)):"
foreach ($n in $vals) {
$res = Resolve-Scalar $pkg.Types[$n] $pkg
$line = " " + $n.PadRight(40) + (Format-Scalar $res)
if ($res.Enum.Count -gt 0) { $line += " значения: " + (($res.Enum | Select-Object -First 6) -join ", ") }
O $line
}
}
O ""
O "Следующий шаг: -Name <Тип> — структура типа для заполнения"
}
# Легенда едет вместе с выводом, а не живёт в инструкции: показываем только те
# обозначения, которые реально встретились, иначе она сама становится шумом.
function Write-Legend($rows) {
$text = ($rows | ForEach-Object { $_.Type + " " + ($_.Flags -join ",") + " " + ($_.Notes -join ",") }) -join " "
$items = @()
if ($text -match "объект ") { $items += "объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth" }
if ($text -match "←") { $items += "← Имя — исходный тип из схемы, слева от стрелки развёрнутое значение" }
if ($text -match "список") { $items += "список — коллекция, заполняется через .Добавить()" }
if ($text -match "до \d") { $items += "до N — коллекция с ограничением сверху" }
if ($text -match "значение элемента") { $items += "значение элемента — собственное значение узла XML" }
if ($text -match "·") { $items += "· Пакет — тип объявлен в другом пакете" }
if ($items.Count -eq 0) { return }
O ""
O "Обозначения:"
foreach ($i in $items) { O " $i" }
}
function Show-Type($pkg, [string]$typeName) {
$el = $pkg.Types[$typeName]
$kind = $el.get_LocalName()
if ($kind -eq "valueType") {
$res = Resolve-Scalar $el $pkg
O "=== Тип значения XDTO: $typeName ==="
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
O ""
O "Значение: $(Format-Scalar $res)"
foreach ($n in (Format-Notes $res)) { O " $n" }
if ($res.Enum.Count -gt 0) {
O ""
O "Допустимые значения ($($res.Enum.Count)):"
foreach ($v in $res.Enum) { O " $v" }
}
O ""
O "Создание:"
# Создать(<Тип>, <Значение>) принимает именно ТипЗначенияXDTO —
# для объектного типа эта форма неприменима
O " Значение = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`"), Значение);"
return
}
$hdr = "=== Тип XDTO: $typeName ==="
if ($Depth -gt 1) { $hdr += " (глубина $Depth)" }
O $hdr
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
$base = Split-Ref $el $el.GetAttribute("base")
if ($base) { O "Наследует: $($base.Local)" }
if ($el.GetAttribute("abstract") -eq "true") { O "Абстрактный — создаётся только тип-наследник" }
if ($el.GetAttribute("open") -eq "true") { O "Открытый — допускает произвольные элементы и атрибуты" }
O ""
$seen = New-Object System.Collections.Generic.HashSet[string]
[void]$seen.Add("$($pkg.Namespace)#$typeName")
$rows = Get-PropRows $el $pkg $Depth 0 $seen
$own = @($rows | Where-Object { $_.Indent -eq 0 })
if ($RequiredOnly) {
$all = $rows.Count
$rows = Select-Required $rows
$ownReq = @($rows | Where-Object { $_.Indent -eq 0 })
# Фильтр обязан сообщать о себе: иначе список читается как полный
O "Свойства: обязательных $($ownReq.Count) из $($own.Count) (-RequiredOnly; скрыто строк: $($all - $rows.Count))"
} else {
O "Свойства ($($own.Count)):"
}
Write-Rows $rows
Write-Legend $rows
O ""
O "Создание:"
O " Тип = ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`");"
O " Объект = ФабрикаXDTO.Создать(Тип);"
# Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
# ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
$named = @($rows | Where-Object { $_.ObjName -and $_.ObjName -ne "(анонимный)" } | Select-Object -First 1)
if ($named.Count -gt 0) {
O " // вложенный именованный тип — так же, как корневой:"
O " $($named[0].Name) = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($named[0].ObjNs)`", `"$($named[0].ObjName)`"));"
}
$anonRow = @($rows | Where-Object { $_.ObjName -eq "(анонимный)" } | Select-Object -First 1)
if ($anonRow.Count -gt 0) {
O " // у анонимного типа нет имени — только через свойство владельца:"
O " $($anonRow[0].Name) = ФабрикаXDTO.Создать(Тип.Свойства.Получить(`"$($anonRow[0].Name)`").Тип);"
}
$textRow = @($rows | Where-Object { $_.Flags -contains "значение элемента" } | Select-Object -First 1)
if ($textRow.Count -gt 0) {
O " // собственное значение узла лежит в свойстве $($textRow[0].Name)"
}
}
function Show-UsedBy([string]$typeName, $ownerPkg) {
O "=== Ссылки на тип: $typeName ==="
if ($ownerPkg) { O "Объявлен в: $($ownerPkg.Name) · $($ownerPkg.Namespace)" }
O ""
$hits = New-Object System.Collections.ArrayList
foreach ($p in $packages) {
foreach ($node in $p.Root.SelectNodes("//*")) {
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
foreach ($a in @("type", "base", "itemType", "memberTypes")) {
$raw = $node.GetAttribute($a)
if (-not $raw) { continue }
foreach ($one in ($raw -split "\s+")) {
$q = Split-Ref $node $one
if (-not $q -or $q.Local -ne $typeName) { continue }
if ($ownerPkg -and $q.Ns -and $q.Ns -ne $ownerPkg.Namespace) { continue }
$owner = $node
while ($owner -and @("objectType", "valueType") -notcontains $owner.get_LocalName()) { $owner = $owner.ParentNode }
$where = $(if ($owner -and $owner.GetAttribute("name")) { $owner.GetAttribute("name") } else { "(верхний уровень)" })
$what = $(if ($node.GetAttribute("name")) { $node.GetAttribute("name") } else { $node.get_LocalName() })
[void]$hits.Add(" $($p.Name).$where.$what ($a)")
}
}
}
}
if ($hits.Count -eq 0) { O " Ссылок не найдено"; return }
O "Найдено ($($hits.Count)):"
foreach ($h in (Sort-Ordinal ($hits | Select-Object -Unique))) { O $h }
}
# --- Dispatch -------------------------------------------------------------------
# Выбор пакета: явный путь, затем -Namespace / -Package, затем поиск типа по всем
$selected = $null
if ($directPkgDir) {
$leaf = [System.IO.Path]::GetFileName($directPkgDir)
$selected = $packages | Where-Object { $_.Name -eq $leaf } | Select-Object -First 1
}
if (-not $selected -and $Namespace) {
$selected = $packages | Where-Object { $_.Namespace -eq $Namespace } | Select-Object -First 1
if (-not $selected) { Fail "Пакет с namespace `"$Namespace`" не найден. Список: -PackagePath <корень> без параметров" }
}
if (-not $selected -and $Package) {
$selected = $packages | Where-Object { $_.Name -eq $Package } | Select-Object -First 1
if (-not $selected) { Fail "Пакет `"$Package`" не найден. Список: -PackagePath <корень> без параметров" }
}
if ($Mode -eq "used-by") {
if (-not $Name) { Fail "Режим used-by требует -Name <Тип>" }
$ownerPkg = $selected
if (-not $ownerPkg) { $ownerPkg = ($packages | Where-Object { $_.Types.ContainsKey($Name) } | Select-Object -First 1) }
Show-UsedBy $Name $ownerPkg
Flush-Output
exit 0
}
if ($Name) {
if (-not $selected) {
# Тип известен, пакет — нет: ищем по всей конфигурации
$found = @($packages | Where-Object { $_.Types.ContainsKey($Name) })
if ($found.Count -eq 0) { Fail "Тип `"$Name`" не найден ни в одном пакете конфигурации" }
if ($found.Count -gt 1) {
O "=== Тип `"$Name`" найден в нескольких пакетах ($($found.Count)) ==="
O "Уточните через -Namespace или -Package:"
O ""
foreach ($f in $found) { O " $($f.Name) · $($f.Namespace)" }
Flush-Output
exit 0
}
$selected = $found[0]
}
if (-not $selected.Types.ContainsKey($Name)) {
Fail "В пакете $($selected.Name) нет типа `"$Name`". Список типов: тот же вызов без -Name"
}
Show-Type $selected $Name
Flush-Output
exit 0
}
if ($selected) { Show-PackageOverview $selected } else { Show-PackageList }
Flush-Output
exit 0