Files

958 lines
41 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-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("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace('"', "&quot;")
}
function EscText([string]$s) {
if ($null -eq $s) { return "" }
return $s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
}
# --- 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 не найден — регистрация пропущена" }
}