fix(xdto-compile,xdto-validate): не терять конструкции молча; вычитать инструкции

xdto-compile терял свойства без единого слова: на реалистичной чужой схеме
из шести объявленных доезжало одно. Вложенные xs:sequence/xs:choice теперь
уплощаются (модель хранит плоский список), xs:all трактуется как
последовательность, xs:group и xs:attributeGroup раскрываются по ссылке —
и о каждом приближении навык пишет предупреждение. Молчаливая потеря — тот же
класс дефекта, что мы ловим у платформы, лечится так же: сообщением, не отказом.

xdto-validate получил проверки на грабли, найденные при разработке: порядок
элементов верхнего уровня (платформа отвергает пакет, не называя причины),
конфликты объявлений (name+ref, type+вложенный тип, тип без разновидности),
несовпадение рода базового типа, дубли имён свойств.

Новые правила прогнаны по всем 760 пакетам выгрузок: всё, что породила
платформа, валидно по определению, поэтому каждая ошибка там — ошибка правила.
Первый прогон дал 7, и все три класса оказались реальным поведением платформы:
length вместе с minLength/maxLength встречается, два пакета делят один
targetNamespace (Envelope и SOAP_Envelope_1_1 в БП), form="Text" называется
не только __content. Правила понижены до предупреждений либо сняты. Заодно
убран шум: предупреждение о неиспользуемом import срабатывало на четверти
корпуса — теперь только вместе с anyType, где оно и означает проблему.
Итог: 0 ошибок на корпусе, предупреждений 53 вместо 242.

Инструкции переписаны под читателя-исполнителя: убраны детали реализации
и наши мерки, каталог проверок валидатора (его вывод самодостаточен),
локальные пути в примерах заменены нейтральными. Таблица соответствий
XSD и справочник аннотаций вынесены в xdto-compile/xsd-reference.md.

