diff --git a/.claude/skills/xdto-compile/SKILL.md b/.claude/skills/xdto-compile/SKILL.md
index 3ce20200..e732b3dd 100644
--- a/.claude/skills/xdto-compile/SKILL.md
+++ b/.claude/skills/xdto-compile/SKILL.md
@@ -60,6 +60,12 @@ XSD выразительнее модели XDTO. Всё, что не перен
`xs:include` игнорируется: зависимости в XDTO разрешаются только по namespace,
поэтому включаемую схему нужно собрать отдельным пакетом и заменить `include` на `import`.
+## Посмотреть, что получилось
+
+Модель пакета лежит в `Ext/Package.bin`. Несмотря на расширение, это текстовый XML,
+но читать его напрямую обычно незачем: состав собранного пакета показывает
+`/xdto-info`, а полную схему — `/xdto-decompile`.
+
## Зависимости между пакетами
`` разрешается по namespace среди пакетов конфигурации
diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1
index eb6ec60b..8e981806 100644
--- a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1
+++ b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1
@@ -493,24 +493,38 @@ function Resolve-Group([System.Xml.XmlElement]$el, [string]$kind) {
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
-function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth) {
+function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth, [bool]$optionalize = $false) {
if ($depth -gt 20) { return }
foreach ($c in $particle.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.NamespaceURI -ne $XS_NS) { continue }
switch ($c.get_LocalName()) {
- "element" { [void]$elemList.Add((Build-Property $c $false)) }
+ "element" {
+ $prop = Build-Property $c $false
+ # Ветка уплощённого xs:choice обязана стать необязательной: иначе
+ # «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
+ if ($optionalize) { Set-AttrValue $prop "lowerBound" "0" }
+ [void]$elemList.Add($prop)
+ }
"any" { $isOpen.Value = $true }
"sequence" {
Warn "$typeName : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств"
- Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
+ Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
}
"choice" {
- Warn "$typeName : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён"
- Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
+ $branches = @()
+ foreach ($b in $c.ChildNodes) {
+ if ($b.NodeType -eq [System.Xml.XmlNodeType]::Element -and $b.NamespaceURI -eq $XS_NS -and $b.HasAttribute("name")) {
+ $branches += $b.GetAttribute("name")
+ }
+ }
+ $list = if ($branches.Count -gt 0) { " (" + ($branches -join ", ") + ")" } else { "" }
+ Warn ("$typeName : вложенная xs:choice уплощена — ветки$list сделаны необязательными. " +
+ "Выбор одного из вариантов не сохранён: модель не запретит заполнить сразу несколько или ни одного")
+ Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $true
}
"all" {
Warn "$typeName : xs:all трактуется как последовательность"
- Collect-Particle $c $elemList $isOpen $typeName ($depth + 1)
+ Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
}
"group" {
$g = Resolve-Group $c "group"
@@ -518,7 +532,7 @@ function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isO
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)
+ Collect-Particle $gc $elemList $isOpen $typeName ($depth + 1) $optionalize
}
}
} else {
diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.py b/.claude/skills/xdto-compile/scripts/xdto-compile.py
index 3a6bc0bf..13ef5500 100644
--- a/.claude/skills/xdto-compile/scripts/xdto-compile.py
+++ b/.claude/skills/xdto-compile/scripts/xdto-compile.py
@@ -541,7 +541,7 @@ def resolve_group(el, kind):
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
-def collect_particle(particle, elem_list, open_flag, type_name, depth):
+def collect_particle(particle, elem_list, open_flag, type_name, depth, optionalize=False):
if depth > 20:
return
for c in particle:
@@ -549,25 +549,35 @@ def collect_particle(particle, elem_list, open_flag, type_name, depth):
continue
ln = local(c)
if ln == "element":
- elem_list.append(build_property(c, False))
+ prop = build_property(c, False)
+ # Ветка уплощённого xs:choice обязана стать необязательной: иначе
+ # «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
+ if optionalize:
+ set_attr_value(prop, "lowerBound", "0")
+ elem_list.append(prop)
elif ln == "any":
open_flag[0] = True
elif ln == "sequence":
warn(type_name + " : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств")
- collect_particle(c, elem_list, open_flag, type_name, depth + 1)
+ collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
elif ln == "choice":
- warn(type_name + " : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён")
- collect_particle(c, elem_list, open_flag, type_name, depth + 1)
+ branches = [b.get("name") for b in c
+ if isinstance(b.tag, str) and etree.QName(b).namespace == XS_NS and b.get("name")]
+ lst = (" (" + ", ".join(branches) + ")") if branches else ""
+ warn(type_name + " : вложенная xs:choice уплощена — ветки" + lst + " сделаны необязательными. "
+ "Выбор одного из вариантов не сохранён: модель не запретит заполнить "
+ "сразу несколько или ни одного")
+ collect_particle(c, elem_list, open_flag, type_name, depth + 1, True)
elif ln == "all":
warn(type_name + " : xs:all трактуется как последовательность")
- collect_particle(c, elem_list, open_flag, type_name, depth + 1)
+ collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
elif ln == "group":
g = resolve_group(c, "group")
if g is not None:
for gc in g:
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
and local(gc) in ("sequence", "choice", "all"):
- collect_particle(gc, elem_list, open_flag, type_name, depth + 1)
+ collect_particle(gc, elem_list, open_flag, type_name, depth + 1, optionalize)
else:
warn(type_name + " : не найдена группа " + str(c.get("ref")) + " — её свойства в пакет не попали")
if ln in ("sequence", "choice", "all", "group"):
diff --git a/.claude/skills/xdto-edit/SKILL.md b/.claude/skills/xdto-edit/SKILL.md
index 58b53ec3..14820b73 100644
--- a/.claude/skills/xdto-edit/SKILL.md
+++ b/.claude/skills/xdto-edit/SKILL.md
@@ -23,7 +23,7 @@ allowed-tools:
| `PackagePath` | да | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml`. Псевдоним — `-Path` |
| `Operation` | да | Операция из таблицы ниже |
| `Target` | зависит | Адрес: имя типа или путь `Тип.Свойство` |
-| `Value` | зависит | Фрагмент XSD, литерал, URI или текст |
+| `Value` | зависит | Фрагмент XSD, литерал, URI или текст. `@путь` — взять содержимое из файла |
| `NoValidate` | нет | Не запускать `xdto-validate` после правки |
```powershell
@@ -60,6 +60,19 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-edit.ps1" -Pac
`/xdto-compile`. Отдельных параметров вида `-MinOccurs` нет: чтобы поменять свойство,
дай его новое объявление целиком через `replace-property`.
+Ограничение длины и прочие фасеты задаются вложенным типом:
+
+```xml
+
+
+
+
+
+```
+
+Многострочный фрагмент передавай файлом: `-Value "@frag.xsd"`. Инлайн через оболочку
+надёжен только для однострочных фрагментов без вложенных кавычек.
+
## Адресация
Путь `Тип.Свойство`. Точка безопасна: имена типов и свойств — идентификаторы 1С.
diff --git a/.claude/skills/xdto-edit/scripts/xdto-edit.ps1 b/.claude/skills/xdto-edit/scripts/xdto-edit.ps1
index 0c2812d4..d5c2c733 100644
--- a/.claude/skills/xdto-edit/scripts/xdto-edit.ps1
+++ b/.claude/skills/xdto-edit/scripts/xdto-edit.ps1
@@ -114,6 +114,18 @@ $binFile = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
$mdFile = Join-Path $xdtoRoot "$pkgName.xml"
$configXml = Join-Path $configRoot "Configuration.xml"
+# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
+# через powershell.exe -File ненадёжно: вложенные кавычки схлопываются на границе
+# процессов, и вместо понятной ошибки получается сырой сбой разбора XML.
+if ($Value -and $Value.StartsWith("@")) {
+ $valueFile = $Value.Substring(1)
+ if (-not [System.IO.Path]::IsPathRooted($valueFile)) {
+ $valueFile = Join-Path (Get-Location).Path $valueFile
+ }
+ if (-not (Test-Path $valueFile -PathType Leaf)) { throw "Файл значения не найден: $valueFile" }
+ $Value = [System.IO.File]::ReadAllText($valueFile).Trim()
+}
+
Assert-EditAllowed $pkgDir
$encBom = New-Object System.Text.UTF8Encoding($true)
@@ -328,7 +340,13 @@ function Import-Fragment($schema, [string]$xml) {
$nsAttrs += " xmlns:$($a.get_LocalName())=`"$($a.Value)`""
}
}
- $tmp.LoadXml("$xml")
+ try { $tmp.LoadXml("$xml") }
+ catch {
+ throw ("Не удалось разобрать -Value как фрагмент XML-схемы: " + $_.Exception.InnerException.Message + "`n" +
+ "Получено: " + $xml + "`n" +
+ "Если фрагмент передан инлайном, кавычки могли схлопнуться на границе процессов — " +
+ "положите его в файл и укажите -Value `"@путь`".")
+ }
$res = New-Object System.Collections.ArrayList
foreach ($c in $tmp.DocumentElement.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) {
diff --git a/.claude/skills/xdto-edit/scripts/xdto-edit.py b/.claude/skills/xdto-edit/scripts/xdto-edit.py
index fd325114..0a551f4a 100644
--- a/.claude/skills/xdto-edit/scripts/xdto-edit.py
+++ b/.claude/skills/xdto-edit/scripts/xdto-edit.py
@@ -145,6 +145,18 @@ bin_file = os.path.join(pkg_dir, "Ext", "Package.bin")
md_file = os.path.join(xdto_root, pkg_name + ".xml")
config_xml = os.path.join(config_root, "Configuration.xml")
+# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
+# ненадёжно: вложенные кавычки схлопываются на границе процессов, и вместо понятной
+# ошибки получается сырой сбой разбора XML.
+if args.Value.startswith("@"):
+ value_file = args.Value[1:]
+ if not os.path.isabs(value_file):
+ value_file = os.path.join(os.getcwd(), value_file)
+ if not os.path.isfile(value_file):
+ die("Файл значения не найден: " + value_file)
+ with open(value_file, encoding="utf-8-sig") as f:
+ args.Value = f.read().strip()
+
assert_edit_allowed(pkg_dir)
SKILLS = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
@@ -323,7 +335,13 @@ def import_fragment(schema, xml):
if px and px not in ns:
ns[px] = uri
decls = " ".join(f'xmlns:{k}="{v}"' for k, v in ns.items())
- wrapped = etree.fromstring(f"{xml}".encode("utf-8"))
+ try:
+ wrapped = etree.fromstring(f"{xml}".encode("utf-8"))
+ except etree.XMLSyntaxError as e:
+ die("Не удалось разобрать -Value как фрагмент XML-схемы: " + str(e) + "\n"
+ + "Получено: " + xml + "\n"
+ + "Если фрагмент передан инлайном, кавычки могли схлопнуться на границе "
+ 'процессов — положите его в файл и укажите -Value "@путь".')
res = [c for c in wrapped if isinstance(c.tag, str)]
if not res:
die(f"Во фрагменте нет ни одного элемента: {xml}")
diff --git a/.claude/skills/xdto-info/SKILL.md b/.claude/skills/xdto-info/SKILL.md
index d6e3c30e..0d4ee9bd 100644
--- a/.claude/skills/xdto-info/SKILL.md
+++ b/.claude/skills/xdto-info/SKILL.md
@@ -1,7 +1,7 @@
---
name: xdto-info
description: Анализ структуры пакета XDTO 1С — типы, свойства, точки входа. Используй как подготовительный шаг при написании кода, создающего и заполняющего объект XDTO, при разборе входящего XML, а также чтобы узнать, какие пакеты есть в конфигурации
-argument-hint: [-Namespace |-Package <имя>] [-Name <Тип>] [-Depth N] [-Mode used-by] [-Limit N] [-Offset N] [-OutFile <файл>]
+argument-hint: [-Namespace |-Package <имя>] [-Name <Тип>] [-Depth N] [-RequiredOnly] [-Mode used-by] [-Limit N] [-Offset N] [-OutFile <файл>]
allowed-tools:
- Bash
- Read
@@ -23,6 +23,7 @@ allowed-tools:
| `Package` | нет | Выбрать пакет по имени объекта метаданных |
| `Name` | нет | Имя типа. Без выбранного пакета ищется по всей конфигурации |
| `Depth` | нет | Глубина разузлования вложенных объектов. По умолчанию 1 |
+| `RequiredOnly` | нет | Оставить только обязательные свойства — скелет для «заполни обязательное». Необязательный объект уходит вместе со своим содержимым |
| `Mode` | нет | `used-by` — показать, кто ссылается на тип |
| `Limit` / `Offset` | нет | Пагинация. По умолчанию 150 строк |
| `OutFile` | нет | Записать результат в файл (UTF-8 BOM) |
diff --git a/.claude/skills/xdto-info/scripts/xdto-info.ps1 b/.claude/skills/xdto-info/scripts/xdto-info.ps1
index 60c17243..c4041857 100644
--- a/.claude/skills/xdto-info/scripts/xdto-info.ps1
+++ b/.claude/skills/xdto-info/scripts/xdto-info.ps1
@@ -10,6 +10,7 @@ param(
[ValidateSet("auto", "used-by")]
[string]$Mode = "auto",
[int]$Depth = 1,
+ [switch]$RequiredOnly,
[int]$Limit = 150,
[int]$Offset = 0,
[string]$OutFile
@@ -27,6 +28,13 @@ $MD_NS = "http://v8.1c.ru/8.3/MDClasses"
$sb = New-Object System.Text.StringBuilder
function O([string]$line = "") { [void]$sb.AppendLine($line) }
+function Fail([string]$msg) {
+ # Отрицательный результат поиска — не исключение: печатаем сообщение
+ # и выходим с кодом 1, без стектрейса PowerShell
+ Write-Host $msg
+ exit 1
+}
+
function Flush-Output {
$text = $sb.ToString().TrimEnd()
if ($OutFile) {
@@ -46,7 +54,7 @@ function Flush-Output {
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
$PackagePath = Join-Path (Get-Location).Path $PackagePath
}
-if (-not (Test-Path $PackagePath)) { throw "Путь не найден: $PackagePath" }
+if (-not (Test-Path $PackagePath)) { Fail "Путь не найден: $PackagePath" }
$configRoot = $null
$directPkgDir = $null
@@ -70,7 +78,7 @@ if (Test-Path (Join-Path $PackagePath "Configuration.xml")) {
$configRoot = Split-Path (Split-Path $stem -Parent) -Parent
}
}
-if (-not $configRoot -and -not $directPkgDir) { throw "Не удалось определить пакет или конфигурацию по пути: $PackagePath" }
+if (-not $configRoot -and -not $directPkgDir) { Fail "Не удалось определить пакет или конфигурацию по пути: $PackagePath" }
# Sort-Object в PowerShell сортирует по культуре, sorted() в Python — по кодам.
# Для паритета портов сортируем ординально в обоих.
@@ -130,7 +138,7 @@ if ($directPkgDir -and $packages.Count -eq 0) {
$p = Read-Package $directPkgDir
if ($p) { [void]$packages.Add($p); $byNamespace[$p.Namespace] = $p }
}
-if ($packages.Count -eq 0) { throw "Пакеты XDTO не найдены: $PackagePath" }
+if ($packages.Count -eq 0) { Fail "Пакеты XDTO не найдены: $PackagePath" }
# --- Type notation: XSD -> 1С ---------------------------------------------------
@@ -389,6 +397,20 @@ function Get-PropRows([System.Xml.XmlElement]$type, $pkg, [int]$depth, [int]$ind
return ,$rows
}
+# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
+# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
+function Select-Required($rows) {
+ $res = New-Object System.Collections.ArrayList
+ $cutFrom = -1
+ foreach ($r in $rows) {
+ if ($cutFrom -ge 0 -and $r.Indent -gt $cutFrom) { continue }
+ $cutFrom = -1
+ if ($r.Flags -notcontains "обязательный") { $cutFrom = $r.Indent; continue }
+ [void]$res.Add($r)
+ }
+ return ,$res
+}
+
function Write-Rows($rows) {
if ($rows.Count -eq 0) { O " (нет свойств)"; return }
$shown = $rows
@@ -450,7 +472,10 @@ function Show-PackageOverview($pkg) {
O " $i → $dep"
}
}
- if ($pkg.GlobalProps.Count -gt 0) {
+ if ($pkg.GlobalProps.Count -eq 0) {
+ O ""
+ O "Точки входа: нет — пакет не объявляет корневых элементов документа"
+ } else {
O ""
O "Точки входа ($($pkg.GlobalProps.Count)) — корневые элементы документа:"
foreach ($gp in $pkg.GlobalProps) {
@@ -540,13 +565,32 @@ function Show-Type($pkg, [string]$typeName) {
[void]$seen.Add("$($pkg.Namespace)#$typeName")
$rows = Get-PropRows $el $pkg $Depth 0 $seen
$own = @($rows | Where-Object { $_.Indent -eq 0 })
- O "Свойства ($($own.Count)):"
+ if ($RequiredOnly) {
+ $all = $rows.Count
+ $rows = Select-Required $rows
+ $ownReq = @($rows | Where-Object { $_.Indent -eq 0 })
+ # Фильтр обязан сообщать о себе: иначе список читается как полный
+ O "Свойства: обязательных $($ownReq.Count) из $($own.Count) (-RequiredOnly; скрыто строк: $($all - $rows.Count))"
+ } else {
+ O "Свойства ($($own.Count)):"
+ }
Write-Rows $rows
Write-Legend $rows
O ""
O "Создание:"
O " Тип = ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`");"
O " Объект = ФабрикаXDTO.Создать(Тип);"
+ # Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
+ # ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
+ $nested = @($rows | Where-Object { $_.Indent -eq 0 -and $_.Type -like "объект *" } | Select-Object -First 1)
+ if ($nested.Count -gt 0) {
+ O " Вложенный = ФабрикаXDTO.Создать(Тип.Свойства.Получить(`"$($nested[0].Name)`").Тип);"
+ }
+ $textProp = @($rows | Where-Object { $_.Flags -contains "значение элемента" } | Select-Object -First 1)
+ if ($textProp.Count -gt 0) {
+ O " // тип со значением элемента: значение задаётся при создании"
+ O " Узел = ФабрикаXDTO.Создать(ТипУзла, Значение);"
+ }
}
function Show-UsedBy([string]$typeName, $ownerPkg) {
@@ -588,15 +632,15 @@ if ($directPkgDir) {
}
if (-not $selected -and $Namespace) {
$selected = $packages | Where-Object { $_.Namespace -eq $Namespace } | Select-Object -First 1
- if (-not $selected) { throw "Пакет с namespace `"$Namespace`" не найден. Список: -PackagePath <корень> без параметров" }
+ if (-not $selected) { Fail "Пакет с namespace `"$Namespace`" не найден. Список: -PackagePath <корень> без параметров" }
}
if (-not $selected -and $Package) {
$selected = $packages | Where-Object { $_.Name -eq $Package } | Select-Object -First 1
- if (-not $selected) { throw "Пакет `"$Package`" не найден. Список: -PackagePath <корень> без параметров" }
+ if (-not $selected) { Fail "Пакет `"$Package`" не найден. Список: -PackagePath <корень> без параметров" }
}
if ($Mode -eq "used-by") {
- if (-not $Name) { throw "Режим used-by требует -Name <Тип>" }
+ if (-not $Name) { Fail "Режим used-by требует -Name <Тип>" }
$ownerPkg = $selected
if (-not $ownerPkg) { $ownerPkg = ($packages | Where-Object { $_.Types.ContainsKey($Name) } | Select-Object -First 1) }
Show-UsedBy $Name $ownerPkg
@@ -608,7 +652,7 @@ if ($Name) {
if (-not $selected) {
# Тип известен, пакет — нет: ищем по всей конфигурации
$found = @($packages | Where-Object { $_.Types.ContainsKey($Name) })
- if ($found.Count -eq 0) { throw "Тип `"$Name`" не найден ни в одном пакете конфигурации" }
+ if ($found.Count -eq 0) { Fail "Тип `"$Name`" не найден ни в одном пакете конфигурации" }
if ($found.Count -gt 1) {
O "=== Тип `"$Name`" найден в нескольких пакетах ($($found.Count)) ==="
O "Уточните через -Namespace или -Package:"
@@ -620,7 +664,7 @@ if ($Name) {
$selected = $found[0]
}
if (-not $selected.Types.ContainsKey($Name)) {
- throw "В пакете $($selected.Name) нет типа `"$Name`". Список типов: тот же вызов без -Name"
+ Fail "В пакете $($selected.Name) нет типа `"$Name`". Список типов: тот же вызов без -Name"
}
Show-Type $selected $Name
Flush-Output
diff --git a/.claude/skills/xdto-info/scripts/xdto-info.py b/.claude/skills/xdto-info/scripts/xdto-info.py
index 38b350d2..76b5524e 100644
--- a/.claude/skills/xdto-info/scripts/xdto-info.py
+++ b/.claude/skills/xdto-info/scripts/xdto-info.py
@@ -20,6 +20,7 @@ parser.add_argument("-Namespace", default="")
parser.add_argument("-Name", default="")
parser.add_argument("-Mode", default="auto", choices=["auto", "used-by"])
parser.add_argument("-Depth", type=int, default=1)
+parser.add_argument("-RequiredOnly", action="store_true")
parser.add_argument("-Limit", type=int, default=150)
parser.add_argument("-Offset", type=int, default=0)
parser.add_argument("-OutFile", default="")
@@ -48,7 +49,9 @@ def flush_output():
def die(msg):
- print(msg, file=sys.stderr)
+ # Отрицательный результат поиска — не исключение: сообщение и код 1,
+ # без трассировки
+ print(msg)
sys.exit(1)
@@ -406,6 +409,22 @@ def get_prop_rows(type_el, pkg, depth, indent, seen):
return rows
+# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
+# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
+def select_required(rows):
+ res = []
+ cut_from = -1
+ for r in rows:
+ if cut_from >= 0 and r["Indent"] > cut_from:
+ continue
+ cut_from = -1
+ if "обязательный" not in r["Flags"]:
+ cut_from = r["Indent"]
+ continue
+ res.append(r)
+ return res
+
+
def write_rows(rows):
if not rows:
O(" (нет свойств)")
@@ -456,7 +475,10 @@ def show_package_overview(pkg):
for i in pkg.Imports:
dep = by_namespace[i].Name if i in by_namespace else "(пакет не найден)"
O(f" {i} → {dep}")
- if pkg.GlobalProps:
+ if not pkg.GlobalProps:
+ O("")
+ O("Точки входа: нет — пакет не объявляет корневых элементов документа")
+ else:
O("")
O(f"Точки входа ({len(pkg.GlobalProps)}) — корневые элементы документа:")
for gp in pkg.GlobalProps:
@@ -546,13 +568,29 @@ def show_type(pkg, type_name):
seen = {f"{pkg.Namespace}#{type_name}"}
rows = get_prop_rows(el, pkg, DEPTH, 0, seen)
own = [r for r in rows if r["Indent"] == 0]
- O(f"Свойства ({len(own)}):")
+ if args.RequiredOnly:
+ total = len(rows)
+ rows = select_required(rows)
+ own_req = [r for r in rows if r["Indent"] == 0]
+ # Фильтр обязан сообщать о себе: иначе список читается как полный
+ O(f"Свойства: обязательных {len(own_req)} из {len(own)} "
+ f"(-RequiredOnly; скрыто строк: {total - len(rows)})")
+ else:
+ O(f"Свойства ({len(own)}):")
write_rows(rows)
write_legend(rows)
O("")
O("Создание:")
O(f' Тип = ФабрикаXDTO.Тип("{pkg.Namespace}", "{type_name}");')
O(" Объект = ФабрикаXDTO.Создать(Тип);")
+ # Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
+ # ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
+ nested = next((r for r in rows if r["Indent"] == 0 and r["Type"].startswith("объект ")), None)
+ if nested:
+ O(f' Вложенный = ФабрикаXDTO.Создать(Тип.Свойства.Получить("{nested["Name"]}").Тип);')
+ if any("значение элемента" in r["Flags"] for r in rows):
+ O(" // тип со значением элемента: значение задаётся при создании")
+ O(" Узел = ФабрикаXDTO.Создать(ТипУзла, Значение);")
def show_used_by(type_name, owner_pkg):
diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.ps1 b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1
index 434dc452..d8deff02 100644
--- a/.claude/skills/xdto-validate/scripts/xdto-validate.ps1
+++ b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1
@@ -266,7 +266,7 @@ if ($anyTypeProps.Count -gt 0 -and $imports.Count -gt 0) {
$names = @()
foreach ($p in $anyTypeProps) { if ($p.HasAttribute("name")) { $names += $p.GetAttribute("name") } }
$shown = ($names | Select-Object -First 5) -join ", "
- Report-Warn "Свойств с type=`"xs:anyType`": $($anyTypeProps.Count) при объявленных импортах ($shown). Возможна тихая деградация: при импорте XML-схемы платформа заменяет неразрешённый чужой тип на anyType без ошибки"
+ Report-Warn "Свойств с type=`"xs:anyType`": $($anyTypeProps.Count) при объявленных импортах ($shown). Тип не разрешён — заполнить структурно такое свойство нельзя. Обычно это след импорта XML-схемы: платформа заменяет неразрешённый чужой тип на anyType без ошибки"
}
# --- 7. Unused imports ---
diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.py b/.claude/skills/xdto-validate/scripts/xdto-validate.py
index f275f59a..2e3c0bc9 100644
--- a/.claude/skills/xdto-validate/scripts/xdto-validate.py
+++ b/.claude/skills/xdto-validate/scripts/xdto-validate.py
@@ -293,7 +293,7 @@ if any_type_props and imports:
shown = ", ".join(names[:5])
report_warn(
f'Свойств с type="xs:anyType": {len(any_type_props)} при объявленных импортах ({shown}). '
- "Возможна тихая деградация: при импорте XML-схемы платформа заменяет неразрешённый "
+ "Тип не разрешён — заполнить структурно такое свойство нельзя. Обычно это след импорта XML-схемы: платформа заменяет неразрешённый "
"чужой тип на anyType без ошибки"
)
diff --git a/tests/skills/cases/xdto-compile/snapshots/nested-particles/XDTOPackages/nested_particles/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/nested-particles/XDTOPackages/nested_particles/Ext/Package.bin
index 9b22c019..296ed2b9 100644
--- a/tests/skills/cases/xdto-compile/snapshots/nested-particles/XDTOPackages/nested_particles/Ext/Package.bin
+++ b/tests/skills/cases/xdto-compile/snapshots/nested-particles/XDTOPackages/nested_particles/Ext/Package.bin
@@ -2,8 +2,8 @@
-
-
+
+
diff --git a/tests/skills/cases/xdto-info/required-only.json b/tests/skills/cases/xdto-info/required-only.json
new file mode 100644
index 00000000..1e37bf4e
--- /dev/null
+++ b/tests/skills/cases/xdto-info/required-only.json
@@ -0,0 +1,31 @@
+{
+ "name": "-RequiredOnly: скелет обязательных, необязательный объект уходит с поддеревом",
+ "caseFiles": [
+ "nested.xsd"
+ ],
+ "preRun": [
+ {
+ "script": "xdto-compile/scripts/xdto-compile",
+ "args": {
+ "-XsdPath": "{workDir}/nested.xsd",
+ "-OutputDir": "{workDir}"
+ }
+ }
+ ],
+ "params": {
+ "packagePath": "XDTOPackages/nested",
+ "outFile": "required-only.txt"
+ },
+ "args_extra": [
+ "-Name",
+ "Документ",
+ "-Depth",
+ "3",
+ "-RequiredOnly"
+ ],
+ "expect": {
+ "files": [
+ "required-only.txt"
+ ]
+ }
+}
diff --git a/tests/skills/cases/xdto-info/snapshots/cross-package/cross-package.txt b/tests/skills/cases/xdto-info/snapshots/cross-package/cross-package.txt
index d0dec5ee..89eb0014 100644
--- a/tests/skills/cases/xdto-info/snapshots/cross-package/cross-package.txt
+++ b/tests/skills/cases/xdto-info/snapshots/cross-package/cross-package.txt
@@ -14,3 +14,4 @@
Создание:
Тип = ФабрикаXDTO.Тип("urn:test:uses", "Заявка");
Объект = ФабрикаXDTO.Создать(Тип);
+ Вложенный = ФабрикаXDTO.Создать(Тип.Свойства.Получить("Вид").Тип);
diff --git a/tests/skills/cases/xdto-info/snapshots/depth-anon-text/depth-anon-text.txt b/tests/skills/cases/xdto-info/snapshots/depth-anon-text/depth-anon-text.txt
index a0361ec1..43a48f60 100644
--- a/tests/skills/cases/xdto-info/snapshots/depth-anon-text/depth-anon-text.txt
+++ b/tests/skills/cases/xdto-info/snapshots/depth-anon-text/depth-anon-text.txt
@@ -18,3 +18,6 @@
Создание:
Тип = ФабрикаXDTO.Тип("urn:test:nested", "Документ");
Объект = ФабрикаXDTO.Создать(Тип);
+ Вложенный = ФабрикаXDTO.Создать(Тип.Свойства.Получить("Реквизиты").Тип);
+ // тип со значением элемента: значение задаётся при создании
+ Узел = ФабрикаXDTO.Создать(ТипУзла, Значение);
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/Configuration.xml b/tests/skills/cases/xdto-info/snapshots/required-only/Configuration.xml
new file mode 100644
index 00000000..4a727d54
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/Configuration.xml
@@ -0,0 +1,252 @@
+
+
+
+
+
+ UUID-002
+ UUID-003
+
+
+ UUID-004
+ UUID-005
+
+
+ UUID-006
+ UUID-007
+
+
+ UUID-008
+ UUID-009
+
+
+ UUID-010
+ UUID-011
+
+
+ UUID-012
+ UUID-013
+
+
+ UUID-014
+ UUID-015
+
+
+
+ TestConfig
+
+
+ ru
+ TestConfig
+
+
+
+
+ Version8_3_24
+ ManagedApplication
+
+ PlatformApplication
+
+ Russian
+
+
+
+
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Biometrics
+ true
+
+
+ Location
+ false
+
+
+ BackgroundLocation
+ false
+
+
+ BluetoothPrinters
+ false
+
+
+ WiFiPrinters
+ false
+
+
+ Contacts
+ false
+
+
+ Calendars
+ false
+
+
+ PushNotifications
+ false
+
+
+ LocalNotifications
+ false
+
+
+ InAppPurchases
+ false
+
+
+ PersonalComputerFileExchange
+ false
+
+
+ Ads
+ false
+
+
+ NumberDialing
+ false
+
+
+ CallProcessing
+ false
+
+
+ CallLog
+ false
+
+
+ AutoSendSMS
+ false
+
+
+ ReceiveSMS
+ false
+
+
+ SMSLog
+ false
+
+
+ Camera
+ false
+
+
+ Microphone
+ false
+
+
+ MusicLibrary
+ false
+
+
+ PictureAndVideoLibraries
+ false
+
+
+ AudioPlaybackAndVibration
+ false
+
+
+ BackgroundAudioPlaybackAndVibration
+ false
+
+
+ InstallPackages
+ false
+
+
+ OSBackup
+ true
+
+
+ ApplicationUsageStatistics
+ false
+
+
+ BarcodeScanning
+ false
+
+
+ BackgroundAudioRecording
+ false
+
+
+ AllFilesAccess
+ false
+
+
+ Videoconferences
+ false
+
+
+ NFC
+ false
+
+
+ DocumentScanning
+ false
+
+
+ SpeechToText
+ false
+
+
+ Geofences
+ false
+
+
+ IncomingShareRequests
+ false
+
+
+ AllIncomingShareRequestsTypesProcessing
+ false
+
+
+
+
+
+ Normal
+
+
+ Language.Русский
+
+
+
+
+
+ Managed
+ NotAutoFree
+ DontUse
+ DontUse
+ TaxiEnableVersion8_2
+ DontUse
+ Version8_3_24
+
+
+
+ Русский
+ nested
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-info/snapshots/required-only/Ext/ClientApplicationInterface.xml
new file mode 100644
index 00000000..3c1161b2
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/Ext/ClientApplicationInterface.xml
@@ -0,0 +1,18 @@
+
+
+
+
+ UUID-002
+
+
+
+
+ UUID-004
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/Languages/Русский.xml b/tests/skills/cases/xdto-info/snapshots/required-only/Languages/Русский.xml
new file mode 100644
index 00000000..37c60d78
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/Languages/Русский.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ Русский
+
+
+ ru
+ Русский
+
+
+
+ ru
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested.xml b/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested.xml
new file mode 100644
index 00000000..c84668ba
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested.xml
@@ -0,0 +1,16 @@
+
+
+
+
+ nested
+
+
+ ru
+ nested
+
+
+
+ urn:test:nested
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested/Ext/Package.bin b/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested/Ext/Package.bin
new file mode 100644
index 00000000..0e2da7d0
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/XDTOPackages/nested/Ext/Package.bin
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/nested.xsd b/tests/skills/cases/xdto-info/snapshots/required-only/nested.xsd
new file mode 100644
index 00000000..b5b6d8fb
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/nested.xsd
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/skills/cases/xdto-info/snapshots/required-only/required-only.txt b/tests/skills/cases/xdto-info/snapshots/required-only/required-only.txt
new file mode 100644
index 00000000..0272951a
--- /dev/null
+++ b/tests/skills/cases/xdto-info/snapshots/required-only/required-only.txt
@@ -0,0 +1,20 @@
+=== Тип XDTO: Документ === (глубина 3)
+Пакет: nested · urn:test:nested
+
+Свойства: обязательных 2 из 3 (-RequiredOnly; скрыто строк: 2)
+ Реквизиты объект Реквизиты [обязательный]
+ Наименование Строка [обязательный]
+ Встроенный объект (анонимный) [обязательный]
+ Ид Строка [обязательный]
+ __content Дата [обязательный, значение элемента]
+
+Обозначения:
+ объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth
+ значение элемента — собственное значение узла XML
+
+Создание:
+ Тип = ФабрикаXDTO.Тип("urn:test:nested", "Документ");
+ Объект = ФабрикаXDTO.Создать(Тип);
+ Вложенный = ФабрикаXDTO.Создать(Тип.Свойства.Получить("Реквизиты").Тип);
+ // тип со значением элемента: значение задаётся при создании
+ Узел = ФабрикаXDTO.Создать(ТипУзла, Значение);
diff --git a/tests/skills/cases/xdto-validate/silent-anytype.json b/tests/skills/cases/xdto-validate/silent-anytype.json
index bf85e821..406fc18a 100644
--- a/tests/skills/cases/xdto-validate/silent-anytype.json
+++ b/tests/skills/cases/xdto-validate/silent-anytype.json
@@ -7,7 +7,7 @@
"params": { "packagePath": "pkg-anytype/XDTOPackages/anytype" },
"expect": {
"stdoutContains": [
- "тихая деградация",
+ "Тип не разрешён",
"признак неразрешённой зависимости"
]
},