mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-27 23:21:02 +03:00
958 lines
41 KiB
PowerShell
958 lines
41 KiB
PowerShell
# xdto-compile v1.1 — Build a 1C XDTO package from an XML Schema (XSD)
|
||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||
param(
|
||
[Parameter(Mandatory=$true, ParameterSetName='File')]
|
||
[Alias('Path')]
|
||
[string]$XsdPath,
|
||
[Parameter(Mandatory=$true, ParameterSetName='Inline')]
|
||
[string]$Xsd,
|
||
[Parameter(Mandatory=$true)]
|
||
[string]$OutputDir,
|
||
[string]$Name,
|
||
[object]$Synonym,
|
||
[string]$Comment,
|
||
[switch]$Force
|
||
)
|
||
|
||
$ErrorActionPreference = "Stop"
|
||
|
||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||
# для них нет и быть не должно (выведено по корпусу: импортируются, но
|
||
# targetNamespace с таким значением ни у одного пакета нет)
|
||
$PLATFORM_NS = @(
|
||
"http://v8.1c.ru/8.1/data/core",
|
||
"http://v8.1c.ru/8.1/data/enterprise",
|
||
"http://v8.1c.ru/8.1/data/enterprise/current-config",
|
||
"http://v8.1c.ru/8.1/data-composition-system/settings",
|
||
"http://v8.1c.ru/8.3/data/ext",
|
||
"http://www.w3.org/2001/XMLSchema"
|
||
)
|
||
|
||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||
|
||
$XDTO_NS = "http://v8.1c.ru/8.1/xdto"
|
||
$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"
|
||
$V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||
|
||
# --- Support guard (Ext/ParentConfigurations.bin) ---
|
||
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
|
||
# read-only configs unless allowed. Trigger = bin present; reaction from
|
||
# .v8-project.json editingAllowedCheck (deny|warn|off, default deny). Never
|
||
# throws — guard errors degrade to allow.
|
||
function Get-RootUuid([string]$xmlPath) {
|
||
if (-not (Test-Path $xmlPath)) { return $null }
|
||
try {
|
||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||
$el = $mx.DocumentElement.FirstChild
|
||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||
if ($el) { $u = $el.GetAttribute("uuid"); if ($u) { return $u } }
|
||
} catch {}
|
||
return $null
|
||
}
|
||
function Test-ExternalObjectRoot([string]$xmlPath) {
|
||
if (-not (Test-Path $xmlPath)) { return $false }
|
||
try {
|
||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||
$el = $mx.DocumentElement.FirstChild
|
||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.get_LocalName() }
|
||
} catch {}
|
||
return $false
|
||
}
|
||
function Find-V8Project([string]$startDir) {
|
||
$d = $startDir
|
||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||
$pj = Join-Path $d ".v8-project.json"
|
||
if (Test-Path $pj) { return $pj }
|
||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||
if ($parent -eq $d) { break }
|
||
$d = $parent
|
||
}
|
||
return $null
|
||
}
|
||
function Get-EditMode([string]$cfgDir) {
|
||
$mode = "deny"
|
||
try {
|
||
$pj = Find-V8Project $cfgDir
|
||
if ($pj) {
|
||
$cfg = Get-Content -Path $pj -Raw -Encoding UTF8 | ConvertFrom-Json
|
||
if ($cfg.PSObject.Properties.Name -contains 'editingAllowedCheck' -and $cfg.editingAllowedCheck) {
|
||
$mode = [string]$cfg.editingAllowedCheck
|
||
}
|
||
}
|
||
} catch {}
|
||
return $mode
|
||
}
|
||
function Assert-EditAllowed([string]$targetPath) {
|
||
try {
|
||
$mode = $null
|
||
$d = $targetPath
|
||
for ($i = 0; $i -lt 20 -and $d; $i++) {
|
||
$cfgXml = Join-Path $d "Configuration.xml"
|
||
$supportBin = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin"
|
||
# Автономный объект (внешняя обработка/отчёт) — граница климба
|
||
foreach ($x in @(Get-ChildItem -Path $d -Filter "*.xml" -File -ErrorAction SilentlyContinue)) {
|
||
if (Test-ExternalObjectRoot $x.FullName) { return }
|
||
}
|
||
if (Test-Path $cfgXml) {
|
||
if (Test-Path $supportBin) {
|
||
$mode = Get-EditMode $d
|
||
if ($mode -eq "off") { return }
|
||
$msg = "Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). Правка может быть запрещена."
|
||
if ($mode -eq "warn") { Write-Warning $msg; return }
|
||
throw "$msg Снимите с поддержки (/support-edit) или задайте editingAllowedCheck в .v8-project.json."
|
||
}
|
||
return
|
||
}
|
||
$parent = [System.IO.Path]::GetDirectoryName($d)
|
||
if ($parent -eq $d) { break }
|
||
$d = $parent
|
||
}
|
||
} catch [System.Management.Automation.RuntimeException] {
|
||
throw
|
||
} catch {}
|
||
}
|
||
|
||
# --- Load the schema ---
|
||
|
||
if ($PSCmdlet.ParameterSetName -eq 'Inline') {
|
||
$xsdText = $Xsd
|
||
$defaultName = "Package"
|
||
} else {
|
||
if (-not (Test-Path $XsdPath -PathType Leaf)) { throw "Файл XSD не найден: $XsdPath" }
|
||
$xsdText = [System.IO.File]::ReadAllText($XsdPath)
|
||
$defaultName = [System.IO.Path]::GetFileNameWithoutExtension($XsdPath)
|
||
}
|
||
|
||
$xdoc = New-Object System.Xml.XmlDocument
|
||
$xdoc.PreserveWhitespace = $false
|
||
try { $xdoc.LoadXml($xsdText) } catch { throw "Не удалось разобрать XSD: $($_.Exception.Message)" }
|
||
|
||
$schema = $xdoc.DocumentElement
|
||
if ($schema.get_LocalName() -ne "schema" -or $schema.NamespaceURI -ne $XS_NS) {
|
||
throw "Ожидался корневой <xs:schema> в пространстве имён $XS_NS"
|
||
}
|
||
|
||
$targetNs = $schema.GetAttribute("targetNamespace")
|
||
|
||
# --- Emit-tree primitives -----------------------------------------------------
|
||
# A node carries attributes in canonical order; QName values keep their namespace
|
||
# so prefixes can be assigned per depth at serialization time (the dNpN scheme).
|
||
|
||
function New-Node([string]$tag) {
|
||
return [pscustomobject]@{ Tag = $tag; Attrs = (New-Object System.Collections.ArrayList); Children = (New-Object System.Collections.ArrayList); Text = $null; Prefix = $null; DeclareNs = $null }
|
||
}
|
||
function Add-Attr($node, [string]$name, $value) {
|
||
# $value НЕ типизируем: [string]$null коэрсится в "" и атрибут ложно появляется
|
||
if ($null -eq $value) { return }
|
||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = [string]$value; Ns = $null; Local = $null })
|
||
}
|
||
function Add-QAttr($node, [string]$name, $ns, $local) {
|
||
if ($null -eq $local) { return }
|
||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = [string]$ns; Local = [string]$local })
|
||
}
|
||
function Add-QListAttr($node, [string]$name, $pairs, [bool]$clark) {
|
||
if (-not $pairs -or $pairs.Count -eq 0) { return }
|
||
[void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = $null; Local = $null; List = $pairs; Clark = $clark })
|
||
}
|
||
function Add-Child($node, $child) { if ($child) { [void]$node.Children.Add($child) } }
|
||
|
||
# Canonical attribute order per element — derived by topological sort over the
|
||
# whole 8.3.24 corpus (acc + erp, 760 packages), see docs/1c-xdto-spec.md.
|
||
$ATTR_ORDER = @{
|
||
"package" = @("targetNamespace", "elementFormQualified", "attributeFormQualified")
|
||
"import" = @("namespace")
|
||
"objectType" = @("name", "base", "open", "abstract", "mixed", "ordered", "sequenced")
|
||
"property" = @("name", "ref", "type", "lowerBound", "upperBound", "nillable", "fixed", "default", "form", "localName", "qualified")
|
||
"valueType" = @("name", "base", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace")
|
||
"typeDef" = @("xsi:type", "base", "mixed", "open", "ordered", "sequenced", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace")
|
||
"enumeration" = @("xsi:type")
|
||
}
|
||
|
||
function Sort-Attrs($node) {
|
||
$order = $ATTR_ORDER[$node.Tag]
|
||
if (-not $order) { return $node.Attrs }
|
||
$sorted = New-Object System.Collections.ArrayList
|
||
foreach ($n in $order) {
|
||
foreach ($a in $node.Attrs) { if ($a.Name -eq $n) { [void]$sorted.Add($a) } }
|
||
}
|
||
foreach ($a in $node.Attrs) { if ($order -notcontains $a.Name) { [void]$sorted.Add($a) } }
|
||
return $sorted
|
||
}
|
||
|
||
function Esc([string]$s) {
|
||
if ($null -eq $s) { return "" }
|
||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace('"', """)
|
||
}
|
||
function EscText([string]$s) {
|
||
if ($null -eq $s) { return "" }
|
||
return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">")
|
||
}
|
||
|
||
# --- Serializer with the dNpN prefix scheme ---
|
||
|
||
$out = New-Object System.Text.StringBuilder
|
||
|
||
function Serialize-Node($node, [int]$depth, $inherited) {
|
||
$indent = "`t" * ($depth - 1)
|
||
$attrs = Sort-Attrs $node
|
||
|
||
# Namespaces needing a NEW declaration here: те, что ещё не в области видимости.
|
||
# Сериализатор платформы объявляет префикс на первом узле, где он нужен, а
|
||
# потомки его переиспользуют — отсюда d2p1 у property внутри objectType.
|
||
$localNs = New-Object System.Collections.ArrayList
|
||
function Need-Prefix([string]$ns) {
|
||
if (-not $ns -or $ns -eq $XS_NS -or $ns -eq $XSI_NS) { return }
|
||
if ($inherited.ContainsKey($ns)) { return }
|
||
if (-not $localNs.Contains($ns)) { [void]$localNs.Add($ns) }
|
||
}
|
||
foreach ($a in $attrs) {
|
||
if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) {
|
||
# Нотация Кларка несёт ns в значении и префикса не требует; редкие
|
||
# случаи, где платформа его всё же объявила, приходят зеркалом declareNs
|
||
if (-not $a.Clark) { foreach ($p in $a.List) { Need-Prefix $p.Ns } }
|
||
} elseif ($a.Ns) { Need-Prefix $a.Ns }
|
||
}
|
||
# Свойство с qualified платформа сериализует с явным префиксом пространства
|
||
# имён XDTO — и в имени тега, и в имени самого атрибута
|
||
$hasQualified = $false
|
||
foreach ($a in $attrs) { if ($a.Name -eq "qualified") { $hasQualified = $true } }
|
||
if ($hasQualified) { Need-Prefix $XDTO_NS }
|
||
if ($node.DeclareNs) { Need-Prefix $node.DeclareNs }
|
||
|
||
$prefixOf = @{}
|
||
foreach ($k in $inherited.Keys) { $prefixOf[$k] = $inherited[$k] }
|
||
$nsDecls = ""
|
||
for ($i = 0; $i -lt $localNs.Count; $i++) {
|
||
# Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет
|
||
$px = if ($i -eq 0 -and $node.Prefix) { $node.Prefix } else { "d${depth}p$($i + 1)" }
|
||
$prefixOf[$localNs[$i]] = $px
|
||
$nsDecls += " xmlns:$px=`"$(Esc $localNs[$i])`""
|
||
}
|
||
function QVal([string]$ns, [string]$local) {
|
||
if (-not $ns) { return $local }
|
||
if ($ns -eq $XS_NS) { return "xs:$local" }
|
||
if ($ns -eq $XSI_NS) { return "xsi:$local" }
|
||
return "$($prefixOf[$ns]):$local"
|
||
}
|
||
|
||
$attrText = ""
|
||
foreach ($a in $attrs) {
|
||
if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) {
|
||
$vals = @()
|
||
foreach ($p in $a.List) {
|
||
if ($a.Clark) { $vals += $(if ($p.Ns) { "{$($p.Ns)}$($p.Local)" } else { $p.Local }) }
|
||
else { $vals += (QVal $p.Ns $p.Local) }
|
||
}
|
||
$attrText += " $($a.Name)=`"$(Esc ($vals -join ' '))`""
|
||
} elseif ($a.Ns -or $a.Local) {
|
||
$attrText += " $($a.Name)=`"$(Esc (QVal $a.Ns $a.Local))`""
|
||
} elseif ($a.Name -eq "qualified") {
|
||
$attrText += " $($prefixOf[$XDTO_NS]):qualified=`"$(Esc $a.Value)`""
|
||
} else {
|
||
$attrText += " $($a.Name)=`"$(Esc $a.Value)`""
|
||
}
|
||
}
|
||
|
||
$tagName = $node.Tag
|
||
if ($hasQualified) { $tagName = "$($prefixOf[$XDTO_NS]):$($node.Tag)" }
|
||
|
||
$hasChildren = $node.Children.Count -gt 0
|
||
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>, а не <enumeration></enumeration>
|
||
$hasText = ($null -ne $node.Text -and $node.Text -ne "")
|
||
|
||
if (-not $hasChildren -and -not $hasText) {
|
||
[void]$out.Append("$indent<$tagName$nsDecls$attrText/>`r`n")
|
||
return
|
||
}
|
||
if ($hasText -and -not $hasChildren) {
|
||
[void]$out.Append("$indent<$tagName$nsDecls$attrText>$(EscText $node.Text)</$tagName>`r`n")
|
||
return
|
||
}
|
||
[void]$out.Append("$indent<$tagName$nsDecls$attrText>`r`n")
|
||
foreach ($c in $node.Children) { Serialize-Node $c ($depth + 1) $prefixOf }
|
||
[void]$out.Append("$indent</$tagName>`r`n")
|
||
}
|
||
|
||
# --- XSD reading helpers ---
|
||
|
||
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
|
||
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
|
||
$script:warnings = New-Object System.Collections.ArrayList
|
||
function Warn([string]$msg) {
|
||
if (-not $script:warnings.Contains($msg)) { [void]$script:warnings.Add($msg) }
|
||
}
|
||
|
||
function XA([System.Xml.XmlElement]$el, [string]$name) {
|
||
if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
|
||
return $null
|
||
}
|
||
function MA([System.Xml.XmlElement]$el, [string]$name) {
|
||
# xdto: mirror attribute — the literal value to write into Package.bin.
|
||
# Префикс не фиксируем: ищем по namespace, а не по строке "xdto:".
|
||
$a = $el.Attributes.GetNamedItem($name, $XDTO_NS)
|
||
if ($null -eq $a) { return $null }
|
||
return $a.Value
|
||
}
|
||
function XChildren([System.Xml.XmlElement]$el, [string]$local) {
|
||
$res = New-Object System.Collections.ArrayList
|
||
foreach ($c in $el.ChildNodes) {
|
||
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.NamespaceURI -eq $XS_NS -and $c.get_LocalName() -eq $local) { [void]$res.Add($c) }
|
||
}
|
||
# ArrayList, а не @(): PowerShell разворачивает массив из одного элемента при return
|
||
return ,$res
|
||
}
|
||
function XFirst([System.Xml.XmlElement]$el, [string]$local) {
|
||
$r = XChildren $el $local
|
||
if ($r.Count -gt 0) { return $r[0] }
|
||
return $null
|
||
}
|
||
|
||
# Split a QName from the XSD into (ns, local) using that element's prefix scope
|
||
function Split-QName([System.Xml.XmlElement]$el, [string]$qname) {
|
||
if ($null -eq $qname -or $qname -eq "") { return $null }
|
||
$parts = $qname.Split(":")
|
||
if ($parts.Count -eq 2) {
|
||
$ns = $el.GetNamespaceOfPrefix($parts[0])
|
||
$local = $parts[1]
|
||
} else {
|
||
# Прощающий ввод: голое имя типа трактуем как тип целевого пространства
|
||
$ns = $el.GetNamespaceOfPrefix("")
|
||
if (-not $ns) { $ns = $targetNs }
|
||
$local = $parts[0]
|
||
}
|
||
return [pscustomobject]@{ Ns = $ns; Local = $local }
|
||
}
|
||
function Split-QNameList([System.Xml.XmlElement]$el, [string]$list) {
|
||
if (-not $list) { return @() }
|
||
$res = @()
|
||
foreach ($q in ($list -split "\s+")) { if ($q) { $res += (Split-QName $el $q) } }
|
||
return $res
|
||
}
|
||
|
||
$FACETS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace")
|
||
|
||
# --- simpleType -> valueType / typeDef(ValueType) ---
|
||
|
||
function Fill-SimpleType($node, [System.Xml.XmlElement]$st) {
|
||
$restriction = XFirst $st "restriction"
|
||
$list = XFirst $st "list"
|
||
$union = XFirst $st "union"
|
||
|
||
if ($list) {
|
||
$it = Split-QName $list (XA $list "itemType")
|
||
$mv = MA $list "variety"
|
||
Add-Attr $node "variety" $(if ($null -ne $mv) { $mv } else { "List" })
|
||
if ($it) { Add-QAttr $node "itemType" $it.Ns $it.Local }
|
||
return
|
||
}
|
||
if ($union) {
|
||
$mv = MA $union "variety"
|
||
Set-AttrValue $node "variety" $(if ($null -ne $mv) { $mv } else { "Union" })
|
||
$members = @(Split-QNameList $union (XA $union "memberTypes"))
|
||
# По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса
|
||
$useClark = ((MA $union "memberTypesForm") -ne "prefixed")
|
||
if ($members.Count -gt 0) { Add-QListAttr $node "memberTypes" $members $useClark }
|
||
$node.DeclareNs = MA $union "declareNs"
|
||
foreach ($anon in (XChildren $union "simpleType")) {
|
||
# typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса)
|
||
$td = New-Node "typeDef"
|
||
Fill-SimpleType $td $anon
|
||
Add-Child $node $td
|
||
}
|
||
return
|
||
}
|
||
if ($restriction) {
|
||
$b = Split-QName $restriction (XA $restriction "base")
|
||
if ($b) { Add-QAttr $node "base" $b.Ns $b.Local }
|
||
$mv = MA $restriction "variety"
|
||
if ($null -ne $mv) { Add-Attr $node "variety" $mv }
|
||
# Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type
|
||
$anonBase = XFirst $restriction "simpleType"
|
||
if ($anonBase) {
|
||
$td = New-Node "typeDef"
|
||
Fill-SimpleType $td $anonBase
|
||
Add-Child $node $td
|
||
}
|
||
foreach ($f in $FACETS) {
|
||
foreach ($fe in (XChildren $restriction $f)) { Add-Attr $node $f (XA $fe "value") }
|
||
}
|
||
foreach ($pe in (XChildren $restriction "pattern")) {
|
||
$pn = New-Node "pattern"; $pn.Text = (XA $pe "value"); Add-Child $node $pn
|
||
}
|
||
foreach ($en in (XChildren $restriction "enumeration")) {
|
||
$enode = New-Node "enumeration"
|
||
$mt = MA $en "type"
|
||
if ($null -ne $mt) {
|
||
$q = Split-QName $en $mt
|
||
Add-QAttr $enode "xsi:type" $q.Ns $q.Local
|
||
}
|
||
$enode.Text = (XA $en "value")
|
||
Add-Child $node $enode
|
||
}
|
||
}
|
||
}
|
||
|
||
function Get-PropKey($p) {
|
||
foreach ($a in $p.Attrs) { if ($a.Name -eq "name") { return $a.Value } }
|
||
foreach ($a in $p.Attrs) { if ($a.Name -eq "ref") { return "@" + $a.Local } }
|
||
return $null
|
||
}
|
||
|
||
# Восстановить исходный порядок свойств по зеркалу xdto:order
|
||
function Reorder-Properties($node, $names) {
|
||
$props = @($node.Children | Where-Object { $_.Tag -eq "property" })
|
||
if ($props.Count -lt 2) { return }
|
||
$byKey = @{}
|
||
foreach ($p in $props) {
|
||
$k = Get-PropKey $p
|
||
if ($null -ne $k -and -not $byKey.ContainsKey($k)) { $byKey[$k] = $p }
|
||
}
|
||
$ordered = New-Object System.Collections.ArrayList
|
||
foreach ($n in $names) {
|
||
if ($byKey.ContainsKey($n)) { [void]$ordered.Add($byKey[$n]); $byKey.Remove($n) }
|
||
}
|
||
foreach ($p in $props) { if ($ordered -notcontains $p) { [void]$ordered.Add($p) } }
|
||
$others = @($node.Children | Where-Object { $_.Tag -ne "property" })
|
||
$node.Children.Clear()
|
||
foreach ($p in $ordered) { [void]$node.Children.Add($p) }
|
||
foreach ($o in $others) { [void]$node.Children.Add($o) }
|
||
}
|
||
|
||
function Set-AttrValue($node, [string]$name, [string]$value) {
|
||
foreach ($a in $node.Attrs) { if ($a.Name -eq $name) { $a.Value = $value; return } }
|
||
Add-Attr $node $name $value
|
||
}
|
||
|
||
# --- element / attribute -> property ---
|
||
|
||
function Build-Property([System.Xml.XmlElement]$el, [bool]$isAttribute) {
|
||
$p = New-Node "property"
|
||
|
||
$xsdName = XA $el "name"
|
||
$mirrorName = MA $el "name"
|
||
if ($null -ne $mirrorName) {
|
||
Add-Attr $p "name" $mirrorName
|
||
$localName = $xsdName
|
||
} else {
|
||
Add-Attr $p "name" $xsdName
|
||
$localName = $null
|
||
}
|
||
|
||
$refQ = Split-QName $el (XA $el "ref")
|
||
if ($refQ) { Add-QAttr $p "ref" $refQ.Ns $refQ.Local }
|
||
|
||
$typeQ = Split-QName $el (XA $el "type")
|
||
if ($typeQ) { Add-QAttr $p "type" $typeQ.Ns $typeQ.Local }
|
||
|
||
if ($isAttribute) {
|
||
Add-Attr $p "lowerBound" (MA $el "lowerBound")
|
||
Add-Attr $p "upperBound" (MA $el "upperBound")
|
||
Add-Attr $p "nillable" (MA $el "nillable")
|
||
} else {
|
||
Add-Attr $p "lowerBound" (XA $el "minOccurs")
|
||
$maxOcc = XA $el "maxOccurs"
|
||
if ($null -ne $maxOcc) { Add-Attr $p "upperBound" $(if ($maxOcc -eq "unbounded") { "-1" } else { $maxOcc }) }
|
||
Add-Attr $p "nillable" (XA $el "nillable")
|
||
}
|
||
|
||
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
|
||
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
|
||
$mFixed = MA $el "fixed"
|
||
if ($null -ne $mFixed) {
|
||
Add-Attr $p "fixed" $mFixed
|
||
Add-Attr $p "default" (XA $el "default")
|
||
if ($mFixed -ceq "true" -and $null -eq (XA $el "default")) {
|
||
Warn "Свойство `"$(XA $el 'name')`": xdto:fixed=`"true`" без default — платформа отвергнет пакет («Отсутствует фиксированное значение»). Значение задаётся атрибутом default, либо пишите XSD-форму fixed=`"значение`""
|
||
}
|
||
} elseif ($null -ne (XA $el "fixed")) {
|
||
Add-Attr $p "fixed" "true"
|
||
Add-Attr $p "default" (XA $el "fixed")
|
||
} else {
|
||
Add-Attr $p "default" (XA $el "default")
|
||
}
|
||
|
||
if ($isAttribute) {
|
||
Add-Attr $p "form" "Attribute"
|
||
} else {
|
||
$mf = MA $el "form"
|
||
if ($null -ne $mf) { Add-Attr $p "form" $mf }
|
||
}
|
||
Add-Attr $p "localName" $localName
|
||
Add-Attr $p "qualified" (MA $el "qualified")
|
||
$p.Prefix = MA $el "prefix"
|
||
|
||
# Anonymous inline type
|
||
$anonSimple = XFirst $el "simpleType"
|
||
$anonComplex = XFirst $el "complexType"
|
||
if ($anonSimple) {
|
||
$td = New-Node "typeDef"
|
||
Add-Attr $td "xsi:type" "ValueType"
|
||
Fill-SimpleType $td $anonSimple
|
||
Add-Child $p $td
|
||
} elseif ($anonComplex) {
|
||
$td = New-Node "typeDef"
|
||
Add-Attr $td "xsi:type" "ObjectType"
|
||
Fill-ComplexType $td $anonComplex
|
||
Add-Child $p $td
|
||
}
|
||
return $p
|
||
}
|
||
|
||
# --- complexType -> objectType / typeDef(ObjectType) ---
|
||
|
||
# Разрешение xs:group / xs:attributeGroup по ссылке
|
||
$script:GROUPS = @{}
|
||
$script:ATTR_GROUPS = @{}
|
||
function Resolve-Group([System.Xml.XmlElement]$el, [string]$kind) {
|
||
$ref = XA $el "ref"
|
||
if (-not $ref) { return $null }
|
||
$q = Split-QName $el $ref
|
||
if (-not $q) { return $null }
|
||
$map = if ($kind -eq "group") { $script:GROUPS } else { $script:ATTR_GROUPS }
|
||
if ($map.ContainsKey($q.Local)) { return $map[$q.Local] }
|
||
return $null
|
||
}
|
||
|
||
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
|
||
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
|
||
function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth, [bool]$optionalize = $false) {
|
||
if ($depth -gt 20) { return }
|
||
foreach ($c in $particle.ChildNodes) {
|
||
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.NamespaceURI -ne $XS_NS) { continue }
|
||
switch ($c.get_LocalName()) {
|
||
"element" {
|
||
$prop = Build-Property $c $false
|
||
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
|
||
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
|
||
if ($optionalize) { Set-AttrValue $prop "lowerBound" "0" }
|
||
[void]$elemList.Add($prop)
|
||
}
|
||
"any" { $isOpen.Value = $true }
|
||
"sequence" {
|
||
Warn "$typeName : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств"
|
||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||
}
|
||
"choice" {
|
||
$branches = @()
|
||
foreach ($b in $c.ChildNodes) {
|
||
if ($b.NodeType -eq [System.Xml.XmlNodeType]::Element -and $b.NamespaceURI -eq $XS_NS -and $b.HasAttribute("name")) {
|
||
$branches += $b.GetAttribute("name")
|
||
}
|
||
}
|
||
$list = if ($branches.Count -gt 0) { " (" + ($branches -join ", ") + ")" } else { "" }
|
||
Warn ("$typeName : вложенная xs:choice уплощена — ветки$list сделаны необязательными. " +
|
||
"Выбор одного из вариантов не сохранён: модель не запретит заполнить сразу несколько или ни одного")
|
||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $true
|
||
}
|
||
"all" {
|
||
Warn "$typeName : xs:all трактуется как последовательность"
|
||
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||
}
|
||
"group" {
|
||
$g = Resolve-Group $c "group"
|
||
if ($g) {
|
||
foreach ($gc in $g.ChildNodes) {
|
||
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
|
||
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
|
||
Collect-Particle $gc $elemList $isOpen $typeName ($depth + 1) $optionalize
|
||
}
|
||
}
|
||
} else {
|
||
Warn "$typeName : не найдена группа $(XA $c 'ref') — её свойства в пакет не попали"
|
||
}
|
||
}
|
||
}
|
||
# Кратность на самой частице модель выразить не может
|
||
if (@("sequence", "choice", "all", "group") -contains $c.get_LocalName()) {
|
||
if ((XA $c "maxOccurs") -or (XA $c "minOccurs")) {
|
||
Warn "$typeName : кратность на вложенной частице (<xs:$($c.get_LocalName()) minOccurs/maxOccurs>) не выражается в модели XDTO"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# open / ordered / sequenced / abstract / mixed: выводим где выводимо,
|
||
# остальное приходит зеркалом xdto:
|
||
function Set-TypeFlags($node, [System.Xml.XmlElement]$ct, $isOpen, $choice) {
|
||
$mOpen = MA $ct "open"
|
||
if ($null -ne $mOpen) { Add-Attr $node "open" $mOpen }
|
||
elseif ($isOpen) { Add-Attr $node "open" "true" }
|
||
|
||
$mOrdered = MA $ct "ordered"
|
||
if ($null -ne $mOrdered) { Add-Attr $node "ordered" $mOrdered }
|
||
elseif ($choice) { Add-Attr $node "ordered" "false" }
|
||
|
||
$mSeq = MA $ct "sequenced"
|
||
if ($null -ne $mSeq) { Add-Attr $node "sequenced" $mSeq }
|
||
|
||
$mAbstract = MA $ct "abstract"
|
||
if ($null -ne $mAbstract) { Add-Attr $node "abstract" $mAbstract }
|
||
elseif ((XA $ct "abstract") -eq "true") { Add-Attr $node "abstract" "true" }
|
||
|
||
$mMixed = MA $ct "mixed"
|
||
if ($null -ne $mMixed) { Add-Attr $node "mixed" $mMixed }
|
||
elseif ((XA $ct "mixed") -eq "true") { Add-Attr $node "mixed" "true" }
|
||
}
|
||
|
||
function Fill-ComplexType($node, [System.Xml.XmlElement]$ct) {
|
||
# xs:complexContent/xs:extension carries the base type
|
||
$content = XFirst $ct "complexContent"
|
||
$body = $ct
|
||
if ($content) {
|
||
$ext = XFirst $content "extension"
|
||
if ($ext) {
|
||
$b = Split-QName $ext (XA $ext "base")
|
||
if ($b) { Add-QAttr $node "base" $b.Ns $b.Local }
|
||
$body = $ext
|
||
}
|
||
}
|
||
|
||
# xs:simpleContent -> a "Text" property holding the element's own value
|
||
$simple = XFirst $ct "simpleContent"
|
||
if ($simple) {
|
||
$ext = XFirst $simple "extension"
|
||
if ($ext) {
|
||
foreach ($a in (XChildren $ext "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||
$tp = New-Node "property"
|
||
$tName = MA $ext "textName"
|
||
Add-Attr $tp "name" $(if ($null -ne $tName) { $tName } else { "__content" })
|
||
$b = Split-QName $ext (XA $ext "base")
|
||
if ($b) { Add-QAttr $tp "type" $b.Ns $b.Local }
|
||
Add-Attr $tp "lowerBound" (MA $ext "textlowerBound")
|
||
Add-Attr $tp "upperBound" (MA $ext "textupperBound")
|
||
Add-Attr $tp "nillable" (MA $ext "textnillable")
|
||
Add-Attr $tp "form" "Text"
|
||
Add-Child $node $tp
|
||
# xs:simpleContent не отменяет флаги самого xs:complexType
|
||
Set-TypeFlags $node $ct $false $null
|
||
return
|
||
}
|
||
}
|
||
|
||
# Particle: xs:sequence (ordered) or xs:choice (ordered="false")
|
||
$seq = XFirst $body "sequence"
|
||
$cho = XFirst $body "choice"
|
||
$all = XFirst $body "all"
|
||
$grp = XFirst $body "group"
|
||
$particle = if ($seq) { $seq } elseif ($cho) { $cho } elseif ($all) { $all } else { $grp }
|
||
$isOpen = $false
|
||
|
||
# Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5%
|
||
# типов корпуса). Отклонения приходят зеркалом xdto:order.
|
||
$elemProps = New-Object System.Collections.ArrayList
|
||
$typeName = if ($ct.HasAttribute("name")) { $ct.GetAttribute("name") } else { "(анонимный тип)" }
|
||
if ($particle) {
|
||
$openRef = [ref]$isOpen
|
||
if ($all) {
|
||
Warn "$typeName : xs:all трактуется как последовательность"
|
||
}
|
||
if ($grp -and -not $seq -and -not $cho -and -not $all) {
|
||
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
|
||
$g = Resolve-Group $grp "group"
|
||
if ($g) {
|
||
foreach ($gc in $g.ChildNodes) {
|
||
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
|
||
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
|
||
Collect-Particle $gc $elemProps $openRef $typeName 1
|
||
}
|
||
}
|
||
} else {
|
||
Warn "$typeName : не найдена группа $(XA $grp 'ref') — её свойства в пакет не попали"
|
||
}
|
||
} else {
|
||
Collect-Particle $particle $elemProps $openRef $typeName 0
|
||
}
|
||
$isOpen = $openRef.Value
|
||
}
|
||
foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||
# xs:attributeGroup раскрываем по ссылке
|
||
foreach ($ag in (XChildren $body "attributeGroup")) {
|
||
$g = Resolve-Group $ag "attributeGroup"
|
||
if ($g) {
|
||
foreach ($a in (XChildren $g "attribute")) { Add-Child $node (Build-Property $a $true) }
|
||
} else {
|
||
Warn "Не найдена группа атрибутов $(XA $ag 'ref') — её атрибуты в пакет не попали"
|
||
}
|
||
}
|
||
foreach ($e in $elemProps) { Add-Child $node $e }
|
||
if ((XChildren $body "anyAttribute").Count -gt 0) { $isOpen = $true }
|
||
|
||
$mOrder = MA $ct "order"
|
||
if ($null -ne $mOrder) { Reorder-Properties $node ($mOrder -split "\|") }
|
||
|
||
Set-TypeFlags $node $ct $isOpen $cho
|
||
}
|
||
|
||
# --- Build the package tree ---
|
||
|
||
$pkgNode = New-Node "package"
|
||
Add-Attr $pkgNode "targetNamespace" $targetNs
|
||
|
||
$efqMirror = MA $schema "elementFormQualified"
|
||
$afqMirror = MA $schema "attributeFormQualified"
|
||
$efd = XA $schema "elementFormDefault"
|
||
$afd = XA $schema "attributeFormDefault"
|
||
if ($null -ne $efqMirror) { Add-Attr $pkgNode "elementFormQualified" $efqMirror }
|
||
elseif ($null -ne $efd) { Add-Attr $pkgNode "elementFormQualified" $(if ($efd -eq "qualified") { "true" } else { "false" }) }
|
||
if ($null -ne $afqMirror) { Add-Attr $pkgNode "attributeFormQualified" $afqMirror }
|
||
elseif ($null -ne $afd) { Add-Attr $pkgNode "attributeFormQualified" $(if ($afd -eq "qualified") { "true" } else { "false" }) }
|
||
|
||
# Metadata properties from xs:annotation/xs:appinfo
|
||
$metaName = $null; $metaComment = $null; $metaSynonym = @()
|
||
$ann = XFirst $schema "annotation"
|
||
if ($ann) {
|
||
$appinfo = XFirst $ann "appinfo"
|
||
if ($appinfo) {
|
||
foreach ($pk in $appinfo.ChildNodes) {
|
||
if ($pk.NodeType -ne [System.Xml.XmlNodeType]::Element -or $pk.NamespaceURI -ne $XDTO_NS) { continue }
|
||
foreach ($f in $pk.ChildNodes) {
|
||
if ($f.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||
switch ($f.get_LocalName()) {
|
||
"name" { $metaName = $f.InnerText }
|
||
"comment" { $metaComment = $f.InnerText }
|
||
"synonym" { $metaSynonym += @{ Lang = $f.GetAttribute("lang"); Content = $f.InnerText } }
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
|
||
foreach ($node in $schema.ChildNodes) {
|
||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
|
||
$nm = XA $node "name"
|
||
if ($node.get_LocalName() -eq "group" -and $nm) { $script:GROUPS[$nm] = $node }
|
||
if ($node.get_LocalName() -eq "attributeGroup" -and $nm) { $script:ATTR_GROUPS[$nm] = $node }
|
||
}
|
||
|
||
# Конструкции XSD, которым в модели XDTO нет соответствия
|
||
foreach ($sg in $schema.SelectNodes("//*[local-name()='element'][@substitutionGroup]")) {
|
||
Warn "Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление $($sg.GetAttribute('name')) сохранено как обычное"
|
||
}
|
||
foreach ($idc in @("key", "keyref", "unique")) {
|
||
if ($schema.SelectNodes("//*[local-name()='$idc']").Count -gt 0) {
|
||
Warn "Ограничения целостности (xs:$idc) в модели XDTO не хранятся — отброшены"
|
||
}
|
||
}
|
||
if ($schema.SelectNodes("//*[local-name()='redefine']").Count -gt 0) {
|
||
Warn "xs:redefine не поддерживается — переопределения проигнорированы"
|
||
}
|
||
if ($schema.SelectNodes("//*[local-name()='include']").Count -gt 0) {
|
||
Warn "xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. Соберите включаемую схему отдельным пакетом и добавьте <xs:import>"
|
||
}
|
||
|
||
foreach ($node in $schema.ChildNodes) {
|
||
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
|
||
switch ($node.get_LocalName()) {
|
||
"annotation" { }
|
||
"group" { }
|
||
"attributeGroup" { }
|
||
"notation" { }
|
||
"import" {
|
||
$n = New-Node "import"
|
||
Add-Attr $n "namespace" (XA $node "namespace")
|
||
Add-Child $pkgNode $n
|
||
}
|
||
"include" { }
|
||
"element" { Add-Child $pkgNode (Build-Property $node $false) }
|
||
"attribute" { Add-Child $pkgNode (Build-Property $node $true) }
|
||
"simpleType" {
|
||
$n = New-Node "valueType"
|
||
Add-Attr $n "name" (XA $node "name")
|
||
Fill-SimpleType $n $node
|
||
Add-Child $pkgNode $n
|
||
}
|
||
"complexType" {
|
||
$n = New-Node "objectType"
|
||
Add-Attr $n "name" (XA $node "name")
|
||
Fill-ComplexType $n $node
|
||
Add-Child $pkgNode $n
|
||
}
|
||
}
|
||
}
|
||
|
||
# --- Serialize Package.bin ---
|
||
|
||
# Модель XDTO требует строгой последовательности элементов верхнего уровня:
|
||
# import → property → valueType → objectType. Порядок объявлений в XSD произвольный,
|
||
# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования
|
||
# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют, так что
|
||
# round-trip не затрагивается.
|
||
$TOP_ORDER = @("import", "property", "valueType", "objectType")
|
||
$sortedChildren = New-Object System.Collections.ArrayList
|
||
foreach ($t in $TOP_ORDER) {
|
||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq $t) { [void]$sortedChildren.Add($c) } }
|
||
}
|
||
foreach ($c in $pkgNode.Children) { if ($TOP_ORDER -notcontains $c.Tag) { [void]$sortedChildren.Add($c) } }
|
||
$pkgNode.Children.Clear()
|
||
foreach ($c in $sortedChildren) { [void]$pkgNode.Children.Add($c) }
|
||
|
||
$attrsRoot = Sort-Attrs $pkgNode
|
||
$rootAttrText = ""
|
||
foreach ($a in $attrsRoot) { $rootAttrText += " $($a.Name)=`"$(Esc $a.Value)`"" }
|
||
[void]$out.Append("<package xmlns=`"$XDTO_NS`" xmlns:xs=`"$XS_NS`" xmlns:xsi=`"$XSI_NS`"$rootAttrText>`r`n")
|
||
foreach ($c in $pkgNode.Children) { Serialize-Node $c 2 @{} }
|
||
[void]$out.Append("</package>")
|
||
|
||
$binText = $out.ToString()
|
||
|
||
# --- Resolve the package name ---
|
||
|
||
if (-not $Name) {
|
||
if ($metaName) { $Name = $metaName } else { $Name = $defaultName }
|
||
}
|
||
# Санация под идентификатор 1С
|
||
$Name = ($Name -replace '[^\wЀ-ӿ]', '_')
|
||
if ($Name -match '^\d') { $Name = "_$Name" }
|
||
|
||
Assert-EditAllowed $OutputDir
|
||
|
||
$pkgRoot = Join-Path $OutputDir "XDTOPackages"
|
||
$pkgDir = Join-Path $pkgRoot $Name
|
||
$extDir = Join-Path $pkgDir "Ext"
|
||
$mdFile = Join-Path $pkgRoot "$Name.xml"
|
||
$binFile = Join-Path $extDir "Package.bin"
|
||
|
||
if ((Test-Path $binFile) -and -not $Force) {
|
||
throw "Пакет уже существует: $binFile. Используйте -Force для перезаписи."
|
||
}
|
||
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
||
|
||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||
[System.IO.File]::WriteAllText($binFile, $binText, $encBom)
|
||
|
||
# --- Metadata object file ---
|
||
|
||
if (-not $Synonym -and $metaSynonym.Count -gt 0) {
|
||
$synItems = $metaSynonym
|
||
} elseif ($Synonym -is [System.Collections.IDictionary]) {
|
||
$synItems = @()
|
||
foreach ($k in $Synonym.Keys) { $synItems += @{ Lang = [string]$k; Content = [string]$Synonym[$k] } }
|
||
} elseif ($Synonym) {
|
||
$synItems = @(@{ Lang = "ru"; Content = [string]$Synonym })
|
||
} else {
|
||
$synItems = @(@{ Lang = "ru"; Content = $Name })
|
||
}
|
||
if (-not $Comment -and $metaComment) { $Comment = $metaComment }
|
||
|
||
$uuid = [guid]::NewGuid().ToString()
|
||
$md = New-Object System.Text.StringBuilder
|
||
function M([string]$s) { [void]$md.Append($s); [void]$md.Append("`r`n") }
|
||
M '<?xml version="1.0" encoding="UTF-8"?>'
|
||
M '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">'
|
||
M "`t<XDTOPackage uuid=`"$uuid`">"
|
||
M "`t`t<Properties>"
|
||
M "`t`t`t<Name>$(EscText $Name)</Name>"
|
||
M "`t`t`t<Synonym>"
|
||
foreach ($s in $synItems) {
|
||
M "`t`t`t`t<v8:item>"
|
||
M "`t`t`t`t`t<v8:lang>$(EscText $s.Lang)</v8:lang>"
|
||
M "`t`t`t`t`t<v8:content>$(EscText $s.Content)</v8:content>"
|
||
M "`t`t`t`t</v8:item>"
|
||
}
|
||
M "`t`t`t</Synonym>"
|
||
if ($Comment) { M "`t`t`t<Comment>$(EscText $Comment)</Comment>" } else { M "`t`t`t<Comment/>" }
|
||
M "`t`t`t<Namespace>$(EscText $targetNs)</Namespace>"
|
||
M "`t`t</Properties>"
|
||
M "`t</XDTOPackage>"
|
||
[void]$md.Append("</MetaDataObject>")
|
||
|
||
[System.IO.File]::WriteAllText($mdFile, $md.ToString(), $encBom)
|
||
|
||
# --- Register in Configuration.xml ---
|
||
|
||
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
|
||
$xdtoRootDir = Join-Path $OutputDir "XDTOPackages"
|
||
$declaredImports = @()
|
||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "import") { foreach ($a in $c.Attrs) { if ($a.Name -eq "namespace") { $declaredImports += $a.Value } } } }
|
||
if ($declaredImports.Count -gt 0 -and (Test-Path $xdtoRootDir)) {
|
||
$knownNs = @{}
|
||
foreach ($other in (Get-ChildItem $xdtoRootDir -Directory -ErrorAction SilentlyContinue)) {
|
||
$ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin"
|
||
if (-not (Test-Path $ob)) { continue }
|
||
try {
|
||
$od = New-Object System.Xml.XmlDocument
|
||
$od.Load($ob)
|
||
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $true
|
||
} catch {}
|
||
}
|
||
foreach ($imp in $declaredImports) {
|
||
if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) {
|
||
Warn "Импорт `"$imp`" не разрешается: пакета с таким namespace в конфигурации нет. Платформа отвергнет пакет при обновлении — соберите зависимость первой"
|
||
}
|
||
}
|
||
}
|
||
|
||
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
|
||
$regResult = "no-config"
|
||
if (Test-Path $configXmlPath) {
|
||
$configDoc = New-Object System.Xml.XmlDocument
|
||
$configDoc.PreserveWhitespace = $true
|
||
$configDoc.Load($configXmlPath)
|
||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable)
|
||
$nsMgr.AddNamespace("md", $MD_NS)
|
||
$childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr)
|
||
if ($childObjects) {
|
||
$existing = $childObjects.SelectNodes("md:XDTOPackage", $nsMgr)
|
||
$already = $false
|
||
foreach ($e in $existing) { if ($e.InnerText -eq $Name) { $already = $true; break } }
|
||
if ($already) {
|
||
$regResult = "already"
|
||
} else {
|
||
$newElem = $configDoc.CreateElement("XDTOPackage", $MD_NS)
|
||
$newElem.InnerText = $Name
|
||
if ($existing.Count -gt 0) {
|
||
$lastElem = $existing[$existing.Count - 1]
|
||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
||
$childObjects.InsertAfter($newWs, $lastElem) | Out-Null
|
||
$childObjects.InsertAfter($newElem, $newWs) | Out-Null
|
||
} else {
|
||
$lastChild = $childObjects.LastChild
|
||
if ($lastChild -and $lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
||
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
||
$childObjects.InsertBefore($newElem, $lastChild) | Out-Null
|
||
} else {
|
||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||
$childObjects.AppendChild($newElem) | Out-Null
|
||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||
}
|
||
}
|
||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||
$cfgSettings.Indent = $false
|
||
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create)
|
||
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings)
|
||
$configDoc.Save($writer)
|
||
$writer.Close()
|
||
$stream.Close()
|
||
$regResult = "added"
|
||
}
|
||
}
|
||
}
|
||
|
||
# --- Report ---
|
||
|
||
$typeCount = 0
|
||
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "objectType" -or $c.Tag -eq "valueType") { $typeCount++ } }
|
||
|
||
Write-Host "✓ Пакет XDTO собран: $Name"
|
||
Write-Host " Namespace: $targetNs"
|
||
Write-Host " Типов: $typeCount"
|
||
Write-Host " Файлы: XDTOPackages/$Name.xml, XDTOPackages/$Name/Ext/Package.bin"
|
||
if ($script:warnings.Count -gt 0) {
|
||
Write-Host ""
|
||
Write-Host "Предупреждения ($($script:warnings.Count)) — конструкции XSD без точного соответствия в модели XDTO:"
|
||
foreach ($w in $script:warnings) { Write-Host " ! $w" }
|
||
Write-Host ""
|
||
}
|
||
switch ($regResult) {
|
||
"added" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> добавлен в ChildObjects" }
|
||
"already" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> уже зарегистрирован" }
|
||
"no-config" { Write-Host " Configuration.xml не найден — регистрация пропущена" }
|
||
}
|