mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-27 07:01:02 +03:00
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:
co-authored by
Claude Opus 5
parent
d05aef54b4
commit
7ca6dfa6b2
@@ -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` имеют приоритет над этим блоком.
|
||||
@@ -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,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
@@ -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>
|
||||
+18
@@ -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>
|
||||
+16
@@ -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>
|
||||
+16
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user