Round-trip 760/760 сохранён, паритет PS/PY сохранён.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-07-25 21:48:48 +03:00
co-authored by Claude Opus 5
parent d05aef54b4
commit 7ca6dfa6b2
24 changed files with 1156 additions and 132 deletions
+51 -33
View File
@@ -1,7 +1,7 @@
---
name: xdto-compile
description: Создание пакета XDTO 1С из XML-схемы (XSD). Используй когда нужно добавить пакет XDTO в конфигурацию — для обмена, интеграции, веб-сервиса, разбора внешнего XML-формата
argument-hint: -XsdPath <файл.xsd> -OutputDir <каталог-конфигурации> [-Name <имя>] [-Synonym <синоним>] [-Comment <текст>] [-Force]
argument-hint: -XsdPath <файл.xsd> -OutputDir <каталог-исходников> [-Name <имя>] [-Synonym <синоним>] [-Comment <текст>] [-Force]
allowed-tools:
- Bash
- Read
@@ -10,12 +10,11 @@ allowed-tools:
# /xdto-compile — Создание пакета XDTO из XML-схемы
Собирает пакет XDTO по обычной XML-схеме: `XDTOPackages/<Имя>.xml`,
Собирает пакет XDTO по XML-схеме: `XDTOPackages/<Имя>.xml`,
`XDTOPackages/<Имя>/Ext/Package.bin` и регистрацию в `Configuration.xml`.
Отдельного DSL нет — входной формат это XSD. Вся механика перевода (локальные объявления
префиксов `dNpM` на каждой ссылке, инвертированная кратность `lowerBound`/`upperBound`,
фасеты атрибутами вместо дочерних элементов) делается навыком.
Вход — обычная XSD. Отдельного формата описания нет: пиши схему так, как её пишут
везде, остальное навык сделает сам.
## Параметры
@@ -23,34 +22,65 @@ allowed-tools:
|----------|----------|
| `XsdPath` | Путь к файлу XML-схемы |
| `Xsd` | Схема строкой, вместо `-XsdPath` |
| `OutputDir` | Каталог конфигурации (где лежит `Configuration.xml`) |
| `Name` | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD, санированное под идентификатор 1С |
| `Synonym` | Синоним: строка (русский) или хеш-таблица `@{ru='…'; en='…'}` |
| `OutputDir` | Каталог исходников конфигурации или расширения — там, где лежит `Configuration.xml` |
| `Name` | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD |
| `Synonym` | Синоним: строка или хеш-таблица `@{ru='…'; en='…'}` |
| `Comment` | Комментарий |
| `Force` | Перезаписать существующий пакет |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -XsdPath "<схема.xsd>" -OutputDir "<каталог>"
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
... -XsdPath bank.xsd -OutputDir src -Name ОбменСБанком -Synonym "Обмен с банком"
... -XsdPath fss.xsd -OutputDir src -Force
```
## Формат входа
## Читай предупреждения
Обычная XML-схема. Две вещи, которые XSD выразить не может (`nillable` у атрибута,
`qualified` у отдельного свойства), пишутся атрибутами из пространства имён модели XDTO:
XSD выразительнее модели XDTO. Всё, что не переносится один в один, навык переносит
приближённо и **пишет об этом**:
```
Предупреждения (2) — конструкции XSD без точного соответствия в модели XDTO:
! Документ : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён
! Документ : кратность на вложенной частице (<xs:sequence minOccurs/maxOccurs>) не выражается в модели XDTO
```
Такое сообщение означает, что пакет собран, но схема упрощена. Если упрощение
недопустимо — меняй схему (например, разноси варианты `xs:choice` по разным типам),
а не игнорируй.
Что переносится приближённо: вложенные `xs:sequence`/`xs:choice` (уплощаются в плоский
список свойств), `xs:all` (становится последовательностью), кратность на частице,
`substitutionGroup`, `xs:key`/`keyref`/`unique`, `xs:redefine`.
`xs:group` и `xs:attributeGroup` раскрываются по ссылке — их содержимое попадает в тип.
`xs:include` игнорируется: зависимости в XDTO разрешаются только по namespace,
поэтому включаемую схему нужно собрать отдельным пакетом и заменить `include` на `import`.
## Зависимости между пакетами
`<xs:import namespace="…"/>` разрешается по namespace среди пакетов конфигурации
или расширения.
Если пакета с таким пространством имён нет, платформа при загрузке молча подменит тип
на `xs:anyType` — без ошибки. Собирай сначала зависимости, потом зависящий пакет,
и проверяй результат через `/xdto-validate`.
## Что XSD выразить не может
Две вещи модель XDTO умеет, а XML Schema — нет: `nillable` у атрибута и `qualified`
у отдельного свойства. Они пишутся атрибутами из пространства имён модели:
```xml
<xs:attribute name="Представление" type="xs:string"
xmlns:xdto="http://v8.1c.ru/8.1/xdto" xdto:nillable="true"/>
```
Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в модели**.
Схема при этом остаётся валидной. Полный список аннотаций — в справке ниже.
Схема остаётся валидной — валидаторы такие атрибуты игнорируют. Полный список
и таблица соответствий XSD ↔ XDTO — в [xsd-reference.md](xsd-reference.md).
Свойства объекта метаданных можно задать прямо в схеме:
@@ -65,29 +95,17 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -
</xs:annotation>
```
Принимается и не вполне каноническая схема: имя типа без префикса трактуется как тип
целевого пространства имён, `form="qualified"` на глобальных объявлениях (так экспортирует
сам Конфигуратор) — читается.
## Зависимости между пакетами
`<xs:import namespace="…"/>` разрешается по namespace среди пакетов конфигурации.
Если пакета с таким пространством имён в конфигурации нет, платформа при загрузке молча
подменит тип на `xs:anyType` — без ошибки. Сначала соберите пакеты-зависимости,
затем зависящий, и проверьте результат через `/xdto-validate`.
## Типичный workflow
1. Получить XSD от контрагента (или выгрузить: Конфигуратор → пакет → Экспорт XML-схемы)
2. `/xdto-compile -XsdPath <файл> -OutputDir <конфигурация>`
3. `/xdto-validate` — убедиться, что все типы разрешились
4. `/db-load-xml` + `/db-update` — загрузить в базу
1. Получить XSD от контрагента (или выгрузить схему существующего пакета: `/xdto-decompile`)
2. `/xdto-compile -XsdPath <файл> -OutputDir <каталог-исходников>` — прочитать предупреждения
3. `/xdto-validate` — убедиться, что типы разрешились
4. `/db-load-xml` + `/db-update`
Правка существующего пакета: `/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
Пара замыкается без потерь, включая свойства объекта метаданных.
## Верификация
```
/xdto-validate <каталог>/XDTOPackages/<Имя> — типы, импорты, регистрация
/xdto-validate <каталог-исходников>/XDTOPackages/<Имя>
```
@@ -265,6 +265,13 @@ function Serialize-Node($node, [int]$depth, $inherited) {
# --- XSD reading helpers ---
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
$script:warnings = New-Object System.Collections.ArrayList
function Warn([string]$msg) {
if (-not $script:warnings.Contains($msg)) { [void]$script:warnings.Add($msg) }
}
function XA([System.Xml.XmlElement]$el, [string]$name) {
if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
return $null
@@ -471,6 +478,63 @@ function Build-Property([System.Xml.XmlElement]$el, [bool]$isAttribute) {
# --- complexType -> objectType / typeDef(ObjectType) ---
# Разрешение xs:group / xs:attributeGroup по ссылке
$script:GROUPS = @{}
$script:ATTR_GROUPS = @{}
function Resolve-Group([System.Xml.XmlElement]$el, [string]$kind) {
$ref = XA $el "ref"
if (-not $ref) { return $null }
$q = Split-QName $el $ref
if (-not $q) { return $null }
$map = if ($kind -eq "group") { $script:GROUPS } else { $script:ATTR_GROUPS }
if ($map.ContainsKey($q.Local)) { return $map[$q.Local] }
return $null
}
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth) {
if ($depth -gt 20) { return }
foreach ($c in $particle.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.NamespaceURI -ne $XS_NS) { continue }
switch ($c.get_LocalName()) {
"element" { [void]$elemList.Add((Build-Property $c $false)) }
"any" { $isOpen.Value = $true }
"sequence" {
Warn "$typeName : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств"
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
}
"choice" {
Warn "$typeName : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён"
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
}
"all" {
Warn "$typeName : xs:all трактуется как последовательность"
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
}
"group" {
$g = Resolve-Group $c "group"
if ($g) {
foreach ($gc in $g.ChildNodes) {
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
Collect-Particle $gc $elemList $isOpen $typeName ($depth + 1)
}
}
} else {
Warn "$typeName : не найдена группа $(XA $c 'ref') — её свойства в пакет не попали"
}
}
}
# Кратность на самой частице модель выразить не может
if (@("sequence", "choice", "all", "group") -contains $c.get_LocalName()) {
if ((XA $c "maxOccurs") -or (XA $c "minOccurs")) {
Warn "$typeName : кратность на вложенной частице (<xs:$($c.get_LocalName()) minOccurs/maxOccurs>) не выражается в модели XDTO"
}
}
}
}
# open / ordered / sequenced / abstract / mixed: выводим где выводимо,
# остальное приходит зеркалом xdto:
function Set-TypeFlags($node, [System.Xml.XmlElement]$ct, $isOpen, $choice) {
@@ -532,22 +596,48 @@ function Fill-ComplexType($node, [System.Xml.XmlElement]$ct) {
# Particle: xs:sequence (ordered) or xs:choice (ordered="false")
$seq = XFirst $body "sequence"
$cho = XFirst $body "choice"
$particle = if ($seq) { $seq } else { $cho }
$all = XFirst $body "all"
$grp = XFirst $body "group"
$particle = if ($seq) { $seq } elseif ($cho) { $cho } elseif ($all) { $all } else { $grp }
$isOpen = $false
# Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5%
# типов корпуса). Отклонения приходят зеркалом xdto:order.
$elemProps = New-Object System.Collections.ArrayList
$typeName = if ($ct.HasAttribute("name")) { $ct.GetAttribute("name") } else { "(анонимный тип)" }
if ($particle) {
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 }
}
$openRef = [ref]$isOpen
if ($all) {
Warn "$typeName : xs:all трактуется как последовательность"
}
if ($grp -and -not $seq -and -not $cho -and -not $all) {
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
$g = Resolve-Group $grp "group"
if ($g) {
foreach ($gc in $g.ChildNodes) {
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
Collect-Particle $gc $elemProps $openRef $typeName 1
}
}
} else {
Warn "$typeName : не найдена группа $(XA $grp 'ref') — её свойства в пакет не попали"
}
} else {
Collect-Particle $particle $elemProps $openRef $typeName 0
}
$isOpen = $openRef.Value
}
foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) }
# xs:attributeGroup раскрываем по ссылке
foreach ($ag in (XChildren $body "attributeGroup")) {
$g = Resolve-Group $ag "attributeGroup"
if ($g) {
foreach ($a in (XChildren $g "attribute")) { Add-Child $node (Build-Property $a $true) }
} else {
Warn "Не найдена группа атрибутов $(XA $ag 'ref') — её атрибуты в пакет не попали"
}
}
foreach ($e in $elemProps) { Add-Child $node $e }
if ((XChildren $body "anyAttribute").Count -gt 0) { $isOpen = $true }
@@ -591,10 +681,37 @@ if ($ann) {
}
}
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
foreach ($node in $schema.ChildNodes) {
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
$nm = XA $node "name"
if ($node.get_LocalName() -eq "group" -and $nm) { $script:GROUPS[$nm] = $node }
if ($node.get_LocalName() -eq "attributeGroup" -and $nm) { $script:ATTR_GROUPS[$nm] = $node }
}
# Конструкции XSD, которым в модели XDTO нет соответствия
foreach ($sg in $schema.SelectNodes("//*[local-name()='element'][@substitutionGroup]")) {
Warn "Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление $($sg.GetAttribute('name')) сохранено как обычное"
}
foreach ($idc in @("key", "keyref", "unique")) {
if ($schema.SelectNodes("//*[local-name()='$idc']").Count -gt 0) {
Warn "Ограничения целостности (xs:$idc) в модели XDTO не хранятся — отброшены"
}
}
if ($schema.SelectNodes("//*[local-name()='redefine']").Count -gt 0) {
Warn "xs:redefine не поддерживается — переопределения проигнорированы"
}
if ($schema.SelectNodes("//*[local-name()='include']").Count -gt 0) {
Warn "xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. Соберите включаемую схему отдельным пакетом и добавьте <xs:import>"
}
foreach ($node in $schema.ChildNodes) {
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
switch ($node.get_LocalName()) {
"annotation" { }
"group" { }
"attributeGroup" { }
"notation" { }
"import" {
$n = New-Node "import"
Add-Attr $n "namespace" (XA $node "namespace")
@@ -765,6 +882,12 @@ Write-Host "✓ Пакет XDTO собран: $Name"
Write-Host " Namespace: $targetNs"
Write-Host " Типов: $typeCount"
Write-Host " Файлы: XDTOPackages/$Name.xml, XDTOPackages/$Name/Ext/Package.bin"
if ($script:warnings.Count -gt 0) {
Write-Host ""
Write-Host "Предупреждения ($($script:warnings.Count)) — конструкции XSD без точного соответствия в модели XDTO:"
foreach ($w in $script:warnings) { Write-Host " ! $w" }
Write-Host ""
}
switch ($regResult) {
"added" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> добавлен в ChildObjects" }
"already" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> уже зарегистрирован" }
@@ -297,6 +297,20 @@ def serialize_node(node, depth, inherited):
# ── XSD reading helpers ──────────────────────────────────────
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
warnings_list = []
def warn(msg):
if msg not in warnings_list:
warnings_list.append(msg)
GROUPS = {}
ATTR_GROUPS = {}
def MA(el, name):
# xdto: mirror attribute — литеральное значение для Package.bin.
# Ищем по namespace, а не по строке префикса.
@@ -497,6 +511,54 @@ def build_property(el, is_attribute):
# ── complexType -> objectType / typeDef(ObjectType) ──────────
def resolve_group(el, kind):
ref = el.get("ref")
if not ref:
return None
q = split_qname(el, ref)
if not q:
return None
m = GROUPS if kind == "group" else ATTR_GROUPS
return m.get(q[1])
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
def collect_particle(particle, elem_list, open_flag, type_name, depth):
if depth > 20:
return
for c in particle:
if not isinstance(c.tag, str) or etree.QName(c).namespace != XS_NS:
continue
ln = local(c)
if ln == "element":
elem_list.append(build_property(c, False))
elif ln == "any":
open_flag[0] = True
elif ln == "sequence":
warn(type_name + " : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств")
collect_particle(c, elem_list, open_flag, type_name, depth + 1)
elif ln == "choice":
warn(type_name + " : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён")
collect_particle(c, elem_list, open_flag, type_name, depth + 1)
elif ln == "all":
warn(type_name + " : xs:all трактуется как последовательность")
collect_particle(c, elem_list, open_flag, type_name, depth + 1)
elif ln == "group":
g = resolve_group(c, "group")
if g is not None:
for gc in g:
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
and local(gc) in ("sequence", "choice", "all"):
collect_particle(gc, elem_list, open_flag, type_name, depth + 1)
else:
warn(type_name + " : не найдена группа " + str(c.get("ref")) + " — её свойства в пакет не попали")
if ln in ("sequence", "choice", "all", "group"):
if c.get("maxOccurs") is not None or c.get("minOccurs") is not None:
warn(type_name + " : кратность на вложенной частице (<xs:" + ln +
" minOccurs/maxOccurs>) не выражается в модели XDTO")
def set_type_flags(node, ct, is_open, choice):
m_open = MA(ct, "open")
if m_open is not None:
@@ -562,22 +624,40 @@ def fill_complex_type(node, ct):
seq = xfirst(body, "sequence")
cho = xfirst(body, "choice")
particle = seq if seq is not None else cho
is_open = False
all_ = xfirst(body, "all")
grp = xfirst(body, "group")
particle = seq if seq is not None else (cho if cho is not None else (all_ if all_ is not None else grp))
open_flag = [False]
# Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса)
elem_props = []
type_name = ct.get("name") or "(анонимный тип)"
if particle is not None:
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
if all_ is not None:
warn(type_name + " : xs:all трактуется как последовательность")
if grp is not None and seq is None and cho is None and all_ is None:
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
g = resolve_group(grp, "group")
if g is not None:
for gc in g:
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
and local(gc) in ("sequence", "choice", "all"):
collect_particle(gc, elem_props, open_flag, type_name, 1)
else:
warn(type_name + " : не найдена группа " + str(grp.get("ref")) + " — её свойства в пакет не попали")
else:
collect_particle(particle, elem_props, open_flag, type_name, 0)
is_open = open_flag[0]
for a in xchildren(body, "attribute"):
node.children.append(build_property(a, True))
# xs:attributeGroup раскрываем по ссылке
for ag in xchildren(body, "attributeGroup"):
g = resolve_group(ag, "attributeGroup")
if g is not None:
for a in xchildren(g, "attribute"):
node.children.append(build_property(a, True))
else:
warn("Не найдена группа атрибутов " + str(ag.get("ref")) + " — её атрибуты в пакет не попали")
node.children.extend(elem_props)
if xchildren(body, "anyAttribute"):
is_open = True
@@ -627,6 +707,30 @@ if ann is not None:
elif ln == "synonym":
meta_synonym.append({"Lang": f.get("lang") or "", "Content": f.text or ""})
# Реестр глобальных групп — нужен до обхода, чтобы раскрывать ссылки
for node in schema:
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
continue
nm = node.get("name")
if local(node) == "group" and nm:
GROUPS[nm] = node
if local(node) == "attributeGroup" and nm:
ATTR_GROUPS[nm] = node
# Конструкции XSD, которым в модели XDTO нет соответствия
for sg in schema.iter():
if isinstance(sg.tag, str) and local(sg) == "element" and sg.get("substitutionGroup"):
warn("Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление "
+ str(sg.get("name")) + " сохранено как обычное")
for idc in ("key", "keyref", "unique"):
if any(isinstance(e.tag, str) and local(e) == idc for e in schema.iter()):
warn("Ограничения целостности (xs:" + idc + ") в модели XDTO не хранятся — отброшены")
if any(isinstance(e.tag, str) and local(e) == "redefine" for e in schema.iter()):
warn("xs:redefine не поддерживается — переопределения проигнорированы")
if any(isinstance(e.tag, str) and local(e) == "include" for e in schema.iter()):
warn("xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. "
"Соберите включаемую схему отдельным пакетом и добавьте <xs:import>")
for node in schema:
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
continue
@@ -635,7 +739,7 @@ for node in schema:
n = Node("import")
add_attr(n, "namespace", node.get("namespace"))
pkg_node.children.append(n)
elif ln in ("annotation", "include"):
elif ln in ("annotation", "include", "group", "attributeGroup", "notation"):
continue
elif ln == "element":
pkg_node.children.append(build_property(node, False))
@@ -772,6 +876,13 @@ print(f"✓ Пакет XDTO собран: {name}")
print(f" Namespace: {target_ns}")
print(f" Типов: {type_count}")
print(f" Файлы: XDTOPackages/{name}.xml, XDTOPackages/{name}/Ext/Package.bin")
if warnings_list:
print("")
print("Предупреждения (" + str(len(warnings_list)) +
") — конструкции XSD без точного соответствия в модели XDTO:")
for w in warnings_list:
print(" ! " + w)
print("")
if reg_result == "added":
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> добавлен в ChildObjects")
elif reg_result == "already":
@@ -0,0 +1,88 @@
# XSD ↔ XDTO — справочник
## Соответствия
| XML Schema | Модель XDTO |
|---|---|
| `xs:schema/@targetNamespace` | пространство имён пакета |
| `elementFormDefault` / `attributeFormDefault` | `elementFormQualified` / `attributeFormQualified` |
| `xs:import/@namespace` | зависимость от другого пакета (разрешается по namespace) |
| `xs:complexType` | объектный тип |
| `xs:simpleType` | тип значения |
| `xs:element` / `xs:attribute` на верхнем уровне | глобальное свойство пакета |
| `xs:element` / `xs:attribute` внутри типа | свойство типа |
| `@minOccurs` / `@maxOccurs="unbounded"` | `lowerBound` / `upperBound="-1"` |
| `@nillable`, `@default`, `@fixed`, `@ref` | те же по смыслу |
| анонимный `xs:simpleType`/`xs:complexType` в объявлении | встроенный тип свойства |
| `xs:complexContent/xs:extension/@base` | наследование типа |
| `@abstract`, `@mixed` | те же |
| `xs:choice` | тип-выбор одного из вариантов |
| `xs:any` + `xs:anyAttribute` | открытый тип |
| `xs:simpleContent/xs:extension/@base` | свойство собственного значения элемента |
| `xs:restriction` + фасеты | базовый тип + ограничения |
| `xs:pattern`, `xs:enumeration` | те же |
| `xs:list/@itemType` | список |
| `xs:union/@memberTypes` | объединение |
Порядок объявлений верхнего уровня в XSD произвольный — навык сам расставит их
в порядке, который требует модель.
## Аннотации `xdto:`
Пространство имён — `http://v8.1c.ru/8.1/xdto`. Нужны только там, где XML Schema
не может выразить то, что умеет модель. В большинстве схем не нужны вовсе.
Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем,
что и в модели**.
| Аннотация | Где | Назначение |
|---|---|---|
| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута (XSD допускает только у элементов) |
| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута |
| `xdto:qualified` | объявление | переопределение `*FormQualified` для одного свойства |
| `xdto:name` | объявление | имя свойства, если XML-имя не годится как идентификатор 1С; XML-имя уйдёт в `localName` |
| `xdto:form` | `xs:element` | записать `form` явно |
| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать разновидность типа явно |
| `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` | параметры свойства собственного значения |
| `xdto:type` | `xs:enumeration` | тип литерала перечисления |
| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого |
| `xdto:memberTypesForm="prefixed"` | `xs:union` | записать состав объединения префиксами, а не `{ns}имя` |
| `xdto:declareNs` | `xs:union` | объявить префикс пространства имён на узле |
| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно |
Пример:
```xml
<xs:complexType name="КонтактнаяИнформация" xdto:sequenced="true">
<xs:sequence>
<xs:element name="Комментарий" type="xs:string" minOccurs="0"/>
<xs:element name="Адрес по документу" type="xs:string"
xdto:name="Адрес_по_документу" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Представление" type="xs:string"
xdto:nillable="true" xdto:lowerBound="0"/>
</xs:complexType>
```
Аннотации, которые проставляет `/xdto-decompile` при выгрузке существующего пакета,
писать вручную не нужно — они нужны, чтобы обратная сборка вернула ровно тот же файл.
## Свойства объекта метаданных
```xml
<xs:annotation>
<xs:appinfo>
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
<xdto:name>ОбменСБанком</xdto:name>
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
<xdto:synonym lang="en">Bank exchange</xdto:synonym>
<xdto:comment>Формат 1С:Предприятие — Клиент банка</xdto:comment>
</xdto:package>
</xs:appinfo>
</xs:annotation>
```
Пространство имён пакета берётся из `targetNamespace` и здесь не дублируется.
Параметры `-Name`, `-Synonym`, `-Comment` имеют приоритет над этим блоком.
+18 -30
View File
@@ -10,16 +10,15 @@ allowed-tools:
# /xdto-decompile — Выгрузка пакета XDTO в XML-схему
Превращает `Ext/Package.bin` в обычную XML-схему. Заменяет чтение модели XDTO с её
инвертированной кратностью, фасетами-атрибутами и локальными объявлениями префиксов
`dNpM` на каждой ссылке.
Превращает пакет XDTO в обычную XML-схему — читаемую и редактируемую.
Заменяет чтение `Ext/Package.bin` напрямую.
## Параметры
| Параметр | Описание |
|----------|----------|
| `PackagePath` | Каталог пакета, путь к `Ext/Package.bin` или к `<Имя>.xml` объекта метаданных |
| `OutFile` | Записать схему в файл (UTF-8 BOM). Без него — вывод в stdout |
| `OutFile` | Записать схему в файл. Без него — вывод в stdout |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>"
@@ -27,38 +26,27 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1"
Примеры:
```powershell
... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange
... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange -OutFile bank.xsd
... -PackagePath src/XDTOPackages/ОбменСБанком
... -PackagePath src/XDTOPackages/ОбменСБанком -OutFile bank.xsd
```
## Round-trip
## Правка пакета
`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает исходный `Package.bin`
байт-в-байт. Инвариант проверен на 760 пакетах выгрузок Бухгалтерии и ERP.
`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает пакет без потерь,
включая имя, синоним и комментарий объекта метаданных — они выгружаются
в `xs:annotation/xs:appinfo`. Это основной способ менять существующий пакет:
отдельных операций правки не нужно, схема читается и правится целиком.
Это основной способ править существующий пакет: точечных операций не требуется, схема
целиком читаема и редактируема.
В схеме могут встретиться атрибуты с префиксом `xdto:` — так записано то, что
XML Schema выразить не может (например `nillable` у атрибута). Схема при этом остаётся
валидной, валидаторы такие атрибуты игнорируют. Трогать их обычно не нужно; смысл
каждого описан в справочнике навыка `/xdto-compile`.
Свойства объекта метаданных (`Name`, `Synonym`, `Comment`) выгружаются в
`xs:annotation/xs:appinfo`, поэтому при обратной сборке не теряются. `Namespace` не
дублируется — его несёт `targetNamespace`.
## Передача схемы наружу
## Отличия от экспорта XML-схемы в Конфигураторе
Штатный экспорт Конфигуратора теряет данные: `nillable` у свойств-атрибутов
(спецификация XSD не допускает его у атрибутов), а для пакетов с
`elementFormQualified="false"` выдаёт невалидную схему — `form="qualified"` на
глобальных объявлениях.
Навык пишет валидную схему, а то, что XSD выразить не может, выносит в атрибуты
пространства имён модели XDTO:
```xml
<xs:attribute name="Представление" type="xs:string" xdto:nillable="true"/>
```
Такие атрибуты валидаторы игнорируют, а `/xdto-compile` читает обратно. Схему без
потерь можно отдавать контрагенту как есть.
Полученную XSD можно отдавать контрагенту как есть. В отличие от команды
«Экспорт XML-схемы» в Конфигураторе, навык не теряет `nillable` у свойств-атрибутов
и не пишет `form` на глобальных объявлениях, где спецификация XSD его не допускает.
## Верификация
+11 -31
View File
@@ -11,15 +11,15 @@ allowed-tools:
# /xdto-validate — Валидация пакета XDTO
Проверяет модель пакета, объект метаданных и его связь с конфигурацией.
Exit code `1` при наличии ошибок.
Каждая находка выводится отдельной строкой с объяснением. Exit code `1` при ошибках.
## Параметры
| Параметр | Описание |
|----------|----------|
| `PackagePath` | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml` объекта метаданных |
| `ConfigDir` | Каталог конфигурации. По умолчанию определяется по расположению пакета |
| `Detailed` | Показывать успешные проверки, а не только ошибки |
| `ConfigDir` | Корень исходников. По умолчанию определяется по расположению пакета |
| `Detailed` | Показывать успешные проверки, а не только проблемы |
| `MaxErrors` | Остановиться после N ошибок (по умолчанию 20) |
| `OutFile` | Записать отчёт в файл |
@@ -27,39 +27,19 @@ Exit code `1` при наличии ошибок.
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>"
```
## Проверки
`[ERROR]` — платформа такой пакет не примет либо примет неправильно.
`[WARN]` — пакет рабочий, но есть риск, о котором стоит знать.
**Модель**
- Package.bin — корректный XML с корнем `<package>`, задан `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`
потом отдаёт бесструктурное значение. Сопутствующий признак — объявленный, но
неиспользуемый `<import>`.
- **`nillable="true"` вместе с `form="Attribute"`.** Спецификация XSD не допускает
`nillable` у атрибутов, поэтому экспорт XML-схемы в Конфигураторе такое свойство теряет.
На саму работу пакета не влияет, но ломает перенос схемы через Конфигуратор.
Часть дефектов платформа не диагностирует: неразрешённый тип из чужого пространства
имён она молча подменяет на `xs:anyType`, и пакет выглядит загруженным, пока
`ФабрикаXDTO` не отдаст в рантайме бесструктурное значение. Такие вещи видно только
статически — до загрузки в базу.
## Типичный workflow
1. `/xdto-compile` или `/xdto-decompile` правка → `/xdto-compile -Force`
1. `/xdto-compile` или правка через `/xdto-decompile``/xdto-compile -Force`
2. `/xdto-validate <путь>` — до загрузки в базу
3. `/db-load-xml` + `/db-update`
@@ -140,18 +140,40 @@ if (-not ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -a
$imports = New-Object System.Collections.ArrayList
$localTypes = New-Object System.Collections.Generic.HashSet[string]
$objectTypeNames = New-Object System.Collections.Generic.HashSet[string]
$valueTypeNames = New-Object System.Collections.Generic.HashSet[string]
$globalProps = New-Object System.Collections.Generic.HashSet[string]
$topSequence = New-Object System.Collections.ArrayList
foreach ($n in $pkg.ChildNodes) {
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
[void]$topSequence.Add($n.get_LocalName())
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")) }
"objectType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$objectTypeNames.Add($n.GetAttribute("name")) }
"valueType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$valueTypeNames.Add($n.GetAttribute("name")) }
"property" { if ($n.HasAttribute("name")) { [void]$globalProps.Add($n.GetAttribute("name")) } }
}
}
# --- Порядок элементов верхнего уровня ---
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
$TOP_ORDER = @("import", "property", "valueType", "objectType")
$prevRank = -1
$orderOk = $true
foreach ($t in $topSequence) {
$rank = [array]::IndexOf($TOP_ORDER, $t)
if ($rank -lt 0) { continue }
if ($rank -lt $prevRank) {
Report-Error "Нарушен порядок элементов верхнего уровня: <$t> после <$($TOP_ORDER[$prevRank])>. Модель требует import -> property -> valueType -> objectType; платформа отвергнет пакет при обновлении конфигурации"
$orderOk = $false
break
}
$prevRank = $rank
}
if ($orderOk) { Report-OK "Порядок элементов верхнего уровня корректен" }
# --- 4. Duplicate type names ---
$seen = @{}
@@ -249,12 +271,15 @@ if ($anyTypeProps.Count -gt 0 -and $imports.Count -gt 0) {
# --- 7. Unused imports ---
foreach ($imp in $imports) {
if (-not $usedNamespaces.Contains($imp)) {
Report-Warn "<import namespace=`"$imp`"/> объявлен, но ни один тип из этого пространства имён не используется"
}
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
# типовых конфигураций. Сигналом он становится только вместе с anyType — тогда это
# почти наверняка неразрешённая зависимость.
$unused = @()
foreach ($imp in $imports) { if (-not $usedNamespaces.Contains($imp)) { $unused += $imp } }
if ($unused.Count -gt 0 -and $anyTypeProps.Count -gt 0) {
Report-Warn "Импорт(ы) без единого использованного типа: $($unused -join ', ') — вместе с anyType это признак неразрешённой зависимости"
}
if ($imports.Count -gt 0 -and $script:warnings -eq 0) { Report-OK "$($imports.Count) импорт(ов) — все используются" }
if ($imports.Count -gt 0 -and $unused.Count -eq 0) { Report-OK "$($imports.Count) импорт(ов) — все используются" }
# --- 8. nillable on attribute-form properties ---
@@ -279,7 +304,8 @@ foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='t
$len = $t.GetAttribute("length")
if ($len -and ($t.HasAttribute("minLength") -or $t.HasAttribute("maxLength"))) {
Report-Error "$nm : length несовместим с minLength/maxLength"
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
Report-Warn "$nm : length задан вместе с minLength/maxLength — спецификация XSD считает их взаимоисключающими"
}
$minL = $t.GetAttribute("minLength"); $maxL = $t.GetAttribute("maxLength")
if ($minL -and $maxL -and ([int]$minL -gt [int]$maxL)) {
@@ -328,6 +354,99 @@ foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
}
if (-not $script:stopped) { Report-OK "Свойства: form и кратности корректны" }
# --- 10b. Structural consistency ---
$structOk = $true
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType' or local-name()='typeDef']")) {
if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -ne "ObjectType") { continue }
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
$propNames = @{}
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue }
$pn = $c.GetAttribute("name")
if ($pn) {
if ($propNames.ContainsKey($pn)) {
Report-Error "$tn : дублирующееся имя свойства `"$pn`""
$structOk = $false
}
$propNames[$pn] = $true
}
}
if ($script:stopped) { break }
}
foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
$pn = if ($p.HasAttribute("name")) { $p.GetAttribute("name") } else { $p.GetAttribute("ref") }
if ($p.HasAttribute("name") -and $p.HasAttribute("ref")) {
Report-Error "Свойство `"$pn`": заданы одновременно name и ref — допустимо только одно"
$structOk = $false
}
$inlineTypeDef = $null
foreach ($c in $p.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $inlineTypeDef = $c; break }
}
if ($inlineTypeDef -and $p.HasAttribute("type")) {
Report-Error "Свойство `"$pn`": заданы одновременно type и вложенный <typeDef> — допустимо только одно"
$structOk = $false
}
if ($inlineTypeDef -and -not $inlineTypeDef.HasAttribute("type", $XSI_NS)) {
Report-Error "Свойство `"$pn`": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)"
$structOk = $false
}
if ($script:stopped) { break }
}
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
foreach ($vt in $pkg.SelectNodes("//*[local-name()='valueType']")) {
foreach ($c in $vt.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "typeDef") { continue }
if ($c.HasAttribute("type", $XSI_NS)) {
Report-Warn "$($vt.GetAttribute('name')) : у <typeDef> внутри <valueType> задан xsi:type — платформа его здесь не пишет"
}
}
}
# Род базового типа должен совпадать
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType'][@base]")) {
$b = $t.GetAttribute("base")
$parts = $b.Split(":")
if ($parts.Count -ne 2) { continue }
$bns = $t.GetNamespaceOfPrefix($parts[0])
if ($bns -ne $targetNs) { continue }
if ($valueTypeNames.Contains($parts[1])) {
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на valueType, а objectType может наследоваться только от objectType"
$structOk = $false
}
}
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType'][@base]")) {
$b = $t.GetAttribute("base")
$parts = $b.Split(":")
if ($parts.Count -ne 2) { continue }
$bns = $t.GetNamespaceOfPrefix($parts[0])
if ($bns -ne $targetNs) { continue }
if ($objectTypeNames.Contains($parts[1])) {
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на objectType, а valueType может строиться только на простом типе"
$structOk = $false
}
}
# Union без состава
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef'][@variety='Union']")) {
if ($t.HasAttribute("memberTypes")) { continue }
$hasMember = $false
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $hasMember = $true; break }
}
if (-not $hasMember) {
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
Report-Warn "$tn : variety=`"Union`" без memberTypes и без вложенных типов — состав объединения пуст"
}
}
if ($structOk -and -not $script:stopped) { Report-OK "Структура типов и свойств согласована" }
if ($script:stopped) { & $finalize; exit 1 }
# --- 11. Metadata object ---
if ($mdPath) {
@@ -386,7 +505,7 @@ if (Test-Path $configXml) {
} catch {}
}
if ($clash.Count -gt 0) {
Report-Error "targetNamespace `"$targetNs`" уже занят пакет(ами): $($clash -join ', '). Платформа не допускает два пакета с одним пространством имён"
Report-Warn "targetNamespace `"$targetNs`" объявлен также в пакет(ах): $($clash -join ', '). Платформа это допускает, но <import> на это пространство имён становится неоднозначным"
} else {
Report-OK "targetNamespace уникален в конфигурации"
}
@@ -145,19 +145,47 @@ else:
imports = []
local_types = set()
object_type_names = set()
value_type_names = set()
global_props = set()
top_sequence = []
for n in pkg:
if not isinstance(n.tag, str):
continue
ln = local(n)
top_sequence.append(ln)
if ln == "import":
imports.append(n.get("namespace"))
elif ln in ("objectType", "valueType"):
elif ln == "objectType":
local_types.add(n.get("name"))
object_type_names.add(n.get("name"))
elif ln == "valueType":
local_types.add(n.get("name"))
value_type_names.add(n.get("name"))
elif ln == "property" and n.get("name"):
global_props.add(n.get("name"))
# ── порядок элементов верхнего уровня ────────────────────────
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
TOP_ORDER = ["import", "property", "valueType", "objectType"]
prev_rank = -1
order_ok = True
for t in top_sequence:
if t not in TOP_ORDER:
continue
rank = TOP_ORDER.index(t)
if rank < prev_rank:
report_error(f"Нарушен порядок элементов верхнего уровня: <{t}> после <{TOP_ORDER[prev_rank]}>. "
"Модель требует import -> property -> valueType -> objectType; "
"платформа отвергнет пакет при обновлении конфигурации")
order_ok = False
break
prev_rank = rank
if order_ok:
report_ok("Порядок элементов верхнего уровня корректен")
# ── 4. duplicate type names ──────────────────────────────────
seen = set()
@@ -254,10 +282,13 @@ if any_type_props and imports:
# ── 7. unused imports ────────────────────────────────────────
for imp in imports:
if imp not in used_namespaces:
report_warn(f'<import namespace="{imp}"/> объявлен, но ни один тип из этого пространства имён не используется')
if imports and state["warnings"] == 0:
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
# типовых конфигураций. Сигналом он становится только вместе с anyType.
unused = [i for i in imports if i not in used_namespaces]
if unused and any_type_props:
report_warn(f'Импорт(ы) без единого использованного типа: {", ".join(unused)}'
"вместе с anyType это признак неразрешённой зависимости")
if imports and not unused:
report_ok(f"{len(imports)} импорт(ов) — все используются")
# ── 8. nillable on attribute-form properties ─────────────────
@@ -283,7 +314,9 @@ for t in pkg.iter():
nm = t.get("name") or "(анонимный тип)"
if t.get("length") and (t.get("minLength") or t.get("maxLength")):
report_error(f"{nm} : length несовместим с minLength/maxLength")
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
report_warn(f"{nm} : length задан вместе с minLength/maxLength — "
"спецификация XSD считает их взаимоисключающими")
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})")
@@ -328,6 +361,92 @@ for p in pkg.iter():
if not state["stopped"]:
report_ok("Свойства: form и кратности корректны")
# ── 10b. structural consistency ──────────────────────────────
struct_ok = True
for t in pkg.iter():
if not isinstance(t.tag, str) or local(t) not in ("objectType", "typeDef"):
continue
if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") != "ObjectType":
continue
tn = t.get("name") or "(анонимный тип)"
prop_names = set()
for c in t:
if not isinstance(c.tag, str) or local(c) != "property":
continue
pn = c.get("name")
if pn:
if pn in prop_names:
report_error(f'{tn} : дублирующееся имя свойства "{pn}"')
struct_ok = False
prop_names.add(pn)
if state["stopped"]:
break
for p_ in pkg.iter():
if not isinstance(p_.tag, str) or local(p_) != "property":
continue
pn = p_.get("name") or p_.get("ref")
if p_.get("name") is not None and p_.get("ref") is not None:
report_error(f'Свойство "{pn}": заданы одновременно name и ref — допустимо только одно')
struct_ok = False
inline = next((c for c in p_ if isinstance(c.tag, str) and local(c) == "typeDef"), None)
if inline is not None and p_.get("type") is not None:
report_error(f'Свойство "{pn}": заданы одновременно type и вложенный <typeDef> — допустимо только одно')
struct_ok = False
if inline is not None and inline.get(f"{{{XSI_NS}}}type") is None:
report_error(f'Свойство "{pn}": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)')
struct_ok = False
if state["stopped"]:
break
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
for vt in pkg.iter():
if not isinstance(vt.tag, str) or local(vt) != "valueType":
continue
for c in vt:
if isinstance(c.tag, str) and local(c) == "typeDef" and c.get(f"{{{XSI_NS}}}type") is not None:
report_warn(f'{vt.get("name")} : у <typeDef> внутри <valueType> задан xsi:type — '
"платформа его здесь не пишет")
# Род базового типа должен совпадать
for t in pkg.iter():
if not isinstance(t.tag, str) or t.get("base") is None:
continue
kind = local(t)
if kind not in ("objectType", "valueType"):
continue
parts = t.get("base").split(":")
if len(parts) != 2:
continue
if t.nsmap.get(parts[0]) != target_ns:
continue
if kind == "objectType" and parts[1] in value_type_names:
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на valueType, '
"а objectType может наследоваться только от objectType")
struct_ok = False
if kind == "valueType" and parts[1] in object_type_names:
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на objectType, '
"а valueType может строиться только на простом типе")
struct_ok = False
# Union без состава
for t in pkg.iter():
if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"):
continue
if t.get("variety") != "Union" or t.get("memberTypes") is not None:
continue
if not any(isinstance(c.tag, str) and local(c) == "typeDef" for c in t):
tn = t.get("name") or "(анонимный тип)"
report_warn(f'{tn} : variety="Union" без memberTypes и без вложенных типов — состав объединения пуст')
if struct_ok and not state["stopped"]:
report_ok("Структура типов и свойств согласована")
if state["stopped"]:
finalize()
sys.exit(1)
# ── 11. metadata object ──────────────────────────────────────
if md_path:
@@ -375,8 +494,8 @@ if os.path.exists(config_xml):
except Exception: # noqa: BLE001
pass
if clash:
report_error(f'targetNamespace "{target_ns}" уже занят пакет(ами): {", ".join(clash)}. '
"Платформа не допускает два пакета с одним пространством имён")
report_warn(f'targetNamespace "{target_ns}" объявлен также в пакет(ах): {", ".join(clash)}. '
"Платформа это допускает, но <import> на это пространство имён становится неоднозначным")
else:
report_ok("targetNamespace уникален в конфигурации")
else:
+22 -2
View File
@@ -169,5 +169,25 @@
- **`form="qualified"` на глобальном объявлении** — так экспортирует XML-схему сам
Конфигуратор, хотя спецификация XSD допускает `form` только у локальных объявлений.
Такие файлы читаются; сам навык пишет корректно.
- **`xs:include`** игнорируется (в модели XDTO соответствия нет).
- Порядок объявлений верхнего уровня произвольный и сохраняется как есть.
- **Порядок объявлений верхнего уровня произвольный** — компилятор расставляет их
в требуемом моделью порядке `import → property → valueType → objectType`.
- **`xs:group` и `xs:attributeGroup`** раскрываются по ссылке: содержимое группы
подставляется в тип.
## 5. Конструкции без точного соответствия
XML Schema выразительнее модели XDTO. Перечисленное ниже переносится приближённо,
и компилятор об этом **предупреждает** — молча терять свойства нельзя.
| Конструкция | Что происходит |
|---|---|
| вложенные `xs:sequence` / `xs:choice` | уплощаются в плоский список свойств; вложенный выбор варианта не сохраняется |
| `xs:all` | трактуется как последовательность |
| `minOccurs`/`maxOccurs` на самой частице | не выражается, отбрасывается |
| `substitutionGroup` | объявление сохраняется как обычное |
| `xs:key`, `xs:keyref`, `xs:unique` | отбрасываются |
| `xs:redefine` | игнорируется |
| `xs:include` | игнорируется: зависимости разрешаются только по namespace — включаемую схему нужно собрать отдельным пакетом и заменить на `xs:import` |
`ordered="false"` (выбор одного из вариантов) выводится только из **корневой** `xs:choice`
типа: модель хранит признак на типе целиком, а не на вложенной частице.
@@ -0,0 +1,14 @@
{
"name": "вложенные частицы и группы: уплощение с предупреждениями, ни одно свойство не потеряно",
"caseFiles": ["nested-particles.xsd"],
"params": { "xsdFile": "nested-particles.xsd" },
"validatePath": "XDTOPackages/nested_particles",
"expect": {
"files": ["XDTOPackages/nested_particles/Ext/Package.bin"],
"stdoutContains": [
"вложенная xs:choice уплощена",
"кратность на вложенной частице",
"xs:all трактуется как последовательность"
]
}
}
@@ -0,0 +1,28 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:t" targetNamespace="urn:t">
<xs:group name="Общие">
<xs:sequence><xs:element name="ИзГруппы" type="xs:string"/></xs:sequence>
</xs:group>
<xs:attributeGroup name="Служебные">
<xs:attribute name="Версия" type="xs:string"/>
</xs:attributeGroup>
<xs:complexType name="Документ">
<xs:sequence>
<xs:element name="Первый" type="xs:string"/>
<xs:choice>
<xs:element name="ВариантА" type="xs:string"/>
<xs:element name="ВариантБ" type="xs:string"/>
</xs:choice>
<xs:group ref="tns:Общие"/>
<xs:sequence maxOccurs="unbounded">
<xs:element name="Повторяемый" type="xs:string"/>
</xs:sequence>
</xs:sequence>
<xs:attributeGroup ref="tns:Служебные"/>
</xs:complexType>
<xs:complexType name="Всё">
<xs:all><xs:element name="Любой" type="xs:string"/></xs:all>
</xs:complexType>
<xs:complexType name="ЧерезГруппу">
<xs:group ref="tns:Общие"/>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<XDTOPackage>nested_particles</XDTOPackage>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<XDTOPackage uuid="UUID-001">
<Properties>
<Name>nested_particles</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>nested_particles</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Namespace>urn:t</Namespace>
</Properties>
</XDTOPackage>
</MetaDataObject>
@@ -0,0 +1,16 @@
<package xmlns="http://v8.1c.ru/8.1/xdto" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="urn:t">
<objectType name="Документ">
<property name="Версия" type="xs:string" form="Attribute"/>
<property name="Первый" type="xs:string"/>
<property name="ВариантА" type="xs:string"/>
<property name="ВариантБ" type="xs:string"/>
<property name="ИзГруппы" type="xs:string"/>
<property name="Повторяемый" type="xs:string"/>
</objectType>
<objectType name="Всё">
<property name="Любой" type="xs:string"/>
</objectType>
<objectType name="ЧерезГруппу">
<property name="ИзГруппы" type="xs:string"/>
</objectType>
</package>
@@ -0,0 +1,28 @@
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:tns="urn:t" targetNamespace="urn:t">
<xs:group name="Общие">
<xs:sequence><xs:element name="ИзГруппы" type="xs:string"/></xs:sequence>
</xs:group>
<xs:attributeGroup name="Служебные">
<xs:attribute name="Версия" type="xs:string"/>
</xs:attributeGroup>
<xs:complexType name="Документ">
<xs:sequence>
<xs:element name="Первый" type="xs:string"/>
<xs:choice>
<xs:element name="ВариантА" type="xs:string"/>
<xs:element name="ВариантБ" type="xs:string"/>
</xs:choice>
<xs:group ref="tns:Общие"/>
<xs:sequence maxOccurs="unbounded">
<xs:element name="Повторяемый" type="xs:string"/>
</xs:sequence>
</xs:sequence>
<xs:attributeGroup ref="tns:Служебные"/>
</xs:complexType>
<xs:complexType name="Всё">
<xs:all><xs:element name="Любой" type="xs:string"/></xs:all>
</xs:complexType>
<xs:complexType name="ЧерезГруппу">
<xs:group ref="tns:Общие"/>
</xs:complexType>
</xs:schema>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:v8="http://v8.1c.ru/8.1/data/core" version="2.17">
<XDTOPackage uuid="11111111-2222-3333-4444-555555555555">
<Properties>
<Name>badorder</Name>
<Synonym><v8:item><v8:lang>ru</v8:lang><v8:content>badorder</v8:content></v8:item></Synonym>
<Comment/>
<Namespace>urn:test:badorder</Namespace>
</Properties>
</XDTOPackage>
</MetaDataObject>
@@ -0,0 +1,6 @@
<package xmlns="http://v8.1c.ru/8.1/xdto" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="urn:test:badorder">
<objectType name="Документ">
<property name="Поле" type="xs:string"/>
</objectType>
<property xmlns:d2p1="urn:test:badorder" name="Корень" type="d2p1:Документ"/>
</package>
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:v8="http://v8.1c.ru/8.1/data/core" version="2.17">
<XDTOPackage uuid="11111111-2222-3333-4444-555555555555">
<Properties>
<Name>conflict</Name>
<Synonym><v8:item><v8:lang>ru</v8:lang><v8:content>conflict</v8:content></v8:item></Synonym>
<Comment/>
<Namespace>urn:test:conflict</Namespace>
</Properties>
</XDTOPackage>
</MetaDataObject>
@@ -0,0 +1,11 @@
<package xmlns="http://v8.1c.ru/8.1/xdto" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" targetNamespace="urn:test:conflict">
<valueType name="Простой" base="xs:string"/>
<objectType name="Документ" xmlns:d2p1="urn:test:conflict" base="d2p1:Простой">
<property xmlns:d3p1="urn:test:conflict" name="Поле" ref="d3p1:Поле" type="xs:string"/>
<property name="Дубль" type="xs:string"/>
<property name="Дубль" type="xs:string"/>
<property name="СВложенным" type="xs:string">
<typeDef base="xs:string"/>
</property>
</objectType>
</package>
@@ -8,7 +8,7 @@
"expect": {
"stdoutContains": [
"тихая деградация",
"ни один тип из этого пространства имён не используется"
"признак неразрешённой зависимости"
]
},
"noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains"
@@ -0,0 +1,18 @@
{
"name": "конфликты объявлений: name+ref, дубль свойства, typeDef без xsi:type, база другого рода",
"caseFiles": [
"pkg-conflict/XDTOPackages/conflict.xml",
"pkg-conflict/XDTOPackages/conflict/Ext/Package.bin"
],
"params": { "packagePath": "pkg-conflict/XDTOPackages/conflict" },
"expectError": true,
"expect": {
"stdoutContains": [
"заданы одновременно name и ref",
"дублирующееся имя свойства",
"не задан xsi:type",
"ссылается на valueType"
]
},
"noSnapshot": "навык только читает — проверка через stdoutContains"
}
@@ -0,0 +1,13 @@
{
"name": "нарушен порядок верхнего уровня — платформа отвергнет пакет",
"caseFiles": [
"pkg-badorder/XDTOPackages/badorder.xml",
"pkg-badorder/XDTOPackages/badorder/Ext/Package.bin"
],
"params": { "packagePath": "pkg-badorder/XDTOPackages/badorder" },
"expectError": true,
"expect": {
"stdoutContains": ["Нарушен порядок элементов верхнего уровня"]
},
"noSnapshot": "навык только читает — проверка через stdoutContains"
}