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": [ - "тихая деградация", + "Тип не разрешён", "признак неразрешённой зависимости" ] },