mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-27 15:11:03 +03:00
Auto-build: codeassistant (powershell) from 5472e03
This commit is contained in:
@@ -0,0 +1,957 @@
|
||||
# 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 не найден — регистрация пропущена" }
|
||||
}
|
||||
@@ -0,0 +1,962 @@
|
||||
# xdto-compile v1.1 — Build a 1C XDTO package from an XML Schema (XSD) (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
|
||||
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
|
||||
# для них нет и быть не должно (выведено по корпусу)
|
||||
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",
|
||||
}
|
||||
|
||||
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
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"
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-XsdPath", "-Path", default="")
|
||||
parser.add_argument("-Xsd", default="")
|
||||
parser.add_argument("-OutputDir", required=True)
|
||||
parser.add_argument("-Name", default="")
|
||||
parser.add_argument("-Synonym", default="")
|
||||
parser.add_argument("-Comment", default="")
|
||||
parser.add_argument("-Force", action="store_true")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
def _parse_xml(source, from_string=False):
|
||||
"""Разбор с узким отступлением для не-URI пространств имён.
|
||||
|
||||
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
|
||||
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
|
||||
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
|
||||
иначе по-настоящему битый XML перестал бы отличаться от корректного.
|
||||
"""
|
||||
try:
|
||||
return (etree.fromstring(source) if from_string else etree.parse(source))
|
||||
except etree.XMLSyntaxError as e:
|
||||
if "is not a valid URI" not in str(e):
|
||||
raise
|
||||
p = etree.XMLParser(recover=True)
|
||||
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
|
||||
|
||||
# ── support guard (Ext/ParentConfigurations.bin) ─────────────
|
||||
# См. docs/1c-support-state-spec.md. Блокирует правку объектов поставщика
|
||||
# «на замке». Триггер — наличие bin; реакция из .v8-project.json
|
||||
# editingAllowedCheck (deny|warn|off, по умолчанию deny).
|
||||
|
||||
|
||||
def find_v8_project(start_dir):
|
||||
d = os.path.abspath(start_dir)
|
||||
for _ in range(20):
|
||||
pj = os.path.join(d, ".v8-project.json")
|
||||
if os.path.exists(pj):
|
||||
return pj
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return None
|
||||
|
||||
|
||||
def get_edit_mode(cfg_dir):
|
||||
try:
|
||||
pj = find_v8_project(cfg_dir)
|
||||
if pj:
|
||||
with open(pj, encoding="utf-8-sig") as f:
|
||||
cfg = json.load(f)
|
||||
return str(cfg.get("editingAllowedCheck") or "deny")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return "deny"
|
||||
|
||||
|
||||
def is_external_object_root(xml_path):
|
||||
try:
|
||||
root = _parse_xml(xml_path).getroot()
|
||||
for el in root:
|
||||
if isinstance(el.tag, str):
|
||||
return etree.QName(el).localname in ("ExternalDataProcessor", "ExternalReport")
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def assert_edit_allowed(target_path):
|
||||
d = os.path.abspath(target_path)
|
||||
for _ in range(20):
|
||||
# Автономный объект (внешняя обработка/отчёт) — граница климба
|
||||
try:
|
||||
for f in os.listdir(d):
|
||||
if f.endswith(".xml") and is_external_object_root(os.path.join(d, f)):
|
||||
return
|
||||
except OSError:
|
||||
pass
|
||||
cfg_xml = os.path.join(d, "Configuration.xml")
|
||||
support_bin = os.path.join(d, "Ext", "ParentConfigurations.bin")
|
||||
if os.path.exists(cfg_xml):
|
||||
if os.path.exists(support_bin):
|
||||
mode = get_edit_mode(d)
|
||||
if mode == "off":
|
||||
return
|
||||
msg = ("Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). "
|
||||
"Правка может быть запрещена.")
|
||||
if mode == "warn":
|
||||
print(f"WARNING: {msg}", file=sys.stderr)
|
||||
return
|
||||
print(f"{msg} Снимите с поддержки (/support-edit) или задайте "
|
||||
"editingAllowedCheck в .v8-project.json.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
return
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
|
||||
|
||||
# ── load the schema ──────────────────────────────────────────
|
||||
|
||||
if args.Xsd:
|
||||
xsd_bytes = args.Xsd.encode("utf-8")
|
||||
default_name = "Package"
|
||||
elif args.XsdPath:
|
||||
if not os.path.isfile(args.XsdPath):
|
||||
print(f"Файл XSD не найден: {args.XsdPath}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
with open(args.XsdPath, "rb") as f:
|
||||
xsd_bytes = f.read()
|
||||
default_name = os.path.splitext(os.path.basename(args.XsdPath))[0]
|
||||
else:
|
||||
print("Укажите -XsdPath или -Xsd", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
schema = _parse_xml(xsd_bytes, from_string=True)
|
||||
except Exception as e: # noqa: BLE001
|
||||
print(f"Не удалось разобрать XSD: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def local(el):
|
||||
return etree.QName(el).localname
|
||||
|
||||
|
||||
if local(schema) != "schema" or etree.QName(schema).namespace != XS_NS:
|
||||
print(f"Ожидался корневой <xs:schema> в пространстве имён {XS_NS}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
target_ns = schema.get("targetNamespace") or ""
|
||||
|
||||
# ── emit-tree primitives ─────────────────────────────────────
|
||||
|
||||
|
||||
class Node:
|
||||
__slots__ = ("tag", "attrs", "children", "text", "prefix", "declare_ns")
|
||||
|
||||
def __init__(self, tag):
|
||||
self.tag = tag
|
||||
self.attrs = [] # список dict: name, value | (ns, local) | list
|
||||
self.children = []
|
||||
self.text = None
|
||||
self.prefix = None
|
||||
self.declare_ns = None
|
||||
|
||||
|
||||
def add_attr(node, name, value):
|
||||
if value is None:
|
||||
return
|
||||
node.attrs.append({"name": name, "value": str(value)})
|
||||
|
||||
|
||||
def add_qattr(node, name, ns, loc):
|
||||
if loc is None:
|
||||
return
|
||||
node.attrs.append({"name": name, "ns": ns, "local": loc})
|
||||
|
||||
|
||||
def add_qlist_attr(node, name, pairs, clark):
|
||||
if not pairs:
|
||||
return
|
||||
node.attrs.append({"name": name, "list": pairs, "clark": clark})
|
||||
|
||||
|
||||
# Канонический порядок атрибутов — топологическая сортировка по корпусу 8.3.24
|
||||
# (acc + erp, 760 пакетов), см. 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"],
|
||||
}
|
||||
|
||||
|
||||
def sort_attrs(node):
|
||||
order = ATTR_ORDER.get(node.tag)
|
||||
if not order:
|
||||
return node.attrs
|
||||
res = []
|
||||
for n in order:
|
||||
res.extend(a for a in node.attrs if a["name"] == n)
|
||||
res.extend(a for a in node.attrs if a["name"] not in order)
|
||||
return res
|
||||
|
||||
|
||||
def esc(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """)
|
||||
|
||||
|
||||
def esc_text(s):
|
||||
if s is None:
|
||||
return ""
|
||||
return str(s).replace("&", "&").replace("<", "<").replace(">", ">")
|
||||
|
||||
|
||||
# ── serializer with the dNpM prefix scheme ───────────────────
|
||||
|
||||
out = []
|
||||
|
||||
|
||||
def serialize_node(node, depth, inherited):
|
||||
indent = "\t" * (depth - 1)
|
||||
attrs_sorted = sort_attrs(node)
|
||||
|
||||
# Объявляем здесь только те ns, которых ещё нет в области видимости:
|
||||
# сериализатор платформы объявляет префикс на первом нуждающемся узле,
|
||||
# а потомки переиспользуют — отсюда d2p1 у property внутри objectType.
|
||||
local_ns = []
|
||||
|
||||
def need_prefix(ns):
|
||||
if not ns or ns in (XS_NS, XSI_NS):
|
||||
return
|
||||
if ns in inherited:
|
||||
return
|
||||
if ns not in local_ns:
|
||||
local_ns.append(ns)
|
||||
|
||||
for a in attrs_sorted:
|
||||
if "list" in a:
|
||||
# Нотация Кларка несёт ns в значении и префикса не требует
|
||||
if not a["clark"]:
|
||||
for p in a["list"]:
|
||||
need_prefix(p[0])
|
||||
elif a.get("ns"):
|
||||
need_prefix(a["ns"])
|
||||
|
||||
has_qualified = any(a["name"] == "qualified" for a in attrs_sorted)
|
||||
if has_qualified:
|
||||
need_prefix(XDTO_NS)
|
||||
if node.declare_ns:
|
||||
need_prefix(node.declare_ns)
|
||||
|
||||
prefix_of = dict(inherited)
|
||||
ns_decls = ""
|
||||
for i, ns in enumerate(local_ns):
|
||||
# Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет
|
||||
px = node.prefix if (i == 0 and node.prefix) else f"d{depth}p{i + 1}"
|
||||
prefix_of[ns] = px
|
||||
ns_decls += f' xmlns:{px}="{esc(ns)}"'
|
||||
|
||||
def qval(ns, loc):
|
||||
if not ns:
|
||||
return loc
|
||||
if ns == XS_NS:
|
||||
return f"xs:{loc}"
|
||||
if ns == XSI_NS:
|
||||
return f"xsi:{loc}"
|
||||
return f"{prefix_of[ns]}:{loc}"
|
||||
|
||||
attr_text = ""
|
||||
for a in attrs_sorted:
|
||||
if "list" in a:
|
||||
vals = []
|
||||
for ns, loc in a["list"]:
|
||||
vals.append((f"{{{ns}}}{loc}" if ns else loc) if a["clark"] else qval(ns, loc))
|
||||
attr_text += f' {a["name"]}="{esc(" ".join(vals))}"'
|
||||
elif a.get("ns") or a.get("local"):
|
||||
attr_text += f' {a["name"]}="{esc(qval(a.get("ns"), a["local"]))}"'
|
||||
elif a["name"] == "qualified":
|
||||
attr_text += f' {prefix_of[XDTO_NS]}:qualified="{esc(a["value"])}"'
|
||||
else:
|
||||
attr_text += f' {a["name"]}="{esc(a["value"])}"'
|
||||
|
||||
tag_name = f"{prefix_of[XDTO_NS]}:{node.tag}" if has_qualified else node.tag
|
||||
|
||||
has_children = bool(node.children)
|
||||
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>
|
||||
has_text = node.text is not None and node.text != ""
|
||||
|
||||
if not has_children and not has_text:
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}/>\r\n")
|
||||
return
|
||||
if has_text and not has_children:
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>{esc_text(node.text)}</{tag_name}>\r\n")
|
||||
return
|
||||
out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>\r\n")
|
||||
for c in node.children:
|
||||
serialize_node(c, depth + 1, prefix_of)
|
||||
out.append(f"{indent}</{tag_name}>\r\n")
|
||||
|
||||
|
||||
# ── XSD reading helpers ──────────────────────────────────────
|
||||
|
||||
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
|
||||
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
|
||||
warnings_list = []
|
||||
|
||||
|
||||
def warn(msg):
|
||||
if msg not in warnings_list:
|
||||
warnings_list.append(msg)
|
||||
|
||||
|
||||
GROUPS = {}
|
||||
ATTR_GROUPS = {}
|
||||
|
||||
|
||||
def MA(el, name):
|
||||
# xdto: mirror attribute — литеральное значение для Package.bin.
|
||||
# Ищем по namespace, а не по строке префикса.
|
||||
return el.get(f"{{{XDTO_NS}}}{name}")
|
||||
|
||||
|
||||
def xchildren(el, name):
|
||||
return [c for c in el if isinstance(c.tag, str)
|
||||
and etree.QName(c).namespace == XS_NS and local(c) == name]
|
||||
|
||||
|
||||
def xfirst(el, name):
|
||||
r = xchildren(el, name)
|
||||
return r[0] if r else None
|
||||
|
||||
|
||||
def split_qname(el, qname):
|
||||
if not qname:
|
||||
return None
|
||||
parts = qname.split(":")
|
||||
if len(parts) == 2:
|
||||
ns = el.nsmap.get(parts[0])
|
||||
loc = parts[1]
|
||||
else:
|
||||
# Прощающий ввод: голое имя типа — тип целевого пространства имён
|
||||
ns = el.nsmap.get(None) or target_ns
|
||||
loc = parts[0]
|
||||
return (ns, loc)
|
||||
|
||||
|
||||
def split_qname_list(el, lst):
|
||||
if not lst:
|
||||
return []
|
||||
return [split_qname(el, q) for q in lst.split() if q]
|
||||
|
||||
|
||||
FACETS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
|
||||
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"]
|
||||
|
||||
|
||||
# ── simpleType -> valueType / typeDef(ValueType) ─────────────
|
||||
|
||||
def fill_simple_type(node, st):
|
||||
restriction = xfirst(st, "restriction")
|
||||
lst = xfirst(st, "list")
|
||||
union = xfirst(st, "union")
|
||||
|
||||
if lst is not None:
|
||||
it = split_qname(lst, lst.get("itemType"))
|
||||
mv = MA(lst, "variety")
|
||||
add_attr(node, "variety", mv if mv is not None else "List")
|
||||
if it:
|
||||
add_qattr(node, "itemType", it[0], it[1])
|
||||
return
|
||||
if union is not None:
|
||||
mv = MA(union, "variety")
|
||||
set_attr_value(node, "variety", mv if mv is not None else "Union")
|
||||
members = split_qname_list(union, union.get("memberTypes"))
|
||||
# По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса
|
||||
use_clark = MA(union, "memberTypesForm") != "prefixed"
|
||||
if members:
|
||||
add_qlist_attr(node, "memberTypes", members, use_clark)
|
||||
node.declare_ns = MA(union, "declareNs")
|
||||
for anon in xchildren(union, "simpleType"):
|
||||
# typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса)
|
||||
td = Node("typeDef")
|
||||
fill_simple_type(td, anon)
|
||||
node.children.append(td)
|
||||
return
|
||||
if restriction is not None:
|
||||
b = split_qname(restriction, restriction.get("base"))
|
||||
if b:
|
||||
add_qattr(node, "base", b[0], b[1])
|
||||
mv = MA(restriction, "variety")
|
||||
if mv is not None:
|
||||
add_attr(node, "variety", mv)
|
||||
# Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type
|
||||
anon_base = xfirst(restriction, "simpleType")
|
||||
if anon_base is not None:
|
||||
td = Node("typeDef")
|
||||
fill_simple_type(td, anon_base)
|
||||
node.children.append(td)
|
||||
for f in FACETS:
|
||||
for fe in xchildren(restriction, f):
|
||||
add_attr(node, f, fe.get("value"))
|
||||
for pe in xchildren(restriction, "pattern"):
|
||||
pn = Node("pattern")
|
||||
pn.text = pe.get("value")
|
||||
node.children.append(pn)
|
||||
for en in xchildren(restriction, "enumeration"):
|
||||
enode = Node("enumeration")
|
||||
mt = MA(en, "type")
|
||||
if mt is not None:
|
||||
q = split_qname(en, mt)
|
||||
add_qattr(enode, "xsi:type", q[0], q[1])
|
||||
enode.text = en.get("value")
|
||||
node.children.append(enode)
|
||||
|
||||
|
||||
def set_attr_value(node, name, value):
|
||||
for a in node.attrs:
|
||||
if a["name"] == name:
|
||||
a["value"] = value
|
||||
return
|
||||
add_attr(node, name, value)
|
||||
|
||||
|
||||
def get_prop_key(p):
|
||||
for a in p.attrs:
|
||||
if a["name"] == "name":
|
||||
return a.get("value")
|
||||
for a in p.attrs:
|
||||
if a["name"] == "ref":
|
||||
return "@" + a["local"]
|
||||
return None
|
||||
|
||||
|
||||
def reorder_properties(node, names):
|
||||
props = [c for c in node.children if c.tag == "property"]
|
||||
if len(props) < 2:
|
||||
return
|
||||
by_key = {}
|
||||
for p in props:
|
||||
k = get_prop_key(p)
|
||||
if k is not None and k not in by_key:
|
||||
by_key[k] = p
|
||||
ordered = []
|
||||
for n in names:
|
||||
if n in by_key:
|
||||
ordered.append(by_key.pop(n))
|
||||
for p in props:
|
||||
if p not in ordered:
|
||||
ordered.append(p)
|
||||
others = [c for c in node.children if c.tag != "property"]
|
||||
node.children = ordered + others
|
||||
|
||||
|
||||
# ── element / attribute -> property ──────────────────────────
|
||||
|
||||
def build_property(el, is_attribute):
|
||||
p = Node("property")
|
||||
|
||||
xsd_name = el.get("name")
|
||||
mirror_name = MA(el, "name")
|
||||
if mirror_name is not None:
|
||||
add_attr(p, "name", mirror_name)
|
||||
local_name = xsd_name
|
||||
else:
|
||||
add_attr(p, "name", xsd_name)
|
||||
local_name = None
|
||||
|
||||
ref_q = split_qname(el, el.get("ref"))
|
||||
if ref_q:
|
||||
add_qattr(p, "ref", ref_q[0], ref_q[1])
|
||||
|
||||
type_q = split_qname(el, el.get("type"))
|
||||
if type_q:
|
||||
add_qattr(p, "type", type_q[0], type_q[1])
|
||||
|
||||
if is_attribute:
|
||||
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", el.get("minOccurs"))
|
||||
max_occ = el.get("maxOccurs")
|
||||
if max_occ is not None:
|
||||
add_attr(p, "upperBound", "-1" if max_occ == "unbounded" else max_occ)
|
||||
add_attr(p, "nillable", el.get("nillable"))
|
||||
|
||||
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
|
||||
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
|
||||
m_fixed = MA(el, "fixed")
|
||||
if m_fixed is not None:
|
||||
add_attr(p, "fixed", m_fixed)
|
||||
add_attr(p, "default", el.get("default"))
|
||||
if m_fixed == "true" and el.get("default") is None:
|
||||
warn('Свойство "' + str(el.get("name")) + '": xdto:fixed="true" без default — '
|
||||
"платформа отвергнет пакет («Отсутствует фиксированное значение»). "
|
||||
'Значение задаётся атрибутом default, либо пишите XSD-форму fixed="значение"')
|
||||
elif el.get("fixed") is not None:
|
||||
add_attr(p, "fixed", "true")
|
||||
add_attr(p, "default", el.get("fixed"))
|
||||
else:
|
||||
add_attr(p, "default", el.get("default"))
|
||||
|
||||
if is_attribute:
|
||||
add_attr(p, "form", "Attribute")
|
||||
else:
|
||||
mf = MA(el, "form")
|
||||
if mf is not None:
|
||||
add_attr(p, "form", mf)
|
||||
add_attr(p, "localName", local_name)
|
||||
add_attr(p, "qualified", MA(el, "qualified"))
|
||||
p.prefix = MA(el, "prefix")
|
||||
|
||||
anon_simple = xfirst(el, "simpleType")
|
||||
anon_complex = xfirst(el, "complexType")
|
||||
if anon_simple is not None:
|
||||
td = Node("typeDef")
|
||||
add_attr(td, "xsi:type", "ValueType")
|
||||
fill_simple_type(td, anon_simple)
|
||||
p.children.append(td)
|
||||
elif anon_complex is not None:
|
||||
td = Node("typeDef")
|
||||
add_attr(td, "xsi:type", "ObjectType")
|
||||
fill_complex_type(td, anon_complex)
|
||||
p.children.append(td)
|
||||
return p
|
||||
|
||||
|
||||
# ── complexType -> objectType / typeDef(ObjectType) ──────────
|
||||
|
||||
def resolve_group(el, kind):
|
||||
ref = el.get("ref")
|
||||
if not ref:
|
||||
return None
|
||||
q = split_qname(el, ref)
|
||||
if not q:
|
||||
return None
|
||||
m = GROUPS if kind == "group" else ATTR_GROUPS
|
||||
return m.get(q[1])
|
||||
|
||||
|
||||
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
|
||||
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
|
||||
def collect_particle(particle, elem_list, open_flag, type_name, depth, optionalize=False):
|
||||
if depth > 20:
|
||||
return
|
||||
for c in particle:
|
||||
if not isinstance(c.tag, str) or etree.QName(c).namespace != XS_NS:
|
||||
continue
|
||||
ln = local(c)
|
||||
if ln == "element":
|
||||
prop = build_property(c, False)
|
||||
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
|
||||
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
|
||||
if optionalize:
|
||||
set_attr_value(prop, "lowerBound", "0")
|
||||
elem_list.append(prop)
|
||||
elif ln == "any":
|
||||
open_flag[0] = True
|
||||
elif ln == "sequence":
|
||||
warn(type_name + " : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
elif ln == "choice":
|
||||
branches = [b.get("name") for b in c
|
||||
if isinstance(b.tag, str) and etree.QName(b).namespace == XS_NS and b.get("name")]
|
||||
lst = (" (" + ", ".join(branches) + ")") if branches else ""
|
||||
warn(type_name + " : вложенная xs:choice уплощена — ветки" + lst + " сделаны необязательными. "
|
||||
"Выбор одного из вариантов не сохранён: модель не запретит заполнить "
|
||||
"сразу несколько или ни одного")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, True)
|
||||
elif ln == "all":
|
||||
warn(type_name + " : xs:all трактуется как последовательность")
|
||||
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
elif ln == "group":
|
||||
g = resolve_group(c, "group")
|
||||
if g is not None:
|
||||
for gc in g:
|
||||
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
|
||||
and local(gc) in ("sequence", "choice", "all"):
|
||||
collect_particle(gc, elem_list, open_flag, type_name, depth + 1, optionalize)
|
||||
else:
|
||||
warn(type_name + " : не найдена группа " + str(c.get("ref")) + " — её свойства в пакет не попали")
|
||||
if ln in ("sequence", "choice", "all", "group"):
|
||||
if c.get("maxOccurs") is not None or c.get("minOccurs") is not None:
|
||||
warn(type_name + " : кратность на вложенной частице (<xs:" + ln +
|
||||
" minOccurs/maxOccurs>) не выражается в модели XDTO")
|
||||
|
||||
|
||||
def set_type_flags(node, ct, is_open, choice):
|
||||
m_open = MA(ct, "open")
|
||||
if m_open is not None:
|
||||
add_attr(node, "open", m_open)
|
||||
elif is_open:
|
||||
add_attr(node, "open", "true")
|
||||
|
||||
m_ordered = MA(ct, "ordered")
|
||||
if m_ordered is not None:
|
||||
add_attr(node, "ordered", m_ordered)
|
||||
elif choice is not None:
|
||||
add_attr(node, "ordered", "false")
|
||||
|
||||
m_seq = MA(ct, "sequenced")
|
||||
if m_seq is not None:
|
||||
add_attr(node, "sequenced", m_seq)
|
||||
|
||||
m_abstract = MA(ct, "abstract")
|
||||
if m_abstract is not None:
|
||||
add_attr(node, "abstract", m_abstract)
|
||||
elif ct.get("abstract") == "true":
|
||||
add_attr(node, "abstract", "true")
|
||||
|
||||
m_mixed = MA(ct, "mixed")
|
||||
if m_mixed is not None:
|
||||
add_attr(node, "mixed", m_mixed)
|
||||
elif ct.get("mixed") == "true":
|
||||
add_attr(node, "mixed", "true")
|
||||
|
||||
|
||||
def fill_complex_type(node, ct):
|
||||
body = ct
|
||||
content = xfirst(ct, "complexContent")
|
||||
if content is not None:
|
||||
ext = xfirst(content, "extension")
|
||||
if ext is not None:
|
||||
b = split_qname(ext, ext.get("base"))
|
||||
if b:
|
||||
add_qattr(node, "base", b[0], b[1])
|
||||
body = ext
|
||||
|
||||
# xs:simpleContent -> свойство "Text", хранящее значение самого элемента
|
||||
simple = xfirst(ct, "simpleContent")
|
||||
if simple is not None:
|
||||
ext = xfirst(simple, "extension")
|
||||
if ext is not None:
|
||||
for a in xchildren(ext, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
tp = Node("property")
|
||||
t_name = MA(ext, "textName")
|
||||
add_attr(tp, "name", t_name if t_name is not None else "__content")
|
||||
b = split_qname(ext, ext.get("base"))
|
||||
if b:
|
||||
add_qattr(tp, "type", b[0], b[1])
|
||||
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")
|
||||
node.children.append(tp)
|
||||
# xs:simpleContent не отменяет флаги самого xs:complexType
|
||||
set_type_flags(node, ct, False, None)
|
||||
return
|
||||
|
||||
seq = xfirst(body, "sequence")
|
||||
cho = xfirst(body, "choice")
|
||||
all_ = xfirst(body, "all")
|
||||
grp = xfirst(body, "group")
|
||||
particle = seq if seq is not None else (cho if cho is not None else (all_ if all_ is not None else grp))
|
||||
open_flag = [False]
|
||||
|
||||
# Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса)
|
||||
elem_props = []
|
||||
type_name = ct.get("name") or "(анонимный тип)"
|
||||
if particle is not None:
|
||||
if all_ is not None:
|
||||
warn(type_name + " : xs:all трактуется как последовательность")
|
||||
if grp is not None and seq is None and cho is None and all_ is None:
|
||||
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
|
||||
g = resolve_group(grp, "group")
|
||||
if g is not None:
|
||||
for gc in g:
|
||||
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
|
||||
and local(gc) in ("sequence", "choice", "all"):
|
||||
collect_particle(gc, elem_props, open_flag, type_name, 1)
|
||||
else:
|
||||
warn(type_name + " : не найдена группа " + str(grp.get("ref")) + " — её свойства в пакет не попали")
|
||||
else:
|
||||
collect_particle(particle, elem_props, open_flag, type_name, 0)
|
||||
is_open = open_flag[0]
|
||||
for a in xchildren(body, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
# xs:attributeGroup раскрываем по ссылке
|
||||
for ag in xchildren(body, "attributeGroup"):
|
||||
g = resolve_group(ag, "attributeGroup")
|
||||
if g is not None:
|
||||
for a in xchildren(g, "attribute"):
|
||||
node.children.append(build_property(a, True))
|
||||
else:
|
||||
warn("Не найдена группа атрибутов " + str(ag.get("ref")) + " — её атрибуты в пакет не попали")
|
||||
node.children.extend(elem_props)
|
||||
if xchildren(body, "anyAttribute"):
|
||||
is_open = True
|
||||
|
||||
m_order = MA(ct, "order")
|
||||
if m_order is not None:
|
||||
reorder_properties(node, m_order.split("|"))
|
||||
|
||||
set_type_flags(node, ct, is_open, cho)
|
||||
|
||||
|
||||
# ── build the package tree ───────────────────────────────────
|
||||
|
||||
pkg_node = Node("package")
|
||||
add_attr(pkg_node, "targetNamespace", target_ns)
|
||||
|
||||
efq_mirror = MA(schema, "elementFormQualified")
|
||||
afq_mirror = MA(schema, "attributeFormQualified")
|
||||
efd = schema.get("elementFormDefault")
|
||||
afd = schema.get("attributeFormDefault")
|
||||
if efq_mirror is not None:
|
||||
add_attr(pkg_node, "elementFormQualified", efq_mirror)
|
||||
elif efd is not None:
|
||||
add_attr(pkg_node, "elementFormQualified", "true" if efd == "qualified" else "false")
|
||||
if afq_mirror is not None:
|
||||
add_attr(pkg_node, "attributeFormQualified", afq_mirror)
|
||||
elif afd is not None:
|
||||
add_attr(pkg_node, "attributeFormQualified", "true" if afd == "qualified" else "false")
|
||||
|
||||
meta_name = meta_comment = None
|
||||
meta_synonym = []
|
||||
ann = xfirst(schema, "annotation")
|
||||
if ann is not None:
|
||||
appinfo = xfirst(ann, "appinfo")
|
||||
if appinfo is not None:
|
||||
for pk in appinfo:
|
||||
if not isinstance(pk.tag, str) or etree.QName(pk).namespace != XDTO_NS:
|
||||
continue
|
||||
for f in pk:
|
||||
if not isinstance(f.tag, str):
|
||||
continue
|
||||
ln = local(f)
|
||||
if ln == "name":
|
||||
meta_name = f.text or ""
|
||||
elif ln == "comment":
|
||||
meta_comment = f.text or ""
|
||||
elif ln == "synonym":
|
||||
meta_synonym.append({"Lang": f.get("lang") or "", "Content": f.text or ""})
|
||||
|
||||
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
|
||||
for node in schema:
|
||||
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
|
||||
continue
|
||||
nm = node.get("name")
|
||||
if local(node) == "group" and nm:
|
||||
GROUPS[nm] = node
|
||||
if local(node) == "attributeGroup" and nm:
|
||||
ATTR_GROUPS[nm] = node
|
||||
|
||||
# Конструкции XSD, которым в модели XDTO нет соответствия
|
||||
for sg in schema.iter():
|
||||
if isinstance(sg.tag, str) and local(sg) == "element" and sg.get("substitutionGroup"):
|
||||
warn("Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление "
|
||||
+ str(sg.get("name")) + " сохранено как обычное")
|
||||
for idc in ("key", "keyref", "unique"):
|
||||
if any(isinstance(e.tag, str) and local(e) == idc for e in schema.iter()):
|
||||
warn("Ограничения целостности (xs:" + idc + ") в модели XDTO не хранятся — отброшены")
|
||||
if any(isinstance(e.tag, str) and local(e) == "redefine" for e in schema.iter()):
|
||||
warn("xs:redefine не поддерживается — переопределения проигнорированы")
|
||||
if any(isinstance(e.tag, str) and local(e) == "include" for e in schema.iter()):
|
||||
warn("xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. "
|
||||
"Соберите включаемую схему отдельным пакетом и добавьте <xs:import>")
|
||||
|
||||
for node in schema:
|
||||
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
|
||||
continue
|
||||
ln = local(node)
|
||||
if ln == "import":
|
||||
n = Node("import")
|
||||
add_attr(n, "namespace", node.get("namespace"))
|
||||
pkg_node.children.append(n)
|
||||
elif ln in ("annotation", "include", "group", "attributeGroup", "notation"):
|
||||
continue
|
||||
elif ln == "element":
|
||||
pkg_node.children.append(build_property(node, False))
|
||||
elif ln == "attribute":
|
||||
pkg_node.children.append(build_property(node, True))
|
||||
elif ln == "simpleType":
|
||||
n = Node("valueType")
|
||||
add_attr(n, "name", node.get("name"))
|
||||
fill_simple_type(n, node)
|
||||
pkg_node.children.append(n)
|
||||
elif ln == "complexType":
|
||||
n = Node("objectType")
|
||||
add_attr(n, "name", node.get("name"))
|
||||
fill_complex_type(n, node)
|
||||
pkg_node.children.append(n)
|
||||
|
||||
# ── serialize Package.bin ────────────────────────────────────
|
||||
|
||||
# Модель XDTO требует строгой последовательности элементов верхнего уровня:
|
||||
# import → property → valueType → objectType. Порядок объявлений в XSD произвольный,
|
||||
# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования
|
||||
# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют.
|
||||
TOP_ORDER = ["import", "property", "valueType", "objectType"]
|
||||
pkg_node.children = (
|
||||
[c for t in TOP_ORDER for c in pkg_node.children if c.tag == t]
|
||||
+ [c for c in pkg_node.children if c.tag not in TOP_ORDER]
|
||||
)
|
||||
|
||||
root_attr_text = "".join(f' {a["name"]}="{esc(a["value"])}"' for a in sort_attrs(pkg_node))
|
||||
out.append(f'<package xmlns="{XDTO_NS}" xmlns:xs="{XS_NS}" xmlns:xsi="{XSI_NS}"{root_attr_text}>\r\n')
|
||||
for c in pkg_node.children:
|
||||
serialize_node(c, 2, {})
|
||||
out.append("</package>")
|
||||
|
||||
bin_text = "".join(out)
|
||||
|
||||
# ── resolve the package name ─────────────────────────────────
|
||||
|
||||
name = args.Name or meta_name or default_name
|
||||
name = re.sub(r"[^\wЀ-ӿ]", "_", name, flags=re.UNICODE)
|
||||
if re.match(r"^\d", name):
|
||||
name = "_" + name
|
||||
|
||||
assert_edit_allowed(args.OutputDir)
|
||||
|
||||
pkg_root = os.path.join(args.OutputDir, "XDTOPackages")
|
||||
pkg_dir = os.path.join(pkg_root, name)
|
||||
ext_dir = os.path.join(pkg_dir, "Ext")
|
||||
md_file = os.path.join(pkg_root, name + ".xml")
|
||||
bin_file = os.path.join(ext_dir, "Package.bin")
|
||||
|
||||
if os.path.exists(bin_file) and not args.Force:
|
||||
print(f"Пакет уже существует: {bin_file}. Используйте -Force для перезаписи.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
os.makedirs(ext_dir, exist_ok=True)
|
||||
|
||||
with open(bin_file, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + bin_text.encode("utf-8"))
|
||||
|
||||
# ── metadata object file ─────────────────────────────────────
|
||||
|
||||
if not args.Synonym and meta_synonym:
|
||||
syn_items = meta_synonym
|
||||
elif args.Synonym:
|
||||
syn_items = [{"Lang": "ru", "Content": args.Synonym}]
|
||||
else:
|
||||
syn_items = [{"Lang": "ru", "Content": name}]
|
||||
comment = args.Comment or meta_comment or ""
|
||||
|
||||
md_lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
'<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">',
|
||||
f'\t<XDTOPackage uuid="{uuid.uuid4()}">',
|
||||
"\t\t<Properties>",
|
||||
f"\t\t\t<Name>{esc_text(name)}</Name>",
|
||||
"\t\t\t<Synonym>",
|
||||
]
|
||||
for s in syn_items:
|
||||
md_lines += [
|
||||
"\t\t\t\t<v8:item>",
|
||||
f'\t\t\t\t\t<v8:lang>{esc_text(s["Lang"])}</v8:lang>',
|
||||
f'\t\t\t\t\t<v8:content>{esc_text(s["Content"])}</v8:content>',
|
||||
"\t\t\t\t</v8:item>",
|
||||
]
|
||||
md_lines.append("\t\t\t</Synonym>")
|
||||
md_lines.append(f"\t\t\t<Comment>{esc_text(comment)}</Comment>" if comment else "\t\t\t<Comment/>")
|
||||
md_lines.append(f"\t\t\t<Namespace>{esc_text(target_ns)}</Namespace>")
|
||||
md_lines += ["\t\t</Properties>", "\t</XDTOPackage>", "</MetaDataObject>"]
|
||||
|
||||
with open(md_file, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf" + "\r\n".join(md_lines).encode("utf-8"))
|
||||
|
||||
# ── register in Configuration.xml ────────────────────────────
|
||||
|
||||
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
|
||||
xdto_root_dir = os.path.join(args.OutputDir, "XDTOPackages")
|
||||
declared_imports = [a["value"] for c in pkg_node.children if c.tag == "import"
|
||||
for a in c.attrs if a["name"] == "namespace"]
|
||||
if declared_imports and os.path.isdir(xdto_root_dir):
|
||||
known_ns = set()
|
||||
for other in sorted(os.listdir(xdto_root_dir)):
|
||||
ob = os.path.join(xdto_root_dir, other, "Ext", "Package.bin")
|
||||
if not os.path.exists(ob):
|
||||
continue
|
||||
try:
|
||||
known_ns.add(_parse_xml(ob).getroot().get("targetNamespace"))
|
||||
except Exception: # noqa: BLE001
|
||||
pass
|
||||
for imp in declared_imports:
|
||||
if imp not in known_ns and imp not in PLATFORM_NS:
|
||||
warn(f'Импорт "{imp}" не разрешается: пакета с таким namespace в конфигурации нет. '
|
||||
"Платформа отвергнет пакет при обновлении — соберите зависимость первой")
|
||||
|
||||
config_xml = os.path.join(args.OutputDir, "Configuration.xml")
|
||||
reg_result = "no-config"
|
||||
if os.path.exists(config_xml):
|
||||
with open(config_xml, "rb") as f:
|
||||
raw = f.read()
|
||||
had_bom = raw.startswith(b"\xef\xbb\xbf")
|
||||
cfg_doc = _parse_xml(config_xml)
|
||||
child_objects = cfg_doc.find(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects")
|
||||
if child_objects is not None:
|
||||
existing = child_objects.findall(f"{{{MD_NS}}}XDTOPackage")
|
||||
if any((e.text or "") == name for e in existing):
|
||||
reg_result = "already"
|
||||
else:
|
||||
new_elem = etree.SubElement(child_objects, f"{{{MD_NS}}}XDTOPackage")
|
||||
new_elem.text = name
|
||||
if existing:
|
||||
last = existing[-1]
|
||||
new_elem.tail = last.tail
|
||||
child_objects.remove(new_elem)
|
||||
last.addnext(new_elem)
|
||||
else:
|
||||
new_elem.tail = child_objects.text
|
||||
data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8")
|
||||
if had_bom:
|
||||
data = b"\xef\xbb\xbf" + data
|
||||
with open(config_xml, "wb") as f:
|
||||
f.write(data)
|
||||
reg_result = "added"
|
||||
|
||||
type_count = sum(1 for c in pkg_node.children if c.tag in ("objectType", "valueType"))
|
||||
print(f"✓ Пакет XDTO собран: {name}")
|
||||
print(f" Namespace: {target_ns}")
|
||||
print(f" Типов: {type_count}")
|
||||
print(f" Файлы: XDTOPackages/{name}.xml, XDTOPackages/{name}/Ext/Package.bin")
|
||||
if warnings_list:
|
||||
print("")
|
||||
print("Предупреждения (" + str(len(warnings_list)) +
|
||||
") — конструкции XSD без точного соответствия в модели XDTO:")
|
||||
for w in warnings_list:
|
||||
print(" ! " + w)
|
||||
print("")
|
||||
if reg_result == "added":
|
||||
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> добавлен в ChildObjects")
|
||||
elif reg_result == "already":
|
||||
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> уже зарегистрирован")
|
||||
else:
|
||||
print(" Configuration.xml не найден — регистрация пропущена")
|
||||
Reference in New Issue
Block a user