From d05aef54b4d1b4909d21130412a7701141b6ea7b Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sat, 25 Jul 2026 21:21:57 +0300 Subject: [PATCH] =?UTF-8?q?feat(xdto-compile,xdto-decompile,xdto-validate)?= =?UTF-8?q?:=20=D0=BF=D0=B0=D0=BA=D0=B5=D1=82=D1=8B=20XDTO=20=D0=B8=D0=B7?= =?UTF-8?q?=20XML-=D1=81=D1=85=D0=B5=D0=BC=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Три навыка для работы с пакетами XDTO. Формат описания — обычная XSD, своего DSL нет: рутину снимает конвертер (локальные объявления префиксов dNpM на каждой ссылке, инвертированная кратность lowerBound/upperBound, фасеты атрибутами вместо дочерних элементов, обязательный порядок элементов верхнего уровня). То, чего XSD выразить не может — nillable у атрибута, qualified у свойства, «атрибут записан явно» — едет атрибутами из пространства имён модели XDTO по правилу «то же имя, что в Package.bin». Свойства объекта метаданных живут в xs:appinfo, поэтому пара decompile → compile замыкается без потерь. Инвариант bin → xsd → bin проверен побайтово на 760 пакетах выгрузок Бухгалтерии и ERP 8.3.24 (харнесс debug/xdto/roundtrip-corpus.mjs). Сборка из рукописной XSD проверена загрузкой в базу 8.3.24 — именно она вскрыла обязательный порядок import→property→valueType→objectType, невидимый для корпусной сверки: все выгрузки уже канонические. xdto-validate ловит два класса тихих дефектов, которые платформа не диагностирует: подмену неразрешённого чужого типа на xs:anyType при импорте XML-схемы и nillable у свойства-атрибута, теряемый экспортом схемы в Конфигураторе. Тесты: 18 снэпшот-кейсов на синтетических схемах (типовые конфигурации в репозиторий не тащим), паритет PS↔PY на общих эталонах. Раннер получил caseFiles — копирование файлов кейса в workDir для навыков с файловым, а не JSON входом. Co-Authored-By: Claude Opus 5 (1M context) --- .claude/skills/xdto-compile/SKILL.md | 93 +++ .../xdto-compile/scripts/xdto-compile.ps1 | 772 +++++++++++++++++ .../xdto-compile/scripts/xdto-compile.py | 780 ++++++++++++++++++ .claude/skills/xdto-decompile/SKILL.md | 68 ++ .../xdto-decompile/scripts/xdto-decompile.ps1 | 603 ++++++++++++++ .../xdto-decompile/scripts/xdto-decompile.py | 567 +++++++++++++ .claude/skills/xdto-validate/SKILL.md | 71 ++ .../xdto-validate/scripts/xdto-validate.ps1 | 402 +++++++++ .../xdto-validate/scripts/xdto-validate.py | 386 +++++++++ .gitattributes | 5 + docs/1c-specs-index.md | 2 +- docs/1c-xdto-spec.md | 329 ++++++++ docs/xdto-dsl-spec.md | 173 ++++ tests/skills/cases/xdto-compile/_skill.json | 17 + .../cases/xdto-compile/annotations.json | 9 + .../skills/cases/xdto-compile/annotations.xsd | 28 + .../cases/xdto-compile/anon-and-content.json | 9 + .../cases/xdto-compile/anon-and-content.xsd | 32 + .../cases/xdto-compile/cardinality.json | 9 + .../skills/cases/xdto-compile/cardinality.xsd | 19 + .../cases/xdto-compile/complex-types.json | 9 + .../cases/xdto-compile/complex-types.xsd | 40 + tests/skills/cases/xdto-compile/minimal.json | 9 + tests/skills/cases/xdto-compile/minimal.xsd | 10 + tests/skills/cases/xdto-compile/refs.json | 9 + tests/skills/cases/xdto-compile/refs.xsd | 21 + .../cases/xdto-compile/simple-types.json | 9 + .../cases/xdto-compile/simple-types.xsd | 35 + .../snapshots/annotations/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../annotations/Languages/Русский.xml | 16 + .../XDTOPackages/ПакетСАннотациями.xml | 20 + .../ПакетСАннотациями/Ext/Package.bin | 11 + .../snapshots/annotations/annotations.xsd | 28 + .../anon-and-content/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../anon-and-content/Languages/Русский.xml | 16 + .../XDTOPackages/anon_and_content.xml | 16 + .../anon_and_content/Ext/Package.bin | 19 + .../anon-and-content/anon-and-content.xsd | 32 + .../snapshots/cardinality/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../cardinality/Languages/Русский.xml | 16 + .../cardinality/XDTOPackages/cardinality.xml | 16 + .../XDTOPackages/cardinality/Ext/Package.bin | 14 + .../snapshots/cardinality/cardinality.xsd | 19 + .../snapshots/complex-types/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../complex-types/Languages/Русский.xml | 16 + .../XDTOPackages/complex_types.xml | 16 + .../complex_types/Ext/Package.bin | 22 + .../snapshots/complex-types/complex-types.xsd | 40 + .../snapshots/minimal/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../snapshots/minimal/Languages/Русский.xml | 16 + .../minimal/XDTOPackages/minimal.xml | 16 + .../XDTOPackages/minimal/Ext/Package.bin | 6 + .../snapshots/minimal/minimal.xsd | 10 + .../snapshots/refs/Configuration.xml | 252 ++++++ .../refs/Ext/ClientApplicationInterface.xml | 18 + .../snapshots/refs/Languages/Русский.xml | 16 + .../snapshots/refs/XDTOPackages/refs.xml | 16 + .../refs/XDTOPackages/refs/Ext/Package.bin | 11 + .../xdto-compile/snapshots/refs/refs.xsd | 21 + .../snapshots/simple-types/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../simple-types/Languages/Русский.xml | 16 + .../XDTOPackages/simple_types.xml | 16 + .../XDTOPackages/simple_types/Ext/Package.bin | 13 + .../snapshots/simple-types/simple-types.xsd | 35 + .../top-level-order/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../top-level-order/Languages/Русский.xml | 16 + .../XDTOPackages/top_level_order.xml | 16 + .../top_level_order/Ext/Package.bin | 8 + .../top-level-order/top-level-order.xsd | 20 + .../cases/xdto-compile/top-level-order.json | 9 + .../cases/xdto-compile/top-level-order.xsd | 20 + tests/skills/cases/xdto-decompile/_skill.json | 12 + .../cases/xdto-decompile/annotations.json | 14 + .../cases/xdto-decompile/annotations.xsd | 28 + .../xdto-decompile/anon-and-content.json | 14 + .../cases/xdto-decompile/anon-and-content.xsd | 32 + .../cases/xdto-decompile/complex-types.json | 14 + .../cases/xdto-decompile/complex-types.xsd | 40 + .../skills/cases/xdto-decompile/minimal.json | 14 + tests/skills/cases/xdto-decompile/minimal.xsd | 10 + .../cases/xdto-decompile/simple-types.json | 14 + .../cases/xdto-decompile/simple-types.xsd | 35 + .../snapshots/annotations/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../annotations/Languages/Русский.xml | 16 + .../XDTOPackages/ПакетСАннотациями.xml | 20 + .../ПакетСАннотациями/Ext/Package.bin | 11 + .../snapshots/annotations/annotations.xsd | 28 + .../snapshots/annotations/roundtrip.xsd | 25 + .../anon-and-content/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../anon-and-content/Languages/Русский.xml | 16 + .../XDTOPackages/anon_and_content.xml | 16 + .../anon_and_content/Ext/Package.bin | 19 + .../anon-and-content/anon-and-content.xsd | 32 + .../snapshots/anon-and-content/roundtrip.xsd | 38 + .../snapshots/complex-types/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../complex-types/Languages/Русский.xml | 16 + .../XDTOPackages/complex_types.xml | 16 + .../complex_types/Ext/Package.bin | 22 + .../snapshots/complex-types/complex-types.xsd | 40 + .../snapshots/complex-types/roundtrip.xsd | 46 ++ .../snapshots/minimal/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../snapshots/minimal/Languages/Русский.xml | 16 + .../minimal/XDTOPackages/minimal.xml | 16 + .../XDTOPackages/minimal/Ext/Package.bin | 6 + .../snapshots/minimal/minimal.xsd | 10 + .../snapshots/minimal/roundtrip.xsd | 16 + .../snapshots/simple-types/Configuration.xml | 252 ++++++ .../Ext/ClientApplicationInterface.xml | 18 + .../simple-types/Languages/Русский.xml | 16 + .../XDTOPackages/simple_types.xml | 16 + .../XDTOPackages/simple_types/Ext/Package.bin | 13 + .../snapshots/simple-types/roundtrip.xsd | 41 + .../snapshots/simple-types/simple-types.xsd | 35 + tests/skills/cases/xdto-validate/_skill.json | 11 + .../cases/xdto-validate/facet-conflicts.json | 17 + .../xdto-validate/nillable-on-attribute.json | 12 + .../pkg-anytype/XDTOPackages/anytype.xml | 16 + .../XDTOPackages/anytype/Ext/Package.bin | 6 + .../pkg-facets/XDTOPackages/facets.xml | 16 + .../XDTOPackages/facets/Ext/Package.bin | 4 + .../pkg-nillattr/XDTOPackages/nillattr.xml | 16 + .../XDTOPackages/nillattr/Ext/Package.bin | 5 + .../XDTOPackages/unresolved.xml | 16 + .../XDTOPackages/unresolved/Ext/Package.bin | 6 + .../cases/xdto-validate/silent-anytype.json | 15 + .../cases/xdto-validate/unresolved-types.json | 16 + .../cases/xdto-validate/valid-package.json | 15 + tests/skills/cases/xdto-validate/valid.xsd | 16 + tests/skills/runner.mjs | 18 + 140 files changed, 9601 insertions(+), 1 deletion(-) create mode 100644 .claude/skills/xdto-compile/SKILL.md create mode 100644 .claude/skills/xdto-compile/scripts/xdto-compile.ps1 create mode 100644 .claude/skills/xdto-compile/scripts/xdto-compile.py create mode 100644 .claude/skills/xdto-decompile/SKILL.md create mode 100644 .claude/skills/xdto-decompile/scripts/xdto-decompile.ps1 create mode 100644 .claude/skills/xdto-decompile/scripts/xdto-decompile.py create mode 100644 .claude/skills/xdto-validate/SKILL.md create mode 100644 .claude/skills/xdto-validate/scripts/xdto-validate.ps1 create mode 100644 .claude/skills/xdto-validate/scripts/xdto-validate.py create mode 100644 docs/1c-xdto-spec.md create mode 100644 docs/xdto-dsl-spec.md create mode 100644 tests/skills/cases/xdto-compile/_skill.json create mode 100644 tests/skills/cases/xdto-compile/annotations.json create mode 100644 tests/skills/cases/xdto-compile/annotations.xsd create mode 100644 tests/skills/cases/xdto-compile/anon-and-content.json create mode 100644 tests/skills/cases/xdto-compile/anon-and-content.xsd create mode 100644 tests/skills/cases/xdto-compile/cardinality.json create mode 100644 tests/skills/cases/xdto-compile/cardinality.xsd create mode 100644 tests/skills/cases/xdto-compile/complex-types.json create mode 100644 tests/skills/cases/xdto-compile/complex-types.xsd create mode 100644 tests/skills/cases/xdto-compile/minimal.json create mode 100644 tests/skills/cases/xdto-compile/minimal.xsd create mode 100644 tests/skills/cases/xdto-compile/refs.json create mode 100644 tests/skills/cases/xdto-compile/refs.xsd create mode 100644 tests/skills/cases/xdto-compile/simple-types.json create mode 100644 tests/skills/cases/xdto-compile/simple-types.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd create mode 100644 tests/skills/cases/xdto-compile/top-level-order.json create mode 100644 tests/skills/cases/xdto-compile/top-level-order.xsd create mode 100644 tests/skills/cases/xdto-decompile/_skill.json create mode 100644 tests/skills/cases/xdto-decompile/annotations.json create mode 100644 tests/skills/cases/xdto-decompile/annotations.xsd create mode 100644 tests/skills/cases/xdto-decompile/anon-and-content.json create mode 100644 tests/skills/cases/xdto-decompile/anon-and-content.xsd create mode 100644 tests/skills/cases/xdto-decompile/complex-types.json create mode 100644 tests/skills/cases/xdto-decompile/complex-types.xsd create mode 100644 tests/skills/cases/xdto-decompile/minimal.json create mode 100644 tests/skills/cases/xdto-decompile/minimal.xsd create mode 100644 tests/skills/cases/xdto-decompile/simple-types.json create mode 100644 tests/skills/cases/xdto-decompile/simple-types.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd create mode 100644 tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd create mode 100644 tests/skills/cases/xdto-validate/_skill.json create mode 100644 tests/skills/cases/xdto-validate/facet-conflicts.json create mode 100644 tests/skills/cases/xdto-validate/nillable-on-attribute.json create mode 100644 tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml create mode 100644 tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml create mode 100644 tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml create mode 100644 tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml create mode 100644 tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin create mode 100644 tests/skills/cases/xdto-validate/silent-anytype.json create mode 100644 tests/skills/cases/xdto-validate/unresolved-types.json create mode 100644 tests/skills/cases/xdto-validate/valid-package.json create mode 100644 tests/skills/cases/xdto-validate/valid.xsd diff --git a/.claude/skills/xdto-compile/SKILL.md b/.claude/skills/xdto-compile/SKILL.md new file mode 100644 index 00000000..2811f1d8 --- /dev/null +++ b/.claude/skills/xdto-compile/SKILL.md @@ -0,0 +1,93 @@ +--- +name: xdto-compile +description: Создание пакета XDTO 1С из XML-схемы (XSD). Используй когда нужно добавить пакет XDTO в конфигурацию — для обмена, интеграции, веб-сервиса, разбора внешнего XML-формата +argument-hint: -XsdPath <файл.xsd> -OutputDir <каталог-конфигурации> [-Name <имя>] [-Synonym <синоним>] [-Comment <текст>] [-Force] +allowed-tools: + - Bash + - Read + - Glob +--- + +# /xdto-compile — Создание пакета XDTO из XML-схемы + +Собирает пакет XDTO по обычной XML-схеме: `XDTOPackages/<Имя>.xml`, +`XDTOPackages/<Имя>/Ext/Package.bin` и регистрацию в `Configuration.xml`. + +Отдельного DSL нет — входной формат это XSD. Вся механика перевода (локальные объявления +префиксов `dNpM` на каждой ссылке, инвертированная кратность `lowerBound`/`upperBound`, +фасеты атрибутами вместо дочерних элементов) делается навыком. + +## Параметры + +| Параметр | Описание | +|----------|----------| +| `XsdPath` | Путь к файлу XML-схемы | +| `Xsd` | Схема строкой, вместо `-XsdPath` | +| `OutputDir` | Каталог конфигурации (где лежит `Configuration.xml`) | +| `Name` | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD, санированное под идентификатор 1С | +| `Synonym` | Синоним: строка (русский) или хеш-таблица `@{ru='…'; en='…'}` | +| `Comment` | Комментарий | +| `Force` | Перезаписать существующий пакет | + +```powershell +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 +``` + +## Формат входа + +Обычная XML-схема. Две вещи, которые XSD выразить не может (`nillable` у атрибута, +`qualified` у отдельного свойства), пишутся атрибутами из пространства имён модели XDTO: + +```xml + +``` + +Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в модели**. +Схема при этом остаётся валидной. Полный список аннотаций — в справке ниже. + +Свойства объекта метаданных можно задать прямо в схеме: + +```xml + + + + ОбменСБанком + Обмен с банком + + + +``` + +Принимается и не вполне каноническая схема: имя типа без префикса трактуется как тип +целевого пространства имён, `form="qualified"` на глобальных объявлениях (так экспортирует +сам Конфигуратор) — читается. + +## Зависимости между пакетами + +`` разрешается по namespace среди пакетов конфигурации. +Если пакета с таким пространством имён в конфигурации нет, платформа при загрузке молча +подменит тип на `xs:anyType` — без ошибки. Сначала соберите пакеты-зависимости, +затем зависящий, и проверьте результат через `/xdto-validate`. + +## Типичный workflow + +1. Получить XSD от контрагента (или выгрузить: Конфигуратор → пакет → Экспорт XML-схемы) +2. `/xdto-compile -XsdPath <файл> -OutputDir <конфигурация>` +3. `/xdto-validate` — убедиться, что все типы разрешились +4. `/db-load-xml` + `/db-update` — загрузить в базу + +Правка существующего пакета: `/xdto-decompile` → правка XSD → `/xdto-compile -Force`. +Пара замыкается без потерь, включая свойства объекта метаданных. + +## Верификация + +``` +/xdto-validate <каталог>/XDTOPackages/<Имя> — типы, импорты, регистрация +``` diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 new file mode 100644 index 00000000..6ed312e4 --- /dev/null +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.ps1 @@ -0,0 +1,772 @@ +# xdto-compile v1.0 — Build a 1C XDTO package from an XML Schema (XSD) +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +param( + [Parameter(Mandatory=$true, ParameterSetName='File')] + [Alias('Path')] + [string]$XsdPath, + [Parameter(Mandatory=$true, ParameterSetName='Inline')] + [string]$Xsd, + [Parameter(Mandatory=$true)] + [string]$OutputDir, + [string]$Name, + [object]$Synonym, + [string]$Comment, + [switch]$Force +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +$XDTO_NS = "http://v8.1c.ru/8.1/xdto" +$XS_NS = "http://www.w3.org/2001/XMLSchema" +$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +$MD_NS = "http://v8.1c.ru/8.3/MDClasses" +$V8_NS = "http://v8.1c.ru/8.1/data/core" + +# --- Support guard (Ext/ParentConfigurations.bin) --- +# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" / +# read-only configs unless allowed. Trigger = bin present; reaction from +# .v8-project.json editingAllowedCheck (deny|warn|off, default deny). Never +# throws — guard errors degrade to allow. +function Get-RootUuid([string]$xmlPath) { + if (-not (Test-Path $xmlPath)) { return $null } + try { + [xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8 + $el = $mx.DocumentElement.FirstChild + while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling } + if ($el) { $u = $el.GetAttribute("uuid"); if ($u) { return $u } } + } catch {} + return $null +} +function Test-ExternalObjectRoot([string]$xmlPath) { + if (-not (Test-Path $xmlPath)) { return $false } + try { + [xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8 + $el = $mx.DocumentElement.FirstChild + while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling } + if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.get_LocalName() } + } catch {} + return $false +} +function Find-V8Project([string]$startDir) { + $d = $startDir + for ($i = 0; $i -lt 20 -and $d; $i++) { + $pj = Join-Path $d ".v8-project.json" + if (Test-Path $pj) { return $pj } + $parent = [System.IO.Path]::GetDirectoryName($d) + if ($parent -eq $d) { break } + $d = $parent + } + return $null +} +function Get-EditMode([string]$cfgDir) { + $mode = "deny" + try { + $pj = Find-V8Project $cfgDir + if ($pj) { + $cfg = Get-Content -Path $pj -Raw -Encoding UTF8 | ConvertFrom-Json + if ($cfg.PSObject.Properties.Name -contains 'editingAllowedCheck' -and $cfg.editingAllowedCheck) { + $mode = [string]$cfg.editingAllowedCheck + } + } + } catch {} + return $mode +} +function Assert-EditAllowed([string]$targetPath) { + try { + $mode = $null + $d = $targetPath + for ($i = 0; $i -lt 20 -and $d; $i++) { + $cfgXml = Join-Path $d "Configuration.xml" + $supportBin = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin" + # Автономный объект (внешняя обработка/отчёт) — граница климба + foreach ($x in @(Get-ChildItem -Path $d -Filter "*.xml" -File -ErrorAction SilentlyContinue)) { + if (Test-ExternalObjectRoot $x.FullName) { return } + } + if (Test-Path $cfgXml) { + if (Test-Path $supportBin) { + $mode = Get-EditMode $d + if ($mode -eq "off") { return } + $msg = "Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). Правка может быть запрещена." + if ($mode -eq "warn") { Write-Warning $msg; return } + throw "$msg Снимите с поддержки (/support-edit) или задайте editingAllowedCheck в .v8-project.json." + } + return + } + $parent = [System.IO.Path]::GetDirectoryName($d) + if ($parent -eq $d) { break } + $d = $parent + } + } catch [System.Management.Automation.RuntimeException] { + throw + } catch {} +} + +# --- Load the schema --- + +if ($PSCmdlet.ParameterSetName -eq 'Inline') { + $xsdText = $Xsd + $defaultName = "Package" +} else { + if (-not (Test-Path $XsdPath -PathType Leaf)) { throw "Файл XSD не найден: $XsdPath" } + $xsdText = [System.IO.File]::ReadAllText($XsdPath) + $defaultName = [System.IO.Path]::GetFileNameWithoutExtension($XsdPath) +} + +$xdoc = New-Object System.Xml.XmlDocument +$xdoc.PreserveWhitespace = $false +try { $xdoc.LoadXml($xsdText) } catch { throw "Не удалось разобрать XSD: $($_.Exception.Message)" } + +$schema = $xdoc.DocumentElement +if ($schema.get_LocalName() -ne "schema" -or $schema.NamespaceURI -ne $XS_NS) { + throw "Ожидался корневой в пространстве имён $XS_NS" +} + +$targetNs = $schema.GetAttribute("targetNamespace") + +# --- Emit-tree primitives ----------------------------------------------------- +# A node carries attributes in canonical order; QName values keep their namespace +# so prefixes can be assigned per depth at serialization time (the dNpN scheme). + +function New-Node([string]$tag) { + return [pscustomobject]@{ Tag = $tag; Attrs = (New-Object System.Collections.ArrayList); Children = (New-Object System.Collections.ArrayList); Text = $null; Prefix = $null; DeclareNs = $null } +} +function Add-Attr($node, [string]$name, $value) { + # $value НЕ типизируем: [string]$null коэрсится в "" и атрибут ложно появляется + if ($null -eq $value) { return } + [void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = [string]$value; Ns = $null; Local = $null }) +} +function Add-QAttr($node, [string]$name, $ns, $local) { + if ($null -eq $local) { return } + [void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = [string]$ns; Local = [string]$local }) +} +function Add-QListAttr($node, [string]$name, $pairs, [bool]$clark) { + if (-not $pairs -or $pairs.Count -eq 0) { return } + [void]$node.Attrs.Add([pscustomobject]@{ Name = $name; Value = $null; Ns = $null; Local = $null; List = $pairs; Clark = $clark }) +} +function Add-Child($node, $child) { if ($child) { [void]$node.Children.Add($child) } } + +# Canonical attribute order per element — derived by topological sort over the +# whole 8.3.24 corpus (acc + erp, 760 packages), see docs/1c-xdto-spec.md. +$ATTR_ORDER = @{ + "package" = @("targetNamespace", "elementFormQualified", "attributeFormQualified") + "import" = @("namespace") + "objectType" = @("name", "base", "open", "abstract", "mixed", "ordered", "sequenced") + "property" = @("name", "ref", "type", "lowerBound", "upperBound", "nillable", "fixed", "default", "form", "localName", "qualified") + "valueType" = @("name", "base", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace") + "typeDef" = @("xsi:type", "base", "mixed", "open", "ordered", "sequenced", "variety", "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace") + "enumeration" = @("xsi:type") +} + +function Sort-Attrs($node) { + $order = $ATTR_ORDER[$node.Tag] + if (-not $order) { return $node.Attrs } + $sorted = New-Object System.Collections.ArrayList + foreach ($n in $order) { + foreach ($a in $node.Attrs) { if ($a.Name -eq $n) { [void]$sorted.Add($a) } } + } + foreach ($a in $node.Attrs) { if ($order -notcontains $a.Name) { [void]$sorted.Add($a) } } + return $sorted +} + +function Esc([string]$s) { + if ($null -eq $s) { return "" } + return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace('"', """) +} +function EscText([string]$s) { + if ($null -eq $s) { return "" } + return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">") +} + +# --- Serializer with the dNpN prefix scheme --- + +$out = New-Object System.Text.StringBuilder + +function Serialize-Node($node, [int]$depth, $inherited) { + $indent = "`t" * ($depth - 1) + $attrs = Sort-Attrs $node + + # Namespaces needing a NEW declaration here: те, что ещё не в области видимости. + # Сериализатор платформы объявляет префикс на первом узле, где он нужен, а + # потомки его переиспользуют — отсюда d2p1 у property внутри objectType. + $localNs = New-Object System.Collections.ArrayList + function Need-Prefix([string]$ns) { + if (-not $ns -or $ns -eq $XS_NS -or $ns -eq $XSI_NS) { return } + if ($inherited.ContainsKey($ns)) { return } + if (-not $localNs.Contains($ns)) { [void]$localNs.Add($ns) } + } + foreach ($a in $attrs) { + if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) { + # Нотация Кларка несёт ns в значении и префикса не требует; редкие + # случаи, где платформа его всё же объявила, приходят зеркалом declareNs + if (-not $a.Clark) { foreach ($p in $a.List) { Need-Prefix $p.Ns } } + } elseif ($a.Ns) { Need-Prefix $a.Ns } + } + # Свойство с qualified платформа сериализует с явным префиксом пространства + # имён XDTO — и в имени тега, и в имени самого атрибута + $hasQualified = $false + foreach ($a in $attrs) { if ($a.Name -eq "qualified") { $hasQualified = $true } } + if ($hasQualified) { Need-Prefix $XDTO_NS } + if ($node.DeclareNs) { Need-Prefix $node.DeclareNs } + + $prefixOf = @{} + foreach ($k in $inherited.Keys) { $prefixOf[$k] = $inherited[$k] } + $nsDecls = "" + for ($i = 0; $i -lt $localNs.Count; $i++) { + # Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет + $px = if ($i -eq 0 -and $node.Prefix) { $node.Prefix } else { "d${depth}p$($i + 1)" } + $prefixOf[$localNs[$i]] = $px + $nsDecls += " xmlns:$px=`"$(Esc $localNs[$i])`"" + } + function QVal([string]$ns, [string]$local) { + if (-not $ns) { return $local } + if ($ns -eq $XS_NS) { return "xs:$local" } + if ($ns -eq $XSI_NS) { return "xsi:$local" } + return "$($prefixOf[$ns]):$local" + } + + $attrText = "" + foreach ($a in $attrs) { + if ($a.PSObject.Properties.Name -contains 'List' -and $a.List) { + $vals = @() + foreach ($p in $a.List) { + if ($a.Clark) { $vals += $(if ($p.Ns) { "{$($p.Ns)}$($p.Local)" } else { $p.Local }) } + else { $vals += (QVal $p.Ns $p.Local) } + } + $attrText += " $($a.Name)=`"$(Esc ($vals -join ' '))`"" + } elseif ($a.Ns -or $a.Local) { + $attrText += " $($a.Name)=`"$(Esc (QVal $a.Ns $a.Local))`"" + } elseif ($a.Name -eq "qualified") { + $attrText += " $($prefixOf[$XDTO_NS]):qualified=`"$(Esc $a.Value)`"" + } else { + $attrText += " $($a.Name)=`"$(Esc $a.Value)`"" + } + } + + $tagName = $node.Tag + if ($hasQualified) { $tagName = "$($prefixOf[$XDTO_NS]):$($node.Tag)" } + + $hasChildren = $node.Children.Count -gt 0 + # Пустое значение пишется самозакрывающимся тегом: , а не + $hasText = ($null -ne $node.Text -and $node.Text -ne "") + + if (-not $hasChildren -and -not $hasText) { + [void]$out.Append("$indent<$tagName$nsDecls$attrText/>`r`n") + return + } + if ($hasText -and -not $hasChildren) { + [void]$out.Append("$indent<$tagName$nsDecls$attrText>$(EscText $node.Text)`r`n") + return + } + [void]$out.Append("$indent<$tagName$nsDecls$attrText>`r`n") + foreach ($c in $node.Children) { Serialize-Node $c ($depth + 1) $prefixOf } + [void]$out.Append("$indent`r`n") +} + +# --- XSD reading helpers --- + +function XA([System.Xml.XmlElement]$el, [string]$name) { + if ($el.HasAttribute($name)) { return $el.GetAttribute($name) } + return $null +} +function MA([System.Xml.XmlElement]$el, [string]$name) { + # xdto: mirror attribute — the literal value to write into Package.bin. + # Префикс не фиксируем: ищем по namespace, а не по строке "xdto:". + $a = $el.Attributes.GetNamedItem($name, $XDTO_NS) + if ($null -eq $a) { return $null } + return $a.Value +} +function XChildren([System.Xml.XmlElement]$el, [string]$local) { + $res = New-Object System.Collections.ArrayList + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.NamespaceURI -eq $XS_NS -and $c.get_LocalName() -eq $local) { [void]$res.Add($c) } + } + # ArrayList, а не @(): PowerShell разворачивает массив из одного элемента при return + return ,$res +} +function XFirst([System.Xml.XmlElement]$el, [string]$local) { + $r = XChildren $el $local + if ($r.Count -gt 0) { return $r[0] } + return $null +} + +# Split a QName from the XSD into (ns, local) using that element's prefix scope +function Split-QName([System.Xml.XmlElement]$el, [string]$qname) { + if ($null -eq $qname -or $qname -eq "") { return $null } + $parts = $qname.Split(":") + if ($parts.Count -eq 2) { + $ns = $el.GetNamespaceOfPrefix($parts[0]) + $local = $parts[1] + } else { + # Прощающий ввод: голое имя типа трактуем как тип целевого пространства + $ns = $el.GetNamespaceOfPrefix("") + if (-not $ns) { $ns = $targetNs } + $local = $parts[0] + } + return [pscustomobject]@{ Ns = $ns; Local = $local } +} +function Split-QNameList([System.Xml.XmlElement]$el, [string]$list) { + if (-not $list) { return @() } + $res = @() + foreach ($q in ($list -split "\s+")) { if ($q) { $res += (Split-QName $el $q) } } + return $res +} + +$FACETS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits", + "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace") + +# --- simpleType -> valueType / typeDef(ValueType) --- + +function Fill-SimpleType($node, [System.Xml.XmlElement]$st) { + $restriction = XFirst $st "restriction" + $list = XFirst $st "list" + $union = XFirst $st "union" + + if ($list) { + $it = Split-QName $list (XA $list "itemType") + $mv = MA $list "variety" + Add-Attr $node "variety" $(if ($null -ne $mv) { $mv } else { "List" }) + if ($it) { Add-QAttr $node "itemType" $it.Ns $it.Local } + return + } + if ($union) { + $mv = MA $union "variety" + Set-AttrValue $node "variety" $(if ($null -ne $mv) { $mv } else { "Union" }) + $members = @(Split-QNameList $union (XA $union "memberTypes")) + # По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса + $useClark = ((MA $union "memberTypesForm") -ne "prefixed") + if ($members.Count -gt 0) { Add-QListAttr $node "memberTypes" $members $useClark } + $node.DeclareNs = MA $union "declareNs" + foreach ($anon in (XChildren $union "simpleType")) { + # typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса) + $td = New-Node "typeDef" + Fill-SimpleType $td $anon + Add-Child $node $td + } + return + } + if ($restriction) { + $b = Split-QName $restriction (XA $restriction "base") + if ($b) { Add-QAttr $node "base" $b.Ns $b.Local } + $mv = MA $restriction "variety" + if ($null -ne $mv) { Add-Attr $node "variety" $mv } + # Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type + $anonBase = XFirst $restriction "simpleType" + if ($anonBase) { + $td = New-Node "typeDef" + Fill-SimpleType $td $anonBase + Add-Child $node $td + } + foreach ($f in $FACETS) { + foreach ($fe in (XChildren $restriction $f)) { Add-Attr $node $f (XA $fe "value") } + } + foreach ($pe in (XChildren $restriction "pattern")) { + $pn = New-Node "pattern"; $pn.Text = (XA $pe "value"); Add-Child $node $pn + } + foreach ($en in (XChildren $restriction "enumeration")) { + $enode = New-Node "enumeration" + $mt = MA $en "type" + if ($null -ne $mt) { + $q = Split-QName $en $mt + Add-QAttr $enode "xsi:type" $q.Ns $q.Local + } + $enode.Text = (XA $en "value") + Add-Child $node $enode + } + } +} + +function Get-PropKey($p) { + foreach ($a in $p.Attrs) { if ($a.Name -eq "name") { return $a.Value } } + foreach ($a in $p.Attrs) { if ($a.Name -eq "ref") { return "@" + $a.Local } } + return $null +} + +# Восстановить исходный порядок свойств по зеркалу xdto:order +function Reorder-Properties($node, $names) { + $props = @($node.Children | Where-Object { $_.Tag -eq "property" }) + if ($props.Count -lt 2) { return } + $byKey = @{} + foreach ($p in $props) { + $k = Get-PropKey $p + if ($null -ne $k -and -not $byKey.ContainsKey($k)) { $byKey[$k] = $p } + } + $ordered = New-Object System.Collections.ArrayList + foreach ($n in $names) { + if ($byKey.ContainsKey($n)) { [void]$ordered.Add($byKey[$n]); $byKey.Remove($n) } + } + foreach ($p in $props) { if ($ordered -notcontains $p) { [void]$ordered.Add($p) } } + $others = @($node.Children | Where-Object { $_.Tag -ne "property" }) + $node.Children.Clear() + foreach ($p in $ordered) { [void]$node.Children.Add($p) } + foreach ($o in $others) { [void]$node.Children.Add($o) } +} + +function Set-AttrValue($node, [string]$name, [string]$value) { + foreach ($a in $node.Attrs) { if ($a.Name -eq $name) { $a.Value = $value; return } } + Add-Attr $node $name $value +} + +# --- element / attribute -> property --- + +function Build-Property([System.Xml.XmlElement]$el, [bool]$isAttribute) { + $p = New-Node "property" + + $xsdName = XA $el "name" + $mirrorName = MA $el "name" + if ($null -ne $mirrorName) { + Add-Attr $p "name" $mirrorName + $localName = $xsdName + } else { + Add-Attr $p "name" $xsdName + $localName = $null + } + + $refQ = Split-QName $el (XA $el "ref") + if ($refQ) { Add-QAttr $p "ref" $refQ.Ns $refQ.Local } + + $typeQ = Split-QName $el (XA $el "type") + if ($typeQ) { Add-QAttr $p "type" $typeQ.Ns $typeQ.Local } + + if ($isAttribute) { + Add-Attr $p "lowerBound" (MA $el "lowerBound") + Add-Attr $p "upperBound" (MA $el "upperBound") + Add-Attr $p "nillable" (MA $el "nillable") + } else { + Add-Attr $p "lowerBound" (XA $el "minOccurs") + $maxOcc = XA $el "maxOccurs" + if ($null -ne $maxOcc) { Add-Attr $p "upperBound" $(if ($maxOcc -eq "unbounded") { "-1" } else { $maxOcc }) } + Add-Attr $p "nillable" (XA $el "nillable") + } + + Add-Attr $p "fixed" (XA $el "fixed") + Add-Attr $p "default" (XA $el "default") + + if ($isAttribute) { + Add-Attr $p "form" "Attribute" + } else { + $mf = MA $el "form" + if ($null -ne $mf) { Add-Attr $p "form" $mf } + } + Add-Attr $p "localName" $localName + Add-Attr $p "qualified" (MA $el "qualified") + $p.Prefix = MA $el "prefix" + + # Anonymous inline type + $anonSimple = XFirst $el "simpleType" + $anonComplex = XFirst $el "complexType" + if ($anonSimple) { + $td = New-Node "typeDef" + Add-Attr $td "xsi:type" "ValueType" + Fill-SimpleType $td $anonSimple + Add-Child $p $td + } elseif ($anonComplex) { + $td = New-Node "typeDef" + Add-Attr $td "xsi:type" "ObjectType" + Fill-ComplexType $td $anonComplex + Add-Child $p $td + } + return $p +} + +# --- complexType -> objectType / typeDef(ObjectType) --- + +# open / ordered / sequenced / abstract / mixed: выводим где выводимо, +# остальное приходит зеркалом xdto: +function Set-TypeFlags($node, [System.Xml.XmlElement]$ct, $isOpen, $choice) { + $mOpen = MA $ct "open" + if ($null -ne $mOpen) { Add-Attr $node "open" $mOpen } + elseif ($isOpen) { Add-Attr $node "open" "true" } + + $mOrdered = MA $ct "ordered" + if ($null -ne $mOrdered) { Add-Attr $node "ordered" $mOrdered } + elseif ($choice) { Add-Attr $node "ordered" "false" } + + $mSeq = MA $ct "sequenced" + if ($null -ne $mSeq) { Add-Attr $node "sequenced" $mSeq } + + $mAbstract = MA $ct "abstract" + if ($null -ne $mAbstract) { Add-Attr $node "abstract" $mAbstract } + elseif ((XA $ct "abstract") -eq "true") { Add-Attr $node "abstract" "true" } + + $mMixed = MA $ct "mixed" + if ($null -ne $mMixed) { Add-Attr $node "mixed" $mMixed } + elseif ((XA $ct "mixed") -eq "true") { Add-Attr $node "mixed" "true" } +} + +function Fill-ComplexType($node, [System.Xml.XmlElement]$ct) { + # xs:complexContent/xs:extension carries the base type + $content = XFirst $ct "complexContent" + $body = $ct + if ($content) { + $ext = XFirst $content "extension" + if ($ext) { + $b = Split-QName $ext (XA $ext "base") + if ($b) { Add-QAttr $node "base" $b.Ns $b.Local } + $body = $ext + } + } + + # xs:simpleContent -> a "Text" property holding the element's own value + $simple = XFirst $ct "simpleContent" + if ($simple) { + $ext = XFirst $simple "extension" + if ($ext) { + foreach ($a in (XChildren $ext "attribute")) { Add-Child $node (Build-Property $a $true) } + $tp = New-Node "property" + $tName = MA $ext "textName" + Add-Attr $tp "name" $(if ($null -ne $tName) { $tName } else { "__content" }) + $b = Split-QName $ext (XA $ext "base") + if ($b) { Add-QAttr $tp "type" $b.Ns $b.Local } + Add-Attr $tp "lowerBound" (MA $ext "textlowerBound") + Add-Attr $tp "upperBound" (MA $ext "textupperBound") + Add-Attr $tp "nillable" (MA $ext "textnillable") + Add-Attr $tp "form" "Text" + Add-Child $node $tp + # xs:simpleContent не отменяет флаги самого xs:complexType + Set-TypeFlags $node $ct $false $null + return + } + } + + # Particle: xs:sequence (ordered) or xs:choice (ordered="false") + $seq = XFirst $body "sequence" + $cho = XFirst $body "choice" + $particle = if ($seq) { $seq } else { $cho } + $isOpen = $false + + # Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5% + # типов корпуса). Отклонения приходят зеркалом xdto:order. + $elemProps = New-Object System.Collections.ArrayList + 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 } + } + } + } + foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) } + foreach ($e in $elemProps) { Add-Child $node $e } + if ((XChildren $body "anyAttribute").Count -gt 0) { $isOpen = $true } + + $mOrder = MA $ct "order" + if ($null -ne $mOrder) { Reorder-Properties $node ($mOrder -split "\|") } + + Set-TypeFlags $node $ct $isOpen $cho +} + +# --- Build the package tree --- + +$pkgNode = New-Node "package" +Add-Attr $pkgNode "targetNamespace" $targetNs + +$efqMirror = MA $schema "elementFormQualified" +$afqMirror = MA $schema "attributeFormQualified" +$efd = XA $schema "elementFormDefault" +$afd = XA $schema "attributeFormDefault" +if ($null -ne $efqMirror) { Add-Attr $pkgNode "elementFormQualified" $efqMirror } +elseif ($null -ne $efd) { Add-Attr $pkgNode "elementFormQualified" $(if ($efd -eq "qualified") { "true" } else { "false" }) } +if ($null -ne $afqMirror) { Add-Attr $pkgNode "attributeFormQualified" $afqMirror } +elseif ($null -ne $afd) { Add-Attr $pkgNode "attributeFormQualified" $(if ($afd -eq "qualified") { "true" } else { "false" }) } + +# Metadata properties from xs:annotation/xs:appinfo +$metaName = $null; $metaComment = $null; $metaSynonym = @() +$ann = XFirst $schema "annotation" +if ($ann) { + $appinfo = XFirst $ann "appinfo" + if ($appinfo) { + foreach ($pk in $appinfo.ChildNodes) { + if ($pk.NodeType -ne [System.Xml.XmlNodeType]::Element -or $pk.NamespaceURI -ne $XDTO_NS) { continue } + foreach ($f in $pk.ChildNodes) { + if ($f.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + switch ($f.get_LocalName()) { + "name" { $metaName = $f.InnerText } + "comment" { $metaComment = $f.InnerText } + "synonym" { $metaSynonym += @{ Lang = $f.GetAttribute("lang"); Content = $f.InnerText } } + } + } + } + } +} + +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" { } + "import" { + $n = New-Node "import" + Add-Attr $n "namespace" (XA $node "namespace") + Add-Child $pkgNode $n + } + "include" { } + "element" { Add-Child $pkgNode (Build-Property $node $false) } + "attribute" { Add-Child $pkgNode (Build-Property $node $true) } + "simpleType" { + $n = New-Node "valueType" + Add-Attr $n "name" (XA $node "name") + Fill-SimpleType $n $node + Add-Child $pkgNode $n + } + "complexType" { + $n = New-Node "objectType" + Add-Attr $n "name" (XA $node "name") + Fill-ComplexType $n $node + Add-Child $pkgNode $n + } + } +} + +# --- Serialize Package.bin --- + +# Модель XDTO требует строгой последовательности элементов верхнего уровня: +# import → property → valueType → objectType. Порядок объявлений в XSD произвольный, +# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования +# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют, так что +# round-trip не затрагивается. +$TOP_ORDER = @("import", "property", "valueType", "objectType") +$sortedChildren = New-Object System.Collections.ArrayList +foreach ($t in $TOP_ORDER) { + foreach ($c in $pkgNode.Children) { if ($c.Tag -eq $t) { [void]$sortedChildren.Add($c) } } +} +foreach ($c in $pkgNode.Children) { if ($TOP_ORDER -notcontains $c.Tag) { [void]$sortedChildren.Add($c) } } +$pkgNode.Children.Clear() +foreach ($c in $sortedChildren) { [void]$pkgNode.Children.Add($c) } + +$attrsRoot = Sort-Attrs $pkgNode +$rootAttrText = "" +foreach ($a in $attrsRoot) { $rootAttrText += " $($a.Name)=`"$(Esc $a.Value)`"" } +[void]$out.Append("`r`n") +foreach ($c in $pkgNode.Children) { Serialize-Node $c 2 @{} } +[void]$out.Append("") + +$binText = $out.ToString() + +# --- Resolve the package name --- + +if (-not $Name) { + if ($metaName) { $Name = $metaName } else { $Name = $defaultName } +} +# Санация под идентификатор 1С +$Name = ($Name -replace '[^\wЀ-ӿ]', '_') +if ($Name -match '^\d') { $Name = "_$Name" } + +Assert-EditAllowed $OutputDir + +$pkgRoot = Join-Path $OutputDir "XDTOPackages" +$pkgDir = Join-Path $pkgRoot $Name +$extDir = Join-Path $pkgDir "Ext" +$mdFile = Join-Path $pkgRoot "$Name.xml" +$binFile = Join-Path $extDir "Package.bin" + +if ((Test-Path $binFile) -and -not $Force) { + throw "Пакет уже существует: $binFile. Используйте -Force для перезаписи." +} +New-Item -ItemType Directory -Path $extDir -Force | Out-Null + +$encBom = New-Object System.Text.UTF8Encoding($true) +[System.IO.File]::WriteAllText($binFile, $binText, $encBom) + +# --- Metadata object file --- + +if (-not $Synonym -and $metaSynonym.Count -gt 0) { + $synItems = $metaSynonym +} elseif ($Synonym -is [System.Collections.IDictionary]) { + $synItems = @() + foreach ($k in $Synonym.Keys) { $synItems += @{ Lang = [string]$k; Content = [string]$Synonym[$k] } } +} elseif ($Synonym) { + $synItems = @(@{ Lang = "ru"; Content = [string]$Synonym }) +} else { + $synItems = @(@{ Lang = "ru"; Content = $Name }) +} +if (-not $Comment -and $metaComment) { $Comment = $metaComment } + +$uuid = [guid]::NewGuid().ToString() +$md = New-Object System.Text.StringBuilder +function M([string]$s) { [void]$md.Append($s); [void]$md.Append("`r`n") } +M '' +M '' +M "`t" +M "`t`t" +M "`t`t`t$(EscText $Name)" +M "`t`t`t" +foreach ($s in $synItems) { + M "`t`t`t`t" + M "`t`t`t`t`t$(EscText $s.Lang)" + M "`t`t`t`t`t$(EscText $s.Content)" + M "`t`t`t`t" +} +M "`t`t`t" +if ($Comment) { M "`t`t`t$(EscText $Comment)" } else { M "`t`t`t" } +M "`t`t`t$(EscText $targetNs)" +M "`t`t" +M "`t" +[void]$md.Append("") + +[System.IO.File]::WriteAllText($mdFile, $md.ToString(), $encBom) + +# --- Register in Configuration.xml --- + +$configXmlPath = Join-Path $OutputDir "Configuration.xml" +$regResult = "no-config" +if (Test-Path $configXmlPath) { + $configDoc = New-Object System.Xml.XmlDocument + $configDoc.PreserveWhitespace = $true + $configDoc.Load($configXmlPath) + $nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable) + $nsMgr.AddNamespace("md", $MD_NS) + $childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr) + if ($childObjects) { + $existing = $childObjects.SelectNodes("md:XDTOPackage", $nsMgr) + $already = $false + foreach ($e in $existing) { if ($e.InnerText -eq $Name) { $already = $true; break } } + if ($already) { + $regResult = "already" + } else { + $newElem = $configDoc.CreateElement("XDTOPackage", $MD_NS) + $newElem.InnerText = $Name + if ($existing.Count -gt 0) { + $lastElem = $existing[$existing.Count - 1] + $newWs = $configDoc.CreateWhitespace("`n`t`t`t") + $childObjects.InsertAfter($newWs, $lastElem) | Out-Null + $childObjects.InsertAfter($newElem, $newWs) | Out-Null + } else { + $lastChild = $childObjects.LastChild + if ($lastChild -and $lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) { + $newWs = $configDoc.CreateWhitespace("`n`t`t`t") + $childObjects.InsertBefore($newWs, $lastChild) | Out-Null + $childObjects.InsertBefore($newElem, $lastChild) | Out-Null + } else { + $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null + $childObjects.AppendChild($newElem) | Out-Null + $childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null + } + } + $cfgSettings = New-Object System.Xml.XmlWriterSettings + $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) + $cfgSettings.Indent = $false + $stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) + $writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) + $configDoc.Save($writer) + $writer.Close() + $stream.Close() + $regResult = "added" + } + } +} + +# --- Report --- + +$typeCount = 0 +foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "objectType" -or $c.Tag -eq "valueType") { $typeCount++ } } + +Write-Host "✓ Пакет XDTO собран: $Name" +Write-Host " Namespace: $targetNs" +Write-Host " Типов: $typeCount" +Write-Host " Файлы: XDTOPackages/$Name.xml, XDTOPackages/$Name/Ext/Package.bin" +switch ($regResult) { + "added" { Write-Host " Configuration.xml: $Name добавлен в ChildObjects" } + "already" { Write-Host " Configuration.xml: $Name уже зарегистрирован" } + "no-config" { Write-Host " Configuration.xml не найден — регистрация пропущена" } +} diff --git a/.claude/skills/xdto-compile/scripts/xdto-compile.py b/.claude/skills/xdto-compile/scripts/xdto-compile.py new file mode 100644 index 00000000..c411ebbb --- /dev/null +++ b/.claude/skills/xdto-compile/scripts/xdto-compile.py @@ -0,0 +1,780 @@ +# xdto-compile v1.0 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +import argparse +import json +import os +import re +import sys +import uuid + +from lxml import etree + +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +XDTO_NS = "http://v8.1c.ru/8.1/xdto" +XS_NS = "http://www.w3.org/2001/XMLSchema" +XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +MD_NS = "http://v8.1c.ru/8.3/MDClasses" + +parser = argparse.ArgumentParser(allow_abbrev=False) +parser.add_argument("-XsdPath", "-Path", default="") +parser.add_argument("-Xsd", default="") +parser.add_argument("-OutputDir", required=True) +parser.add_argument("-Name", default="") +parser.add_argument("-Synonym", default="") +parser.add_argument("-Comment", default="") +parser.add_argument("-Force", action="store_true") +args = parser.parse_args() + +# ── support guard (Ext/ParentConfigurations.bin) ───────────── +# См. docs/1c-support-state-spec.md. Блокирует правку объектов поставщика +# «на замке». Триггер — наличие bin; реакция из .v8-project.json +# editingAllowedCheck (deny|warn|off, по умолчанию deny). + + +def find_v8_project(start_dir): + d = os.path.abspath(start_dir) + for _ in range(20): + pj = os.path.join(d, ".v8-project.json") + if os.path.exists(pj): + return pj + parent = os.path.dirname(d) + if parent == d: + break + d = parent + return None + + +def get_edit_mode(cfg_dir): + try: + pj = find_v8_project(cfg_dir) + if pj: + with open(pj, encoding="utf-8-sig") as f: + cfg = json.load(f) + return str(cfg.get("editingAllowedCheck") or "deny") + except Exception: # noqa: BLE001 + pass + return "deny" + + +def is_external_object_root(xml_path): + try: + root = etree.parse(xml_path).getroot() + for el in root: + if isinstance(el.tag, str): + return etree.QName(el).localname in ("ExternalDataProcessor", "ExternalReport") + except Exception: # noqa: BLE001 + pass + return False + + +def assert_edit_allowed(target_path): + d = os.path.abspath(target_path) + for _ in range(20): + # Автономный объект (внешняя обработка/отчёт) — граница климба + try: + for f in os.listdir(d): + if f.endswith(".xml") and is_external_object_root(os.path.join(d, f)): + return + except OSError: + pass + cfg_xml = os.path.join(d, "Configuration.xml") + support_bin = os.path.join(d, "Ext", "ParentConfigurations.bin") + if os.path.exists(cfg_xml): + if os.path.exists(support_bin): + mode = get_edit_mode(d) + if mode == "off": + return + msg = ("Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). " + "Правка может быть запрещена.") + if mode == "warn": + print(f"WARNING: {msg}", file=sys.stderr) + return + print(f"{msg} Снимите с поддержки (/support-edit) или задайте " + "editingAllowedCheck в .v8-project.json.", file=sys.stderr) + sys.exit(1) + return + parent = os.path.dirname(d) + if parent == d: + break + d = parent + + +# ── load the schema ────────────────────────────────────────── + +if args.Xsd: + xsd_bytes = args.Xsd.encode("utf-8") + default_name = "Package" +elif args.XsdPath: + if not os.path.isfile(args.XsdPath): + print(f"Файл XSD не найден: {args.XsdPath}", file=sys.stderr) + sys.exit(1) + with open(args.XsdPath, "rb") as f: + xsd_bytes = f.read() + default_name = os.path.splitext(os.path.basename(args.XsdPath))[0] +else: + print("Укажите -XsdPath или -Xsd", file=sys.stderr) + sys.exit(1) + +try: + schema = etree.fromstring(xsd_bytes) +except Exception as e: # noqa: BLE001 + print(f"Не удалось разобрать XSD: {e}", file=sys.stderr) + sys.exit(1) + + +def local(el): + return etree.QName(el).localname + + +if local(schema) != "schema" or etree.QName(schema).namespace != XS_NS: + print(f"Ожидался корневой в пространстве имён {XS_NS}", file=sys.stderr) + sys.exit(1) + +target_ns = schema.get("targetNamespace") or "" + +# ── emit-tree primitives ───────────────────────────────────── + + +class Node: + __slots__ = ("tag", "attrs", "children", "text", "prefix", "declare_ns") + + def __init__(self, tag): + self.tag = tag + self.attrs = [] # список dict: name, value | (ns, local) | list + self.children = [] + self.text = None + self.prefix = None + self.declare_ns = None + + +def add_attr(node, name, value): + if value is None: + return + node.attrs.append({"name": name, "value": str(value)}) + + +def add_qattr(node, name, ns, loc): + if loc is None: + return + node.attrs.append({"name": name, "ns": ns, "local": loc}) + + +def add_qlist_attr(node, name, pairs, clark): + if not pairs: + return + node.attrs.append({"name": name, "list": pairs, "clark": clark}) + + +# Канонический порядок атрибутов — топологическая сортировка по корпусу 8.3.24 +# (acc + erp, 760 пакетов), см. docs/1c-xdto-spec.md. +ATTR_ORDER = { + "package": ["targetNamespace", "elementFormQualified", "attributeFormQualified"], + "import": ["namespace"], + "objectType": ["name", "base", "open", "abstract", "mixed", "ordered", "sequenced"], + "property": ["name", "ref", "type", "lowerBound", "upperBound", "nillable", + "fixed", "default", "form", "localName", "qualified"], + "valueType": ["name", "base", "variety", "itemType", "length", "memberTypes", + "minExclusive", "maxExclusive", "minInclusive", "maxInclusive", + "minLength", "maxLength", "totalDigits", "fractionDigits", "whiteSpace"], + "typeDef": ["xsi:type", "base", "mixed", "open", "ordered", "sequenced", "variety", + "itemType", "length", "memberTypes", "minExclusive", "maxExclusive", + "minInclusive", "maxInclusive", "minLength", "maxLength", + "totalDigits", "fractionDigits", "whiteSpace"], + "enumeration": ["xsi:type"], +} + + +def sort_attrs(node): + order = ATTR_ORDER.get(node.tag) + if not order: + return node.attrs + res = [] + for n in order: + res.extend(a for a in node.attrs if a["name"] == n) + res.extend(a for a in node.attrs if a["name"] not in order) + return res + + +def esc(s): + if s is None: + return "" + return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) + + +def esc_text(s): + if s is None: + return "" + return str(s).replace("&", "&").replace("<", "<").replace(">", ">") + + +# ── serializer with the dNpM prefix scheme ─────────────────── + +out = [] + + +def serialize_node(node, depth, inherited): + indent = "\t" * (depth - 1) + attrs_sorted = sort_attrs(node) + + # Объявляем здесь только те ns, которых ещё нет в области видимости: + # сериализатор платформы объявляет префикс на первом нуждающемся узле, + # а потомки переиспользуют — отсюда d2p1 у property внутри objectType. + local_ns = [] + + def need_prefix(ns): + if not ns or ns in (XS_NS, XSI_NS): + return + if ns in inherited: + return + if ns not in local_ns: + local_ns.append(ns) + + for a in attrs_sorted: + if "list" in a: + # Нотация Кларка несёт ns в значении и префикса не требует + if not a["clark"]: + for p in a["list"]: + need_prefix(p[0]) + elif a.get("ns"): + need_prefix(a["ns"]) + + has_qualified = any(a["name"] == "qualified" for a in attrs_sorted) + if has_qualified: + need_prefix(XDTO_NS) + if node.declare_ns: + need_prefix(node.declare_ns) + + prefix_of = dict(inherited) + ns_decls = "" + for i, ns in enumerate(local_ns): + # Осмысленный префикс из исходника (зеркало xdto:prefix) имеет приоритет + px = node.prefix if (i == 0 and node.prefix) else f"d{depth}p{i + 1}" + prefix_of[ns] = px + ns_decls += f' xmlns:{px}="{esc(ns)}"' + + def qval(ns, loc): + if not ns: + return loc + if ns == XS_NS: + return f"xs:{loc}" + if ns == XSI_NS: + return f"xsi:{loc}" + return f"{prefix_of[ns]}:{loc}" + + attr_text = "" + for a in attrs_sorted: + if "list" in a: + vals = [] + for ns, loc in a["list"]: + vals.append((f"{{{ns}}}{loc}" if ns else loc) if a["clark"] else qval(ns, loc)) + attr_text += f' {a["name"]}="{esc(" ".join(vals))}"' + elif a.get("ns") or a.get("local"): + attr_text += f' {a["name"]}="{esc(qval(a.get("ns"), a["local"]))}"' + elif a["name"] == "qualified": + attr_text += f' {prefix_of[XDTO_NS]}:qualified="{esc(a["value"])}"' + else: + attr_text += f' {a["name"]}="{esc(a["value"])}"' + + tag_name = f"{prefix_of[XDTO_NS]}:{node.tag}" if has_qualified else node.tag + + has_children = bool(node.children) + # Пустое значение пишется самозакрывающимся тегом: + has_text = node.text is not None and node.text != "" + + if not has_children and not has_text: + out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}/>\r\n") + return + if has_text and not has_children: + out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>{esc_text(node.text)}\r\n") + return + out.append(f"{indent}<{tag_name}{ns_decls}{attr_text}>\r\n") + for c in node.children: + serialize_node(c, depth + 1, prefix_of) + out.append(f"{indent}\r\n") + + +# ── XSD reading helpers ────────────────────────────────────── + +def MA(el, name): + # xdto: mirror attribute — литеральное значение для Package.bin. + # Ищем по namespace, а не по строке префикса. + return el.get(f"{{{XDTO_NS}}}{name}") + + +def xchildren(el, name): + return [c for c in el if isinstance(c.tag, str) + and etree.QName(c).namespace == XS_NS and local(c) == name] + + +def xfirst(el, name): + r = xchildren(el, name) + return r[0] if r else None + + +def split_qname(el, qname): + if not qname: + return None + parts = qname.split(":") + if len(parts) == 2: + ns = el.nsmap.get(parts[0]) + loc = parts[1] + else: + # Прощающий ввод: голое имя типа — тип целевого пространства имён + ns = el.nsmap.get(None) or target_ns + loc = parts[0] + return (ns, loc) + + +def split_qname_list(el, lst): + if not lst: + return [] + return [split_qname(el, q) for q in lst.split() if q] + + +FACETS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits", + "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"] + + +# ── simpleType -> valueType / typeDef(ValueType) ───────────── + +def fill_simple_type(node, st): + restriction = xfirst(st, "restriction") + lst = xfirst(st, "list") + union = xfirst(st, "union") + + if lst is not None: + it = split_qname(lst, lst.get("itemType")) + mv = MA(lst, "variety") + add_attr(node, "variety", mv if mv is not None else "List") + if it: + add_qattr(node, "itemType", it[0], it[1]) + return + if union is not None: + mv = MA(union, "variety") + set_attr_value(node, "variety", mv if mv is not None else "Union") + members = split_qname_list(union, union.get("memberTypes")) + # По умолчанию нотация Кларка — так записано 125 из 135 memberTypes корпуса + use_clark = MA(union, "memberTypesForm") != "prefixed" + if members: + add_qlist_attr(node, "memberTypes", members, use_clark) + node.declare_ns = MA(union, "declareNs") + for anon in xchildren(union, "simpleType"): + # typeDef в контексте простого типа xsi:type не несёт (40 узлов корпуса) + td = Node("typeDef") + fill_simple_type(td, anon) + node.children.append(td) + return + if restriction is not None: + b = split_qname(restriction, restriction.get("base")) + if b: + add_qattr(node, "base", b[0], b[1]) + mv = MA(restriction, "variety") + if mv is not None: + add_attr(node, "variety", mv) + # Анонимный базовый тип внутри xs:restriction — typeDef без xsi:type + anon_base = xfirst(restriction, "simpleType") + if anon_base is not None: + td = Node("typeDef") + fill_simple_type(td, anon_base) + node.children.append(td) + for f in FACETS: + for fe in xchildren(restriction, f): + add_attr(node, f, fe.get("value")) + for pe in xchildren(restriction, "pattern"): + pn = Node("pattern") + pn.text = pe.get("value") + node.children.append(pn) + for en in xchildren(restriction, "enumeration"): + enode = Node("enumeration") + mt = MA(en, "type") + if mt is not None: + q = split_qname(en, mt) + add_qattr(enode, "xsi:type", q[0], q[1]) + enode.text = en.get("value") + node.children.append(enode) + + +def set_attr_value(node, name, value): + for a in node.attrs: + if a["name"] == name: + a["value"] = value + return + add_attr(node, name, value) + + +def get_prop_key(p): + for a in p.attrs: + if a["name"] == "name": + return a.get("value") + for a in p.attrs: + if a["name"] == "ref": + return "@" + a["local"] + return None + + +def reorder_properties(node, names): + props = [c for c in node.children if c.tag == "property"] + if len(props) < 2: + return + by_key = {} + for p in props: + k = get_prop_key(p) + if k is not None and k not in by_key: + by_key[k] = p + ordered = [] + for n in names: + if n in by_key: + ordered.append(by_key.pop(n)) + for p in props: + if p not in ordered: + ordered.append(p) + others = [c for c in node.children if c.tag != "property"] + node.children = ordered + others + + +# ── element / attribute -> property ────────────────────────── + +def build_property(el, is_attribute): + p = Node("property") + + xsd_name = el.get("name") + mirror_name = MA(el, "name") + if mirror_name is not None: + add_attr(p, "name", mirror_name) + local_name = xsd_name + else: + add_attr(p, "name", xsd_name) + local_name = None + + ref_q = split_qname(el, el.get("ref")) + if ref_q: + add_qattr(p, "ref", ref_q[0], ref_q[1]) + + type_q = split_qname(el, el.get("type")) + if type_q: + add_qattr(p, "type", type_q[0], type_q[1]) + + if is_attribute: + add_attr(p, "lowerBound", MA(el, "lowerBound")) + add_attr(p, "upperBound", MA(el, "upperBound")) + add_attr(p, "nillable", MA(el, "nillable")) + else: + add_attr(p, "lowerBound", el.get("minOccurs")) + max_occ = el.get("maxOccurs") + if max_occ is not None: + add_attr(p, "upperBound", "-1" if max_occ == "unbounded" else max_occ) + add_attr(p, "nillable", el.get("nillable")) + + add_attr(p, "fixed", el.get("fixed")) + add_attr(p, "default", el.get("default")) + + if is_attribute: + add_attr(p, "form", "Attribute") + else: + mf = MA(el, "form") + if mf is not None: + add_attr(p, "form", mf) + add_attr(p, "localName", local_name) + add_attr(p, "qualified", MA(el, "qualified")) + p.prefix = MA(el, "prefix") + + anon_simple = xfirst(el, "simpleType") + anon_complex = xfirst(el, "complexType") + if anon_simple is not None: + td = Node("typeDef") + add_attr(td, "xsi:type", "ValueType") + fill_simple_type(td, anon_simple) + p.children.append(td) + elif anon_complex is not None: + td = Node("typeDef") + add_attr(td, "xsi:type", "ObjectType") + fill_complex_type(td, anon_complex) + p.children.append(td) + return p + + +# ── complexType -> objectType / typeDef(ObjectType) ────────── + +def set_type_flags(node, ct, is_open, choice): + m_open = MA(ct, "open") + if m_open is not None: + add_attr(node, "open", m_open) + elif is_open: + add_attr(node, "open", "true") + + m_ordered = MA(ct, "ordered") + if m_ordered is not None: + add_attr(node, "ordered", m_ordered) + elif choice is not None: + add_attr(node, "ordered", "false") + + m_seq = MA(ct, "sequenced") + if m_seq is not None: + add_attr(node, "sequenced", m_seq) + + m_abstract = MA(ct, "abstract") + if m_abstract is not None: + add_attr(node, "abstract", m_abstract) + elif ct.get("abstract") == "true": + add_attr(node, "abstract", "true") + + m_mixed = MA(ct, "mixed") + if m_mixed is not None: + add_attr(node, "mixed", m_mixed) + elif ct.get("mixed") == "true": + add_attr(node, "mixed", "true") + + +def fill_complex_type(node, ct): + body = ct + content = xfirst(ct, "complexContent") + if content is not None: + ext = xfirst(content, "extension") + if ext is not None: + b = split_qname(ext, ext.get("base")) + if b: + add_qattr(node, "base", b[0], b[1]) + body = ext + + # xs:simpleContent -> свойство "Text", хранящее значение самого элемента + simple = xfirst(ct, "simpleContent") + if simple is not None: + ext = xfirst(simple, "extension") + if ext is not None: + for a in xchildren(ext, "attribute"): + node.children.append(build_property(a, True)) + tp = Node("property") + t_name = MA(ext, "textName") + add_attr(tp, "name", t_name if t_name is not None else "__content") + b = split_qname(ext, ext.get("base")) + if b: + add_qattr(tp, "type", b[0], b[1]) + add_attr(tp, "lowerBound", MA(ext, "textlowerBound")) + add_attr(tp, "upperBound", MA(ext, "textupperBound")) + add_attr(tp, "nillable", MA(ext, "textnillable")) + add_attr(tp, "form", "Text") + node.children.append(tp) + # xs:simpleContent не отменяет флаги самого xs:complexType + set_type_flags(node, ct, False, None) + return + + seq = xfirst(body, "sequence") + cho = xfirst(body, "choice") + particle = seq if seq is not None else cho + is_open = False + + # Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса) + elem_props = [] + 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 + for a in xchildren(body, "attribute"): + node.children.append(build_property(a, True)) + node.children.extend(elem_props) + if xchildren(body, "anyAttribute"): + is_open = True + + m_order = MA(ct, "order") + if m_order is not None: + reorder_properties(node, m_order.split("|")) + + set_type_flags(node, ct, is_open, cho) + + +# ── build the package tree ─────────────────────────────────── + +pkg_node = Node("package") +add_attr(pkg_node, "targetNamespace", target_ns) + +efq_mirror = MA(schema, "elementFormQualified") +afq_mirror = MA(schema, "attributeFormQualified") +efd = schema.get("elementFormDefault") +afd = schema.get("attributeFormDefault") +if efq_mirror is not None: + add_attr(pkg_node, "elementFormQualified", efq_mirror) +elif efd is not None: + add_attr(pkg_node, "elementFormQualified", "true" if efd == "qualified" else "false") +if afq_mirror is not None: + add_attr(pkg_node, "attributeFormQualified", afq_mirror) +elif afd is not None: + add_attr(pkg_node, "attributeFormQualified", "true" if afd == "qualified" else "false") + +meta_name = meta_comment = None +meta_synonym = [] +ann = xfirst(schema, "annotation") +if ann is not None: + appinfo = xfirst(ann, "appinfo") + if appinfo is not None: + for pk in appinfo: + if not isinstance(pk.tag, str) or etree.QName(pk).namespace != XDTO_NS: + continue + for f in pk: + if not isinstance(f.tag, str): + continue + ln = local(f) + if ln == "name": + meta_name = f.text or "" + elif ln == "comment": + meta_comment = f.text or "" + 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 + ln = local(node) + if ln == "import": + n = Node("import") + add_attr(n, "namespace", node.get("namespace")) + pkg_node.children.append(n) + elif ln in ("annotation", "include"): + continue + elif ln == "element": + pkg_node.children.append(build_property(node, False)) + elif ln == "attribute": + pkg_node.children.append(build_property(node, True)) + elif ln == "simpleType": + n = Node("valueType") + add_attr(n, "name", node.get("name")) + fill_simple_type(n, node) + pkg_node.children.append(n) + elif ln == "complexType": + n = Node("objectType") + add_attr(n, "name", node.get("name")) + fill_complex_type(n, node) + pkg_node.children.append(n) + +# ── serialize Package.bin ──────────────────────────────────── + +# Модель XDTO требует строгой последовательности элементов верхнего уровня: +# import → property → valueType → objectType. Порядок объявлений в XSD произвольный, +# поэтому пересортировываем — иначе платформа отвергает пакет с «Ошибка преобразования +# данных XDTO». Все 760 пакетов корпуса этому порядку удовлетворяют. +TOP_ORDER = ["import", "property", "valueType", "objectType"] +pkg_node.children = ( + [c for t in TOP_ORDER for c in pkg_node.children if c.tag == t] + + [c for c in pkg_node.children if c.tag not in TOP_ORDER] +) + +root_attr_text = "".join(f' {a["name"]}="{esc(a["value"])}"' for a in sort_attrs(pkg_node)) +out.append(f'\r\n') +for c in pkg_node.children: + serialize_node(c, 2, {}) +out.append("") + +bin_text = "".join(out) + +# ── resolve the package name ───────────────────────────────── + +name = args.Name or meta_name or default_name +name = re.sub(r"[^\wЀ-ӿ]", "_", name, flags=re.UNICODE) +if re.match(r"^\d", name): + name = "_" + name + +assert_edit_allowed(args.OutputDir) + +pkg_root = os.path.join(args.OutputDir, "XDTOPackages") +pkg_dir = os.path.join(pkg_root, name) +ext_dir = os.path.join(pkg_dir, "Ext") +md_file = os.path.join(pkg_root, name + ".xml") +bin_file = os.path.join(ext_dir, "Package.bin") + +if os.path.exists(bin_file) and not args.Force: + print(f"Пакет уже существует: {bin_file}. Используйте -Force для перезаписи.", file=sys.stderr) + sys.exit(1) +os.makedirs(ext_dir, exist_ok=True) + +with open(bin_file, "wb") as f: + f.write(b"\xef\xbb\xbf" + bin_text.encode("utf-8")) + +# ── metadata object file ───────────────────────────────────── + +if not args.Synonym and meta_synonym: + syn_items = meta_synonym +elif args.Synonym: + syn_items = [{"Lang": "ru", "Content": args.Synonym}] +else: + syn_items = [{"Lang": "ru", "Content": name}] +comment = args.Comment or meta_comment or "" + +md_lines = [ + '', + '', + f'\t', + "\t\t", + f"\t\t\t{esc_text(name)}", + "\t\t\t", +] +for s in syn_items: + md_lines += [ + "\t\t\t\t", + f'\t\t\t\t\t{esc_text(s["Lang"])}', + f'\t\t\t\t\t{esc_text(s["Content"])}', + "\t\t\t\t", + ] +md_lines.append("\t\t\t") +md_lines.append(f"\t\t\t{esc_text(comment)}" if comment else "\t\t\t") +md_lines.append(f"\t\t\t{esc_text(target_ns)}") +md_lines += ["\t\t", "\t", ""] + +with open(md_file, "wb") as f: + f.write(b"\xef\xbb\xbf" + "\r\n".join(md_lines).encode("utf-8")) + +# ── register in Configuration.xml ──────────────────────────── + +config_xml = os.path.join(args.OutputDir, "Configuration.xml") +reg_result = "no-config" +if os.path.exists(config_xml): + with open(config_xml, "rb") as f: + raw = f.read() + had_bom = raw.startswith(b"\xef\xbb\xbf") + cfg_doc = etree.parse(config_xml) + child_objects = cfg_doc.find(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects") + if child_objects is not None: + existing = child_objects.findall(f"{{{MD_NS}}}XDTOPackage") + if any((e.text or "") == name for e in existing): + reg_result = "already" + else: + new_elem = etree.SubElement(child_objects, f"{{{MD_NS}}}XDTOPackage") + new_elem.text = name + if existing: + last = existing[-1] + new_elem.tail = last.tail + child_objects.remove(new_elem) + last.addnext(new_elem) + else: + new_elem.tail = child_objects.text + data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8") + if had_bom: + data = b"\xef\xbb\xbf" + data + with open(config_xml, "wb") as f: + f.write(data) + reg_result = "added" + +type_count = sum(1 for c in pkg_node.children if c.tag in ("objectType", "valueType")) +print(f"✓ Пакет XDTO собран: {name}") +print(f" Namespace: {target_ns}") +print(f" Типов: {type_count}") +print(f" Файлы: XDTOPackages/{name}.xml, XDTOPackages/{name}/Ext/Package.bin") +if reg_result == "added": + print(f" Configuration.xml: {name} добавлен в ChildObjects") +elif reg_result == "already": + print(f" Configuration.xml: {name} уже зарегистрирован") +else: + print(" Configuration.xml не найден — регистрация пропущена") diff --git a/.claude/skills/xdto-decompile/SKILL.md b/.claude/skills/xdto-decompile/SKILL.md new file mode 100644 index 00000000..105d8a14 --- /dev/null +++ b/.claude/skills/xdto-decompile/SKILL.md @@ -0,0 +1,68 @@ +--- +name: xdto-decompile +description: Выгрузка пакета XDTO 1С в XML-схему (XSD). Используй когда нужно прочитать или отредактировать существующий пакет XDTO, отдать схему контрагенту, перенести пакет между конфигурациями +argument-hint: [-OutFile <файл.xsd>] +allowed-tools: + - Bash + - Read + - Glob +--- + +# /xdto-decompile — Выгрузка пакета XDTO в XML-схему + +Превращает `Ext/Package.bin` в обычную XML-схему. Заменяет чтение модели XDTO с её +инвертированной кратностью, фасетами-атрибутами и локальными объявлениями префиксов +`dNpM` на каждой ссылке. + +## Параметры + +| Параметр | Описание | +|----------|----------| +| `PackagePath` | Каталог пакета, путь к `Ext/Package.bin` или к `<Имя>.xml` объекта метаданных | +| `OutFile` | Записать схему в файл (UTF-8 BOM). Без него — вывод в stdout | + +```powershell +powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>" +``` + +Примеры: +```powershell +... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange +... -PackagePath C:\cfsrc\erp\XDTOPackages\ClientBankExchange -OutFile bank.xsd +``` + +## Round-trip + +`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает исходный `Package.bin` +байт-в-байт. Инвариант проверен на 760 пакетах выгрузок Бухгалтерии и ERP. + +Это основной способ править существующий пакет: точечных операций не требуется, схема +целиком читаема и редактируема. + +Свойства объекта метаданных (`Name`, `Synonym`, `Comment`) выгружаются в +`xs:annotation/xs:appinfo`, поэтому при обратной сборке не теряются. `Namespace` не +дублируется — его несёт `targetNamespace`. + +## Отличия от экспорта XML-схемы в Конфигураторе + +Штатный экспорт Конфигуратора теряет данные: `nillable` у свойств-атрибутов +(спецификация XSD не допускает его у атрибутов), а для пакетов с +`elementFormQualified="false"` выдаёт невалидную схему — `form="qualified"` на +глобальных объявлениях. + +Навык пишет валидную схему, а то, что XSD выразить не может, выносит в атрибуты +пространства имён модели XDTO: + +```xml + +``` + +Такие атрибуты валидаторы игнорируют, а `/xdto-compile` читает обратно. Схему без +потерь можно отдавать контрагенту как есть. + +## Верификация + +``` +/xdto-decompile <путь> — схема в stdout +/xdto-decompile <путь> -OutFile schema.xsd — в файл +``` diff --git a/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1 b/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1 new file mode 100644 index 00000000..bca5ac44 --- /dev/null +++ b/.claude/skills/xdto-decompile/scripts/xdto-decompile.ps1 @@ -0,0 +1,603 @@ +# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD) +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +param( + [Parameter(Mandatory=$true)] + [Alias('Path')] + [string]$PackagePath, + [string]$OutFile +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +$XDTO_NS = "http://v8.1c.ru/8.1/xdto" +$XS_NS = "http://www.w3.org/2001/XMLSchema" +$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +$MD_NS = "http://v8.1c.ru/8.3/MDClasses" +$V8_NS = "http://v8.1c.ru/8.1/data/core" + +# --- Resolve paths: accept package dir, Package.bin, or metadata .xml --- + +function Resolve-PackagePaths([string]$p) { + $binPath = $null + $mdPath = $null + + if (Test-Path $p -PathType Leaf) { + if ([System.IO.Path]::GetFileName($p) -eq "Package.bin") { + $binPath = $p + # /Ext/Package.bin -> .xml + $extDir = [System.IO.Path]::GetDirectoryName($p) + $pkgDir = [System.IO.Path]::GetDirectoryName($extDir) + $cand = "$pkgDir.xml" + if (Test-Path $cand) { $mdPath = $cand } + } elseif ($p.EndsWith(".xml")) { + $mdPath = $p + $pkgDir = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($p), [System.IO.Path]::GetFileNameWithoutExtension($p)) + $cand = Join-Path (Join-Path $pkgDir "Ext") "Package.bin" + if (Test-Path $cand) { $binPath = $cand } + } + } elseif (Test-Path $p -PathType Container) { + $cand = Join-Path (Join-Path $p "Ext") "Package.bin" + if (Test-Path $cand) { + $binPath = $cand + $mdCand = "$($p.TrimEnd('\','/')).xml" + if (Test-Path $mdCand) { $mdPath = $mdCand } + } + } + + if (-not $binPath) { throw "Не найден Ext/Package.bin для пути: $p" } + return @{ Bin = $binPath; Md = $mdPath } +} + +$paths = Resolve-PackagePaths $PackagePath + +# --- Load package model --- + +$doc = New-Object System.Xml.XmlDocument +$doc.PreserveWhitespace = $false +$doc.Load($paths.Bin) +$pkg = $doc.DocumentElement +if ($pkg.get_LocalName() -ne "package") { throw "Ожидался корневой , получен <$($pkg.get_LocalName())>" } + +$targetNs = $pkg.GetAttribute("targetNamespace") + +# --- Namespace -> prefix map for the emitted schema --- + +$nsPrefix = @{} +$nsPrefix[$XS_NS] = "xs" +if ($targetNs) { $nsPrefix[$targetNs] = "tns" } + +$imports = @() +foreach ($imp in $pkg.ChildNodes) { + if ($imp.NodeType -ne [System.Xml.XmlNodeType]::Element -or $imp.get_LocalName() -ne "import") { continue } + $ns = $imp.GetAttribute("namespace") + $imports += $ns + if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) } +} + +# Any foreign namespace referenced but not imported still needs a prefix +function Register-Ns([string]$ns) { + if (-not $ns) { return } + if (-not $nsPrefix.ContainsKey($ns)) { $nsPrefix[$ns] = "ns" + ($nsPrefix.Count) } +} + +# --- Output buffer --- + +$sb = New-Object System.Text.StringBuilder +function X([string]$line) { [void]$sb.Append($line); [void]$sb.Append("`r`n") } +function Esc([string]$s) { + if ($null -eq $s) { return "" } + return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">").Replace('"', """) +} +function EscText([string]$s) { + if ($null -eq $s) { return "" } + return $s.Replace("&", "&").Replace("<", "<").Replace(">", ">") +} + +# --- QName conversion: bin prefix -> schema prefix --- + +function Convert-QName([System.Xml.XmlElement]$el, [string]$qname) { + if (-not $qname) { return $null } + # Нотация Кларка {ns}local — так записаны почти все memberTypes + if ($qname.StartsWith("{")) { + $close = $qname.IndexOf("}") + if ($close -gt 0) { + $ns = $qname.Substring(1, $close - 1) + $local = $qname.Substring($close + 1) + if (-not $ns) { return $local } + Register-Ns $ns + return "$($nsPrefix[$ns]):$local" + } + } + $parts = $qname.Split(":") + if ($parts.Count -eq 2) { + $ns = $el.GetNamespaceOfPrefix($parts[0]) + $local = $parts[1] + } else { + $ns = $el.GetNamespaceOfPrefix("") + $local = $parts[0] + } + if (-not $ns) { return $qname } + Register-Ns $ns + return "$($nsPrefix[$ns]):$local" +} + +function Convert-QNameList([System.Xml.XmlElement]$el, [string]$list) { + if (-not $list) { return $null } + $out = @() + foreach ($q in ($list -split "\s+")) { + if ($q) { $out += (Convert-QName $el $q) } + } + return ($out -join " ") +} + +# --- Attribute helpers --- + +function A([System.Xml.XmlElement]$el, [string]$name) { + if ($el.HasAttribute($name)) { return $el.GetAttribute($name) } + return $null +} + +# Emits `key="value"` pairs, skipping nulls +function Attrs([object[]]$pairs) { + $out = "" + for ($i = 0; $i -lt $pairs.Count; $i += 2) { + $v = $pairs[$i + 1] + if ($null -ne $v) { $out += " $($pairs[$i])=`"$(Esc ([string]$v))`"" } + } + return $out +} + +# --- xdto: mirror attributes --- +# Everything XSD cannot express literally rides as xdto:. +# Mirrors are emitted only when the literal bin form is not recoverable from the XSD. + +$usesXdtoNs = $false +function Mirror([string]$name, $value) { + # $value НЕ типизируем: [string]$null коэрсится в "" и зеркало ложно появляется + if ($null -eq $value) { return "" } + $script:usesXdtoNs = $true + return " xdto:$name=`"$(Esc ([string]$value))`"" +} + +# Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный +# префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся. +function Mirror-Prefix([System.Xml.XmlElement]$el) { + foreach ($a in $el.Attributes) { + if ($a.Prefix -ne "xmlns") { continue } + if ($a.get_LocalName() -match '^d\d+p\d+$') { continue } + return (Mirror "prefix" $a.get_LocalName()) + } + return "" +} + +# --- Facet emission (simple types) --- + +$FACET_ATTRS = @("length", "minLength", "maxLength", "totalDigits", "fractionDigits", + "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace") + +function Emit-Facets([System.Xml.XmlElement]$el, [string]$indent) { + foreach ($f in $FACET_ATTRS) { + $v = A $el $f + if ($null -ne $v) { X "$indent" } + } + foreach ($child in $el.ChildNodes) { + if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + if ($child.get_LocalName() -eq "pattern") { + X "$indent" + } elseif ($child.get_LocalName() -eq "enumeration") { + # xsi:type on enumeration has no XSD counterpart — mirror it + $xsiType = $child.GetAttribute("type", $XSI_NS) + $m = "" + if ($xsiType) { $m = Mirror "type" (Convert-QName $child $xsiType) } + X "$indent" + } + } +} + +function Has-SimpleContent([System.Xml.XmlElement]$el) { + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { return $true } + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { return $true } + } + foreach ($f in $FACET_ATTRS) { if ($null -ne (A $el $f)) { return $true } } + return $false +} + +# --- Simple type body (valueType / typeDef xsi:type=ValueType) --- + +function Emit-SimpleTypeBody([System.Xml.XmlElement]$el, [string]$indent) { + $variety = A $el "variety" + $base = Convert-QName $el (A $el "base") + $itemType = Convert-QName $el (A $el "itemType") + $memberTypes= Convert-QNameList $el (A $el "memberTypes") + + # variety is mirrored: "Atomic" is written explicitly for only part of the corpus + $mv = Mirror "variety" $variety + + # memberTypes почти всегда записаны нотацией Кларка; редкую префиксную форму зеркалим + $rawMembers = A $el "memberTypes" + if ($null -ne $rawMembers -and -not $rawMembers.StartsWith("{")) { + $mv += Mirror "memberTypesForm" "prefixed" + } + # При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет — + # из значения это не выводится (зависит от состояния сериализатора), зеркалим факт + if ($null -ne $rawMembers -and $rawMembers.StartsWith("{")) { + foreach ($a in $el.Attributes) { + if ($a.Prefix -eq "xmlns" -and $a.get_LocalName() -match '^d\d+p\d+$') { + $mv += Mirror "declareNs" $a.Value + break + } + } + } + + if ($variety -eq "List" -or $itemType) { + X "$indent" + return + } + if ($variety -eq "Union" -or $memberTypes) { + $anon = @() + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon += $c } + } + if ($anon.Count -eq 0) { + X "$indent" + } else { + X "$indent" + foreach ($c in $anon) { + X "$indent`t" + Emit-SimpleTypeBody $c "$indent`t`t" + X "$indent`t" + } + X "$indent" + } + return + } + + # Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef + $anonBase = $null + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break } + } + + if ((Has-SimpleContent $el) -or $anonBase) { + X "$indent" + if ($anonBase) { + X "$indent`t" + Emit-SimpleTypeBody $anonBase "$indent`t`t" + X "$indent`t" + } + Emit-Facets $el "$indent`t" + X "$indent" + } else { + X "$indent" + } +} + +# --- Property classification --- + +function Get-PropForm([System.Xml.XmlElement]$p) { + $f = A $p "form" + if ($null -eq $f) { return "Element" } + return $f +} + +function Get-AnonTypeDef([System.Xml.XmlElement]$p) { + foreach ($c in $p.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { return $c } + } + return $null +} + +# --- Property emission --- + +function Emit-Property([System.Xml.XmlElement]$p, [string]$indent, [bool]$isGlobal) { + $form = Get-PropForm $p + $name = A $p "name" + $type = Convert-QName $p (A $p "type") + $ref = Convert-QName $p (A $p "ref") + $local = A $p "localName" + $lower = A $p "lowerBound" + $upper = A $p "upperBound" + $nill = A $p "nillable" + $def = A $p "default" + $fix = A $p "fixed" + $anon = Get-AnonTypeDef $p + # qualified записан как атрибут в пространстве имён XDTO + $qual = $p.GetAttribute("qualified", $XDTO_NS) + if ($qual -eq "") { $qual = $null } + + # lowerBound/upperBound map 1:1 onto minOccurs/maxOccurs, including "written explicitly" + $minOccurs = $lower + $maxOccurs = $null + if ($null -ne $upper) { $maxOccurs = if ($upper -eq "-1") { "unbounded" } else { $upper } } + + # localName carries the original XML name when it is not a valid 1C identifier + $xmlName = if ($null -ne $local) { $local } else { $name } + $mirrorName = if ($null -ne $local) { Mirror "name" $name } else { "" } + + $m = "" + $isAttr = ($form -eq "Attribute") + + if ($isAttr) { + # XSD forbids nillable on attributes, and has no minOccurs/maxOccurs + if ($null -ne $qual) { $m += Mirror "qualified" $qual } + if ($null -ne $nill) { $m += Mirror "nillable" $nill } + if ($null -ne $lower) { $m += Mirror "lowerBound" $lower } + if ($null -ne $upper) { $m += Mirror "upperBound" $upper } + $m += $mirrorName + $body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $def, 'fixed', $fix) + if ($anon) { + X "$indent" + X "$indent`t" + Emit-SimpleTypeBody $anon "$indent`t`t" + X "$indent`t" + X "$indent" + } else { + X "$indent" + } + return + } + + if ($form -eq "Text") { + # handled by the owning complexType (xs:simpleContent) + return + } + + # form="Element" written explicitly is indistinguishable in XSD from the default + if ($null -ne (A $p "form")) { $m += Mirror "form" $form } + if ($null -ne $qual) { $m += Mirror "qualified" $qual } + $m += $mirrorName + $m += (Mirror-Prefix $p) + + $body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, + 'minOccurs', $minOccurs, 'maxOccurs', $maxOccurs, + 'nillable', $nill, 'default', $def, 'fixed', $fix) + + if ($anon) { + X "$indent" + if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") { + $anonBase = Convert-QName $anon (A $anon "base") + if ($anonBase) { + X "$indent`t" + X "$indent`t`t" + X "$indent`t`t`t" + Emit-ComplexTypeBody $anon "$indent`t`t`t`t" + X "$indent`t`t`t" + X "$indent`t`t" + X "$indent`t" + } else { + X "$indent`t" + Emit-ComplexTypeBody $anon "$indent`t`t" + X "$indent`t" + } + } else { + X "$indent`t" + Emit-SimpleTypeBody $anon "$indent`t`t" + X "$indent`t" + } + X "$indent" + } else { + X "$indent" + } +} + +# --- Complex type body (objectType / typeDef xsi:type=ObjectType) --- + +function Emit-ComplexTypeBody([System.Xml.XmlElement]$el, [string]$indent) { + $open = A $el "open" + $ordered = A $el "ordered" + $sequenced = A $el "sequenced" + + $props = @() + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $props += $c } + } + + $elems = @(); $attrs = @(); $text = $null + foreach ($p in $props) { + switch (Get-PropForm $p) { + "Attribute" { $attrs += $p } + "Text" { $text = $p } + default { $elems += $p } + } + } + + # simpleContent: a "Text" property holds the element's own value + if ($null -ne $text) { + $tType = Convert-QName $text (A $text "type") + $tm = "" + $tName = A $text "name" + if ($tName -ne "__content") { $tm += Mirror "textName" $tName } + foreach ($extra in @("lowerBound", "upperBound", "nillable")) { + $v = A $text $extra + if ($null -ne $v) { $tm += Mirror "text$extra" $v } + } + X "$indent" + X "$indent`t" + foreach ($a in $attrs) { Emit-Property $a "$indent`t`t" $false } + X "$indent`t" + X "$indent" + return + } + + $particleTag = if ($ordered -eq "false") { "xs:choice" } else { "xs:sequence" } + $needParticle = ($elems.Count -gt 0) -or ($open -eq "true") + + if ($needParticle) { + X "$indent<$particleTag>" + foreach ($e in $elems) { Emit-Property $e "$indent`t" $false } + if ($open -eq "true") { + X "$indent`t" + } + X "$indent" + } + + foreach ($a in $attrs) { Emit-Property $a "$indent" $false } + if ($open -eq "true") { + X "$indent" + } +} + +# Attributes of a complexType tag itself (mirrors + XSD-native abstract/mixed) +function ComplexTypeAttrs([System.Xml.XmlElement]$el) { + $open = A $el "open" + $ordered = A $el "ordered" + $sequenced = A $el "sequenced" + $abstract = A $el "abstract" + $mixed = A $el "mixed" + + $out = "" + if ($abstract -eq "true") { $out += " abstract=`"true`"" } elseif ($null -ne $abstract) { $out += Mirror "abstract" $abstract } + if ($mixed -eq "true") { $out += " mixed=`"true`"" } elseif ($null -ne $mixed) { $out += Mirror "mixed" $mixed } + + # XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств + # восстановим как «сначала form=Attribute, потом остальные» — это верно для 96.5% + # типов корпуса. Расхождения (768 типов) зеркалим списком имён. + $order = @(); $kinds = @() + foreach ($c in $el.ChildNodes) { + if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue } + $nm = A $c "name" + if ($null -eq $nm) { $nm = "@" + ((A $c "ref") -split ":")[-1] } + $order += $nm + $kinds += $(if ((Get-PropForm $c) -eq "Attribute") { 0 } else { 1 }) + } + if ($order.Count -gt 1) { + $natural = $true + for ($i = 1; $i -lt $kinds.Count; $i++) { if ($kinds[$i] -lt $kinds[$i - 1]) { $natural = $false; break } } + if (-not $natural) { $out += Mirror "order" ($order -join "|") } + } + + # open="true" is rendered as xs:any + xs:anyAttribute; anything else is mirrored + if ($null -ne $open -and $open -ne "true") { $out += Mirror "open" $open } + # ordered="false" is rendered as xs:choice; "true" written explicitly is mirrored + if ($null -ne $ordered -and $ordered -ne "false") { $out += Mirror "ordered" $ordered } + # sequenced has no XSD counterpart at all + if ($null -ne $sequenced) { $out += Mirror "sequenced" $sequenced } + + return $out +} + +# --- Metadata properties (Name/Synonym/Comment) from the object's .xml --- + +function Get-MetadataBlock() { + if (-not $paths.Md -or -not (Test-Path $paths.Md)) { return $null } + $md = New-Object System.Xml.XmlDocument + $md.Load($paths.Md) + $nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable) + $nsm.AddNamespace("md", $MD_NS) + $nsm.AddNamespace("v8", $V8_NS) + $props = $md.SelectSingleNode("//md:XDTOPackage/md:Properties", $nsm) + if (-not $props) { return $null } + + $res = @{ Name = $null; Comment = $null; Synonym = @() } + $n = $props.SelectSingleNode("md:Name", $nsm); if ($n) { $res.Name = $n.InnerText } + $c = $props.SelectSingleNode("md:Comment", $nsm); if ($c) { $res.Comment = $c.InnerText } + foreach ($item in $props.SelectNodes("md:Synonym/v8:item", $nsm)) { + $lang = $item.SelectSingleNode("v8:lang", $nsm) + $cont = $item.SelectSingleNode("v8:content", $nsm) + $res.Synonym += @{ Lang = $(if ($lang) { $lang.InnerText } else { "" }); Content = $(if ($cont) { $cont.InnerText } else { "" }) } + } + return $res +} + +$meta = Get-MetadataBlock + +# --- Emit --- +# Body first: emitting it registers every namespace actually referenced, so the +# schema element can declare a complete prefix map. + +$bodyBuilder = New-Object System.Text.StringBuilder +$mainBuilder = $sb +$sb = $bodyBuilder + +foreach ($node in $pkg.ChildNodes) { + if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + switch ($node.get_LocalName()) { + "import" { + X "`t" + } + "property" { + Emit-Property $node "`t" $true + } + "valueType" { + $name = A $node "name" + X "`t" + Emit-SimpleTypeBody $node "`t`t" + X "`t" + } + "objectType" { + $name = A $node "name" + $base = Convert-QName $node (A $node "base") + $cta = ComplexTypeAttrs $node + if ($base) { + X "`t" + X "`t`t" + X "`t`t`t" + Emit-ComplexTypeBody $node "`t`t`t`t" + X "`t`t`t" + X "`t`t" + X "`t" + } else { + $hasBody = $false + foreach ($c in $node.ChildNodes) { if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) { $hasBody = $true; break } } + if (-not $hasBody -and (A $node "open") -ne "true") { + X "`t" + } else { + X "`t" + Emit-ComplexTypeBody $node "`t`t" + X "`t" + } + } + } + } +} + +$sb = $mainBuilder + +# --- Schema element --- + +$nsDecls = "" +foreach ($kv in ($nsPrefix.GetEnumerator() | Sort-Object { $_.Value })) { + $nsDecls += " xmlns:$($kv.Value)=`"$(Esc $kv.Key)`"" +} +if ($usesXdtoNs) { $nsDecls += " xmlns:xdto=`"$XDTO_NS`"" } + +$schemaAttrs = "" +$efq = A $pkg "elementFormQualified" +$afq = A $pkg "attributeFormQualified" +if ($null -ne $efq) { $schemaAttrs += " elementFormDefault=`"$(if ($efq -eq 'true') { 'qualified' } else { 'unqualified' })`"" } +if ($null -ne $afq) { $schemaAttrs += " attributeFormDefault=`"$(if ($afq -eq 'true') { 'qualified' } else { 'unqualified' })`"" } + +X "" + +if ($meta) { + X "`t" + X "`t`t" + X "`t`t`t" + if ($null -ne $meta.Name) { X "`t`t`t`t$(EscText $meta.Name)" } + if ($null -ne $meta.Comment -and $meta.Comment -ne "") { X "`t`t`t`t$(EscText $meta.Comment)" } + foreach ($s in $meta.Synonym) { + X "`t`t`t`t$(EscText $s.Content)" + } + X "`t`t`t" + X "`t`t" + X "`t" +} + +[void]$sb.Append($bodyBuilder.ToString()) +X "" + +# --- Output (UTF-8 with BOM, CRLF — matches the Designer's own XSD export) --- + +$text = $sb.ToString() +if ($OutFile) { + $dir = [System.IO.Path]::GetDirectoryName($OutFile) + if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null } + $enc = New-Object System.Text.UTF8Encoding($true) + [System.IO.File]::WriteAllText($OutFile, $text, $enc) + Write-Host "✓ XSD записана: $OutFile" + Write-Host " targetNamespace: $targetNs" +} else { + [Console]::Out.Write($text) +} diff --git a/.claude/skills/xdto-decompile/scripts/xdto-decompile.py b/.claude/skills/xdto-decompile/scripts/xdto-decompile.py new file mode 100644 index 00000000..133e68b8 --- /dev/null +++ b/.claude/skills/xdto-decompile/scripts/xdto-decompile.py @@ -0,0 +1,567 @@ +# xdto-decompile v1.0 — Convert 1C XDTO package to XML Schema (XSD) (Python port) +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +import argparse +import os +import re +import sys + +from lxml import etree + +# newline="" — иначе Windows транслирует \n и CRLF схемы удваивается в \r\r\n +sys.stdout.reconfigure(encoding="utf-8", newline="") +sys.stderr.reconfigure(encoding="utf-8") + +XDTO_NS = "http://v8.1c.ru/8.1/xdto" +XS_NS = "http://www.w3.org/2001/XMLSchema" +XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +MD_NS = "http://v8.1c.ru/8.3/MDClasses" +V8_NS = "http://v8.1c.ru/8.1/data/core" + +parser = argparse.ArgumentParser(allow_abbrev=False) +parser.add_argument("-PackagePath", "-Path", required=True) +parser.add_argument("-OutFile", default="") +args = parser.parse_args() + +# ── resolve paths ──────────────────────────────────────────── + +package_path = args.PackagePath +bin_path = None +md_path = None + +if os.path.isfile(package_path): + if os.path.basename(package_path) == "Package.bin": + bin_path = package_path + pkg_dir = os.path.dirname(os.path.dirname(package_path)) + if os.path.exists(pkg_dir + ".xml"): + md_path = pkg_dir + ".xml" + elif package_path.endswith(".xml"): + md_path = package_path + stem = os.path.join(os.path.dirname(package_path), + os.path.splitext(os.path.basename(package_path))[0]) + c = os.path.join(stem, "Ext", "Package.bin") + if os.path.exists(c): + bin_path = c +elif os.path.isdir(package_path): + c = os.path.join(package_path, "Ext", "Package.bin") + if os.path.exists(c): + bin_path = c + m = package_path.rstrip("\\/") + ".xml" + if os.path.exists(m): + md_path = m + +if not bin_path: + print(f"Не найден Ext/Package.bin для пути: {package_path}", file=sys.stderr) + sys.exit(1) + +doc = etree.parse(bin_path) +pkg = doc.getroot() + + +def local(el): + return etree.QName(el).localname + + +if local(pkg) != "package": + print(f"Ожидался корневой , получен <{local(pkg)}>", file=sys.stderr) + sys.exit(1) + +target_ns = pkg.get("targetNamespace") + +# ── namespace -> prefix map for the emitted schema ─────────── + +ns_prefix = {XS_NS: "xs"} +if target_ns: + ns_prefix[target_ns] = "tns" + +imports = [] +for imp in pkg: + if isinstance(imp.tag, str) and local(imp) == "import": + ns = imp.get("namespace") + imports.append(ns) + if ns not in ns_prefix: + ns_prefix[ns] = "ns" + str(len(ns_prefix)) + + +def register_ns(ns): + if ns and ns not in ns_prefix: + ns_prefix[ns] = "ns" + str(len(ns_prefix)) + + +# ── output buffer ──────────────────────────────────────────── + +lines = [] + + +def X(line): + lines.append(line) + + +def esc(s): + if s is None: + return "" + return str(s).replace("&", "&").replace("<", "<").replace(">", ">").replace('"', """) + + +def esc_text(s): + if s is None: + return "" + return str(s).replace("&", "&").replace("<", "<").replace(">", ">") + + +# ── QName conversion: bin prefix -> schema prefix ──────────── + +def convert_qname(el, qname): + if not qname: + return None + # Нотация Кларка {ns}local — так записаны почти все memberTypes + if qname.startswith("{"): + close = qname.find("}") + if close > 0: + ns = qname[1:close] + loc = qname[close + 1:] + if not ns: + return loc + register_ns(ns) + return f"{ns_prefix[ns]}:{loc}" + parts = qname.split(":") + if len(parts) == 2: + ns = el.nsmap.get(parts[0]) + loc = parts[1] + else: + ns = el.nsmap.get(None) + loc = parts[0] + if not ns: + return qname + register_ns(ns) + return f"{ns_prefix[ns]}:{loc}" + + +def convert_qname_list(el, lst): + if not lst: + return None + return " ".join(convert_qname(el, q) for q in lst.split() if q) + + +def attrs(pairs): + out = "" + for i in range(0, len(pairs), 2): + v = pairs[i + 1] + if v is not None: + out += f' {pairs[i]}="{esc(v)}"' + return out + + +# ── xdto: mirror attributes ────────────────────────────────── + +state = {"uses_xdto": False} + + +def mirror(name, value): + if value is None: + return "" + state["uses_xdto"] = True + return f' xdto:{name}="{esc(value)}"' + + +DNPM = re.compile(r"^d\d+p\d+$") + + +def ns_decls_of(el): + """Локальные объявления xmlns на самом узле, как (префикс, uri).""" + parent_map = el.getparent().nsmap if el.getparent() is not None else {} + for px, uri in el.nsmap.items(): + if px is None: + continue + if parent_map.get(px) == uri: + continue + yield px, uri + + +def mirror_prefix(el): + # Обычно префиксы генерируются схемой dNpM, но изредка узел несёт осмысленный + # префикс (например dcsset) — его надо сохранить, иначе round-trip не сойдётся. + for px, _uri in ns_decls_of(el): + if DNPM.match(px): + continue + return mirror("prefix", px) + return "" + + +FACET_ATTRS = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits", + "minInclusive", "maxInclusive", "minExclusive", "maxExclusive", "whiteSpace"] + + +def emit_facets(el, indent): + for f in FACET_ATTRS: + v = el.get(f) + if v is not None: + X(f'{indent}') + for child in el: + if not isinstance(child.tag, str): + continue + ln = local(child) + if ln == "pattern": + X(f'{indent}') + elif ln == "enumeration": + xsi_type = child.get(f"{{{XSI_NS}}}type") + m = mirror("type", convert_qname(child, xsi_type)) if xsi_type else "" + X(f'{indent}') + + +def has_simple_content(el): + for c in el: + if isinstance(c.tag, str) and local(c) in ("pattern", "enumeration"): + return True + return any(el.get(f) is not None for f in FACET_ATTRS) + + +# ── simple type body (valueType / typeDef xsi:type=ValueType) ─ + +def emit_simple_type_body(el, indent): + variety = el.get("variety") + base = convert_qname(el, el.get("base")) + item_type = convert_qname(el, el.get("itemType")) + member_types = convert_qname_list(el, el.get("memberTypes")) + + mv = mirror("variety", variety) + + raw_members = el.get("memberTypes") + if raw_members is not None and not raw_members.startswith("{"): + mv += mirror("memberTypesForm", "prefixed") + # При нотации Кларка объявление xmlns:dNpM иногда присутствует, иногда нет — + # из значения это не выводится (зависит от состояния сериализатора), зеркалим факт + if raw_members is not None and raw_members.startswith("{"): + for px, uri in ns_decls_of(el): + if DNPM.match(px): + mv += mirror("declareNs", uri) + break + + if variety == "List" or item_type: + X(f'{indent}') + return + if variety == "Union" or member_types: + anon = [c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"] + if not anon: + X(f'{indent}') + else: + X(f'{indent}') + for c in anon: + X(f"{indent}\t") + emit_simple_type_body(c, indent + "\t\t") + X(f"{indent}\t") + X(f"{indent}") + return + + # Базовый тип может быть задан не атрибутом base, а вложенным анонимным typeDef + anon_base = next((c for c in el if isinstance(c.tag, str) and local(c) == "typeDef"), None) + + if has_simple_content(el) or anon_base is not None: + X(f'{indent}') + if anon_base is not None: + X(f"{indent}\t") + emit_simple_type_body(anon_base, indent + "\t\t") + X(f"{indent}\t") + emit_facets(el, indent + "\t") + X(f"{indent}") + else: + X(f'{indent}') + + +# ── property classification ────────────────────────────────── + +def prop_form(p): + f = p.get("form") + return "Element" if f is None else f + + +def anon_type_def(p): + return next((c for c in p if isinstance(c.tag, str) and local(c) == "typeDef"), None) + + +# ── property emission ──────────────────────────────────────── + +def emit_property(p, indent): + form = prop_form(p) + name = p.get("name") + type_ = convert_qname(p, p.get("type")) + ref = convert_qname(p, p.get("ref")) + local_name = p.get("localName") + lower = p.get("lowerBound") + upper = p.get("upperBound") + nill = p.get("nillable") + default = p.get("default") + fixed = p.get("fixed") + anon = anon_type_def(p) + qual = p.get(f"{{{XDTO_NS}}}qualified") + + min_occurs = lower + max_occurs = None + if upper is not None: + max_occurs = "unbounded" if upper == "-1" else upper + + xml_name = local_name if local_name is not None else name + mirror_name = mirror("name", name) if local_name is not None else "" + + m = "" + if form == "Attribute": + if qual is not None: + m += mirror("qualified", qual) + if nill is not None: + m += mirror("nillable", nill) + if lower is not None: + m += mirror("lowerBound", lower) + if upper is not None: + m += mirror("upperBound", upper) + m += mirror_name + body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", default, "fixed", fixed]) + if anon is not None: + X(f"{indent}") + X(f"{indent}\t") + emit_simple_type_body(anon, indent + "\t\t") + X(f"{indent}\t") + X(f"{indent}") + else: + X(f"{indent}") + return + + if form == "Text": + # handled by the owning complexType (xs:simpleContent) + return + + if p.get("form") is not None: + m += mirror("form", form) + if qual is not None: + m += mirror("qualified", qual) + m += mirror_name + m += mirror_prefix(p) + + body = attrs(["name", xml_name, "ref", ref, "type", type_, + "minOccurs", min_occurs, "maxOccurs", max_occurs, + "nillable", nill, "default", default, "fixed", fixed]) + + if anon is not None: + X(f"{indent}") + if anon.get(f"{{{XSI_NS}}}type") == "ObjectType": + anon_base = convert_qname(anon, anon.get("base")) + if anon_base: + X(f"{indent}\t") + X(f"{indent}\t\t") + X(f'{indent}\t\t\t') + emit_complex_type_body(anon, indent + "\t\t\t\t") + X(f"{indent}\t\t\t") + X(f"{indent}\t\t") + X(f"{indent}\t") + else: + X(f"{indent}\t") + emit_complex_type_body(anon, indent + "\t\t") + X(f"{indent}\t") + else: + X(f"{indent}\t") + emit_simple_type_body(anon, indent + "\t\t") + X(f"{indent}\t") + X(f"{indent}") + else: + X(f"{indent}") + + +# ── complex type body ──────────────────────────────────────── + +def emit_complex_type_body(el, indent): + open_ = el.get("open") + ordered = el.get("ordered") + + props = [c for c in el if isinstance(c.tag, str) and local(c) == "property"] + elems, attr_props, text = [], [], None + for p in props: + f = prop_form(p) + if f == "Attribute": + attr_props.append(p) + elif f == "Text": + text = p + else: + elems.append(p) + + if text is not None: + t_type = convert_qname(text, text.get("type")) + tm = "" + t_name = text.get("name") + if t_name != "__content": + tm += mirror("textName", t_name) + for extra in ("lowerBound", "upperBound", "nillable"): + v = text.get(extra) + if v is not None: + tm += mirror("text" + extra, v) + X(f"{indent}") + X(f'{indent}\t') + for a in attr_props: + emit_property(a, indent + "\t\t") + X(f"{indent}\t") + X(f"{indent}") + return + + particle_tag = "xs:choice" if ordered == "false" else "xs:sequence" + if elems or open_ == "true": + X(f"{indent}<{particle_tag}>") + for e in elems: + emit_property(e, indent + "\t") + if open_ == "true": + X(f'{indent}\t') + X(f"{indent}") + + for a in attr_props: + emit_property(a, indent) + if open_ == "true": + X(f'{indent}') + + +def complex_type_attrs(el): + open_ = el.get("open") + ordered = el.get("ordered") + sequenced = el.get("sequenced") + abstract = el.get("abstract") + mixed = el.get("mixed") + + out = "" + if abstract == "true": + out += ' abstract="true"' + elif abstract is not None: + out += mirror("abstract", abstract) + if mixed == "true": + out += ' mixed="true"' + elif mixed is not None: + out += mirror("mixed", mixed) + + # XSD требует объявлять атрибуты после частицы, поэтому исходный порядок свойств + # восстановим как «сначала form=Attribute, потом остальные» — верно для 96.5% типов + order, kinds = [], [] + for c in el: + if not isinstance(c.tag, str) or local(c) != "property": + continue + nm = c.get("name") + if nm is None: + nm = "@" + (c.get("ref") or "").split(":")[-1] + order.append(nm) + kinds.append(0 if prop_form(c) == "Attribute" else 1) + if len(order) > 1: + natural = all(kinds[i] >= kinds[i - 1] for i in range(1, len(kinds))) + if not natural: + out += mirror("order", "|".join(order)) + + if open_ is not None and open_ != "true": + out += mirror("open", open_) + if ordered is not None and ordered != "false": + out += mirror("ordered", ordered) + if sequenced is not None: + out += mirror("sequenced", sequenced) + return out + + +# ── metadata properties ────────────────────────────────────── + +meta = None +if md_path and os.path.exists(md_path): + md = etree.parse(md_path) + props_el = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties") + if props_el is not None: + meta = {"Name": None, "Comment": None, "Synonym": []} + n = props_el.find(f"{{{MD_NS}}}Name") + if n is not None: + meta["Name"] = n.text or "" + c = props_el.find(f"{{{MD_NS}}}Comment") + if c is not None: + meta["Comment"] = c.text or "" + for item in props_el.iterfind(f"{{{MD_NS}}}Synonym/{{{V8_NS}}}item"): + lang = item.find(f"{{{V8_NS}}}lang") + cont = item.find(f"{{{V8_NS}}}content") + meta["Synonym"].append({ + "Lang": (lang.text or "") if lang is not None else "", + "Content": (cont.text or "") if cont is not None else "", + }) + +# ── emit ───────────────────────────────────────────────────── +# Тело первым: при его генерации регистрируются все использованные пространства +# имён, поэтому корневой элемент может объявить полную карту префиксов. + +for node in pkg: + if not isinstance(node.tag, str): + continue + ln = local(node) + if ln == "import": + X(f'\t') + elif ln == "property": + emit_property(node, "\t") + elif ln == "valueType": + X(f'\t') + emit_simple_type_body(node, "\t\t") + X("\t") + elif ln == "objectType": + name = node.get("name") + base = convert_qname(node, node.get("base")) + cta = complex_type_attrs(node) + if base: + X(f'\t') + X("\t\t") + X(f'\t\t\t') + emit_complex_type_body(node, "\t\t\t\t") + X("\t\t\t") + X("\t\t") + X("\t") + else: + has_body = any(isinstance(c.tag, str) for c in node) + if not has_body and node.get("open") != "true": + X(f'\t') + else: + X(f'\t') + emit_complex_type_body(node, "\t\t") + X("\t") + +body_lines = lines +lines = [] + +# ── schema element ─────────────────────────────────────────── + +ns_decls = "" +for uri, px in sorted(ns_prefix.items(), key=lambda kv: kv[1]): + ns_decls += f' xmlns:{px}="{esc(uri)}"' +if state["uses_xdto"]: + ns_decls += f' xmlns:xdto="{XDTO_NS}"' + +schema_attrs = "" +efq = pkg.get("elementFormQualified") +afq = pkg.get("attributeFormQualified") +if efq is not None: + schema_attrs += f' elementFormDefault="{"qualified" if efq == "true" else "unqualified"}"' +if afq is not None: + schema_attrs += f' attributeFormDefault="{"qualified" if afq == "true" else "unqualified"}"' + +X(f'') + +if meta: + X("\t") + X("\t\t") + X(f'\t\t\t') + if meta["Name"] is not None: + X(f'\t\t\t\t{esc_text(meta["Name"])}') + if meta["Comment"]: + X(f'\t\t\t\t{esc_text(meta["Comment"])}') + for s in meta["Synonym"]: + X(f'\t\t\t\t{esc_text(s["Content"])}') + X("\t\t\t") + X("\t\t") + X("\t") + +lines.extend(body_lines) +X("") + +text = "\r\n".join(lines) + "\r\n" + +if args.OutFile: + d = os.path.dirname(args.OutFile) + if d and not os.path.isdir(d): + os.makedirs(d, exist_ok=True) + with open(args.OutFile, "wb") as f: + f.write(b"\xef\xbb\xbf" + text.encode("utf-8")) + print(f"✓ XSD записана: {args.OutFile}") + print(f" targetNamespace: {target_ns}") +else: + sys.stdout.write(text) diff --git a/.claude/skills/xdto-validate/SKILL.md b/.claude/skills/xdto-validate/SKILL.md new file mode 100644 index 00000000..354b0989 --- /dev/null +++ b/.claude/skills/xdto-validate/SKILL.md @@ -0,0 +1,71 @@ +--- +name: xdto-validate +description: Валидация пакета XDTO 1С. Используй после создания или модификации пакета XDTO для проверки корректности +argument-hint: [-ConfigDir <каталог>] [-Detailed] [-MaxErrors N] [-OutFile <файл>] +allowed-tools: + - Bash + - Read + - Glob +--- + +# /xdto-validate — Валидация пакета XDTO + +Проверяет модель пакета, объект метаданных и его связь с конфигурацией. +Exit code `1` при наличии ошибок. + +## Параметры + +| Параметр | Описание | +|----------|----------| +| `PackagePath` | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml` объекта метаданных | +| `ConfigDir` | Каталог конфигурации. По умолчанию определяется по расположению пакета | +| `Detailed` | Показывать успешные проверки, а не только ошибки | +| `MaxErrors` | Остановиться после N ошибок (по умолчанию 20) | +| `OutFile` | Записать отчёт в файл | + +```powershell +powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>" +``` + +## Проверки + +**Модель** +- Package.bin — корректный XML с корнем ``, задан `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` + потом отдаёт бесструктурное значение. Сопутствующий признак — объявленный, но + неиспользуемый ``. +- **`nillable="true"` вместе с `form="Attribute"`.** Спецификация XSD не допускает + `nillable` у атрибутов, поэтому экспорт XML-схемы в Конфигураторе такое свойство теряет. + На саму работу пакета не влияет, но ломает перенос схемы через Конфигуратор. + +## Типичный workflow + +1. `/xdto-compile` или `/xdto-decompile` → правка → `/xdto-compile -Force` +2. `/xdto-validate <путь>` — до загрузки в базу +3. `/db-load-xml` + `/db-update` + +## Верификация + +``` +/xdto-validate <путь> — только проблемы +/xdto-validate <путь> -Detailed — все проверки +``` diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.ps1 b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1 new file mode 100644 index 00000000..525bdeb7 --- /dev/null +++ b/.claude/skills/xdto-validate/scripts/xdto-validate.ps1 @@ -0,0 +1,402 @@ +# xdto-validate v1.0 — Validate a 1C XDTO package +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +param( + [Parameter(Mandatory)] + [Alias('Path')] + [string]$PackagePath, + + [string]$ConfigDir, + + [switch]$Detailed, + + [int]$MaxErrors = 20, + + [string]$OutFile +) + +$ErrorActionPreference = "Stop" +[Console]::OutputEncoding = [System.Text.Encoding]::UTF8 + +$XS_NS = "http://www.w3.org/2001/XMLSchema" +$XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +$MD_NS = "http://v8.1c.ru/8.3/MDClasses" + +# --- Reporting --- + +$script:errors = 0 +$script:warnings = 0 +$script:okCount = 0 +$script:stopped = $false +$script:output = New-Object System.Text.StringBuilder + +function Out-Line([string]$s) { [void]$script:output.AppendLine($s) } +function Report-OK([string]$msg) { + $script:okCount++ + if ($Detailed) { Out-Line "[OK] $msg" } +} +function Report-Error([string]$msg) { + $script:errors++ + Out-Line "[ERROR] $msg" + if ($script:errors -ge $MaxErrors) { $script:stopped = $true } +} +function Report-Warn([string]$msg) { + $script:warnings++ + Out-Line "[WARN] $msg" +} + +# --- Resolve paths --- + +if (-not [System.IO.Path]::IsPathRooted($PackagePath)) { + $PackagePath = Join-Path (Get-Location).Path $PackagePath +} + +$binPath = $null +$mdPath = $null +if (Test-Path $PackagePath -PathType Leaf) { + if ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin") { + $binPath = $PackagePath + $pkgDir = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($PackagePath)) + if (Test-Path "$pkgDir.xml") { $mdPath = "$pkgDir.xml" } + } elseif ($PackagePath.EndsWith(".xml")) { + $mdPath = $PackagePath + $stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath), [System.IO.Path]::GetFileNameWithoutExtension($PackagePath)) + $c = Join-Path (Join-Path $stem "Ext") "Package.bin" + if (Test-Path $c) { $binPath = $c } + } +} elseif (Test-Path $PackagePath -PathType Container) { + $c = Join-Path (Join-Path $PackagePath "Ext") "Package.bin" + if (Test-Path $c) { + $binPath = $c + $m = "$($PackagePath.TrimEnd('\','/')).xml" + if (Test-Path $m) { $mdPath = $m } + } +} + +$fileName = if ($binPath) { [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath))) } else { $PackagePath } + +if (-not $binPath) { + Write-Host "[ERROR] Не найден Ext/Package.bin для пути: $PackagePath" + exit 1 +} + +# Каталог конфигурации — для проверок регистрации и зависимостей +if (-not $ConfigDir) { + $d = [System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($binPath))) + # .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень + $ConfigDir = [System.IO.Path]::GetDirectoryName($d) +} + +$finalize = { + $checks = $script:okCount + $script:errors + $script:warnings + if ($script:errors -eq 0 -and $script:warnings -eq 0 -and -not $Detailed) { + $result = "=== Validation OK: $fileName ($checks checks) ===" + } else { + Out-Line "" + Out-Line "=== Result: $($script:errors) errors, $($script:warnings) warnings ($checks checks) ===" + $result = $script:output.ToString() + } + Write-Host $result + if ($OutFile) { + $utf8Bom = New-Object System.Text.UTF8Encoding $true + [System.IO.File]::WriteAllText($OutFile, $script:output.ToString(), $utf8Bom) + } +} + +# --- 1. Well-formedness --- + +$doc = New-Object System.Xml.XmlDocument +try { + $doc.Load($binPath) +} catch { + Report-Error "Package.bin не является корректным XML: $($_.Exception.Message)" + & $finalize + exit 1 +} +$pkg = $doc.DocumentElement +if ($pkg.get_LocalName() -ne "package") { + Report-Error "Ожидался корневой , найден <$($pkg.get_LocalName())>" + & $finalize + exit 1 +} +Report-OK "Package.bin: корректный XML, корень " + +$targetNs = $pkg.GetAttribute("targetNamespace") +if (-not $targetNs) { + Report-Error "У не задан targetNamespace" +} else { + Report-OK "targetNamespace: $targetNs" +} + +# --- 2. Encoding / EOL --- + +$bytes = [System.IO.File]::ReadAllBytes($binPath) +if (-not ($bytes.Length -ge 3 -and $bytes[0] -eq 0xEF -and $bytes[1] -eq 0xBB -and $bytes[2] -eq 0xBF)) { + Report-Warn "Package.bin без BOM UTF-8 — платформа пишет файл с BOM" +} else { + Report-OK "Кодировка: UTF-8 с BOM" +} + +# --- 3. Collect declarations --- + +$imports = New-Object System.Collections.ArrayList +$localTypes = New-Object System.Collections.Generic.HashSet[string] +$globalProps = New-Object System.Collections.Generic.HashSet[string] + +foreach ($n in $pkg.ChildNodes) { + if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + 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")) } + "property" { if ($n.HasAttribute("name")) { [void]$globalProps.Add($n.GetAttribute("name")) } } + } +} + +# --- 4. Duplicate type names --- + +$seen = @{} +foreach ($n in $pkg.ChildNodes) { + if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + if ($n.get_LocalName() -ne "objectType" -and $n.get_LocalName() -ne "valueType") { continue } + $nm = $n.GetAttribute("name") + if (-not $nm) { Report-Error "<$($n.get_LocalName())> без атрибута name"; continue } + if ($seen.ContainsKey($nm)) { + Report-Error "Дублирующееся имя типа: $nm" + } else { + $seen[$nm] = $true + } +} +if ($localTypes.Count -gt 0) { Report-OK "$($localTypes.Count) тип(ов), имена уникальны" } + +# --- 5. Type references resolve --- + +$usedNamespaces = New-Object System.Collections.Generic.HashSet[string] +$anyTypeProps = New-Object System.Collections.ArrayList +$refAttrs = @("type", "base", "ref", "itemType") + +function Resolve-Ref([System.Xml.XmlElement]$el, [string]$attr, [string]$raw) { + if (-not $raw) { return } + # Нотация Кларка {ns}local + if ($raw.StartsWith("{")) { + $close = $raw.IndexOf("}") + if ($close -lt 0) { Report-Error "Некорректная нотация Кларка в $attr=`"$raw`""; return } + $ns = $raw.Substring(1, $close - 1) + $local = $raw.Substring($close + 1) + } else { + $parts = $raw.Split(":") + if ($parts.Count -eq 2) { + $ns = $el.GetNamespaceOfPrefix($parts[0]) + $local = $parts[1] + if (-not $ns) { + Report-Error "Префикс `"$($parts[0])`" не объявлен: $attr=`"$raw`" (тип $($el.get_LocalName()))" + return + } + } else { + $ns = $null + $local = $parts[0] + } + } + + if ($ns -eq $XS_NS -or $ns -eq $XSI_NS) { + if ($local -eq "anyType") { [void]$anyTypeProps.Add($el) } + return + } + if ($ns -eq $targetNs) { + if ($attr -eq "ref") { + if (-not $globalProps.Contains($local)) { + Report-Error "ref=`"$raw`" не разрешается: в пакете нет глобального свойства `"$local`"" + } + } elseif (-not $localTypes.Contains($local)) { + Report-Error "$attr=`"$raw`" не разрешается: в пакете нет типа `"$local`"" + } + return + } + if ($ns) { + [void]$usedNamespaces.Add($ns) + if (-not $imports.Contains($ns)) { + Report-Error "$attr=`"$raw`" ссылается на `"$ns`", но не объявлен" + } + } +} + +$nodeCount = 0 +foreach ($el in $pkg.SelectNodes("//*")) { + if ($el.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue } + $nodeCount++ + foreach ($a in $refAttrs) { + if ($el.HasAttribute($a)) { Resolve-Ref $el $a $el.GetAttribute($a) } + } + if ($el.HasAttribute("memberTypes")) { + foreach ($m in ($el.GetAttribute("memberTypes") -split "\s+")) { + if ($m) { Resolve-Ref $el "memberTypes" $m } + } + } + if ($script:stopped) { break } +} +if (-not $script:stopped) { Report-OK "$nodeCount узлов: ссылки на типы разрешаются" } + +if ($script:stopped) { & $finalize; exit 1 } + +# --- 6. Silent degradation to xs:anyType --- +# Платформа при импорте XML-схемы молча подменяет неразрешённый чужой тип на anyType. + +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 без ошибки" +} + +# --- 7. Unused imports --- + +foreach ($imp in $imports) { + if (-not $usedNamespaces.Contains($imp)) { + Report-Warn " объявлен, но ни один тип из этого пространства имён не используется" + } +} +if ($imports.Count -gt 0 -and $script:warnings -eq 0) { Report-OK "$($imports.Count) импорт(ов) — все используются" } + +# --- 8. nillable on attribute-form properties --- + +$nillAttrs = @() +foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) { + if ($p.GetAttribute("form") -eq "Attribute" -and $p.GetAttribute("nillable") -eq "true") { + $nillAttrs += $p.GetAttribute("name") + } +} +if ($nillAttrs.Count -gt 0) { + Report-Warn "Свойств с nillable=`"true`" и form=`"Attribute`": $($nillAttrs.Count) ($(($nillAttrs | Select-Object -First 5) -join ', ')). Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет" +} + +# --- 9. Facet consistency --- + +$FACET_NUM = @("totalDigits", "fractionDigits") +$facetChecked = 0 +foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef']")) { + if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -eq "ObjectType") { continue } + $facetChecked++ + $nm = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" } + + $len = $t.GetAttribute("length") + if ($len -and ($t.HasAttribute("minLength") -or $t.HasAttribute("maxLength"))) { + Report-Error "$nm : length несовместим с minLength/maxLength" + } + $minL = $t.GetAttribute("minLength"); $maxL = $t.GetAttribute("maxLength") + if ($minL -and $maxL -and ([int]$minL -gt [int]$maxL)) { + Report-Error "$nm : minLength ($minL) больше maxLength ($maxL)" + } + $td = $t.GetAttribute("totalDigits"); $fd = $t.GetAttribute("fractionDigits") + if ($td -and $fd -and ([int]$fd -gt [int]$td)) { + Report-Error "$nm : fractionDigits ($fd) больше totalDigits ($td)" + } + $ws = $t.GetAttribute("whiteSpace") + if ($ws -and @("preserve", "replace", "collapse") -notcontains $ws) { + Report-Error "$nm : недопустимое whiteSpace=`"$ws`"" + } + $var = $t.GetAttribute("variety") + if ($var -and @("Atomic", "List", "Union") -notcontains $var) { + Report-Error "$nm : недопустимое variety=`"$var`"" + } + if ($var -eq "List" -and -not $t.HasAttribute("itemType")) { + Report-Warn "$nm : variety=`"List`" без itemType" + } + if ($script:stopped) { break } +} +if ($facetChecked -gt 0 -and -not $script:stopped) { Report-OK "$facetChecked простых тип(ов): фасеты согласованы" } + +if ($script:stopped) { & $finalize; exit 1 } + +# --- 10. property form / bounds --- + +foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) { + $form = $p.GetAttribute("form") + if ($form -and @("Element", "Attribute", "Text") -notcontains $form) { + Report-Error "Свойство `"$($p.GetAttribute('name'))`": недопустимое form=`"$form`"" + } + $ub = $p.GetAttribute("upperBound") + if ($ub -and $ub -ne "-1" -and ([int]$ub -lt 1)) { + Report-Error "Свойство `"$($p.GetAttribute('name'))`": upperBound=`"$ub`" (допустимы -1 или число ≥ 1)" + } + $lb = $p.GetAttribute("lowerBound") + if ($lb -and $ub -and $ub -ne "-1" -and ([int]$lb -gt [int]$ub)) { + Report-Error "Свойство `"$($p.GetAttribute('name'))`": lowerBound ($lb) больше upperBound ($ub)" + } + if (-not $p.HasAttribute("name") -and -not $p.HasAttribute("ref")) { + Report-Error "Свойство без name и без ref" + } + if ($script:stopped) { break } +} +if (-not $script:stopped) { Report-OK "Свойства: form и кратности корректны" } + +# --- 11. Metadata object --- + +if ($mdPath) { + $md = New-Object System.Xml.XmlDocument + $md.Load($mdPath) + $nsm = New-Object System.Xml.XmlNamespaceManager($md.NameTable) + $nsm.AddNamespace("md", $MD_NS) + $mdName = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Name", $nsm) + $mdNs = $md.SelectSingleNode("//md:XDTOPackage/md:Properties/md:Namespace", $nsm) + if (-not $mdName) { + Report-Error "В объекте метаданных не задано " + } elseif ($mdName.InnerText -ne $fileName) { + Report-Error "$($mdName.InnerText) не совпадает с именем каталога `"$fileName`"" + } else { + Report-OK "Объект метаданных: Name = $($mdName.InnerText)" + } + if ($mdNs -and $mdNs.InnerText -ne $targetNs) { + Report-Error "$($mdNs.InnerText) не совпадает с targetNamespace пакета ($targetNs)" + } elseif ($mdNs) { + Report-OK "Namespace объекта метаданных совпадает с targetNamespace" + } +} else { + Report-Warn "Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета" +} + +# --- 12. Registration in Configuration.xml + namespace uniqueness --- + +$configXml = Join-Path $ConfigDir "Configuration.xml" +if (Test-Path $configXml) { + $cfg = New-Object System.Xml.XmlDocument + $cfg.Load($configXml) + $nsm2 = New-Object System.Xml.XmlNamespaceManager($cfg.NameTable) + $nsm2.AddNamespace("md", $MD_NS) + $registered = $false + foreach ($e in $cfg.SelectNodes("//md:Configuration/md:ChildObjects/md:XDTOPackage", $nsm2)) { + if ($e.InnerText -eq $fileName) { $registered = $true; break } + } + if ($registered) { + Report-OK "Зарегистрирован в Configuration.xml" + } else { + Report-Error "$fileName отсутствует в ChildObjects файла Configuration.xml — платформа пакет не увидит" + } + + # Уникальность targetNamespace среди пакетов конфигурации + $pkgRoot = Join-Path $ConfigDir "XDTOPackages" + if (Test-Path $pkgRoot) { + $clash = @() + foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) { + if ($other.Name -eq $fileName) { continue } + $ob = Join-Path (Join-Path $other.FullName "Ext") "Package.bin" + if (-not (Test-Path $ob)) { continue } + try { + $od = New-Object System.Xml.XmlDocument + $od.Load($ob) + if ($od.DocumentElement.GetAttribute("targetNamespace") -eq $targetNs) { $clash += $other.Name } + } catch {} + } + if ($clash.Count -gt 0) { + Report-Error "targetNamespace `"$targetNs`" уже занят пакет(ами): $($clash -join ', '). Платформа не допускает два пакета с одним пространством имён" + } else { + Report-OK "targetNamespace уникален в конфигурации" + } + } +} else { + Report-Warn "Configuration.xml не найден ($ConfigDir) — проверки регистрации и уникальности namespace пропущены" +} + +# --- Final --- + +& $finalize +if ($script:errors -gt 0) { exit 1 } +exit 0 diff --git a/.claude/skills/xdto-validate/scripts/xdto-validate.py b/.claude/skills/xdto-validate/scripts/xdto-validate.py new file mode 100644 index 00000000..74030839 --- /dev/null +++ b/.claude/skills/xdto-validate/scripts/xdto-validate.py @@ -0,0 +1,386 @@ +# xdto-validate v1.0 — Validate a 1C XDTO package (Python port) +# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills +import argparse +import os +import sys + +from lxml import etree + +sys.stdout.reconfigure(encoding="utf-8") +sys.stderr.reconfigure(encoding="utf-8") + +XS_NS = "http://www.w3.org/2001/XMLSchema" +XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" +MD_NS = "http://v8.1c.ru/8.3/MDClasses" + +parser = argparse.ArgumentParser(allow_abbrev=False) +parser.add_argument("-PackagePath", "-Path", required=True) +parser.add_argument("-ConfigDir", default="") +parser.add_argument("-Detailed", action="store_true") +parser.add_argument("-MaxErrors", type=int, default=20) +parser.add_argument("-OutFile", default="") +args = parser.parse_args() + +package_path = os.path.abspath(args.PackagePath) +detailed = args.Detailed +max_errors = args.MaxErrors +out_file = args.OutFile + +# ── reporting ──────────────────────────────────────────────── + +state = {"errors": 0, "warnings": 0, "ok": 0, "stopped": False} +output = [] + + +def out_line(s): + output.append(s) + + +def report_ok(msg): + state["ok"] += 1 + if detailed: + out_line(f"[OK] {msg}") + + +def report_error(msg): + state["errors"] += 1 + out_line(f"[ERROR] {msg}") + if state["errors"] >= max_errors: + state["stopped"] = True + + +def report_warn(msg): + state["warnings"] += 1 + out_line(f"[WARN] {msg}") + + +# ── resolve paths ──────────────────────────────────────────── + +bin_path = None +md_path = None + +if os.path.isfile(package_path): + if os.path.basename(package_path) == "Package.bin": + bin_path = package_path + pkg_dir = os.path.dirname(os.path.dirname(package_path)) + if os.path.exists(pkg_dir + ".xml"): + md_path = pkg_dir + ".xml" + elif package_path.endswith(".xml"): + md_path = package_path + stem = os.path.join(os.path.dirname(package_path), + os.path.splitext(os.path.basename(package_path))[0]) + c = os.path.join(stem, "Ext", "Package.bin") + if os.path.exists(c): + bin_path = c +elif os.path.isdir(package_path): + c = os.path.join(package_path, "Ext", "Package.bin") + if os.path.exists(c): + bin_path = c + m = package_path.rstrip("\\/") + ".xml" + if os.path.exists(m): + md_path = m + +if not bin_path: + print(f"[ERROR] Не найден Ext/Package.bin для пути: {package_path}") + sys.exit(1) + +file_name = os.path.basename(os.path.dirname(os.path.dirname(bin_path))) + +config_dir = args.ConfigDir +if not config_dir: + # .../XDTOPackages/<Имя>/Ext/Package.bin -> .../XDTOPackages -> корень + config_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(bin_path)))) + + +def finalize(): + checks = state["ok"] + state["errors"] + state["warnings"] + if state["errors"] == 0 and state["warnings"] == 0 and not detailed: + result = f"=== Validation OK: {file_name} ({checks} checks) ===" + else: + out_line("") + out_line(f"=== Result: {state['errors']} errors, {state['warnings']} warnings ({checks} checks) ===") + result = "\n".join(output) + print(result) + if out_file: + with open(out_file, "w", encoding="utf-8-sig", newline="") as f: + f.write("\n".join(output)) + + +def local(el): + return etree.QName(el).localname + + +# ── 1. well-formedness ─────────────────────────────────────── + +try: + doc = etree.parse(bin_path) +except Exception as e: # noqa: BLE001 + report_error(f"Package.bin не является корректным XML: {e}") + finalize() + sys.exit(1) + +pkg = doc.getroot() +if local(pkg) != "package": + report_error(f"Ожидался корневой , найден <{local(pkg)}>") + finalize() + sys.exit(1) +report_ok("Package.bin: корректный XML, корень ") + +target_ns = pkg.get("targetNamespace") +if not target_ns: + report_error("У не задан targetNamespace") +else: + report_ok(f"targetNamespace: {target_ns}") + +# ── 2. encoding ────────────────────────────────────────────── + +with open(bin_path, "rb") as f: + head = f.read(3) +if head != b"\xef\xbb\xbf": + report_warn("Package.bin без BOM UTF-8 — платформа пишет файл с BOM") +else: + report_ok("Кодировка: UTF-8 с BOM") + +# ── 3. declarations ────────────────────────────────────────── + +imports = [] +local_types = set() +global_props = set() + +for n in pkg: + if not isinstance(n.tag, str): + continue + ln = local(n) + if ln == "import": + imports.append(n.get("namespace")) + elif ln in ("objectType", "valueType"): + local_types.add(n.get("name")) + elif ln == "property" and n.get("name"): + global_props.add(n.get("name")) + +# ── 4. duplicate type names ────────────────────────────────── + +seen = set() +for n in pkg: + if not isinstance(n.tag, str) or local(n) not in ("objectType", "valueType"): + continue + nm = n.get("name") + if not nm: + report_error(f"<{local(n)}> без атрибута name") + continue + if nm in seen: + report_error(f"Дублирующееся имя типа: {nm}") + else: + seen.add(nm) +if local_types: + report_ok(f"{len(local_types)} тип(ов), имена уникальны") + +# ── 5. type references resolve ─────────────────────────────── + +used_namespaces = set() +any_type_props = [] +REF_ATTRS = ("type", "base", "ref", "itemType") + + +def resolve_ref(el, attr, raw): + if not raw: + return + if raw.startswith("{"): + close = raw.find("}") + if close < 0: + report_error(f'Некорректная нотация Кларка в {attr}="{raw}"') + return + ns = raw[1:close] + loc = raw[close + 1:] + else: + parts = raw.split(":") + if len(parts) == 2: + ns = el.nsmap.get(parts[0]) + loc = parts[1] + if not ns: + report_error(f'Префикс "{parts[0]}" не объявлен: {attr}="{raw}" (тип {local(el)})') + return + else: + ns = None + loc = parts[0] + + if ns in (XS_NS, XSI_NS): + if loc == "anyType": + any_type_props.append(el) + return + if ns == target_ns: + if attr == "ref": + if loc not in global_props: + report_error(f'ref="{raw}" не разрешается: в пакете нет глобального свойства "{loc}"') + elif loc not in local_types: + report_error(f'{attr}="{raw}" не разрешается: в пакете нет типа "{loc}"') + return + if ns: + used_namespaces.add(ns) + if ns not in imports: + report_error(f'{attr}="{raw}" ссылается на "{ns}", но не объявлен') + + +node_count = 0 +for el in pkg.iter(): + if not isinstance(el.tag, str): + continue + node_count += 1 + for a in REF_ATTRS: + if el.get(a) is not None: + resolve_ref(el, a, el.get(a)) + if el.get("memberTypes"): + for m in el.get("memberTypes").split(): + resolve_ref(el, "memberTypes", m) + if state["stopped"]: + break +if not state["stopped"]: + report_ok(f"{node_count} узлов: ссылки на типы разрешаются") + +if state["stopped"]: + finalize() + sys.exit(1) + +# ── 6. silent degradation to xs:anyType ────────────────────── + +if any_type_props and imports: + names = [p.get("name") for p in any_type_props if p.get("name")] + shown = ", ".join(names[:5]) + report_warn( + f'Свойств с type="xs:anyType": {len(any_type_props)} при объявленных импортах ({shown}). ' + "Возможна тихая деградация: при импорте XML-схемы платформа заменяет неразрешённый " + "чужой тип на anyType без ошибки" + ) + +# ── 7. unused imports ──────────────────────────────────────── + +for imp in imports: + if imp not in used_namespaces: + report_warn(f' объявлен, но ни один тип из этого пространства имён не используется') +if imports and state["warnings"] == 0: + report_ok(f"{len(imports)} импорт(ов) — все используются") + +# ── 8. nillable on attribute-form properties ───────────────── + +nill_attrs = [p.get("name") for p in pkg.iter() + if isinstance(p.tag, str) and local(p) == "property" + and p.get("form") == "Attribute" and p.get("nillable") == "true"] +if nill_attrs: + report_warn( + f'Свойств с nillable="true" и form="Attribute": {len(nill_attrs)} ({", ".join(nill_attrs[:5])}). ' + "Спецификация XSD не допускает nillable у атрибутов — экспорт XML-схемы в Конфигураторе их потеряет" + ) + +# ── 9. facet consistency ───────────────────────────────────── + +facet_checked = 0 +for t in pkg.iter(): + if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"): + continue + if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") == "ObjectType": + continue + facet_checked += 1 + nm = t.get("name") or "(анонимный тип)" + + if t.get("length") and (t.get("minLength") or t.get("maxLength")): + report_error(f"{nm} : length несовместим с minLength/maxLength") + 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})") + td, fd = t.get("totalDigits"), t.get("fractionDigits") + if td and fd and int(fd) > int(td): + report_error(f"{nm} : fractionDigits ({fd}) больше totalDigits ({td})") + ws = t.get("whiteSpace") + if ws and ws not in ("preserve", "replace", "collapse"): + report_error(f'{nm} : недопустимое whiteSpace="{ws}"') + var = t.get("variety") + if var and var not in ("Atomic", "List", "Union"): + report_error(f'{nm} : недопустимое variety="{var}"') + if var == "List" and t.get("itemType") is None: + report_warn(f'{nm} : variety="List" без itemType') + if state["stopped"]: + break +if facet_checked and not state["stopped"]: + report_ok(f"{facet_checked} простых тип(ов): фасеты согласованы") + +if state["stopped"]: + finalize() + sys.exit(1) + +# ── 10. property form / bounds ─────────────────────────────── + +for p in pkg.iter(): + if not isinstance(p.tag, str) or local(p) != "property": + continue + form = p.get("form") + if form and form not in ("Element", "Attribute", "Text"): + report_error(f'Свойство "{p.get("name")}": недопустимое form="{form}"') + ub = p.get("upperBound") + if ub and ub != "-1" and int(ub) < 1: + report_error(f'Свойство "{p.get("name")}": upperBound="{ub}" (допустимы -1 или число ≥ 1)') + lb = p.get("lowerBound") + if lb and ub and ub != "-1" and int(lb) > int(ub): + report_error(f'Свойство "{p.get("name")}": lowerBound ({lb}) больше upperBound ({ub})') + if p.get("name") is None and p.get("ref") is None: + report_error("Свойство без name и без ref") + if state["stopped"]: + break +if not state["stopped"]: + report_ok("Свойства: form и кратности корректны") + +# ── 11. metadata object ────────────────────────────────────── + +if md_path: + md = etree.parse(md_path) + md_name = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Name") + md_ns = md.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties/{{{MD_NS}}}Namespace") + if md_name is None: + report_error("В объекте метаданных не задано ") + elif (md_name.text or "") != file_name: + report_error(f'{md_name.text} не совпадает с именем каталога "{file_name}"') + else: + report_ok(f"Объект метаданных: Name = {md_name.text}") + if md_ns is not None and (md_ns.text or "") != target_ns: + report_error(f"{md_ns.text} не совпадает с targetNamespace пакета ({target_ns})") + elif md_ns is not None: + report_ok("Namespace объекта метаданных совпадает с targetNamespace") +else: + report_warn("Файл объекта метаданных <Имя>.xml не найден рядом с каталогом пакета") + +# ── 12. registration + namespace uniqueness ────────────────── + +config_xml = os.path.join(config_dir, "Configuration.xml") +if os.path.exists(config_xml): + cfg = etree.parse(config_xml) + registered = any((e.text or "") == file_name + for e in cfg.iterfind(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects/{{{MD_NS}}}XDTOPackage")) + if registered: + report_ok("Зарегистрирован в Configuration.xml") + else: + report_error(f"{file_name} отсутствует в ChildObjects файла " + "Configuration.xml — платформа пакет не увидит") + + pkg_root = os.path.join(config_dir, "XDTOPackages") + if os.path.isdir(pkg_root): + clash = [] + for other in sorted(os.listdir(pkg_root)): + if other == file_name: + continue + ob = os.path.join(pkg_root, other, "Ext", "Package.bin") + if not os.path.exists(ob): + continue + try: + if etree.parse(ob).getroot().get("targetNamespace") == target_ns: + clash.append(other) + except Exception: # noqa: BLE001 + pass + if clash: + report_error(f'targetNamespace "{target_ns}" уже занят пакет(ами): {", ".join(clash)}. ' + "Платформа не допускает два пакета с одним пространством имён") + else: + report_ok("targetNamespace уникален в конфигурации") +else: + report_warn(f"Configuration.xml не найден ({config_dir}) — проверки регистрации и уникальности namespace пропущены") + +finalize() +sys.exit(1 if state["errors"] > 0 else 0) diff --git a/.gitattributes b/.gitattributes index cacc4e54..ea34d45c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -21,3 +21,8 @@ # Бинарники 1С *.bin binary + +# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем +# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff, +# иначе изменение модели пакета в истории выглядит как «Binary files differ». +XDTOPackages/**/Package.bin diff diff --git a/docs/1c-specs-index.md b/docs/1c-specs-index.md index 83deaa3c..28164e58 100644 --- a/docs/1c-specs-index.md +++ b/docs/1c-specs-index.md @@ -49,7 +49,7 @@ | XML-элемент | Каталог | Русское название | Спецификация | |-------------|---------|-----------------|--------------| | `ExchangePlan` | `ExchangePlans/` | Планы обмена | [1c-config-objects-spec.md § 15](1c-config-objects-spec.md#15-планы-обмена-exchangeplans) | -| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-configuration-spec.md § 6.14](1c-configuration-spec.md#614-xdtopackage--xdto-пакет) | +| `XDTOPackage` | `XDTOPackages/` | XDTO-пакеты | [1c-xdto-spec.md](1c-xdto-spec.md) | | `WebService` | `WebServices/` | Веб-сервисы | [1c-config-objects-spec.md § 25](1c-config-objects-spec.md#25-веб-сервисы-webservices) | | `HTTPService` | `HTTPServices/` | HTTP-сервисы | [1c-config-objects-spec.md § 24](1c-config-objects-spec.md#24-http-сервисы-httpservices) | | `WSReference` | `WSReferences/` | WS-ссылки | [1c-configuration-spec.md § 6.15](1c-configuration-spec.md#615-wsreference--ws-ссылка) | diff --git a/docs/1c-xdto-spec.md b/docs/1c-xdto-spec.md new file mode 100644 index 00000000..d357608a --- /dev/null +++ b/docs/1c-xdto-spec.md @@ -0,0 +1,329 @@ +# Спецификация формата XML пакетов XDTO 1С + +Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы). +Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.20` (платформа 8.3.27+). + +Источники: выгрузки Бухгалтерия предприятия (8.3.24), ERP 2 (8.3.24) — 760 пакетов. + +> **Связанные спецификации:** +> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md) +> - DSL навыков (XSD как формат описания) — [xdto-dsl-spec.md](xdto-dsl-spec.md) +> - Сводный индекс — [1c-specs-index.md](1c-specs-index.md) + +--- + +## 1. Структура каталогов + +``` +XDTOPackages/ +├── ОбменСБанком.xml # Объект метаданных — 4 свойства, и всё +├── ОбменСБанком/ +│ └── Ext/ +│ └── Package.bin # Модель пакета +└── ... +``` + +Регистрация в корневом `Configuration.xml`: + +```xml + + ... + ОбменСБанком + +``` + +Ни форм, ни модулей, ни макетов у пакета XDTO нет. + +--- + +## 2. Объект метаданных `<Имя>.xml` + +```xml + + + + + ApdexExport + + + ru + Apdex export + + + + www.v8.1c.ru/ssl/performace-assessment/apdexExport + + + +``` + +| Свойство | Описание | +|---|---| +| `Name` | Имя объекта метаданных. Должно быть валидным идентификатором 1С | +| `Synonym` | Многоязычное представление | +| `Comment` | Комментарий | +| `Namespace` | URI целевого пространства имён. Дублирует `targetNamespace` в `Package.bin` | + +Других свойств у пакета XDTO нет. + +--- + +## 3. `Ext/Package.bin` — модель пакета + +Несмотря на расширение `.bin`, это **текстовый XML**: UTF-8 с BOM, перевод строки CRLF, +отступ — символы табуляции. + +```xml + + + + + + + + + + +``` + +### 3.1. Вложенность элементов + +``` +package > objectType | valueType | property | import +objectType > property +valueType > enumeration | pattern | typeDef +property > typeDef +typeDef > property | enumeration | pattern +``` + +Всего восемь имён элементов. `typeDef` — анонимный (встроенный) тип; его разновидность +задаёт `xsi:type` = `ValueType` либо `ObjectType`. + +**Порядок элементов верхнего уровня обязателен:** + +``` +import* → property* → valueType* → objectType* +``` + +Ему удовлетворяют все 760 пакетов корпуса. Нарушение порядка платформа не прощает — +`db-update` падает с «Ошибка преобразования данных XDTO: Чтение объекта типа +`{http://v8.1c.ru/8.1/xdto}Package`… Проверка свойства 'property'». Это существенно +при сборке из XML-схемы, где порядок объявлений верхнего уровня произвольный. + +### 3.2. `` + +| Атрибут | Значения | Описание | +|---|---|---| +| `targetNamespace` | URI | Целевое пространство имён | +| `elementFormQualified` | `true` \| `false` | Умолчание при отсутствии — `true` | +| `attributeFormQualified` | `true` \| `false` | Умолчание при отсутствии — `false` | + +Порядок атрибутов: `targetNamespace`, `elementFormQualified`, `attributeFormQualified`. + +### 3.3. `` + +`` — объявление зависимости от другого пакета. Разрешается +по namespace среди пакетов конфигурации; `schemaLocation` в модели XDTO нет. + +### 3.4. `` и `` + +| Атрибут | Значения | Описание | +|---|---|---| +| `name` | идентификатор | Только у именованного `objectType` | +| `base` | QName | Базовый тип (наследование) | +| `open` | `true` \| `false` | Допускает произвольные элементы и атрибуты | +| `abstract` | `true` \| `false` | Абстрактный тип | +| `mixed` | `true` \| `false` | Смешанное содержимое | +| `ordered` | `true` \| `false` | `false` — выбор одного из вариантов (аналог `xs:choice`) | +| `sequenced` | `true` | Последовательное содержимое | + +Порядок атрибутов `objectType`: `name`, `base`, `open`, `abstract`, `mixed`, `ordered`, `sequenced`. +Порядок атрибутов `typeDef`: `xsi:type`, `base`, `mixed`, `open`, `ordered`, `sequenced`, далее атрибуты простого типа. + +### 3.5. `` и `` + +| Атрибут | Значения | +|---|---| +| `name` | идентификатор (только у именованного `valueType`) | +| `base` | QName базового типа | +| `variety` | `Atomic` \| `List` \| `Union` | +| `itemType` | QName — тип элемента списка (`variety="List"`) | +| `memberTypes` | список типов объединения (`variety="Union"`) | + +Фасеты задаются **атрибутами**, а не дочерними элементами: +`length`, `minLength`, `maxLength`, `totalDigits`, `fractionDigits`, +`minInclusive`, `maxInclusive`, `minExclusive`, `maxExclusive`, +`whiteSpace` (`preserve` \| `collapse`). + +Базовый тип может задаваться не атрибутом `base`, а вложенным анонимным `typeDef` +**без** `xsi:type` — соответствует анонимному `xs:simpleType` внутри `xs:restriction`: + +```xml + + + [0-9]{12} + +``` + +Дочерними элементами идут только `` и `` — значение в тексте узла: + +```xml + + [0-9]{20} + +``` + +У `` встречается атрибут `xsi:type` (например `xs:string`), указывающий тип литерала. + +### 3.6. `` + +| Атрибут | Значения | Описание | +|---|---|---| +| `name` | идентификатор | Имя свойства | +| `ref` | QName | Ссылка на глобальное свойство вместо собственного объявления | +| `type` | QName | Тип свойства. При отсутствии и `type`, и вложенного `typeDef` — произвольный тип | +| `lowerBound` | `0` \| `1` | Минимальная кратность. Умолчание при отсутствии — `1` | +| `upperBound` | число \| `-1` | Максимальная кратность; `-1` — неограниченно. Умолчание — `1` | +| `nillable` | `true` \| `false` | Допускает `xsi:nil` | +| `fixed` | значение | Фиксированное значение | +| `default` | значение | Значение по умолчанию | +| `form` | `Element` \| `Attribute` \| `Text` | Форма представления в XML. Умолчание — `Element` | +| `localName` | строка | Исходное XML-имя, если оно не является валидным идентификатором 1С | +| `qualified` | `true` \| `false` | Переопределение `*FormQualified` для конкретного свойства | + +Порядок атрибутов: `name`, `ref`, `type`, `lowerBound`, `upperBound`, `nillable`, +`fixed`, `default`, `form`, `localName`, `qualified`. + +**`form="Text"`** — свойство хранит собственное значение элемента (аналог `xs:simpleContent`). +Такое свойство платформа всегда называет `__content`: + +```xml + + + + +``` + +Наличие `form="Text"` не отменяет флагов самого типа — `mixed`, `sequenced` и прочие +задаются как обычно: + +```xml + + + + +``` + +**`localName`** — при импорте XML-схемы имя, недопустимое как идентификатор 1С, +санируется, а оригинал сохраняется: + +```xml + +``` + +### 3.7. Порядок свойств внутри типа + +Свойства с `form="Attribute"` обычно идут перед остальными — так записаны 96.5% типов +корпуса. Оставшиеся 3.5% содержат произвольное чередование; порядок значим и сохраняется +платформой как есть. + +--- + +## 4. Схема префиксов пространств имён + +Каждая ссылка на тип из пространства имён, отличного от `xs:`/`xsi:`, требует объявления +префикса вида `dNpM`: + +```xml + +``` + +- `N` — глубина узла: `package` = 1, его прямые потомки = 2, свойство внутри `objectType` = 3, + свойство внутри `typeDef` = 5, глубже — 7, 9 и т.д. +- `M` — порядковый номер нового пространства имён, объявляемого на этом узле (с 1). + +Префикс объявляется **на первом узле, которому он нужен**; потомки переиспользуют его из +области видимости, а не объявляют заново: + +```xml + + + +``` + +Целевое пространство имён самого пакета исключением не является — ссылка на собственный +тип тоже требует локального объявления. + +Частоты по корпусу: `d3p1` — 109 738, `d2p1` — 13 108, `d5p1` — 9 581, далее по убыванию до `d16p1`. + +### 4.1. Нотация Кларка в `memberTypes` + +Значения `memberTypes` записываются нотацией Кларка `{URI}Локальное`, без префикса +(125 случаев из 135 в корпусе; остальные — обычные префиксные QName): + +```xml + +``` + +Обратите внимание: объявление `xmlns:d2p1` присутствует, хотя в значении не используется — +пространство имён всё равно проходит через общий механизм выделения префиксов. Если оно уже +объявлено предком, нового объявления не появляется. + +Остальные атрибуты-QName (`type`, `base`, `ref`, `itemType`) всегда префиксные. + +### 4.2. Осмысленные префиксы + +Изредка вместо сгенерированного `dNpM` встречается содержательный префикс — например +`dcsset` для настроек компоновки данных: + +```xml + +``` + +### 4.3. Свойство с `qualified` сериализуется с префиксом + +Если у свойства задан `qualified`, платформа пишет и сам тег, и имя атрибута с явным +префиксом пространства имён модели XDTO (хотя по умолчанию оно и так объявлено как `xmlns=`): + +```xml + +``` + +Префикс для XDTO выделяется последним, после префиксов типов. Такое встречается редко +(16 узлов на весь корпус) и появляется при импорте XML-схемы, где `form` задан на +объявлении в unqualified-пакете. + +--- + +## 5. Умолчания записываются непоследовательно + +Один и тот же смысл в корпусе встречается в двух написаниях: `nillable="false"` присутствует +явно 6 899 раз, `objectType open="false"` — 6 раз, `abstract="false"` — 2 раза, явное +`form="Element"` — 44 раза. Пакеты, полученные импортом XML-схемы, и пакеты, созданные +руками в Конфигураторе, отличаются стилем. + +Практическое следствие: приведение пакета к «каноническому» виду меняет байты реальных +файлов. Инструменты, которым важен точный round-trip, обязаны сохранять literal-форму. + +--- + +## 6. Тихая деградация в `xs:anyType` + +При импорте XML-схемы через Конфигуратор тип из чужого пространства имён, для которого +в конфигурации нет соответствующего пакета, **молча** заменяется на `xs:anyType` — +без ошибки и без предупреждения: + +```xml + + +``` + +При этом `` в пакете остаётся. Признак «объявлен импорт, но +ни один тип из этого пространства имён не используется» — надёжный индикатор проблемы. diff --git a/docs/xdto-dsl-spec.md b/docs/xdto-dsl-spec.md new file mode 100644 index 00000000..016dab7a --- /dev/null +++ b/docs/xdto-dsl-spec.md @@ -0,0 +1,173 @@ +# Спецификация XDTO DSL — XML Schema как формат описания пакета + +Навыки `/xdto-compile` (XSD → пакет) и `/xdto-decompile` (пакет → XSD) используют в качестве +формата описания **обычную XML-схему**. Отдельного DSL нет: XSD и модель XDTO выражают одно и +то же, различаясь синтаксисом и умолчаниями, а схема в реальных задачах обычно уже есть — +прислана контрагентом. + +> Формат самих исходников (`Package.bin`, объект метаданных, схема префиксов) — +> [1c-xdto-spec.md](1c-xdto-spec.md). + +## Пример + +```xml + + + + + + + + + + + + + + + + + +``` + +--- + +## 1. Таблица соответствий + +| XML Schema | Модель XDTO | +|---|---| +| `xs:schema/@targetNamespace` | `package/@targetNamespace` | +| `elementFormDefault="qualified"` | `elementFormQualified="true"` | +| `attributeFormDefault="qualified"` | `attributeFormQualified="true"` | +| `xs:import/@namespace` | `import/@namespace` | +| `xs:complexType` | `objectType` | +| `xs:simpleType` | `valueType` | +| `xs:element` (глобальный) | `property` в `package` | +| `xs:attribute` (глобальный) | `property form="Attribute"` в `package` | +| `xs:element` (локальный) | `property` | +| `xs:attribute` (локальный) | `property form="Attribute"` | +| `@minOccurs` | `@lowerBound` | +| `@maxOccurs="unbounded"` | `@upperBound="-1"` | +| `@nillable`, `@default`, `@fixed` | те же имена | +| `xs:element/@ref` | `property/@ref` | +| анонимный `xs:simpleType` в объявлении | `typeDef xsi:type="ValueType"` | +| анонимный `xs:complexType` в объявлении | `typeDef xsi:type="ObjectType"` | +| `xs:complexContent/xs:extension/@base` | `objectType/@base` | +| `@abstract="true"`, `@mixed="true"` | те же имена | +| `xs:choice` | `ordered="false"` | +| `xs:sequence` | порядок по умолчанию | +| `xs:any` + `xs:anyAttribute` | `open="true"` | +| `xs:simpleContent/xs:extension/@base` | свойство `__content` с `form="Text"` | +| `xs:restriction/@base` + дочерние фасеты | `@base` + фасеты **атрибутами** | +| `xs:pattern`, `xs:enumeration` | ``, `` — значение в тексте узла | +| `xs:list/@itemType` | `variety="List"` + `@itemType` | +| `xs:union/@memberTypes` | `variety="Union"` + `@memberTypes` | + +Ловушки, на которых модель ошибается чаще всего: + +- **Кратность инвертирована по смыслу**: `lowerBound="0"` = необязательный, `upperBound="-1"` = неограниченный. +- **Фасеты в XDTO — атрибуты**, а не дочерние элементы: `maxLength="30"`, не ``. +- **Каждая ссылка на тип требует локального объявления префикса** `dNpM` — в том числе на + собственный `targetNamespace`. + +Всё это делает компилятор; писать вручную ничего из перечисленного не нужно. + +--- + +## 2. Аннотации `xdto:` + +Две вещи XDTO выражает, а XML Schema — нет: + +- `nillable` у атрибута (спецификация XSD допускает его только у элементов); +- `qualified` у отдельного свойства. + +Плюс XSD не различает «атрибут записан явно» и «атрибут опущен, действует умолчание», +хотя в реальных пакетах встречаются оба написания одного смысла. + +Такие случаи описываются атрибутами из пространства имён самой модели XDTO — +`http://v8.1c.ru/8.1/xdto`. Правило одно: + +> **Чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем, что в `Package.bin`.** + +```xml + +``` + +Такая схема остаётся валидной: XML Schema разрешает атрибуты из чужих пространств имён +на объявлениях (`anyAttribute namespace="##other"` в схеме схем). Валидаторы их игнорируют. + +Аннотации строго опциональны — подавляющее большинство схем обходится без единой. + +| Аннотация | Где | Назначение | +|---|---|---| +| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута | +| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута | +| `xdto:qualified` | объявление | переопределение `*FormQualified` | +| `xdto:form` | `xs:element` | записать `form` явно (например `form="Element"`) | +| `xdto:name` | объявление | имя свойства, если XML-имя не является идентификатором 1С; тогда XML-имя уходит в `localName` | +| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать `variety` явно | +| `xdto:memberTypesForm="prefixed"` | `xs:union` | писать `memberTypes` префиксами, а не нотацией Кларка | +| `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` | параметры свойства `form="Text"`, если оно названо не `__content` | +| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно | +| `xdto:type` | `xs:enumeration` | `xsi:type` литерала перечисления | +| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого `dNpM` (например `dcsset`) | + +### Что выводится само + +Аннотация нужна только там, где вывод невозможен: + +| Свойство XDTO | Выводится из | +|---|---| +| `open="true"` | наличие `xs:any` / `xs:anyAttribute` | +| `ordered="false"` | `xs:choice` вместо `xs:sequence` | +| `abstract`, `mixed` | одноимённые атрибуты `xs:complexType` | +| `variety="List"` / `"Union"` | `xs:list` / `xs:union` | +| порядок свойств | атрибуты первыми, затем остальные | + +`sequenced` из XSD не выводится (в корпусе он не коррелирует однозначно ни с одной +конструкцией) и всегда приходит аннотацией. + +--- + +## 3. Свойства объекта метаданных + +`Name`, `Synonym` и `Comment` живут в `xs:annotation/xs:appinfo` — штатном месте XML Schema +для инструментальных метаданных. `Namespace` не дублируется: его единственный источник — +`targetNamespace`. + +```xml + + + + ОбменСБанком + Обмен с банком + Формат 1С:Предприятие — Клиент банка + + + +``` + +Блок опционален. Параметры `-Name`, `-Synonym`, `-Comment` навыка `/xdto-compile` +имеют приоритет над ним. Без того и другого имя берётся из имени файла XSD. + +Благодаря этому блоку пара `/xdto-decompile` → `/xdto-compile` замыкается без потерь, +включая свойства объекта метаданных. + +--- + +## 4. Прощающий ввод + +Компилятор принимает и не вполне канонические схемы: + +- **Имя типа без префикса** (`type="Платёж"`) трактуется как тип целевого пространства имён. +- **`form="qualified"` на глобальном объявлении** — так экспортирует XML-схему сам + Конфигуратор, хотя спецификация XSD допускает `form` только у локальных объявлений. + Такие файлы читаются; сам навык пишет корректно. +- **`xs:include`** игнорируется (в модели XDTO соответствия нет). +- Порядок объявлений верхнего уровня произвольный и сохраняется как есть. diff --git a/tests/skills/cases/xdto-compile/_skill.json b/tests/skills/cases/xdto-compile/_skill.json new file mode 100644 index 00000000..3e242609 --- /dev/null +++ b/tests/skills/cases/xdto-compile/_skill.json @@ -0,0 +1,17 @@ +{ + "script": "xdto-compile/scripts/xdto-compile", + "setup": "empty-config", + "args": [ + { "flag": "-XsdPath", "from": "workPath", "field": "xsdFile" }, + { "flag": "-OutputDir", "from": "workDir" } + ], + "snapshot": { + "root": "workDir", + "normalizeUuids": true + }, + "postValidate": { + "script": "xdto-validate/scripts/xdto-validate", + "flag": "-PackagePath", + "pathFrom": "validatePath" + } +} diff --git a/tests/skills/cases/xdto-compile/annotations.json b/tests/skills/cases/xdto-compile/annotations.json new file mode 100644 index 00000000..afac2c6e --- /dev/null +++ b/tests/skills/cases/xdto-compile/annotations.json @@ -0,0 +1,9 @@ +{ + "name": "xdto:-аннотации и свойства метаданного через xs:appinfo", + "caseFiles": ["annotations.xsd"], + "params": { "xsdFile": "annotations.xsd" }, + "validatePath": "XDTOPackages/ПакетСАннотациями", + "expect": { + "files": ["XDTOPackages/ПакетСАннотациями.xml", "XDTOPackages/ПакетСАннотациями/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/annotations.xsd b/tests/skills/cases/xdto-compile/annotations.xsd new file mode 100644 index 00000000..346709ed --- /dev/null +++ b/tests/skills/cases/xdto-compile/annotations.xsd @@ -0,0 +1,28 @@ + + + + + ПакетСАннотациями + Пакет с аннотациями + Annotated package + Проверка xs:appinfo + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/anon-and-content.json b/tests/skills/cases/xdto-compile/anon-and-content.json new file mode 100644 index 00000000..84d647b7 --- /dev/null +++ b/tests/skills/cases/xdto-compile/anon-and-content.json @@ -0,0 +1,9 @@ +{ + "name": "анонимные simple/complex типы и xs:simpleContent", + "caseFiles": ["anon-and-content.xsd"], + "params": { "xsdFile": "anon-and-content.xsd" }, + "validatePath": "XDTOPackages/anon_and_content", + "expect": { + "files": ["XDTOPackages/anon_and_content.xml", "XDTOPackages/anon_and_content/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/anon-and-content.xsd b/tests/skills/cases/xdto-compile/anon-and-content.xsd new file mode 100644 index 00000000..71f80913 --- /dev/null +++ b/tests/skills/cases/xdto-compile/anon-and-content.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/cardinality.json b/tests/skills/cases/xdto-compile/cardinality.json new file mode 100644 index 00000000..a948992e --- /dev/null +++ b/tests/skills/cases/xdto-compile/cardinality.json @@ -0,0 +1,9 @@ +{ + "name": "кратности, nillable, default/fixed, атрибуты, свойство без типа", + "caseFiles": ["cardinality.xsd"], + "params": { "xsdFile": "cardinality.xsd" }, + "validatePath": "XDTOPackages/cardinality", + "expect": { + "files": ["XDTOPackages/cardinality.xml", "XDTOPackages/cardinality/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/cardinality.xsd b/tests/skills/cases/xdto-compile/cardinality.xsd new file mode 100644 index 00000000..fc82497e --- /dev/null +++ b/tests/skills/cases/xdto-compile/cardinality.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/complex-types.json b/tests/skills/cases/xdto-compile/complex-types.json new file mode 100644 index 00000000..c5e5cece --- /dev/null +++ b/tests/skills/cases/xdto-compile/complex-types.json @@ -0,0 +1,9 @@ +{ + "name": "наследование, abstract, choice, open (any+anyAttribute), mixed", + "caseFiles": ["complex-types.xsd"], + "params": { "xsdFile": "complex-types.xsd" }, + "validatePath": "XDTOPackages/complex_types", + "expect": { + "files": ["XDTOPackages/complex_types.xml", "XDTOPackages/complex_types/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/complex-types.xsd b/tests/skills/cases/xdto-compile/complex-types.xsd new file mode 100644 index 00000000..f54435a3 --- /dev/null +++ b/tests/skills/cases/xdto-compile/complex-types.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/minimal.json b/tests/skills/cases/xdto-compile/minimal.json new file mode 100644 index 00000000..81a07ea2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/minimal.json @@ -0,0 +1,9 @@ +{ + "name": "минимальный пакет: один objectType, два свойства", + "caseFiles": ["minimal.xsd"], + "params": { "xsdFile": "minimal.xsd" }, + "validatePath": "XDTOPackages/minimal", + "expect": { + "files": ["XDTOPackages/minimal.xml", "XDTOPackages/minimal/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/minimal.xsd b/tests/skills/cases/xdto-compile/minimal.xsd new file mode 100644 index 00000000..fe77cf12 --- /dev/null +++ b/tests/skills/cases/xdto-compile/minimal.xsd @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/refs.json b/tests/skills/cases/xdto-compile/refs.json new file mode 100644 index 00000000..74c069b5 --- /dev/null +++ b/tests/skills/cases/xdto-compile/refs.json @@ -0,0 +1,9 @@ +{ + "name": "ссылки на типы: свой ns (dNpN), голое имя, чужой ns с импортом, ref", + "caseFiles": ["refs.xsd"], + "params": { "xsdFile": "refs.xsd" }, + "validatePath": "XDTOPackages/refs", + "expect": { + "files": ["XDTOPackages/refs.xml", "XDTOPackages/refs/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/refs.xsd b/tests/skills/cases/xdto-compile/refs.xsd new file mode 100644 index 00000000..a16e72d0 --- /dev/null +++ b/tests/skills/cases/xdto-compile/refs.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/simple-types.json b/tests/skills/cases/xdto-compile/simple-types.json new file mode 100644 index 00000000..cdc26e56 --- /dev/null +++ b/tests/skills/cases/xdto-compile/simple-types.json @@ -0,0 +1,9 @@ +{ + "name": "фасеты, pattern, enumeration, list, union", + "caseFiles": ["simple-types.xsd"], + "params": { "xsdFile": "simple-types.xsd" }, + "validatePath": "XDTOPackages/simple_types", + "expect": { + "files": ["XDTOPackages/simple_types.xml", "XDTOPackages/simple_types/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/simple-types.xsd b/tests/skills/cases/xdto-compile/simple-types.xsd new file mode 100644 index 00000000..71ca76cf --- /dev/null +++ b/tests/skills/cases/xdto-compile/simple-types.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Configuration.xml new file mode 100644 index 00000000..96a8531d --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/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 + + + + Русский + ПакетСАннотациями + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml new file mode 100644 index 00000000..9f7fd64e --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml @@ -0,0 +1,20 @@ + + + + + ПакетСАннотациями + + + ru + Пакет с аннотациями + + + en + Annotated package + + + Проверка xs:appinfo + urn:test:annotations + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin new file mode 100644 index 00000000..3540686c --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd b/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd new file mode 100644 index 00000000..346709ed --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/annotations/annotations.xsd @@ -0,0 +1,28 @@ + + + + + ПакетСАннотациями + Пакет с аннотациями + Annotated package + Проверка xs:appinfo + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Configuration.xml new file mode 100644 index 00000000..2330f34e --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/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 + + + + Русский + anon_and_content + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml new file mode 100644 index 00000000..1a74b7e4 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml @@ -0,0 +1,16 @@ + + + + + anon_and_content + + + ru + anon_and_content + + + + urn:test:anon + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin new file mode 100644 index 00000000..f0f7d6ce --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd new file mode 100644 index 00000000..71f80913 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/anon-and-content/anon-and-content.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Configuration.xml new file mode 100644 index 00000000..190eb88a --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/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 + + + + Русский + cardinality + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml new file mode 100644 index 00000000..e1e93bd2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality.xml @@ -0,0 +1,16 @@ + + + + + cardinality + + + ru + cardinality + + + + urn:test:cardinality + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin new file mode 100644 index 00000000..3cb2e162 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/XDTOPackages/cardinality/Ext/Package.bin @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd b/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd new file mode 100644 index 00000000..fc82497e --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/cardinality/cardinality.xsd @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Configuration.xml new file mode 100644 index 00000000..963a4d32 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/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 + + + + Русский + complex_types + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml new file mode 100644 index 00000000..51808fde --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types.xml @@ -0,0 +1,16 @@ + + + + + complex_types + + + ru + complex_types + + + + urn:test:complex + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin new file mode 100644 index 00000000..d70504cb --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd b/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd new file mode 100644 index 00000000..f54435a3 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/complex-types/complex-types.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Configuration.xml new file mode 100644 index 00000000..f32bf316 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/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 + + + + Русский + minimal + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml new file mode 100644 index 00000000..c603340a --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal.xml @@ -0,0 +1,16 @@ + + + + + minimal + + + ru + minimal + + + + urn:test:minimal + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin new file mode 100644 index 00000000..54b87db8 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd b/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd new file mode 100644 index 00000000..fe77cf12 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/minimal/minimal.xsd @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Configuration.xml new file mode 100644 index 00000000..e37817d2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/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 + + + + Русский + refs + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml new file mode 100644 index 00000000..8dfec905 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs.xml @@ -0,0 +1,16 @@ + + + + + refs + + + ru + refs + + + + urn:test:refs + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin new file mode 100644 index 00000000..cbe9e8eb --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/XDTOPackages/refs/Ext/Package.bin @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd b/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd new file mode 100644 index 00000000..a16e72d0 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/refs/refs.xsd @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Configuration.xml new file mode 100644 index 00000000..931d80c4 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/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 + + + + Русский + simple_types + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml new file mode 100644 index 00000000..220d7da4 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types.xml @@ -0,0 +1,16 @@ + + + + + simple_types + + + ru + simple_types + + + + urn:test:simple + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin new file mode 100644 index 00000000..2de23a8f --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin @@ -0,0 +1,13 @@ + + + [0-9]{20} + + + 1.05 + 1.06 + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd b/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd new file mode 100644 index 00000000..71ca76cf --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/simple-types/simple-types.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Configuration.xml new file mode 100644 index 00000000..bf0d487b --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/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 + + + + Русский + top_level_order + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml new file mode 100644 index 00000000..42d3c27d --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order.xml @@ -0,0 +1,16 @@ + + + + + top_level_order + + + ru + top_level_order + + + + urn:test:order + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin new file mode 100644 index 00000000..2d533b96 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/XDTOPackages/top_level_order/Ext/Package.bin @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd b/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd new file mode 100644 index 00000000..075f1b95 --- /dev/null +++ b/tests/skills/cases/xdto-compile/snapshots/top-level-order/top-level-order.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-compile/top-level-order.json b/tests/skills/cases/xdto-compile/top-level-order.json new file mode 100644 index 00000000..81325940 --- /dev/null +++ b/tests/skills/cases/xdto-compile/top-level-order.json @@ -0,0 +1,9 @@ +{ + "name": "порядок верхнего уровня: import→property→valueType→objectType независимо от порядка в XSD", + "caseFiles": ["top-level-order.xsd"], + "params": { "xsdFile": "top-level-order.xsd" }, + "validatePath": "XDTOPackages/top_level_order", + "expect": { + "files": ["XDTOPackages/top_level_order/Ext/Package.bin"] + } +} diff --git a/tests/skills/cases/xdto-compile/top-level-order.xsd b/tests/skills/cases/xdto-compile/top-level-order.xsd new file mode 100644 index 00000000..075f1b95 --- /dev/null +++ b/tests/skills/cases/xdto-compile/top-level-order.xsd @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/_skill.json b/tests/skills/cases/xdto-decompile/_skill.json new file mode 100644 index 00000000..aee894df --- /dev/null +++ b/tests/skills/cases/xdto-decompile/_skill.json @@ -0,0 +1,12 @@ +{ + "script": "xdto-decompile/scripts/xdto-decompile", + "setup": "empty-config", + "args": [ + { "flag": "-PackagePath", "from": "workPath", "field": "packagePath" }, + { "flag": "-OutFile", "from": "workPath", "field": "outFile" } + ], + "snapshot": { + "root": "workDir", + "normalizeUuids": true + } +} diff --git a/tests/skills/cases/xdto-decompile/annotations.json b/tests/skills/cases/xdto-decompile/annotations.json new file mode 100644 index 00000000..46a4d634 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/annotations.json @@ -0,0 +1,14 @@ +{ + "name": "round-trip: xdto:-аннотации и свойства метаданного восстанавливаются", + "caseFiles": ["annotations.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/annotations.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/ПакетСАннотациями", "outFile": "roundtrip.xsd" }, + "expect": { + "files": ["roundtrip.xsd"] + } +} diff --git a/tests/skills/cases/xdto-decompile/annotations.xsd b/tests/skills/cases/xdto-decompile/annotations.xsd new file mode 100644 index 00000000..346709ed --- /dev/null +++ b/tests/skills/cases/xdto-decompile/annotations.xsd @@ -0,0 +1,28 @@ + + + + + ПакетСАннотациями + Пакет с аннотациями + Annotated package + Проверка xs:appinfo + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/anon-and-content.json b/tests/skills/cases/xdto-decompile/anon-and-content.json new file mode 100644 index 00000000..2f3974ee --- /dev/null +++ b/tests/skills/cases/xdto-decompile/anon-and-content.json @@ -0,0 +1,14 @@ +{ + "name": "round-trip: анонимные типы и simpleContent", + "caseFiles": ["anon-and-content.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/anon-and-content.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/anon_and_content", "outFile": "roundtrip.xsd" }, + "expect": { + "files": ["roundtrip.xsd"] + } +} diff --git a/tests/skills/cases/xdto-decompile/anon-and-content.xsd b/tests/skills/cases/xdto-decompile/anon-and-content.xsd new file mode 100644 index 00000000..71f80913 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/anon-and-content.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/complex-types.json b/tests/skills/cases/xdto-decompile/complex-types.json new file mode 100644 index 00000000..c504729b --- /dev/null +++ b/tests/skills/cases/xdto-decompile/complex-types.json @@ -0,0 +1,14 @@ +{ + "name": "round-trip: наследование, choice, open, mixed", + "caseFiles": ["complex-types.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/complex-types.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/complex_types", "outFile": "roundtrip.xsd" }, + "expect": { + "files": ["roundtrip.xsd"] + } +} diff --git a/tests/skills/cases/xdto-decompile/complex-types.xsd b/tests/skills/cases/xdto-decompile/complex-types.xsd new file mode 100644 index 00000000..f54435a3 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/complex-types.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/minimal.json b/tests/skills/cases/xdto-decompile/minimal.json new file mode 100644 index 00000000..067e6ae5 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/minimal.json @@ -0,0 +1,14 @@ +{ + "name": "round-trip минимального пакета через пару навыков", + "caseFiles": ["minimal.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/minimal.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/minimal", "outFile": "roundtrip.xsd" }, + "expect": { + "files": ["roundtrip.xsd"] + } +} diff --git a/tests/skills/cases/xdto-decompile/minimal.xsd b/tests/skills/cases/xdto-decompile/minimal.xsd new file mode 100644 index 00000000..fe77cf12 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/minimal.xsd @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/simple-types.json b/tests/skills/cases/xdto-decompile/simple-types.json new file mode 100644 index 00000000..b310804c --- /dev/null +++ b/tests/skills/cases/xdto-decompile/simple-types.json @@ -0,0 +1,14 @@ +{ + "name": "round-trip: фасеты, enumeration, list, union", + "caseFiles": ["simple-types.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/simple-types.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/simple_types", "outFile": "roundtrip.xsd" }, + "expect": { + "files": ["roundtrip.xsd"] + } +} diff --git a/tests/skills/cases/xdto-decompile/simple-types.xsd b/tests/skills/cases/xdto-decompile/simple-types.xsd new file mode 100644 index 00000000..71ca76cf --- /dev/null +++ b/tests/skills/cases/xdto-decompile/simple-types.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Configuration.xml new file mode 100644 index 00000000..96a8531d --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/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 + + + + Русский + ПакетСАннотациями + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml new file mode 100644 index 00000000..9f7fd64e --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями.xml @@ -0,0 +1,20 @@ + + + + + ПакетСАннотациями + + + ru + Пакет с аннотациями + + + en + Annotated package + + + Проверка xs:appinfo + urn:test:annotations + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin new file mode 100644 index 00000000..3540686c --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/XDTOPackages/ПакетСАннотациями/Ext/Package.bin @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd b/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd new file mode 100644 index 00000000..346709ed --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/annotations.xsd @@ -0,0 +1,28 @@ + + + + + ПакетСАннотациями + Пакет с аннотациями + Annotated package + Проверка xs:appinfo + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd new file mode 100644 index 00000000..017d000f --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/annotations/roundtrip.xsd @@ -0,0 +1,25 @@ + + + + + ПакетСАннотациями + Проверка xs:appinfo + Пакет с аннотациями + Annotated package + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Configuration.xml new file mode 100644 index 00000000..2330f34e --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/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 + + + + Русский + anon_and_content + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml new file mode 100644 index 00000000..1a74b7e4 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content.xml @@ -0,0 +1,16 @@ + + + + + anon_and_content + + + ru + anon_and_content + + + + urn:test:anon + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin new file mode 100644 index 00000000..f0f7d6ce --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/XDTOPackages/anon_and_content/Ext/Package.bin @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd new file mode 100644 index 00000000..71f80913 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/anon-and-content.xsd @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd new file mode 100644 index 00000000..45b7a5b6 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/anon-and-content/roundtrip.xsd @@ -0,0 +1,38 @@ + + + + + anon_and_content + anon_and_content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Configuration.xml new file mode 100644 index 00000000..963a4d32 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/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 + + + + Русский + complex_types + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml new file mode 100644 index 00000000..51808fde --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types.xml @@ -0,0 +1,16 @@ + + + + + complex_types + + + ru + complex_types + + + + urn:test:complex + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin new file mode 100644 index 00000000..d70504cb --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/XDTOPackages/complex_types/Ext/Package.bin @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd b/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd new file mode 100644 index 00000000..f54435a3 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/complex-types.xsd @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd new file mode 100644 index 00000000..7eb9a5cd --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/complex-types/roundtrip.xsd @@ -0,0 +1,46 @@ + + + + + complex_types + complex_types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Configuration.xml new file mode 100644 index 00000000..f32bf316 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/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 + + + + Русский + minimal + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml new file mode 100644 index 00000000..c603340a --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal.xml @@ -0,0 +1,16 @@ + + + + + minimal + + + ru + minimal + + + + urn:test:minimal + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin new file mode 100644 index 00000000..54b87db8 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/XDTOPackages/minimal/Ext/Package.bin @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd b/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd new file mode 100644 index 00000000..fe77cf12 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/minimal.xsd @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd new file mode 100644 index 00000000..0607fc58 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/minimal/roundtrip.xsd @@ -0,0 +1,16 @@ + + + + + minimal + minimal + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Configuration.xml new file mode 100644 index 00000000..931d80c4 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/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 + + + + Русский + simple_types + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml new file mode 100644 index 00000000..3c1161b2 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Ext/ClientApplicationInterface.xml @@ -0,0 +1,18 @@ + + + + + UUID-002 + + + + + UUID-004 + + + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml new file mode 100644 index 00000000..37c60d78 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/Languages/Русский.xml @@ -0,0 +1,16 @@ + + + + + Русский + + + ru + Русский + + + + ru + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml new file mode 100644 index 00000000..220d7da4 --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types.xml @@ -0,0 +1,16 @@ + + + + + simple_types + + + ru + simple_types + + + + urn:test:simple + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin new file mode 100644 index 00000000..2de23a8f --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/XDTOPackages/simple_types/Ext/Package.bin @@ -0,0 +1,13 @@ + + + [0-9]{20} + + + 1.05 + 1.06 + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd b/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd new file mode 100644 index 00000000..0899371f --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/roundtrip.xsd @@ -0,0 +1,41 @@ + + + + + simple_types + simple_types + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd b/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd new file mode 100644 index 00000000..71ca76cf --- /dev/null +++ b/tests/skills/cases/xdto-decompile/snapshots/simple-types/simple-types.xsd @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/xdto-validate/_skill.json b/tests/skills/cases/xdto-validate/_skill.json new file mode 100644 index 00000000..ba1c18b3 --- /dev/null +++ b/tests/skills/cases/xdto-validate/_skill.json @@ -0,0 +1,11 @@ +{ + "script": "xdto-validate/scripts/xdto-validate", + "setup": "empty-config", + "args": [ + { "flag": "-PackagePath", "from": "workPath", "field": "packagePath" } + ], + "snapshot": { + "root": "workDir", + "normalizeUuids": true + } +} diff --git a/tests/skills/cases/xdto-validate/facet-conflicts.json b/tests/skills/cases/xdto-validate/facet-conflicts.json new file mode 100644 index 00000000..95b15286 --- /dev/null +++ b/tests/skills/cases/xdto-validate/facet-conflicts.json @@ -0,0 +1,17 @@ +{ + "name": "несогласованные фасеты: length с min/maxLength, fractionDigits > totalDigits", + "caseFiles": [ + "pkg-facets/XDTOPackages/facets.xml", + "pkg-facets/XDTOPackages/facets/Ext/Package.bin" + ], + "params": { "packagePath": "pkg-facets/XDTOPackages/facets" }, + "expectError": true, + "expect": { + "stdoutContains": [ + "length несовместим с minLength/maxLength", + "minLength (3) больше maxLength (2)", + "fractionDigits (9) больше totalDigits (5)" + ] + }, + "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains" +} diff --git a/tests/skills/cases/xdto-validate/nillable-on-attribute.json b/tests/skills/cases/xdto-validate/nillable-on-attribute.json new file mode 100644 index 00000000..ecd88723 --- /dev/null +++ b/tests/skills/cases/xdto-validate/nillable-on-attribute.json @@ -0,0 +1,12 @@ +{ + "name": "nillable у свойства-атрибута — не переживёт экспорт XML-схемы", + "caseFiles": [ + "pkg-nillattr/XDTOPackages/nillattr.xml", + "pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin" + ], + "params": { "packagePath": "pkg-nillattr/XDTOPackages/nillattr" }, + "expect": { + "stdoutContains": ["nillable", "form=\"Attribute\""] + }, + "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains" +} diff --git a/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml new file mode 100644 index 00000000..e8386de4 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype.xml @@ -0,0 +1,16 @@ + + + + + anytype + + + ru + anytype + + + + urn:test:anytype + + + diff --git a/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin new file mode 100644 index 00000000..1c093d6f --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-anytype/XDTOPackages/anytype/Ext/Package.bin @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml new file mode 100644 index 00000000..2b781f30 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets.xml @@ -0,0 +1,16 @@ + + + + + facets + + + ru + facets + + + + urn:test:facets + + + diff --git a/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin new file mode 100644 index 00000000..a26f93c3 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-facets/XDTOPackages/facets/Ext/Package.bin @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml new file mode 100644 index 00000000..f650ac21 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr.xml @@ -0,0 +1,16 @@ + + + + + nillattr + + + ru + nillattr + + + + urn:test:nillattr + + + diff --git a/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin new file mode 100644 index 00000000..b46d8724 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-nillattr/XDTOPackages/nillattr/Ext/Package.bin @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml new file mode 100644 index 00000000..e5f7317e --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved.xml @@ -0,0 +1,16 @@ + + + + + unresolved + + + ru + unresolved + + + + urn:test:unresolved + + + diff --git a/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin new file mode 100644 index 00000000..ea92d1a9 --- /dev/null +++ b/tests/skills/cases/xdto-validate/pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/tests/skills/cases/xdto-validate/silent-anytype.json b/tests/skills/cases/xdto-validate/silent-anytype.json new file mode 100644 index 00000000..bc1eef90 --- /dev/null +++ b/tests/skills/cases/xdto-validate/silent-anytype.json @@ -0,0 +1,15 @@ +{ + "name": "тихая деградация: xs:anyType при объявленном import", + "caseFiles": [ + "pkg-anytype/XDTOPackages/anytype.xml", + "pkg-anytype/XDTOPackages/anytype/Ext/Package.bin" + ], + "params": { "packagePath": "pkg-anytype/XDTOPackages/anytype" }, + "expect": { + "stdoutContains": [ + "тихая деградация", + "ни один тип из этого пространства имён не используется" + ] + }, + "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains" +} diff --git a/tests/skills/cases/xdto-validate/unresolved-types.json b/tests/skills/cases/xdto-validate/unresolved-types.json new file mode 100644 index 00000000..d5542509 --- /dev/null +++ b/tests/skills/cases/xdto-validate/unresolved-types.json @@ -0,0 +1,16 @@ +{ + "name": "неразрешимые ссылки: нет локального типа и нет import под чужой ns", + "caseFiles": [ + "pkg-unresolved/XDTOPackages/unresolved.xml", + "pkg-unresolved/XDTOPackages/unresolved/Ext/Package.bin" + ], + "params": { "packagePath": "pkg-unresolved/XDTOPackages/unresolved" }, + "expectError": true, + "expect": { + "stdoutContains": [ + "не разрешается: в пакете нет типа \"НетТакогоТипа\"", + "не объявлен" + ] + }, + "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains" +} diff --git a/tests/skills/cases/xdto-validate/valid-package.json b/tests/skills/cases/xdto-validate/valid-package.json new file mode 100644 index 00000000..cd58f91b --- /dev/null +++ b/tests/skills/cases/xdto-validate/valid-package.json @@ -0,0 +1,15 @@ +{ + "name": "корректный пакет, собранный xdto-compile — ошибок нет", + "caseFiles": ["valid.xsd"], + "preRun": [ + { + "script": "xdto-compile/scripts/xdto-compile", + "args": { "-XsdPath": "{workDir}/valid.xsd", "-OutputDir": "{workDir}" } + } + ], + "params": { "packagePath": "XDTOPackages/valid" }, + "expect": { + "stdoutContains": ["Validation OK"] + }, + "noSnapshot": "навык только читает — сравнивать нечего, проверка через stdoutContains" +} diff --git a/tests/skills/cases/xdto-validate/valid.xsd b/tests/skills/cases/xdto-validate/valid.xsd new file mode 100644 index 00000000..fe158180 --- /dev/null +++ b/tests/skills/cases/xdto-validate/valid.xsd @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + diff --git a/tests/skills/runner.mjs b/tests/skills/runner.mjs index f1b30c84..0a1cf73d 100644 --- a/tests/skills/runner.mjs +++ b/tests/skills/runner.mjs @@ -603,6 +603,7 @@ async function runCaseAsync(testCase, opts) { const isExternal = typeof setupName === 'string' && setupName.startsWith('external:'); workspace = createWorkspace(fixturePath, isExternal); workDir = workspace.path; + copyCaseFiles(caseData, workDir, skillCasesDir); // Pre-run steps if (caseData.preRun) { @@ -742,6 +743,22 @@ async function runCaseAsync(testCase, opts) { } } +// Скопировать файлы из каталога кейса в workDir — для навыков, вход которых +// не JSON, а файл (например XSD для xdto-compile). Так эталонные входы остаются +// читаемыми файлами, а не строками внутри JSON кейса. +function copyCaseFiles(caseData, workDir, skillCasesDir) { + if (!caseData.caseFiles) return; + for (const rel of caseData.caseFiles) { + const src = join(skillCasesDir, rel); + if (!existsSync(src)) throw new Error(`caseFiles: файл не найден: ${src}`); + // Путь со слэшем сохраняет структуру каталогов (напр. дерево пакета), + // простое имя кладётся в корень workDir + const dst = rel.includes('/') ? join(workDir, rel) : join(workDir, basename(rel)); + mkdirSync(dirname(dst), { recursive: true }); + copyFileSync(src, dst); + } +} + function runCase(testCase, opts) { const { skillConfig, caseData, snapshotDir } = testCase; const t0 = performance.now(); @@ -769,6 +786,7 @@ function runCase(testCase, opts) { const isExternal = typeof setupName === 'string' && setupName.startsWith('external:'); workspace = createWorkspace(fixturePath, isExternal); workDir = workspace.path; + copyCaseFiles(caseData, workDir, skillCasesDir); // 2. Pre-run steps (setup prerequisites like creating objects) if (caseData.preRun) {