diff --git a/.claude/skills/xdto-compile/SKILL.md b/.claude/skills/xdto-compile/SKILL.md
new file mode 100644
index 00000000..2811f1d8
--- /dev/null
+++ b/.claude/skills/xdto-compile/SKILL.md
@@ -0,0 +1,93 @@
+---
+name: xdto-compile
+description: Создание пакета XDTO 1С из XML-схемы (XSD). Используй когда нужно добавить пакет XDTO в конфигурацию — для обмена, интеграции, веб-сервиса, разбора внешнего XML-формата
+argument-hint: -XsdPath <файл.xsd> -OutputDir <каталог-конфигурации> [-Name <имя>] [-Synonym <синоним>] [-Comment <текст>] [-Force]
+allowed-tools:
+ - Bash
+ - Read
+ - Glob
+---
+
+# /xdto-compile — Создание пакета XDTO из XML-схемы
+
+Собирает пакет XDTO по обычной XML-схеме: `XDTOPackages/<Имя>.xml`,
+`XDTOPackages/<Имя>/Ext/Package.bin` и регистрацию в `Configuration.xml`.
+
+Отдельного DSL нет — входной формат это XSD. Вся механика перевода (локальные объявления
+префиксов `dNpM` на каждой ссылке, инвертированная кратность `lowerBound`/`upperBound`,
+фасеты атрибутами вместо дочерних элементов) делается навыком.
+
+## Параметры
+
+| Параметр | Описание |
+|----------|----------|
+| `XsdPath` | Путь к файлу XML-схемы |
+| `Xsd` | Схема строкой, вместо `-XsdPath` |
+| `OutputDir` | Каталог конфигурации (где лежит `Configuration.xml`) |
+| `Name` | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD, санированное под идентификатор 1С |
+| `Synonym` | Синоним: строка (русский) или хеш-таблица `@{ru='…'; en='…'}` |
+| `Comment` | Комментарий |
+| `Force` | Перезаписать существующий пакет |
+
+```powershell
+powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -XsdPath "<схема.xsd>" -OutputDir "<каталог>"
+```
+
+Примеры:
+```powershell
+... -XsdPath bank.xsd -OutputDir C:\cfsrc\erp -Name ОбменСБанком -Synonym "Обмен с банком"
+... -XsdPath fss.xsd -OutputDir C:\cfsrc\erp -Force
+```
+
+## Формат входа
+
+Обычная XML-схема. Две вещи, которые XSD выразить не может (`nillable` у атрибута,
+`qualified` у отдельного свойства), пишутся атрибутами из пространства имён модели XDTO:
+
+```xml
+
+```
+
+Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в модели**.
+Схема при этом остаётся валидной. Полный список аннотаций — в справке ниже.
+
+Свойства объекта метаданных можно задать прямо в схеме:
+
+```xml
+
+
+
+ ОбменСБанком
+ Обмен с банком
+
+
+
+```
+
+Принимается и не вполне каноническая схема: имя типа без префикса трактуется как тип
+целевого пространства имён, `form="qualified"` на глобальных объявлениях (так экспортирует
+сам Конфигуратор) — читается.
+
+## Зависимости между пакетами
+
+`` разрешается по namespace среди пакетов конфигурации.
+Если пакета с таким пространством имён в конфигурации нет, платформа при загрузке молча
+подменит тип на `xs:anyType` — без ошибки. Сначала соберите пакеты-зависимости,
+затем зависящий, и проверьте результат через `/xdto-validate`.
+
+## Типичный workflow
+
+1. Получить XSD от контрагента (или выгрузить: Конфигуратор → пакет → Экспорт XML-схемы)
+2. `/xdto-compile -XsdPath <файл> -OutputDir <конфигурация>`
+3. `/xdto-validate` — убедиться, что все типы разрешились
+4. `/db-load-xml` + `/db-update` — загрузить в базу
+
+Правка существующего пакета: `/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
+Пара замыкается без потерь, включая свойства объекта метаданных.
+
+## Верификация
+
+```
+/xdto-validate <каталог>/XDTOPackages/<Имя> — типы, импорты, регистрация
+```
diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1
new file mode 100644
index 00000000..6ed312e4
--- /dev/null
+++ b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1
@@ -0,0 +1,772 @@
+# xdto-compile v1.0 — 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"
+[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_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
+ # Пустое значение пишется самозакрывающимся тегом: , а не
+ $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 ---
+
+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")
+ }
+
+ Add-Attr $p "fixed" (XA $el "fixed")
+ 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) ---
+
+# 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"
+ $particle = if ($seq) { $seq } else { $cho }
+ $isOpen = $false
+
+ # Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5%
+ # типов корпуса). Отклонения приходят зеркалом xdto:order.
+ $elemProps = New-Object System.Collections.ArrayList
+ if ($particle) {
+ 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" { [void]$elemProps.Add((Build-Property $c $false)) }
+ "any" { $isOpen = $true }
+ }
+ }
+ }
+ foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) }
+ 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 }
+ switch ($node.get_LocalName()) {
+ "annotation" { }
+ "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("`r`n")
+foreach ($c in $pkgNode.Children) { Serialize-Node $c 2 @{} }
+[void]$out.Append("")
+
+$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 ''
+M ''
+M "`t"
+M "`t`t"
+M "`t`t`t$(EscText $Name)"
+M "`t`t`t"
+foreach ($s in $synItems) {
+ M "`t`t`t`t"
+ M "`t`t`t`t`t$(EscText $s.Lang)"
+ M "`t`t`t`t`t$(EscText $s.Content)"
+ M "`t`t`t`t"
+}
+M "`t`t`t"
+if ($Comment) { M "`t`t`t$(EscText $Comment)" } else { M "`t`t`t" }
+M "`t`t`t$(EscText $targetNs)"
+M "`t`t"
+M "`t"
+[void]$md.Append("")
+
+[System.IO.File]::WriteAllText($mdFile, $md.ToString(), $encBom)
+
+# --- Register in Configuration.xml ---
+
+$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"
+switch ($regResult) {
+ "added" { Write-Host " Configuration.xml: $Name добавлен в ChildObjects" }
+ "already" { Write-Host " Configuration.xml: $Name уже зарегистрирован" }
+ "no-config" { Write-Host " Configuration.xml не найден — регистрация пропущена" }
+}
diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.py b/.claude/skills/xdto-compile/scripts/xdto-compile.py
new file mode 100644
index 00000000..c411ebbb
--- /dev/null
+++ b/.claude/skills/xdto-compile/scripts/xdto-compile.py
@@ -0,0 +1,780 @@
+# xdto-compile v1.0 — 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
+
+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()
+
+# ── 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 = etree.parse(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 = etree.fromstring(xsd_bytes)
+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_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)
+ # Пустое значение пишется самозакрывающимся тегом:
+ 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 ──────────────────────────────────────
+
+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"))
+
+ add_attr(p, "fixed", el.get("fixed"))
+ 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 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")
+ particle = seq if seq is not None else cho
+ is_open = False
+
+ # Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса)
+ elem_props = []
+ if particle is not None:
+ for c in particle:
+ if not isinstance(c.tag, str) or etree.QName(c).namespace != XS_NS:
+ continue
+ ln = local(c)
+ if ln == "element":
+ elem_props.append(build_property(c, False))
+ elif ln == "any":
+ is_open = True
+ for a in xchildren(body, "attribute"):
+ node.children.append(build_property(a, True))
+ 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
+ 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"):
+ 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'\r\n')
+for c in pkg_node.children:
+ serialize_node(c, 2, {})
+out.append("")
+
+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 = [
+ '',
+ '',
+ f'\t',
+ "\t\t",
+ f"\t\t\t{esc_text(name)}",
+ "\t\t\t",
+]
+for s in syn_items:
+ md_lines += [
+ "\t\t\t\t",
+ f'\t\t\t\t\t{esc_text(s["Lang"])}',
+ f'\t\t\t\t\t{esc_text(s["Content"])}',
+ "\t\t\t\t",
+ ]
+md_lines.append("\t\t\t")
+md_lines.append(f"\t\t\t{esc_text(comment)}" if comment else "\t\t\t")
+md_lines.append(f"\t\t\t{esc_text(target_ns)}")
+md_lines += ["\t\t", "\t", ""]
+
+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 ────────────────────────────
+
+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 = etree.parse(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 reg_result == "added":
+ print(f" Configuration.xml: {name} добавлен в ChildObjects")
+elif reg_result == "already":
+ print(f" Configuration.xml: {name} уже зарегистрирован")
+else:
+ print(" Configuration.xml не найден — регистрация пропущена")
diff --git a/.claude/skills/xdto-decompile/SKILL.md b/.claude/skills/xdto-decompile/SKILL.md
new file mode 100644
index 00000000..105d8a14
--- /dev/null
+++ b/.claude/skills/xdto-decompile/SKILL.md
@@ -0,0 +1,68 @@
+---
+name: xdto-decompile
+description: Выгрузка пакета XDTO 1С в XML-схему (XSD). Используй когда нужно прочитать или отредактировать существующий пакет XDTO, отдать схему контрагенту, перенести пакет между конфигурациями
+argument-hint: [-OutFile <файл.xsd>]
+allowed-tools:
+ - Bash
+ - Read
+ - Glob
+---
+
+# /xdto-decompile — Выгрузка пакета XDTO в XML-схему
+
+Превращает `Ext/Package.bin` в обычную XML-схему. Заменяет чтение модели XDTO с её
+инвертированной кратностью, фасетами-атрибутами и локальными объявлениями префиксов
+`dNpM` на каждой ссылке.
+
+## Параметры
+
+| Параметр | Описание |
+|----------|----------|
+| `PackagePath` | Каталог пакета, путь к `Ext/Package.bin` или к `<Имя>.xml` объекта метаданных |
+| `OutFile` | Записать схему в файл (UTF-8 BOM). Без него — вывод в stdout |
+
+```powershell
+powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>"
+```
+
+Примеры:
+```powershell
+... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange
+... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange -OutFile bank.xsd
+```
+
+## Round-trip
+
+`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает исходный `Package.bin`
+байт-в-байт. Инвариант проверен на 760 пакетах выгрузок Бухгалтерии и ERP.
+
+Это основной способ править существующий пакет: точечных операций не требуется, схема
+целиком читаема и редактируема.
+
+Свойства объекта метаданных (`Name`, `Synonym`, `Comment`) выгружаются в
+`xs:annotation/xs:appinfo`, поэтому при обратной сборке не теряются. `Namespace` не
+дублируется — его несёт `targetNamespace`.
+
+## Отличия от экспорта XML-схемы в Конфигураторе
+
+Штатный экспорт Конфигуратора теряет данные: `nillable` у свойств-атрибутов
+(спецификация XSD не допускает его у атрибутов), а для пакетов с
+`elementFormQualified="false"` выдаёт невалидную схему — `form="qualified"` на
+глобальных объявлениях.
+
+Навык пишет валидную схему, а то, что XSD выразить не может, выносит в атрибуты
+пространства имён модели XDTO:
+
+```xml
+
+```
+
+Такие атрибуты валидаторы игнорируют, а `/xdto-compile` читает обратно. Схему без
+потерь можно отдавать контрагенту как есть.
+
+## Верификация
+
+```
+/xdto-decompile <путь> — схема в stdout
+/xdto-decompile <путь> -OutFile schema.xsd — в файл
+```
diff --git a/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1 b/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1
new file mode 100644
index 00000000..bca5ac44
--- /dev/null
+++ b/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1
@@ -0,0 +1,603 @@
+# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD)
+# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
+param(
+ [Parameter(Mandatory=$true)]
+ [Alias('Path')]
+ [string]$PackagePath,
+ [string]$OutFile
+)
+
+$ErrorActionPreference = "Stop"
+[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"
+
+# --- Resolve paths: accept package dir, Package.bin, or metadata .xml ---
+
+function Resolve-PackagePaths([string]$p) {
+ $binPath = $null
+ $mdPath = $null
+
+ if (Test-Path $p -PathType Leaf) {
+ if ([System.IO.Path]::GetFileName($p) -eq "Package.bin") {
+ $binPath = $p
+ # /Ext/Package.bin -> .xml
+ $extDir = [System.IO.Path]::GetDirectoryName($p)
+ $pkgDir = [System.IO.Path]::GetDirectoryName($extDir)
+ $cand = "$pkgDir.xml"
+ if (Test-Path $cand) { $mdPath = $cand }
+ } elseif ($p.EndsWith(".xml")) {
+ $mdPath = $p
+ $pkgDir = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($p), [System.IO.Path]::GetFileNameWithoutExtension($p))
+ $cand = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
+ if (Test-Path $cand) { $binPath = $cand }
+ }
+ } elseif (Test-Path $p -PathType Container) {
+ $cand = Join-Path (Join-Path $p "Ext") "Package.bin"
+ if (Test-Path $cand) {
+ $binPath = $cand
+ $mdCand = "$($p.TrimEnd('\','/')).xml"
+ if (Test-Path $mdCand) { $mdPath = $mdCand }
+ }
+ }
+
+ if (-not $binPath) { throw "Не найден Ext/Package.bin для пути: $p" }
+ return @{ Bin = $binPath; Md = $mdPath }
+}
+
+$paths = Resolve-PackagePaths $PackagePath
+
+# --- Load package model ---
+
+$doc = New-Object System.Xml.XmlDocument
+$doc.PreserveWhitespace = $false
+$doc.Load($paths.Bin)
+$pkg = $doc.DocumentElement
+if ($pkg.get_LocalName() -ne "package") { throw "Ожидался корневой , получен <$($pkg.get_LocalName())>" }
+
+$targetNs = $pkg.GetAttribute("targetNamespace")
+
+# --- Namespace -> prefix map for the emitted schema ---
+
+$nsPrefix = @{}
+$nsPrefix[$XS_NS] = "xs"
+if ($targetNs) { $nsPrefix[$targetNs] = "tns" }
+
+$imports = @()
+foreach ($imp in $pkg.ChildNodes) {
+ if ($imp.NodeType -ne [System.Xml.XmlNodeType]::Element -or $imp.get_LocalName() -ne "import") { continue }
+ $ns = $imp.GetAttribute("namespace")
+ $imports += $ns
+ if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) }
+}
+
+# Any foreign namespace referenced but not imported still needs a prefix
+function Register-Ns([string]$ns) {
+ if (-not $ns) { return }
+ if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) }
+}
+
+# --- Output buffer ---
+
+$sb = New-Object System.Text.StringBuilder
+function X([string]$line) { [void]$sb.Append($line); [void]$sb.Append("`r`n") }
+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(">", ">")
+}
+
+# --- QName conversion: bin prefix -> schema prefix ---
+
+function Convert-QName([System.Xml.XmlElement]$el, [string]$qname) {
+ if (-not $qname) { return $null }
+ # Нотация Кларка {ns}local — так записаны почти все memberTypes
+ if ($qname.StartsWith("{")) {
+ $close = $qname.IndexOf("}")
+ if ($close -gt 0) {
+ $ns = $qname.Substring(1, $close - 1)
+ $local = $qname.Substring($close + 1)
+ if (-not $ns) { return $local }
+ Register-Ns $ns
+ return "$($nsPrefix[$ns]):$local"
+ }
+ }
+ $parts = $qname.Split(":")
+ if ($parts.Count -eq 2) {
+ $ns = $el.GetNamespaceOfPrefix($parts[0])
+ $local = $parts[1]
+ } else {
+ $ns = $el.GetNamespaceOfPrefix("")
+ $local = $parts[0]
+ }
+ if (-not $ns) { return $qname }
+ Register-Ns $ns
+ return "$($nsPrefix[$ns]):$local"
+}
+
+function Convert-QNameList([System.Xml.XmlElement]$el, [string]$list) {
+ if (-not $list) { return $null }
+ $out = @()
+ foreach ($q in ($list -split "\s+")) {
+ if ($q) { $out += (Convert-QName $el $q) }
+ }
+ return ($out -join " ")
+}
+
+# --- Attribute helpers ---
+
+function A([System.Xml.XmlElement]$el, [string]$name) {
+ if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
+ return $null
+}
+
+# Emits `key="value"` pairs, skipping nulls
+function Attrs([object[]]$pairs) {
+ $out = ""
+ for ($i = 0; $i -lt $pairs.Count; $i += 2) {
+ $v = $pairs[$i + 1]
+ if ($null -ne $v) { $out += " $($pairs[$i])=`"$(Esc ([string]$v))`"" }
+ }
+ return $out
+}
+
+# --- xdto: mirror attributes ---
+# Everything XSD cannot express literally rides as xdto:.
+# Mirrors are emitted only when the literal bin form is not recoverable from the XSD.
+
+$usesXdtoNs = $false
+function Mirror([string]$name, $value) {
+ # $value НЕ типизируем: [string]$null коэрсится в "" и зеркало ложно появляется
+ if ($null -eq $value) { return "" }
+ $script:usesXdtoNs = $true
+ return " xdto:$name=`"$(Esc ([string]$value))`""
+}
+
+# Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный
+# префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся.
+function Mirror-Prefix([System.Xml.XmlElement]$el) {
+ foreach ($a in $el.Attributes) {
+ if ($a.Prefix -ne "xmlns") { continue }
+ if ($a.get_LocalName() -match '^d\d+p\d+$') { continue }
+ return (Mirror "prefix" $a.get_LocalName())
+ }
+ return ""
+}
+
+# --- Facet emission (simple types) ---
+
+$FACET_ATTRS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
+ "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace")
+
+function Emit-Facets([System.Xml.XmlElement]$el, [string]$indent) {
+ foreach ($f in $FACET_ATTRS) {
+ $v = A $el $f
+ if ($null -ne $v) { X "$indent" }
+ }
+ foreach ($child in $el.ChildNodes) {
+ if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
+ if ($child.get_LocalName() -eq "pattern") {
+ X "$indent"
+ } elseif ($child.get_LocalName() -eq "enumeration") {
+ # xsi:type on enumeration has no XSD counterpart — mirror it
+ $xsiType = $child.GetAttribute("type", $XSI_NS)
+ $m = ""
+ if ($xsiType) { $m = Mirror "type" (Convert-QName $child $xsiType) }
+ X "$indent"
+ }
+ }
+}
+
+function Has-SimpleContent([System.Xml.XmlElement]$el) {
+ foreach ($c in $el.ChildNodes) {
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { return $true }
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { return $true }
+ }
+ foreach ($f in $FACET_ATTRS) { if ($null -ne (A $el $f)) { return $true } }
+ return $false
+}
+
+# --- Simple type body (valueType / typeDef xsi:type=ValueType) ---
+
+function Emit-SimpleTypeBody([System.Xml.XmlElement]$el, [string]$indent) {
+ $variety = A $el "variety"
+ $base = Convert-QName $el (A $el "base")
+ $itemType = Convert-QName $el (A $el "itemType")
+ $memberTypes= Convert-QNameList $el (A $el "memberTypes")
+
+ # variety is mirrored: "Atomic" is written explicitly for only part of the corpus
+ $mv = Mirror "variety" $variety
+
+ # memberTypes почти всегда записаны нотацией Кларка; редкую префиксную форму зеркалим
+ $rawMembers = A $el "memberTypes"
+ if ($null -ne $rawMembers -and -not $rawMembers.StartsWith("{")) {
+ $mv += Mirror "memberTypesForm" "prefixed"
+ }
+ # При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет —
+ # из значения это не выводится (зависит от состояния сериализатора), зеркалим факт
+ if ($null -ne $rawMembers -and $rawMembers.StartsWith("{")) {
+ foreach ($a in $el.Attributes) {
+ if ($a.Prefix -eq "xmlns" -and $a.get_LocalName() -match '^d\d+p\d+$') {
+ $mv += Mirror "declareNs" $a.Value
+ break
+ }
+ }
+ }
+
+ if ($variety -eq "List" -or $itemType) {
+ X "$indent"
+ return
+ }
+ if ($variety -eq "Union" -or $memberTypes) {
+ $anon = @()
+ foreach ($c in $el.ChildNodes) {
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon += $c }
+ }
+ if ($anon.Count -eq 0) {
+ X "$indent"
+ } else {
+ X "$indent"
+ foreach ($c in $anon) {
+ X "$indent`t"
+ Emit-SimpleTypeBody $c "$indent`t`t"
+ X "$indent`t"
+ }
+ X "$indent"
+ }
+ return
+ }
+
+ # Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef
+ $anonBase = $null
+ foreach ($c in $el.ChildNodes) {
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break }
+ }
+
+ if ((Has-SimpleContent $el) -or $anonBase) {
+ X "$indent"
+ if ($anonBase) {
+ X "$indent`t"
+ Emit-SimpleTypeBody $anonBase "$indent`t`t"
+ X "$indent`t"
+ }
+ Emit-Facets $el "$indent`t"
+ X "$indent"
+ } else {
+ X "$indent"
+ }
+}
+
+# --- Property classification ---
+
+function Get-PropForm([System.Xml.XmlElement]$p) {
+ $f = A $p "form"
+ if ($null -eq $f) { return "Element" }
+ return $f
+}
+
+function Get-AnonTypeDef([System.Xml.XmlElement]$p) {
+ foreach ($c in $p.ChildNodes) {
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { return $c }
+ }
+ return $null
+}
+
+# --- Property emission ---
+
+function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlobal) {
+ $form = Get-PropForm $p
+ $name = A $p "name"
+ $type = Convert-QName $p (A $p "type")
+ $ref = Convert-QName $p (A $p "ref")
+ $local = A $p "localName"
+ $lower = A $p "lowerBound"
+ $upper = A $p "upperBound"
+ $nill = A $p "nillable"
+ $def = A $p "default"
+ $fix = A $p "fixed"
+ $anon = Get-AnonTypeDef $p
+ # qualified записан как атрибут в пространстве имён XDTO
+ $qual = $p.GetAttribute("qualified", $XDTO_NS)
+ if ($qual -eq "") { $qual = $null }
+
+ # lowerBound/upperBound map 1:1 onto minOccurs/maxOccurs, including "written explicitly"
+ $minOccurs = $lower
+ $maxOccurs = $null
+ if ($null -ne $upper) { $maxOccurs = if ($upper -eq "-1") { "unbounded" } else { $upper } }
+
+ # localName carries the original XML name when it is not a valid 1C identifier
+ $xmlName = if ($null -ne $local) { $local } else { $name }
+ $mirrorName = if ($null -ne $local) { Mirror "name" $name } else { "" }
+
+ $m = ""
+ $isAttr = ($form -eq "Attribute")
+
+ if ($isAttr) {
+ # XSD forbids nillable on attributes, and has no minOccurs/maxOccurs
+ if ($null -ne $qual) { $m += Mirror "qualified" $qual }
+ if ($null -ne $nill) { $m += Mirror "nillable" $nill }
+ if ($null -ne $lower) { $m += Mirror "lowerBound" $lower }
+ if ($null -ne $upper) { $m += Mirror "upperBound" $upper }
+ $m += $mirrorName
+ $body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $def, 'fixed', $fix)
+ if ($anon) {
+ X "$indent"
+ X "$indent`t"
+ Emit-SimpleTypeBody $anon "$indent`t`t"
+ X "$indent`t"
+ X "$indent"
+ } else {
+ X "$indent"
+ }
+ return
+ }
+
+ if ($form -eq "Text") {
+ # handled by the owning complexType (xs:simpleContent)
+ return
+ }
+
+ # form="Element" written explicitly is indistinguishable in XSD from the default
+ if ($null -ne (A $p "form")) { $m += Mirror "form" $form }
+ if ($null -ne $qual) { $m += Mirror "qualified" $qual }
+ $m += $mirrorName
+ $m += (Mirror-Prefix $p)
+
+ $body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type,
+ 'minOccurs', $minOccurs, 'maxOccurs', $maxOccurs,
+ 'nillable', $nill, 'default', $def, 'fixed', $fix)
+
+ if ($anon) {
+ X "$indent"
+ if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
+ $anonBase = Convert-QName $anon (A $anon "base")
+ if ($anonBase) {
+ X "$indent`t"
+ X "$indent`t`t"
+ X "$indent`t`t`t"
+ Emit-ComplexTypeBody $anon "$indent`t`t`t`t"
+ X "$indent`t`t`t"
+ X "$indent`t`t"
+ X "$indent`t"
+ } else {
+ X "$indent`t"
+ Emit-ComplexTypeBody $anon "$indent`t`t"
+ X "$indent`t"
+ }
+ } else {
+ X "$indent`t"
+ Emit-SimpleTypeBody $anon "$indent`t`t"
+ X "$indent`t"
+ }
+ X "$indent"
+ } else {
+ X "$indent"
+ }
+}
+
+# --- Complex type body (objectType / typeDef xsi:type=ObjectType) ---
+
+function Emit-ComplexTypeBody([System.Xml.XmlElement]$el, [string]$indent) {
+ $open = A $el "open"
+ $ordered = A $el "ordered"
+ $sequenced = A $el "sequenced"
+
+ $props = @()
+ foreach ($c in $el.ChildNodes) {
+ if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $props += $c }
+ }
+
+ $elems = @(); $attrs = @(); $text = $null
+ foreach ($p in $props) {
+ switch (Get-PropForm $p) {
+ "Attribute" { $attrs += $p }
+ "Text" { $text = $p }
+ default { $elems += $p }
+ }
+ }
+
+ # simpleContent: a "Text" property holds the element's own value
+ if ($null -ne $text) {
+ $tType = Convert-QName $text (A $text "type")
+ $tm = ""
+ $tName = A $text "name"
+ if ($tName -ne "__content") { $tm += Mirror "textName" $tName }
+ foreach ($extra in @("lowerBound", "upperBound", "nillable")) {
+ $v = A $text $extra
+ if ($null -ne $v) { $tm += Mirror "text$extra" $v }
+ }
+ X "$indent"
+ X "$indent`t"
+ foreach ($a in $attrs) { Emit-Property $a "$indent`t`t" $false }
+ X "$indent`t"
+ X "$indent"
+ return
+ }
+
+ $particleTag = if ($ordered -eq "false") { "xs:choice" } else { "xs:sequence" }
+ $needParticle = ($elems.Count -gt 0) -or ($open -eq "true")
+
+ if ($needParticle) {
+ X "$indent<$particleTag>"
+ foreach ($e in $elems) { Emit-Property $e "$indent`t" $false }
+ if ($open -eq "true") {
+ X "$indent`t"
+ }
+ X "$indent$particleTag>"
+ }
+
+ foreach ($a in $attrs) { Emit-Property $a "$indent" $false }
+ if ($open -eq "true") {
+ X "$indent"
+ }
+}
+
+# Attributes of a complexType tag itself (mirrors + XSD-native abstract/mixed)
+function ComplexTypeAttrs([System.Xml.XmlElement]$el) {
+ $open = A $el "open"
+ $ordered = A $el "ordered"
+ $sequenced = A $el "sequenced"
+ $abstract = A $el "abstract"
+ $mixed = A $el "mixed"
+
+ $out = ""
+ if ($abstract -eq "true") { $out += " abstract=`"true`"" } elseif ($null -ne $abstract) { $out += Mirror "abstract" $abstract }
+ if ($mixed -eq "true") { $out += " mixed=`"true`"" } elseif ($null -ne $mixed) { $out += Mirror "mixed" $mixed }
+
+ # XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств
+ # восстановим как «сначала form=Attribute, потом остальные» — это верно для 96.5%
+ # типов корпуса. Расхождения (768 типов) зеркалим списком имён.
+ $order = @(); $kinds = @()
+ foreach ($c in $el.ChildNodes) {
+ if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue }
+ $nm = A $c "name"
+ if ($null -eq $nm) { $nm = "@" + ((A $c "ref") -split ":")[-1] }
+ $order += $nm
+ $kinds += $(if ((Get-PropForm $c) -eq "Attribute") { 0 } else { 1 })
+ }
+ if ($order.Count -gt 1) {
+ $natural = $true
+ for ($i = 1; $i -lt $kinds.Count; $i++) { if ($kinds[$i] -lt $kinds[$i - 1]) { $natural = $false; break } }
+ if (-not $natural) { $out += Mirror "order" ($order -join "|") }
+ }
+
+ # open="true" is rendered as xs:any + xs:anyAttribute; anything else is mirrored
+ if ($null -ne $open -and $open -ne "true") { $out += Mirror "open" $open }
+ # ordered="false" is rendered as xs:choice; "true" written explicitly is mirrored
+ if ($null -ne $ordered -and $ordered -ne "false") { $out += Mirror "ordered" $ordered }
+ # sequenced has no XSD counterpart at all
+ if ($null -ne $sequenced) { $out += Mirror "sequenced" $sequenced }
+
+ return $out
+}
+
+# --- Metadata properties (Name/Synonym/Comment) from the object's .xml ---
+
+function Get-MetadataBlock() {
+ if (-not $paths.Md -or -not (Test-Path $paths.Md)) { return $null }
+ $md = New-Object System.Xml.XmlDocument
+ $md.Load($paths.Md)
+ $nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable)
+ $nsm.AddNamespace("md", $MD_NS)
+ $nsm.AddNamespace("v8", $V8_NS)
+ $props = $md.SelectSingleNode("//md:XDTOPackage/md:Properties", $nsm)
+ if (-not $props) { return $null }
+
+ $res = @{ Name = $null; Comment = $null; Synonym = @() }
+ $n = $props.SelectSingleNode("md:Name", $nsm); if ($n) { $res.Name = $n.InnerText }
+ $c = $props.SelectSingleNode("md:Comment", $nsm); if ($c) { $res.Comment = $c.InnerText }
+ foreach ($item in $props.SelectNodes("md:Synonym/v8:item", $nsm)) {
+ $lang = $item.SelectSingleNode("v8:lang", $nsm)
+ $cont = $item.SelectSingleNode("v8:content", $nsm)
+ $res.Synonym += @{ Lang = $(if ($lang) { $lang.InnerText } else { "" }); Content = $(if ($cont) { $cont.InnerText } else { "" }) }
+ }
+ return $res
+}
+
+$meta = Get-MetadataBlock
+
+# --- Emit ---
+# Body first: emitting it registers every namespace actually referenced, so the
+# schema element can declare a complete prefix map.
+
+$bodyBuilder = New-Object System.Text.StringBuilder
+$mainBuilder = $sb
+$sb = $bodyBuilder
+
+foreach ($node in $pkg.ChildNodes) {
+ if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
+ switch ($node.get_LocalName()) {
+ "import" {
+ X "`t"
+ }
+ "property" {
+ Emit-Property $node "`t" $true
+ }
+ "valueType" {
+ $name = A $node "name"
+ X "`t"
+ Emit-SimpleTypeBody $node "`t`t"
+ X "`t"
+ }
+ "objectType" {
+ $name = A $node "name"
+ $base = Convert-QName $node (A $node "base")
+ $cta = ComplexTypeAttrs $node
+ if ($base) {
+ X "`t"
+ X "`t`t"
+ X "`t`t`t"
+ Emit-ComplexTypeBody $node "`t`t`t`t"
+ X "`t`t`t"
+ X "`t`t"
+ X "`t"
+ } else {
+ $hasBody = $false
+ foreach ($c in $node.ChildNodes) { if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) { $hasBody = $true; break } }
+ if (-not $hasBody -and (A $node "open") -ne "true") {
+ X "`t"
+ } else {
+ X "`t"
+ Emit-ComplexTypeBody $node "`t`t"
+ X "`t"
+ }
+ }
+ }
+ }
+}
+
+$sb = $mainBuilder
+
+# --- Schema element ---
+
+$nsDecls = ""
+foreach ($kv in ($nsPrefix.GetEnumerator() | Sort-Object { $_.Value })) {
+ $nsDecls += " xmlns:$($kv.Value)=`"$(Esc $kv.Key)`""
+}
+if ($usesXdtoNs) { $nsDecls += " xmlns:xdto=`"$XDTO_NS`"" }
+
+$schemaAttrs = ""
+$efq = A $pkg "elementFormQualified"
+$afq = A $pkg "attributeFormQualified"
+if ($null -ne $efq) { $schemaAttrs += " elementFormDefault=`"$(if ($efq -eq 'true') { 'qualified' } else { 'unqualified' })`"" }
+if ($null -ne $afq) { $schemaAttrs += " attributeFormDefault=`"$(if ($afq -eq 'true') { 'qualified' } else { 'unqualified' })`"" }
+
+X ""
+
+if ($meta) {
+ X "`t"
+ X "`t`t"
+ X "`t`t`t"
+ if ($null -ne $meta.Name) { X "`t`t`t`t$(EscText $meta.Name)" }
+ if ($null -ne $meta.Comment -and $meta.Comment -ne "") { X "`t`t`t`t$(EscText $meta.Comment)" }
+ foreach ($s in $meta.Synonym) {
+ X "`t`t`t`t$(EscText $s.Content)"
+ }
+ X "`t`t`t"
+ X "`t`t"
+ X "`t"
+}
+
+[void]$sb.Append($bodyBuilder.ToString())
+X ""
+
+# --- Output (UTF-8 with BOM, CRLF — matches the Designer's own XSD export) ---
+
+$text = $sb.ToString()
+if ($OutFile) {
+ $dir = [System.IO.Path]::GetDirectoryName($OutFile)
+ if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
+ $enc = New-Object System.Text.UTF8Encoding($true)
+ [System.IO.File]::WriteAllText($OutFile, $text, $enc)
+ Write-Host "✓ XSD записана: $OutFile"
+ Write-Host " targetNamespace: $targetNs"
+} else {
+ [Console]::Out.Write($text)
+}
diff --git a/.claude/skills/xdto-decompile/scripts/xdto-decompile.py b/.claude/skills/xdto-decompile/scripts/xdto-decompile.py
new file mode 100644
index 00000000..133e68b8
--- /dev/null
+++ b/.claude/skills/xdto-decompile/scripts/xdto-decompile.py
@@ -0,0 +1,567 @@
+# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD) (Python port)
+# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
+import argparse
+import os
+import re
+import sys
+
+from lxml import etree
+
+# newline="" — иначе Windows транслирует \n и CRLF схемы удваивается в \r\r\n
+sys.stdout.reconfigure(encoding="utf-8", newline="")
+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"
+V8_NS = "http://v8.1c.ru/8.1/data/core"
+
+parser = argparse.ArgumentParser(allow_abbrev=False)
+parser.add_argument("-PackagePath", "-Path", required=True)
+parser.add_argument("-OutFile", default="")
+args = parser.parse_args()
+
+# ── resolve paths ────────────────────────────────────────────
+
+package_path = args.PackagePath
+bin_path = None
+md_path = None
+
+if os.path.isfile(package_path):
+ if os.path.basename(package_path) == "Package.bin":
+ bin_path = package_path
+ pkg_dir = os.path.dirname(os.path.dirname(package_path))
+ if os.path.exists(pkg_dir + ".xml"):
+ md_path = pkg_dir + ".xml"
+ elif package_path.endswith(".xml"):
+ md_path = package_path
+ stem = os.path.join(os.path.dirname(package_path),
+ os.path.splitext(os.path.basename(package_path))[0])
+ c = os.path.join(stem, "Ext", "Package.bin")
+ if os.path.exists(c):
+ bin_path = c
+elif os.path.isdir(package_path):
+ c = os.path.join(package_path, "Ext", "Package.bin")
+ if os.path.exists(c):
+ bin_path = c
+ m = package_path.rstrip("\\/") + ".xml"
+ if os.path.exists(m):
+ md_path = m
+
+if not bin_path:
+ print(f"Не найден Ext/Package.bin для пути: {package_path}", file=sys.stderr)
+ sys.exit(1)
+
+doc = etree.parse(bin_path)
+pkg = doc.getroot()
+
+
+def local(el):
+ return etree.QName(el).localname
+
+
+if local(pkg) != "package":
+ print(f"Ожидался корневой , получен <{local(pkg)}>", file=sys.stderr)
+ sys.exit(1)
+
+target_ns = pkg.get("targetNamespace")
+
+# ── namespace -> prefix map for the emitted schema ───────────
+
+ns_prefix = {XS_NS: "xs"}
+if target_ns:
+ ns_prefix[target_ns] = "tns"
+
+imports = []
+for imp in pkg:
+ if isinstance(imp.tag, str) and local(imp) == "import":
+ ns = imp.get("namespace")
+ imports.append(ns)
+ if ns not in ns_prefix:
+ ns_prefix[ns] = "ns" + str(len(ns_prefix))
+
+
+def register_ns(ns):
+ if ns and ns not in ns_prefix:
+ ns_prefix[ns] = "ns" + str(len(ns_prefix))
+
+
+# ── output buffer ────────────────────────────────────────────
+
+lines = []
+
+
+def X(line):
+ lines.append(line)
+
+
+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(">", ">")
+
+
+# ── QName conversion: bin prefix -> schema prefix ────────────
+
+def convert_qname(el, qname):
+ if not qname:
+ return None
+ # Нотация Кларка {ns}local — так записаны почти все memberTypes
+ if qname.startswith("{"):
+ close = qname.find("}")
+ if close > 0:
+ ns = qname[1:close]
+ loc = qname[close + 1:]
+ if not ns:
+ return loc
+ register_ns(ns)
+ return f"{ns_prefix[ns]}:{loc}"
+ parts = qname.split(":")
+ if len(parts) == 2:
+ ns = el.nsmap.get(parts[0])
+ loc = parts[1]
+ else:
+ ns = el.nsmap.get(None)
+ loc = parts[0]
+ if not ns:
+ return qname
+ register_ns(ns)
+ return f"{ns_prefix[ns]}:{loc}"
+
+
+def convert_qname_list(el, lst):
+ if not lst:
+ return None
+ return " ".join(convert_qname(el, q) for q in lst.split() if q)
+
+
+def attrs(pairs):
+ out = ""
+ for i in range(0, len(pairs), 2):
+ v = pairs[i + 1]
+ if v is not None:
+ out += f' {pairs[i]}="{esc(v)}"'
+ return out
+
+
+# ── xdto: mirror attributes ──────────────────────────────────
+
+state = {"uses_xdto": False}
+
+
+def mirror(name, value):
+ if value is None:
+ return ""
+ state["uses_xdto"] = True
+ return f' xdto:{name}="{esc(value)}"'
+
+
+DNPM = re.compile(r"^d\d+p\d+$")
+
+
+def ns_decls_of(el):
+ """Локальные объявления xmlns на самом узле, как (префикс, uri)."""
+ parent_map = el.getparent().nsmap if el.getparent() is not None else {}
+ for px, uri in el.nsmap.items():
+ if px is None:
+ continue
+ if parent_map.get(px) == uri:
+ continue
+ yield px, uri
+
+
+def mirror_prefix(el):
+ # Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный
+ # префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся.
+ for px, _uri in ns_decls_of(el):
+ if DNPM.match(px):
+ continue
+ return mirror("prefix", px)
+ return ""
+
+
+FACET_ATTRS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
+ "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"]
+
+
+def emit_facets(el, indent):
+ for f in FACET_ATTRS:
+ v = el.get(f)
+ if v is not None:
+ X(f'{indent}')
+ for child in el:
+ if not isinstance(child.tag, str):
+ continue
+ ln = local(child)
+ if ln == "pattern":
+ X(f'{indent}')
+ elif ln == "enumeration":
+ xsi_type = child.get(f"{{{XSI_NS}}}type")
+ m = mirror("type", convert_qname(child, xsi_type)) if xsi_type else ""
+ X(f'{indent}')
+
+
+def has_simple_content(el):
+ for c in el:
+ if isinstance(c.tag, str) and local(c) in ("pattern", "enumeration"):
+ return True
+ return any(el.get(f) is not None for f in FACET_ATTRS)
+
+
+# ── simple type body (valueType / typeDef xsi:type=ValueType) ─
+
+def emit_simple_type_body(el, indent):
+ variety = el.get("variety")
+ base = convert_qname(el, el.get("base"))
+ item_type = convert_qname(el, el.get("itemType"))
+ member_types = convert_qname_list(el, el.get("memberTypes"))
+
+ mv = mirror("variety", variety)
+
+ raw_members = el.get("memberTypes")
+ if raw_members is not None and not raw_members.startswith("{"):
+ mv += mirror("memberTypesForm", "prefixed")
+ # При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет —
+ # из значения это не выводится (зависит от состояния сериализатора), зеркалим факт
+ if raw_members is not None and raw_members.startswith("{"):
+ for px, uri in ns_decls_of(el):
+ if DNPM.match(px):
+ mv += mirror("declareNs", uri)
+ break
+
+ if variety == "List" or item_type:
+ X(f'{indent}')
+ return
+ if variety == "Union" or member_types:
+ anon = [c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"]
+ if not anon:
+ X(f'{indent}')
+ else:
+ X(f'{indent}')
+ for c in anon:
+ X(f"{indent}\t")
+ emit_simple_type_body(c, indent + "\t\t")
+ X(f"{indent}\t")
+ X(f"{indent}")
+ return
+
+ # Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef
+ anon_base = next((c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"), None)
+
+ if has_simple_content(el) or anon_base is not None:
+ X(f'{indent}')
+ if anon_base is not None:
+ X(f"{indent}\t")
+ emit_simple_type_body(anon_base, indent + "\t\t")
+ X(f"{indent}\t")
+ emit_facets(el, indent + "\t")
+ X(f"{indent}")
+ else:
+ X(f'{indent}')
+
+
+# ── property classification ──────────────────────────────────
+
+def prop_form(p):
+ f = p.get("form")
+ return "Element" if f is None else f
+
+
+def anon_type_def(p):
+ return next((c for c in p if isinstance(c.tag, str) and local(c) == "typeDef"), None)
+
+
+# ── property emission ────────────────────────────────────────
+
+def emit_property(p, indent):
+ form = prop_form(p)
+ name = p.get("name")
+ type_ = convert_qname(p, p.get("type"))
+ ref = convert_qname(p, p.get("ref"))
+ local_name = p.get("localName")
+ lower = p.get("lowerBound")
+ upper = p.get("upperBound")
+ nill = p.get("nillable")
+ default = p.get("default")
+ fixed = p.get("fixed")
+ anon = anon_type_def(p)
+ qual = p.get(f"{{{XDTO_NS}}}qualified")
+
+ min_occurs = lower
+ max_occurs = None
+ if upper is not None:
+ max_occurs = "unbounded" if upper == "-1" else upper
+
+ xml_name = local_name if local_name is not None else name
+ mirror_name = mirror("name", name) if local_name is not None else ""
+
+ m = ""
+ if form == "Attribute":
+ if qual is not None:
+ m += mirror("qualified", qual)
+ if nill is not None:
+ m += mirror("nillable", nill)
+ if lower is not None:
+ m += mirror("lowerBound", lower)
+ if upper is not None:
+ m += mirror("upperBound", upper)
+ m += mirror_name
+ body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", default, "fixed", fixed])
+ if anon is not None:
+ X(f"{indent}")
+ X(f"{indent}\t")
+ emit_simple_type_body(anon, indent + "\t\t")
+ X(f"{indent}\t")
+ X(f"{indent}")
+ else:
+ X(f"{indent}")
+ return
+
+ if form == "Text":
+ # handled by the owning complexType (xs:simpleContent)
+ return
+
+ if p.get("form") is not None:
+ m += mirror("form", form)
+ if qual is not None:
+ m += mirror("qualified", qual)
+ m += mirror_name
+ m += mirror_prefix(p)
+
+ body = attrs(["name", xml_name, "ref", ref, "type", type_,
+ "minOccurs", min_occurs, "maxOccurs", max_occurs,
+ "nillable", nill, "default", default, "fixed", fixed])
+
+ if anon is not None:
+ X(f"{indent}")
+ if anon.get(f"{{{XSI_NS}}}type") == "ObjectType":
+ anon_base = convert_qname(anon, anon.get("base"))
+ if anon_base:
+ X(f"{indent}\t")
+ X(f"{indent}\t\t")
+ X(f'{indent}\t\t\t')
+ emit_complex_type_body(anon, indent + "\t\t\t\t")
+ X(f"{indent}\t\t\t")
+ X(f"{indent}\t\t")
+ X(f"{indent}\t")
+ else:
+ X(f"{indent}\t")
+ emit_complex_type_body(anon, indent + "\t\t")
+ X(f"{indent}\t")
+ else:
+ X(f"{indent}\t")
+ emit_simple_type_body(anon, indent + "\t\t")
+ X(f"{indent}\t")
+ X(f"{indent}")
+ else:
+ X(f"{indent}")
+
+
+# ── complex type body ────────────────────────────────────────
+
+def emit_complex_type_body(el, indent):
+ open_ = el.get("open")
+ ordered = el.get("ordered")
+
+ props = [c for c in el if isinstance(c.tag, str) and local(c) == "property"]
+ elems, attr_props, text = [], [], None
+ for p in props:
+ f = prop_form(p)
+ if f == "Attribute":
+ attr_props.append(p)
+ elif f == "Text":
+ text = p
+ else:
+ elems.append(p)
+
+ if text is not None:
+ t_type = convert_qname(text, text.get("type"))
+ tm = ""
+ t_name = text.get("name")
+ if t_name != "__content":
+ tm += mirror("textName", t_name)
+ for extra in ("lowerBound", "upperBound", "nillable"):
+ v = text.get(extra)
+ if v is not None:
+ tm += mirror("text" + extra, v)
+ X(f"{indent}")
+ X(f'{indent}\t')
+ for a in attr_props:
+ emit_property(a, indent + "\t\t")
+ X(f"{indent}\t")
+ X(f"{indent}")
+ return
+
+ particle_tag = "xs:choice" if ordered == "false" else "xs:sequence"
+ if elems or open_ == "true":
+ X(f"{indent}<{particle_tag}>")
+ for e in elems:
+ emit_property(e, indent + "\t")
+ if open_ == "true":
+ X(f'{indent}\t')
+ X(f"{indent}{particle_tag}>")
+
+ for a in attr_props:
+ emit_property(a, indent)
+ if open_ == "true":
+ X(f'{indent}')
+
+
+def complex_type_attrs(el):
+ open_ = el.get("open")
+ ordered = el.get("ordered")
+ sequenced = el.get("sequenced")
+ abstract = el.get("abstract")
+ mixed = el.get("mixed")
+
+ out = ""
+ if abstract == "true":
+ out += ' abstract="true"'
+ elif abstract is not None:
+ out += mirror("abstract", abstract)
+ if mixed == "true":
+ out += ' mixed="true"'
+ elif mixed is not None:
+ out += mirror("mixed", mixed)
+
+ # XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств
+ # восстановим как «сначала form=Attribute, потом остальные» — верно для 96.5% типов
+ order, kinds = [], []
+ for c in el:
+ if not isinstance(c.tag, str) or local(c) != "property":
+ continue
+ nm = c.get("name")
+ if nm is None:
+ nm = "@" + (c.get("ref") or "").split(":")[-1]
+ order.append(nm)
+ kinds.append(0 if prop_form(c) == "Attribute" else 1)
+ if len(order) > 1:
+ natural = all(kinds[i] >= kinds[i - 1] for i in range(1, len(kinds)))
+ if not natural:
+ out += mirror("order", "|".join(order))
+
+ if open_ is not None and open_ != "true":
+ out += mirror("open", open_)
+ if ordered is not None and ordered != "false":
+ out += mirror("ordered", ordered)
+ if sequenced is not None:
+ out += mirror("sequenced", sequenced)
+ return out
+
+
+# ── metadata properties ──────────────────────────────────────
+
+meta = None
+if md_path and os.path.exists(md_path):
+ md = etree.parse(md_path)
+ props_el = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties")
+ if props_el is not None:
+ meta = {"Name": None, "Comment": None, "Synonym": []}
+ n = props_el.find(f"{{{MD_NS}}}Name")
+ if n is not None:
+ meta["Name"] = n.text or ""
+ c = props_el.find(f"{{{MD_NS}}}Comment")
+ if c is not None:
+ meta["Comment"] = c.text or ""
+ for item in props_el.iterfind(f"{{{MD_NS}}}Synonym/{{{V8_NS}}}item"):
+ lang = item.find(f"{{{V8_NS}}}lang")
+ cont = item.find(f"{{{V8_NS}}}content")
+ meta["Synonym"].append({
+ "Lang": (lang.text or "") if lang is not None else "",
+ "Content": (cont.text or "") if cont is not None else "",
+ })
+
+# ── emit ─────────────────────────────────────────────────────
+# Тело первым: при его генерации регистрируются все использованные пространства
+# имён, поэтому корневой элемент может объявить полную карту префиксов.
+
+for node in pkg:
+ if not isinstance(node.tag, str):
+ continue
+ ln = local(node)
+ if ln == "import":
+ X(f'\t')
+ elif ln == "property":
+ emit_property(node, "\t")
+ elif ln == "valueType":
+ X(f'\t')
+ emit_simple_type_body(node, "\t\t")
+ X("\t")
+ elif ln == "objectType":
+ name = node.get("name")
+ base = convert_qname(node, node.get("base"))
+ cta = complex_type_attrs(node)
+ if base:
+ X(f'\t')
+ X("\t\t")
+ X(f'\t\t\t')
+ emit_complex_type_body(node, "\t\t\t\t")
+ X("\t\t\t")
+ X("\t\t")
+ X("\t")
+ else:
+ has_body = any(isinstance(c.tag, str) for c in node)
+ if not has_body and node.get("open") != "true":
+ X(f'\t')
+ else:
+ X(f'\t')
+ emit_complex_type_body(node, "\t\t")
+ X("\t")
+
+body_lines = lines
+lines = []
+
+# ── schema element ───────────────────────────────────────────
+
+ns_decls = ""
+for uri, px in sorted(ns_prefix.items(), key=lambda kv: kv[1]):
+ ns_decls += f' xmlns:{px}="{esc(uri)}"'
+if state["uses_xdto"]:
+ ns_decls += f' xmlns:xdto="{XDTO_NS}"'
+
+schema_attrs = ""
+efq = pkg.get("elementFormQualified")
+afq = pkg.get("attributeFormQualified")
+if efq is not None:
+ schema_attrs += f' elementFormDefault="{"qualified" if efq == "true" else "unqualified"}"'
+if afq is not None:
+ schema_attrs += f' attributeFormDefault="{"qualified" if afq == "true" else "unqualified"}"'
+
+X(f'')
+
+if meta:
+ X("\t")
+ X("\t\t")
+ X(f'\t\t\t')
+ if meta["Name"] is not None:
+ X(f'\t\t\t\t{esc_text(meta["Name"])}')
+ if meta["Comment"]:
+ X(f'\t\t\t\t{esc_text(meta["Comment"])}')
+ for s in meta["Synonym"]:
+ X(f'\t\t\t\t{esc_text(s["Content"])}')
+ X("\t\t\t")
+ X("\t\t")
+ X("\t")
+
+lines.extend(body_lines)
+X("")
+
+text = "\r\n".join(lines) + "\r\n"
+
+if args.OutFile:
+ d = os.path.dirname(args.OutFile)
+ if d and not os.path.isdir(d):
+ os.makedirs(d, exist_ok=True)
+ with open(args.OutFile, "wb") as f:
+ f.write(b"\xef\xbb\xbf" + text.encode("utf-8"))
+ print(f"✓ XSD записана: {args.OutFile}")
+ print(f" targetNamespace: {target_ns}")
+else:
+ sys.stdout.write(text)
diff --git a/.claude/skills/xdto-validate/SKILL.md b/.claude/skills/xdto-validate/SKILL.md
new file mode 100644
index 00000000..354b0989
--- /dev/null
+++ b/.claude/skills/xdto-validate/SKILL.md
@@ -0,0 +1,71 @@
+---
+name: xdto-validate
+description: Валидация пакета XDTO 1С. Используй после создания или модификации пакета XDTO для проверки корректности
+argument-hint: [-ConfigDir <каталог>] [-Detailed] [-MaxErrors N] [-OutFile <файл>]
+allowed-tools:
+ - Bash
+ - Read
+ - Glob
+---
+
+# /xdto-validate — Валидация пакета XDTO
+
+Проверяет модель пакета, объект метаданных и его связь с конфигурацией.
+Exit code `1` при наличии ошибок.
+
+## Параметры
+
+| Параметр | Описание |
+|----------|----------|
+| `PackagePath` | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml` объекта метаданных |
+| `ConfigDir` | Каталог конфигурации. По умолчанию определяется по расположению пакета |
+| `Detailed` | Показывать успешные проверки, а не только ошибки |
+| `MaxErrors` | Остановиться после N ошибок (по умолчанию 20) |
+| `OutFile` | Записать отчёт в файл |
+
+```powershell
+powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>"
+```
+
+## Проверки
+
+**Модель**
+- Package.bin — корректный XML с корнем ``, задан `targetNamespace`, кодировка UTF-8 с BOM
+- Имена типов уникальны внутри пакета
+- Каждая ссылка (`type`, `base`, `ref`, `itemType`, `memberTypes`) разрешается: собственный
+ тип, импортированное пространство имён или `xs:`. Поддержана нотация Кларка `{ns}local`
+- Префиксы объявлены (нет ссылок на необъявленный `dNpM`)
+- Фасеты согласованы: `length` не вместе с `minLength`/`maxLength`, `minLength ≤ maxLength`,
+ `fractionDigits ≤ totalDigits`, допустимые `whiteSpace` и `variety`
+- Свойства: допустимая `form`, `lowerBound ≤ upperBound`, есть `name` либо `ref`
+
+**Связь с конфигурацией**
+- Объект метаданных существует, `Name` совпадает с именем каталога, `Namespace` — с `targetNamespace`
+- Пакет зарегистрирован в `ChildObjects` файла `Configuration.xml`
+- `targetNamespace` уникален среди пакетов конфигурации
+
+**Предупреждения о тихих дефектах**
+
+Два класса проблем платформа не диагностирует — их ловит навык:
+
+- **`xs:anyType` при объявленных импортах.** При импорте XML-схемы тип из чужого
+ пространства имён, для которого в конфигурации нет пакета, заменяется на `xs:anyType`
+ молча — ни ошибки, ни предупреждения. Пакет выглядит загруженным, а `ФабрикаXDTO`
+ потом отдаёт бесструктурное значение. Сопутствующий признак — объявленный, но
+ неиспользуемый ``.
+- **`nillable="true"` вместе с `form="Attribute"`.** Спецификация XSD не допускает
+ `nillable` у атрибутов, поэтому экспорт XML-схемы в Конфигураторе такое свойство теряет.
+ На саму работу пакета не влияет, но ломает перенос схемы через Конфигуратор.
+
+## Типичный workflow
+
+1. `/xdto-compile` или `/xdto-decompile` → правка → `/xdto-compile -Force`
+2. `/xdto-validate <путь>` — до загрузки в базу
+3. `/db-load-xml` + `/db-update`
+
+## Верификация
+
+```
+/xdto-validate <путь> — только проблемы
+/xdto-validate <путь> -Detailed — все проверки
+```
diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.ps1 b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1
new file mode 100644
index 00000000..525bdeb7
--- /dev/null
+++ b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1
@@ -0,0 +1,402 @@
+# xdto-validate v1.0 — Validate a 1C XDTO package
+# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
+param(
+ [Parameter(Mandatory)]
+ [Alias('Path')]
+ [string]$PackagePath,
+
+ [string]$ConfigDir,
+
+ [switch]$Detailed,
+
+ [int]$MaxErrors = 20,
+
+ [string]$OutFile
+)
+
+$ErrorActionPreference = "Stop"
+[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
+
+$XS_NS = "http://www.w3.org/2001/XMLSchema"
+$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
+$MD_NS = "http://v8.1c.ru/8.3/MDClasses"
+
+# --- Reporting ---
+
+$script:errors = 0
+$script:warnings = 0
+$script:okCount = 0
+$script:stopped = $false
+$script:output = New-Object System.Text.StringBuilder
+
+function Out-Line([string]$s) { [void]$script:output.AppendLine($s) }
+function Report-OK([string]$msg) {
+ $script:okCount++
+ if ($Detailed) { Out-Line "[OK] $msg" }
+}
+function Report-Error([string]$msg) {
+ $script:errors++
+ Out-Line "[ERROR] $msg"
+ if ($script:errors -ge $MaxErrors) { $script:stopped = $true }
+}
+function Report-Warn([string]$msg) {
+ $script:warnings++
+ Out-Line "[WARN] $msg"
+}
+
+# --- Resolve paths ---
+
+if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
+ $PackagePath = Join-Path (Get-Location).Path $PackagePath
+}
+
+$binPath = $null
+$mdPath = $null
+if (Test-Path $PackagePath -PathType Leaf) {
+ if ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin") {
+ $binPath = $PackagePath
+ $pkgDir = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($PackagePath))
+ if (Test-Path "$pkgDir.xml") { $mdPath = "$pkgDir.xml" }
+ } elseif ($PackagePath.EndsWith(".xml")) {
+ $mdPath = $PackagePath
+ $stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath), [System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
+ $c = Join-Path (Join-Path $stem "Ext") "Package.bin"
+ if (Test-Path $c) { $binPath = $c }
+ }
+} elseif (Test-Path $PackagePath -PathType Container) {
+ $c = Join-Path (Join-Path $PackagePath "Ext") "Package.bin"
+ if (Test-Path $c) {
+ $binPath = $c
+ $m = "$($PackagePath.TrimEnd('\','/')).xml"
+ if (Test-Path $m) { $mdPath = $m }
+ }
+}
+
+$fileName = if ($binPath) { [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath))) } else { $PackagePath }
+
+if (-not $binPath) {
+ Write-Host "[ERROR] Не найден Ext/Package.bin для пути: $PackagePath"
+ exit 1
+}
+
+# Каталог конфигурации — для проверок регистрации и зависимостей
+if (-not $ConfigDir) {
+ $d = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath)))
+ # .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень
+ $ConfigDir = [System.IO.Path]::GetDirectoryName($d)
+}
+
+$finalize = {
+ $checks = $script:okCount + $script:errors + $script:warnings
+ if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) {
+ $result = "=== Validation OK: $fileName ($checks checks) ==="
+ } else {
+ Out-Line ""
+ Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ==="
+ $result = $script:output.ToString()
+ }
+ Write-Host $result
+ if ($OutFile) {
+ $utf8Bom = New-Object System.Text.UTF8Encoding $true
+ [System.IO.File]::WriteAllText($OutFile, $script:output.ToString(), $utf8Bom)
+ }
+}
+
+# --- 1. Well-formedness ---
+
+$doc = New-Object System.Xml.XmlDocument
+try {
+ $doc.Load($binPath)
+} catch {
+ Report-Error "Package.bin не является корректным XML: $($_.Exception.Message)"
+ & $finalize
+ exit 1
+}
+$pkg = $doc.DocumentElement
+if ($pkg.get_LocalName() -ne "package") {
+ Report-Error "Ожидался корневой , найден <$($pkg.get_LocalName())>"
+ & $finalize
+ exit 1
+}
+Report-OK "Package.bin: корректный XML, корень "
+
+$targetNs = $pkg.GetAttribute("targetNamespace")
+if (-not $targetNs) {
+ Report-Error "У не задан targetNamespace"
+} else {
+ Report-OK "targetNamespace: $targetNs"
+}
+
+# --- 2. Encoding / EOL ---
+
+$bytes = [System.IO.File]::ReadAllBytes($binPath)
+if (-not ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)) {
+ Report-Warn "Package.bin без BOM UTF-8 — платформа пишет файл с BOM"
+} else {
+ Report-OK "Кодировка: UTF-8 с BOM"
+}
+
+# --- 3. Collect declarations ---
+
+$imports = New-Object System.Collections.ArrayList
+$localTypes = New-Object System.Collections.Generic.HashSet[string]
+$globalProps = New-Object System.Collections.Generic.HashSet[string]
+
+foreach ($n in $pkg.ChildNodes) {
+ if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
+ switch ($n.get_LocalName()) {
+ "import" { [void]$imports.Add($n.GetAttribute("namespace")) }
+ "objectType" { [void]$localTypes.Add($n.GetAttribute("name")) }
+ "valueType" { [void]$localTypes.Add($n.GetAttribute("name")) }
+ "property" { if ($n.HasAttribute("name")) { [void]$globalProps.Add($n.GetAttribute("name")) } }
+ }
+}
+
+# --- 4. Duplicate type names ---
+
+$seen = @{}
+foreach ($n in $pkg.ChildNodes) {
+ if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
+ if ($n.get_LocalName() -ne "objectType" -and $n.get_LocalName() -ne "valueType") { continue }
+ $nm = $n.GetAttribute("name")
+ if (-not $nm) { Report-Error "<$($n.get_LocalName())> без атрибута name"; continue }
+ if ($seen.ContainsKey($nm)) {
+ Report-Error "Дублирующееся имя типа: $nm"
+ } else {
+ $seen[$nm] = $true
+ }
+}
+if ($localTypes.Count -gt 0) { Report-OK "$($localTypes.Count) тип(ов), имена уникальны" }
+
+# --- 5. Type references resolve ---
+
+$usedNamespaces = New-Object System.Collections.Generic.HashSet[string]
+$anyTypeProps = New-Object System.Collections.ArrayList
+$refAttrs = @("type", "base", "ref", "itemType")
+
+function Resolve-Ref([System.Xml.XmlElement]$el, [string]$attr, [string]$raw) {
+ if (-not $raw) { return }
+ # Нотация Кларка {ns}local
+ if ($raw.StartsWith("{")) {
+ $close = $raw.IndexOf("}")
+ if ($close -lt 0) { Report-Error "Некорректная нотация Кларка в $attr=`"$raw`""; return }
+ $ns = $raw.Substring(1, $close - 1)
+ $local = $raw.Substring($close + 1)
+ } else {
+ $parts = $raw.Split(":")
+ if ($parts.Count -eq 2) {
+ $ns = $el.GetNamespaceOfPrefix($parts[0])
+ $local = $parts[1]
+ if (-not $ns) {
+ Report-Error "Префикс `"$($parts[0])`" не объявлен: $attr=`"$raw`" (тип $($el.get_LocalName()))"
+ return
+ }
+ } else {
+ $ns = $null
+ $local = $parts[0]
+ }
+ }
+
+ if ($ns -eq $XS_NS -or $ns -eq $XSI_NS) {
+ if ($local -eq "anyType") { [void]$anyTypeProps.Add($el) }
+ return
+ }
+ if ($ns -eq $targetNs) {
+ if ($attr -eq "ref") {
+ if (-not $globalProps.Contains($local)) {
+ Report-Error "ref=`"$raw`" не разрешается: в пакете нет глобального свойства `"$local`""
+ }
+ } elseif (-not $localTypes.Contains($local)) {
+ Report-Error "$attr=`"$raw`" не разрешается: в пакете нет типа `"$local`""
+ }
+ return
+ }
+ if ($ns) {
+ [void]$usedNamespaces.Add($ns)
+ if (-not $imports.Contains($ns)) {
+ Report-Error "$attr=`"$raw`" ссылается на `"$ns`", но не объявлен"
+ }
+ }
+}
+
+$nodeCount = 0
+foreach ($el in $pkg.SelectNodes("//*")) {
+ if ($el.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
+ $nodeCount++
+ foreach ($a in $refAttrs) {
+ if ($el.HasAttribute($a)) { Resolve-Ref $el $a $el.GetAttribute($a) }
+ }
+ if ($el.HasAttribute("memberTypes")) {
+ foreach ($m in ($el.GetAttribute("memberTypes") -split "\s+")) {
+ if ($m) { Resolve-Ref $el "memberTypes" $m }
+ }
+ }
+ if ($script:stopped) { break }
+}
+if (-not $script:stopped) { Report-OK "$nodeCount узлов: ссылки на типы разрешаются" }
+
+if ($script:stopped) { & $finalize; exit 1 }
+
+# --- 6. Silent degradation to xs:anyType ---
+# Платформа при импорте XML-схемы молча подменяет неразрешённый чужой тип на anyType.
+
+if ($anyTypeProps.Count -gt 0 -and $imports.Count -gt 0) {
+ $names = @()
+ foreach ($p in $anyTypeProps) { if ($p.HasAttribute("name")) { $names += $p.GetAttribute("name") } }
+ $shown = ($names | Select-Object -First 5) -join ", "
+ Report-Warn "Свойств с type=`"xs:anyType`": $($anyTypeProps.Count) при объявленных импортах ($shown). Возможна тихая деградация: при импорте XML-схемы платформа заменяет неразрешённый чужой тип на anyType без ошибки"
+}
+
+# --- 7. Unused imports ---
+
+foreach ($imp in $imports) {
+ if (-not $usedNamespaces.Contains($imp)) {
+ Report-Warn " объявлен, но ни один тип из этого пространства имён не используется"
+ }
+}
+if ($imports.Count -gt 0 -and $script:warnings -eq 0) { Report-OK "$($imports.Count) импорт(ов) — все используются" }
+
+# --- 8. nillable on attribute-form properties ---
+
+$nillAttrs = @()
+foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
+ if ($p.GetAttribute("form") -eq "Attribute" -and $p.GetAttribute("nillable") -eq "true") {
+ $nillAttrs += $p.GetAttribute("name")
+ }
+}
+if ($nillAttrs.Count -gt 0) {
+ Report-Warn "Свойств с nillable=`"true`" и form=`"Attribute`": $($nillAttrs.Count) ($(($nillAttrs | Select-Object -First 5) -join ', ')). Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет"
+}
+
+# --- 9. Facet consistency ---
+
+$FACET_NUM = @("totalDigits", "fractionDigits")
+$facetChecked = 0
+foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef']")) {
+ if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -eq "ObjectType") { continue }
+ $facetChecked++
+ $nm = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
+
+ $len = $t.GetAttribute("length")
+ if ($len -and ($t.HasAttribute("minLength") -or $t.HasAttribute("maxLength"))) {
+ Report-Error "$nm : length несовместим с minLength/maxLength"
+ }
+ $minL = $t.GetAttribute("minLength"); $maxL = $t.GetAttribute("maxLength")
+ if ($minL -and $maxL -and ([int]$minL -gt [int]$maxL)) {
+ Report-Error "$nm : minLength ($minL) больше maxLength ($maxL)"
+ }
+ $td = $t.GetAttribute("totalDigits"); $fd = $t.GetAttribute("fractionDigits")
+ if ($td -and $fd -and ([int]$fd -gt [int]$td)) {
+ Report-Error "$nm : fractionDigits ($fd) больше totalDigits ($td)"
+ }
+ $ws = $t.GetAttribute("whiteSpace")
+ if ($ws -and @("preserve", "replace", "collapse") -notcontains $ws) {
+ Report-Error "$nm : недопустимое whiteSpace=`"$ws`""
+ }
+ $var = $t.GetAttribute("variety")
+ if ($var -and @("Atomic", "List", "Union") -notcontains $var) {
+ Report-Error "$nm : недопустимое variety=`"$var`""
+ }
+ if ($var -eq "List" -and -not $t.HasAttribute("itemType")) {
+ Report-Warn "$nm : variety=`"List`" без itemType"
+ }
+ if ($script:stopped) { break }
+}
+if ($facetChecked -gt 0 -and -not $script:stopped) { Report-OK "$facetChecked простых тип(ов): фасеты согласованы" }
+
+if ($script:stopped) { & $finalize; exit 1 }
+
+# --- 10. property form / bounds ---
+
+foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
+ $form = $p.GetAttribute("form")
+ if ($form -and @("Element", "Attribute", "Text") -notcontains $form) {
+ Report-Error "Свойство `"$($p.GetAttribute('name'))`": недопустимое form=`"$form`""
+ }
+ $ub = $p.GetAttribute("upperBound")
+ if ($ub -and $ub -ne "-1" -and ([int]$ub -lt 1)) {
+ Report-Error "Свойство `"$($p.GetAttribute('name'))`": upperBound=`"$ub`" (допустимы -1 или число ≥ 1)"
+ }
+ $lb = $p.GetAttribute("lowerBound")
+ if ($lb -and $ub -and $ub -ne "-1" -and ([int]$lb -gt [int]$ub)) {
+ Report-Error "Свойство `"$($p.GetAttribute('name'))`": lowerBound ($lb) больше upperBound ($ub)"
+ }
+ if (-not $p.HasAttribute("name") -and -not $p.HasAttribute("ref")) {
+ Report-Error "Свойство без name и без ref"
+ }
+ if ($script:stopped) { break }
+}
+if (-not $script:stopped) { Report-OK "Свойства: form и кратности корректны" }
+
+# --- 11. Metadata object ---
+
+if ($mdPath) {
+ $md = New-Object System.Xml.XmlDocument
+ $md.Load($mdPath)
+ $nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable)
+ $nsm.AddNamespace("md", $MD_NS)
+ $mdName = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Name", $nsm)
+ $mdNs = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Namespace", $nsm)
+ if (-not $mdName) {
+ Report-Error "В объекте метаданных не задано "
+ } elseif ($mdName.InnerText -ne $fileName) {
+ Report-Error "$($mdName.InnerText) не совпадает с именем каталога `"$fileName`""
+ } else {
+ Report-OK "Объект метаданных: Name = $($mdName.InnerText)"
+ }
+ if ($mdNs -and $mdNs.InnerText -ne $targetNs) {
+ Report-Error "$($mdNs.InnerText) не совпадает с targetNamespace пакета ($targetNs)"
+ } elseif ($mdNs) {
+ Report-OK "Namespace объекта метаданных совпадает с targetNamespace"
+ }
+} else {
+ Report-Warn "Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета"
+}
+
+# --- 12. Registration in Configuration.xml + namespace uniqueness ---
+
+$configXml = Join-Path $ConfigDir "Configuration.xml"
+if (Test-Path $configXml) {
+ $cfg = New-Object System.Xml.XmlDocument
+ $cfg.Load($configXml)
+ $nsm2 = New-Object System.Xml.XmlNamespaceManager($cfg.NameTable)
+ $nsm2.AddNamespace("md", $MD_NS)
+ $registered = $false
+ foreach ($e in $cfg.SelectNodes("//md:Configuration/md:ChildObjects/md:XDTOPackage", $nsm2)) {
+ if ($e.InnerText -eq $fileName) { $registered = $true; break }
+ }
+ if ($registered) {
+ Report-OK "Зарегистрирован в Configuration.xml"
+ } else {
+ Report-Error "$fileName отсутствует в ChildObjects файла Configuration.xml — платформа пакет не увидит"
+ }
+
+ # Уникальность targetNamespace среди пакетов конфигурации
+ $pkgRoot = Join-Path $ConfigDir "XDTOPackages"
+ if (Test-Path $pkgRoot) {
+ $clash = @()
+ foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) {
+ if ($other.Name -eq $fileName) { continue }
+ $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)
+ if ($od.DocumentElement.GetAttribute("targetNamespace") -eq $targetNs) { $clash += $other.Name }
+ } catch {}
+ }
+ if ($clash.Count -gt 0) {
+ Report-Error "targetNamespace `"$targetNs`" уже занят пакет(ами): $($clash -join ', '). Платформа не допускает два пакета с одним пространством имён"
+ } else {
+ Report-OK "targetNamespace уникален в конфигурации"
+ }
+ }
+} else {
+ Report-Warn "Configuration.xml не найден ($ConfigDir) — проверки регистрации и уникальности namespace пропущены"
+}
+
+# --- Final ---
+
+& $finalize
+if ($script:errors -gt 0) { exit 1 }
+exit 0
diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.py b/.claude/skills/xdto-validate/scripts/xdto-validate.py
new file mode 100644
index 00000000..74030839
--- /dev/null
+++ b/.claude/skills/xdto-validate/scripts/xdto-validate.py
@@ -0,0 +1,386 @@
+# xdto-validate v1.0 — Validate a 1C XDTO package (Python port)
+# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
+import argparse
+import os
+import sys
+
+from lxml import etree
+
+sys.stdout.reconfigure(encoding="utf-8")
+sys.stderr.reconfigure(encoding="utf-8")
+
+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("-PackagePath", "-Path", required=True)
+parser.add_argument("-ConfigDir", default="")
+parser.add_argument("-Detailed", action="store_true")
+parser.add_argument("-MaxErrors", type=int, default=20)
+parser.add_argument("-OutFile", default="")
+args = parser.parse_args()
+
+package_path = os.path.abspath(args.PackagePath)
+detailed = args.Detailed
+max_errors = args.MaxErrors
+out_file = args.OutFile
+
+# ── reporting ────────────────────────────────────────────────
+
+state = {"errors": 0, "warnings": 0, "ok": 0, "stopped": False}
+output = []
+
+
+def out_line(s):
+ output.append(s)
+
+
+def report_ok(msg):
+ state["ok"] += 1
+ if detailed:
+ out_line(f"[OK] {msg}")
+
+
+def report_error(msg):
+ state["errors"] += 1
+ out_line(f"[ERROR] {msg}")
+ if state["errors"] >= max_errors:
+ state["stopped"] = True
+
+
+def report_warn(msg):
+ state["warnings"] += 1
+ out_line(f"[WARN] {msg}")
+
+
+# ── resolve paths ────────────────────────────────────────────
+
+bin_path = None
+md_path = None
+
+if os.path.isfile(package_path):
+ if os.path.basename(package_path) == "Package.bin":
+ bin_path = package_path
+ pkg_dir = os.path.dirname(os.path.dirname(package_path))
+ if os.path.exists(pkg_dir + ".xml"):
+ md_path = pkg_dir + ".xml"
+ elif package_path.endswith(".xml"):
+ md_path = package_path
+ stem = os.path.join(os.path.dirname(package_path),
+ os.path.splitext(os.path.basename(package_path))[0])
+ c = os.path.join(stem, "Ext", "Package.bin")
+ if os.path.exists(c):
+ bin_path = c
+elif os.path.isdir(package_path):
+ c = os.path.join(package_path, "Ext", "Package.bin")
+ if os.path.exists(c):
+ bin_path = c
+ m = package_path.rstrip("\\/") + ".xml"
+ if os.path.exists(m):
+ md_path = m
+
+if not bin_path:
+ print(f"[ERROR] Не найден Ext/Package.bin для пути: {package_path}")
+ sys.exit(1)
+
+file_name = os.path.basename(os.path.dirname(os.path.dirname(bin_path)))
+
+config_dir = args.ConfigDir
+if not config_dir:
+ # .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень
+ config_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(bin_path))))
+
+
+def finalize():
+ checks = state["ok"] + state["errors"] + state["warnings"]
+ if state["errors"] == 0 and state["warnings"] == 0 and not detailed:
+ result = f"=== Validation OK: {file_name} ({checks} checks) ==="
+ else:
+ out_line("")
+ out_line(f"=== Result: {state['errors']} errors, {state['warnings']} warnings ({checks} checks) ===")
+ result = "\n".join(output)
+ print(result)
+ if out_file:
+ with open(out_file, "w", encoding="utf-8-sig", newline="") as f:
+ f.write("\n".join(output))
+
+
+def local(el):
+ return etree.QName(el).localname
+
+
+# ── 1. well-formedness ───────────────────────────────────────
+
+try:
+ doc = etree.parse(bin_path)
+except Exception as e: # noqa: BLE001
+ report_error(f"Package.bin не является корректным XML: {e}")
+ finalize()
+ sys.exit(1)
+
+pkg = doc.getroot()
+if local(pkg) != "package":
+ report_error(f"Ожидался корневой , найден <{local(pkg)}>")
+ finalize()
+ sys.exit(1)
+report_ok("Package.bin: корректный XML, корень ")
+
+target_ns = pkg.get("targetNamespace")
+if not target_ns:
+ report_error("У не задан targetNamespace")
+else:
+ report_ok(f"targetNamespace: {target_ns}")
+
+# ── 2. encoding ──────────────────────────────────────────────
+
+with open(bin_path, "rb") as f:
+ head = f.read(3)
+if head != b"\xef\xbb\xbf":
+ report_warn("Package.bin без BOM UTF-8 — платформа пишет файл с BOM")
+else:
+ report_ok("Кодировка: UTF-8 с BOM")
+
+# ── 3. declarations ──────────────────────────────────────────
+
+imports = []
+local_types = set()
+global_props = set()
+
+for n in pkg:
+ if not isinstance(n.tag, str):
+ continue
+ ln = local(n)
+ if ln == "import":
+ imports.append(n.get("namespace"))
+ elif ln in ("objectType", "valueType"):
+ local_types.add(n.get("name"))
+ elif ln == "property" and n.get("name"):
+ global_props.add(n.get("name"))
+
+# ── 4. duplicate type names ──────────────────────────────────
+
+seen = set()
+for n in pkg:
+ if not isinstance(n.tag, str) or local(n) not in ("objectType", "valueType"):
+ continue
+ nm = n.get("name")
+ if not nm:
+ report_error(f"<{local(n)}> без атрибута name")
+ continue
+ if nm in seen:
+ report_error(f"Дублирующееся имя типа: {nm}")
+ else:
+ seen.add(nm)
+if local_types:
+ report_ok(f"{len(local_types)} тип(ов), имена уникальны")
+
+# ── 5. type references resolve ───────────────────────────────
+
+used_namespaces = set()
+any_type_props = []
+REF_ATTRS = ("type", "base", "ref", "itemType")
+
+
+def resolve_ref(el, attr, raw):
+ if not raw:
+ return
+ if raw.startswith("{"):
+ close = raw.find("}")
+ if close < 0:
+ report_error(f'Некорректная нотация Кларка в {attr}="{raw}"')
+ return
+ ns = raw[1:close]
+ loc = raw[close + 1:]
+ else:
+ parts = raw.split(":")
+ if len(parts) == 2:
+ ns = el.nsmap.get(parts[0])
+ loc = parts[1]
+ if not ns:
+ report_error(f'Префикс "{parts[0]}" не объявлен: {attr}="{raw}" (тип {local(el)})')
+ return
+ else:
+ ns = None
+ loc = parts[0]
+
+ if ns in (XS_NS, XSI_NS):
+ if loc == "anyType":
+ any_type_props.append(el)
+ return
+ if ns == target_ns:
+ if attr == "ref":
+ if loc not in global_props:
+ report_error(f'ref="{raw}" не разрешается: в пакете нет глобального свойства "{loc}"')
+ elif loc not in local_types:
+ report_error(f'{attr}="{raw}" не разрешается: в пакете нет типа "{loc}"')
+ return
+ if ns:
+ used_namespaces.add(ns)
+ if ns not in imports:
+ report_error(f'{attr}="{raw}" ссылается на "{ns}", но не объявлен')
+
+
+node_count = 0
+for el in pkg.iter():
+ if not isinstance(el.tag, str):
+ continue
+ node_count += 1
+ for a in REF_ATTRS:
+ if el.get(a) is not None:
+ resolve_ref(el, a, el.get(a))
+ if el.get("memberTypes"):
+ for m in el.get("memberTypes").split():
+ resolve_ref(el, "memberTypes", m)
+ if state["stopped"]:
+ break
+if not state["stopped"]:
+ report_ok(f"{node_count} узлов: ссылки на типы разрешаются")
+
+if state["stopped"]:
+ finalize()
+ sys.exit(1)
+
+# ── 6. silent degradation to xs:anyType ──────────────────────
+
+if any_type_props and imports:
+ names = [p.get("name") for p in any_type_props if p.get("name")]
+ shown = ", ".join(names[:5])
+ report_warn(
+ f'Свойств с type="xs:anyType": {len(any_type_props)} при объявленных импортах ({shown}). '
+ "Возможна тихая деградация: при импорте XML-схемы платформа заменяет неразрешённый "
+ "чужой тип на anyType без ошибки"
+ )
+
+# ── 7. unused imports ────────────────────────────────────────
+
+for imp in imports:
+ if imp not in used_namespaces:
+ report_warn(f' объявлен, но ни один тип из этого пространства имён не используется')
+if imports and state["warnings"] == 0:
+ report_ok(f"{len(imports)} импорт(ов) — все используются")
+
+# ── 8. nillable on attribute-form properties ─────────────────
+
+nill_attrs = [p.get("name") for p in pkg.iter()
+ if isinstance(p.tag, str) and local(p) == "property"
+ and p.get("form") == "Attribute" and p.get("nillable") == "true"]
+if nill_attrs:
+ report_warn(
+ f'Свойств с nillable="true" и form="Attribute": {len(nill_attrs)} ({", ".join(nill_attrs[:5])}). '
+ "Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет"
+ )
+
+# ── 9. facet consistency ─────────────────────────────────────
+
+facet_checked = 0
+for t in pkg.iter():
+ if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"):
+ continue
+ if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") == "ObjectType":
+ continue
+ facet_checked += 1
+ nm = t.get("name") or "(анонимный тип)"
+
+ if t.get("length") and (t.get("minLength") or t.get("maxLength")):
+ report_error(f"{nm} : length несовместим с minLength/maxLength")
+ min_l, max_l = t.get("minLength"), t.get("maxLength")
+ if min_l and max_l and int(min_l) > int(max_l):
+ report_error(f"{nm} : minLength ({min_l}) больше maxLength ({max_l})")
+ td, fd = t.get("totalDigits"), t.get("fractionDigits")
+ if td and fd and int(fd) > int(td):
+ report_error(f"{nm} : fractionDigits ({fd}) больше totalDigits ({td})")
+ ws = t.get("whiteSpace")
+ if ws and ws not in ("preserve", "replace", "collapse"):
+ report_error(f'{nm} : недопустимое whiteSpace="{ws}"')
+ var = t.get("variety")
+ if var and var not in ("Atomic", "List", "Union"):
+ report_error(f'{nm} : недопустимое variety="{var}"')
+ if var == "List" and t.get("itemType") is None:
+ report_warn(f'{nm} : variety="List" без itemType')
+ if state["stopped"]:
+ break
+if facet_checked and not state["stopped"]:
+ report_ok(f"{facet_checked} простых тип(ов): фасеты согласованы")
+
+if state["stopped"]:
+ finalize()
+ sys.exit(1)
+
+# ── 10. property form / bounds ───────────────────────────────
+
+for p in pkg.iter():
+ if not isinstance(p.tag, str) or local(p) != "property":
+ continue
+ form = p.get("form")
+ if form and form not in ("Element", "Attribute", "Text"):
+ report_error(f'Свойство "{p.get("name")}": недопустимое form="{form}"')
+ ub = p.get("upperBound")
+ if ub and ub != "-1" and int(ub) < 1:
+ report_error(f'Свойство "{p.get("name")}": upperBound="{ub}" (допустимы -1 или число ≥ 1)')
+ lb = p.get("lowerBound")
+ if lb and ub and ub != "-1" and int(lb) > int(ub):
+ report_error(f'Свойство "{p.get("name")}": lowerBound ({lb}) больше upperBound ({ub})')
+ if p.get("name") is None and p.get("ref") is None:
+ report_error("Свойство без name и без ref")
+ if state["stopped"]:
+ break
+if not state["stopped"]:
+ report_ok("Свойства: form и кратности корректны")
+
+# ── 11. metadata object ──────────────────────────────────────
+
+if md_path:
+ md = etree.parse(md_path)
+ md_name = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
+ md_ns = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Namespace")
+ if md_name is None:
+ report_error("В объекте метаданных не задано ")
+ elif (md_name.text or "") != file_name:
+ report_error(f'{md_name.text} не совпадает с именем каталога "{file_name}"')
+ else:
+ report_ok(f"Объект метаданных: Name = {md_name.text}")
+ if md_ns is not None and (md_ns.text or "") != target_ns:
+ report_error(f"{md_ns.text} не совпадает с targetNamespace пакета ({target_ns})")
+ elif md_ns is not None:
+ report_ok("Namespace объекта метаданных совпадает с targetNamespace")
+else:
+ report_warn("Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета")
+
+# ── 12. registration + namespace uniqueness ──────────────────
+
+config_xml = os.path.join(config_dir, "Configuration.xml")
+if os.path.exists(config_xml):
+ cfg = etree.parse(config_xml)
+ registered = any((e.text or "") == file_name
+ for e in cfg.iterfind(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects/{{{MD_NS}}}XDTOPackage"))
+ if registered:
+ report_ok("Зарегистрирован в Configuration.xml")
+ else:
+ report_error(f"{file_name} отсутствует в ChildObjects файла "
+ "Configuration.xml — платформа пакет не увидит")
+
+ pkg_root = os.path.join(config_dir, "XDTOPackages")
+ if os.path.isdir(pkg_root):
+ clash = []
+ for other in sorted(os.listdir(pkg_root)):
+ if other == file_name:
+ continue
+ ob = os.path.join(pkg_root, other, "Ext", "Package.bin")
+ if not os.path.exists(ob):
+ continue
+ try:
+ if etree.parse(ob).getroot().get("targetNamespace") == target_ns:
+ clash.append(other)
+ except Exception: # noqa: BLE001
+ pass
+ if clash:
+ report_error(f'targetNamespace "{target_ns}" уже занят пакет(ами): {", ".join(clash)}. '
+ "Платформа не допускает два пакета с одним пространством имён")
+ else:
+ report_ok("targetNamespace уникален в конфигурации")
+else:
+ report_warn(f"Configuration.xml не найден ({config_dir}) — проверки регистрации и уникальности namespace пропущены")
+
+finalize()
+sys.exit(1 if state["errors"] > 0 else 0)
diff --git a/.gitattributes b/.gitattributes
index cacc4e54..ea34d45c 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -21,3 +21,8 @@
# Бинарники 1С
*.bin binary
+
+# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем
+# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff,
+# иначе изменение модели пакета в истории выглядит как «Binary files differ».
+XDTOPackages/**/Package.bin diff
diff --git a/docs/1c-specs-index.md b/docs/1c-specs-index.md
index 83deaa3c..28164e58 100644
--- a/docs/1c-specs-index.md
+++ b/docs/1c-specs-index.md
@@ -49,7 +49,7 @@
| XML-элемент | Каталог | Русское название | Спецификация |
|-------------|---------|-----------------|--------------|
| `ExchangePlan` | `ExchangePlans/` | Планы обмена | [1c-config-objects-spec.md § 15](1c-config-objects-spec.md#15-планы-обмена-exchangeplans) |
-| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-configuration-spec.md § 6.14](1c-configuration-spec.md#614-xdtopackage--xdto-пакет) |
+| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-xdto-spec.md](1c-xdto-spec.md) |
| `WebService` | `WebServices/` | Веб-сервисы | [1c-config-objects-spec.md § 25](1c-config-objects-spec.md#25-веб-сервисы-webservices) |
| `HTTPService` | `HTTPServices/` | HTTP-сервисы | [1c-config-objects-spec.md § 24](1c-config-objects-spec.md#24-http-сервисы-httpservices) |
| `WSReference` | `WSReferences/` | WS-ссылки | [1c-configuration-spec.md § 6.15](1c-configuration-spec.md#615-wsreference--ws-ссылка) |
diff --git a/docs/1c-xdto-spec.md b/docs/1c-xdto-spec.md
new file mode 100644
index 00000000..d357608a
--- /dev/null
+++ b/docs/1c-xdto-spec.md
@@ -0,0 +1,329 @@
+# Спецификация формата XML пакетов XDTO 1С
+
+Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
+Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.20` (платформа 8.3.27+).
+
+Источники: выгрузки Бухгалтерия предприятия (8.3.24), ERP 2 (8.3.24) — 760 пакетов.
+
+> **Связанные спецификации:**
+> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md)
+> - DSL навыков (XSD как формат описания) — [xdto-dsl-spec.md](xdto-dsl-spec.md)
+> - Сводный индекс — [1c-specs-index.md](1c-specs-index.md)
+
+---
+
+## 1. Структура каталогов
+
+```
+XDTOPackages/
+├── ОбменСБанком.xml # Объект метаданных — 4 свойства, и всё
+├── ОбменСБанком/
+│ └── Ext/
+│ └── Package.bin # Модель пакета
+└── ...
+```
+
+Регистрация в корневом `Configuration.xml`:
+
+```xml
+
+ ...
+ ОбменСБанком
+
+```
+
+Ни форм, ни модулей, ни макетов у пакета XDTO нет.
+
+---
+
+## 2. Объект метаданных `<Имя>.xml`
+
+```xml
+
+
+
+
+ ApdexExport
+
+
+ ru
+ Apdex export
+
+
+
+ www.v8.1c.ru/ssl/performace-assessment/apdexExport
+
+
+
+```
+
+| Свойство | Описание |
+|---|---|
+| `Name` | Имя объекта метаданных. Должно быть валидным идентификатором 1С |
+| `Synonym` | Многоязычное представление |
+| `Comment` | Комментарий |
+| `Namespace` | URI целевого пространства имён. Дублирует `targetNamespace` в `Package.bin` |
+
+Других свойств у пакета XDTO нет.
+
+---
+
+## 3. `Ext/Package.bin` — модель пакета
+
+Несмотря на расширение `.bin`, это **текстовый XML**: UTF-8 с BOM, перевод строки CRLF,
+отступ — символы табуляции.
+
+```xml
+
+
+
+
+
+
+
+
+
+
+```
+
+### 3.1. Вложенность элементов
+
+```
+package > objectType | valueType | property | import
+objectType > property
+valueType > enumeration | pattern | typeDef
+property > typeDef
+typeDef > property | enumeration | pattern
+```
+
+Всего восемь имён элементов. `typeDef` — анонимный (встроенный) тип; его разновидность
+задаёт `xsi:type` = `ValueType` либо `ObjectType`.
+
+**Порядок элементов верхнего уровня обязателен:**
+
+```
+import* → property* → valueType* → objectType*
+```
+
+Ему удовлетворяют все 760 пакетов корпуса. Нарушение порядка платформа не прощает —
+`db-update` падает с «Ошибка преобразования данных XDTO: Чтение объекта типа
+`{http://v8.1c.ru/8.1/xdto}Package`… Проверка свойства 'property'». Это существенно
+при сборке из XML-схемы, где порядок объявлений верхнего уровня произвольный.
+
+### 3.2. ``
+
+| Атрибут | Значения | Описание |
+|---|---|---|
+| `targetNamespace` | URI | Целевое пространство имён |
+| `elementFormQualified` | `true` \| `false` | Умолчание при отсутствии — `true` |
+| `attributeFormQualified` | `true` \| `false` | Умолчание при отсутствии — `false` |
+
+Порядок атрибутов: `targetNamespace`, `elementFormQualified`, `attributeFormQualified`.
+
+### 3.3. ``
+
+`` — объявление зависимости от другого пакета. Разрешается
+по namespace среди пакетов конфигурации; `schemaLocation` в модели XDTO нет.
+
+### 3.4. `` и ``
+
+| Атрибут | Значения | Описание |
+|---|---|---|
+| `name` | идентификатор | Только у именованного `objectType` |
+| `base` | QName | Базовый тип (наследование) |
+| `open` | `true` \| `false` | Допускает произвольные элементы и атрибуты |
+| `abstract` | `true` \| `false` | Абстрактный тип |
+| `mixed` | `true` \| `false` | Смешанное содержимое |
+| `ordered` | `true` \| `false` | `false` — выбор одного из вариантов (аналог `xs:choice`) |
+| `sequenced` | `true` | Последовательное содержимое |
+
+Порядок атрибутов `objectType`: `name`, `base`, `open`, `abstract`, `mixed`, `ordered`, `sequenced`.
+Порядок атрибутов `typeDef`: `xsi:type`, `base`, `mixed`, `open`, `ordered`, `sequenced`, далее атрибуты простого типа.
+
+### 3.5. `` и ``
+
+| Атрибут | Значения |
+|---|---|
+| `name` | идентификатор (только у именованного `valueType`) |
+| `base` | QName базового типа |
+| `variety` | `Atomic` \| `List` \| `Union` |
+| `itemType` | QName — тип элемента списка (`variety="List"`) |
+| `memberTypes` | список типов объединения (`variety="Union"`) |
+
+Фасеты задаются **атрибутами**, а не дочерними элементами:
+`length`, `minLength`, `maxLength`, `totalDigits`, `fractionDigits`,
+`minInclusive`, `maxInclusive`, `minExclusive`, `maxExclusive`,
+`whiteSpace` (`preserve` \| `collapse`).
+
+Базовый тип может задаваться не атрибутом `base`, а вложенным анонимным `typeDef`
+**без** `xsi:type` — соответствует анонимному `xs:simpleType` внутри `xs:restriction`:
+
+```xml
+
+
+ [0-9]{12}
+
+```
+
+Дочерними элементами идут только `` и `` — значение в тексте узла:
+
+```xml
+
+ [0-9]{20}
+
+```
+
+У `` встречается атрибут `xsi:type` (например `xs:string`), указывающий тип литерала.
+
+### 3.6. ``
+
+| Атрибут | Значения | Описание |
+|---|---|---|
+| `name` | идентификатор | Имя свойства |
+| `ref` | QName | Ссылка на глобальное свойство вместо собственного объявления |
+| `type` | QName | Тип свойства. При отсутствии и `type`, и вложенного `typeDef` — произвольный тип |
+| `lowerBound` | `0` \| `1` | Минимальная кратность. Умолчание при отсутствии — `1` |
+| `upperBound` | число \| `-1` | Максимальная кратность; `-1` — неограниченно. Умолчание — `1` |
+| `nillable` | `true` \| `false` | Допускает `xsi:nil` |
+| `fixed` | значение | Фиксированное значение |
+| `default` | значение | Значение по умолчанию |
+| `form` | `Element` \| `Attribute` \| `Text` | Форма представления в XML. Умолчание — `Element` |
+| `localName` | строка | Исходное XML-имя, если оно не является валидным идентификатором 1С |
+| `qualified` | `true` \| `false` | Переопределение `*FormQualified` для конкретного свойства |
+
+Порядок атрибутов: `name`, `ref`, `type`, `lowerBound`, `upperBound`, `nillable`,
+`fixed`, `default`, `form`, `localName`, `qualified`.
+
+**`form="Text"`** — свойство хранит собственное значение элемента (аналог `xs:simpleContent`).
+Такое свойство платформа всегда называет `__content`:
+
+```xml
+
+
+
+
+```
+
+Наличие `form="Text"` не отменяет флагов самого типа — `mixed`, `sequenced` и прочие
+задаются как обычно:
+
+```xml
+
+
+
+
+```
+
+**`localName`** — при импорте XML-схемы имя, недопустимое как идентификатор 1С,
+санируется, а оригинал сохраняется:
+
+```xml
+
+```
+
+### 3.7. Порядок свойств внутри типа
+
+Свойства с `form="Attribute"` обычно идут перед остальными — так записаны 96.5% типов
+корпуса. Оставшиеся 3.5% содержат произвольное чередование; порядок значим и сохраняется
+платформой как есть.
+
+---
+
+## 4. Схема префиксов пространств имён
+
+Каждая ссылка на тип из пространства имён, отличного от `xs:`/`xsi:`, требует объявления
+префикса вида `dNpM`:
+
+```xml
+
+```
+
+- `N` — глубина узла: `package` = 1, его прямые потомки = 2, свойство внутри `objectType` = 3,
+ свойство внутри `typeDef` = 5, глубже — 7, 9 и т.д.
+- `M` — порядковый номер нового пространства имён, объявляемого на этом узле (с 1).
+
+Префикс объявляется **на первом узле, которому он нужен**; потомки переиспользуют его из
+области видимости, а не объявляют заново:
+
+```xml
+
+
+
+```
+
+Целевое пространство имён самого пакета исключением не является — ссылка на собственный
+тип тоже требует локального объявления.
+
+Частоты по корпусу: `d3p1` — 109 738, `d2p1` — 13 108, `d5p1` — 9 581, далее по убыванию до `d16p1`.
+
+### 4.1. Нотация Кларка в `memberTypes`
+
+Значения `memberTypes` записываются нотацией Кларка `{URI}Локальное`, без префикса
+(125 случаев из 135 в корпусе; остальные — обычные префиксные QName):
+
+```xml
+
+```
+
+Обратите внимание: объявление `xmlns:d2p1` присутствует, хотя в значении не используется —
+пространство имён всё равно проходит через общий механизм выделения префиксов. Если оно уже
+объявлено предком, нового объявления не появляется.
+
+Остальные атрибуты-QName (`type`, `base`, `ref`, `itemType`) всегда префиксные.
+
+### 4.2. Осмысленные префиксы
+
+Изредка вместо сгенерированного `dNpM` встречается содержательный префикс — например
+`dcsset` для настроек компоновки данных:
+
+```xml
+
+```
+
+### 4.3. Свойство с `qualified` сериализуется с префиксом
+
+Если у свойства задан `qualified`, платформа пишет и сам тег, и имя атрибута с явным
+префиксом пространства имён модели XDTO (хотя по умолчанию оно и так объявлено как `xmlns=`):
+
+```xml
+
+```
+
+Префикс для XDTO выделяется последним, после префиксов типов. Такое встречается редко
+(16 узлов на весь корпус) и появляется при импорте XML-схемы, где `form` задан на
+объявлении в unqualified-пакете.
+
+---
+
+## 5. Умолчания записываются непоследовательно
+
+Один и тот же смысл в корпусе встречается в двух написаниях: `nillable="false"` присутствует
+явно 6 899 раз, `objectType open="false"` — 6 раз, `abstract="false"` — 2 раза, явное
+`form="Element"` — 44 раза. Пакеты, полученные импортом XML-схемы, и пакеты, созданные
+руками в Конфигураторе, отличаются стилем.
+
+Практическое следствие: приведение пакета к «каноническому» виду меняет байты реальных
+файлов. Инструменты, которым важен точный round-trip, обязаны сохранять literal-форму.
+
+---
+
+## 6. Тихая деградация в `xs:anyType`
+
+При импорте XML-схемы через Конфигуратор тип из чужого пространства имён, для которого
+в конфигурации нет соответствующего пакета, **молча** заменяется на `xs:anyType` —
+без ошибки и без предупреждения:
+
+```xml
+
+
+```
+
+При этом `` в пакете остаётся. Признак «объявлен импорт, но
+ни один тип из этого пространства имён не используется» — надёжный индикатор проблемы.
diff --git a/docs/xdto-dsl-spec.md b/docs/xdto-dsl-spec.md
new file mode 100644
index 00000000..016dab7a
--- /dev/null
+++ b/docs/xdto-dsl-spec.md
@@ -0,0 +1,173 @@
+# Спецификация XDTO DSL — XML Schema как формат описания пакета
+
+Навыки `/xdto-compile` (XSD → пакет) и `/xdto-decompile` (пакет → XSD) используют в качестве
+формата описания **обычную XML-схему**. Отдельного DSL нет: XSD и модель XDTO выражают одно и
+то же, различаясь синтаксисом и умолчаниями, а схема в реальных задачах обычно уже есть —
+прислана контрагентом.
+
+> Формат самих исходников (`Package.bin`, объект метаданных, схема префиксов) —
+> [1c-xdto-spec.md](1c-xdto-spec.md).
+
+## Пример
+
+```xml
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## 1. Таблица соответствий
+
+| XML Schema | Модель XDTO |
+|---|---|
+| `xs:schema/@targetNamespace` | `package/@targetNamespace` |
+| `elementFormDefault="qualified"` | `elementFormQualified="true"` |
+| `attributeFormDefault="qualified"` | `attributeFormQualified="true"` |
+| `xs:import/@namespace` | `import/@namespace` |
+| `xs:complexType` | `objectType` |
+| `xs:simpleType` | `valueType` |
+| `xs:element` (глобальный) | `property` в `package` |
+| `xs:attribute` (глобальный) | `property form="Attribute"` в `package` |
+| `xs:element` (локальный) | `property` |
+| `xs:attribute` (локальный) | `property form="Attribute"` |
+| `@minOccurs` | `@lowerBound` |
+| `@maxOccurs="unbounded"` | `@upperBound="-1"` |
+| `@nillable`, `@default`, `@fixed` | те же имена |
+| `xs:element/@ref` | `property/@ref` |
+| анонимный `xs:simpleType` в объявлении | `typeDef xsi:type="ValueType"` |
+| анонимный `xs:complexType` в объявлении | `typeDef xsi:type="ObjectType"` |
+| `xs:complexContent/xs:extension/@base` | `objectType/@base` |
+| `@abstract="true"`, `@mixed="true"` | те же имена |
+| `xs:choice` | `ordered="false"` |
+| `xs:sequence` | порядок по умолчанию |
+| `xs:any` + `xs:anyAttribute` | `open="true"` |
+| `xs:simpleContent/xs:extension/@base` | свойство `__content` с `form="Text"` |
+| `xs:restriction/@base` + дочерние фасеты | `@base` + фасеты **атрибутами** |
+| `xs:pattern`, `xs:enumeration` | ``, `` — значение в тексте узла |
+| `xs:list/@itemType` | `variety="List"` + `@itemType` |
+| `xs:union/@memberTypes` | `variety="Union"` + `@memberTypes` |
+
+Ловушки, на которых модель ошибается чаще всего:
+
+- **Кратность инвертирована по смыслу**: `lowerBound="0"` = необязательный, `upperBound="-1"` = неограниченный.
+- **Фасеты в XDTO — атрибуты**, а не дочерние элементы: `maxLength="30"`, не ``.
+- **Каждая ссылка на тип требует локального объявления префикса** `dNpM` — в том числе на
+ собственный `targetNamespace`.
+
+Всё это делает компилятор; писать вручную ничего из перечисленного не нужно.
+
+---
+
+## 2. Аннотации `xdto:`
+
+Две вещи XDTO выражает, а XML Schema — нет:
+
+- `nillable` у атрибута (спецификация XSD допускает его только у элементов);
+- `qualified` у отдельного свойства.
+
+Плюс XSD не различает «атрибут записан явно» и «атрибут опущен, действует умолчание»,
+хотя в реальных пакетах встречаются оба написания одного смысла.
+
+Такие случаи описываются атрибутами из пространства имён самой модели XDTO —
+`http://v8.1c.ru/8.1/xdto`. Правило одно:
+
+> **Чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в `Package.bin`.**
+
+```xml
+
+```
+
+Такая схема остаётся валидной: XML Schema разрешает атрибуты из чужих пространств имён
+на объявлениях (`anyAttribute namespace="##other"` в схеме схем). Валидаторы их игнорируют.
+
+Аннотации строго опциональны — подавляющее большинство схем обходится без единой.
+
+| Аннотация | Где | Назначение |
+|---|---|---|
+| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута |
+| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута |
+| `xdto:qualified` | объявление | переопределение `*FormQualified` |
+| `xdto:form` | `xs:element` | записать `form` явно (например `form="Element"`) |
+| `xdto:name` | объявление | имя свойства, если XML-имя не является идентификатором 1С; тогда XML-имя уходит в `localName` |
+| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать `variety` явно |
+| `xdto:memberTypesForm="prefixed"` | `xs:union` | писать `memberTypes` префиксами, а не нотацией Кларка |
+| `xdto:open`, `xdto:abstract`, `xdto:mixed`, `xdto:ordered`, `xdto:sequenced` | `xs:complexType` | значения, не выводимые из модели содержимого |
+| `xdto:order` | `xs:complexType` | исходный порядок свойств, если он не «атрибуты первыми»; имена через `\|` |
+| `xdto:textName`, `xdto:textlowerBound`, `xdto:textupperBound`, `xdto:textnillable` | `xs:extension` в `xs:simpleContent` | параметры свойства `form="Text"`, если оно названо не `__content` |
+| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно |
+| `xdto:type` | `xs:enumeration` | `xsi:type` литерала перечисления |
+| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого `dNpM` (например `dcsset`) |
+
+### Что выводится само
+
+Аннотация нужна только там, где вывод невозможен:
+
+| Свойство XDTO | Выводится из |
+|---|---|
+| `open="true"` | наличие `xs:any` / `xs:anyAttribute` |
+| `ordered="false"` | `xs:choice` вместо `xs:sequence` |
+| `abstract`, `mixed` | одноимённые атрибуты `xs:complexType` |
+| `variety="List"` / `"Union"` | `xs:list` / `xs:union` |
+| порядок свойств | атрибуты первыми, затем остальные |
+
+`sequenced` из XSD не выводится (в корпусе он не коррелирует однозначно ни с одной
+конструкцией) и всегда приходит аннотацией.
+
+---
+
+## 3. Свойства объекта метаданных
+
+`Name`, `Synonym` и `Comment` живут в `xs:annotation/xs:appinfo` — штатном месте XML Schema
+для инструментальных метаданных. `Namespace` не дублируется: его единственный источник —
+`targetNamespace`.
+
+```xml
+
+
+
+ ОбменСБанком
+ Обмен с банком
+ Формат 1С:Предприятие — Клиент банка
+
+
+
+```
+
+Блок опционален. Параметры `-Name`, `-Synonym`, `-Comment` навыка `/xdto-compile`
+имеют приоритет над ним. Без того и другого имя берётся из имени файла XSD.
+
+Благодаря этому блоку пара `/xdto-decompile` → `/xdto-compile` замыкается без потерь,
+включая свойства объекта метаданных.
+
+---
+
+## 4. Прощающий ввод
+
+Компилятор принимает и не вполне канонические схемы:
+
+- **Имя типа без префикса** (`type="Платёж"`) трактуется как тип целевого пространства имён.
+- **`form="qualified"` на глобальном объявлении** — так экспортирует XML-схему сам
+ Конфигуратор, хотя спецификация XSD допускает `form` только у локальных объявлений.
+ Такие файлы читаются; сам навык пишет корректно.
+- **`xs:include`** игнорируется (в модели XDTO соответствия нет).
+- Порядок объявлений верхнего уровня произвольный и сохраняется как есть.
diff --git a/tests/skills/cases/xdto-compile/_skill.json b/tests/skills/cases/xdto-compile/_skill.json
new file mode 100644
index 00000000..3e242609
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/_skill.json
@@ -0,0 +1,17 @@
+{
+ "script": "xdto-compile/scripts/xdto-compile",
+ "setup": "empty-config",
+ "args": [
+ { "flag": "-XsdPath", "from": "workPath", "field": "xsdFile" },
+ { "flag": "-OutputDir", "from": "workDir" }
+ ],
+ "snapshot": {
+ "root": "workDir",
+ "normalizeUuids": true
+ },
+ "postValidate": {
+ "script": "xdto-validate/scripts/xdto-validate",
+ "flag": "-PackagePath",
+ "pathFrom": "validatePath"
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/annotations.json b/tests/skills/cases/xdto-compile/annotations.json
new file mode 100644
index 00000000..afac2c6e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/annotations.json
@@ -0,0 +1,9 @@
+{
+ "name": "xdto:-аннотации и свойства метаданного через xs:appinfo",
+ "caseFiles": ["annotations.xsd"],
+ "params": { "xsdFile": "annotations.xsd" },
+ "validatePath": "XDTOPackages/ПакетСАннотациями",
+ "expect": {
+ "files": ["XDTOPackages/ПакетСАннотациями.xml", "XDTOPackages/ПакетСАннотациями/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/annotations.xsd b/tests/skills/cases/xdto-compile/annotations.xsd
new file mode 100644
index 00000000..346709ed
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/annotations.xsd
@@ -0,0 +1,28 @@
+
+
+
+
+ ПакетСАннотациями
+ Пакет с аннотациями
+ Annotated package
+ Проверка xs:appinfo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/anon-and-content.json b/tests/skills/cases/xdto-compile/anon-and-content.json
new file mode 100644
index 00000000..84d647b7
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/anon-and-content.json
@@ -0,0 +1,9 @@
+{
+ "name": "анонимные simple/complex типы и xs:simpleContent",
+ "caseFiles": ["anon-and-content.xsd"],
+ "params": { "xsdFile": "anon-and-content.xsd" },
+ "validatePath": "XDTOPackages/anon_and_content",
+ "expect": {
+ "files": ["XDTOPackages/anon_and_content.xml", "XDTOPackages/anon_and_content/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/anon-and-content.xsd b/tests/skills/cases/xdto-compile/anon-and-content.xsd
new file mode 100644
index 00000000..71f80913
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/anon-and-content.xsd
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/cardinality.json b/tests/skills/cases/xdto-compile/cardinality.json
new file mode 100644
index 00000000..a948992e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/cardinality.json
@@ -0,0 +1,9 @@
+{
+ "name": "кратности, nillable, default/fixed, атрибуты, свойство без типа",
+ "caseFiles": ["cardinality.xsd"],
+ "params": { "xsdFile": "cardinality.xsd" },
+ "validatePath": "XDTOPackages/cardinality",
+ "expect": {
+ "files": ["XDTOPackages/cardinality.xml", "XDTOPackages/cardinality/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/cardinality.xsd b/tests/skills/cases/xdto-compile/cardinality.xsd
new file mode 100644
index 00000000..fc82497e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/cardinality.xsd
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/complex-types.json b/tests/skills/cases/xdto-compile/complex-types.json
new file mode 100644
index 00000000..c5e5cece
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/complex-types.json
@@ -0,0 +1,9 @@
+{
+ "name": "наследование, abstract, choice, open (any+anyAttribute), mixed",
+ "caseFiles": ["complex-types.xsd"],
+ "params": { "xsdFile": "complex-types.xsd" },
+ "validatePath": "XDTOPackages/complex_types",
+ "expect": {
+ "files": ["XDTOPackages/complex_types.xml", "XDTOPackages/complex_types/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/complex-types.xsd b/tests/skills/cases/xdto-compile/complex-types.xsd
new file mode 100644
index 00000000..f54435a3
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/complex-types.xsd
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/minimal.json b/tests/skills/cases/xdto-compile/minimal.json
new file mode 100644
index 00000000..81a07ea2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/minimal.json
@@ -0,0 +1,9 @@
+{
+ "name": "минимальный пакет: один objectType, два свойства",
+ "caseFiles": ["minimal.xsd"],
+ "params": { "xsdFile": "minimal.xsd" },
+ "validatePath": "XDTOPackages/minimal",
+ "expect": {
+ "files": ["XDTOPackages/minimal.xml", "XDTOPackages/minimal/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/minimal.xsd b/tests/skills/cases/xdto-compile/minimal.xsd
new file mode 100644
index 00000000..fe77cf12
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/minimal.xsd
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/refs.json b/tests/skills/cases/xdto-compile/refs.json
new file mode 100644
index 00000000..74c069b5
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/refs.json
@@ -0,0 +1,9 @@
+{
+ "name": "ссылки на типы: свой ns (dNpN), голое имя, чужой ns с импортом, ref",
+ "caseFiles": ["refs.xsd"],
+ "params": { "xsdFile": "refs.xsd" },
+ "validatePath": "XDTOPackages/refs",
+ "expect": {
+ "files": ["XDTOPackages/refs.xml", "XDTOPackages/refs/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/refs.xsd b/tests/skills/cases/xdto-compile/refs.xsd
new file mode 100644
index 00000000..a16e72d0
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/refs.xsd
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/simple-types.json b/tests/skills/cases/xdto-compile/simple-types.json
new file mode 100644
index 00000000..cdc26e56
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/simple-types.json
@@ -0,0 +1,9 @@
+{
+ "name": "фасеты, pattern, enumeration, list, union",
+ "caseFiles": ["simple-types.xsd"],
+ "params": { "xsdFile": "simple-types.xsd" },
+ "validatePath": "XDTOPackages/simple_types",
+ "expect": {
+ "files": ["XDTOPackages/simple_types.xml", "XDTOPackages/simple_types/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/simple-types.xsd b/tests/skills/cases/xdto-compile/simple-types.xsd
new file mode 100644
index 00000000..71ca76cf
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/simple-types.xsd
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml
new file mode 100644
index 00000000..96a8531d
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ ПакетСАннотациями
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml
new file mode 100644
index 00000000..9f7fd64e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ ПакетСАннотациями
+
+
+ ru
+ Пакет с аннотациями
+
+
+ en
+ Annotated package
+
+
+ Проверка xs:appinfo
+ urn:test:annotations
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin
new file mode 100644
index 00000000..3540686c
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd b/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd
new file mode 100644
index 00000000..346709ed
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd
@@ -0,0 +1,28 @@
+
+
+
+
+ ПакетСАннотациями
+ Пакет с аннотациями
+ Annotated package
+ Проверка xs:appinfo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml
new file mode 100644
index 00000000..2330f34e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ anon_and_content
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml
new file mode 100644
index 00000000..1a74b7e4
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ anon_and_content
+
+
+ ru
+ anon_and_content
+
+
+
+ urn:test:anon
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin
new file mode 100644
index 00000000..f0f7d6ce
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd
new file mode 100644
index 00000000..71f80913
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml
new file mode 100644
index 00000000..190eb88a
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ cardinality
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml
new file mode 100644
index 00000000..e1e93bd2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ cardinality
+
+
+ ru
+ cardinality
+
+
+
+ urn:test:cardinality
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin
new file mode 100644
index 00000000..3cb2e162
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd b/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd
new file mode 100644
index 00000000..fc82497e
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml
new file mode 100644
index 00000000..963a4d32
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ complex_types
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml
new file mode 100644
index 00000000..51808fde
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ complex_types
+
+
+ ru
+ complex_types
+
+
+
+ urn:test:complex
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin
new file mode 100644
index 00000000..d70504cb
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd b/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd
new file mode 100644
index 00000000..f54435a3
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml
new file mode 100644
index 00000000..f32bf316
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ minimal
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml
new file mode 100644
index 00000000..c603340a
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ minimal
+
+
+ ru
+ minimal
+
+
+
+ urn:test:minimal
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin
new file mode 100644
index 00000000..54b87db8
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd b/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd
new file mode 100644
index 00000000..fe77cf12
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml
new file mode 100644
index 00000000..e37817d2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ refs
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml
new file mode 100644
index 00000000..8dfec905
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ refs
+
+
+ ru
+ refs
+
+
+
+ urn:test:refs
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin
new file mode 100644
index 00000000..cbe9e8eb
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd b/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd
new file mode 100644
index 00000000..a16e72d0
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml
new file mode 100644
index 00000000..931d80c4
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ simple_types
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml
new file mode 100644
index 00000000..220d7da4
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ simple_types
+
+
+ ru
+ simple_types
+
+
+
+ urn:test:simple
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin
new file mode 100644
index 00000000..2de23a8f
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin
@@ -0,0 +1,13 @@
+
+
+ [0-9]{20}
+
+
+ 1.05
+ 1.06
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd b/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd
new file mode 100644
index 00000000..71ca76cf
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml
new file mode 100644
index 00000000..bf0d487b
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ top_level_order
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml
new file mode 100644
index 00000000..42d3c27d
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ top_level_order
+
+
+ ru
+ top_level_order
+
+
+
+ urn:test:order
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin
new file mode 100644
index 00000000..2d533b96
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd b/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd
new file mode 100644
index 00000000..075f1b95
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-compile/top-level-order.json b/tests/skills/cases/xdto-compile/top-level-order.json
new file mode 100644
index 00000000..81325940
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/top-level-order.json
@@ -0,0 +1,9 @@
+{
+ "name": "порядок верхнего уровня: import→property→valueType→objectType независимо от порядка в XSD",
+ "caseFiles": ["top-level-order.xsd"],
+ "params": { "xsdFile": "top-level-order.xsd" },
+ "validatePath": "XDTOPackages/top_level_order",
+ "expect": {
+ "files": ["XDTOPackages/top_level_order/Ext/Package.bin"]
+ }
+}
diff --git a/tests/skills/cases/xdto-compile/top-level-order.xsd b/tests/skills/cases/xdto-compile/top-level-order.xsd
new file mode 100644
index 00000000..075f1b95
--- /dev/null
+++ b/tests/skills/cases/xdto-compile/top-level-order.xsd
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/_skill.json b/tests/skills/cases/xdto-decompile/_skill.json
new file mode 100644
index 00000000..aee894df
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/_skill.json
@@ -0,0 +1,12 @@
+{
+ "script": "xdto-decompile/scripts/xdto-decompile",
+ "setup": "empty-config",
+ "args": [
+ { "flag": "-PackagePath", "from": "workPath", "field": "packagePath" },
+ { "flag": "-OutFile", "from": "workPath", "field": "outFile" }
+ ],
+ "snapshot": {
+ "root": "workDir",
+ "normalizeUuids": true
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/annotations.json b/tests/skills/cases/xdto-decompile/annotations.json
new file mode 100644
index 00000000..46a4d634
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/annotations.json
@@ -0,0 +1,14 @@
+{
+ "name": "round-trip: xdto:-аннотации и свойства метаданного восстанавливаются",
+ "caseFiles": ["annotations.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/annotations.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/ПакетСАннотациями", "outFile": "roundtrip.xsd" },
+ "expect": {
+ "files": ["roundtrip.xsd"]
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/annotations.xsd b/tests/skills/cases/xdto-decompile/annotations.xsd
new file mode 100644
index 00000000..346709ed
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/annotations.xsd
@@ -0,0 +1,28 @@
+
+
+
+
+ ПакетСАннотациями
+ Пакет с аннотациями
+ Annotated package
+ Проверка xs:appinfo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/anon-and-content.json b/tests/skills/cases/xdto-decompile/anon-and-content.json
new file mode 100644
index 00000000..2f3974ee
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/anon-and-content.json
@@ -0,0 +1,14 @@
+{
+ "name": "round-trip: анонимные типы и simpleContent",
+ "caseFiles": ["anon-and-content.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/anon-and-content.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/anon_and_content", "outFile": "roundtrip.xsd" },
+ "expect": {
+ "files": ["roundtrip.xsd"]
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/anon-and-content.xsd b/tests/skills/cases/xdto-decompile/anon-and-content.xsd
new file mode 100644
index 00000000..71f80913
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/anon-and-content.xsd
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/complex-types.json b/tests/skills/cases/xdto-decompile/complex-types.json
new file mode 100644
index 00000000..c504729b
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/complex-types.json
@@ -0,0 +1,14 @@
+{
+ "name": "round-trip: наследование, choice, open, mixed",
+ "caseFiles": ["complex-types.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/complex-types.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/complex_types", "outFile": "roundtrip.xsd" },
+ "expect": {
+ "files": ["roundtrip.xsd"]
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/complex-types.xsd b/tests/skills/cases/xdto-decompile/complex-types.xsd
new file mode 100644
index 00000000..f54435a3
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/complex-types.xsd
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/minimal.json b/tests/skills/cases/xdto-decompile/minimal.json
new file mode 100644
index 00000000..067e6ae5
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/minimal.json
@@ -0,0 +1,14 @@
+{
+ "name": "round-trip минимального пакета через пару навыков",
+ "caseFiles": ["minimal.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/minimal.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/minimal", "outFile": "roundtrip.xsd" },
+ "expect": {
+ "files": ["roundtrip.xsd"]
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/minimal.xsd b/tests/skills/cases/xdto-decompile/minimal.xsd
new file mode 100644
index 00000000..fe77cf12
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/minimal.xsd
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/simple-types.json b/tests/skills/cases/xdto-decompile/simple-types.json
new file mode 100644
index 00000000..b310804c
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/simple-types.json
@@ -0,0 +1,14 @@
+{
+ "name": "round-trip: фасеты, enumeration, list, union",
+ "caseFiles": ["simple-types.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/simple-types.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/simple_types", "outFile": "roundtrip.xsd" },
+ "expect": {
+ "files": ["roundtrip.xsd"]
+ }
+}
diff --git a/tests/skills/cases/xdto-decompile/simple-types.xsd b/tests/skills/cases/xdto-decompile/simple-types.xsd
new file mode 100644
index 00000000..71ca76cf
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/simple-types.xsd
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml
new file mode 100644
index 00000000..96a8531d
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ ПакетСАннотациями
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml
new file mode 100644
index 00000000..9f7fd64e
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml
@@ -0,0 +1,20 @@
+
+
+
+
+ ПакетСАннотациями
+
+
+ ru
+ Пакет с аннотациями
+
+
+ en
+ Annotated package
+
+
+ Проверка xs:appinfo
+ urn:test:annotations
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin
new file mode 100644
index 00000000..3540686c
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd b/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd
new file mode 100644
index 00000000..346709ed
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd
@@ -0,0 +1,28 @@
+
+
+
+
+ ПакетСАннотациями
+ Пакет с аннотациями
+ Annotated package
+ Проверка xs:appinfo
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd
new file mode 100644
index 00000000..017d000f
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd
@@ -0,0 +1,25 @@
+
+
+
+
+ ПакетСАннотациями
+ Проверка xs:appinfo
+ Пакет с аннотациями
+ Annotated package
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml
new file mode 100644
index 00000000..2330f34e
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ anon_and_content
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml
new file mode 100644
index 00000000..1a74b7e4
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ anon_and_content
+
+
+ ru
+ anon_and_content
+
+
+
+ urn:test:anon
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin
new file mode 100644
index 00000000..f0f7d6ce
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd
new file mode 100644
index 00000000..71f80913
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd
new file mode 100644
index 00000000..45b7a5b6
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd
@@ -0,0 +1,38 @@
+
+
+
+
+ anon_and_content
+ anon_and_content
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml
new file mode 100644
index 00000000..963a4d32
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ complex_types
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml
new file mode 100644
index 00000000..51808fde
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ complex_types
+
+
+ ru
+ complex_types
+
+
+
+ urn:test:complex
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin
new file mode 100644
index 00000000..d70504cb
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd b/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd
new file mode 100644
index 00000000..f54435a3
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd
new file mode 100644
index 00000000..7eb9a5cd
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd
@@ -0,0 +1,46 @@
+
+
+
+
+ complex_types
+ complex_types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml
new file mode 100644
index 00000000..f32bf316
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ minimal
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml
new file mode 100644
index 00000000..c603340a
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ minimal
+
+
+ ru
+ minimal
+
+
+
+ urn:test:minimal
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin
new file mode 100644
index 00000000..54b87db8
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd b/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd
new file mode 100644
index 00000000..fe77cf12
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd
new file mode 100644
index 00000000..0607fc58
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd
@@ -0,0 +1,16 @@
+
+
+
+
+ minimal
+ minimal
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml
new file mode 100644
index 00000000..931d80c4
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ simple_types
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml
new file mode 100644
index 00000000..220d7da4
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ simple_types
+
+
+ ru
+ simple_types
+
+
+
+ urn:test:simple
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin
new file mode 100644
index 00000000..2de23a8f
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin
@@ -0,0 +1,13 @@
+
+
+ [0-9]{20}
+
+
+ 1.05
+ 1.06
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd
new file mode 100644
index 00000000..0899371f
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd
@@ -0,0 +1,41 @@
+
+
+
+
+ simple_types
+ simple_types
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd b/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd
new file mode 100644
index 00000000..71ca76cf
--- /dev/null
+++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-validate/_skill.json b/tests/skills/cases/xdto-validate/_skill.json
new file mode 100644
index 00000000..ba1c18b3
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/_skill.json
@@ -0,0 +1,11 @@
+{
+ "script": "xdto-validate/scripts/xdto-validate",
+ "setup": "empty-config",
+ "args": [
+ { "flag": "-PackagePath", "from": "workPath", "field": "packagePath" }
+ ],
+ "snapshot": {
+ "root": "workDir",
+ "normalizeUuids": true
+ }
+}
diff --git a/tests/skills/cases/xdto-validate/facet-conflicts.json b/tests/skills/cases/xdto-validate/facet-conflicts.json
new file mode 100644
index 00000000..95b15286
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/facet-conflicts.json
@@ -0,0 +1,17 @@
+{
+ "name": "несогласованные фасеты: length с min/maxLength, fractionDigits > totalDigits",
+ "caseFiles": [
+ "pkg-facets/XDTOPackages/facets.xml",
+ "pkg-facets/XDTOPackages/facets/Ext/Package.bin"
+ ],
+ "params": { "packagePath": "pkg-facets/XDTOPackages/facets" },
+ "expectError": true,
+ "expect": {
+ "stdoutContains": [
+ "length несовместим с minLength/maxLength",
+ "minLength (3) больше maxLength (2)",
+ "fractionDigits (9) больше totalDigits (5)"
+ ]
+ },
+ "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
+}
diff --git a/tests/skills/cases/xdto-validate/nillable-on-attribute.json b/tests/skills/cases/xdto-validate/nillable-on-attribute.json
new file mode 100644
index 00000000..ecd88723
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/nillable-on-attribute.json
@@ -0,0 +1,12 @@
+{
+ "name": "nillable у свойства-атрибута — не переживёт экспорт XML-схемы",
+ "caseFiles": [
+ "pkg-nillattr/XDTOPackages/nillattr.xml",
+ "pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin"
+ ],
+ "params": { "packagePath": "pkg-nillattr/XDTOPackages/nillattr" },
+ "expect": {
+ "stdoutContains": ["nillable", "form=\"Attribute\""]
+ },
+ "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
+}
diff --git a/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml
new file mode 100644
index 00000000..e8386de4
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ anytype
+
+
+ ru
+ anytype
+
+
+
+ urn:test:anytype
+
+
+
diff --git a/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin
new file mode 100644
index 00000000..1c093d6f
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml
new file mode 100644
index 00000000..2b781f30
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ facets
+
+
+ ru
+ facets
+
+
+
+ urn:test:facets
+
+
+
diff --git a/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin
new file mode 100644
index 00000000..a26f93c3
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml
new file mode 100644
index 00000000..f650ac21
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ nillattr
+
+
+ ru
+ nillattr
+
+
+
+ urn:test:nillattr
+
+
+
diff --git a/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin
new file mode 100644
index 00000000..b46d8724
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml
new file mode 100644
index 00000000..e5f7317e
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ unresolved
+
+
+ ru
+ unresolved
+
+
+
+ urn:test:unresolved
+
+
+
diff --git a/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin
new file mode 100644
index 00000000..ea92d1a9
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-validate/silent-anytype.json b/tests/skills/cases/xdto-validate/silent-anytype.json
new file mode 100644
index 00000000..bc1eef90
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/silent-anytype.json
@@ -0,0 +1,15 @@
+{
+ "name": "тихая деградация: xs:anyType при объявленном import",
+ "caseFiles": [
+ "pkg-anytype/XDTOPackages/anytype.xml",
+ "pkg-anytype/XDTOPackages/anytype/Ext/Package.bin"
+ ],
+ "params": { "packagePath": "pkg-anytype/XDTOPackages/anytype" },
+ "expect": {
+ "stdoutContains": [
+ "тихая деградация",
+ "ни один тип из этого пространства имён не используется"
+ ]
+ },
+ "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
+}
diff --git a/tests/skills/cases/xdto-validate/unresolved-types.json b/tests/skills/cases/xdto-validate/unresolved-types.json
new file mode 100644
index 00000000..d5542509
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/unresolved-types.json
@@ -0,0 +1,16 @@
+{
+ "name": "неразрешимые ссылки: нет локального типа и нет import под чужой ns",
+ "caseFiles": [
+ "pkg-unresolved/XDTOPackages/unresolved.xml",
+ "pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin"
+ ],
+ "params": { "packagePath": "pkg-unresolved/XDTOPackages/unresolved" },
+ "expectError": true,
+ "expect": {
+ "stdoutContains": [
+ "не разрешается: в пакете нет типа \"НетТакогоТипа\"",
+ "не объявлен"
+ ]
+ },
+ "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
+}
diff --git a/tests/skills/cases/xdto-validate/valid-package.json b/tests/skills/cases/xdto-validate/valid-package.json
new file mode 100644
index 00000000..cd58f91b
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/valid-package.json
@@ -0,0 +1,15 @@
+{
+ "name": "корректный пакет, собранный xdto-compile — ошибок нет",
+ "caseFiles": ["valid.xsd"],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": { "-XsdPath": "{workDir}/valid.xsd", "-OutputDir": "{workDir}" }
+ }
+ ],
+ "params": { "packagePath": "XDTOPackages/valid" },
+ "expect": {
+ "stdoutContains": ["Validation OK"]
+ },
+ "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
+}
diff --git a/tests/skills/cases/xdto-validate/valid.xsd b/tests/skills/cases/xdto-validate/valid.xsd
new file mode 100644
index 00000000..fe158180
--- /dev/null
+++ b/tests/skills/cases/xdto-validate/valid.xsd
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs
index f1b30c84..0a1cf73d 100644
--- a/tests/skills/runner.mjs
+++ b/tests/skills/runner.mjs
@@ -603,6 +603,7 @@ async function runCaseAsync(testCase, opts) {
const isExternal = typeof setupName === 'string' && setupName.startsWith('external:');
workspace = createWorkspace(fixturePath, isExternal);
workDir = workspace.path;
+ copyCaseFiles(caseData, workDir, skillCasesDir);
// Pre-run steps
if (caseData.preRun) {
@@ -742,6 +743,22 @@ async function runCaseAsync(testCase, opts) {
}
}
+// Скопировать файлы из каталога кейса в workDir — для навыков, вход которых
+// не JSON, а файл (например XSD для xdto-compile). Так эталонные входы остаются
+// читаемыми файлами, а не строками внутри JSON кейса.
+function copyCaseFiles(caseData, workDir, skillCasesDir) {
+ if (!caseData.caseFiles) return;
+ for (const rel of caseData.caseFiles) {
+ const src = join(skillCasesDir, rel);
+ if (!existsSync(src)) throw new Error(`caseFiles: файл не найден: ${src}`);
+ // Путь со слэшем сохраняет структуру каталогов (напр. дерево пакета),
+ // простое имя кладётся в корень workDir
+ const dst = rel.includes('/') ? join(workDir, rel) : join(workDir, basename(rel));
+ mkdirSync(dirname(dst), { recursive: true });
+ copyFileSync(src, dst);
+ }
+}
+
function runCase(testCase, opts) {
const { skillConfig, caseData, snapshotDir } = testCase;
const t0 = performance.now();
@@ -769,6 +786,7 @@ function runCase(testCase, opts) {
const isExternal = typeof setupName === 'string' && setupName.startsWith('external:');
workspace = createWorkspace(fixturePath, isExternal);
workDir = workspace.path;
+ copyCaseFiles(caseData, workDir, skillCasesDir);
// 2. Pre-run steps (setup prerequisites like creating objects)
if (caseData.preRun) {