mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-27 07:01:02 +03:00
fix(xdto-compile,xdto-info,xdto-edit): правки по итогам прогона на субагентах
Четырём субагентам выданы реалистичные задачи по песочнице, навыки в формулировках не назывались. Разбор — в debug/xdto/FINDINGS.md, §13. ГЛАВНОЕ — дефект компилятора. При уплощении вложенного xs:choice ветки оставались обязательными: схема «самовывоз ИЛИ адрес доставки» давала пакет, требующий заполнить оба, и ни один реальный документ в него не ложился. Компилятор предупреждал о потере выбора, но молчал о последствии, а валидатор показывал «0 ошибок, 0 предупреждений» — структурно пакет корректен, семантически мёртв. Теперь ветки становятся необязательными (единственное уплощение, оставляющее тип заполнимым), предупреждение называет их поимённо. Корневой xs:choice не затронут: он отображается в ordered="false". xdto-edit: -Value "@файл" по конвенции skd-edit — на кавычках при инлайновой передаче XSD споткнулись двое агентов из четырёх, причём сырой LoadXml уводил чинить схему вместо транспорта. При сбое разбора теперь понятное сообщение. xdto-info: поиск, законно ничего не нашедший, падал throw'ом со стектрейсом и читался как поломка инструмента — теперь строка и exit 1. Блок «Создание» покрывал только корневой тип, хотя вся реальная работа в XDTO — вложенные и анонимные типы; добавлены рецепты по факту наличия. Отсутствие раздела «Точки входа» было неоднозначным — теперь явная строка. Новый -RequiredOnly даёт скелет «заполни обязательное»: необязательный объект уходит вместе с поддеревом. По умолчанию выключен — иначе список читался бы как полный. xdto-validate: предупреждение про anyType описывало историю («платформа заменяет при импорте»), хотя в файле уже зафиксирован anyType; сначала состояние, потом происхождение. Проверено: 40 тестов на обоих рантаймах, round-trip 760/760, валидатор 0 ложных срабатываний на корпусе. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
1a1bbbac6f
commit
6486f433de
@@ -60,6 +60,12 @@ XSD выразительнее модели XDTO. Всё, что не перен
|
||||
`xs:include` игнорируется: зависимости в XDTO разрешаются только по namespace,
|
||||
поэтому включаемую схему нужно собрать отдельным пакетом и заменить `include` на `import`.
|
||||
|
||||
## Посмотреть, что получилось
|
||||
|
||||
Модель пакета лежит в `Ext/Package.bin`. Несмотря на расширение, это текстовый XML,
|
||||
но читать его напрямую обычно незачем: состав собранного пакета показывает
|
||||
`/xdto-info`, а полную схему — `/xdto-decompile`.
|
||||
|
||||
## Зависимости между пакетами
|
||||
|
||||
`<xs:import namespace="…"/>` разрешается по namespace среди пакетов конфигурации
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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"):
|
||||
|
||||
@@ -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
|
||||
<xs:element name="Комментарий" minOccurs="0">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string"><xs:maxLength value="200"/></xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
```
|
||||
|
||||
Многострочный фрагмент передавай файлом: `-Value "@frag.xsd"`. Инлайн через оболочку
|
||||
надёжен только для однострочных фрагментов без вложенных кавычек.
|
||||
|
||||
## Адресация
|
||||
|
||||
Путь `Тип.Свойство`. Точка безопасна: имена типов и свойств — идентификаторы 1С.
|
||||
|
||||
@@ -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("<wrap$nsAttrs>$xml</wrap>")
|
||||
try { $tmp.LoadXml("<wrap$nsAttrs>$xml</wrap>") }
|
||||
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) {
|
||||
|
||||
@@ -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"<wrap {decls}>{xml}</wrap>".encode("utf-8"))
|
||||
try:
|
||||
wrapped = etree.fromstring(f"<wrap {decls}>{xml}</wrap>".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}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: xdto-info
|
||||
description: Анализ структуры пакета XDTO 1С — типы, свойства, точки входа. Используй как подготовительный шаг при написании кода, создающего и заполняющего объект XDTO, при разборе входящего XML, а также чтобы узнать, какие пакеты есть в конфигурации
|
||||
argument-hint: <PackagePath> [-Namespace <URI>|-Package <имя>] [-Name <Тип>] [-Depth N] [-Mode used-by] [-Limit N] [-Offset N] [-OutFile <файл>]
|
||||
argument-hint: <PackagePath> [-Namespace <URI>|-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) |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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 ---
|
||||
|
||||
@@ -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 без ошибки"
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user