Compare commits

...
Author SHA1 Message Date
Nick ShirokovandClaude Opus 5 5472e03417 docs(form-compile): согласовать порядок вызова с form-add
Раздел Workflow описывал порядок «сначала компиляция, потом form-add», тогда как
form-add/SKILL.md и все тестовые кейсы задают обратный: каркас, затем наполнение.
Модель получала разный ответ в зависимости от того, какой навык прочитала первым.

Оба порядка дают одинаковый результат, но начинать с form-add надёжнее: при неверном
пути к объекту он сообщает об этом сразу, тогда как компиляция создаст каталоги по
указанному пути и отчитается об успехе.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 17:28:41 +03:00
Nick ShirokovandClaude Opus 5 05856abe25 docs(xdto-guide,xdto-decompile): вернуть рецепт версионной копии исполнителю
Предыдущая правка гайда изложила копирование пакета пошагово в повелительном
наклонении, и стало неясно, кому эти шаги адресованы: гайд построен на том, что
задачу ставят словами, а шаги делает агент. Пользователь мог прочитать это как
работу, которую надо сделать самому.

В гайде теперь сказано, что происходит и чего достаточно назвать в задаче.
Сами шаги и острая кромка (менять targetNamespace вместе с объявлением xmlns,
не заменять все вхождения строки) — в SKILL.md навыка выгрузки, там же, где
описан путь «выгрузить → поправить → собрать».

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:36:38 +03:00
Nick ShirokovandClaude Opus 5 32455a64b1 docs(xdto-guide): рецепт версионной копии пакета вместо описания результата
Раздел «Новая версия пакета» описывал, что получается, но не как это сделать,
и из-за этого выглядел местом, требующим отдельного флага компилятора. Рутины
там на самом деле немного: выгрузить схему, поменять в шапке targetNamespace
и связанное объявление xmlns, собрать под новым именем — имя и синоним задаются
флагами, править их внутри схемы не нужно.

Названа острая кромка: заменять все вхождения URI строкой нельзя — пострадает
импорт пространства имён, для которого старый URI является префиксом.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:33:27 +03:00
Nick ShirokovandClaude Opus 5 64b461930b docs(xdto-dsl-spec): отображение fixed и отсутствовавшее зеркало xdto:fixed
Таблица соответствий утверждала «@nillable, @default, @fixed — те же имена».
Для fixed это неверно: в модели признак и значение разнесены, fixed="V" из XSD
превращается в fixed="true" + default="V". Компилятор был написан ровно по этой
строке — неверная строка спеки воспроизвелась в коде буквально и дожила до
первой проверки платформой.

Плюс в таблице аннотаций не было xdto:fixed, хотя компилятор его принимает:
зеркало нужно для fixed="false" при заданном default.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:25:47 +03:00
Nick ShirokovandClaude Opus 5 79beba2d1f fix(xdto-compile,xdto-validate): признак фиксированного значения без самого значения
В модели XDTO fixed — булев признак, а значение лежит в default; в XML-схеме
fixed="V" совмещает и признак, и значение. Компилятор оба идиома принимал, но
не проверял принятое: зеркало xdto:fixed="true" без default собиралось молча
в пакет, который платформа отвергает («Отсутствует фиксированное значение
свойства»). Прощающий ввод был сделан наполовину.

xdto-validate v1.1 — два ERROR: значение попало в признак (fixed не булев)
и признак без значения. Формулировка второго повторяет платформенную дословно,
чтобы отказ загрузки и наш вывод читались как одно и то же.

xdto-compile v1.1 — то же условие предупреждением на сборке, то есть на шаг
раньше db-update, где починить дешевле.

Кейсы: оба идиома плюс только default и атрибут (загружается в базу);
зеркало без значения — проверка диагностики, из платформенной верификации
исключено штатным skipPlatformVerify, пакет невалиден by design.

Правило откалибровано корпусом (760 пакетов, оба рантайма): 0 ложных
срабатываний, состав предупреждений не изменился. Round-trip остался
760/760 байт-в-байт.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 16:25:38 +03:00
Nick ShirokovandClaude Opus 5 20d86ae10f docs(xdto-guide): примеры с названным объектом работы, три новых сценария
Формулировка задачи может быть общей, но объект работы нужно назвать: без пути
к файлу или имени пакета агент останавливается и просит уточнений вместо работы.
Проверено прогоном триггеров: тот же запрос без якоря и с якорем даёт разный
исход. Отсюда абзац «что стоит назвать в задаче» и переформулировка примеров,
где объект не назывался.

Сняты два неудачных примера: «создай по нему документы» (вторая половина не про
XDTO) и симптом без якоря, дублирующий соседний сильный пример.

Добавлены сценарии, которых не было: инвентаризация незнакомой конфигурации,
обратные ссылки перед правкой (в прозе упоминались, примера не было) и проверка
перед загрузкой — у навыка проверки не было ни одного примера.

Уточнения по тексту:
- импорт пространств имён самой платформы пакетами объявлять не нужно;
- штатный экспорт XML-схемы даёт невалидную XSD и для неквалифицированной формы
  элементов, не только теряет nillable;
- обещание round-trip подкреплено измерением на корпусе.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 15:48:29 +03:00
Nick ShirokovandClaude Opus 5 5d5a1bc36a fix(xdto): три дефекта, найденных платформенной верификацией снэпшотов
verify-snapshots загружает результат каждого кейса в 1С. Раньше навыки xdto
через него не проходили вовсе; первый прогон дал 5 из 9. Ни корпусная сверка,
ни валидатор такого не ловили: корпус состоит из заведомо валидных пакетов,
а синтетические кейсы до сих пор в базу не грузились.

1. fixed. В модели XDTO это булев флаг, значение лежит в default; в XSD наоборот —
   fixed="V" несёт значение. Компилятор писал значение прямо в fixed, и платформа
   отвергала пакет («Отсутствует фиксированное значение свойства»). Перевод сделан
   в обе стороны; по принципу прощающего ввода принимается и модельная форма через
   зеркало xdto:fixed. Отображение выведено по корпусу: fixed встречается только
   вместе с default, значений всего два.

2. Импорт на несуществующий пакет платформа отвергает («xdto-package-3.3 …
   не определен»), а у нас проверки не было. Добавлена ошибка валидатора и,
   что важнее, предупреждение прямо на сборке — отказ при db-update дешевле
   поймать на шаг раньше. Правило пришлось калибровать корпусом: сначала оно
   дало 67 ложных срабатываний на платформенных пространствах имён, их список
   выведен и исключён.

3. localName проверяется как NCName — фикстура с пробелом в имени была негодной,
   заменена на реалистичный дефис (name="alpha_3" localName="alpha-3").

Харнесс получил skipPlatformVerify с обязательной причиной: результат
set-namespace невалиден by design, операция намеренно оставляет висящий импорт
у зависящего пакета.

Итог: 9/9 компилятора, 9/10 + 1 осознанный пропуск у edit, round-trip 760/760,
валидатор 0 ложных, 40 тестов на обоих рантаймах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:51:08 +03:00
Nick ShirokovandClaude Opus 5 3eb805f7b0 docs(xdto-guide): примеры задачами, а не синтаксисом команд
Гайд адресован неподготовленному читателю, а основной сценарий — задача
в произвольной форме или её часть внутри большей. Синтаксис вызовов такого
читателя скорее отпугнёт, к тому же он уже описан в SKILL.md каждого навыка
и в гайде дублировался.

Каждый сценарий теперь начинается с того, как задачу формулируют словами
(«сформируй платёжку в формате клиент-банка», «обращение к Смена.Сотрудник
возвращает что-то бесструктурное»), дальше — что за этим происходит и на что
обратить внимание. За точным синтаксисом — ссылки на SKILL.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:13:48 +03:00
Nick ShirokovandClaude Opus 5 8232cbeaaa test(verify-snapshots): поддержка caseFiles и навыков xdto
Харнесс платформенной верификации не знал про caseFiles — механизм файлового
входа кейса, добавленный в runner.mjs. Та же функция перенесена сюда,
xdto-compile и xdto-edit добавлены в список проверяемых навыков.

Первый прогон отвергает 4 кейса из 9 — разбор в debug/xdto/FINDINGS.md §15.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:12:02 +03:00
Nick ShirokovandClaude Opus 5 49d7204385 docs(xdto): пользовательский гайд и группа в README
Из семейств гайды есть у cf, cfe, db, epf, form, meta, role, skd, web —
у XDTO не было. Гайд построен вокруг задач, а не вокруг навыков: написать код
заполнения, разобрать входящий XML, добавить пакет по схеме контрагента,
поправить существующий, выпустить новую версию, отдать схему наружу,
разобраться с «бесструктурным» свойством.

Группа добавлена в таблицу README.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:06:26 +03:00
Nick ShirokovandClaude Opus 5 5fd952a796 docs(xdto-dsl-spec): синхронизировать с правкой уплощения xs:choice
Спека описывала уплощение как «вложенный выбор варианта не сохраняется»,
не упоминая, что ветки теперь становятся необязательными — а это и есть
суть правки: иначе «одно из двух» превращалось в «оба обязательны»
и тип нельзя было заполнить.

Заодно в таблицу аннотаций добавлен xdto:declareNs, который был реализован
и описан в справочнике навыка, но в спеку не попал.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 14:00:25 +03:00
Nick ShirokovandClaude Opus 5 817ae0fea7 fix(xdto-info): рецепты создания по факту вида типа
Рецепт для вложенного объекта учил окольной форме там, где она не нужна.
Именованный тип берётся так же, как корневой — ФабрикаXDTO.Тип(ns, имя);
через Свойства.Получить(...).Тип идут только к анонимному, у которого имени
нет. Теперь строка выдаётся по факту: для именованного одна форма, для
анонимного другая, и обе с настоящими именами из пакета.

Убрано утверждение «Узел = ФабрикаXDTO.Создать(ТипУзла, Значение)» для типа
со значением элемента. По синтакс-помощнику Создать(<Тип>, <Значение>)
принимает ТипЗначенияXDTO, а такой узел — объектный тип, то есть форма была
просто неверной. Вместо неё проверяемый факт: значение лежит в свойстве
__content.

Зато для типа значения эта форма как раз корректна, а рецепта там не было
вовсе — добавлен.

Попутно: строка-заглушка «(раскрыт выше)» создавалась без новых ключей, и
py-порт падал с KeyError там, где PowerShell молча возвращает $null на
отсутствующем свойстве. Ключи добавлены, доступ переведён на .get().

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:52:52 +03:00
Nick ShirokovandClaude Opus 5 6486f433de fix(xdto-compile,xdto-info,xdto-edit): правки по итогам прогона на субагентах
Четырём субагентам выданы реалистичные задачи по песочнице, навыки в
формулировках не назывались. Разбор — в debug/xdto/FINDINGS.md, §13.

ГЛАВНОЕ — дефект компилятора. При уплощении вложенного xs:choice ветки
оставались обязательными: схема «самовывоз ИЛИ адрес доставки» давала пакет,
требующий заполнить оба, и ни один реальный документ в него не ложился.
Компилятор предупреждал о потере выбора, но молчал о последствии, а валидатор
показывал «0 ошибок, 0 предупреждений» — структурно пакет корректен,
семантически мёртв. Теперь ветки становятся необязательными (единственное
уплощение, оставляющее тип заполнимым), предупреждение называет их поимённо.
Корневой xs:choice не затронут: он отображается в ordered="false".

xdto-edit: -Value "@файл" по конвенции skd-edit — на кавычках при инлайновой
передаче XSD споткнулись двое агентов из четырёх, причём сырой LoadXml уводил
чинить схему вместо транспорта. При сбое разбора теперь понятное сообщение.

xdto-info: поиск, законно ничего не нашедший, падал throw'ом со стектрейсом и
читался как поломка инструмента — теперь строка и exit 1. Блок «Создание»
покрывал только корневой тип, хотя вся реальная работа в XDTO — вложенные и
анонимные типы; добавлены рецепты по факту наличия. Отсутствие раздела «Точки
входа» было неоднозначным — теперь явная строка. Новый -RequiredOnly даёт
скелет «заполни обязательное»: необязательный объект уходит вместе с поддеревом.
По умолчанию выключен — иначе список читался бы как полный.

xdto-validate: предупреждение про anyType описывало историю («платформа заменяет
при импорте»), хотя в файле уже зафиксирован anyType; сначала состояние, потом
происхождение.

Проверено: 40 тестов на обоих рантаймах, round-trip 760/760, валидатор
0 ложных срабатываний на корпусе.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:44:46 +03:00
Nick ShirokovandClaude Opus 5 1a1bbbac6f refactor(xdto-info): легенда обозначений в выводе, а не в инструкции
Инструкция несла 14-строчный пример вывода — то самое, что модель увидит,
запустив навык, но читаемое при каждой загрузке инструкции. Та же логика,
по которой из xdto-validate убран каталог проверок.

Легенда при этом нужна: ← Имя, [значение элемента], · Пакет из вывода сами
не читаются. Поэтому она переехала в вывод и печатается только для тех
обозначений, которые в нём реально встретились — на плоском типе легенды
нет вовсе. В самом навыке уже был такой прецедент: режим списка пакетов
поясняет свои колонки прямо в выводе.

Инструкция сократилась с 99 до 77 строк.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:05:15 +03:00
Nick ShirokovandClaude Opus 5 105ba67cb5 docs(xdto): триггеры не обещают того, чего навыки не делают
xdto-info объявлял себя средством «для написания кода» и «для разбора
входящего XML» — он не делает ни того ни другого, а даёт структуру, чтобы
это написал вызывающий. Формулировка приведена к принятой в семействе:
meta-info говорит «как подготовительный шаг при написании запросов и кода».

xdto-compile тем же оборотом обещал «разбор внешнего XML-формата», хотя
собирает пакет; заменено на «под внешний XML-формат».

xdto-decompile претендовал на «отредактировать существующий пакет» —
это роль xdto-edit, и ровно то противоречие, что было устранено в теле
инструкций. Теперь триггер описывает свою настоящую нишу: получить схему,
чтобы переработать целиком, отдать контрагенту или перенести пакет.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 13:00:42 +03:00
Nick ShirokovandClaude Opus 5 1d49c16a67 docs(xdto): устранить противоречие «как править пакет» между навыками
На один и тот же вопрос четыре инструкции отвечали по-разному: decompile
объявлял себя «основным способом менять пакет», edit сам себя занижал
до варианта для маленьких пакетов, compile про edit не упоминал вовсе,
а workflow валидатора его не знал. Модель получала бы разный ответ
в зависимости от того, на какой файл попала, причём два из них уводили
от единственного навыка, созданного ровно для этой задачи.

Единое правило проведено через все четыре: точечная правка — xdto-edit;
переработка схемы целиком или знакомство с ней — decompile → compile.
Заодно в decompile добавлена развилка на xdto-info, чтобы разграничить
«нужна схема» и «нужна сводка для кода».

Мелочи: грамматика в xdto-edit, служебное значение -Mode auto убрано
из таблицы параметров (пользователь его не пишет), описан флаг [до N].

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:58:07 +03:00
Nick ShirokovandClaude Opus 5 a146fc1467 fix(xdto-edit): диагностика без привязки к харнессу и к «всему комплекту»
Сообщение называло каталог .claude/skills, хотя проект портируется в
.cursor/skills, .codex/skills, .gemini/skills и другие — путь теперь
вычисляется от расположения скрипта и потому верен на любой порт-ветке.

И предлагало копировать весь набор навыков, хотя нужны ровно два соседа:
xdto-decompile и xdto-compile. Теперь называются только недостающие.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:50:31 +03:00
Nick ShirokovandClaude Opus 5 679f820e52 fix(xdto-edit): преflight-проверка соседних навыков
xdto-edit — единственный навык с жёсткой зависимостью: без xdto-decompile
и xdto-compile модельные операции не работают. Остальные пять межнавыковых
вызовов в репозитории ведут только к *-validate, необязательному post-шагу
с деградацией в [SKIP], так что это отступление, а не следование практике.

Раньше отсутствие соседа обнаруживалось на середине правки. Теперь комплектность
проверяется до начала работы, с указанием, что навыки ставятся комплектом.
Операции над объектом метаданных (rename, set-synonym, set-comment) соседей
не требуют и работают в одиночку — проверка их не блокирует.

В SKILL.md зависимость намеренно не описана: это раздуло бы инструкцию и подало
бы исключение как допустимую практику. Причины, по которым не сделана копия
(конвертер — скрипт, а не библиотека; вторая реализация разошлась бы, чему есть
прямая улика в learning_meta_edit_emitter_ports; гарантия байт-точности держится
на тождестве кода) записаны в debug/xdto/FINDINGS.md — там, где их увидит тот,
кто соберётся «починить» это дублированием.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:43:57 +03:00
Nick ShirokovandClaude Opus 5 318abe3fc9 feat(xdto-edit): точечная правка пакета без чтения всей схемы
Навык нужен ровно для одного: не втаскивать в контекст мегабайтную схему ради
одного поля. Чистота диффа тут ни при чём — она уже обеспечена round-trip'ом
(замер: правка двух вещей через decompile→compile даёт 3 изменённые строки из 241).

Поэтому edit не заводит второй эмиттер, а строится поверх round-trip'а: пакет
выгружается в XSD, операция применяется к схеме, пакет собирается обратно
компилятором. Байт-точность для нетронутого достаётся даром, а смена namespace
перегенерирует все объявления префиксов сама — в EnterpriseData_1_20_2 их 5280.
На лишний шаг (загрузка XSD в DOM и пересохранение) заведён отдельный харнесс:
холостая правка не меняет ни байта на всех 760 пакетах.

Операции: add/replace/remove-property, add/remove-type, add-enum, add-import,
rename, set-synonym, set-comment, set-namespace. Содержимое — всегда фрагмент
XSD, тем же языком, что в компиляторе; отдельных -MinOccurs нет, свойство
меняется целиком через replace-property. Адресация точкой, путь заходит внутрь
встроенных типов.

rename трогает три места (объект метаданных, имена файла и каталога, регистрацию
в Configuration.xml). set-namespace правит свой пакет и перечисляет зависящие,
но не меняет их: при версионировании они и должны смотреть на прежнее
пространство имён. После правки автоматически запускается xdto-validate.

Проверено загрузкой в базу 8.3.24: add-property, add-enum и set-namespace
переживают db-load-xml + db-update.

Попутные ловушки портирования (детали — debug/xdto/FINDINGS.md): пустой элемент
в lxml ложен, из-за чего "or"-цепочка создавала бы вторую частицу в типе
с пустой sequence; диапазон [Ѐ-ӿԀ-ӿ] валиден в .NET и не компилируется в Python.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:29:28 +03:00
Nick ShirokovandClaude Opus 5 a4a55cf883 feat(xdto-info): структура пакета и типа в терминах 1С
Навык отвечает на вопрос «что присвоить и что обязательно», а не показывает
модель как есть. Типы переведены в нотацию 1С с учётом ограничений
(xs:decimal + totalDigits → Число(18,2)), псевдонимы развёрнуты со стрелкой
на исходное имя, кратность вынесена во флаги, для перечислимых типов выводятся
допустимые литералы. Различие атрибут/элемент в таблице свойств не показывается:
в коде 1С обращение одинаковое.

Флаг ставится на обязательные, хотя в модели XDTO умолчание обратное. Причина —
соседний meta-info, где непомеченный реквизит необязательный: один значок,
означающий в двух навыках противоположное, сам по себе источник ошибок.

Режимы: список пакетов конфигурации, состав пакета с точками входа, структура
типа с разузлованием на -Depth и used-by. Разузлование идёт через границы
пакетов с пометкой источника, анонимные типы раскрываются всегда, циклы
обрываются. Пакет адресуется путём, именем или namespace — последнее потому,
что модель приходит к задаче от строки ФабрикаXDTO.Тип(ns, имя), а не от имени
пакета в конфигурации.

Попутно закрыт баг паритета во всех четырёх py-портах: платформа допускает
в targetNamespace произвольную строку (в БП есть пакет с кириллическим
«ДопФайлУниверсальный»), .NET такое принимает, а libxml2 отвергает как
невалидный URI. Добавлено узкое отступление на восстанавливающий разбор —
только для этой ошибки, чтобы валидатор не перестал замечать битый XML.
Обнаружено это только потому, что корпус впервые прогнан на Python: раньше
все 760 гонялись лишь на PowerShell. Теперь 760/760 на обоих рантаймах.

Сортировка в PS переведена на ординальную: Sort-Object сортирует по культуре,
sorted() в Python — по кодам, и на смешанных латиница/кириллица имена
расходились бы.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 12:05:08 +03:00
Nick ShirokovandClaude Opus 5 bd4a082259 docs(xdto-decompile,xdto-validate,xdto-compile): убрать дублирующие разделы «Верификация»
У декомпилятора и валидатора раздел дословно повторял блок примеров и таблицу
параметров строкой выше, у компилятора — шаг 3 из «Типичного workflow».
Конкретная форма команды перенесена в этот шаг, разделы убраны.

Раздел полезен там, где отсылает к другим навыкам (как в role-compile),
и есть лишь у 14 навыков из 75 — обязательной конвенцией не является.
Инструкции стали короче на 30 строк без потери содержания.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:50:52 +03:00
Nick ShirokovandClaude Opus 5 0aa9342407 docs(xdto-compile,xdto-decompile,xdto-validate): обязательность параметров и умолчания
В таблицах параметров не было видно, что обязательно, а что нет, и какие
значения подставляются по умолчанию. Добавлена колонка «Обязательный»
(по образцу form-edit), умолчания расписаны, отмечена взаимоисключающая
пара -XsdPath/-Xsd, псевдонимы -Path и поведение без -Force.

Сверил документацию с поведением скриптов: хеш-таблица в -Synonym работает
только в PS-порте, в Python её нет — из инструкции убрана, многоязычный
синоним задаётся блоком xs:appinfo, который поддерживают оба порта.

Убран последний след нашего обсуждения формата («отдельного DSL нет» —
читателю не с чем сравнивать), добавлено, где посмотреть уже собранные
пакеты, и точное имя файла справочника аннотаций.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 10:46:59 +03:00
Nick ShirokovandClaude Opus 5 7ca6dfa6b2 fix(xdto-compile,xdto-validate): не терять конструкции молча; вычитать инструкции
xdto-compile терял свойства без единого слова: на реалистичной чужой схеме
из шести объявленных доезжало одно. Вложенные xs:sequence/xs:choice теперь
уплощаются (модель хранит плоский список), xs:all трактуется как
последовательность, xs:group и xs:attributeGroup раскрываются по ссылке —
и о каждом приближении навык пишет предупреждение. Молчаливая потеря — тот же
класс дефекта, что мы ловим у платформы, лечится так же: сообщением, не отказом.

xdto-validate получил проверки на грабли, найденные при разработке: порядок
элементов верхнего уровня (платформа отвергает пакет, не называя причины),
конфликты объявлений (name+ref, type+вложенный тип, тип без разновидности),
несовпадение рода базового типа, дубли имён свойств.

Новые правила прогнаны по всем 760 пакетам выгрузок: всё, что породила
платформа, валидно по определению, поэтому каждая ошибка там — ошибка правила.
Первый прогон дал 7, и все три класса оказались реальным поведением платформы:
length вместе с minLength/maxLength встречается, два пакета делят один
targetNamespace (Envelope и SOAP_Envelope_1_1 в БП), form="Text" называется
не только __content. Правила понижены до предупреждений либо сняты. Заодно
убран шум: предупреждение о неиспользуемом import срабатывало на четверти
корпуса — теперь только вместе с anyType, где оно и означает проблему.
Итог: 0 ошибок на корпусе, предупреждений 53 вместо 242.

Инструкции переписаны под читателя-исполнителя: убраны детали реализации
и наши мерки, каталог проверок валидатора (его вывод самодостаточен),
локальные пути в примерах заменены нейтральными. Таблица соответствий
XSD и справочник аннотаций вынесены в xdto-compile/xsd-reference.md.

Round-trip 760/760 сохранён, паритет PS/PY сохранён.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 21:48:48 +03:00
Nick ShirokovandClaude Opus 5 d05aef54b4 feat(xdto-compile,xdto-decompile,xdto-validate): пакеты XDTO из XML-схемы
Три навыка для работы с пакетами 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) <noreply@anthropic.com>
2026-07-25 21:21:57 +03:00
Nick ShirokovandClaude Opus 5 d544071b1e feat(meta-validate): версионные свойства и диапазон LineNumberLength
Две проверки, обе про формат 2.20.

Проверка 18 — реестр versionedProps «тег → минимальная версия формата». Если
свойство присутствует в файле со слишком старым штампом, при сборке на платформе
той версии оно будет молча отброшено: платформа рапортует успех (exit 0), а
свойство теряется — проверено экспериментально на 8.3.24. Реестр расширяется
одной строкой на свойство и служит заделом под 2.21 (8.5) и последующие: он же
подсказывает, что конструкция требует более нового формата.

Проверка 19 — LineNumberLength вне диапазона 5..9 (границы из документации 1С).

Компаратор версий числовой по компонентам: строковое сравнение дало бы
"2.9" > "2.17".

Кейсы: error-lnl-out-of-range, error-220-props-in-217. Регресс 25/25 ps1+py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:54:20 +03:00
Nick ShirokovandClaude Opus 5 10ca8ac873 fix(cf-init,cf-edit): версия формата выгрузки — параметр и наследование
Версию формата задаёт ПЛАТФОРМА выгрузки, а не режим совместимости: одна и та же
БП с режимом Version8_3_24 даёт 2.17 на платформе 8.3.24 и 2.20 на 8.3.27.

- cf-init: параметр -FormatVersion (2.17|2.20|2.21, дефолт 2.17 — читается всеми
  платформами). Конфигурация создаётся с нуля, наследовать не от чего; выводить
  версию из CompatibilityMode было бы неверно. Без параметра нельзя было собрать
  2.20-проект — в том числе для тестовых фикстур.
- cf-edit: шаблон Ext/HomePageWorkArea.xml нёс жёстко вписанный version="2.17",
  то есть в 2.20-конфигурации создавал файл чужой версии. Теперь наследует версию
  из редактируемого Configuration.xml (образец — cfe-init, который так уже умеет).

epf-init/erf-init/cfe-init не трогаем: у первых двух наследовать не от чего
(внешние объекты живут вне конфигурации), cfe-init уже наследует от базовой
конфигурации корректно.

Регресс cf-init 6/6, cf-edit 12/12 — ps1 и py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:54:20 +03:00
Nick ShirokovandClaude Opus 5 068928646d feat(meta-compile,meta-decompile): свойства формата 2.20 (платформа 8.3.27)
Дельта формата 2.17→2.20 содержит три безусловных свойства, которых компилятор
не эмитил. Все три пишутся ТОЛЬКО при формате >= 2.20 (Detect-FormatVersion),
поэтому 2.17-проекты не меняются: полная сюита зелёная, ни один существующий
снэпшот не сдвинулся.

- xr:TypeReductionMode — каждому стандартному реквизиту, после CreateOnInput.
  TransformValues, кроме Owner → Deny (правило проверено против выгрузки acc:
  9 из 9 реквизитов совпали, включая Owner).
- TypeReductionMode — измерениям регистра СВЕДЕНИЙ (у прочих семейств и у
  реквизитов/ресурсов платформа его не пишет).
- LineNumberLength — табличным частям, последним в Properties.

LineNumberLength — прикладная возможность 8.3.27 (5..9 → до 999 999 999 строк
вместо 99 999), поэтому получил полноценный DSL-ключ и описание в spec §5.2.
Его дефолт зависит НЕ от версии формата, а от режима совместимости на момент
создания ТЧ (<=8_3_26 → 5, >=8_3_27 → 9) — платформа фиксирует значение и позже
не пересчитывает, поэтому в одной конфигурации соседствуют ТЧ с 5 и 9. Отсюда
новая Detect-CompatibilityMode: читает CompatibilityMode из Configuration.xml
(префикс 64 КБ — тег лежит на ~11-12 КБ, существующим 2000 байт не хватает).

Декомпилятор: TypeReductionMode захватывается только при отклонении от правила
(компилятор выводит его сам), LineNumberLength — всегда при наличии тега:
выводить его дефолт значило бы дублировать логику компилятора с риском разойтись.

Компараторы версий числовые по компонентам — строковое сравнение неверно
("2.9" > "2.17" лексикографически).

Тест-инфра: setup-фикстуры empty-config-220 и empty-config-220-compat24
(строятся тем же cf-init), два кейса — по одному на каждую ось.

Проверка: роундтрип реального 2.20-документа БП (АвансовыйОтчет, 7 ТЧ) —
по новым тегам 0 расхождений, значения и позиции совпали; остаточный хвост
52/39 идентичен такому же на 2.17, то есть пред-существующий. Сюита 570/570
ps1, 567+3 skipped py, ps1==py. 1С-сертификация обоих кейсов на 8.3.27 ✓.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 17:53:54 +03:00
Nick ShirokovandClaude Opus 5 769b4d3dbd docs(tests): описать все поля тест-кейса в README
Таблица «Все поля кейса» отставала от раннера: не были описаны idempotent,
runtimeOnly, skipValidation, а expect ограничивался упоминанием files/
stdoutContains/stdoutNotContains — preserves и структура preRun не
документировались вовсе.

Из-за таких пробелов формат кейса приходится выяснять по коду — а это ровно
тот способ, который однажды дал 9 кейсов meta-edit с несуществующим ключом:
тесты зелёные, навык no-op, снэпшот фиксирует исходник.

Добавлено (сверено с runner.mjs и с реальными кейсами):
- idempotent, runtimeOnly, skipValidation в основную таблицу;
- таблица ключей expect + вложенная таблица preserves (file/bom/eol/encoding/
  finalNewline/noCR13) с пометкой, что preserves и эталон дополняют друг друга:
  первый следит за байтовым стилем, второй за структурой;
- формы шагов preRun (прогон навыка и writeFile).

editFile намеренно не описан — это шаг интеграционных тестов, не preRun кейса.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:35:02 +03:00
Nick ShirokovandClaude Opus 5 3b5444e69d test(runner): строгий режим снэпшотов — отсутствие эталона не проходит молча
compareSnapshot при отсутствии каталога эталона возвращал {match:true,
reason:'no snapshot (skipped)'}, причём reason никуда не выводился. Кейс без
эталона был молча зелёным, а «намеренно нет» и «эталон потерялся / не создан
при добавлении кейса» — неразличимы. README закреплял это как штатное
(«совпадает со snapshot (если есть)»).

Теперь эталон обязателен везде, кроме expectError, readonly external: и явного
opt-out. Диагностика — на месте кейса, с готовой командой; сводной статистики
не добавляем (вне контекста она ничего не сообщает).

- noSnapshot: "<причина>" — легальный пропуск. Причина обязательна: отключение
  сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е;
  осмысленность причины рантайм проверить не может. true/"" → падение.
- Нет эталона и нет opt-out → падение с рецептом (команда --update-snapshots
  либо подсказка объявить noSnapshot).
- Мёртвый эталон (noSnapshot + существующий каталог) → падение: не сверяется,
  но выглядит покрытием.
- updateSnapshot пропускает кейсы с noSnapshot — иначе --update-snapshots сам
  порождал бы противоречие. Опечатка в имени поля fail-safe: opt-out не
  сработает, кейс упадёт как «эталон отсутствует».
- Диагностика вынесена в общий snapshotErrors() — обе ветки (runCase /
  runCaseAsync) больше не дублируют логику.

Размечены 3 кейса meta-validate: навык только читает и печатает, эталон
зафиксировал бы выход preRun (meta-compile), а не проверяемого навыка.

Проверка: до разметки сюита падала ровно на этих 3 кейсах (независимое
подтверждение аудита). Негативные сценарии проверены все пять: потерянный
эталон, мёртвый эталон, noSnapshot без причины, update на opt-out кейсе
(не создаёт), update на обычном (создаёт байт-в-байт прежний).
Полная сюита 566/566 ps1; python 563 passed + 3 skipped — идентично HEAD.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 15:25:20 +03:00
Nick ShirokovandClaude Opus 5 b194834f2b test: снэпшоты для roundtrip-crlf-preserve (cf-edit, meta-edit, subsystem-edit)
Кейсы проверяли только БАЙТОВЫЙ стиль файла (expect.preserves: BOM/CRLF/
encoding/finalNewline/noCR13) и что валидатор не ругнулся. Что в CRLF-файл
записан КОРРЕКТНЫЙ XML, не проверял никто: снэпшота не было, а
compareSnapshot при отсутствии эталона молча возвращает pass.

Снэпшот ортогонален preserves — сравнивается нормализованное содержимое
(структура), preserves остаётся на байтовых характеристиках. Дублирования нет.

Регресс 12/12, 20/20, 6/6 — ps1 и py. 1С-сертификация 3/3.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:40:27 +03:00
Nick ShirokovandClaude Opus 5 387f10edf0 feat(meta-validate): проверка формы MDObjectRef-ссылок
Значения xsi:type="xr:MDObjectRef" не проверялись вообще. Ошибка «тип ссылки
вместо объекта метаданных» обнаруживалась только платформой при загрузке
(«Неизвестный объект метаданных»), причём в логе, а не в коде возврата.

Проверка 17 по первому сегменту пути (переиспользован $validTypes +
$structuralOnlyTypes):
- сегмент оканчивается на Ref → Error: вида метаданных с таким именем
  не существует, ссылка гарантированно нерабочая; в тексте подсказана
  исправленная форма;
- неизвестный сегмент без Ref → Warn (список видов может быть неполон).

Ловит дефект статически, без платформы, независимо от происхождения файла.

Кейс error-mdobjectref-type-form + фикстура. Регресс 23/23 ps1+py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:27:14 +03:00
Nick ShirokovandClaude Opus 5 eb1a2ed8c1 fix(meta-edit): нормализация MDObjectRef в Owners/BasedOn/RegisterRecords/References
Та же дыра, что и в meta-compile, но здесь нормализации не было вообще —
Normalize-MDObjectRef отсутствовала как функция. set-owners "CatalogRef.Валюты"
(или modify.properties.Owners) записывал неверную ссылку молча.

- Перенесена мапа корней + Normalize-MDObjectRef (зеркало meta-compile).
- В complexPropertyMap добавлены флаги mdref/root; нормализация подключена
  в Add-/Remove-/Set-ComplexPropertyItem рядом с существующим expand.
  Покрывает Owners, RegisterRecords, BasedOn, RegisteredDocuments.
- References графы журнала документов — эмитились напрямую, тоже нормализуются.

Инструкция навыка не менялась: SKILL.md и json-dsl.md уже показывают
каноническую форму Catalog.Контрагенты.

Кейс modify-property-mdobjectref (документированный путь modify.properties).
Регресс 20/20 ps1+py, 1С-сертификация снэпшота пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:27:14 +03:00
Nick ShirokovandClaude Opus 5 66d45d3654 fix(meta-compile): нормализация MDObjectRef — CatalogRef./русская запись → Catalog.
MDObjectRef ссылается на ОБЪЕКТ метаданных (Catalog.Валюты), а не на тип ссылки.
Эмиттеры пропускали значение как есть, если в нём была точка, поэтому
"CatalogRef.Валюты" доходил до XML без изменений → при загрузке конфигурации
платформа отвечала «Неизвестный объект метаданных».

Инструкция вела в баг сама: reference/catalog.md документировал
owners: ["CatalogRef.Контрагенты"]. Тестами не ловилось — все кейсы
использовали каноническую форму.

- Normalize-MDObjectRef расширена ссылочными формами (англ. *Ref + рус. *Ссылка);
  вида метаданных, оканчивающегося на Ref, не существует → схлопывание однозначно.
  В ТИПАХ реквизитов запись CatalogRef.X верна — там мапа не применяется.
- Добавлен параметр defaultRoot (голое имя без точки), инлайн-подстановка
  "Catalog.$ownerRef" в owners убрана — логика теперь в одном месте.
- Нормализация применена в 4 местах, где её не было: owners, basedOn,
  registerRecords, baseCalculationTypes.
- Кейс catalog-inputbystring-datalock переведён на неканонический вход:
  снэпшот не изменился ни на байт — прямое доказательство нормализации.

Регресс 73/73 ps1+py, полная сюита 566/566. Живая проверка на 8.3.27:
подчинённый справочник с owners CatalogRef./СправочникСсылка. грузится чисто.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-25 14:26:55 +03:00
Nick ShirokovandClaude Opus 4.8 e01688e764 fix(form-add): идемпотентная регистрация <Form>/<Template> в ChildObjects
form-add и template-add вставляли запись в ChildObjects безусловно.
Если форма/макет уже зарегистрированы (например, form-compile
регистрирует <Form>, не создавая файл метаданных, а затем вызывается
form-add) — возникал дубль <Form>/<Template>, ломавший валидацию.

Приведено к идемпотентной модели, уже применённой в form-compile и
meta-compile: перед вставкой ищем существующую запись по имени; при
наличии — пропускаем и печатаем "Already registered ... (skipped
duplicate)". Зеркально в ps1 и py, регрессионный тест-кейс.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:28:16 +03:00
Nick ShirokovandClaude Opus 4.8 b892379202 fix(skd-compile): авто-Auto для осей таблицы, диаграмм и объектных групп
Оси таблицы (columns/rows), точки/серии диаграмм и объектные группировки без
явного selection получали пустой пивот молча — ресурсы не попадали в ячейки
пересечения. Теперь при отсутствии ключа selection/order эмитится
SelectedItemAuto/OrderItemAuto (как строковый shorthand и как ручное добавление
оси в Конфигураторе). Пустой [] уважается как «явно ничего».

skd-decompile теперь эмитит [] для отсутствующих selection/order на осях,
группах и диаграммах — decompile→compile round-trip остаётся бит-в-бит (иначе
compile впаял бы Auto на боевых узлах без выбора, напр. ветках use=false).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-23 13:05:22 +03:00
Nick ShirokovandClaude Opus 4.8 8826a88427 fix(web-test): заголовок формы в состоянии + formHasField по массиву fields
Два задокументированных ассерта бросали ВСЕГДА, то есть были мертвы:

- formTitle читал state.title, которого не заполнял никто: getFormStateScript
  собирал форму без заголовка. Единственным носителем оставалась панель
  открытых окон (activeTab), а она отключается в настройках 1С — на такой базе
  заголовок был недоступен ничем.
- formHasField читал state.fields[name], хотя fields — массив объектов
  {name, value, …}. На массиве это всегда undefined.

getFormState теперь отдаёт title. Берётся он из шапки самой формы: заголовок
лежит в атрибуте (title у .toplineBoxTitle, data-title у родителя), сам элемент
пустой — поэтому поиском по тексту он и не находился.

Выбор шапки — не «первая видимая»: при открытом всплывающем окне видимы ДВЕ,
родителя и окна, и наивное правило отдавало заголовок родителя — правдоподобный
неверный ответ, при котором тест «окно выбора открылось» зеленел бы по
документу. Приоритет взят тот же, что уже отлажен для крестика закрытия в
closeCrossScript: плавающее окно ps<N> с наибольшим индексом → собственная шапка
формы → и только потом панель открытых окон. Привязка к id, а не к тексту — не
ломается на другой локали. Панель осталась последним звеном: она отключаема, а
при всплывающем окне ещё и показывает родителя.

Диагностика раннера (resetState) тоже переведена на title с прежним activeTab
как запасным.

formHasField ищет по массиву и перечисляет доступные имена в ошибке (раньше
Object.keys по массиву давал индексы). formTitle отличает «заголовок недоступен»
(title === null) от несовпадения.

Почему не поймали раньше: из 12 ассертов сюита вызывала 8, и оба сломанных были
среди четырёх невызываемых. Теперь все четыре задействованы на настоящем выводе
getFormState — formTitle/formHasField/noErrors в 12-formstate (включая случай
всплывающего окна), tableRowCount в 09-filter. Отдельного юнит-теста намеренно
нет: состояние для него пришлось бы писать руками, а именно неверное
представление о форме состояния и породило оба дефекта.

Доки приведены к массиву: примеры вида s.fields['X']?.value в regress.md и в
спеке заменены на fields.find(f => f.name === 'X').

Проверено: заголовок на списке, форме элемента и всплывающем окне; каскад
разведён по значениям (шапка выигрывает у панели, при пустой шапке — откат);
позитив и негатив всех четырёх ассертов на реальном состоянии формы; полный
регресс 29/29 до и после, file/name/status идентичны.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:51:12 +03:00
Nick ShirokovandClaude Opus 4.8 9b65dccd8a fix(web-test): не отдавать управление, пока список ещё ищет
filterList отчитывался успехом, пока динамический список ещё выполнял поиск:
readTable возвращал предыдущие строки, а следующий клик по строке попадал в
чужой объект. Отличить такой результат от верного нельзя — сценарий проходит
зелёным по не тому документу.

Дыра оказалась не в filterList, а в общем ожидании. waitForStable следил только
за .loadingImage/.waitCurtain/.progressBar и счётчиком полей ввода — ни то, ни
другое не меняется при перерисовке строк списка. Замер на реальной базе: за весь
поиск старый признак isLoading не сработал НИ РАЗУ.

При этом 1С всё это время показывает над списком информбар «Поиск...» — тот же
.stateWindowSupportSurface, который движок уже отдаёт в errors.stateText. Читать
умели, ждать — нет.

waitForStable теперь считает видимый маркер занятости признаком «не готово»:
счётчик стабильности сбрасывается, дедлайн продлевается, пока маркер виден, но
не дольше BUSY_MAX_WAIT (60 с) — реальный поиск на боевом списке идёт десятки
секунд, а зависшая операция всё равно завершает ожидание.

Маркеры сопоставляются ПО ТЕКСТУ (Поиск/Ожид/Searching/Please wait), а не по
факту наличия информбара: тот же носитель несёт терминальные сообщения отчётов
(«Отчет не сформирован», «Не установлено значение параметра»), и ожидание их
исчезновения вешало бы каждый отчёт до таймаута.

Радиус общий, а не точечный в filterList: маркер — индикатор длительной операции
вообще, тот же класс гонки достижим из clickElement и openCommand. Частное
лечение для кнопок (CDP-монитор в click-form) уже есть; второй костыль сделал бы
третий неизбежным. CDP-монитор как гейт не годится отдельно: он считает
готовностью паузу 300 мс без запросов, а при фоновой операции с периодическим
опросом такие паузы штатны.

Проверено на реальной базе: текст информбара — «Поиск...», виден t=6.7..13.2 с,
опрос, идентичный гейтовому, увидел занятость дважды (isLoading — ноль раз);
полный регресс 29/29 до и после, file/name/status идентичны, суммарное время
879.9 → 859.0 с, отчёты не зависли.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 18:24:01 +03:00
Nick ShirokovandClaude Opus 4.8 6256c48c05 docs(web-test): в regress.md оставить поведение, убрать механику резолва
Инструкция навыка описывает использование: конфиг и хуки берутся из корня
сьюта при любом переданном пути, разные сьюты в одном прогоне отвергаются.
Маркеры подъёма и ограничители (.git / .v8-project.json / cwd) — контракт
реализации, их место в спеке; читатель инструкции о них не спросит.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 15:01:45 +03:00
Nick ShirokovandClaude Opus 4.8 90d8263a05 fix(web-test): резолвить корень сьюта подъёмом вверх, а не от переданного пути
Конфиг и хуки резолвились строго от каталога первого позиционного пути, поэтому
запуск подкаталога сьюта был невозможен: `test tests/app/00-smoke/` падал с
«No URL provided and no webtest.config.mjs found» — хотя спека прямо обещает
запуск подкаталога («Фильтр по пути с CLI»).

Опаснее отказа по URL были два молчаливых следствия: при `--url=` прогон
подкаталога терял `_hooks.mjs` и ехал по неподготовленному стенду без единого
предупреждения, а `_allure/` не находился. Плюс `file:` в отчёте считался от
переданного пути, из-за чего один и тот же тест получал разный ID в зависимости
от способа запуска и рвал историю Allure/JUnit.

Введён корень сьюта: подъём от каталога пути до первого `webtest.config.mjs`
ИЛИ `_hooks.mjs` (конфиг необязателен — сьют только с хуками иначе снова терял
бы подготовку), с ограничением подъёма каталогом `.git`/`.v8-project.json`, а
при их отсутствии — cwd. Граница ничего не выбирает, только останавливает, так
что ложная граница даёт «корень не найден», а не чужой корень. От найденного
корня берутся все пять ролей: конфиг, хуки, каталог отчёта, пути в отчёте,
`_allure/`.

Попутно: пути из разных сьютов в одном прогоне теперь отвергаются (раньше
молча выигрывал первый путь, и сьют B ехал по подготовке сьюта A); найденный
корень печатается в шапке; отсутствие корня — предупреждение в stderr;
диагностика говорит про корень сьюта, а не только про URL.

Проверено: 12/12 офлайн-кейсов резолвера; полный регресс 29/29 до и после —
`file`/`name`/`status` идентичны; `_suite-root/nested/` (сценарий, который
падал) проходит с подхваченными конфигом и хуками; `_hang/` 6/6.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-20 14:41:16 +03:00
474 changed files with 28254 additions and 283 deletions
+7 -2
View File
@@ -1,4 +1,4 @@
# cf-edit v1.10 — Edit 1C configuration root (Configuration.xml)
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
@@ -167,6 +167,11 @@ $script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath)
# Версия формата редактируемой конфигурации — создаваемые рядом файлы (Ext/HomePageWorkArea.xml)
# должны нести ту же версию, иначе в проекте окажутся файлы разных версий формата.
$script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version")
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
$script:addCount = 0
$script:removeCount = 0
$script:modifyCount = 0
@@ -864,7 +869,7 @@ function Do-SetHomePage($valArg) {
$hpXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$($script:formatVersion)">
<WorkingAreaTemplate>$tmpl</WorkingAreaTemplate>
$leftXml
$rightXml
+6 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-edit v1.10 — Edit 1C configuration root (Configuration.xml)
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -410,6 +410,10 @@ def main():
tree = etree.parse(resolved_path, xml_parser)
xml_root = tree.getroot()
# Версия формата редактируемой конфигурации — создаваемые рядом файлы (Ext/HomePageWorkArea.xml)
# должны нести ту же версию, иначе в проекте окажутся файлы разных версий формата.
format_version = xml_root.get('version') or '2.17'
add_count = 0
remove_count = 0
modify_count = 0
@@ -959,7 +963,7 @@ def main():
'<HomePageWorkArea xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" '
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">\r\n'
f'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">\r\n'
f'\t<WorkingAreaTemplate>{tmpl}</WorkingAreaTemplate>\r\n'
f'{left_xml}\r\n'
f'{right_xml}\r\n'
+9 -4
View File
@@ -1,4 +1,4 @@
# cf-init v1.2 — Create empty 1C configuration scaffold
# cf-init v1.3 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -7,7 +7,12 @@ param(
[string]$OutputDir = "src",
[string]$Version,
[string]$Vendor,
[string]$CompatibilityMode = "Version8_3_24"
[string]$CompatibilityMode = "Version8_3_24",
# Версия формата выгрузки (MDClasses). Её задаёт ПЛАТФОРМА, которой выгружают, и от режима
# совместимости она не зависит: 8.3.24 пишет 2.17, 8.3.27 — 2.20. Дефолт консервативный —
# 2.17 читается всеми поддерживаемыми платформами.
[ValidateSet("2.17", "2.20", "2.21")]
[string]$FormatVersion = "2.17"
)
$ErrorActionPreference = "Stop"
@@ -73,7 +78,7 @@ $versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version
# --- Configuration.xml ---
$cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
<Configuration uuid="$uuidCfg">
<InternalInfo>
<xr:ContainedObject>
@@ -175,7 +180,7 @@ $cfgXml = @"
# --- Languages/Русский.xml ---
$langXml = @"
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$FormatVersion">
<Language uuid="$uuidLang">
<Properties>
<Name>Русский</Name>
+6 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-init v1.2 — Create empty 1C configuration scaffold
# cf-init v1.3 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C configuration."""
import sys, os, argparse, uuid
@@ -24,6 +24,9 @@ def main():
parser.add_argument('-Version', dest='Version', default='')
parser.add_argument('-Vendor', dest='Vendor', default='')
parser.add_argument('-CompatibilityMode', dest='CompatibilityMode', default='Version8_3_24')
# Версия формата выгрузки (MDClasses) — её задаёт ПЛАТФОРМА, а не режим совместимости:
# 8.3.24 пишет 2.17, 8.3.27 — 2.20. Дефолт консервативный: 2.17 читается всеми платформами.
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17', choices=['2.17', '2.20', '2.21'])
args = parser.parse_args()
name = args.Name
@@ -96,7 +99,7 @@ def main():
\t\t\t</xr:ContainedObject>\n"""
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
\t<Configuration uuid="{uuid_cfg}">
\t\t<InternalInfo>
{contained_objects}\t\t</InternalInfo>
@@ -168,7 +171,7 @@ def main():
# --- Languages/Русский.xml ---
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{args.FormatVersion}">
\t<Language uuid="{uuid_lang}">
\t\t<Properties>
\t\t\t<Name>Русский</Name>
+11 -3
View File
@@ -1,4 +1,4 @@
# form-add v1.10 — Add managed form to 1C config object
# form-add v1.11 — Add managed form to 1C config object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -484,7 +484,10 @@ if (-not $childObjects) {
exit 1
}
# Добавить <Form>$FormName</Form>
# Добавить <Form>$FormName</Form> — идемпотентно (не дублировать уже зарегистрированную)
$alreadyRegistered = [bool]$childObjects.SelectSingleNode("md:Form[text()='$FormName']", $nsMgr)
if (-not $alreadyRegistered) {
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
$formElem.InnerText = $FormName
@@ -538,6 +541,7 @@ if ($insertBefore) {
}
}
}
}
# --- SetDefault ---
@@ -603,7 +607,11 @@ Write-Host " Metadata: $objDirName\$objBaseName\Forms\$FormName.xml"
Write-Host " Form: $objDirName\$objBaseName\Forms\$FormName\Ext\Form.xml"
Write-Host " Module: $objDirName\$objBaseName\Forms\$FormName\Ext\Form\Module.bsl"
Write-Host ""
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
if ($alreadyRegistered) {
Write-Host "Already registered: <Form>$FormName</Form> in ChildObjects (skipped duplicate)"
} else {
Write-Host "Registered: <Form>$FormName</Form> in ChildObjects"
}
if ($defaultUpdated) {
Write-Host "${defaultPropName}: $defaultValue"
}
+43 -37
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# form-add v1.10 — Add managed form to 1C config object
# form-add v1.11 — Add managed form to 1C config object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -592,47 +592,50 @@ def main():
print(f"Не найден элемент ChildObjects в {object_path}", file=sys.stderr)
sys.exit(1)
# Add <Form>$FormName</Form>
form_elem = etree.Element(f"{{{ns}}}Form")
form_elem.text = form_name
# Add <Form>$FormName</Form> — idempotent (do not duplicate already-registered form)
already_registered = child_objects.find(f"md:Form[.='{form_name}']", NSMAP) is not None
# Find first <Template> to insert before it
first_template = child_objects.find("md:Template", NSMAP)
# Find first <TabularSection> to insert before it (if no Template)
first_tabular = child_objects.find("md:TabularSection", NSMAP)
if not already_registered:
form_elem = etree.Element(f"{{{ns}}}Form")
form_elem.text = form_name
# Determine insertion point: before Template, before TabularSection, or at end
insert_before = None
if first_template is not None:
insert_before = first_template
elif first_tabular is not None:
insert_before = first_tabular
# Find first <Template> to insert before it
first_template = child_objects.find("md:Template", NSMAP)
# Find first <TabularSection> to insert before it (if no Template)
first_tabular = child_objects.find("md:TabularSection", NSMAP)
if insert_before is not None:
# Insert before the found element
idx = list(child_objects).index(insert_before)
child_objects.insert(idx, form_elem)
# Whitespace: form_elem gets "\n\t\t\t" as tail (indent before insert_before)
form_elem.tail = "\n\t\t\t"
else:
# Add to end of ChildObjects
children = list(child_objects)
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
# Empty ChildObjects (self-closing)
child_objects.text = "\n\t\t\t"
child_objects.append(form_elem)
form_elem.tail = "\n\t\t"
# Determine insertion point: before Template, before TabularSection, or at end
insert_before = None
if first_template is not None:
insert_before = first_template
elif first_tabular is not None:
insert_before = first_tabular
if insert_before is not None:
# Insert before the found element
idx = list(child_objects).index(insert_before)
child_objects.insert(idx, form_elem)
# Whitespace: form_elem gets "\n\t\t\t" as tail (indent before insert_before)
form_elem.tail = "\n\t\t\t"
else:
if len(children) > 0:
last_child = children[-1]
old_tail = last_child.tail
last_child.tail = "\n\t\t\t"
child_objects.append(form_elem)
form_elem.tail = old_tail if old_tail else "\n\t\t"
else:
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
# Add to end of ChildObjects
children = list(child_objects)
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
# Empty ChildObjects (self-closing)
child_objects.text = "\n\t\t\t"
child_objects.append(form_elem)
form_elem.tail = "\n\t\t"
else:
if len(children) > 0:
last_child = children[-1]
old_tail = last_child.tail
last_child.tail = "\n\t\t\t"
child_objects.append(form_elem)
form_elem.tail = old_tail if old_tail else "\n\t\t"
else:
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
child_objects.append(form_elem)
form_elem.tail = "\n\t\t"
# --- SetDefault ---
@@ -677,7 +680,10 @@ def main():
print(f" Form: {obj_dir_name}\\{obj_base_name}\\Forms\\{form_name}\\Ext\\Form.xml")
print(f" Module: {obj_dir_name}\\{obj_base_name}\\Forms\\{form_name}\\Ext\\Form\\Module.bsl")
print()
print(f"Registered: <Form>{form_name}</Form> in ChildObjects")
if already_registered:
print(f"Already registered: <Form>{form_name}</Form> in ChildObjects (skipped duplicate)")
else:
print(f"Registered: <Form>{form_name}</Form> in ChildObjects")
if default_updated:
print(f"{default_prop_name}: {default_value}")
print()
+2 -2
View File
@@ -550,8 +550,8 @@ PictureField, привязанный к булеву/числу, рисует и
## Workflow
1. **Компиляция**: `/form-compile` генерирует `Form.xml` и автоматически регистрирует `<Form>` в `ChildObjects` родительского объекта (если OutputPath следует конвенции `.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml`).
2. **Метаданные формы** (`ФормаСписка.xml`) и `Module.bsl` создаёт `/form-add`. Если `/form-add` ещё не вызывался — вызови после `/form-compile`. Он не перезаписывает существующий Form.xml.
1. **Каркас**: `/form-add` создаёт метаданные формы (`ФормаСписка.xml`), `Module.bsl` и регистрирует форму у объекта.
2. **Компиляция**: `/form-compile` наполняет `Form.xml` элементами.
3. **Проверка**: `/form-validate`, `/form-info`.
## Верификация
@@ -125,6 +125,7 @@ shorthand — вместо строки задаётся объект:
| `use` | `ForItem` | `ForItem` / `ForFolder` / `ForFolderAndItem` (только Catalog / ChartOfCharacteristicTypes) |
| `attributes` | `[]` | колонки (shorthand или объектная форма реквизита) |
| `lineNumber` | — | кастомизация стандартного реквизита НомерСтроки (см. ниже) |
| `lineNumberLength` | по режиму совместимости | `5``9` — разрядность номера строки: `5` → до 99 999 строк, `9` → до 999 999 999. Требует формата 2.20 (платформа 8.3.27) |
### `lineNumber` — стандартный реквизит НомерСтроки
@@ -15,7 +15,7 @@
| `limitLevelCount` | `false` | bool (ограничивать кол-во уровней) |
| `levelCount` | `2` | число уровней (при `limitLevelCount`) |
| `foldersOnTop` | `true` | bool (группы сверху) |
| `owners` | `[]` | массив ссылок-владельцев: `["CatalogRef.Контрагенты"]` |
| `owners` | `[]` | массив владельцев: `["Catalog.Контрагенты"]` |
| `subordinationUse` | `ToItems` | `ToItems` / `ToFolders` / `ToFoldersAndItems` (кому подчинён) |
| `codeLength` | `9` | длина кода (0 — без кода) |
| `codeType` | `String` | `String` / `Number` |
@@ -1,4 +1,4 @@
# meta-compile v1.66 — Compile 1C metadata object from JSON
# meta-compile v1.68 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -786,11 +786,28 @@ $script:mdRefRoots = @{
'журналдокументов'='DocumentJournal'; 'отчет'='Report'; 'отчёт'='Report'; 'обработка'='DataProcessor';
'табличнаячасть'='TabularSection'; 'реквизит'='Attribute'; 'измерение'='Dimension'; 'ресурс'='Resource';
'стандартныйреквизит'='StandardAttribute'; 'значениеперечисления'='EnumValue'; 'команда'='Command';
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag'
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag';
# Ссылочные формы (тип ссылки вместо объекта метаданных): в MDObjectRef-пути нужен ОБЪЕКТ, т.е.
# "CatalogRef.Валюты" → "Catalog.Валюты". Вид метаданных, оканчивающийся на Ref, не существует,
# поэтому схлопывание однозначно. В ТИПАХ реквизитов запись CatalogRef.X верна — там эта мапа не применяется.
'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
'businessprocessref'='BusinessProcess'; 'taskref'='Task';
'справочникссылка'='Catalog'; 'документссылка'='Document'; 'перечислениессылка'='Enum';
'плансчетовссылка'='ChartOfAccounts'; 'планвидовхарактеристикссылка'='ChartOfCharacteristicTypes';
'планвидоврасчетассылка'='ChartOfCalculationTypes'; 'планвидоврасчётассылка'='ChartOfCalculationTypes';
'планобменассылка'='ExchangePlan'; 'бизнеспроцессссылка'='BusinessProcess'; 'задачассылка'='Task'
}
# $defaultRoot — корень для ГОЛОГО имени без точки (напр. owners: "Валюты" → "Catalog.Валюты").
# Без него голое имя возвращается как есть (прежнее поведение вызывающих без подстановки).
function Normalize-MDObjectRef {
param([string]$ref)
if (-not $ref -or -not $ref.Contains('.')) { return $ref }
param([string]$ref, [string]$defaultRoot)
if (-not $ref) { return $ref }
if (-not $ref.Contains('.')) {
if ($defaultRoot) { return "$defaultRoot.$ref" }
return $ref
}
$parts = $ref -split '\.'
for ($k = 0; $k -lt $parts.Count; $k += 2) {
$t = $script:mdRefRoots[$parts[$k].ToLower()]
@@ -1304,6 +1321,12 @@ function Emit-StandardAttribute {
X "$indent`t<xr:MultiLine>false</xr:MultiLine>"
X "$indent`t<xr:FillFromFillingValue>$ffv</xr:FillFromFillingValue>"
X "$indent`t<xr:CreateOnInput>Auto</xr:CreateOnInput>"
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
if ($script:isFormat220) {
$trm = OvOr 'TypeReductionMode' $(if ($attrName -ceq 'Owner') { 'Deny' } else { 'TransformValues' })
X "$indent`t<xr:TypeReductionMode>$trm</xr:TypeReductionMode>"
}
X "$indent`t<xr:MaxValue xsi:nil=`"true`"/>"
Emit-MLText "$indent`t" "xr:ToolTip" $tt
X "$indent`t<xr:ExtendedEdit>false</xr:ExtendedEdit>"
@@ -1497,7 +1520,7 @@ function Emit-BasedOn {
$arr = @($items | Where-Object { $_ })
if ($arr.Count -eq 0) { X "$indent<BasedOn/>"; return }
X "$indent<BasedOn>"
foreach ($it in $arr) { X "$indent`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml "$it")</xr:Item>" }
foreach ($it in $arr) { X "$indent`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$it"))</xr:Item>" }
X "$indent</BasedOn>"
}
@@ -1932,6 +1955,12 @@ function Emit-Attribute {
X "$indent`t`t<DataHistory>$dh</DataHistory>"
}
}
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
if ($script:isFormat220 -and $elemTag -eq "Dimension" -and $context -eq "register-info") {
$trm = if ($parsed.typeReductionMode) { "$($parsed.typeReductionMode)" } else { "TransformValues" }
X "$indent`t`t<TypeReductionMode>$trm</TypeReductionMode>"
}
X "$indent`t</Properties>"
X "$indent</$elemTag>"
@@ -2003,7 +2032,7 @@ function Emit-Command {
# --- 9. TabularSection emitter ---
function Emit-TabularSection {
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null)
param([string]$indent, [string]$tsName, $columns, [string]$objectType, [string]$objectName, $tsSynonymArg = $null, $tsTooltip = $null, $tsComment = $null, $tsLineNumber = $null, $tsFillChecking = $null, $tsUse = $null, $tsLineNumberLength = $null)
$uuid = New-Guid-String
X "$indent<TabularSection uuid=`"$uuid`">"
@@ -2042,6 +2071,12 @@ function Emit-TabularSection {
$use = if ($tsUse) { "$tsUse" } else { "ForItem" }
X "$indent`t`t<Use>$use</Use>"
}
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
if ($script:isFormat220) {
$lnl = if ($null -ne $tsLineNumberLength -and "$tsLineNumberLength" -ne '') { [int]$tsLineNumberLength } else { $script:lineNumberLengthDefault }
X "$indent`t`t<LineNumberLength>$lnl</LineNumberLength>"
}
X "$indent`t</Properties>"
$tsContext = if ($objectType -in @("DataProcessor","Report")) { "processor-tabular" } else { "tabular" }
@@ -2258,8 +2293,7 @@ function Emit-CatalogProperties {
if ($def.owners -and $def.owners.Count -gt 0) {
X "$i<Owners>"
foreach ($ownerRef in $def.owners) {
$fullRef = if ("$ownerRef" -match '\.') { "$ownerRef" } else { "Catalog.$ownerRef" }
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$fullRef</xr:Item>"
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$ownerRef" 'Catalog'))</xr:Item>"
}
X "$i</Owners>"
} else {
@@ -2410,7 +2444,7 @@ function Emit-DocumentProperties {
}
if ($regRecords.Count -gt 0) {
X "$i<RegisterRecords>"
foreach ($rr in $regRecords) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$rr</xr:Item>" }
foreach ($rr in $regRecords) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$rr"))</xr:Item>" }
X "$i</RegisterRecords>"
} else {
X "$i<RegisterRecords/>"
@@ -3524,7 +3558,7 @@ function Emit-ChartOfCalculationTypesProperties {
$baseTypes = @(); if ($def.baseCalculationTypes) { $baseTypes = @($def.baseCalculationTypes | ForEach-Object { Resolve-TypePrefixSyn "$_" }) }
if ($baseTypes.Count -gt 0) {
X "$i<BaseCalculationTypes>"
foreach ($bt in $baseTypes) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml $bt)</xr:Item>" }
foreach ($bt in $baseTypes) { X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$bt"))</xr:Item>" }
X "$i</BaseCalculationTypes>"
} else { X "$i<BaseCalculationTypes/>" }
$actionPeriodUse = if ($def.actionPeriodUse -eq $true) { "true" } else { "false" }
@@ -3968,7 +4002,51 @@ function Detect-FormatVersion([string]$dir) {
return "2.17"
}
# Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
# (≤Version8_3_26 → 5, ≥Version8_3_27 → 9; платформа фиксирует значение при СОЗДАНии ТЧ).
# NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки) — это
# независимые вещи, читаются из одного файла разными функциями.
# Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала (в отличие от version=
# в первой строке), 2000 байт Detect-FormatVersion сюда не хватает.
function Detect-CompatibilityMode([string]$dir) {
$d = $dir
while ($d) {
$cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) {
# NB: длина файла — в БАЙТАХ, а Substring режет по СИМВОЛАМ (кириллица = 2 байта),
# поэтому ограничиваем по длине уже декодированной строки.
$text = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
$head = $text.Substring(0, [Math]::Min(65536, $text.Length))
if ($head -match '<CompatibilityMode>([^<]+)</CompatibilityMode>') { return $Matches[1].Trim() }
}
$parent = Split-Path $d -Parent
if ($parent -eq $d) { break }
$d = $parent
}
return "Version8_3_24"
}
# Номер версии режима совместимости для сравнений: "Version8_3_27" → 80327, "Version8_5_1" → 80501.
function Get-CompatModeRank([string]$mode) {
if ($mode -match '^Version(\d+)_(\d+)_(\d+)$') {
return [int]$Matches[1] * 10000 + [int]$Matches[2] * 100 + [int]$Matches[3]
}
return 0
}
# Версия формата как число для сравнений: "2.20" → 220, "2.9" → 209.
# Строковое сравнение здесь неверно ("2.9" > "2.17" лексикографически) — известная ловушка.
function Get-FormatRank([string]$ver) {
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
return 0
}
$script:formatVersion = Detect-FormatVersion $OutputDir
$script:compatMode = Detect-CompatibilityMode $OutputDir
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
# --- 15. Main assembler ---
@@ -4048,10 +4126,10 @@ if ($objType -in $typesWithAttrTS) {
# Нормализуем в $tsSections[name] = @{ columns; synonym; tooltip; comment }.
function New-TsEntry { param($val)
if ($val -is [array] -or $val.GetType().Name -eq 'Object[]') {
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null }
return @{ columns = @($val); synonym = $null; tooltip = $null; comment = $null; lineNumber = $null; fillChecking = $null; use = $null; lineNumberLength = $null }
}
$cols = if ($val.attributes) { @($val.attributes) } elseif ($val.columns) { @($val.columns) } else { @() }
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use }
return @{ columns = $cols; synonym = $val.synonym; tooltip = $val.tooltip; comment = if ($val.comment) { "$($val.comment)" } else { $null }; lineNumber = $val.lineNumber; fillChecking = $val.fillChecking; use = $val.use; lineNumberLength = $val.lineNumberLength }
}
if ($def.tabularSections -is [array] -or $def.tabularSections.GetType().Name -eq "Object[]") {
foreach ($ts in $def.tabularSections) { $tsSections[$ts.name] = New-TsEntry $ts }
@@ -4101,7 +4179,7 @@ if ($objType -in $typesWithAttrTS) {
}
foreach ($tsName in $tsSections.Keys) {
$tsE = $tsSections[$tsName]
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use
Emit-TabularSection "`t`t`t" $tsName $tsE.columns $objType $objName $tsE.synonym $tsE.tooltip $tsE.comment $tsE.lineNumber $tsE.fillChecking $tsE.use $tsE.lineNumberLength
}
foreach ($af in $acctFlags) {
Emit-Attribute "`t`t`t" $af "account-flag" "AccountingFlag"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.66 — Compile 1C metadata object from JSON
# meta-compile v1.68 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -822,11 +822,26 @@ md_ref_roots = {
'табличнаячасть': 'TabularSection', 'реквизит': 'Attribute', 'измерение': 'Dimension', 'ресурс': 'Resource',
'стандартныйреквизит': 'StandardAttribute', 'значениеперечисления': 'EnumValue', 'команда': 'Command',
'признакучета': 'AccountingFlag', 'признакучёта': 'AccountingFlag',
# Ссылочные формы (тип ссылки вместо объекта метаданных): в MDObjectRef-пути нужен ОБЪЕКТ, т.е.
# "CatalogRef.Валюты" → "Catalog.Валюты". Вид метаданных, оканчивающийся на Ref, не существует,
# поэтому схлопывание однозначно. В ТИПАХ реквизитов запись CatalogRef.X верна — там мапа не применяется.
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
'справочникссылка': 'Catalog', 'документссылка': 'Document', 'перечислениессылка': 'Enum',
'плансчетовссылка': 'ChartOfAccounts', 'планвидовхарактеристикссылка': 'ChartOfCharacteristicTypes',
'планвидоврасчетассылка': 'ChartOfCalculationTypes', 'планвидоврасчётассылка': 'ChartOfCalculationTypes',
'планобменассылка': 'ExchangePlan', 'бизнеспроцессссылка': 'BusinessProcess', 'задачассылка': 'Task',
}
def normalize_md_object_ref(ref):
if not ref or '.' not in ref:
def normalize_md_object_ref(ref, default_root=None):
"""default_root — корень для ГОЛОГО имени без точки (owners: "Валюты""Catalog.Валюты").
Без него голое имя возвращается как есть (прежнее поведение вызывающих без подстановки)."""
if not ref:
return ref
if '.' not in ref:
return f'{default_root}.{ref}' if default_root else ref
parts = ref.split('.')
for k in range(0, len(parts), 2):
t = md_ref_roots.get(parts[k].lower())
@@ -1325,6 +1340,11 @@ def emit_standard_attribute(indent, attr_name, ov=None):
X(f'{indent}\t<xr:MultiLine>false</xr:MultiLine>')
X(f'{indent}\t<xr:FillFromFillingValue>{ffv}</xr:FillFromFillingValue>')
X(f'{indent}\t<xr:CreateOnInput>Auto</xr:CreateOnInput>')
# Формат 2.20 (8.3.27): режим приведения типов. Платформа пишет его КАЖДОМУ стандартному
# реквизиту; значение всегда TransformValues, кроме владельца (Owner) — там Deny.
if is_format_220:
trm = ov.get('TypeReductionMode', 'Deny' if attr_name == 'Owner' else 'TransformValues')
X(f'{indent}\t<xr:TypeReductionMode>{trm}</xr:TypeReductionMode>')
X(f'{indent}\t<xr:MaxValue xsi:nil="true"/>')
emit_mltext(f'{indent}\t', 'xr:ToolTip', tt)
X(f'{indent}\t<xr:ExtendedEdit>false</xr:ExtendedEdit>')
@@ -1587,7 +1607,7 @@ def emit_based_on(indent, items):
return
X(f'{indent}<BasedOn>')
for it in arr:
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(str(it))}</xr:Item>')
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(it)))}</xr:Item>')
X(f'{indent}</BasedOn>')
# --- Параметры/связи выбора (порт из form-compile) ---
@@ -1997,6 +2017,10 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
# DataHistory — not for Chart* types and non-InformationRegister register family
if context not in ('chart', 'register-other', 'register-accum', 'register-calc', 'register-account'):
X(f'{indent}\t\t<DataHistory>{parsed.get("dataHistory") or "Use"}</DataHistory>')
# Формат 2.20 (8.3.27): режим приведения типов — последним в Properties и ТОЛЬКО у измерений
# регистра сведений (у реквизитов/ресурсов и у прочих семейств регистров платформа его не пишет).
if is_format_220 and elem_tag == 'Dimension' and context == 'register-info':
X(f'{indent}\t\t<TypeReductionMode>{parsed.get("typeReductionMode") or "TransformValues"}</TypeReductionMode>')
X(f'{indent}\t</Properties>')
X(f'{indent}</{elem_tag}>')
@@ -2073,7 +2097,7 @@ def emit_command(indent, cmd_name, cmd):
X(f'{indent}\t</Properties>')
X(f'{indent}</Command>')
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None):
def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_synonym_arg=None, ts_tooltip=None, ts_comment=None, ts_line_number=None, ts_fill_checking=None, ts_use=None, ts_line_number_length=None):
uid = new_uuid()
X(f'{indent}<TabularSection uuid="{uid}">')
type_prefix = f'{object_type}TabularSection'
@@ -2104,6 +2128,11 @@ def emit_tabular_section(indent, ts_name, columns, object_type, object_name, ts_
emit_tabular_standard_attributes(f'{indent}\t\t', ts_line_number)
if object_type in ('Catalog', 'ChartOfCharacteristicTypes'):
X(f'{indent}\t\t<Use>{ts_use if ts_use else "ForItem"}</Use>')
# Формат 2.20 (8.3.27): длина номера строки ТЧ (5..9 → до 999 999 999 строк вместо 99 999).
# Последним в Properties. Дефолт платформа берёт из режима совместимости на момент создания ТЧ.
if is_format_220:
lnl = int(ts_line_number_length) if ts_line_number_length not in (None, '') else line_number_length_default
X(f'{indent}\t\t<LineNumberLength>{lnl}</LineNumberLength>')
X(f'{indent}\t</Properties>')
ts_context = 'processor-tabular' if object_type in ('DataProcessor', 'Report') else 'tabular'
X(f'{indent}\t<ChildObjects>')
@@ -2288,8 +2317,7 @@ def emit_catalog_properties(indent):
if owners:
X(f'{i}<Owners>')
for owner_ref in owners:
full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}'
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{full_ref}</xr:Item>')
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(owner_ref), "Catalog"))}</xr:Item>')
X(f'{i}</Owners>')
else:
X(f'{i}<Owners/>')
@@ -2429,7 +2457,7 @@ def emit_document_properties(indent):
if reg_records:
X(f'{i}<RegisterRecords>')
for rr in reg_records:
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{rr}</xr:Item>')
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(rr)))}</xr:Item>')
X(f'{i}</RegisterRecords>')
else:
X(f'{i}<RegisterRecords/>')
@@ -3483,7 +3511,7 @@ def emit_chart_of_calculation_types_properties(indent):
if base_types:
X(f'{i}<BaseCalculationTypes>')
for bt in base_types:
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(bt)}</xr:Item>')
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(bt))}</xr:Item>')
X(f'{i}</BaseCalculationTypes>')
else:
X(f'{i}<BaseCalculationTypes/>')
@@ -3873,7 +3901,44 @@ def detect_format_version(d):
d = parent
return "2.17"
def detect_compatibility_mode(d):
"""Режим совместимости конфигурации — из него выводится дефолт <LineNumberLength> табличной части
(<=Version8_3_26 5, >=Version8_3_27 9; платформа фиксирует значение при СОЗДАНИИ ТЧ).
NB: версия ФОРМАТА от режима совместимости не зависит (её задаёт платформа выгрузки).
Читаем префикс побольше: <CompatibilityMode> лежит ~11-12 КБ от начала."""
while d:
cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f:
head = f.read(65536)
m = re.search(r'<CompatibilityMode>([^<]+)</CompatibilityMode>', head)
if m:
return m.group(1).strip()
parent = os.path.dirname(d)
if parent == d:
break
d = parent
return "Version8_3_24"
def compat_mode_rank(mode):
""""Version8_3_27" → 80327, "Version8_5_1" → 80501."""
m = re.match(r'^Version(\d+)_(\d+)_(\d+)$', mode or '')
return int(m.group(1)) * 10000 + int(m.group(2)) * 100 + int(m.group(3)) if m else 0
def format_rank(ver):
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
format_version = detect_format_version(output_dir)
compat_mode = detect_compatibility_mode(output_dir)
# Формат 2.20+ (платформа 8.3.27) — только тогда эмитим новые свойства.
is_format_220 = format_rank(format_version) >= 220
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
# ---------------------------------------------------------------------------
# 15. Main assembler
@@ -3966,10 +4031,10 @@ if obj_type in types_with_attr_ts:
# Значение ТЧ: массив колонок (синоним авто) ЛИБО объект {attributes/columns, synonym, tooltip, comment}.
def new_ts_entry(val):
if isinstance(val, list):
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None}
return {'columns': val, 'synonym': None, 'tooltip': None, 'comment': None, 'lineNumber': None, 'fillChecking': None, 'use': None, 'lineNumberLength': None}
cols = _as_list(val.get('attributes') or val.get('columns') or [])
return {'columns': cols, 'synonym': val.get('synonym'), 'tooltip': val.get('tooltip'),
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use')}
'comment': str(val['comment']) if val.get('comment') else None, 'lineNumber': val.get('lineNumber'), 'fillChecking': val.get('fillChecking'), 'use': val.get('use'), 'lineNumberLength': val.get('lineNumberLength')}
if isinstance(ts_data, list):
for ts in ts_data:
ts_sections[ts['name']] = new_ts_entry(ts)
@@ -4021,7 +4086,7 @@ if obj_type in types_with_attr_ts:
emit_attribute('\t\t\t', a, context)
for ts_name in ts_order:
e = ts_sections[ts_name]
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'))
emit_tabular_section('\t\t\t', ts_name, e['columns'], obj_type, obj_name, e['synonym'], e['tooltip'], e['comment'], e.get('lineNumber'), e.get('fillChecking'), e.get('use'), e.get('lineNumberLength'))
for af in acct_flags:
emit_attribute('\t\t\t', af, 'account-flag', 'AccountingFlag')
for edf in ext_dim_flags:
@@ -1,4 +1,4 @@
# meta-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.55 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
@@ -323,6 +323,9 @@ function Attr-ToDsl {
$v = & $en 'MainFilter'; if ($v -eq 'true') { $extra['mainFilter'] = $true }
$v = & $en 'DenyIncompleteValues'; if ($v -eq 'true') { $extra['denyIncompleteValues'] = $true }
$v = & $en 'UseInTotals'; if ($v -eq 'false') { $extra['useInTotals'] = $false } # дефолт true → захват при false
# Формат 2.20: режим приведения типов измерения РС. Дефолт TransformValues (его компилятор
# эмитит сам) → захватываем только отклонение.
$v = & $en 'TypeReductionMode'; if ($v -and $v -ne 'TransformValues') { $extra['typeReductionMode'] = $v }
$v = & $en 'BaseDimension'; if ($v -eq 'true') { $extra['baseDimension'] = $true }
$v = & $en 'ScheduleLink'; if ($v) { $extra['scheduleLink'] = $v } # ссылка на измерение графика (пустой → пропуск)
$v = & $en 'Balance'; if ($v -eq 'true') { $extra['balance'] = $true }
@@ -1092,6 +1095,13 @@ if ($saNode) {
$ov['linkByType'] = [ordered]@{ dataPath = $saLbtDp.InnerText; linkItem = $li }
}
}
# Формат 2.20: режим приведения типов. Компилятор выводит его сам (TransformValues, у Owner —
# Deny), поэтому захватываем только отклонение от этого правила.
$saTrmN = $sa.SelectSingleNode('xr:TypeReductionMode', $nsm)
if ($saTrmN -and $saTrmN.InnerText) {
$saTrmDef = if ($an -ceq 'Owner') { 'Deny' } else { 'TransformValues' }
if ($saTrmN.InnerText -ne $saTrmDef) { $ov['TypeReductionMode'] = $saTrmN.InnerText }
}
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
if ($ov.Count -gt 0 -or ($stdFixed -notcontains $an)) { $saMap[$an] = $ov }
}
@@ -1262,13 +1272,20 @@ if ($childObjs) {
if ($lnFvT -match 'decimal$') { $lnObj['fillValue'] = if ($lnFvN.InnerText -match '^-?\d+$') { [long]$lnFvN.InnerText } else { [double]$lnFvN.InnerText } }
}
}
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $tsFc -or $tsUse -or $lnObj.Count -gt 0 -or (-not $hasBlock)) {
# Формат 2.20: длина номера строки ТЧ. Захватываем ВСЕГДА при наличии тега, а не
# omit-on-default: дефолт зависит от режима совместимости конфигурации (≤8_3_26 → 5,
# ≥8_3_27 → 9) и фиксируется платформой при создании ТЧ, так что вывести его здесь
# значило бы продублировать логику компилятора с риском разойтись. Явный захват точен.
$tsLnlN = $tsp.SelectSingleNode('md:LineNumberLength', $nsm)
$tsLnl = if ($tsLnlN -and $tsLnlN.InnerText) { [int]$tsLnlN.InnerText } else { $null }
if ($tsSynCustom -or ($null -ne $tsTt) -or $tsCmt -or $tsFc -or $tsUse -or $lnObj.Count -gt 0 -or (-not $hasBlock) -or ($null -ne $tsLnl)) {
$to = [ordered]@{}
if ($tsSynCustom) { $to['synonym'] = $tsSyn }
if ($null -ne $tsTt) { $to['tooltip'] = $tsTt }
if ($tsCmt) { $to['comment'] = $tsCmt }
if ($tsFc) { $to['fillChecking'] = $tsFc }
if ($tsUse) { $to['use'] = $tsUse }
if ($null -ne $tsLnl) { $to['lineNumberLength'] = $tsLnl }
if (-not $hasBlock) { $to['lineNumber'] = '' } elseif ($lnObj.Count -gt 0) { $to['lineNumber'] = $lnObj }
$to['attributes'] = $cols
$tsMap[$tsName] = $to
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-decompile v0.54 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.55 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
@@ -456,6 +456,11 @@ def attr_to_dsl(attr_node):
v = en('UseInTotals')
if v == 'false':
extra['useInTotals'] = False # дефолт true → захват при false
# Формат 2.20: режим приведения типов измерения РС. Дефолт TransformValues (его компилятор
# эмитит сам) → захватываем только отклонение.
v = en('TypeReductionMode')
if v and v != 'TransformValues':
extra['typeReductionMode'] = v
v = en('BaseDimension')
if v == 'true':
extra['baseDimension'] = True
@@ -1587,6 +1592,13 @@ def build_dsl():
li = int(_text(sa_lbt_li)) if (sa_lbt_li is not None and _text(sa_lbt_li)) else 0
ov['linkByType'] = {'dataPath': _text(sa_lbt_dp), 'linkItem': li}
# Доп./опциональный реквизит (не в фикс-списке) — эмитим по присутствию даже без отклонений.
# Формат 2.20: режим приведения типов. Компилятор выводит его сам (TransformValues,
# у Owner — Deny), поэтому захватываем только отклонение от этого правила.
sa_trm_n = _single(sa, 'xr:TypeReductionMode')
if sa_trm_n is not None and (sa_trm_n.text or '').strip():
sa_trm_def = 'Deny' if an == 'Owner' else 'TransformValues'
if sa_trm_n.text.strip() != sa_trm_def:
ov['TypeReductionMode'] = sa_trm_n.text.strip()
if len(ov) > 0 or (an not in std_fixed):
sa_map[an] = ov
if len(sa_map) > 0 or (obj_type in std_conditional_types):
@@ -1782,7 +1794,13 @@ def build_dsl():
ln_fv_t = _attr(ln_fv_n, 'type', NS_XSI)
if re.search(r'decimal$', ln_fv_t, re.I):
ln_obj['fillValue'] = int(_text(ln_fv_n)) if re.match(r'^-?\d+$', _text(ln_fv_n)) else float(_text(ln_fv_n))
if ts_syn_custom or (ts_tt is not None) or ts_cmt or ts_fc or ts_use or len(ln_obj) > 0 or (not has_block):
# Формат 2.20: длина номера строки ТЧ. Захватываем ВСЕГДА при наличии тега, а не
# omit-on-default: дефолт зависит от режима совместимости конфигурации (<=8_3_26 → 5,
# >=8_3_27 → 9) и фиксируется платформой при создании ТЧ, так что вывести его здесь
# значило бы продублировать логику компилятора с риском разойтись. Явный захват точен.
ts_lnl_n = _single(tsp, 'md:LineNumberLength')
ts_lnl = int(ts_lnl_n.text) if ts_lnl_n is not None and (ts_lnl_n.text or '').strip() else None
if ts_syn_custom or (ts_tt is not None) or ts_cmt or ts_fc or ts_use or len(ln_obj) > 0 or (not has_block) or (ts_lnl is not None):
to = {}
if ts_syn_custom:
to['synonym'] = ts_syn
@@ -1794,6 +1812,8 @@ def build_dsl():
to['fillChecking'] = ts_fc
if ts_use:
to['use'] = ts_use
if ts_lnl is not None:
to['lineNumberLength'] = ts_lnl
if not has_block:
to['lineNumber'] = ''
elif len(ln_obj) > 0:
+52 -6
View File
@@ -1,4 +1,4 @@
# meta-edit v1.22 — Edit existing 1C metadata object XML
# meta-edit v1.23 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -1313,7 +1313,7 @@ function Build-ColumnFragment {
if ($references.Count -gt 0) {
$sb.AppendLine("$indent`t`t<References>") | Out-Null
foreach ($ref in $references) {
$sb.AppendLine("$indent`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$ref</xr:Item>") | Out-Null
$sb.AppendLine("$indent`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-Xml (Normalize-MDObjectRef "$ref"))</xr:Item>") | Out-Null
}
$sb.AppendLine("$indent`t`t</References>") | Out-Null
} else {
@@ -2416,13 +2416,56 @@ function Process-Modify($modifyDef) {
# Section 12.5: Complex property helpers
# ============================================================
# Прощающий ввод MDObjectRef-путей: русские корни метаданных → английские + ссылочные формы
# ("CatalogRef.Валюты"/"СправочникСсылка.Валюты" → "Catalog.Валюты"). MDObjectRef ссылается на ОБЪЕКТ
# метаданных, а не на тип ссылки; вида метаданных, оканчивающегося на Ref, не существует → схлопывание
# однозначно. Виды стоят на ЧЁТНЫХ позициях (0,2,4…), имена (нечётные) не трогаем. Канонические
# английские пути неизменны (в мапе только неканонические ключи). Зеркало meta-compile.
$script:mdRefRoots = @{
'справочник'='Catalog'; 'документ'='Document'; 'перечисление'='Enum'; 'константа'='Constant';
'регистрсведений'='InformationRegister'; 'регистрнакопления'='AccumulationRegister';
'регистрбухгалтерии'='AccountingRegister'; 'регистррасчета'='CalculationRegister'; 'регистррасчёта'='CalculationRegister';
'плансчетов'='ChartOfAccounts'; 'планвидовхарактеристик'='ChartOfCharacteristicTypes';
'планвидоврасчета'='ChartOfCalculationTypes'; 'планвидоврасчёта'='ChartOfCalculationTypes';
'планобмена'='ExchangePlan'; 'бизнеспроцесс'='BusinessProcess'; 'задача'='Task';
'журналдокументов'='DocumentJournal'; 'отчет'='Report'; 'отчёт'='Report'; 'обработка'='DataProcessor';
'табличнаячасть'='TabularSection'; 'реквизит'='Attribute'; 'измерение'='Dimension'; 'ресурс'='Resource';
'стандартныйреквизит'='StandardAttribute'; 'значениеперечисления'='EnumValue'; 'команда'='Command';
'признакучета'='AccountingFlag'; 'признакучёта'='AccountingFlag';
'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
'businessprocessref'='BusinessProcess'; 'taskref'='Task';
'справочникссылка'='Catalog'; 'документссылка'='Document'; 'перечислениессылка'='Enum';
'плансчетовссылка'='ChartOfAccounts'; 'планвидовхарактеристикссылка'='ChartOfCharacteristicTypes';
'планвидоврасчетассылка'='ChartOfCalculationTypes'; 'планвидоврасчётассылка'='ChartOfCalculationTypes';
'планобменассылка'='ExchangePlan'; 'бизнеспроцессссылка'='BusinessProcess'; 'задачассылка'='Task'
}
# $defaultRoot — корень для ГОЛОГО имени без точки (owners: "Валюты" → "Catalog.Валюты").
function Normalize-MDObjectRef {
param([string]$ref, [string]$defaultRoot)
if (-not $ref) { return $ref }
if (-not $ref.Contains('.')) {
if ($defaultRoot) { return "$defaultRoot.$ref" }
return $ref
}
$parts = $ref -split '\.'
for ($k = 0; $k -lt $parts.Count; $k += 2) {
$t = $script:mdRefRoots[$parts[$k].ToLower()]
if ($t) { $parts[$k] = $t }
}
return ($parts -join '.')
}
# mdref — значения списка суть MDObjectRef-пути → прогоняем через Normalize-MDObjectRef.
# root — корень для голого имени без точки.
$script:complexPropertyMap = @{
"Owners" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"RegisterRecords" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"BasedOn" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"Owners" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true; root = 'Catalog' }
"RegisterRecords" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
"BasedOn" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
"InputByString" = @{ tag = "xr:Field"; attr = $null }
"DataLockFields" = @{ tag = "xr:Field"; attr = $null; expand = $true }
"RegisteredDocuments" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"' }
"RegisteredDocuments" = @{ tag = "xr:Item"; attr = 'xsi:type="xr:MDObjectRef"'; mdref = $true }
}
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
@@ -2873,6 +2916,7 @@ function Add-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
$mapEntry = $script:complexPropertyMap[$propertyName]
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
if ($mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
if ($mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
@@ -2922,6 +2966,7 @@ function Add-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
function Remove-ComplexPropertyItem([string]$propertyName, [string[]]$values) {
$mapEntry = $script:complexPropertyMap[$propertyName]
if ($mapEntry -and $mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
if ($mapEntry -and $mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
Warn "Property element '$propertyName' not found in Properties"
@@ -2960,6 +3005,7 @@ function Set-ComplexProperty([string]$propertyName, [string[]]$values) {
$mapEntry = $script:complexPropertyMap[$propertyName]
if (-not $mapEntry) { Warn "Unknown complex property: $propertyName"; return }
if ($mapEntry.expand) { $values = @($values | ForEach-Object { Expand-DataPath "$_" }) }
if ($mapEntry.mdref) { $values = @($values | ForEach-Object { Normalize-MDObjectRef "$_" $mapEntry.root }) }
$propEl = Find-PropertyElement $propertyName
if (-not $propEl) {
+55 -6
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-edit v1.22 — Edit existing 1C metadata object XML
# meta-edit v1.23 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1288,7 +1288,7 @@ def build_column_fragment(col_def, indent):
if references:
lines.append(f"{indent}\t\t<References>")
for ref in references:
lines.append(f'{indent}\t\t\t<xr:Item xsi:type="xr:MDObjectRef">{ref}</xr:Item>')
lines.append(f'{indent}\t\t\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml(normalize_md_object_ref(str(ref)))}</xr:Item>')
lines.append(f"{indent}\t\t</References>")
else:
lines.append(f"{indent}\t\t<References/>")
@@ -2235,13 +2235,56 @@ def process_modify(modify_def):
# Complex property helpers
# ============================================================
# Прощающий ввод MDObjectRef-путей: русские корни метаданных → английские + ссылочные формы
# ("CatalogRef.Валюты"/"СправочникСсылка.Валюты" → "Catalog.Валюты"). MDObjectRef ссылается на ОБЪЕКТ
# метаданных, а не на тип ссылки; вида метаданных, оканчивающегося на Ref, не существует → схлопывание
# однозначно. Виды на ЧЁТНЫХ позициях (0,2,4…), имена (нечётные) не трогаем. Канонические английские
# пути неизменны. Зеркало meta-compile.
md_ref_roots = {
'справочник': 'Catalog', 'документ': 'Document', 'перечисление': 'Enum', 'константа': 'Constant',
'регистрсведений': 'InformationRegister', 'регистрнакопления': 'AccumulationRegister',
'регистрбухгалтерии': 'AccountingRegister', 'регистррасчета': 'CalculationRegister', 'регистррасчёта': 'CalculationRegister',
'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
'журналдокументов': 'DocumentJournal', 'отчет': 'Report', 'отчёт': 'Report', 'обработка': 'DataProcessor',
'табличнаячасть': 'TabularSection', 'реквизит': 'Attribute', 'измерение': 'Dimension', 'ресурс': 'Resource',
'стандартныйреквизит': 'StandardAttribute', 'значениеперечисления': 'EnumValue', 'команда': 'Command',
'признакучета': 'AccountingFlag', 'признакучёта': 'AccountingFlag',
'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
'справочникссылка': 'Catalog', 'документссылка': 'Document', 'перечислениессылка': 'Enum',
'плансчетовссылка': 'ChartOfAccounts', 'планвидовхарактеристикссылка': 'ChartOfCharacteristicTypes',
'планвидоврасчетассылка': 'ChartOfCalculationTypes', 'планвидоврасчётассылка': 'ChartOfCalculationTypes',
'планобменассылка': 'ExchangePlan', 'бизнеспроцессссылка': 'BusinessProcess', 'задачассылка': 'Task',
}
def normalize_md_object_ref(ref, default_root=None):
"""default_root — корень для ГОЛОГО имени без точки (owners: "Валюты""Catalog.Валюты")."""
if not ref:
return ref
if '.' not in ref:
return f'{default_root}.{ref}' if default_root else ref
parts = ref.split('.')
for k in range(0, len(parts), 2):
t = md_ref_roots.get(parts[k].lower())
if t:
parts[k] = t
return '.'.join(parts)
# mdref — значения списка суть MDObjectRef-пути → прогоняем через normalize_md_object_ref.
# root — корень для голого имени без точки.
complex_property_map = {
"Owners": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
"RegisterRecords": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
"BasedOn": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
"Owners": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True, "root": "Catalog"},
"RegisterRecords": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
"BasedOn": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
"InputByString": {"tag": "xr:Field", "attr": None},
"DataLockFields": {"tag": "xr:Field", "attr": None, "expand": True},
"RegisteredDocuments": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"'},
"RegisteredDocuments": {"tag": "xr:Item", "attr": 'xsi:type="xr:MDObjectRef"', "mdref": True},
}
# Известные свойства объекта (union по корпусу acc+erp 8.3.24) — allowlist для modify-property.
@@ -2777,6 +2820,8 @@ def add_complex_property_item(property_name, values):
return
if map_entry.get("expand"):
values = [expand_data_path(str(v)) for v in values]
if map_entry.get("mdref"):
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
prop_el = find_property_element(property_name)
if prop_el is None:
@@ -2819,6 +2864,8 @@ def remove_complex_property_item(property_name, values):
map_entry = complex_property_map.get(property_name)
if map_entry and map_entry.get("expand"):
values = [expand_data_path(str(v)) for v in values]
if map_entry and map_entry.get("mdref"):
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
prop_el = find_property_element(property_name)
if prop_el is None:
warn(f"Property element '{property_name}' not found in Properties")
@@ -2851,6 +2898,8 @@ def set_complex_property(property_name, values):
return
if map_entry.get("expand"):
values = [expand_data_path(str(v)) for v in values]
if map_entry.get("mdref"):
values = [normalize_md_object_ref(str(v), map_entry.get("root")) for v in values]
prop_el = find_property_element(property_name)
if prop_el is None:
@@ -1,4 +1,4 @@
# meta-validate v1.10 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
# meta-validate v1.12 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -1493,6 +1493,71 @@ if ($script:configDir) {
}
}
# --- Check 18: свойства, появившиеся в новых версиях формата ---
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
# рапортует успех, а свойство теряется); (2) подсказать, что конструкция требует более нового
# формата. Расширяется одной строкой на свойство — задел под 2.21 (8.5) и последующие.
$versionedProps = @{
"TypeReductionMode" = "2.20" # режим приведения типов (стандартные реквизиты, измерения РС)
"LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9)
}
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
function Get-FormatRank([string]$v) {
if ($v -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
return 0
}
$fileRank = Get-FormatRank $version
if ($fileRank -gt 0) {
foreach ($vp in ($versionedProps.Keys | Sort-Object)) {
$nodes = $xmlDoc.SelectNodes("//md:$vp | //xr:$vp", $ns)
if ($nodes -and $nodes.Count -gt 0 -and $fileRank -lt (Get-FormatRank $versionedProps[$vp])) {
Report-Error "18. <$vp> появился в формате $($versionedProps[$vp]), а файл объявлен как $version — на платформе этой версии свойство будет отброшено при загрузке"
}
}
}
# --- Check 19: LineNumberLength — допустимый диапазон 5..9 ---
# Длина номера строки ТЧ: 5 (до 99 999 строк) … 9 (до 999 999 999). Границы — из документации 1С.
foreach ($lnl in @($xmlDoc.SelectNodes("//md:LineNumberLength", $ns))) {
$raw = $lnl.InnerText.Trim()
if ($raw -notmatch '^\d+$') {
Report-Error "19. LineNumberLength='$raw' — должно быть целое число 5..9"
} elseif ([int]$raw -lt 5 -or [int]$raw -gt 9) {
Report-Error "19. LineNumberLength=$raw вне допустимого диапазона 5..9"
}
}
# --- Check 17: MDObjectRef form — ссылка должна указывать на ОБЪЕКТ метаданных, а не на тип ссылки ---
# Owners/BasedOn/RegisterRecords/RegisteredDocuments/References содержат путь вида "Catalog.Валюты".
# "CatalogRef.Валюты" — частая ошибка (тип ссылки вместо объекта): платформа отвечает
# «Неизвестный объект метаданных». Вида метаданных, оканчивающегося на Ref, не существует → ERROR.
# Неизвестный первый сегмент без Ref — только WARN (список видов может быть неполон).
$mdRefNodes = $xmlDoc.SelectNodes("//*[@xsi:type='xr:MDObjectRef']", $ns)
if ($mdRefNodes -and $mdRefNodes.Count -gt 0) {
$knownRoots = @($validTypes) + @($structuralOnlyTypes)
$badRefForm = @{} # значение -> $true (ссылочная форма, гарантированно нерабочая)
$unknownRoot = @{} # значение -> корень
foreach ($rn in $mdRefNodes) {
$rv = $rn.InnerText.Trim()
if (-not $rv) { continue }
$root = $rv.Split('.')[0]
if ($knownRoots -ccontains $root) { continue }
if ($root -cmatch 'Ref$') { $badRefForm[$rv] = $true } else { $unknownRoot[$rv] = $root }
}
foreach ($bk in ($badRefForm.Keys | Sort-Object)) {
$fixed = $bk -replace '^([A-Za-z]+)Ref\.', '$1.'
Report-Error "17. MDObjectRef '$bk' — ссылка на ТИП, а не на объект метаданных; нужно '$fixed' (иначе «Неизвестный объект метаданных» при загрузке)"
}
foreach ($uk in ($unknownRoot.Keys | Sort-Object)) {
Report-Warn "17. MDObjectRef '$uk' — неизвестный вид метаданных '$($unknownRoot[$uk])' (опечатка?)"
}
if ($badRefForm.Count -eq 0 -and $unknownRoot.Count -eq 0) {
Report-OK "17. MDObjectRef form: $($mdRefNodes.Count) checked"
}
}
# --- Final output ---
& $finalize
@@ -1,4 +1,4 @@
# meta-validate v1.10 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
# meta-validate v1.12 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -1395,6 +1395,69 @@ if config_dir:
elif checked_refs:
report_ok(f"16. Reference types: {len(checked_refs)} resolved")
# ── Check 18: свойства, появившиеся в новых версиях формата ──
# Реестр «тег → минимальная версия формата». Служит двум целям: (1) поймать свойство в файле со
# слишком старым штампом — при сборке на старой платформе оно будет молча отброшено (платформа
# рапортует успех, а свойство теряется); (2) подсказать, что конструкция требует более нового
# формата. Расширяется одной строкой на свойство — задел под 2.21 (8.5) и последующие.
versioned_props = {
"TypeReductionMode": "2.20", # режим приведения типов (стандартные реквизиты, измерения РС)
"LineNumberLength": "2.20", # длина номера строки ТЧ (5..9)
}
def format_rank(v):
""""2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17")."""
m = re.match(r'^(\d+)\.(\d+)$', v or '')
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
file_rank = format_rank(version)
if file_rank > 0:
for vp in sorted(versioned_props):
nodes = find_all(root, f"//md:{vp} | //xr:{vp}")
if nodes and file_rank < format_rank(versioned_props[vp]):
report_error(f"18. <{vp}> появился в формате {versioned_props[vp]}, а файл объявлен как {version} — на платформе этой версии свойство будет отброшено при загрузке")
# ── Check 19: LineNumberLength — допустимый диапазон 5..9 ──
# Длина номера строки ТЧ: 5 (до 99 999 строк) … 9 (до 999 999 999). Границы — из документации 1С.
for lnl in find_all(root, "//md:LineNumberLength"):
raw = inner_text(lnl).strip()
if not re.match(r'^\d+$', raw):
report_error(f"19. LineNumberLength='{raw}' — должно быть целое число 5..9")
elif int(raw) < 5 or int(raw) > 9:
report_error(f"19. LineNumberLength={raw} вне допустимого диапазона 5..9")
# ── Check 17: MDObjectRef form — ссылка на ОБЪЕКТ метаданных, а не на тип ссылки ──
# Owners/BasedOn/RegisterRecords/RegisteredDocuments/References содержат путь вида "Catalog.Валюты".
# "CatalogRef.Валюты" — частая ошибка (тип ссылки вместо объекта): платформа отвечает
# «Неизвестный объект метаданных». Вида метаданных, оканчивающегося на Ref, не существует → ERROR.
# Неизвестный первый сегмент без Ref — только WARN (список видов может быть неполон).
md_ref_nodes = find_all(root, "//*[@xsi:type='xr:MDObjectRef']")
if md_ref_nodes:
known_roots = tuple(valid_types) + tuple(structural_only_types)
bad_ref_form = {} # значение -> True (ссылочная форма, гарантированно нерабочая)
unknown_root = {} # значение -> корень
for rn in md_ref_nodes:
rv = inner_text(rn).strip()
if not rv:
continue
rroot = rv.split('.')[0]
if rroot in known_roots:
continue
if rroot.endswith('Ref'):
bad_ref_form[rv] = True
else:
unknown_root[rv] = rroot
for bk in sorted(bad_ref_form):
fixed = re.sub(r'^([A-Za-z]+)Ref\.', r'\1.', bk)
report_error(f"17. MDObjectRef '{bk}' — ссылка на ТИП, а не на объект метаданных; нужно '{fixed}' (иначе «Неизвестный объект метаданных» при загрузке)")
for uk in sorted(unknown_root):
report_warn(f"17. MDObjectRef '{uk}' — неизвестный вид метаданных '{unknown_root[uk]}' (опечатка?)")
if not bad_ref_form and not unknown_root:
report_ok(f"17. MDObjectRef form: {len(md_ref_nodes)} checked")
# ── Final output ──────────────────────────────────────────────
finalize()
@@ -1,4 +1,4 @@
# skd-compile v1.108 — Compile 1C DCS from JSON
# skd-compile v1.109 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -3202,12 +3202,15 @@ function Emit-TableAxisBlock {
if ($block.filter) {
Emit-Filter -items $block.filter -indent $indent
}
if ($block.order) {
Emit-Order -items $block.order -indent $indent
}
if ($block.selection) {
Emit-Selection -items $block.selection -indent $indent
}
# Платформа на осях (column/row/point/series) всегда пишет order+selection; при отсутствии
# ключа кладёт Auto (как ручное добавление оси в конфигураторе). Ключ присутствует (в т.ч.
# пустой [] ) — уважаем как задано.
$hasOrderKey = $block.PSObject.Properties.Match('order').Count -gt 0
$orderItems = if ($hasOrderKey) { $block.order } else { @('Auto') }
Emit-Order -items $orderItems -indent $indent
$hasSelKey = $block.PSObject.Properties.Match('selection').Count -gt 0
$selItems = if ($hasSelKey) { $block.selection } else { @('Auto') }
Emit-Selection -items $selItems -indent $indent
if ($block.conditionalAppearance) {
Emit-ConditionalAppearance -items $block.conditionalAppearance -indent $indent
}
@@ -3262,13 +3265,15 @@ function Emit-StructureItem {
$gb = if ($item.groupBy) { $item.groupBy } else { $item.groupFields }
Emit-GroupItems -groupBy $gb -indent "$indent`t"
# Emit order/selection only if specified — platform doesn't always emit them on group
if ($item.order) {
Emit-Order -items $item.order -indent "$indent`t" -blockViewMode $item.orderViewMode -blockUserSettingID $item.orderUserSettingID
}
if ($item.selection) {
Emit-Selection -items $item.selection -indent "$indent`t"
}
# Платформа на группировке (плоской и вложенной в ось, short/explicit) всегда пишет
# order+selection; при отсутствии ключа кладёт Auto. Ключ присутствует (в т.ч. пустой [])
# — уважаем как задано (blockViewMode/userSettingID имеют смысл только при явном order).
$hasGrpOrderKey = $item.PSObject.Properties.Match('order').Count -gt 0
$grpOrderItems = if ($hasGrpOrderKey) { $item.order } else { @('Auto') }
Emit-Order -items $grpOrderItems -indent "$indent`t" -blockViewMode $item.orderViewMode -blockUserSettingID $item.orderUserSettingID
$hasGrpSelKey = $item.PSObject.Properties.Match('selection').Count -gt 0
$grpSelItems = if ($hasGrpSelKey) { $item.selection } else { @('Auto') }
Emit-Selection -items $grpSelItems -indent "$indent`t"
Emit-Filter -items $item.filter -indent "$indent`t"
@@ -3422,8 +3427,11 @@ function Emit-StructureItem {
}
}
# Selection (chart values)
Emit-Selection -items $item.selection -indent "$indent`t"
# Selection (chart values) — платформа всегда пишет chart-level selection; при отсутствии
# ключа кладёт Auto.
$hasChartSelKey = $item.PSObject.Properties.Match('selection').Count -gt 0
$chartSelItems = if ($hasChartSelKey) { $item.selection } else { @('Auto') }
Emit-Selection -items $chartSelItems -indent "$indent`t"
if ($item.outputParameters) {
Emit-OutputParameters -params $item.outputParameters -indent "$indent`t"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-compile v1.108 — Compile 1C DCS from JSON
# skd-compile v1.109 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -2631,10 +2631,13 @@ def emit_table_axis_block(lines, block, indent, emit_name=True):
emit_group_items(lines, gb, indent)
if block.get('filter'):
emit_filter(lines, block['filter'], indent)
if block.get('order'):
emit_order(lines, block['order'], indent)
if block.get('selection'):
emit_selection(lines, block['selection'], indent)
# Платформа на осях (column/row/point/series) всегда пишет order+selection; при отсутствии
# ключа кладёт Auto (как ручное добавление оси в конфигураторе). Ключ присутствует (в т.ч.
# пустой [] ) — уважаем как задано.
order_items = block['order'] if 'order' in block else ['Auto']
emit_order(lines, order_items, indent)
sel_items = block['selection'] if 'selection' in block else ['Auto']
emit_selection(lines, sel_items, indent)
if block.get('conditionalAppearance'):
emit_conditional_appearance(lines, block['conditionalAppearance'], indent)
if block.get('outputParameters'):
@@ -2674,11 +2677,13 @@ def emit_structure_item(lines, item, indent, short_group=False):
emit_group_items(lines, item.get('groupBy') or item.get('groupFields'), f'{indent}\t')
# Emit order/selection only if specified — platform doesn't always emit them on group
if item.get('order'):
emit_order(lines, item['order'], f'{indent}\t', block_view_mode=item.get('orderViewMode'), block_user_setting_id=item.get('orderUserSettingID'))
if item.get('selection'):
emit_selection(lines, item['selection'], f'{indent}\t')
# Платформа на группировке (плоской и вложенной в ось, short/explicit) всегда пишет
# order+selection; при отсутствии ключа кладёт Auto. Ключ присутствует (в т.ч. пустой [])
# — уважаем как задано (blockViewMode/userSettingID имеют смысл только при явном order).
grp_order_items = item['order'] if 'order' in item else ['Auto']
emit_order(lines, grp_order_items, f'{indent}\t', block_view_mode=item.get('orderViewMode'), block_user_setting_id=item.get('orderUserSettingID'))
grp_sel_items = item['selection'] if 'selection' in item else ['Auto']
emit_selection(lines, grp_sel_items, f'{indent}\t')
emit_filter(lines, item.get('filter'), f'{indent}\t')
@@ -2783,8 +2788,10 @@ def emit_structure_item(lines, item, indent, short_group=False):
emit_table_axis_block(lines, sb, f'{indent}\t\t')
lines.append(f'{indent}\t</dcsset:series>')
# Selection (chart values)
emit_selection(lines, item.get('selection'), f'{indent}\t')
# Selection (chart values) — платформа всегда пишет chart-level selection; при отсутствии
# ключа кладёт Auto.
chart_sel_items = item['selection'] if 'selection' in item else ['Auto']
emit_selection(lines, chart_sel_items, f'{indent}\t')
if item.get('outputParameters'):
emit_output_parameters(lines, item['outputParameters'], f'{indent}\t')
@@ -1,4 +1,4 @@
# skd-decompile v0.90 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.91 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -2223,18 +2223,20 @@ function Build-TableAxisBlock {
foreach ($fc in $fNode.SelectNodes("dcsset:item", $ns)) { $fa += (Build-FilterItem -itemNode $fc -loc "$loc/filter") }
$entry['filter'] = $fa
}
# order — preserve presence (even [Auto]) for bit-perfect round-trip
# order/selection — всегда явные (принцип «декомпилятор всегда явный»): [Auto] сохраняем как есть,
# отсутствие/пустоту эмитим как [] — иначе compile впаяет дефолтный Auto (round-trip рвётся на
# осях без выбора, напр. ветки use=false). [] на входе compile → эмитит ничего = «нет выбора».
# NB: прямое присваивание @() (не через if-выражение — там пустой массив схлопнется в $null).
$ordNode = $node.SelectSingleNode("dcsset:order", $ns)
if ($ordNode) {
$ordItems = Build-Order -ordNode $ordNode -loc "$loc/order"
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems }
}
# selection — preserve presence (even [Auto])
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems } else { $entry['order'] = @() }
} else { $entry['order'] = @() }
$selNode = $node.SelectSingleNode("dcsset:selection", $ns)
if ($selNode) {
$selItems = Build-Selection -selNode $selNode -loc "$loc/selection"
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems }
}
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems } else { $entry['selection'] = @() }
} else { $entry['selection'] = @() }
# conditionalAppearance block
$caN = $node.SelectSingleNode("dcsset:conditionalAppearance", $ns)
if ($caN) {
@@ -2381,11 +2383,13 @@ function Build-Structure {
$entry['series'] = $sArr
}
# Selection (chart values) — сохраняем даже [Auto] для bit-perfect presence
# chart-level selection — всегда явно ([] при отсутствии/пустоте, иначе compile впаяет Auto).
# NB: прямое присваивание @() (не через if-выражение — пустой массив там схлопнется в $null).
$selN = $it.SelectSingleNode("dcsset:selection", $ns)
if ($selN) {
$selI = Build-Selection -selNode $selN -loc "$loc/$idx/selection"
if ($selI.Count -gt 0) { $entry['selection'] = $selI }
}
if ($selI.Count -gt 0) { $entry['selection'] = $selI } else { $entry['selection'] = @() }
} else { $entry['selection'] = @() }
$opN = $it.SelectSingleNode("dcsset:outputParameters", $ns)
$op = Build-OutputParameters -opNode $opN
if ($op -and $op.Count -gt 0) { $entry['outputParameters'] = $op }
@@ -2427,17 +2431,21 @@ function Build-Structure {
$gFields = Get-GroupFields -parentNode $it -loc $loc
if ($gFields.Count -gt 0) { $entry['groupFields'] = $gFields }
# Local selection — preserve presence (even [Auto]) for bit-perfect round-trip
# Local selection/order — всегда явные: [Auto] как есть, отсутствие/пустоту как [] (иначе compile
# впаяет дефолтный Auto → round-trip рвётся на группах без выбора, напр. ветки use=false).
# [] не Auto-only → Try-StructureShorthand не свернёт такую группу в shorthand (и не добавит Auto).
# NB: прямое присваивание @() (не через if-выражение — пустой массив там схлопнется в $null).
$selNode = $it.SelectSingleNode("dcsset:selection", $ns)
if ($selNode) {
$selItems = Build-Selection -selNode $selNode -loc "$loc/selection"
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems }
}
# Local order — same
if ($selItems.Count -gt 0) { $entry['selection'] = $selItems } else { $entry['selection'] = @() }
} else { $entry['selection'] = @() }
$ordNode = $it.SelectSingleNode("dcsset:order", $ns)
if ($ordNode) {
$ordItems = Build-Order -ordNode $ordNode -loc "$loc/order"
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems }
if ($ordItems.Count -gt 0) { $entry['order'] = $ordItems } else { $entry['order'] = @() }
} else { $entry['order'] = @() }
if ($ordNode) {
# Block-level viewMode/userSettingID на <dcsset:order>
foreach ($ch in $ordNode.ChildNodes) {
if ($ch.NodeType -ne 'Element' -or $ch.NamespaceURI -ne 'http://v8.1c.ru/8.1/data-composition-system/settings') { continue }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# skd-decompile v0.90 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# skd-decompile v0.91 — Decompile 1C DCS Template.xml to JSON DSL (draft)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -2327,16 +2327,14 @@ def build_table_axis_block(node, loc, include_name=False):
for fc in f_node.select_nodes("dcsset:item"):
fa.append(build_filter_item(fc, "%s/filter" % loc))
entry['filter'] = fa
# order/selection — всегда явные ([Auto] как есть, отсутствие/пустоту как []): иначе compile
# впаяет дефолтный Auto → round-trip рвётся на осях без выбора (напр. ветки use=false).
ord_node = node.select_single_node("dcsset:order")
if ord_node:
ord_items = build_order(ord_node, "%s/order" % loc)
if len(ord_items) > 0:
entry['order'] = ord_items
ord_items = build_order(ord_node, "%s/order" % loc) if ord_node else []
entry['order'] = ord_items if len(ord_items) > 0 else []
sel_node = node.select_single_node("dcsset:selection")
if sel_node:
sel_items = build_selection(sel_node, "%s/selection" % loc)
if len(sel_items) > 0:
entry['selection'] = sel_items
sel_items = build_selection(sel_node, "%s/selection" % loc) if sel_node else []
entry['selection'] = sel_items if len(sel_items) > 0 else []
ca_n = node.select_single_node("dcsset:conditionalAppearance")
if ca_n:
ca = build_conditional_appearance(ca_n, "%s/ca" % loc)
@@ -2485,11 +2483,10 @@ def build_structure(node, loc):
s_arr.append(build_table_axis_block(s, "%s/%d/series[%d]" % (loc, idx, si)))
si += 1
entry['series'] = s_arr
# chart-level selection — всегда явно ([] при отсутствии/пустоте, иначе compile впаяет Auto)
sel_n = it.select_single_node("dcsset:selection")
if sel_n:
sel_i = build_selection(sel_n, "%s/%d/selection" % (loc, idx))
if len(sel_i) > 0:
entry['selection'] = sel_i
sel_i = build_selection(sel_n, "%s/%d/selection" % (loc, idx)) if sel_n else []
entry['selection'] = sel_i if len(sel_i) > 0 else []
op_n = it.select_single_node("dcsset:outputParameters")
op = build_output_parameters(op_n)
if op and len(op) > 0:
@@ -2532,16 +2529,16 @@ def build_structure(node, loc):
if len(g_fields) > 0:
entry['groupFields'] = g_fields
# Local selection/order — всегда явные ([Auto] как есть, отсутствие/пустоту как []): иначе
# compile впаяет дефолтный Auto → round-trip рвётся на группах без выбора (напр. use=false).
# [] не Auto-only → try_structure_shorthand не свернёт такую группу в shorthand (без Auto).
sel_node = it.select_single_node("dcsset:selection")
if sel_node:
sel_items = build_selection(sel_node, "%s/selection" % loc)
if len(sel_items) > 0:
entry['selection'] = sel_items
sel_items = build_selection(sel_node, "%s/selection" % loc) if sel_node else []
entry['selection'] = sel_items if len(sel_items) > 0 else []
ord_node = it.select_single_node("dcsset:order")
ord_items = build_order(ord_node, "%s/order" % loc) if ord_node else []
entry['order'] = ord_items if len(ord_items) > 0 else []
if ord_node:
ord_items = build_order(ord_node, "%s/order" % loc)
if len(ord_items) > 0:
entry['order'] = ord_items
for ch in ord_node.child_nodes:
if ch.namespace_uri != NS_SET:
continue
@@ -1,4 +1,4 @@
# template-add v1.9 — Add template to 1C object
# template-add v1.10 — Add template to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -329,7 +329,10 @@ if (-not $childObjects) {
exit 1
}
# Добавить <Template> в конец ChildObjects
# Добавить <Template> в конец ChildObjects — идемпотентно (не дублировать уже зарегистрированный)
$alreadyRegistered = [bool]$childObjects.SelectSingleNode("md:Template[text()='$TemplateName']", $nsMgr)
if (-not $alreadyRegistered) {
$templateElem = $xmlDoc.CreateElement("Template", "http://v8.1c.ru/8.3/MDClasses")
$templateElem.InnerText = $TemplateName
@@ -349,6 +352,7 @@ if ($childObjects.ChildNodes.Count -eq 0) {
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
}
}
}
# --- 4. MainDataCompositionSchema (для ExternalReport / Report) ---
@@ -392,6 +396,9 @@ $writer.Close()
$stream.Close()
Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)"
if ($alreadyRegistered) {
Write-Host " Already registered: <Template>$TemplateName</Template> in ChildObjects (skipped duplicate)"
}
Write-Host " Метаданные: $templateMetaPath"
Write-Host " Содержимое: $templateFilePath"
if ($mainDCSUpdated) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# add-template v1.9 — Add template to 1C object
# add-template v1.10 — Add template to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -446,31 +446,34 @@ def main():
print(f"Не найден элемент ChildObjects в {root_xml_path}", file=sys.stderr)
sys.exit(1)
# Add <Template> to end of ChildObjects
template_elem = etree.SubElement(child_objects, f"{{{ns}}}Template")
template_elem.text = template_name
# Remove auto-appended element to reinsert with proper whitespace
child_objects.remove(template_elem)
# Add <Template> to end of ChildObjects — idempotent (do not duplicate already-registered template)
already_registered = child_objects.find(f"md:Template[.='{template_name}']", NSMAP) is not None
children = list(child_objects)
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
# Empty ChildObjects (self-closing)
child_objects.text = "\n\t\t\t"
child_objects.append(template_elem)
template_elem.tail = "\n\t\t"
else:
if len(children) > 0:
last_child = children[-1]
# last_child.tail is the trailing whitespace before </ChildObjects>
old_tail = last_child.tail
last_child.tail = "\n\t\t\t"
child_objects.append(template_elem)
template_elem.tail = old_tail if old_tail else "\n\t\t"
else:
# Has text content but no element children
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
if not already_registered:
template_elem = etree.SubElement(child_objects, f"{{{ns}}}Template")
template_elem.text = template_name
# Remove auto-appended element to reinsert with proper whitespace
child_objects.remove(template_elem)
children = list(child_objects)
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
# Empty ChildObjects (self-closing)
child_objects.text = "\n\t\t\t"
child_objects.append(template_elem)
template_elem.tail = "\n\t\t"
else:
if len(children) > 0:
last_child = children[-1]
# last_child.tail is the trailing whitespace before </ChildObjects>
old_tail = last_child.tail
last_child.tail = "\n\t\t\t"
child_objects.append(template_elem)
template_elem.tail = old_tail if old_tail else "\n\t\t"
else:
# Has text content but no element children
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
child_objects.append(template_elem)
template_elem.tail = "\n\t\t"
# --- 4. MainDataCompositionSchema (for ExternalReport / Report) ---
@@ -500,6 +503,8 @@ def main():
save_xml_with_bom(tree, root_xml_full)
print(f"[OK] Создан макет: {template_name} ({template_type})")
if already_registered:
print(f" Already registered: <Template>{template_name}</Template> in ChildObjects (skipped duplicate)")
print(f" Метаданные: {template_meta_path}")
print(f" Содержимое: {template_file_path}")
if main_dcs_updated:
+4 -2
View File
@@ -129,7 +129,7 @@ Switch to an already-open tab/window (fuzzy match).
### Reading form state
#### `getFormState()``{ form, formCount, openForms, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
#### `getFormState()``{ form, formCount, openForms, title, fields, buttons, tabs, navigation?, table, tables, filters, reportSettings? }`
Returns current form structure. This is the primary way to understand what's on screen.
**form** — active form number, or `null` when no form is open (desktop).
@@ -142,6 +142,8 @@ Returns current form structure. This is the primary way to understand what's on
**openTabs** — array of `{ name, active? }` from the open-windows tab bar. Only present when the tab bar is enabled in 1C settings. Do NOT rely on this — use `formCount`/`openForms` instead.
**title** — caption of the active form (`"Контрагенты"`, `"Заказ поставщику ТД00-000052 от 05.07.2022"`). Read from the form's own header, which does not depend on the open-windows tab bar; when the form shows no header, falls back to the active tab's caption, and is `null` when neither is available.
**fields** — each field has: `name`, `value`, `label?`, `actions?` (select, clear, open), `required?` (true for unfilled mandatory fields), `disabled?` (control is unavailable). `buttons[]` carry `disabled?` too.
**navigation** — form navigation panel links (for objects with subordinate catalogs): `[{ name, active? }]`. Clickable via `clickElement()`. Only present when the form has a navigation panel (e.g. "Основное", "Объекты метаданных", "Подсистемы").
@@ -170,7 +172,7 @@ const form = await getFormState();
**confirmation** — if present, a Yes/No dialog is shown. Call `clickElement('Да')` or `clickElement('Нет')`.
**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data.
**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data. The same info bar carries `"Поиск..."` while a list is still searching — actions do not return while it is up, so a filtered list never hands you the previous rows.
### Reading data
+8 -6
View File
@@ -10,6 +10,8 @@ node $RUN test <dir|file>... [flags]
Positional args are test paths (files and/or dirs, multiple allowed). URL is NOT positional — it comes from `webtest.config.mjs`; override with `--url=<url>`.
`webtest.config.mjs` and `_hooks.mjs` always come from the suite root, whatever path you pass: `test tests/myapp/sales/` and `test tests/myapp/sales/01-order.test.mjs` both run under the config and hooks of `tests/myapp/`, no `--url=` needed. Paths from two different suites in one run are refused — pass one suite and narrow with `--grep=` / `--tags=`.
Tests live next to the project they cover (not inside the skill). Convention: `tests/` at the project root, with `_hooks.mjs` and `webtest.config.mjs` at the suite root. Tests are ES modules with `*.test.mjs` suffix.
## When to choose `test` over `exec`
@@ -69,7 +71,7 @@ tests/<app-name>/
01-end-to-end.test.mjs # multi-user
```
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported — only the application-root copies are loaded.
Per-folder `_hooks.mjs` / `webtest.config.mjs` inside the application subfolder are NOT supported — only the application-root copies are loaded, whichever subfolder you point the runner at.
## Test file anatomy
@@ -184,8 +186,8 @@ assert.match(string, regex, msg?) // regex.test(string)
await assert.throws(asyncFn, msg?) // passes if fn throws (use await)
// 1C-specific — operate on getFormState() / readTable() output
assert.formHasField(state, 'Контрагент', msg?) // state.fields[name] exists
assert.formTitle(state, expected, msg?) // state.title includes expected
assert.formHasField(state, 'Контрагент', msg?) // fields[] contains a field with that name
assert.formTitle(state, expected, msg?) // state.title includes expected (null title → fails saying so)
assert.tableHasRow(table, predicate, msg?) // predicate: object (partial match) or fn(row) => bool
// object form: { 'Наименование': 'Тест' }
// fn form: r => r['Сумма'] > 100
@@ -316,7 +318,7 @@ export default async function({ clerk, manager, step, assert }) {
});
await step('Кладовщик видит новый статус', async () => {
const s = await clerk.getFormState();
assert.equal(s.fields['Статус']?.value, 'Утверждён');
assert.equal(s.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
});
await step('Освободить сессию кладовщика', async () => {
await manager.closeContext('clerk'); // free a 1C license for the next test
@@ -339,7 +341,7 @@ export default async function({ openCommand, clickElement, getFormState, assert,
await clickElement('Создать');
await clickElement('Провести');
const s = await getFormState();
assert.ok(s.errorModal || s.fields['Контрагент']?.required,
assert.ok(s.errorModal || s.fields.find(f => f.name === 'Контрагент')?.required,
'Должна быть ошибка валидации или поле помечено обязательным');
}
```
@@ -359,7 +361,7 @@ export const params = [
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
await fillFields({ [field]: value });
const state = await getFormState();
assert.equal(state.fields[field]?.value, String(value));
assert.equal(state.fields.find(f => f.name === field)?.value, String(value));
}
```
@@ -1,4 +1,4 @@
// web-test cli/commands/test v1.8 — regression test runner
// web-test cli/commands/test v1.9 — regression test runner
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, writeFileSync, mkdirSync, renameSync, copyFileSync, unlinkSync } from 'fs';
import { resolve, dirname, basename, relative } from 'path';
@@ -9,6 +9,7 @@ import { createAssertions } from '../test-runner/assertions.mjs';
import { buildSeverityIndex } from '../test-runner/severity.mjs';
import { writeAllure, buildJUnit, syncAllureExtras } from '../test-runner/reporters.mjs';
import { discoverTests, resetState } from '../test-runner/discover.mjs';
import { findSuiteRoot, startDirOf } from '../test-runner/suite-root.mjs';
import { planEviction, touchLru, dropLru } from '../test-runner/context-pool.mjs';
export async function cmdTest(rawArgs) {
@@ -78,12 +79,24 @@ export async function cmdTest(rawArgs) {
die(`Test path not found: "${p}". To run a subset use --grep= / --tags=, or pass an existing dir/file.`);
}
// Load config if exists. config (webtest.config.mjs) and hooks (_hooks.mjs) resolve from
// the FIRST path's directory — list paths from the same suite folder.
const firstPath = resolve(testPaths[0]);
const isFile = firstPath.endsWith('.test.mjs');
const testDir = isFile ? dirname(firstPath) : firstPath;
const configPath = resolve(testDir, 'webtest.config.mjs');
// Suite root — the directory `webtest.config.mjs`, `_hooks.mjs`, `_allure/` and report paths
// all hang off. It is NOT the passed path: walking up to the nearest marker is what makes
// `test tests/myapp/sales/` work, as docs/web-test-regression-spec.md has always promised.
// Resolving from the passed path instead lost the hooks of any subfolder run — silently, so
// the run went ahead against an unprepared stand.
const startDirs = testPaths.map(p => startDirOf(p));
const roots = startDirs.map(d => findSuiteRoot(d));
// Paths from different suites must not share hooks — that used to resolve to "first path
// wins", silently running suite B's tests under suite A's preparation.
const distinct = [...new Set(roots.map(r => r?.root ?? null))];
if (distinct.length > 1) {
const lines = testPaths.map((p, i) => ` ${p}${roots[i]?.root ?? '(корень не найден)'}`);
die(`Paths belong to different suites — config and hooks would be ambiguous:\n${lines.join('\n')}\n` +
`Run them separately, or pass one suite root and narrow with --grep= / --tags=.`);
}
const suiteRoot = roots[0]?.root ?? startDirs[0];
const suiteRootFound = !!roots[0];
const configPath = resolve(suiteRoot, 'webtest.config.mjs');
let config = {};
if (existsSync(configPath)) {
const mod = await import('file:///' + configPath.replace(/\\/g, '/'));
@@ -103,7 +116,16 @@ export async function cmdTest(rawArgs) {
if (url) contextSpecs[defaultContextName] = { ...contextSpecs[defaultContextName], url };
} else {
const fallbackUrl = url || config.url;
if (!fallbackUrl) die('No URL provided and no webtest.config.mjs found');
// Name the real problem: with no suite root there is no config to take a URL from — and,
// more dangerously, no `_hooks.mjs` either. The old wording talked only about the URL and
// sent readers looking in the wrong place.
if (!fallbackUrl) {
die(suiteRootFound
? `No URL: ${configPath} defines neither "contexts" nor "url", and --url= was not given.`
: `Suite root not found above "${testPaths[0]}" — no webtest.config.mjs / _hooks.mjs up to ` +
`the repository (or working) directory, so there is no URL and no stand preparation.\n` +
`Pass the suite root (e.g. tests/myapp/) and narrow with --grep= / --tags=, or give --url=.`);
}
contextSpecs.default = { url: fallbackUrl };
}
if (!contextSpecs[defaultContextName]) {
@@ -175,7 +197,7 @@ export async function cmdTest(rawArgs) {
}
const reportDir = opts.reportDir
? resolve(opts.reportDir)
: (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : testDir);
: (opts.report && !reportToStdout ? dirname(resolve(opts.report)) : suiteRoot);
if (opts.screenshot !== 'off') {
try { mkdirSync(reportDir, { recursive: true }); } catch {}
// 1C-error screenshots (taken inside the action wrapper) default to a single
@@ -194,7 +216,10 @@ export async function cmdTest(rawArgs) {
for (const file of testFiles) {
const mod = await import('file:///' + file.replace(/\\/g, '/'));
const base = {
file: relative(testDir, file).replace(/\\/g, '/'),
// Relative to the SUITE ROOT, not to the passed path — otherwise the same test gets a
// different id depending on how it was launched (`sales/01-x.test.mjs` vs `01-x.test.mjs`),
// and Allure history / JUnit trends treat the two as unrelated tests.
file: relative(suiteRoot, file).replace(/\\/g, '/'),
name: mod.name || basename(file, '.test.mjs'),
tags: mod.tags || [],
timeout: mod.timeout || opts.timeout,
@@ -229,7 +254,7 @@ export async function cmdTest(rawArgs) {
});
// Load hooks
const hooksPath = resolve(testDir, '_hooks.mjs');
const hooksPath = resolve(suiteRoot, '_hooks.mjs');
let hooks = {};
if (existsSync(hooksPath)) {
hooks = await import('file:///' + hooksPath.replace(/\\/g, '/'));
@@ -239,7 +264,17 @@ export async function cmdTest(rawArgs) {
// In `--report -` mode the machine JSON/XML takes over stdout, so progress moves to stderr.
const W = reportToStdout ? process.stderr : process.stdout;
W.write(`\nweb-test -- ${url}\n`);
W.write(`Running ${filtered.length} tests from ${relative(process.cwd(), testDir).replace(/\\/g, '/') || '.'}/\n\n`);
// Always name the resolved suite root: a climb that landed on the wrong directory is then
// visible in the first line of output instead of being diagnosed from symptoms later.
const rel = (p) => relative(process.cwd(), p).replace(/\\/g, '/') || '.';
const shownPaths = testPaths.map(p => rel(resolve(p))).filter(p => p !== rel(suiteRoot));
W.write(`Running ${filtered.length} tests from ${rel(suiteRoot)}/`);
W.write(shownPaths.length ? ` (paths: ${shownPaths.join(', ')})\n\n` : `\n\n`);
if (!suiteRootFound) {
// Not fatal — a one-off test outside any suite is legitimate. But a missing suite root also
// means no `_hooks.mjs` was even looked for above, so the stand is whatever it was.
process.stderr.write(`! no suite root (webtest.config.mjs / _hooks.mjs) found above ${rel(startDirs[0])} — running without hooks\n`);
}
const startedAt = new Date().toISOString();
const results = [];
@@ -819,10 +854,10 @@ export async function cmdTest(rawArgs) {
if (opts.format === 'allure') {
// Guard against a result-producing path that skipped recordResult; normally a no-op.
if (!allureWritten) writeAllure(results, reportDir, severityIndex);
syncAllureExtras(testDir, reportDir);
syncAllureExtras(suiteRoot, reportDir);
} else if (opts.format === 'junit') {
if (reportToStdout) process.stdout.write(buildJUnit(report, testDir) + '\n');
else writeFileSync(resolve(opts.report), buildJUnit(report, testDir));
if (reportToStdout) process.stdout.write(buildJUnit(report, suiteRoot) + '\n');
else writeFileSync(resolve(opts.report), buildJUnit(report, suiteRoot));
} else if (reportToStdout) {
out(report);
} else if (opts.report) {
@@ -1,4 +1,4 @@
// web-test cli/test-runner/assertions v1.0 — ctx.assert API
// web-test cli/test-runner/assertions v1.1 — ctx.assert API
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
export function createAssertions() {
@@ -37,11 +37,23 @@ export function createAssertions() {
throw new AssertionError(msg || 'Expected function to throw');
},
// 1C-specific
// `fields` is an ARRAY of { name, value, ... } — indexing it by field name yields undefined,
// which used to make this assertion throw on every call, including the valid ones.
formHasField(state, fieldName, msg) {
if (!state?.fields?.[fieldName]) throw new AssertionError(msg || `Field "${fieldName}" not found in form. Available: ${Object.keys(state?.fields || {}).join(', ')}`, null, fieldName);
const names = (state?.fields || []).map(f => f.name);
if (!names.includes(fieldName)) {
throw new AssertionError(msg || `Field "${fieldName}" not found in form. Available: ${names.join(', ')}`, null, fieldName);
}
},
formTitle(state, expected, msg) {
if (!state?.title?.includes(expected)) throw new AssertionError(msg || `Form title "${state?.title}" does not contain "${expected}"`, state?.title, expected);
// `title` is null when the form exposes no caption and the open-windows panel is off —
// say so instead of reporting a mismatch against "null".
if (state?.title == null) {
throw new AssertionError(msg || `Form title is not available (state.title is null), expected it to contain "${expected}"`, null, expected);
}
if (!state.title.includes(expected)) {
throw new AssertionError(msg || `Form title "${state.title}" does not contain "${expected}"`, state.title, expected);
}
},
tableHasRow(table, predicate, msg) {
const rows = table?.rows || [];
@@ -1,4 +1,4 @@
// web-test cli/test-runner/discover v1.3 — test file discovery + state reset between tests
// web-test cli/test-runner/discover v1.4 — test file discovery + state reset between tests
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, readdirSync } from 'fs';
import { resolve } from 'path';
@@ -73,7 +73,9 @@ export async function resetState(ctx) {
return {
clean: false, attempts, lastError,
form: state.form,
title: state.activeTab || null,
// state.title is the form's own caption; activeTab reads the open-windows panel, which the
// user can switch off — keep it only as the fallback it always was.
title: state.title || state.activeTab || null,
modal: !!state.modal,
};
} catch (e) {
@@ -0,0 +1,55 @@
// web-test cli/test-runner/suite-root v1.0 — locate the suite root above a given test path
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { existsSync, statSync } from 'fs';
import { resolve, dirname } from 'path';
// Files that MARK a suite root. Both count, not just the config: `webtest.config.mjs` is
// optional (a single-URL suite may pass --url= instead), and a suite that ships only
// `_hooks.mjs` must still be found — otherwise its stand preparation is silently skipped,
// which is worse than any URL error.
const MARKERS = ['webtest.config.mjs', '_hooks.mjs'];
// Files that BOUND the climb. A boundary never selects a root — it only stops the search,
// so a wrong boundary degrades to "root not found" (= the pre-v1.9 behaviour plus a clear
// message) and can never produce a wrong root. `package.json` is deliberately absent: it
// occurs nested and would stop the climb below a legitimate suite root.
const BOUNDARIES = ['.git', '.v8-project.json'];
const isDir = (p) => { try { return statSync(p).isDirectory(); } catch { return false; } };
/**
* Walk up from `startPath` looking for a suite root.
*
* @param {string} startPath A test file or directory (absolute or cwd-relative).
* @param {{cwd?: string}} [opts]
* @returns {{root: string, marker: string} | null} null when no marker was found within bounds.
*
* Stops after examining the first directory that contains `.git` / `.v8-project.json`
* (that directory IS examined for markers), or when neither is met after examining `cwd`.
* A path outside `cwd` degenerates to the filesystem root; the marker requirement still
* makes a wrong hit unlikely, and the resolved root is printed in the run banner.
*/
export function findSuiteRoot(startPath, { cwd = process.cwd() } = {}) {
const full = resolve(startPath);
let dir = isDir(full) ? full : dirname(full);
const cwdAbs = resolve(cwd);
while (true) {
for (const m of MARKERS) {
if (existsSync(resolve(dir, m))) return { root: dir, marker: m };
}
const atBoundary = BOUNDARIES.some(b => existsSync(resolve(dir, b))) || dir === cwdAbs;
const parent = dirname(dir);
if (atBoundary || parent === dir) return null;
dir = parent;
}
}
/**
* The directory a path contributes to root resolution its own dir for a file, itself for
* a directory. Also the fallback root when no marker is found (pre-v1.9 behaviour).
*/
export function startDirOf(testPath) {
const full = resolve(testPath);
return isDir(full) ? full : dirname(full);
}
@@ -1,4 +1,4 @@
// web-test dom/form-state v1.0 — combined detectForm + readForm + open tabs
// web-test dom/form-state v1.1 — combined detectForm + readForm + open tabs + form caption
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { DETECT_FORM_FN, DETECT_FORMS_FN, READ_FORM_FN } from './_shared.mjs';
@@ -26,7 +26,27 @@ export function getFormStateScript() {
openTabs.push(entry);
});
const activeTab = openTabs.find(t => t.active)?.name || null;
const result = { form: formNum, activeTab, openForms: meta.allForms, formCount: meta.formCount, ...formData };
// Caption of the ACTIVE form. Lives in an attribute, not in text — the div itself is empty:
// <div class="toplineBox" data-title="Контрагенты">
// <div id="VW_page1headerTopLine_title" class="toplineBoxTitle" title="Контрагенты"></div>
// Header numbering (VW_page<M>) does not match form numbering (form<N>), so the header cannot
// be picked by form number. Several headers can be visible at once — with a selection form up,
// BOTH the parent form's header and the pop-up's are visible — so "first visible" would report
// the parent's caption for the pop-up: a plausible, wrong answer.
// Priority is therefore the one already measured for the close cross (dom/forms.mjs
// closeCrossScript): floating window (ps<N>, highest index = topmost) → the form's own header →
// and only then the open-windows tab, which the user can switch off in 1C settings.
// Anchored on ids, not on the visible text, so a non-Russian locale keeps working.
const heads = [...document.querySelectorAll('[id*="headerTopLine_title"]')]
.filter(e => e.offsetWidth > 0 && e.offsetHeight > 0);
const floating = heads.filter(e => /ps\\d+headerTopLine_title$/.test(e.id));
const own = heads.filter(e => /^VW_page\\d+headerTopLine_title$/.test(e.id));
const head = floating.pop() || own.pop() || null;
let title = head
? (head.getAttribute('title') || head.parentElement?.getAttribute('data-title') || null)
: null;
if (!title) title = activeTab;
const result = { form: formNum, activeTab, title, openForms: meta.allForms, formCount: meta.formCount, ...formData };
if (meta.modal) result.modal = true;
if (openTabs.length) result.openTabs = openTabs;
return result;
@@ -1,4 +1,4 @@
// web-test core/state v1.17 — module-level state for the web-test engine.
// web-test core/state v1.18 — module-level state for the web-test engine.
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
//
// Holds the single browser/page/recorder slot plus the multi-context registry,
@@ -80,6 +80,11 @@ export const ACTION_WAIT = 2000; // fallback minimum wait
export const MAX_WAIT = 10000; // max wait for stability
export const POLL_INTERVAL = 200; // polling interval
export const STABLE_CYCLES = 3; // consecutive stable cycles needed
// Ceiling for the case where 1C is VISIBLY still working (its "Поиск…" state window is up).
// Higher than MAX_WAIT on purpose: a search on a production-sized list legitimately runs for
// tens of seconds, and returning mid-search hands the caller the previous rows — a wrong
// result that looks like a right one. Bounded so a wedged operation still ends the wait.
export const BUSY_MAX_WAIT = 60000;
// 1C browser extension ID (stable across versions, defined by key in manifest.json)
export const EXT_ID = 'pbhelknnhilelbnhfpcjlcabhmfangik';
@@ -1,30 +1,49 @@
// web-test core/wait v1.17 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor.
// web-test core/wait v1.18 — Smart wait helpers: DOM stability polling, JS-expression polling, CDP network monitor.
// Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import { page, MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs';
import { page, MAX_WAIT, BUSY_MAX_WAIT, POLL_INTERVAL, STABLE_CYCLES } from './state.mjs';
import { detectFormScript } from '../../dom.mjs';
/**
* Smart wait: poll until DOM is stable and no loading indicators are visible.
* Checks: form number change, loading indicators, DOM stability.
* Checks: form number change, loading indicators, busy state window, DOM stability.
* @param {number|null} previousFormNum form number before the action (null = don't check)
*/
export async function waitForStable(previousFormNum = null) {
let stableCount = 0;
let lastSnapshot = '';
const start = Date.now();
let deadline = start + MAX_WAIT;
while (Date.now() - start < MAX_WAIT) {
while (Date.now() < deadline) {
await page.waitForTimeout(POLL_INTERVAL);
// Check for loading indicators
const status = await page.evaluate(`(() => {
const loading = document.querySelector('.loadingImage, .waitCurtain, .progressBar');
const isLoading = loading && loading.offsetWidth > 0;
// While a dynamic list is still searching, 1C floats a state window over the grid
// ("Поиск…") — and NOTHING else in the DOM says so: the old rows stay put, the element
// counters below don't move, so the page looks perfectly stable with stale data.
// Match busy markers by text, never "state window present": the same carrier also holds
// TERMINAL report messages ("Отчет не сформирован", "Не установлено значение параметра"),
// and waiting for those to disappear would hang until the timeout on every report.
const busy = [...document.querySelectorAll('.stateWindowSupportSurface')].some(el =>
el.offsetWidth > 0 && /^\\s*(Поиск|Ожид|Searching|Please wait)/i.test(el.innerText || ''));
const formCount = document.querySelectorAll('input.editInput[id], a.press[id]').length;
return { isLoading, formCount };
return { isLoading, busy, formCount };
})()`);
// A visible busy indicator outranks DOM stability — it is the only evidence we get that the
// server is still working. Push the deadline while it lasts (bounded by BUSY_MAX_WAIT) instead
// of reporting "stable": returning mid-search is what handed a caller the previous rows and
// let the next click open the wrong document.
if (status.busy) {
deadline = Math.min(Date.now() + MAX_WAIT, start + BUSY_MAX_WAIT);
stableCount = 0;
continue;
}
if (status.isLoading) {
stableCount = 0;
continue;
+112
View File
@@ -0,0 +1,112 @@
---
name: xdto-compile
description: Создание пакета XDTO 1С из XML-схемы (XSD). Используй когда нужно добавить в конфигурацию пакет XDTO — под обмен, интеграцию, веб-сервис или внешний XML-формат
argument-hint: -XsdPath <файл.xsd>|-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`.
Вход — обычная XML-схема, писать её нужно так же, как для любого другого инструмента.
## Параметры
| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `XsdPath` | один из двух | Путь к файлу XML-схемы. Псевдоним — `-Path` |
| `Xsd` | один из двух | Схема строкой, вместо `-XsdPath` |
| `OutputDir` | да | Каталог исходников конфигурации или расширения — там, где лежит `Configuration.xml` |
| `Name` | нет | Имя объекта метаданных. По умолчанию — из `xs:appinfo`, иначе имя файла XSD, санированное под идентификатор 1С |
| `Synonym` | нет | Синоним (строка). По умолчанию — из `xs:appinfo`, иначе имя пакета. Для нескольких языков задавай синоним в схеме, блоком `xs:appinfo` |
| `Comment` | нет | Комментарий. По умолчанию — из `xs:appinfo` |
| `Force` | нет | Перезаписать существующий пакет. Без него навык откажется затирать уже собранный пакет |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-compile.ps1" -XsdPath "<схема.xsd>" -OutputDir "<каталог-исходников>"
```
Примеры:
```powershell
... -XsdPath bank.xsd -OutputDir src -Name ОбменСБанком -Synonym "Обмен с банком"
... -XsdPath fss.xsd -OutputDir src -Force
```
## Читай предупреждения
XSD выразительнее модели XDTO. Всё, что не переносится один в один, навык переносит
приближённо и **пишет об этом**:
```
Предупреждения (2) — конструкции XSD без точного соответствия в модели XDTO:
! Документ : вложенная xs:choice уплощена в последовательность — выбор одного из вариантов не сохранён
! Документ : кратность на вложенной частице (<xs:sequence minOccurs/maxOccurs>) не выражается в модели XDTO
```
Такое сообщение означает, что пакет собран, но схема упрощена. Если упрощение
недопустимо — меняй схему (например, разноси варианты `xs:choice` по разным типам),
а не игнорируй.
Что переносится приближённо: вложенные `xs:sequence`/`xs:choice` (уплощаются в плоский
список свойств), `xs:all` (становится последовательностью), кратность на частице,
`substitutionGroup`, `xs:key`/`keyref`/`unique`, `xs:redefine`.
`xs:group` и `xs:attributeGroup` раскрываются по ссылке — их содержимое попадает в тип.
`xs:include` игнорируется: зависимости в XDTO разрешаются только по namespace,
поэтому включаемую схему нужно собрать отдельным пакетом и заменить `include` на `import`.
## Посмотреть, что получилось
Модель пакета лежит в `Ext/Package.bin`. Несмотря на расширение, это текстовый XML,
но читать его напрямую обычно незачем: состав собранного пакета показывает
`/xdto-info`, а полную схему — `/xdto-decompile`.
## Зависимости между пакетами
`<xs:import namespace="…"/>` разрешается по namespace среди пакетов конфигурации
или расширения. Если пакета с таким пространством имён нет, платформа при загрузке
молча подменит тип на `xs:anyType` — без ошибки. Собирай сначала зависимости, потом
зависящий пакет, и проверяй результат через `/xdto-validate`.
Какие пакеты уже собраны, видно в `ChildObjects` файла `Configuration.xml`.
## Что XSD выразить не может
Две вещи модель XDTO умеет, а XML Schema — нет: `nillable` у атрибута и `qualified`
у отдельного свойства. Они пишутся атрибутами из пространства имён модели:
```xml
<xs:attribute name="Представление" type="xs:string"
xmlns:xdto="http://v8.1c.ru/8.1/xdto" xdto:nillable="true"/>
```
Схема остаётся валидной — валидаторы такие атрибуты игнорируют. Полный список
и таблица соответствий XSD ↔ XDTO — в [xsd-reference.md](xsd-reference.md).
Свойства объекта метаданных можно задать прямо в схеме:
```xml
<xs:annotation>
<xs:appinfo>
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
<xdto:name>ОбменСБанком</xdto:name>
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
</xdto:package>
</xs:appinfo>
</xs:annotation>
```
## Типичный workflow
1. Получить XSD от контрагента (или выгрузить схему существующего пакета: `/xdto-decompile`)
2. `/xdto-compile -XsdPath <файл> -OutputDir <каталог-исходников>` — прочитать предупреждения
3. `/xdto-validate <каталог-исходников>/XDTOPackages/<Имя>` — убедиться, что типы разрешились
4. `/db-load-xml` + `/db-update`
Правка существующего пакета: точечно — `/xdto-edit`; переработать схему целиком —
`/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
@@ -0,0 +1,957 @@
# xdto-compile v1.1 — 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"
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу: импортируются, но
# targetNamespace с таким значением ни у одного пакета нет)
$PLATFORM_NS = @(
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema"
)
[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:schema> в пространстве имён $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("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace('"', "&quot;")
}
function EscText([string]$s) {
if ($null -eq $s) { return "" }
return $s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
}
# --- 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
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>, а не <enumeration></enumeration>
$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)</$tagName>`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</$tagName>`r`n")
}
# --- XSD reading helpers ---
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
$script:warnings = New-Object System.Collections.ArrayList
function Warn([string]$msg) {
if (-not $script:warnings.Contains($msg)) { [void]$script:warnings.Add($msg) }
}
function XA([System.Xml.XmlElement]$el, [string]$name) {
if ($el.HasAttribute($name)) { return $el.GetAttribute($name) }
return $null
}
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")
}
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
$mFixed = MA $el "fixed"
if ($null -ne $mFixed) {
Add-Attr $p "fixed" $mFixed
Add-Attr $p "default" (XA $el "default")
if ($mFixed -ceq "true" -and $null -eq (XA $el "default")) {
Warn "Свойство `"$(XA $el 'name')`": xdto:fixed=`"true`" без default — платформа отвергнет пакет («Отсутствует фиксированное значение»). Значение задаётся атрибутом default, либо пишите XSD-форму fixed=`"значение`""
}
} elseif ($null -ne (XA $el "fixed")) {
Add-Attr $p "fixed" "true"
Add-Attr $p "default" (XA $el "fixed")
} else {
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) ---
# Разрешение xs:group / xs:attributeGroup по ссылке
$script:GROUPS = @{}
$script:ATTR_GROUPS = @{}
function Resolve-Group([System.Xml.XmlElement]$el, [string]$kind) {
$ref = XA $el "ref"
if (-not $ref) { return $null }
$q = Split-QName $el $ref
if (-not $q) { return $null }
$map = if ($kind -eq "group") { $script:GROUPS } else { $script:ATTR_GROUPS }
if ($map.ContainsKey($q.Local)) { return $map[$q.Local] }
return $null
}
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
function Collect-Particle([System.Xml.XmlElement]$particle, $elemList, [ref]$isOpen, [string]$typeName, [int]$depth, [bool]$optionalize = $false) {
if ($depth -gt 20) { return }
foreach ($c in $particle.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.NamespaceURI -ne $XS_NS) { continue }
switch ($c.get_LocalName()) {
"element" {
$prop = Build-Property $c $false
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
if ($optionalize) { Set-AttrValue $prop "lowerBound" "0" }
[void]$elemList.Add($prop)
}
"any" { $isOpen.Value = $true }
"sequence" {
Warn "$typeName : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств"
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
}
"choice" {
$branches = @()
foreach ($b in $c.ChildNodes) {
if ($b.NodeType -eq [System.Xml.XmlNodeType]::Element -and $b.NamespaceURI -eq $XS_NS -and $b.HasAttribute("name")) {
$branches += $b.GetAttribute("name")
}
}
$list = if ($branches.Count -gt 0) { " (" + ($branches -join ", ") + ")" } else { "" }
Warn ("$typeName : вложенная xs:choice уплощена — ветки$list сделаны необязательными. " +
"Выбор одного из вариантов не сохранён: модель не запретит заполнить сразу несколько или ни одного")
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $true
}
"all" {
Warn "$typeName : xs:all трактуется как последовательность"
Collect-Particle $c $elemList $isOpen $typeName ($depth + 1) $optionalize
}
"group" {
$g = Resolve-Group $c "group"
if ($g) {
foreach ($gc in $g.ChildNodes) {
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
Collect-Particle $gc $elemList $isOpen $typeName ($depth + 1) $optionalize
}
}
} else {
Warn "$typeName : не найдена группа $(XA $c 'ref') — её свойства в пакет не попали"
}
}
}
# Кратность на самой частице модель выразить не может
if (@("sequence", "choice", "all", "group") -contains $c.get_LocalName()) {
if ((XA $c "maxOccurs") -or (XA $c "minOccurs")) {
Warn "$typeName : кратность на вложенной частице (<xs:$($c.get_LocalName()) minOccurs/maxOccurs>) не выражается в модели XDTO"
}
}
}
}
# open / ordered / sequenced / abstract / mixed: выводим где выводимо,
# остальное приходит зеркалом xdto:
function Set-TypeFlags($node, [System.Xml.XmlElement]$ct, $isOpen, $choice) {
$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"
$all = XFirst $body "all"
$grp = XFirst $body "group"
$particle = if ($seq) { $seq } elseif ($cho) { $cho } elseif ($all) { $all } else { $grp }
$isOpen = $false
# Порядок в XDTO: сначала form="Attribute", потом остальные (верно для 96.5%
# типов корпуса). Отклонения приходят зеркалом xdto:order.
$elemProps = New-Object System.Collections.ArrayList
$typeName = if ($ct.HasAttribute("name")) { $ct.GetAttribute("name") } else { "(анонимный тип)" }
if ($particle) {
$openRef = [ref]$isOpen
if ($all) {
Warn "$typeName : xs:all трактуется как последовательность"
}
if ($grp -and -not $seq -and -not $cho -and -not $all) {
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
$g = Resolve-Group $grp "group"
if ($g) {
foreach ($gc in $g.ChildNodes) {
if ($gc.NodeType -eq [System.Xml.XmlNodeType]::Element -and $gc.NamespaceURI -eq $XS_NS -and
@("sequence", "choice", "all") -contains $gc.get_LocalName()) {
Collect-Particle $gc $elemProps $openRef $typeName 1
}
}
} else {
Warn "$typeName : не найдена группа $(XA $grp 'ref') — её свойства в пакет не попали"
}
} else {
Collect-Particle $particle $elemProps $openRef $typeName 0
}
$isOpen = $openRef.Value
}
foreach ($a in (XChildren $body "attribute")) { Add-Child $node (Build-Property $a $true) }
# xs:attributeGroup раскрываем по ссылке
foreach ($ag in (XChildren $body "attributeGroup")) {
$g = Resolve-Group $ag "attributeGroup"
if ($g) {
foreach ($a in (XChildren $g "attribute")) { Add-Child $node (Build-Property $a $true) }
} else {
Warn "Не найдена группа атрибутов $(XA $ag 'ref') — её атрибуты в пакет не попали"
}
}
foreach ($e in $elemProps) { Add-Child $node $e }
if ((XChildren $body "anyAttribute").Count -gt 0) { $isOpen = $true }
$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 }
$nm = XA $node "name"
if ($node.get_LocalName() -eq "group" -and $nm) { $script:GROUPS[$nm] = $node }
if ($node.get_LocalName() -eq "attributeGroup" -and $nm) { $script:ATTR_GROUPS[$nm] = $node }
}
# Конструкции XSD, которым в модели XDTO нет соответствия
foreach ($sg in $schema.SelectNodes("//*[local-name()='element'][@substitutionGroup]")) {
Warn "Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление $($sg.GetAttribute('name')) сохранено как обычное"
}
foreach ($idc in @("key", "keyref", "unique")) {
if ($schema.SelectNodes("//*[local-name()='$idc']").Count -gt 0) {
Warn "Ограничения целостности (xs:$idc) в модели XDTO не хранятся — отброшены"
}
}
if ($schema.SelectNodes("//*[local-name()='redefine']").Count -gt 0) {
Warn "xs:redefine не поддерживается — переопределения проигнорированы"
}
if ($schema.SelectNodes("//*[local-name()='include']").Count -gt 0) {
Warn "xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. Соберите включаемую схему отдельным пакетом и добавьте <xs:import>"
}
foreach ($node in $schema.ChildNodes) {
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element -or $node.NamespaceURI -ne $XS_NS) { continue }
switch ($node.get_LocalName()) {
"annotation" { }
"group" { }
"attributeGroup" { }
"notation" { }
"import" {
$n = New-Node "import"
Add-Attr $n "namespace" (XA $node "namespace")
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("<package xmlns=`"$XDTO_NS`" xmlns:xs=`"$XS_NS`" xmlns:xsi=`"$XSI_NS`"$rootAttrText>`r`n")
foreach ($c in $pkgNode.Children) { Serialize-Node $c 2 @{} }
[void]$out.Append("</package>")
$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 '<?xml version="1.0" encoding="UTF-8"?>'
M '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">'
M "`t<XDTOPackage uuid=`"$uuid`">"
M "`t`t<Properties>"
M "`t`t`t<Name>$(EscText $Name)</Name>"
M "`t`t`t<Synonym>"
foreach ($s in $synItems) {
M "`t`t`t`t<v8:item>"
M "`t`t`t`t`t<v8:lang>$(EscText $s.Lang)</v8:lang>"
M "`t`t`t`t`t<v8:content>$(EscText $s.Content)</v8:content>"
M "`t`t`t`t</v8:item>"
}
M "`t`t`t</Synonym>"
if ($Comment) { M "`t`t`t<Comment>$(EscText $Comment)</Comment>" } else { M "`t`t`t<Comment/>" }
M "`t`t`t<Namespace>$(EscText $targetNs)</Namespace>"
M "`t`t</Properties>"
M "`t</XDTOPackage>"
[void]$md.Append("</MetaDataObject>")
[System.IO.File]::WriteAllText($mdFile, $md.ToString(), $encBom)
# --- Register in Configuration.xml ---
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
$xdtoRootDir = Join-Path $OutputDir "XDTOPackages"
$declaredImports = @()
foreach ($c in $pkgNode.Children) { if ($c.Tag -eq "import") { foreach ($a in $c.Attrs) { if ($a.Name -eq "namespace") { $declaredImports += $a.Value } } } }
if ($declaredImports.Count -gt 0 -and (Test-Path $xdtoRootDir)) {
$knownNs = @{}
foreach ($other in (Get-ChildItem $xdtoRootDir -Directory -ErrorAction SilentlyContinue)) {
$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)
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $true
} catch {}
}
foreach ($imp in $declaredImports) {
if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) {
Warn "Импорт `"$imp`" не разрешается: пакета с таким namespace в конфигурации нет. Платформа отвергнет пакет при обновлении — соберите зависимость первой"
}
}
}
$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"
if ($script:warnings.Count -gt 0) {
Write-Host ""
Write-Host "Предупреждения ($($script:warnings.Count)) — конструкции XSD без точного соответствия в модели XDTO:"
foreach ($w in $script:warnings) { Write-Host " ! $w" }
Write-Host ""
}
switch ($regResult) {
"added" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> добавлен в ChildObjects" }
"already" { Write-Host " Configuration.xml: <XDTOPackage>$Name</XDTOPackage> уже зарегистрирован" }
"no-config" { Write-Host " Configuration.xml не найден — регистрация пропущена" }
}
@@ -0,0 +1,962 @@
# xdto-compile v1.1 — 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
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу)
PLATFORM_NS = {
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema",
}
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()
def _parse_xml(source, from_string=False):
"""Разбор с узким отступлением для не-URI пространств имён.
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
иначе по-настоящему битый XML перестал бы отличаться от корректного.
"""
try:
return (etree.fromstring(source) if from_string else etree.parse(source))
except etree.XMLSyntaxError as e:
if "is not a valid URI" not in str(e):
raise
p = etree.XMLParser(recover=True)
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
# ── 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 = _parse_xml(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 = _parse_xml(xsd_bytes, from_string=True)
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:schema> в пространстве имён {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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
def esc_text(s):
if s is None:
return ""
return str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
# ── 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)
# Пустое значение пишется самозакрывающимся тегом: <enumeration/>
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)}</{tag_name}>\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}</{tag_name}>\r\n")
# ── XSD reading helpers ──────────────────────────────────────
# Предупреждения о том, что XSD выражает, а модель XDTO — нет. Молча ронять
# такие конструкции нельзя: пакет соберётся, а половина свойств исчезнет.
warnings_list = []
def warn(msg):
if msg not in warnings_list:
warnings_list.append(msg)
GROUPS = {}
ATTR_GROUPS = {}
def MA(el, name):
# xdto: mirror attribute — литеральное значение для Package.bin.
# Ищем по namespace, а не по строке префикса.
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"))
# XSD-шный fixed="V" несёт значение, в модели это fixed="true" + default="V".
# Прощающий ввод: модельная форма через зеркало xdto:fixed тоже принимается.
m_fixed = MA(el, "fixed")
if m_fixed is not None:
add_attr(p, "fixed", m_fixed)
add_attr(p, "default", el.get("default"))
if m_fixed == "true" and el.get("default") is None:
warn('Свойство "' + str(el.get("name")) + '": xdto:fixed="true" без default — '
"платформа отвергнет пакет («Отсутствует фиксированное значение»). "
'Значение задаётся атрибутом default, либо пишите XSD-форму fixed="значение"')
elif el.get("fixed") is not None:
add_attr(p, "fixed", "true")
add_attr(p, "default", el.get("fixed"))
else:
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 resolve_group(el, kind):
ref = el.get("ref")
if not ref:
return None
q = split_qname(el, ref)
if not q:
return None
m = GROUPS if kind == "group" else ATTR_GROUPS
return m.get(q[1])
# Модель XDTO знает только плоский список свойств: вложенные частицы уплощаются.
# Каждое уплощение — предупреждение, потому что меняется смысл схемы.
def collect_particle(particle, elem_list, open_flag, type_name, depth, optionalize=False):
if depth > 20:
return
for c in particle:
if not isinstance(c.tag, str) or etree.QName(c).namespace != XS_NS:
continue
ln = local(c)
if ln == "element":
prop = build_property(c, False)
# Ветка уплощённого xs:choice обязана стать необязательной: иначе
# «одно из двух» превращается в «оба сразу», и тип нельзя заполнить
if optionalize:
set_attr_value(prop, "lowerBound", "0")
elem_list.append(prop)
elif ln == "any":
open_flag[0] = True
elif ln == "sequence":
warn(type_name + " : вложенная xs:sequence уплощена — модель XDTO хранит плоский список свойств")
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
elif ln == "choice":
branches = [b.get("name") for b in c
if isinstance(b.tag, str) and etree.QName(b).namespace == XS_NS and b.get("name")]
lst = (" (" + ", ".join(branches) + ")") if branches else ""
warn(type_name + " : вложенная xs:choice уплощена — ветки" + lst + " сделаны необязательными. "
"Выбор одного из вариантов не сохранён: модель не запретит заполнить "
"сразу несколько или ни одного")
collect_particle(c, elem_list, open_flag, type_name, depth + 1, True)
elif ln == "all":
warn(type_name + " : xs:all трактуется как последовательность")
collect_particle(c, elem_list, open_flag, type_name, depth + 1, optionalize)
elif ln == "group":
g = resolve_group(c, "group")
if g is not None:
for gc in g:
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
and local(gc) in ("sequence", "choice", "all"):
collect_particle(gc, elem_list, open_flag, type_name, depth + 1, optionalize)
else:
warn(type_name + " : не найдена группа " + str(c.get("ref")) + " — её свойства в пакет не попали")
if ln in ("sequence", "choice", "all", "group"):
if c.get("maxOccurs") is not None or c.get("minOccurs") is not None:
warn(type_name + " : кратность на вложенной частице (<xs:" + ln +
" minOccurs/maxOccurs>) не выражается в модели XDTO")
def set_type_flags(node, ct, is_open, choice):
m_open = MA(ct, "open")
if m_open is not None:
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")
all_ = xfirst(body, "all")
grp = xfirst(body, "group")
particle = seq if seq is not None else (cho if cho is not None else (all_ if all_ is not None else grp))
open_flag = [False]
# Порядок в XDTO: сначала form="Attribute", потом остальные (96.5% типов корпуса)
elem_props = []
type_name = ct.get("name") or "(анонимный тип)"
if particle is not None:
if all_ is not None:
warn(type_name + " : xs:all трактуется как последовательность")
if grp is not None and seq is None and cho is None and all_ is None:
# Корневая частица задана ссылкой на группу — раскрываем её содержимое
g = resolve_group(grp, "group")
if g is not None:
for gc in g:
if isinstance(gc.tag, str) and etree.QName(gc).namespace == XS_NS \
and local(gc) in ("sequence", "choice", "all"):
collect_particle(gc, elem_props, open_flag, type_name, 1)
else:
warn(type_name + " : не найдена группа " + str(grp.get("ref")) + " — её свойства в пакет не попали")
else:
collect_particle(particle, elem_props, open_flag, type_name, 0)
is_open = open_flag[0]
for a in xchildren(body, "attribute"):
node.children.append(build_property(a, True))
# xs:attributeGroup раскрываем по ссылке
for ag in xchildren(body, "attributeGroup"):
g = resolve_group(ag, "attributeGroup")
if g is not None:
for a in xchildren(g, "attribute"):
node.children.append(build_property(a, True))
else:
warn("Не найдена группа атрибутов " + str(ag.get("ref")) + " — её атрибуты в пакет не попали")
node.children.extend(elem_props)
if xchildren(body, "anyAttribute"):
is_open = True
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
nm = node.get("name")
if local(node) == "group" and nm:
GROUPS[nm] = node
if local(node) == "attributeGroup" and nm:
ATTR_GROUPS[nm] = node
# Конструкции XSD, которым в модели XDTO нет соответствия
for sg in schema.iter():
if isinstance(sg.tag, str) and local(sg) == "element" and sg.get("substitutionGroup"):
warn("Подстановочные группы (substitutionGroup) не поддерживаются моделью XDTO — объявление "
+ str(sg.get("name")) + " сохранено как обычное")
for idc in ("key", "keyref", "unique"):
if any(isinstance(e.tag, str) and local(e) == idc for e in schema.iter()):
warn("Ограничения целостности (xs:" + idc + ") в модели XDTO не хранятся — отброшены")
if any(isinstance(e.tag, str) and local(e) == "redefine" for e in schema.iter()):
warn("xs:redefine не поддерживается — переопределения проигнорированы")
if any(isinstance(e.tag, str) and local(e) == "include" for e in schema.iter()):
warn("xs:include проигнорирован: модель XDTO разрешает зависимости только по namespace. "
"Соберите включаемую схему отдельным пакетом и добавьте <xs:import>")
for node in schema:
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
continue
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", "group", "attributeGroup", "notation"):
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'<package xmlns="{XDTO_NS}" xmlns:xs="{XS_NS}" xmlns:xsi="{XSI_NS}"{root_attr_text}>\r\n')
for c in pkg_node.children:
serialize_node(c, 2, {})
out.append("</package>")
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 = [
'<?xml version="1.0" encoding="UTF-8"?>',
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" '
'xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" '
'xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" '
'xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" '
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" '
'xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" '
'xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" '
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" '
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">',
f'\t<XDTOPackage uuid="{uuid.uuid4()}">',
"\t\t<Properties>",
f"\t\t\t<Name>{esc_text(name)}</Name>",
"\t\t\t<Synonym>",
]
for s in syn_items:
md_lines += [
"\t\t\t\t<v8:item>",
f'\t\t\t\t\t<v8:lang>{esc_text(s["Lang"])}</v8:lang>',
f'\t\t\t\t\t<v8:content>{esc_text(s["Content"])}</v8:content>',
"\t\t\t\t</v8:item>",
]
md_lines.append("\t\t\t</Synonym>")
md_lines.append(f"\t\t\t<Comment>{esc_text(comment)}</Comment>" if comment else "\t\t\t<Comment/>")
md_lines.append(f"\t\t\t<Namespace>{esc_text(target_ns)}</Namespace>")
md_lines += ["\t\t</Properties>", "\t</XDTOPackage>", "</MetaDataObject>"]
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 ────────────────────────────
# Ранняя диагностика: отказ платформы при db-update дешевле поймать на сборке
xdto_root_dir = os.path.join(args.OutputDir, "XDTOPackages")
declared_imports = [a["value"] for c in pkg_node.children if c.tag == "import"
for a in c.attrs if a["name"] == "namespace"]
if declared_imports and os.path.isdir(xdto_root_dir):
known_ns = set()
for other in sorted(os.listdir(xdto_root_dir)):
ob = os.path.join(xdto_root_dir, other, "Ext", "Package.bin")
if not os.path.exists(ob):
continue
try:
known_ns.add(_parse_xml(ob).getroot().get("targetNamespace"))
except Exception: # noqa: BLE001
pass
for imp in declared_imports:
if imp not in known_ns and imp not in PLATFORM_NS:
warn(f'Импорт "{imp}" не разрешается: пакета с таким namespace в конфигурации нет. '
"Платформа отвергнет пакет при обновлении — соберите зависимость первой")
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 = _parse_xml(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 warnings_list:
print("")
print("Предупреждения (" + str(len(warnings_list)) +
") — конструкции XSD без точного соответствия в модели XDTO:")
for w in warnings_list:
print(" ! " + w)
print("")
if reg_result == "added":
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> добавлен в ChildObjects")
elif reg_result == "already":
print(f" Configuration.xml: <XDTOPackage>{name}</XDTOPackage> уже зарегистрирован")
else:
print(" Configuration.xml не найден — регистрация пропущена")
@@ -0,0 +1,88 @@
# XSD ↔ XDTO — справочник
## Соответствия
| XML Schema | Модель XDTO |
|---|---|
| `xs:schema/@targetNamespace` | пространство имён пакета |
| `elementFormDefault` / `attributeFormDefault` | `elementFormQualified` / `attributeFormQualified` |
| `xs:import/@namespace` | зависимость от другого пакета (разрешается по namespace) |
| `xs:complexType` | объектный тип |
| `xs:simpleType` | тип значения |
| `xs:element` / `xs:attribute` на верхнем уровне | глобальное свойство пакета |
| `xs:element` / `xs:attribute` внутри типа | свойство типа |
| `@minOccurs` / `@maxOccurs="unbounded"` | `lowerBound` / `upperBound="-1"` |
| `@nillable`, `@default`, `@fixed`, `@ref` | те же по смыслу |
| анонимный `xs:simpleType`/`xs:complexType` в объявлении | встроенный тип свойства |
| `xs:complexContent/xs:extension/@base` | наследование типа |
| `@abstract`, `@mixed` | те же |
| `xs:choice` | тип-выбор одного из вариантов |
| `xs:any` + `xs:anyAttribute` | открытый тип |
| `xs:simpleContent/xs:extension/@base` | свойство собственного значения элемента |
| `xs:restriction` + фасеты | базовый тип + ограничения |
| `xs:pattern`, `xs:enumeration` | те же |
| `xs:list/@itemType` | список |
| `xs:union/@memberTypes` | объединение |
Порядок объявлений верхнего уровня в XSD произвольный — навык сам расставит их
в порядке, который требует модель.
## Аннотации `xdto:`
Пространство имён — `http://v8.1c.ru/8.1/xdto`. Нужны только там, где XML Schema
не может выразить то, что умеет модель. В большинстве схем не нужны вовсе.
Правило: **чего XSD сказать не может — пиши атрибутом `xdto:` с тем же именем,
что и в модели**.
| Аннотация | Где | Назначение |
|---|---|---|
| `xdto:nillable` | `xs:attribute` | `nillable` у свойства-атрибута (XSD допускает только у элементов) |
| `xdto:lowerBound`, `xdto:upperBound` | `xs:attribute` | кратность свойства-атрибута |
| `xdto:qualified` | объявление | переопределение `*FormQualified` для одного свойства |
| `xdto:name` | объявление | имя свойства, если XML-имя не годится как идентификатор 1С; XML-имя уйдёт в `localName` |
| `xdto:form` | `xs:element` | записать `form` явно |
| `xdto:variety` | `xs:restriction`, `xs:list`, `xs:union` | записать разновидность типа явно |
| `xdto:open`, `xdto:abstract`, `xdto:mixed`, `xdto:ordered`, `xdto:sequenced` | `xs:complexType` | флаги типа, не выводимые из модели содержимого |
| `xdto:order` | `xs:complexType` | исходный порядок свойств, если он не «атрибуты первыми»; имена через `\|` |
| `xdto:textName`, `xdto:textlowerBound`, `xdto:textupperBound`, `xdto:textnillable` | `xs:extension` в `xs:simpleContent` | параметры свойства собственного значения |
| `xdto:type` | `xs:enumeration` | тип литерала перечисления |
| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого |
| `xdto:memberTypesForm="prefixed"` | `xs:union` | записать состав объединения префиксами, а не `{ns}имя` |
| `xdto:declareNs` | `xs:union` | объявить префикс пространства имён на узле |
| `xdto:elementFormQualified`, `xdto:attributeFormQualified` | `xs:schema` | записать флаги явно |
Пример:
```xml
<xs:complexType name="КонтактнаяИнформация" xdto:sequenced="true">
<xs:sequence>
<xs:element name="Комментарий" type="xs:string" minOccurs="0"/>
<xs:element name="Адрес по документу" type="xs:string"
xdto:name="Адрес_по_документу" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Представление" type="xs:string"
xdto:nillable="true" xdto:lowerBound="0"/>
</xs:complexType>
```
Аннотации, которые проставляет `/xdto-decompile` при выгрузке существующего пакета,
писать вручную не нужно — они нужны, чтобы обратная сборка вернула ровно тот же файл.
## Свойства объекта метаданных
```xml
<xs:annotation>
<xs:appinfo>
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
<xdto:name>ОбменСБанком</xdto:name>
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
<xdto:synonym lang="en">Bank exchange</xdto:synonym>
<xdto:comment>Формат 1С:Предприятие — Клиент банка</xdto:comment>
</xdto:package>
</xs:appinfo>
</xs:annotation>
```
Пространство имён пакета берётся из `targetNamespace` и здесь не дублируется.
Параметры `-Name`, `-Synonym`, `-Comment` имеют приоритет над этим блоком.
+59
View File
@@ -0,0 +1,59 @@
---
name: xdto-decompile
description: Выгрузка пакета XDTO 1С в XML-схему (XSD). Используй когда нужно получить схему существующего пакета — чтобы переработать её целиком, отдать контрагенту или перенести пакет в другую конфигурацию
argument-hint: <PackagePath> [-OutFile <файл.xsd>]
allowed-tools:
- Bash
- Read
- Glob
---
# /xdto-decompile — Выгрузка пакета XDTO в XML-схему
Превращает пакет XDTO в обычную XML-схему — читаемую и редактируемую.
Заменяет чтение `Ext/Package.bin` напрямую.
## Параметры
| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `PackagePath` | да | Каталог пакета, путь к `Ext/Package.bin` или к `<Имя>.xml` объекта метаданных. Псевдоним — `-Path` |
| `OutFile` | нет | Записать схему в файл (UTF-8 с BOM). Без него — вывод в stdout |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-decompile.ps1" -PackagePath "<путь>"
```
Примеры:
```powershell
... -PackagePath src/XDTOPackages/ОбменСБанком
... -PackagePath src/XDTOPackages/ОбменСБанком -OutFile bank.xsd
```
## Переработка схемы целиком
`/xdto-decompile` → правка XSD → `/xdto-compile -Force` возвращает пакет без потерь,
включая имя, синоним и комментарий объекта метаданных — они выгружаются
в `xs:annotation/xs:appinfo`.
Этот же путь даёт версионную копию пакета: смени в схеме `targetNamespace` и собери
её с `-Name` нового пакета — имя и синоним задаются флагами `/xdto-compile`, внутри
схемы их править не нужно. Меняя пространство имён, поправь **и объявление `xmlns`
с тем же URI**: внутренние ссылки пользуются им как префиксом. Заменять все вхождения
строки нельзя — пострадает импорт пространства имён, для которого старый URI является
префиксом (`urn:пример:обмен` и `urn:пример:обмен:legacy`).
Этот путь нужен, когда схему меняют широко или сначала надо разобраться, как она
устроена. Чтобы поправить одно свойство, схему целиком читать не нужно — `/xdto-edit`.
Если нужна не схема, а сводка «что присвоить и что обязательно», — `/xdto-info`.
В схеме могут встретиться атрибуты с префиксом `xdto:` — так записано то, что
XML Schema выразить не может (например `nillable` у атрибута). Схема при этом остаётся
валидной, валидаторы такие атрибуты игнорируют. Трогать их обычно не нужно; смысл
каждого описан в `xsd-reference.md` навыка `/xdto-compile`.
## Передача схемы наружу
Полученную XSD можно отдавать контрагенту как есть — она валидна и не теряет
данных пакета. Штатная команда «Экспорт XML-схемы» в Конфигураторе для этого
хуже: она теряет `nillable` у свойств-атрибутов.
@@ -0,0 +1,612 @@
# 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
# <Name>/Ext/Package.bin -> <Name>.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 "Ожидался корневой <package>, получен <$($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("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;").Replace('"', "&quot;")
}
function EscText([string]$s) {
if ($null -eq $s) { return "" }
return $s.Replace("&", "&amp;").Replace("<", "&lt;").Replace(">", "&gt;")
}
# --- 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:<same name as in Package.bin>.
# 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<xs:$f value=`"$(Esc $v)`"/>" }
}
foreach ($child in $el.ChildNodes) {
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
if ($child.get_LocalName() -eq "pattern") {
X "$indent<xs:pattern value=`"$(Esc $child.InnerText)`"/>"
} 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<xs:enumeration value=`"$(Esc $child.InnerText)`"$m/>"
}
}
}
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<xs:list$(Attrs @('itemType', $itemType))$mv/>"
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<xs:union$(Attrs @('memberTypes', $memberTypes))$mv/>"
} else {
X "$indent<xs:union$(Attrs @('memberTypes', $memberTypes))$mv>"
foreach ($c in $anon) {
X "$indent`t<xs:simpleType>"
Emit-SimpleTypeBody $c "$indent`t`t"
X "$indent`t</xs:simpleType>"
}
X "$indent</xs:union>"
}
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<xs:restriction$(Attrs @('base', $base))$mv>"
if ($anonBase) {
X "$indent`t<xs:simpleType>"
Emit-SimpleTypeBody $anonBase "$indent`t`t"
X "$indent`t</xs:simpleType>"
}
Emit-Facets $el "$indent`t"
X "$indent</xs:restriction>"
} else {
X "$indent<xs:restriction$(Attrs @('base', $base))$mv/>"
}
}
# --- 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"
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
# fixed="V" несёт само значение. Переводим, а не копируем.
$defOut = $def
$fixOut = $null
$fixMirror = ""
if ($fix -eq "true" -and $null -ne $def) { $fixOut = $def; $defOut = $null }
elseif ($null -ne $fix) { $fixMirror = Mirror "fixed" $fix }
$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
$m += $fixMirror
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type, 'default', $defOut, 'fixed', $fixOut)
if ($anon) {
X "$indent<xs:attribute$body$m>"
X "$indent`t<xs:simpleType>"
Emit-SimpleTypeBody $anon "$indent`t`t"
X "$indent`t</xs:simpleType>"
X "$indent</xs:attribute>"
} else {
X "$indent<xs:attribute$body$m/>"
}
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)
$m += $fixMirror
$body = Attrs @('name', $xmlName, 'ref', $ref, 'type', $type,
'minOccurs', $minOccurs, 'maxOccurs', $maxOccurs,
'nillable', $nill, 'default', $defOut, 'fixed', $fixOut)
if ($anon) {
X "$indent<xs:element$body$m>"
if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
$anonBase = Convert-QName $anon (A $anon "base")
if ($anonBase) {
X "$indent`t<xs:complexType$(ComplexTypeAttrs $anon)>"
X "$indent`t`t<xs:complexContent>"
X "$indent`t`t`t<xs:extension$(Attrs @('base', $anonBase))>"
Emit-ComplexTypeBody $anon "$indent`t`t`t`t"
X "$indent`t`t`t</xs:extension>"
X "$indent`t`t</xs:complexContent>"
X "$indent`t</xs:complexType>"
} else {
X "$indent`t<xs:complexType$(ComplexTypeAttrs $anon)>"
Emit-ComplexTypeBody $anon "$indent`t`t"
X "$indent`t</xs:complexType>"
}
} else {
X "$indent`t<xs:simpleType>"
Emit-SimpleTypeBody $anon "$indent`t`t"
X "$indent`t</xs:simpleType>"
}
X "$indent</xs:element>"
} else {
X "$indent<xs:element$body$m/>"
}
}
# --- 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<xs:simpleContent>"
X "$indent`t<xs:extension$(Attrs @('base', $tType))$tm>"
foreach ($a in $attrs) { Emit-Property $a "$indent`t`t" $false }
X "$indent`t</xs:extension>"
X "$indent</xs:simpleContent>"
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<xs:any namespace=`"##any`" processContents=`"lax`" minOccurs=`"0`" maxOccurs=`"unbounded`"/>"
}
X "$indent</$particleTag>"
}
foreach ($a in $attrs) { Emit-Property $a "$indent" $false }
if ($open -eq "true") {
X "$indent<xs:anyAttribute namespace=`"##any`" processContents=`"lax`"/>"
}
}
# 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<xs:import namespace=`"$(Esc $node.GetAttribute('namespace'))`"/>"
}
"property" {
Emit-Property $node "`t" $true
}
"valueType" {
$name = A $node "name"
X "`t<xs:simpleType$(Attrs @('name', $name))>"
Emit-SimpleTypeBody $node "`t`t"
X "`t</xs:simpleType>"
}
"objectType" {
$name = A $node "name"
$base = Convert-QName $node (A $node "base")
$cta = ComplexTypeAttrs $node
if ($base) {
X "`t<xs:complexType$(Attrs @('name', $name))$cta>"
X "`t`t<xs:complexContent>"
X "`t`t`t<xs:extension$(Attrs @('base', $base))>"
Emit-ComplexTypeBody $node "`t`t`t`t"
X "`t`t`t</xs:extension>"
X "`t`t</xs:complexContent>"
X "`t</xs:complexType>"
} 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<xs:complexType$(Attrs @('name', $name))$cta/>"
} else {
X "`t<xs:complexType$(Attrs @('name', $name))$cta>"
Emit-ComplexTypeBody $node "`t`t"
X "`t</xs:complexType>"
}
}
}
}
}
$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 "<xs:schema$nsDecls$(Attrs @('targetNamespace', $targetNs))$schemaAttrs>"
if ($meta) {
X "`t<xs:annotation>"
X "`t`t<xs:appinfo>"
X "`t`t`t<xdto:package xmlns:xdto=`"$XDTO_NS`">"
if ($null -ne $meta.Name) { X "`t`t`t`t<xdto:name>$(EscText $meta.Name)</xdto:name>" }
if ($null -ne $meta.Comment -and $meta.Comment -ne "") { X "`t`t`t`t<xdto:comment>$(EscText $meta.Comment)</xdto:comment>" }
foreach ($s in $meta.Synonym) {
X "`t`t`t`t<xdto:synonym lang=`"$(Esc $s.Lang)`">$(EscText $s.Content)</xdto:synonym>"
}
X "`t`t`t</xdto:package>"
X "`t`t</xs:appinfo>"
X "`t</xs:annotation>"
}
[void]$sb.Append($bodyBuilder.ToString())
X "</xs:schema>"
# --- 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)
}
@@ -0,0 +1,593 @@
# 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()
def _parse_xml(source, from_string=False):
"""Разбор с узким отступлением для не-URI пространств имён.
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
иначе по-настоящему битый XML перестал бы отличаться от корректного.
"""
try:
return (etree.fromstring(source) if from_string else etree.parse(source))
except etree.XMLSyntaxError as e:
if "is not a valid URI" not in str(e):
raise
p = etree.XMLParser(recover=True)
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
# ── 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 = _parse_xml(bin_path)
pkg = doc.getroot()
def local(el):
return etree.QName(el).localname
if local(pkg) != "package":
print(f"Ожидался корневой <package>, получен <{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("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;").replace('"', "&quot;")
def esc_text(s):
if s is None:
return ""
return str(s).replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")
# ── 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}<xs:{f} value="{esc(v)}"/>')
for child in el:
if not isinstance(child.tag, str):
continue
ln = local(child)
if ln == "pattern":
X(f'{indent}<xs:pattern value="{esc(child.text or "")}"/>')
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}<xs:enumeration value="{esc(child.text or "")}"{m}/>')
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}<xs:list{attrs(["itemType", item_type])}{mv}/>')
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}<xs:union{attrs(["memberTypes", member_types])}{mv}/>')
else:
X(f'{indent}<xs:union{attrs(["memberTypes", member_types])}{mv}>')
for c in anon:
X(f"{indent}\t<xs:simpleType>")
emit_simple_type_body(c, indent + "\t\t")
X(f"{indent}\t</xs:simpleType>")
X(f"{indent}</xs:union>")
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}<xs:restriction{attrs(["base", base])}{mv}>')
if anon_base is not None:
X(f"{indent}\t<xs:simpleType>")
emit_simple_type_body(anon_base, indent + "\t\t")
X(f"{indent}\t</xs:simpleType>")
emit_facets(el, indent + "\t")
X(f"{indent}</xs:restriction>")
else:
X(f'{indent}<xs:restriction{attrs(["base", base])}{mv}/>')
# ── 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")
# В модели fixed — булев флаг, значение лежит в default; в XSD наоборот:
# fixed="V" несёт само значение. Переводим, а не копируем.
def_out, fix_out, fix_mirror = default, None, ""
if fixed == "true" and default is not None:
fix_out, def_out = default, None
elif fixed is not None:
fix_mirror = mirror("fixed", 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
m += fix_mirror
body = attrs(["name", xml_name, "ref", ref, "type", type_, "default", def_out, "fixed", fix_out])
if anon is not None:
X(f"{indent}<xs:attribute{body}{m}>")
X(f"{indent}\t<xs:simpleType>")
emit_simple_type_body(anon, indent + "\t\t")
X(f"{indent}\t</xs:simpleType>")
X(f"{indent}</xs:attribute>")
else:
X(f"{indent}<xs:attribute{body}{m}/>")
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)
m += fix_mirror
body = attrs(["name", xml_name, "ref", ref, "type", type_,
"minOccurs", min_occurs, "maxOccurs", max_occurs,
"nillable", nill, "default", def_out, "fixed", fix_out])
if anon is not None:
X(f"{indent}<xs:element{body}{m}>")
if anon.get(f"{{{XSI_NS}}}type") == "ObjectType":
anon_base = convert_qname(anon, anon.get("base"))
if anon_base:
X(f"{indent}\t<xs:complexType{complex_type_attrs(anon)}>")
X(f"{indent}\t\t<xs:complexContent>")
X(f'{indent}\t\t\t<xs:extension{attrs(["base", anon_base])}>')
emit_complex_type_body(anon, indent + "\t\t\t\t")
X(f"{indent}\t\t\t</xs:extension>")
X(f"{indent}\t\t</xs:complexContent>")
X(f"{indent}\t</xs:complexType>")
else:
X(f"{indent}\t<xs:complexType{complex_type_attrs(anon)}>")
emit_complex_type_body(anon, indent + "\t\t")
X(f"{indent}\t</xs:complexType>")
else:
X(f"{indent}\t<xs:simpleType>")
emit_simple_type_body(anon, indent + "\t\t")
X(f"{indent}\t</xs:simpleType>")
X(f"{indent}</xs:element>")
else:
X(f"{indent}<xs:element{body}{m}/>")
# ── 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}<xs:simpleContent>")
X(f'{indent}\t<xs:extension{attrs(["base", t_type])}{tm}>')
for a in attr_props:
emit_property(a, indent + "\t\t")
X(f"{indent}\t</xs:extension>")
X(f"{indent}</xs:simpleContent>")
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<xs:any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>')
X(f"{indent}</{particle_tag}>")
for a in attr_props:
emit_property(a, indent)
if open_ == "true":
X(f'{indent}<xs:anyAttribute namespace="##any" processContents="lax"/>')
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 = _parse_xml(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<xs:import namespace="{esc(node.get("namespace"))}"/>')
elif ln == "property":
emit_property(node, "\t")
elif ln == "valueType":
X(f'\t<xs:simpleType{attrs(["name", node.get("name")])}>')
emit_simple_type_body(node, "\t\t")
X("\t</xs:simpleType>")
elif ln == "objectType":
name = node.get("name")
base = convert_qname(node, node.get("base"))
cta = complex_type_attrs(node)
if base:
X(f'\t<xs:complexType{attrs(["name", name])}{cta}>')
X("\t\t<xs:complexContent>")
X(f'\t\t\t<xs:extension{attrs(["base", base])}>')
emit_complex_type_body(node, "\t\t\t\t")
X("\t\t\t</xs:extension>")
X("\t\t</xs:complexContent>")
X("\t</xs:complexType>")
else:
has_body = any(isinstance(c.tag, str) for c in node)
if not has_body and node.get("open") != "true":
X(f'\t<xs:complexType{attrs(["name", name])}{cta}/>')
else:
X(f'\t<xs:complexType{attrs(["name", name])}{cta}>')
emit_complex_type_body(node, "\t\t")
X("\t</xs:complexType>")
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'<xs:schema{ns_decls}{attrs(["targetNamespace", target_ns])}{schema_attrs}>')
if meta:
X("\t<xs:annotation>")
X("\t\t<xs:appinfo>")
X(f'\t\t\t<xdto:package xmlns:xdto="{XDTO_NS}">')
if meta["Name"] is not None:
X(f'\t\t\t\t<xdto:name>{esc_text(meta["Name"])}</xdto:name>')
if meta["Comment"]:
X(f'\t\t\t\t<xdto:comment>{esc_text(meta["Comment"])}</xdto:comment>')
for s in meta["Synonym"]:
X(f'\t\t\t\t<xdto:synonym lang="{esc(s["Lang"])}">{esc_text(s["Content"])}</xdto:synonym>')
X("\t\t\t</xdto:package>")
X("\t\t</xs:appinfo>")
X("\t</xs:annotation>")
lines.extend(body_lines)
X("</xs:schema>")
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)
+103
View File
@@ -0,0 +1,103 @@
---
name: xdto-edit
description: Точечное редактирование пакета XDTO 1С. Используй когда нужно добавить, изменить или удалить тип или свойство в существующем пакете, переименовать пакет, сменить пространство имён
argument-hint: <PackagePath> -Operation <операция> [-Target <путь>] [-Value <значение>] [-NoValidate]
allowed-tools:
- Bash
- Read
- Glob
---
# /xdto-edit — Точечное редактирование пакета XDTO
Меняет один элемент пакета, не требуя читать и переписывать всю схему — для больших
пакетов (`EnterpriseData` — около мегабайта) это единственный практичный путь.
Если нужно переработать схему целиком или сперва разобраться, как она устроена, —
`/xdto-decompile` → правка XSD → `/xdto-compile -Force`.
## Параметры
| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `PackagePath` | да | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml`. Псевдоним — `-Path` |
| `Operation` | да | Операция из таблицы ниже |
| `Target` | зависит | Адрес: имя типа или путь `Тип.Свойство` |
| `Value` | зависит | Фрагмент XSD, литерал, URI или текст. `@путь` — взять содержимое из файла |
| `NoValidate` | нет | Не запускать `xdto-validate` после правки |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-edit.ps1" -PackagePath "<путь>" -Operation <op> -Target "<адрес>" -Value "<значение>"
```
## Операции
| Операция | `-Target` | `-Value` |
|---|---|---|
| `add-property` | имя типа | `<xs:element>` или `<xs:attribute>` |
| `replace-property` | `Тип.Свойство` | новое объявление целиком |
| `remove-property` | `Тип.Свойство` | — |
| `add-type` | — | `<xs:complexType>` или `<xs:simpleType>` |
| `remove-type` | имя типа | — |
| `add-enum` | имя типа значения | литерал |
| `add-import` | — | URI пространства имён |
| `rename` | — | новое имя объекта метаданных |
| `set-synonym` | — | синоним |
| `set-comment` | — | комментарий |
| `set-namespace` | — | новый URI пространства имён |
Батч через `;;` там, где перечисление осмысленно: `remove-property`, `remove-type`,
`add-enum`, `add-import`.
```powershell
... -Operation add-property -Target "Платёж" -Value '<xs:element name="Комментарий" type="xs:string" minOccurs="0"/>'
... -Operation remove-property -Target "Платёж.Комментарий ;; Платёж.Черновик"
... -Operation add-enum -Target "ВидДокумента" -Value "Инкассо ;; Аккредитив"
... -Operation rename -Value ОбменСБанком
```
Содержимое всегда описывается фрагментом XML-схемы — тем же языком, что и в
`/xdto-compile`. Отдельных параметров вида `-MinOccurs` нет: чтобы поменять свойство,
дай его новое объявление целиком через `replace-property`.
Ограничение длины и прочие фасеты задаются вложенным типом:
```xml
<xs:element name="Комментарий" minOccurs="0">
<xs:simpleType>
<xs:restriction base="xs:string"><xs:maxLength value="200"/></xs:restriction>
</xs:simpleType>
</xs:element>
```
Многострочный фрагмент передавай файлом: `-Value "@frag.xsd"`. Инлайн через оболочку
надёжен только для однострочных фрагментов без вложенных кавычек.
## Адресация
Путь `Тип.Свойство`. Точка безопасна: имена типов и свойств — идентификаторы 1С.
Путь продолжается внутрь встроенных типов: `ПлатежныйДокумент.ДатаСписано.ИдПлатежа`.
Посмотреть, что есть в пакете и как называется нужный тип, — `/xdto-info`.
Перед правкой типа полезно `/xdto-info -Mode used-by -Name <Тип>`: покажет,
кого затронет изменение.
## Что тянет за собой переименование и смена namespace
`rename` меняет имя в объекте метаданных, переименовывает файл `<Имя>.xml` и каталог
`<Имя>/`, правит регистрацию в `Configuration.xml`. Новое имя проверяется на
допустимость как идентификатор 1С и на занятость.
`set-namespace` меняет `targetNamespace`, все внутренние ссылки на собственные типы
и `<Namespace>` объекта метаданных. Пакеты, импортирующие старое пространство имён,
**не изменяются** — при версионировании они и должны продолжать смотреть на прежнее.
Навык их перечислит; если правка не версионная, поправь их импорты сам.
После правки автоматически запускается `/xdto-validate` — отключается через `-NoValidate`.
## Типичный workflow
1. `/xdto-info <пакет>` — найти нужный тип
2. `/xdto-info <пакет> -Mode used-by -Name <Тип>` — если меняешь существующее
3. `/xdto-edit <пакет> -Operation <op> …`
4. `/db-load-xml` + `/db-update`
@@ -0,0 +1,562 @@
# xdto-edit v1.0 — Point edits of a 1C XDTO package
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)]
[Alias('Path')]
[string]$PackagePath,
[Parameter(Mandatory=$true)]
[ValidateSet("add-property", "replace-property", "remove-property",
"add-type", "remove-type", "add-enum", "add-import",
"rename", "set-synonym", "set-comment", "set-namespace")]
[string]$Operation,
[string]$Target,
[string]$Value,
[switch]$NoValidate
)
$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"
$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 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 {
$d = $targetPath
for ($i = 0; $i -lt 20 -and $d; $i++) {
foreach ($x in @(Get-ChildItem -Path $d -Filter "*.xml" -File -ErrorAction SilentlyContinue)) {
if (Test-ExternalObjectRoot $x.FullName) { return }
}
$cfgXml = Join-Path $d "Configuration.xml"
$supportBin = Join-Path (Join-Path $d "Ext") "ParentConfigurations.bin"
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 {}
}
# --- Resolve package ------------------------------------------------------------
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
$PackagePath = Join-Path (Get-Location).Path $PackagePath
}
$pkgDir = $null
if (Test-Path $PackagePath -PathType Container) {
if (Test-Path (Join-Path (Join-Path $PackagePath "Ext") "Package.bin")) { $pkgDir = $PackagePath }
} elseif ((Test-Path $PackagePath -PathType Leaf) -and ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin")) {
$pkgDir = Split-Path (Split-Path $PackagePath -Parent) -Parent
} elseif ($PackagePath.EndsWith(".xml")) {
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath),
[System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
if (Test-Path (Join-Path (Join-Path $stem "Ext") "Package.bin")) { $pkgDir = $stem }
}
if (-not $pkgDir) { throw "Не найден пакет XDTO по пути: $PackagePath" }
$pkgName = [System.IO.Path]::GetFileName($pkgDir)
$xdtoRoot = Split-Path $pkgDir -Parent
$configRoot = Split-Path $xdtoRoot -Parent
$binFile = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
$mdFile = Join-Path $xdtoRoot "$pkgName.xml"
$configXml = Join-Path $configRoot "Configuration.xml"
# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
# через powershell.exe -File ненадёжно: вложенные кавычки схлопываются на границе
# процессов, и вместо понятной ошибки получается сырой сбой разбора XML.
if ($Value -and $Value.StartsWith("@")) {
$valueFile = $Value.Substring(1)
if (-not [System.IO.Path]::IsPathRooted($valueFile)) {
$valueFile = Join-Path (Get-Location).Path $valueFile
}
if (-not (Test-Path $valueFile -PathType Leaf)) { throw "Файл значения не найден: $valueFile" }
$Value = [System.IO.File]::ReadAllText($valueFile).Trim()
}
Assert-EditAllowed $pkgDir
$encBom = New-Object System.Text.UTF8Encoding($true)
# --- Sibling skills -------------------------------------------------------------
# Правка идёт через уже проверенный round-trip: пакет выгружается в XSD, операция
# применяется к схеме, пакет собирается обратно. Второго эмиттера не заводим —
# байт-точность для всего нетронутого достаётся от компилятора.
$decompileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-decompile") "scripts\xdto-decompile.ps1"
$compileScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-compile") "scripts\xdto-compile.ps1"
$validateScript = Join-Path (Join-Path $PSScriptRoot "..\..\xdto-validate") "scripts\xdto-validate.ps1"
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD ↔ модель
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
# в нетронутом» держится именно на том, что код тот же самый.
# Проверяем комплектность заранее, чтобы не падать на середине правки.
function Assert-SiblingsPresent([string]$operation) {
$needed = @{}
if (@("rename", "set-synonym", "set-comment") -notcontains $operation) {
$needed["xdto-decompile"] = $decompileScript
$needed["xdto-compile"] = $compileScript
}
$missing = @()
foreach ($k in $needed.Keys) { if (-not (Test-Path $needed[$k])) { $missing += $k } }
if ($missing.Count -gt 0) {
$skillsRoot = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
throw ("Навык неработоспособен: рядом нет " + ($missing -join ", ") + ".`n" +
"Операция `"$operation`" выполняется через " +
$(if ($missing.Count -gt 1) { "них" } else { "него" }) + ".`n" +
"Ожидаются в каталоге навыков: $skillsRoot")
}
}
function Invoke-Sibling([string]$script, [string[]]$argList, [string]$what) {
if (-not (Test-Path $script)) { throw "Не найден навык $what по пути: $script" }
$out = & powershell.exe -NoProfile -ExecutionPolicy Bypass -File $script @argList 2>&1
if ($LASTEXITCODE -ne 0) { throw "$what завершился с ошибкой:`n$($out -join "`n")" }
return $out
}
# --- Metadata object edits ------------------------------------------------------
function Edit-Metadata([string]$field, [string]$newValue) {
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $true
$doc.Load($mdFile)
$nsm = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
$nsm.AddNamespace("md", $MD_NS)
$nsm.AddNamespace("v8", $V8_NS)
$props = $doc.SelectSingleNode("//md:XDTOPackage/md:Properties", $nsm)
if (-not $props) { throw "В объекте метаданных не найден блок <Properties>" }
switch ($field) {
"Name" {
$n = $props.SelectSingleNode("md:Name", $nsm)
if (-not $n) { throw "В объекте метаданных нет <Name>" }
$n.InnerText = $newValue
}
"Comment" {
$c = $props.SelectSingleNode("md:Comment", $nsm)
if (-not $c) {
$c = $doc.CreateElement("Comment", $MD_NS)
$props.AppendChild($c) | Out-Null
}
$c.InnerText = $newValue
}
"Namespace" {
$ns = $props.SelectSingleNode("md:Namespace", $nsm)
if (-not $ns) { throw "В объекте метаданных нет <Namespace>" }
$ns.InnerText = $newValue
}
"Synonym" {
$syn = $props.SelectSingleNode("md:Synonym", $nsm)
if (-not $syn) {
$syn = $doc.CreateElement("Synonym", $MD_NS)
$props.AppendChild($syn) | Out-Null
}
$item = $syn.SelectSingleNode("v8:item[v8:lang='ru']", $nsm)
if (-not $item) {
$item = $doc.CreateElement("v8", "item", $V8_NS)
$lang = $doc.CreateElement("v8", "lang", $V8_NS); $lang.InnerText = "ru"
$cont = $doc.CreateElement("v8", "content", $V8_NS)
$item.AppendChild($lang) | Out-Null
$item.AppendChild($cont) | Out-Null
$syn.AppendChild($item) | Out-Null
}
$item.SelectSingleNode("v8:content", $nsm).InnerText = $newValue
}
}
$settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom
$settings.Indent = $false
$stream = New-Object System.IO.FileStream($mdFile, [System.IO.FileMode]::Create)
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
$doc.Save($writer)
$writer.Close(); $stream.Close()
}
function Rename-Package([string]$newName) {
if ($newName -notmatch '^[\wЀ-ӿ]+$' -or $newName -match '^\d') {
throw "`"$newName`" не является допустимым идентификатором 1С"
}
$newMd = Join-Path $xdtoRoot "$newName.xml"
$newDir = Join-Path $xdtoRoot $newName
if ((Test-Path $newMd) -or (Test-Path $newDir)) { throw "Имя `"$newName`" уже занято другим пакетом" }
Edit-Metadata "Name" $newName
Move-Item $mdFile $newMd
Move-Item $pkgDir $newDir
if (Test-Path $configXml) {
$cfg = New-Object System.Xml.XmlDocument
$cfg.PreserveWhitespace = $true
$cfg.Load($configXml)
$nsm = New-Object System.Xml.XmlNamespaceManager($cfg.NameTable)
$nsm.AddNamespace("md", $MD_NS)
$found = $false
foreach ($e in $cfg.SelectNodes("//md:Configuration/md:ChildObjects/md:XDTOPackage", $nsm)) {
if ($e.InnerText -eq $pkgName) { $e.InnerText = $newName; $found = $true; break }
}
if ($found) {
$s = New-Object System.Xml.XmlWriterSettings
$s.Encoding = $encBom; $s.Indent = $false
$st = New-Object System.IO.FileStream($configXml, [System.IO.FileMode]::Create)
$w = [System.Xml.XmlWriter]::Create($st, $s)
$cfg.Save($w); $w.Close(); $st.Close()
Write-Host " Configuration.xml: <XDTOPackage> переименован в $newName"
} else {
Write-Warning "В Configuration.xml не найдена запись <XDTOPackage>$pkgName</XDTOPackage> — зарегистрируйте пакет вручную"
}
}
Write-Host "✓ Пакет переименован: $pkgName$newName"
Write-Host " Перемещены: $newName.xml, $newName/"
}
# --- Model edits through the XSD round-trip -------------------------------------
$XSD_DECL = @{ "add-property" = "element"; "replace-property" = "element"; "remove-property" = "element" }
function Get-SchemaChildren([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) }
}
return ,$res
}
function Get-SchemaFirst([System.Xml.XmlElement]$el, [string]$local) {
$r = Get-SchemaChildren $el $local
if ($r.Count -gt 0) { return $r[0] }
return $null
}
function Find-TypeElement($schema, [string]$typeName) {
foreach ($kind in @("complexType", "simpleType")) {
foreach ($t in (Get-SchemaChildren $schema $kind)) {
if ($t.GetAttribute("name") -eq $typeName) { return $t }
}
}
throw "В пакете нет типа `"$typeName`""
}
# Тело типа: внутрь xs:complexContent/xs:extension, если тип наследуется
function Get-TypeBody([System.Xml.XmlElement]$ct) {
$content = Get-SchemaFirst $ct "complexContent"
if ($content) {
$ext = Get-SchemaFirst $content "extension"
if ($ext) { return $ext }
}
return $ct
}
function Find-Declaration([System.Xml.XmlElement]$body, [string]$propName) {
foreach ($node in $body.SelectNodes(".//*")) {
if ($node.NamespaceURI -ne $XS_NS) { continue }
if (@("element", "attribute") -notcontains $node.get_LocalName()) { continue }
if ($node.GetAttribute("name") -eq $propName) { return $node }
}
return $null
}
# Путь Тип.Свойство[.Вложенное...] — точка безопасна: имена в модели XDTO
# являются идентификаторами 1С и точку содержать не могут
function Resolve-Path($schema, [string]$path) {
$segments = $path.Split(".")
$typeEl = Find-TypeElement $schema $segments[0]
if ($segments.Count -eq 1) { return [pscustomobject]@{ Type = $typeEl; Decl = $null } }
$body = Get-TypeBody $typeEl
$decl = $null
for ($i = 1; $i -lt $segments.Count; $i++) {
$decl = Find-Declaration $body $segments[$i]
if (-not $decl) { throw "По пути `"$path`" не найдено свойство `"$($segments[$i])`"" }
if ($i -lt $segments.Count - 1) {
$inner = Get-SchemaFirst $decl "complexType"
if (-not $inner) { throw "Свойство `"$($segments[$i])`" не содержит вложенного типа — путь дальше не идёт" }
$body = Get-TypeBody $inner
}
}
return [pscustomobject]@{ Type = $typeEl; Decl = $decl }
}
function Import-Fragment($schema, [string]$xml) {
$tmp = New-Object System.Xml.XmlDocument
$nsAttrs = " xmlns:xs=`"$XS_NS`" xmlns:xdto=`"$XDTO_NS`""
$tns = $schema.GetAttribute("targetNamespace")
if ($tns) { $nsAttrs += " xmlns:tns=`"$tns`"" }
foreach ($a in $schema.Attributes) {
if ($a.Prefix -eq "xmlns" -and $a.get_LocalName() -notin @("xs", "xdto", "tns")) {
$nsAttrs += " xmlns:$($a.get_LocalName())=`"$($a.Value)`""
}
}
try { $tmp.LoadXml("<wrap$nsAttrs>$xml</wrap>") }
catch {
throw ("Не удалось разобрать -Value как фрагмент XML-схемы: " + $_.Exception.InnerException.Message + "`n" +
"Получено: " + $xml + "`n" +
"Если фрагмент передан инлайном, кавычки могли схлопнуться на границе процессов — " +
"положите его в файл и укажите -Value `"@путь`".")
}
$res = New-Object System.Collections.ArrayList
foreach ($c in $tmp.DocumentElement.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element) {
[void]$res.Add($schema.OwnerDocument.ImportNode($c, $true))
}
}
if ($res.Count -eq 0) { throw "Во фрагменте нет ни одного элемента: $xml" }
return ,$res
}
function Apply-ModelOperation($schema) {
switch ($Operation) {
"add-property" {
if (-not $Target) { throw "add-property требует -Target <Тип>" }
$loc = Resolve-Path $schema $Target
$body = Get-TypeBody $(if ($loc.Decl) { Get-SchemaFirst $loc.Decl "complexType" } else { $loc.Type })
foreach ($frag in (Import-Fragment $schema $Value)) {
$kind = $frag.get_LocalName()
if ($kind -eq "attribute") {
$body.AppendChild($frag) | Out-Null
} elseif ($kind -eq "element") {
$particle = Get-SchemaFirst $body "sequence"
if (-not $particle) { $particle = Get-SchemaFirst $body "choice" }
if (-not $particle) { $particle = Get-SchemaFirst $body "all" }
if (-not $particle) {
$particle = $schema.OwnerDocument.CreateElement("xs", "sequence", $XS_NS)
$firstAttr = Get-SchemaFirst $body "attribute"
if ($firstAttr) { $body.InsertBefore($particle, $firstAttr) | Out-Null }
else { $body.AppendChild($particle) | Out-Null }
}
$particle.AppendChild($frag) | Out-Null
} else {
throw "add-property ожидает <xs:element> или <xs:attribute>, получен <xs:$kind>"
}
Write-Host " + $($frag.GetAttribute('name')) в тип $Target"
}
}
"replace-property" {
if (-not $Target) { throw "replace-property требует -Target `"Тип.Свойство`"" }
$loc = Resolve-Path $schema $Target
if (-not $loc.Decl) { throw "replace-property требует путь вида `"Тип.Свойство`"" }
$frags = Import-Fragment $schema $Value
if ($frags.Count -ne 1) { throw "replace-property ожидает ровно одно объявление" }
$loc.Decl.ParentNode.ReplaceChild($frags[0], $loc.Decl) | Out-Null
Write-Host " ~ $Target заменено"
}
"remove-property" {
if (-not $Target) { throw "remove-property требует путь `"Тип.Свойство`"" }
foreach ($one in ($Target -split "\s*;;\s*")) {
if (-not $one) { continue }
$loc = Resolve-Path $schema $one
if (-not $loc.Decl) { throw "remove-property требует путь вида `"Тип.Свойство`", получено `"$one`"" }
$loc.Decl.ParentNode.RemoveChild($loc.Decl) | Out-Null
Write-Host " $one удалено"
}
}
"add-type" {
foreach ($frag in (Import-Fragment $schema $Value)) {
if (@("complexType", "simpleType") -notcontains $frag.get_LocalName()) {
throw "add-type ожидает <xs:complexType> или <xs:simpleType>, получен <xs:$($frag.get_LocalName())>"
}
$schema.AppendChild($frag) | Out-Null
Write-Host " + тип $($frag.GetAttribute('name'))"
}
}
"remove-type" {
if (-not $Target) { throw "remove-type требует -Target <Тип>" }
foreach ($one in ($Target -split "\s*;;\s*")) {
if (-not $one) { continue }
$t = Find-TypeElement $schema $one
$t.ParentNode.RemoveChild($t) | Out-Null
Write-Host " тип $one удалён"
}
}
"add-enum" {
if (-not $Target) { throw "add-enum требует -Target <ТипЗначения>" }
$t = Find-TypeElement $schema $Target
$restriction = Get-SchemaFirst $t "restriction"
if (-not $restriction) { throw "Тип `"$Target`" не является ограничением простого типа" }
foreach ($lit in ($Value -split "\s*;;\s*")) {
if (-not $lit) { continue }
$e = $schema.OwnerDocument.CreateElement("xs", "enumeration", $XS_NS)
$e.SetAttribute("value", $lit)
$restriction.AppendChild($e) | Out-Null
Write-Host " + значение `"$lit`" в тип $Target"
}
}
"add-import" {
foreach ($uri in ($Value -split "\s*;;\s*")) {
if (-not $uri) { continue }
$exists = $false
foreach ($i in (Get-SchemaChildren $schema "import")) {
if ($i.GetAttribute("namespace") -eq $uri) { $exists = $true; break }
}
if ($exists) { Write-Host " = импорт $uri уже объявлен"; continue }
$imp = $schema.OwnerDocument.CreateElement("xs", "import", $XS_NS)
$imp.SetAttribute("namespace", $uri)
$firstOther = $null
foreach ($c in $schema.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -notin @("annotation", "import")) { $firstOther = $c; break }
}
if ($firstOther) { $schema.InsertBefore($imp, $firstOther) | Out-Null } else { $schema.AppendChild($imp) | Out-Null }
Write-Host " + импорт $uri"
}
}
"set-namespace" {
if (-not $Value) { throw "set-namespace требует -Value <URI>" }
$old = $schema.GetAttribute("targetNamespace")
# Установка того же значения не отбрасывается: пакет пересобирается вхолостую,
# и это заодно проба точности пути «выгрузить → собрать» на любом пакете
if ($old -eq $Value) { Write-Host " = namespace уже $Value, пакет пересобран без изменений" }
# Меняем и targetNamespace, и объявление префикса, который на него указывал:
# иначе ссылки на собственные типы станут ссылками в чужое пространство имён
$schema.SetAttribute("targetNamespace", $Value)
foreach ($a in @($schema.Attributes)) {
if ($a.Prefix -eq "xmlns" -and $a.Value -eq $old) {
$schema.SetAttribute("xmlns:$($a.get_LocalName())", $Value)
}
}
Write-Host " ~ namespace: $old$Value"
}
}
}
# --- Dispatch -------------------------------------------------------------------
$metaOps = @("rename", "set-synonym", "set-comment")
$touchesModel = ($metaOps -notcontains $Operation)
Assert-SiblingsPresent $Operation
Write-Host "Пакет: $pkgName"
if ($Operation -eq "rename") {
if (-not $Value) { throw "rename требует -Value <НовоеИмя>" }
Rename-Package $Value
$pkgName = $Value
$pkgDir = Join-Path $xdtoRoot $Value
} elseif ($Operation -eq "set-synonym") {
if (-not $Value) { throw "set-synonym требует -Value <текст>" }
Edit-Metadata "Synonym" $Value
Write-Host "✓ Синоним: $Value"
} elseif ($Operation -eq "set-comment") {
Edit-Metadata "Comment" $Value
Write-Host "✓ Комментарий обновлён"
} else {
$tmpDir = Join-Path ([System.IO.Path]::GetTempPath()) ("xdto-edit_" + [guid]::NewGuid().ToString("N").Substring(0, 8))
New-Item -ItemType Directory -Path $tmpDir -Force | Out-Null
try {
$xsdPath = Join-Path $tmpDir "schema.xsd"
Invoke-Sibling $decompileScript @("-PackagePath", $binFile, "-OutFile", $xsdPath) "xdto-decompile" | Out-Null
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($xsdPath)
$schema = $doc.DocumentElement
$oldNamespace = $schema.GetAttribute("targetNamespace")
Apply-ModelOperation $schema
$doc.Save($xsdPath)
Invoke-Sibling $compileScript @("-XsdPath", $xsdPath, "-OutputDir", $configRoot, "-Name", $pkgName, "-Force") "xdto-compile" | Out-Null
if ($Operation -eq "set-namespace") {
Edit-Metadata "Namespace" $Value
# Зависящие пакеты не трогаем: при версионировании они обязаны продолжать
# смотреть на прежний namespace. Но молчать о них нельзя.
$dependents = @()
foreach ($d in (Get-ChildItem $xdtoRoot -Directory -ErrorAction SilentlyContinue)) {
if ($d.Name -eq $pkgName) { continue }
$ob = Join-Path (Join-Path $d.FullName "Ext") "Package.bin"
if (-not (Test-Path $ob)) { continue }
try {
$od = New-Object System.Xml.XmlDocument
$od.Load($ob)
foreach ($imp in $od.DocumentElement.ChildNodes) {
if ($imp.NodeType -eq [System.Xml.XmlNodeType]::Element -and $imp.get_LocalName() -eq "import" -and
$imp.GetAttribute("namespace") -eq $oldNamespace) { $dependents += $d.Name; break }
}
} catch {}
}
if ($dependents.Count -gt 0) {
Write-Host ""
Write-Warning ("Старый namespace импортируют пакеты ($($dependents.Count)): " + ($dependents -join ", ") +
". Они не изменены — при версионировании это верно; если нет, поправьте их импорты.")
}
}
Write-Host "✓ Пакет пересобран: XDTOPackages/$pkgName/Ext/Package.bin"
} finally {
Remove-Item $tmpDir -Recurse -Force -ErrorAction SilentlyContinue
}
}
# --- Validate -------------------------------------------------------------------
if (-not $NoValidate) {
if (Test-Path $validateScript) {
Write-Host ""
Write-Host "--- xdto-validate ---"
& powershell.exe -NoProfile -ExecutionPolicy Bypass -File $validateScript -PackagePath (Join-Path $xdtoRoot $pkgName)
} else {
Write-Host "[SKIP] xdto-validate не найден: $validateScript"
}
}
exit 0
@@ -0,0 +1,541 @@
# xdto-edit v1.0 — Point edits of a 1C XDTO package (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
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"
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
V8_NS = "http://v8.1c.ru/8.1/data/core"
OPS = ["add-property", "replace-property", "remove-property", "add-type", "remove-type",
"add-enum", "add-import", "rename", "set-synonym", "set-comment", "set-namespace"]
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-PackagePath", "-Path", required=True)
parser.add_argument("-Operation", required=True, choices=OPS)
parser.add_argument("-Target", default="")
parser.add_argument("-Value", default="")
parser.add_argument("-NoValidate", action="store_true")
args = parser.parse_args()
def die(msg):
print(msg, file=sys.stderr)
sys.exit(1)
def local(el):
return etree.QName(el).localname
def _parse_xml(source, from_string=False):
"""Разбор с узким отступлением для не-URI пространств имён.
Платформа допускает в targetNamespace произвольную строку, .NET такое принимает,
а libxml2 отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке.
"""
try:
return (etree.fromstring(source) if from_string else etree.parse(source))
except etree.XMLSyntaxError as e:
if "is not a valid URI" not in str(e):
raise
p = etree.XMLParser(recover=True)
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
# ── support guard ────────────────────────────────────────────
# См. docs/1c-support-state-spec.md.
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:
return str(json.load(f).get("editingAllowedCheck") or "deny")
except Exception: # noqa: BLE001
pass
return "deny"
def is_external_object_root(xml_path):
try:
for el in _parse_xml(xml_path).getroot():
if isinstance(el.tag, str):
return local(el) 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
if os.path.exists(os.path.join(d, "Configuration.xml")):
if os.path.exists(os.path.join(d, "Ext", "ParentConfigurations.bin")):
mode = get_edit_mode(d)
if mode == "off":
return
msg = ("Конфигурация находится на поддержке (Ext/ParentConfigurations.bin). "
"Правка может быть запрещена.")
if mode == "warn":
print("WARNING: " + msg, file=sys.stderr)
return
die(msg + " Снимите с поддержки (/support-edit) или задайте "
"editingAllowedCheck в .v8-project.json.")
return
parent = os.path.dirname(d)
if parent == d:
break
d = parent
# ── resolve package ──────────────────────────────────────────
package_path = os.path.abspath(args.PackagePath)
pkg_dir = None
if os.path.isdir(package_path):
if os.path.exists(os.path.join(package_path, "Ext", "Package.bin")):
pkg_dir = package_path
elif os.path.isfile(package_path) and os.path.basename(package_path) == "Package.bin":
pkg_dir = os.path.dirname(os.path.dirname(package_path))
elif package_path.endswith(".xml"):
stem = os.path.join(os.path.dirname(package_path),
os.path.splitext(os.path.basename(package_path))[0])
if os.path.exists(os.path.join(stem, "Ext", "Package.bin")):
pkg_dir = stem
if not pkg_dir:
die(f"Не найден пакет XDTO по пути: {package_path}")
pkg_name = os.path.basename(pkg_dir.rstrip("\\/"))
xdto_root = os.path.dirname(pkg_dir)
config_root = os.path.dirname(xdto_root)
bin_file = os.path.join(pkg_dir, "Ext", "Package.bin")
md_file = os.path.join(xdto_root, pkg_name + ".xml")
config_xml = os.path.join(config_root, "Configuration.xml")
# -Value "@путь" — содержимое берётся из файла. Передавать XSD-фрагмент инлайном
# ненадёжно: вложенные кавычки схлопываются на границе процессов, и вместо понятной
# ошибки получается сырой сбой разбора XML.
if args.Value.startswith("@"):
value_file = args.Value[1:]
if not os.path.isabs(value_file):
value_file = os.path.join(os.getcwd(), value_file)
if not os.path.isfile(value_file):
die("Файл значения не найден: " + value_file)
with open(value_file, encoding="utf-8-sig") as f:
args.Value = f.read().strip()
assert_edit_allowed(pkg_dir)
SKILLS = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
DECOMPILE = os.path.join(SKILLS, "xdto-decompile", "scripts", "xdto-decompile.py")
COMPILE = os.path.join(SKILLS, "xdto-compile", "scripts", "xdto-compile.py")
VALIDATE = os.path.join(SKILLS, "xdto-validate", "scripts", "xdto-validate.py")
# Исключение из автономности навыков, сделанное осознанно: конвертер XSD <-> модель
# нельзя скопировать буквально (xdto-compile — скрипт со сквозным потоком, не библиотека),
# а вторая его реализация разошлась бы с первой. Обещание «правка не меняет ни байта
# в нетронутом» держится именно на том, что код тот же самый.
# Проверяем комплектность заранее, чтобы не падать на середине правки.
def assert_siblings_present(operation):
needed = {}
if operation not in ("rename", "set-synonym", "set-comment"):
needed["xdto-decompile"] = DECOMPILE
needed["xdto-compile"] = COMPILE
missing = [k for k, v in needed.items() if not os.path.exists(v)]
if missing:
die("Навык неработоспособен: рядом нет " + ", ".join(missing) + ".\n"
+ f'Операция "{operation}" выполняется через '
+ ("них" if len(missing) > 1 else "него") + ".\n"
+ "Ожидаются в каталоге навыков: " + SKILLS)
def invoke_sibling(script, argv, what):
if not os.path.exists(script):
die(f"Не найден навык {what} по пути: {script}")
r = subprocess.run([sys.executable, script, *argv], capture_output=True, text=True, encoding="utf-8")
if r.returncode != 0:
die(f"{what} завершился с ошибкой:\n{(r.stderr or '') + (r.stdout or '')}")
return r.stdout
def save_xml(doc, path):
raw = etree.tostring(doc, xml_declaration=True, encoding="UTF-8")
with open(path, "wb") as f:
f.write(b"\xef\xbb\xbf" + raw)
# ── metadata object edits ────────────────────────────────────
def edit_metadata(field, new_value):
doc = _parse_xml(md_file)
props = doc.find(f".//{{{MD_NS}}}XDTOPackage/{{{MD_NS}}}Properties")
if props is None:
die("В объекте метаданных не найден блок <Properties>")
if field in ("Name", "Namespace"):
el = props.find(f"{{{MD_NS}}}{field}")
if el is None:
die(f"В объекте метаданных нет <{field}>")
el.text = new_value
elif field == "Comment":
el = props.find(f"{{{MD_NS}}}Comment")
if el is None:
el = etree.SubElement(props, f"{{{MD_NS}}}Comment")
el.text = new_value
elif field == "Synonym":
syn = props.find(f"{{{MD_NS}}}Synonym")
if syn is None:
syn = etree.SubElement(props, f"{{{MD_NS}}}Synonym")
item = None
for it in syn.iterfind(f"{{{V8_NS}}}item"):
lg = it.find(f"{{{V8_NS}}}lang")
if lg is not None and (lg.text or "") == "ru":
item = it
break
if item is None:
item = etree.SubElement(syn, f"{{{V8_NS}}}item")
etree.SubElement(item, f"{{{V8_NS}}}lang").text = "ru"
etree.SubElement(item, f"{{{V8_NS}}}content")
item.find(f"{{{V8_NS}}}content").text = new_value
save_xml(doc, md_file)
def rename_package(new_name):
global pkg_name, pkg_dir
# \w с re.UNICODE уже покрывает кириллицу; явные диапазоны только плодят ошибки
if not re.match(r"^\w+$", new_name, re.UNICODE) or re.match(r"^\d", new_name):
die(f'"{new_name}" не является допустимым идентификатором 1С')
new_md = os.path.join(xdto_root, new_name + ".xml")
new_dir = os.path.join(xdto_root, new_name)
if os.path.exists(new_md) or os.path.exists(new_dir):
die(f'Имя "{new_name}" уже занято другим пакетом')
edit_metadata("Name", new_name)
shutil.move(md_file, new_md)
shutil.move(pkg_dir, new_dir)
if os.path.exists(config_xml):
cfg = _parse_xml(config_xml)
found = False
for e in cfg.iterfind(f".//{{{MD_NS}}}Configuration/{{{MD_NS}}}ChildObjects/{{{MD_NS}}}XDTOPackage"):
if (e.text or "") == pkg_name:
e.text = new_name
found = True
break
if found:
save_xml(cfg, config_xml)
print(f" Configuration.xml: <XDTOPackage> переименован в {new_name}")
else:
print(f"WARNING: В Configuration.xml не найдена запись <XDTOPackage>{pkg_name}</XDTOPackage> — "
"зарегистрируйте пакет вручную", file=sys.stderr)
print(f"✓ Пакет переименован: {pkg_name}{new_name}")
print(f" Перемещены: {new_name}.xml, {new_name}/")
pkg_name = new_name
pkg_dir = new_dir
# ── model edits through the XSD round-trip ───────────────────
def xs_children(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 xs_first(el, name):
r = xs_children(el, name)
return r[0] if r else None
def find_type_element(schema, type_name):
for kind in ("complexType", "simpleType"):
for t in xs_children(schema, kind):
if t.get("name") == type_name:
return t
die(f'В пакете нет типа "{type_name}"')
def get_type_body(ct):
content = xs_first(ct, "complexContent")
if content is not None:
ext = xs_first(content, "extension")
if ext is not None:
return ext
return ct
def find_declaration(body, prop_name):
for node in body.iter():
if not isinstance(node.tag, str) or etree.QName(node).namespace != XS_NS:
continue
if local(node) in ("element", "attribute") and node.get("name") == prop_name:
return node
return None
def resolve_path(schema, path):
# Точка безопасна: имена в модели XDTO — идентификаторы 1С
segments = path.split(".")
type_el = find_type_element(schema, segments[0])
if len(segments) == 1:
return (type_el, None)
body = get_type_body(type_el)
decl = None
for i in range(1, len(segments)):
decl = find_declaration(body, segments[i])
if decl is None:
die(f'По пути "{path}" не найдено свойство "{segments[i]}"')
if i < len(segments) - 1:
inner = xs_first(decl, "complexType")
if inner is None:
die(f'Свойство "{segments[i]}" не содержит вложенного типа — путь дальше не идёт')
body = get_type_body(inner)
return (type_el, decl)
def import_fragment(schema, xml):
ns = {"xs": XS_NS, "xdto": XDTO_NS}
tns = schema.get("targetNamespace")
if tns:
ns["tns"] = tns
for px, uri in schema.nsmap.items():
if px and px not in ns:
ns[px] = uri
decls = " ".join(f'xmlns:{k}="{v}"' for k, v in ns.items())
try:
wrapped = etree.fromstring(f"<wrap {decls}>{xml}</wrap>".encode("utf-8"))
except etree.XMLSyntaxError as e:
die("Не удалось разобрать -Value как фрагмент XML-схемы: " + str(e) + "\n"
+ "Получено: " + xml + "\n"
+ "Если фрагмент передан инлайном, кавычки могли схлопнуться на границе "
'процессов — положите его в файл и укажите -Value "@путь".')
res = [c for c in wrapped if isinstance(c.tag, str)]
if not res:
die(f"Во фрагменте нет ни одного элемента: {xml}")
return res
def apply_model_operation(schema):
op = args.Operation
if op == "add-property":
if not args.Target:
die("add-property требует -Target <Тип>")
type_el, decl = resolve_path(schema, args.Target)
host = xs_first(decl, "complexType") if decl is not None else type_el
body = get_type_body(host)
for frag in import_fragment(schema, args.Value):
kind = local(frag)
if kind == "attribute":
body.append(frag)
elif kind == "element":
# Явные is not None: пустой <xs:sequence/> в lxml ложен,
# и через "or" мы бы создали вторую частицу
particle = xs_first(body, "sequence")
if particle is None:
particle = xs_first(body, "choice")
if particle is None:
particle = xs_first(body, "all")
if particle is None:
particle = etree.Element(f"{{{XS_NS}}}sequence")
first_attr = xs_first(body, "attribute")
if first_attr is not None:
first_attr.addprevious(particle)
else:
body.append(particle)
particle.append(frag)
else:
die(f"add-property ожидает <xs:element> или <xs:attribute>, получен <xs:{kind}>")
print(f' + {frag.get("name")} в тип {args.Target}')
elif op == "replace-property":
if not args.Target:
die('replace-property требует -Target "Тип.Свойство"')
_, decl = resolve_path(schema, args.Target)
if decl is None:
die('replace-property требует путь вида "Тип.Свойство"')
frags = import_fragment(schema, args.Value)
if len(frags) != 1:
die("replace-property ожидает ровно одно объявление")
decl.getparent().replace(decl, frags[0])
print(f" ~ {args.Target} заменено")
elif op == "remove-property":
if not args.Target:
die('remove-property требует путь "Тип.Свойство"')
for one in [x.strip() for x in args.Target.split(";;") if x.strip()]:
_, decl = resolve_path(schema, one)
if decl is None:
die(f'remove-property требует путь вида "Тип.Свойство", получено "{one}"')
decl.getparent().remove(decl)
print(f" {one} удалено")
elif op == "add-type":
for frag in import_fragment(schema, args.Value):
if local(frag) not in ("complexType", "simpleType"):
die(f"add-type ожидает <xs:complexType> или <xs:simpleType>, получен <xs:{local(frag)}>")
schema.append(frag)
print(f' + тип {frag.get("name")}')
elif op == "remove-type":
if not args.Target:
die("remove-type требует -Target <Тип>")
for one in [x.strip() for x in args.Target.split(";;") if x.strip()]:
t = find_type_element(schema, one)
t.getparent().remove(t)
print(f" тип {one} удалён")
elif op == "add-enum":
if not args.Target:
die("add-enum требует -Target <ТипЗначения>")
t = find_type_element(schema, args.Target)
restriction = xs_first(t, "restriction")
if restriction is None:
die(f'Тип "{args.Target}" не является ограничением простого типа')
for lit in [x.strip() for x in args.Value.split(";;") if x.strip()]:
e = etree.SubElement(restriction, f"{{{XS_NS}}}enumeration")
e.set("value", lit)
print(f' + значение "{lit}" в тип {args.Target}')
elif op == "add-import":
for uri in [x.strip() for x in args.Value.split(";;") if x.strip()]:
if any(i.get("namespace") == uri for i in xs_children(schema, "import")):
print(f" = импорт {uri} уже объявлен")
continue
imp = etree.Element(f"{{{XS_NS}}}import")
imp.set("namespace", uri)
first_other = next((c for c in schema if isinstance(c.tag, str)
and local(c) not in ("annotation", "import")), None)
if first_other is not None:
first_other.addprevious(imp)
else:
schema.append(imp)
print(f" + импорт {uri}")
elif op == "set-namespace":
if not args.Value:
die("set-namespace требует -Value <URI>")
old = schema.get("targetNamespace")
if old == args.Value:
# Установка того же значения не отбрасывается: пакет пересобирается вхолостую
print(f" = namespace уже {args.Value}, пакет пересобран без изменений")
else:
print(f" ~ namespace: {old}{args.Value}")
# Меняем и targetNamespace, и объявление префикса, который на него указывал
new_nsmap = {px: (args.Value if uri == old else uri) for px, uri in schema.nsmap.items()}
rebuilt = etree.Element(schema.tag, nsmap=new_nsmap)
for k, v in schema.attrib.items():
rebuilt.set(k, v)
rebuilt.set("targetNamespace", args.Value)
rebuilt.text = schema.text
for c in list(schema):
rebuilt.append(c)
return rebuilt
return schema
# ── dispatch ─────────────────────────────────────────────────
assert_siblings_present(args.Operation)
print(f"Пакет: {pkg_name}")
old_namespace = None
if args.Operation == "rename":
if not args.Value:
die("rename требует -Value <НовоеИмя>")
rename_package(args.Value)
elif args.Operation == "set-synonym":
if not args.Value:
die("set-synonym требует -Value <текст>")
edit_metadata("Synonym", args.Value)
print(f"✓ Синоним: {args.Value}")
elif args.Operation == "set-comment":
edit_metadata("Comment", args.Value)
print("✓ Комментарий обновлён")
else:
tmp_dir = tempfile.mkdtemp(prefix="xdto-edit_")
try:
xsd_path = os.path.join(tmp_dir, "schema.xsd")
invoke_sibling(DECOMPILE, ["-PackagePath", bin_file, "-OutFile", xsd_path], "xdto-decompile")
doc = _parse_xml(xsd_path)
schema = doc.getroot()
old_namespace = schema.get("targetNamespace")
schema = apply_model_operation(schema)
with open(xsd_path, "wb") as f:
f.write(etree.tostring(schema, xml_declaration=True, encoding="UTF-8"))
invoke_sibling(COMPILE, ["-XsdPath", xsd_path, "-OutputDir", config_root,
"-Name", pkg_name, "-Force"], "xdto-compile")
if args.Operation == "set-namespace":
edit_metadata("Namespace", args.Value)
# Зависящие пакеты не трогаем: при версионировании они обязаны продолжать
# смотреть на прежний namespace. Но молчать о них нельзя.
dependents = []
for d in sorted(os.listdir(xdto_root)):
if d == pkg_name or not os.path.isdir(os.path.join(xdto_root, d)):
continue
ob = os.path.join(xdto_root, d, "Ext", "Package.bin")
if not os.path.exists(ob):
continue
try:
for imp in _parse_xml(ob).getroot():
if isinstance(imp.tag, str) and local(imp) == "import" \
and imp.get("namespace") == old_namespace:
dependents.append(d)
break
except Exception: # noqa: BLE001
pass
if dependents:
print("")
print(f"WARNING: Старый namespace импортируют пакеты ({len(dependents)}): "
+ ", ".join(dependents)
+ ". Они не изменены — при версионировании это верно; "
"если нет, поправьте их импорты.", file=sys.stderr)
print(f"✓ Пакет пересобран: XDTOPackages/{pkg_name}/Ext/Package.bin")
finally:
shutil.rmtree(tmp_dir, ignore_errors=True)
if not args.NoValidate:
if os.path.exists(VALIDATE):
print("")
print("--- xdto-validate ---")
subprocess.run([sys.executable, VALIDATE, "-PackagePath", os.path.join(xdto_root, pkg_name)])
else:
print(f"[SKIP] xdto-validate не найден: {VALIDATE}")
sys.exit(0)
+78
View File
@@ -0,0 +1,78 @@
---
name: xdto-info
description: Анализ структуры пакета XDTO 1С — типы, свойства, точки входа. Используй как подготовительный шаг при написании кода, создающего и заполняющего объект XDTO, при разборе входящего XML, а также чтобы узнать, какие пакеты есть в конфигурации
argument-hint: <PackagePath> [-Namespace <URI>|-Package <имя>] [-Name <Тип>] [-Depth N] [-RequiredOnly] [-Mode used-by] [-Limit N] [-Offset N] [-OutFile <файл>]
allowed-tools:
- Bash
- Read
- Glob
---
# /xdto-info — Анализ структуры пакета XDTO
Показывает структуру типа в терминах 1С: какой тип значения присваивать, что обязательно,
где нужен вложенный объект, какие значения допустимы. Заменяет чтение `Package.bin`
или XSD с ручным переводом `xs:decimal``Число` и `lowerBound="0"` → необязательный.
## Параметры
| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `PackagePath` | да | Каталог пакета либо корень исходников конфигурации. Псевдоним — `-Path` |
| `Namespace` | нет | Выбрать пакет по пространству имён (когда путь — корень исходников) |
| `Package` | нет | Выбрать пакет по имени объекта метаданных |
| `Name` | нет | Имя типа. Без выбранного пакета ищется по всей конфигурации |
| `Depth` | нет | Глубина разузлования вложенных объектов. По умолчанию 1 |
| `RequiredOnly` | нет | Оставить только обязательные свойства — скелет для «заполни обязательное». Необязательный объект уходит вместе со своим содержимым |
| `Mode` | нет | `used-by` — показать, кто ссылается на тип |
| `Limit` / `Offset` | нет | Пагинация. По умолчанию 150 строк |
| `OutFile` | нет | Записать результат в файл (UTF-8 BOM) |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-info.ps1" -PackagePath "<путь>"
```
## Что показывает
Точка входа определяется по пути: корень исходников — список пакетов, каталог пакета —
его состав.
| Вызов | Результат |
|---|---|
| `-PackagePath src` | все пакеты конфигурации: имя, число типов, namespace |
| `-PackagePath src/XDTOPackages/ОбменСБанком` | импорты, точки входа, списки типов |
| `... -Name ПлатежныйДокумент` | структура типа для заполнения |
| `... -Name ПлатежныйДокумент -Depth 3` | то же с раскрытием вложенных объектов |
| `... -Mode used-by -Name СуммаТип` | кто ссылается на тип, включая соседние пакеты |
## Когда известны namespace и тип, но не имя пакета
Так бывает чаще всего: namespace и имя типа видны в коде или в образце XML,
а как называется пакет — нет. Вызов повторяет строку, от которой отталкиваешься:
```powershell
# ФабрикаXDTO.Тип("urn:1C.ru:ClientBankExchange", "ПлатежныйДокумент")
... -PackagePath src -Namespace "urn:1C.ru:ClientBankExchange" -Name ПлатежныйДокумент
```
Если известно только имя типа — укажи `-Name` и корень исходников: тип найдётся
по всем пакетам. При нескольких совпадениях навык покажет, где именно, чтобы уточнить.
## Что в выводе
Свойства показаны так, как их предстоит заполнять в коде: тип значения — в нотации
1С и с учётом ограничений (`Строка(6)`, `Число(18,2)`), обязательность и коллекции —
флагами, для перечислимых типов перечислены допустимые значения. Непомеченное
свойство необязательно.
Обозначения, которые сами по себе неочевидны, навык поясняет прямо в выводе —
и только те, что в нём встретились.
## Типичный workflow
1. `/xdto-info src` — какие пакеты есть
2. `/xdto-info src -Namespace "<URI>"` — точки входа и типы пакета
3. `/xdto-info src -Namespace "<URI>" -Name <Тип> -Depth 2` — структура для кода
4. Перед правкой типа: `-Mode used-by -Name <Тип>` — кого затронет
Нужна сама XML-схема, а не сводка, — это `/xdto-decompile`.
@@ -0,0 +1,694 @@
# xdto-info v1.0 — Analyze 1C XDTO package structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)]
[Alias('Path')]
[string]$PackagePath,
[string]$Package,
[string]$Namespace,
[string]$Name,
[ValidateSet("auto", "used-by")]
[string]$Mode = "auto",
[int]$Depth = 1,
[switch]$RequiredOnly,
[int]$Limit = 150,
[int]$Offset = 0,
[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"
# --- Output ---
$sb = New-Object System.Text.StringBuilder
function O([string]$line = "") { [void]$sb.AppendLine($line) }
function Fail([string]$msg) {
# Отрицательный результат поиска — не исключение: печатаем сообщение
# и выходим с кодом 1, без стектрейса PowerShell
Write-Host $msg
exit 1
}
function Flush-Output {
$text = $sb.ToString().TrimEnd()
if ($OutFile) {
$dir = [System.IO.Path]::GetDirectoryName($OutFile)
if ($dir -and -not (Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
[System.IO.File]::WriteAllText($OutFile, $text + "`r`n", (New-Object System.Text.UTF8Encoding($true)))
Write-Host "✓ Записано: $OutFile"
} else {
Write-Host $text
}
}
# --- Path resolution -----------------------------------------------------------
# Путь может указывать на корень конфигурации (тогда работаем со всеми пакетами)
# либо на конкретный пакет.
if (-not [System.IO.Path]::IsPathRooted($PackagePath)) {
$PackagePath = Join-Path (Get-Location).Path $PackagePath
}
if (-not (Test-Path $PackagePath)) { Fail "Путь не найден: $PackagePath" }
$configRoot = $null
$directPkgDir = $null
if (Test-Path (Join-Path $PackagePath "Configuration.xml")) {
$configRoot = $PackagePath
} elseif ((Split-Path $PackagePath -Leaf) -eq "XDTOPackages") {
$configRoot = Split-Path $PackagePath -Parent
} elseif (Test-Path (Join-Path (Join-Path $PackagePath "Ext") "Package.bin")) {
$directPkgDir = $PackagePath
$pkgRoot = Split-Path $PackagePath -Parent
$configRoot = Split-Path $pkgRoot -Parent
} elseif ((Test-Path $PackagePath -PathType Leaf) -and ([System.IO.Path]::GetFileName($PackagePath) -eq "Package.bin")) {
$directPkgDir = Split-Path (Split-Path $PackagePath -Parent) -Parent
$configRoot = Split-Path (Split-Path $directPkgDir -Parent) -Parent
} elseif ($PackagePath.EndsWith(".xml")) {
$stem = [System.IO.Path]::Combine([System.IO.Path]::GetDirectoryName($PackagePath),
[System.IO.Path]::GetFileNameWithoutExtension($PackagePath))
if (Test-Path (Join-Path (Join-Path $stem "Ext") "Package.bin")) {
$directPkgDir = $stem
$configRoot = Split-Path (Split-Path $stem -Parent) -Parent
}
}
if (-not $configRoot -and -not $directPkgDir) { Fail "Не удалось определить пакет или конфигурацию по пути: $PackagePath" }
# Sort-Object в PowerShell сортирует по культуре, sorted() в Python — по кодам.
# Для паритета портов сортируем ординально в обоих.
function Sort-Ordinal($items) {
$arr = [string[]]@($items)
[array]::Sort($arr, [StringComparer]::Ordinal)
return ,$arr
}
# --- Package index -------------------------------------------------------------
function Read-Package([string]$pkgDir) {
$bin = Join-Path (Join-Path $pkgDir "Ext") "Package.bin"
if (-not (Test-Path $bin)) { return $null }
try {
$doc = New-Object System.Xml.XmlDocument
$doc.PreserveWhitespace = $false
$doc.Load($bin)
} catch { return $null }
$root = $doc.DocumentElement
if ($root.get_LocalName() -ne "package") { return $null }
$info = [pscustomobject]@{
Name = [System.IO.Path]::GetFileName($pkgDir)
Dir = $pkgDir
Namespace = $root.GetAttribute("targetNamespace")
Root = $root
Imports = (New-Object System.Collections.ArrayList)
Types = @{}
GlobalProps= (New-Object System.Collections.ArrayList)
}
foreach ($n in $root.ChildNodes) {
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
switch ($n.get_LocalName()) {
"import" { [void]$info.Imports.Add($n.GetAttribute("namespace")) }
"objectType" { $info.Types[$n.GetAttribute("name")] = $n }
"valueType" { $info.Types[$n.GetAttribute("name")] = $n }
"property" { [void]$info.GlobalProps.Add($n) }
}
}
return $info
}
$packages = New-Object System.Collections.ArrayList
$byNamespace = @{}
if ($configRoot -and (Test-Path (Join-Path $configRoot "XDTOPackages"))) {
foreach ($dn in (Sort-Ordinal ((Get-ChildItem (Join-Path $configRoot "XDTOPackages") -Directory -ErrorAction SilentlyContinue).Name))) {
$p = Read-Package (Join-Path (Join-Path $configRoot "XDTOPackages") $dn)
if ($p) {
[void]$packages.Add($p)
if (-not $byNamespace.ContainsKey($p.Namespace)) { $byNamespace[$p.Namespace] = $p }
}
}
}
if ($directPkgDir -and $packages.Count -eq 0) {
$p = Read-Package $directPkgDir
if ($p) { [void]$packages.Add($p); $byNamespace[$p.Namespace] = $p }
}
if ($packages.Count -eq 0) { Fail "Пакеты XDTO не найдены: $PackagePath" }
# --- Type notation: XSD -> 1С ---------------------------------------------------
$XS_TO_1C = @{
"string" = "Строка"; "normalizedString" = "Строка"; "token" = "Строка"; "NCName" = "Строка"
"Name" = "Строка"; "QName" = "Строка"; "anyURI" = "Строка"; "language" = "Строка"
"ID" = "Строка"; "IDREF" = "Строка"; "NMTOKEN" = "Строка"
"decimal" = "Число"; "integer" = "Число"; "int" = "Число"; "long" = "Число"; "short" = "Число"
"byte" = "Число"; "float" = "Число"; "double" = "Число"
"nonNegativeInteger" = "Число"; "positiveInteger" = "Число"; "nonPositiveInteger" = "Число"
"negativeInteger" = "Число"; "unsignedInt" = "Число"; "unsignedLong" = "Число"
"unsignedShort" = "Число"; "unsignedByte" = "Число"
"date" = "Дата"; "dateTime" = "Дата"; "time" = "Дата"
"boolean" = "Булево"
"base64Binary" = "ДвоичныеДанные"; "hexBinary" = "ДвоичныеДанные"
"anyType" = "произвольный"; "anySimpleType" = "произвольный"
}
function Split-Ref([System.Xml.XmlElement]$el, [string]$raw) {
if (-not $raw) { return $null }
if ($raw.StartsWith("{")) {
$close = $raw.IndexOf("}")
if ($close -lt 0) { return $null }
return [pscustomobject]@{ Ns = $raw.Substring(1, $close - 1); Local = $raw.Substring($close + 1) }
}
$parts = $raw.Split(":")
if ($parts.Count -eq 2) {
return [pscustomobject]@{ Ns = $el.GetNamespaceOfPrefix($parts[0]); Local = $parts[1] }
}
return [pscustomobject]@{ Ns = $null; Local = $parts[0] }
}
function Get-Facets([System.Xml.XmlElement]$t) {
$res = @{}
foreach ($f in @("length", "minLength", "maxLength", "totalDigits", "fractionDigits",
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
$v = $t.GetAttribute($f)
if ($v) { $res[$f] = $v }
}
$pat = $null
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "pattern") { $pat = $c.InnerText; break }
}
if ($pat) { $res["pattern"] = $pat }
return $res
}
function Get-Enumerations([System.Xml.XmlElement]$t) {
$vals = @()
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "enumeration") { $vals += $c.InnerText }
}
return $vals
}
# Разворачивает цепочку псевдонимов до примитива, собирая фасеты по пути.
# Возвращает @{ Base1C; Facets; Alias; Enum; Kind }
function Resolve-Scalar([System.Xml.XmlElement]$t, $pkg, [int]$guard = 0) {
$acc = @{ Base1C = $null; Facets = @{}; Alias = $null; Enum = @(); Kind = "scalar" }
if ($guard -gt 10 -or -not $t) { return $acc }
$variety = $t.GetAttribute("variety")
if ($variety -eq "List") {
$it = Split-Ref $t $t.GetAttribute("itemType")
$acc.Kind = "list"
$acc.Base1C = "список " + $(if ($it) { Format-RefName $it $pkg } else { "значений" })
return $acc
}
if ($variety -eq "Union" -or $t.GetAttribute("memberTypes")) {
$members = @()
foreach ($m in (($t.GetAttribute("memberTypes") -split "\s+") | Where-Object { $_ })) {
$q = Split-Ref $t $m
if ($q) { $members += (Format-RefName $q $pkg) }
}
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") {
$inner = Resolve-Scalar $c $pkg ($guard + 1)
$members += $inner.Base1C
}
}
$acc.Kind = "union"
$acc.Base1C = "одно из (" + (($members | Where-Object { $_ }) -join " | ") + ")"
return $acc
}
$acc.Facets = Get-Facets $t
$acc.Enum = Get-Enumerations $t
# базовый тип: атрибут base или вложенный анонимный typeDef
$baseQ = Split-Ref $t $t.GetAttribute("base")
$anonBase = $null
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anonBase = $c; break }
}
if (-not $baseQ -and $anonBase) {
$inner = Resolve-Scalar $anonBase $pkg ($guard + 1)
$acc.Base1C = $inner.Base1C
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
return $acc
}
if (-not $baseQ) { $acc.Base1C = "произвольный"; return $acc }
if ($baseQ.Ns -eq $XS_NS) {
$acc.Base1C = $(if ($XS_TO_1C.ContainsKey($baseQ.Local)) { $XS_TO_1C[$baseQ.Local] } else { "xs:$($baseQ.Local)" })
return $acc
}
# база — именованный тип значения: разворачиваем дальше
$target = Find-Type $baseQ $pkg
if ($target -and $target.Element.get_LocalName() -eq "valueType") {
$inner = Resolve-Scalar $target.Element $target.Package ($guard + 1)
$acc.Base1C = $inner.Base1C
foreach ($k in $inner.Facets.Keys) { if (-not $acc.Facets.ContainsKey($k)) { $acc.Facets[$k] = $inner.Facets[$k] } }
if ($inner.Enum.Count -gt 0 -and $acc.Enum.Count -eq 0) { $acc.Enum = $inner.Enum }
if (-not $acc.Alias) { $acc.Alias = $baseQ.Local }
return $acc
}
$acc.Base1C = $baseQ.Local
return $acc
}
function Find-Type($q, $pkg) {
if (-not $q) { return $null }
$targetPkg = $null
if (-not $q.Ns -or ($pkg -and $q.Ns -eq $pkg.Namespace)) { $targetPkg = $pkg }
elseif ($byNamespace.ContainsKey($q.Ns)) { $targetPkg = $byNamespace[$q.Ns] }
if (-not $targetPkg) { return $null }
if (-not $targetPkg.Types.ContainsKey($q.Local)) { return $null }
return [pscustomobject]@{ Element = $targetPkg.Types[$q.Local]; Package = $targetPkg }
}
function Format-RefName($q, $pkg) {
if (-not $q) { return "" }
if ($q.Ns -eq $XS_NS) {
return $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
}
return $q.Local
}
function Format-Scalar($res) {
$t = $res.Base1C
$f = $res.Facets
if ($t -eq "Строка") {
if ($f.ContainsKey("length")) { $t = "Строка($($f['length']))" }
elseif ($f.ContainsKey("maxLength")) { $t = "Строка($($f['maxLength']))" }
} elseif ($t -eq "Число") {
if ($f.ContainsKey("totalDigits")) {
$frac = $(if ($f.ContainsKey("fractionDigits")) { $f["fractionDigits"] } else { "0" })
$t = "Число($($f['totalDigits']),$frac)"
}
}
return $t
}
function Format-Notes($res) {
$notes = @()
if ($res.Alias) { $notes += "$($res.Alias)" }
if ($res.Facets.ContainsKey("pattern")) {
$p = $res.Facets["pattern"]
if ($p.Length -gt 40) { $p = $p.Substring(0, 40) + "" }
$notes += "шаблон $p"
}
foreach ($k in @("minInclusive", "maxInclusive", "minExclusive", "maxExclusive")) {
if ($res.Facets.ContainsKey($k)) { $notes += "$k $($res.Facets[$k])" }
}
return $notes
}
# --- Property rendering ---------------------------------------------------------
function Get-PropRows([System.Xml.XmlElement]$type, $pkg, [int]$depth, [int]$indent, $seen) {
$rows = New-Object System.Collections.ArrayList
foreach ($p in $type.ChildNodes) {
if ($p.NodeType -ne [System.Xml.XmlNodeType]::Element -or $p.get_LocalName() -ne "property") { continue }
$pname = $p.GetAttribute("name")
if (-not $pname) {
$refQ = Split-Ref $p $p.GetAttribute("ref")
$pname = $(if ($refQ) { $refQ.Local } else { "(без имени)" })
}
$lower = $p.GetAttribute("lowerBound")
$upper = $p.GetAttribute("upperBound")
$flags = @()
# В модели умолчание lowerBound = 1; помечаем обязательные, как в meta-info
if ($lower -ne "0") { $flags += "обязательный" }
if ($upper -eq "-1") { $flags += "список" }
elseif ($upper -and $upper -ne "1") { $flags += "до $upper" }
if ($p.GetAttribute("form") -eq "Text") { $flags += "значение элемента" }
$notes = @()
$typeText = ""
$children = $null
$childPkg = $pkg
# Именованный вложенный тип берётся так же, как корневой; окольный путь
# через свойство владельца нужен только анонимному — имени у него нет
$objName = $null
$objNs = $null
$anon = $null
foreach ($c in $p.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $anon = $c; break }
}
if ($anon) {
if ($anon.GetAttribute("type", $XSI_NS) -eq "ObjectType") {
$typeText = "объект (анонимный)"
$objName = "(анонимный)"
$children = $anon # анонимные раскрываем всегда: смотреть отдельно негде
} else {
$res = Resolve-Scalar $anon $pkg
$typeText = Format-Scalar $res
$notes += (Format-Notes $res)
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
}
} else {
$q = Split-Ref $p $p.GetAttribute("type")
if (-not $q) {
$typeText = "произвольный"
} elseif ($q.Ns -eq $XS_NS) {
$typeText = $(if ($XS_TO_1C.ContainsKey($q.Local)) { $XS_TO_1C[$q.Local] } else { "xs:$($q.Local)" })
} else {
$target = Find-Type $q $pkg
if (-not $target) {
$typeText = "объект $($q.Local)"
$notes += "(пакет не найден: $($q.Ns))"
} elseif ($target.Element.get_LocalName() -eq "objectType") {
$typeText = "объект $($q.Local)"
if ($target.Package.Namespace -ne $pkg.Namespace) { $typeText += " · $($target.Package.Name)" }
$objName = $q.Local
$objNs = $target.Package.Namespace
$children = $target.Element
$childPkg = $target.Package
} else {
$res = Resolve-Scalar $target.Element $target.Package
$typeText = Format-Scalar $res
$notes += "$($q.Local)"
$n2 = Format-Notes $res | Where-Object { -not $_.StartsWith("") }
$notes += $n2
if ($res.Enum.Count -gt 0) { $notes += "значения: " + (($res.Enum | Select-Object -First 8) -join ", ") }
}
}
}
[void]$rows.Add([pscustomobject]@{
Indent = $indent; Name = $pname; Type = $typeText
Flags = $flags; Notes = ($notes | Where-Object { $_ })
ObjName = $objName; ObjNs = $objNs
})
if ($children) {
$key = "$($childPkg.Namespace)#$($children.GetAttribute('name'))"
$isAnon = -not $children.GetAttribute("name")
if (-not $isAnon -and $seen.Contains($key)) {
[void]$rows.Add([pscustomobject]@{ Indent = $indent + 1; Name = "(раскрыт выше)"; Type = ""; Flags = @(); Notes = @() })
} elseif ($isAnon -or $depth -gt 1) {
$nextSeen = New-Object System.Collections.Generic.HashSet[string] (,[string[]]$seen)
if (-not $isAnon) { [void]$nextSeen.Add($key) }
$nextDepth = $(if ($isAnon) { $depth } else { $depth - 1 })
foreach ($r in (Get-PropRows $children $childPkg $nextDepth ($indent + 1) $nextSeen)) { [void]$rows.Add($r) }
}
}
}
return ,$rows
}
# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
function Select-Required($rows) {
$res = New-Object System.Collections.ArrayList
$cutFrom = -1
foreach ($r in $rows) {
if ($cutFrom -ge 0 -and $r.Indent -gt $cutFrom) { continue }
$cutFrom = -1
if ($r.Flags -notcontains "обязательный") { $cutFrom = $r.Indent; continue }
[void]$res.Add($r)
}
return ,$res
}
function Write-Rows($rows) {
if ($rows.Count -eq 0) { O " (нет свойств)"; return }
$shown = $rows
if ($Offset -gt 0 -or $rows.Count -gt $Limit) {
$end = [Math]::Min($Offset + $Limit, $rows.Count) - 1
if ($Offset -le $end) { $shown = $rows[$Offset..$end] } else { $shown = @() }
}
$wName = 0; $wType = 0
foreach ($r in $shown) {
$n = (" " * $r.Indent) + $r.Name
if ($n.Length -gt $wName) { $wName = $n.Length }
if ($r.Type.Length -gt $wType) { $wType = $r.Type.Length }
}
foreach ($r in $shown) {
$n = (" " * $r.Indent) + $r.Name
$line = " " + $n.PadRight($wName + 2) + $r.Type.PadRight($wType + 2)
if ($r.Flags.Count -gt 0) { $line += "[" + ($r.Flags -join ", ") + "] " }
if ($r.Notes.Count -gt 0) { $line += ($r.Notes -join ", ") }
O $line.TrimEnd()
}
if ($rows.Count -gt $shown.Count) {
O " … показано $($shown.Count) из $($rows.Count); листать через -Offset/-Limit"
}
}
# --- Modes ----------------------------------------------------------------------
function Show-PackageList {
O "=== Пакеты XDTO: $($packages.Count) ==="
O ""
$shown = $packages
if ($Offset -gt 0 -or $packages.Count -gt $Limit) {
$end = [Math]::Min($Offset + $Limit, $packages.Count) - 1
if ($Offset -le $end) { $shown = $packages[$Offset..$end] } else { $shown = @() }
}
$wn = 0
foreach ($p in $shown) { if ($p.Name.Length -gt $wn) { $wn = $p.Name.Length } }
foreach ($p in $shown) {
$cnt = $p.Types.Count
O (" " + $p.Name.PadRight($wn + 2) + "$cnt".PadLeft(4) + " " + $p.Namespace)
}
if ($packages.Count -gt $shown.Count) {
O ""
O " … показано $($shown.Count) из $($packages.Count); листать через -Offset/-Limit"
}
O ""
O "Колонки: имя пакета, число типов, namespace."
O "Следующий шаг: -Package <имя> или -Namespace <URI> — состав пакета; -Name <Тип> — поиск типа по всем пакетам"
}
function Show-PackageOverview($pkg) {
O "=== Пакет XDTO: $($pkg.Name) ==="
O "Namespace: $($pkg.Namespace)"
if ($pkg.Imports.Count -gt 0) {
O ""
O "Импорты ($($pkg.Imports.Count)):"
foreach ($i in $pkg.Imports) {
$dep = $(if ($byNamespace.ContainsKey($i)) { $byNamespace[$i].Name } else { "(пакет не найден)" })
O " $i$dep"
}
}
if ($pkg.GlobalProps.Count -eq 0) {
O ""
O "Точки входа: нет — пакет не объявляет корневых элементов документа"
} else {
O ""
O "Точки входа ($($pkg.GlobalProps.Count)) — корневые элементы документа:"
foreach ($gp in $pkg.GlobalProps) {
$q = Split-Ref $gp $gp.GetAttribute("type")
$tn = $(if ($q) { Format-RefName $q $pkg } else { "произвольный" })
$form = $(if ($gp.GetAttribute("form") -eq "Attribute") { " (атрибут)" } else { "" })
O (" <" + $gp.GetAttribute("name") + "> → " + $tn + $form)
}
}
$objs = @(); $vals = @()
foreach ($k in (Sort-Ordinal $pkg.Types.Keys)) {
if ($pkg.Types[$k].get_LocalName() -eq "objectType") { $objs += $k } else { $vals += $k }
}
if ($objs.Count -gt 0) {
O ""
O "Объектные типы ($($objs.Count)):"
foreach ($n in $objs) {
$cnt = 0
foreach ($c in $pkg.Types[$n].ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "property") { $cnt++ }
}
$base = Split-Ref $pkg.Types[$n] $pkg.Types[$n].GetAttribute("base")
$suffix = $(if ($base) { "$($base.Local)" } else { "" })
O (" " + $n.PadRight(40) + "свойств: $cnt" + $suffix)
}
}
if ($vals.Count -gt 0) {
O ""
O "Типы значений ($($vals.Count)):"
foreach ($n in $vals) {
$res = Resolve-Scalar $pkg.Types[$n] $pkg
$line = " " + $n.PadRight(40) + (Format-Scalar $res)
if ($res.Enum.Count -gt 0) { $line += " значения: " + (($res.Enum | Select-Object -First 6) -join ", ") }
O $line
}
}
O ""
O "Следующий шаг: -Name <Тип> — структура типа для заполнения"
}
# Легенда едет вместе с выводом, а не живёт в инструкции: показываем только те
# обозначения, которые реально встретились, иначе она сама становится шумом.
function Write-Legend($rows) {
$text = ($rows | ForEach-Object { $_.Type + " " + ($_.Flags -join ",") + " " + ($_.Notes -join ",") }) -join " "
$items = @()
if ($text -match "объект ") { $items += "объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth" }
if ($text -match "") { $items += "← Имя — исходный тип из схемы, слева от стрелки развёрнутое значение" }
if ($text -match "список") { $items += "список — коллекция, заполняется через .Добавить()" }
if ($text -match "до \d") { $items += "до N — коллекция с ограничением сверху" }
if ($text -match "значение элемента") { $items += "значение элемента — собственное значение узла XML" }
if ($text -match "·") { $items += "· Пакет — тип объявлен в другом пакете" }
if ($items.Count -eq 0) { return }
O ""
O "Обозначения:"
foreach ($i in $items) { O " $i" }
}
function Show-Type($pkg, [string]$typeName) {
$el = $pkg.Types[$typeName]
$kind = $el.get_LocalName()
if ($kind -eq "valueType") {
$res = Resolve-Scalar $el $pkg
O "=== Тип значения XDTO: $typeName ==="
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
O ""
O "Значение: $(Format-Scalar $res)"
foreach ($n in (Format-Notes $res)) { O " $n" }
if ($res.Enum.Count -gt 0) {
O ""
O "Допустимые значения ($($res.Enum.Count)):"
foreach ($v in $res.Enum) { O " $v" }
}
O ""
O "Создание:"
# Создать(<Тип>, <Значение>) принимает именно ТипЗначенияXDTO —
# для объектного типа эта форма неприменима
O " Значение = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`"), Значение);"
return
}
$hdr = "=== Тип XDTO: $typeName ==="
if ($Depth -gt 1) { $hdr += " (глубина $Depth)" }
O $hdr
O "Пакет: $($pkg.Name) · $($pkg.Namespace)"
$base = Split-Ref $el $el.GetAttribute("base")
if ($base) { O "Наследует: $($base.Local)" }
if ($el.GetAttribute("abstract") -eq "true") { O "Абстрактный — создаётся только тип-наследник" }
if ($el.GetAttribute("open") -eq "true") { O "Открытый — допускает произвольные элементы и атрибуты" }
O ""
$seen = New-Object System.Collections.Generic.HashSet[string]
[void]$seen.Add("$($pkg.Namespace)#$typeName")
$rows = Get-PropRows $el $pkg $Depth 0 $seen
$own = @($rows | Where-Object { $_.Indent -eq 0 })
if ($RequiredOnly) {
$all = $rows.Count
$rows = Select-Required $rows
$ownReq = @($rows | Where-Object { $_.Indent -eq 0 })
# Фильтр обязан сообщать о себе: иначе список читается как полный
O "Свойства: обязательных $($ownReq.Count) из $($own.Count) (-RequiredOnly; скрыто строк: $($all - $rows.Count))"
} else {
O "Свойства ($($own.Count)):"
}
Write-Rows $rows
Write-Legend $rows
O ""
O "Создание:"
O " Тип = ФабрикаXDTO.Тип(`"$($pkg.Namespace)`", `"$typeName`");"
O " Объект = ФабрикаXDTO.Создать(Тип);"
# Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
# ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
$named = @($rows | Where-Object { $_.ObjName -and $_.ObjName -ne "(анонимный)" } | Select-Object -First 1)
if ($named.Count -gt 0) {
O " // вложенный именованный тип — так же, как корневой:"
O " $($named[0].Name) = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип(`"$($named[0].ObjNs)`", `"$($named[0].ObjName)`"));"
}
$anonRow = @($rows | Where-Object { $_.ObjName -eq "(анонимный)" } | Select-Object -First 1)
if ($anonRow.Count -gt 0) {
O " // у анонимного типа нет имени — только через свойство владельца:"
O " $($anonRow[0].Name) = ФабрикаXDTO.Создать(Тип.Свойства.Получить(`"$($anonRow[0].Name)`").Тип);"
}
$textRow = @($rows | Where-Object { $_.Flags -contains "значение элемента" } | Select-Object -First 1)
if ($textRow.Count -gt 0) {
O " // собственное значение узла лежит в свойстве $($textRow[0].Name)"
}
}
function Show-UsedBy([string]$typeName, $ownerPkg) {
O "=== Ссылки на тип: $typeName ==="
if ($ownerPkg) { O "Объявлен в: $($ownerPkg.Name) · $($ownerPkg.Namespace)" }
O ""
$hits = New-Object System.Collections.ArrayList
foreach ($p in $packages) {
foreach ($node in $p.Root.SelectNodes("//*")) {
if ($node.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
foreach ($a in @("type", "base", "itemType", "memberTypes")) {
$raw = $node.GetAttribute($a)
if (-not $raw) { continue }
foreach ($one in ($raw -split "\s+")) {
$q = Split-Ref $node $one
if (-not $q -or $q.Local -ne $typeName) { continue }
if ($ownerPkg -and $q.Ns -and $q.Ns -ne $ownerPkg.Namespace) { continue }
$owner = $node
while ($owner -and @("objectType", "valueType") -notcontains $owner.get_LocalName()) { $owner = $owner.ParentNode }
$where = $(if ($owner -and $owner.GetAttribute("name")) { $owner.GetAttribute("name") } else { "(верхний уровень)" })
$what = $(if ($node.GetAttribute("name")) { $node.GetAttribute("name") } else { $node.get_LocalName() })
[void]$hits.Add(" $($p.Name).$where.$what ($a)")
}
}
}
}
if ($hits.Count -eq 0) { O " Ссылок не найдено"; return }
O "Найдено ($($hits.Count)):"
foreach ($h in (Sort-Ordinal ($hits | Select-Object -Unique))) { O $h }
}
# --- Dispatch -------------------------------------------------------------------
# Выбор пакета: явный путь, затем -Namespace / -Package, затем поиск типа по всем
$selected = $null
if ($directPkgDir) {
$leaf = [System.IO.Path]::GetFileName($directPkgDir)
$selected = $packages | Where-Object { $_.Name -eq $leaf } | Select-Object -First 1
}
if (-not $selected -and $Namespace) {
$selected = $packages | Where-Object { $_.Namespace -eq $Namespace } | Select-Object -First 1
if (-not $selected) { Fail "Пакет с namespace `"$Namespace`" не найден. Список: -PackagePath <корень> без параметров" }
}
if (-not $selected -and $Package) {
$selected = $packages | Where-Object { $_.Name -eq $Package } | Select-Object -First 1
if (-not $selected) { Fail "Пакет `"$Package`" не найден. Список: -PackagePath <корень> без параметров" }
}
if ($Mode -eq "used-by") {
if (-not $Name) { Fail "Режим used-by требует -Name <Тип>" }
$ownerPkg = $selected
if (-not $ownerPkg) { $ownerPkg = ($packages | Where-Object { $_.Types.ContainsKey($Name) } | Select-Object -First 1) }
Show-UsedBy $Name $ownerPkg
Flush-Output
exit 0
}
if ($Name) {
if (-not $selected) {
# Тип известен, пакет — нет: ищем по всей конфигурации
$found = @($packages | Where-Object { $_.Types.ContainsKey($Name) })
if ($found.Count -eq 0) { Fail "Тип `"$Name`" не найден ни в одном пакете конфигурации" }
if ($found.Count -gt 1) {
O "=== Тип `"$Name`" найден в нескольких пакетах ($($found.Count)) ==="
O "Уточните через -Namespace или -Package:"
O ""
foreach ($f in $found) { O " $($f.Name) · $($f.Namespace)" }
Flush-Output
exit 0
}
$selected = $found[0]
}
if (-not $selected.Types.ContainsKey($Name)) {
Fail "В пакете $($selected.Name) нет типа `"$Name`". Список типов: тот же вызов без -Name"
}
Show-Type $selected $Name
Flush-Output
exit 0
}
if ($selected) { Show-PackageOverview $selected } else { Show-PackageList }
Flush-Output
exit 0
@@ -0,0 +1,698 @@
# xdto-info v1.0 — Analyze 1C XDTO package structure (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
import re
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"
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("-PackagePath", "-Path", required=True)
parser.add_argument("-Package", default="")
parser.add_argument("-Namespace", default="")
parser.add_argument("-Name", default="")
parser.add_argument("-Mode", default="auto", choices=["auto", "used-by"])
parser.add_argument("-Depth", type=int, default=1)
parser.add_argument("-RequiredOnly", action="store_true")
parser.add_argument("-Limit", type=int, default=150)
parser.add_argument("-Offset", type=int, default=0)
parser.add_argument("-OutFile", default="")
args = parser.parse_args()
LIMIT, OFFSET, DEPTH = args.Limit, args.Offset, args.Depth
lines = []
def O(line=""):
lines.append(line)
def flush_output():
text = "\n".join(lines).rstrip()
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 + "\r\n").encode("utf-8"))
print(f"✓ Записано: {args.OutFile}")
else:
print(text)
def die(msg):
# Отрицательный результат поиска — не исключение: сообщение и код 1,
# без трассировки
print(msg)
sys.exit(1)
def _parse_xml(source, from_string=False):
"""Разбор с узким отступлением для не-URI пространств имён.
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
иначе по-настоящему битый XML перестал бы отличаться от корректного.
"""
try:
return (etree.fromstring(source) if from_string else etree.parse(source))
except etree.XMLSyntaxError as e:
if "is not a valid URI" not in str(e):
raise
p = etree.XMLParser(recover=True)
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
def local(el):
return etree.QName(el).localname
# ── resolve path ─────────────────────────────────────────────
package_path = os.path.abspath(args.PackagePath)
if not os.path.exists(package_path):
die(f"Путь не найден: {package_path}")
config_root = None
direct_pkg_dir = None
if os.path.exists(os.path.join(package_path, "Configuration.xml")):
config_root = package_path
elif os.path.basename(package_path.rstrip("\\/")) == "XDTOPackages":
config_root = os.path.dirname(package_path.rstrip("\\/"))
elif os.path.exists(os.path.join(package_path, "Ext", "Package.bin")):
direct_pkg_dir = package_path
config_root = os.path.dirname(os.path.dirname(package_path))
elif os.path.isfile(package_path) and os.path.basename(package_path) == "Package.bin":
direct_pkg_dir = os.path.dirname(os.path.dirname(package_path))
config_root = os.path.dirname(os.path.dirname(direct_pkg_dir))
elif package_path.endswith(".xml"):
stem = os.path.join(os.path.dirname(package_path),
os.path.splitext(os.path.basename(package_path))[0])
if os.path.exists(os.path.join(stem, "Ext", "Package.bin")):
direct_pkg_dir = stem
config_root = os.path.dirname(os.path.dirname(stem))
if not config_root and not direct_pkg_dir:
die(f"Не удалось определить пакет или конфигурацию по пути: {package_path}")
# ── package index ────────────────────────────────────────────
class Pkg:
__slots__ = ("Name", "Dir", "Namespace", "Root", "Imports", "Types", "GlobalProps")
def read_package(pkg_dir):
b = os.path.join(pkg_dir, "Ext", "Package.bin")
if not os.path.exists(b):
return None
try:
root = _parse_xml(b).getroot()
except Exception: # noqa: BLE001
return None
if local(root) != "package":
return None
p = Pkg()
p.Name = os.path.basename(pkg_dir.rstrip("\\/"))
p.Dir = pkg_dir
p.Namespace = root.get("targetNamespace")
p.Root = root
p.Imports = []
p.Types = {}
p.GlobalProps = []
for n in root:
if not isinstance(n.tag, str):
continue
ln = local(n)
if ln == "import":
p.Imports.append(n.get("namespace"))
elif ln in ("objectType", "valueType"):
p.Types[n.get("name")] = n
elif ln == "property":
p.GlobalProps.append(n)
return p
packages = []
by_namespace = {}
if config_root and os.path.isdir(os.path.join(config_root, "XDTOPackages")):
base = os.path.join(config_root, "XDTOPackages")
for dn in sorted(d for d in os.listdir(base) if os.path.isdir(os.path.join(base, d))):
p = read_package(os.path.join(base, dn))
if p:
packages.append(p)
by_namespace.setdefault(p.Namespace, p)
if direct_pkg_dir and not packages:
p = read_package(direct_pkg_dir)
if p:
packages.append(p)
by_namespace[p.Namespace] = p
if not packages:
die(f"Пакеты XDTO не найдены: {package_path}")
# ── type notation: XSD -> 1С ─────────────────────────────────
XS_TO_1C = {
"string": "Строка", "normalizedString": "Строка", "token": "Строка", "NCName": "Строка",
"Name": "Строка", "QName": "Строка", "anyURI": "Строка", "language": "Строка",
"ID": "Строка", "IDREF": "Строка", "NMTOKEN": "Строка",
"decimal": "Число", "integer": "Число", "int": "Число", "long": "Число", "short": "Число",
"byte": "Число", "float": "Число", "double": "Число",
"nonNegativeInteger": "Число", "positiveInteger": "Число", "nonPositiveInteger": "Число",
"negativeInteger": "Число", "unsignedInt": "Число", "unsignedLong": "Число",
"unsignedShort": "Число", "unsignedByte": "Число",
"date": "Дата", "dateTime": "Дата", "time": "Дата",
"boolean": "Булево",
"base64Binary": "ДвоичныеДанные", "hexBinary": "ДвоичныеДанные",
"anyType": "произвольный", "anySimpleType": "произвольный",
}
FACET_NAMES = ["length", "minLength", "maxLength", "totalDigits", "fractionDigits",
"minInclusive", "maxInclusive", "minExclusive", "maxExclusive"]
def split_ref(el, raw):
if not raw:
return None
if raw.startswith("{"):
close = raw.find("}")
if close < 0:
return None
return (raw[1:close], raw[close + 1:])
parts = raw.split(":")
if len(parts) == 2:
return (el.nsmap.get(parts[0]), parts[1])
return (None, parts[0])
def get_facets(t):
res = {}
for f in FACET_NAMES:
v = t.get(f)
if v:
res[f] = v
for c in t:
if isinstance(c.tag, str) and local(c) == "pattern":
res["pattern"] = c.text or ""
break
return res
def get_enumerations(t):
return [(c.text or "") for c in t if isinstance(c.tag, str) and local(c) == "enumeration"]
def find_type(q, pkg):
if not q:
return None
ns, loc = q
target = None
if not ns or (pkg and ns == pkg.Namespace):
target = pkg
elif ns in by_namespace:
target = by_namespace[ns]
if not target or loc not in target.Types:
return None
return (target.Types[loc], target)
def format_ref_name(q, pkg):
if not q:
return ""
ns, loc = q
if ns == XS_NS:
return XS_TO_1C.get(loc, f"xs:{loc}")
return loc
def resolve_scalar(t, pkg, guard=0):
acc = {"Base1C": None, "Facets": {}, "Alias": None, "Enum": [], "Kind": "scalar"}
if guard > 10 or t is None:
return acc
variety = t.get("variety")
if variety == "List":
it = split_ref(t, t.get("itemType"))
acc["Kind"] = "list"
acc["Base1C"] = "список " + (format_ref_name(it, pkg) if it else "значений")
return acc
if variety == "Union" or t.get("memberTypes"):
members = []
for m in (t.get("memberTypes") or "").split():
q = split_ref(t, m)
if q:
members.append(format_ref_name(q, pkg))
for c in t:
if isinstance(c.tag, str) and local(c) == "typeDef":
members.append(resolve_scalar(c, pkg, guard + 1)["Base1C"])
acc["Kind"] = "union"
acc["Base1C"] = "одно из (" + " | ".join(m for m in members if m) + ")"
return acc
acc["Facets"] = get_facets(t)
acc["Enum"] = get_enumerations(t)
base_q = split_ref(t, t.get("base"))
anon_base = next((c for c in t if isinstance(c.tag, str) and local(c) == "typeDef"), None)
if not base_q and anon_base is not None:
inner = resolve_scalar(anon_base, pkg, guard + 1)
acc["Base1C"] = inner["Base1C"]
for k, v in inner["Facets"].items():
acc["Facets"].setdefault(k, v)
if inner["Enum"] and not acc["Enum"]:
acc["Enum"] = inner["Enum"]
return acc
if not base_q:
acc["Base1C"] = "произвольный"
return acc
if base_q[0] == XS_NS:
acc["Base1C"] = XS_TO_1C.get(base_q[1], f"xs:{base_q[1]}")
return acc
target = find_type(base_q, pkg)
if target and local(target[0]) == "valueType":
inner = resolve_scalar(target[0], target[1], guard + 1)
acc["Base1C"] = inner["Base1C"]
for k, v in inner["Facets"].items():
acc["Facets"].setdefault(k, v)
if inner["Enum"] and not acc["Enum"]:
acc["Enum"] = inner["Enum"]
if not acc["Alias"]:
acc["Alias"] = base_q[1]
return acc
acc["Base1C"] = base_q[1]
return acc
def format_scalar(res):
t, f = res["Base1C"], res["Facets"]
if t == "Строка":
if "length" in f:
t = f'Строка({f["length"]})'
elif "maxLength" in f:
t = f'Строка({f["maxLength"]})'
elif t == "Число":
if "totalDigits" in f:
t = f'Число({f["totalDigits"]},{f.get("fractionDigits", "0")})'
return t
def format_notes(res):
notes = []
if res["Alias"]:
notes.append("" + res["Alias"])
if "pattern" in res["Facets"]:
p = res["Facets"]["pattern"]
if len(p) > 40:
p = p[:40] + ""
notes.append("шаблон " + p)
for k in ("minInclusive", "maxInclusive", "minExclusive", "maxExclusive"):
if k in res["Facets"]:
notes.append(f'{k} {res["Facets"][k]}')
return notes
# ── property rendering ───────────────────────────────────────
def get_prop_rows(type_el, pkg, depth, indent, seen):
rows = []
for p in type_el:
if not isinstance(p.tag, str) or local(p) != "property":
continue
pname = p.get("name")
if not pname:
ref_q = split_ref(p, p.get("ref"))
pname = ref_q[1] if ref_q else "(без имени)"
lower, upper = p.get("lowerBound"), p.get("upperBound")
flags = []
# В модели умолчание lowerBound = 1; помечаем обязательные, как в meta-info
if lower != "0":
flags.append("обязательный")
if upper == "-1":
flags.append("список")
elif upper and upper != "1":
flags.append("до " + upper)
if p.get("form") == "Text":
flags.append("значение элемента")
notes = []
type_text = ""
children = None
child_pkg = pkg
# Именованный вложенный тип берётся так же, как корневой; окольный путь
# через свойство владельца нужен только анонимному — имени у него нет
obj_name = None
obj_ns = None
anon = next((c for c in p if isinstance(c.tag, str) and local(c) == "typeDef"), None)
if anon is not None:
if anon.get(f"{{{XSI_NS}}}type") == "ObjectType":
type_text = "объект (анонимный)"
obj_name = "(анонимный)"
children = anon # анонимные раскрываем всегда: смотреть отдельно негде
else:
res = resolve_scalar(anon, pkg)
type_text = format_scalar(res)
notes += format_notes(res)
if res["Enum"]:
notes.append("значения: " + ", ".join(res["Enum"][:8]))
else:
q = split_ref(p, p.get("type"))
if not q:
type_text = "произвольный"
elif q[0] == XS_NS:
type_text = XS_TO_1C.get(q[1], f"xs:{q[1]}")
else:
target = find_type(q, pkg)
if not target:
type_text = "объект " + q[1]
notes.append(f"(пакет не найден: {q[0]})")
elif local(target[0]) == "objectType":
type_text = "объект " + q[1]
if target[1].Namespace != pkg.Namespace:
type_text += " · " + target[1].Name
obj_name = q[1]
obj_ns = target[1].Namespace
children = target[0]
child_pkg = target[1]
else:
res = resolve_scalar(target[0], target[1])
type_text = format_scalar(res)
notes.append("" + q[1])
notes += [n for n in format_notes(res) if not n.startswith("")]
if res["Enum"]:
notes.append("значения: " + ", ".join(res["Enum"][:8]))
rows.append({"Indent": indent, "Name": pname, "Type": type_text,
"Flags": flags, "Notes": [n for n in notes if n],
"ObjName": obj_name, "ObjNs": obj_ns})
if children is not None:
cname = children.get("name")
key = f"{child_pkg.Namespace}#{cname}"
is_anon = not cname
if not is_anon and key in seen:
rows.append({"Indent": indent + 1, "Name": "(раскрыт выше)", "Type": "",
"Flags": [], "Notes": [], "ObjName": None, "ObjNs": None})
elif is_anon or depth > 1:
next_seen = set(seen)
if not is_anon:
next_seen.add(key)
next_depth = depth if is_anon else depth - 1
rows += get_prop_rows(children, child_pkg, next_depth, indent + 1, next_seen)
return rows
# Оставить только обязательные свойства. Ребёнок необязательного объекта тоже
# уходит: он лежит под необязательной веткой и заполнять его не обязательно.
def select_required(rows):
res = []
cut_from = -1
for r in rows:
if cut_from >= 0 and r["Indent"] > cut_from:
continue
cut_from = -1
if "обязательный" not in r["Flags"]:
cut_from = r["Indent"]
continue
res.append(r)
return res
def write_rows(rows):
if not rows:
O(" (нет свойств)")
return
shown = rows
if OFFSET > 0 or len(rows) > LIMIT:
shown = rows[OFFSET:OFFSET + LIMIT]
w_name = max((len(" " * r["Indent"] + r["Name"]) for r in shown), default=0)
w_type = max((len(r["Type"]) for r in shown), default=0)
for r in shown:
n = " " * r["Indent"] + r["Name"]
line = " " + n.ljust(w_name + 2) + r["Type"].ljust(w_type + 2)
if r["Flags"]:
line += "[" + ", ".join(r["Flags"]) + "] "
if r["Notes"]:
line += ", ".join(r["Notes"])
O(line.rstrip())
if len(rows) > len(shown):
O(f" … показано {len(shown)} из {len(rows)}; листать через -Offset/-Limit")
# ── modes ────────────────────────────────────────────────────
def show_package_list():
O(f"=== Пакеты XDTO: {len(packages)} ===")
O("")
shown = packages
if OFFSET > 0 or len(packages) > LIMIT:
shown = packages[OFFSET:OFFSET + LIMIT]
wn = max((len(p.Name) for p in shown), default=0)
for p in shown:
O(" " + p.Name.ljust(wn + 2) + str(len(p.Types)).rjust(4) + " " + p.Namespace)
if len(packages) > len(shown):
O("")
O(f" … показано {len(shown)} из {len(packages)}; листать через -Offset/-Limit")
O("")
O("Колонки: имя пакета, число типов, namespace.")
O("Следующий шаг: -Package <имя> или -Namespace <URI> — состав пакета; "
"-Name <Тип> — поиск типа по всем пакетам")
def show_package_overview(pkg):
O(f"=== Пакет XDTO: {pkg.Name} ===")
O(f"Namespace: {pkg.Namespace}")
if pkg.Imports:
O("")
O(f"Импорты ({len(pkg.Imports)}):")
for i in pkg.Imports:
dep = by_namespace[i].Name if i in by_namespace else "(пакет не найден)"
O(f" {i}{dep}")
if not pkg.GlobalProps:
O("")
O("Точки входа: нет — пакет не объявляет корневых элементов документа")
else:
O("")
O(f"Точки входа ({len(pkg.GlobalProps)}) — корневые элементы документа:")
for gp in pkg.GlobalProps:
q = split_ref(gp, gp.get("type"))
tn = format_ref_name(q, pkg) if q else "произвольный"
form = " (атрибут)" if gp.get("form") == "Attribute" else ""
O(f' <{gp.get("name")}> → {tn}{form}')
objs = [k for k in sorted(pkg.Types) if local(pkg.Types[k]) == "objectType"]
vals = [k for k in sorted(pkg.Types) if local(pkg.Types[k]) == "valueType"]
if objs:
O("")
O(f"Объектные типы ({len(objs)}):")
for n in objs:
cnt = sum(1 for c in pkg.Types[n] if isinstance(c.tag, str) and local(c) == "property")
base = split_ref(pkg.Types[n], pkg.Types[n].get("base"))
suffix = f"{base[1]}" if base else ""
O(" " + n.ljust(40) + f"свойств: {cnt}" + suffix)
if vals:
O("")
O(f"Типы значений ({len(vals)}):")
for n in vals:
res = resolve_scalar(pkg.Types[n], pkg)
line = " " + n.ljust(40) + format_scalar(res)
if res["Enum"]:
line += " значения: " + ", ".join(res["Enum"][:6])
O(line)
O("")
O("Следующий шаг: -Name <Тип> — структура типа для заполнения")
# Легенда едет вместе с выводом, а не живёт в инструкции: показываем только те
# обозначения, которые реально встретились, иначе она сама становится шумом.
def write_legend(rows):
text = " ".join(r["Type"] + " " + ",".join(r["Flags"]) + " " + ",".join(r["Notes"]) for r in rows)
items = []
if "объект " in text:
items.append("объект X — присвоить вложенный объект XDTO, состав раскрывает -Depth")
if "" in text:
items.append("← Имя — исходный тип из схемы, слева от стрелки развёрнутое значение")
if "список" in text:
items.append("список — коллекция, заполняется через .Добавить()")
if re.search(r"до \d", text):
items.append("до N — коллекция с ограничением сверху")
if "значение элемента" in text:
items.append("значение элемента — собственное значение узла XML")
if "·" in text:
items.append("· Пакет — тип объявлен в другом пакете")
if not items:
return
O("")
O("Обозначения:")
for i in items:
O(" " + i)
def show_type(pkg, type_name):
el = pkg.Types[type_name]
if local(el) == "valueType":
res = resolve_scalar(el, pkg)
O(f"=== Тип значения XDTO: {type_name} ===")
O(f"Пакет: {pkg.Name} · {pkg.Namespace}")
O("")
O("Значение: " + format_scalar(res))
for n in format_notes(res):
O(" " + n)
if res["Enum"]:
O("")
O(f'Допустимые значения ({len(res["Enum"])}):')
for v in res["Enum"]:
O(" " + v)
O("")
O("Создание:")
# Создать(<Тип>, <Значение>) принимает именно ТипЗначенияXDTO —
# для объектного типа эта форма неприменима
O(f' Значение = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("{pkg.Namespace}", "{type_name}"), Значение);')
return
hdr = f"=== Тип XDTO: {type_name} ==="
if DEPTH > 1:
hdr += f" (глубина {DEPTH})"
O(hdr)
O(f"Пакет: {pkg.Name} · {pkg.Namespace}")
base = split_ref(el, el.get("base"))
if base:
O("Наследует: " + base[1])
if el.get("abstract") == "true":
O("Абстрактный — создаётся только тип-наследник")
if el.get("open") == "true":
O("Открытый — допускает произвольные элементы и атрибуты")
O("")
seen = {f"{pkg.Namespace}#{type_name}"}
rows = get_prop_rows(el, pkg, DEPTH, 0, seen)
own = [r for r in rows if r["Indent"] == 0]
if args.RequiredOnly:
total = len(rows)
rows = select_required(rows)
own_req = [r for r in rows if r["Indent"] == 0]
# Фильтр обязан сообщать о себе: иначе список читается как полный
O(f"Свойства: обязательных {len(own_req)} из {len(own)} "
f"(-RequiredOnly; скрыто строк: {total - len(rows)})")
else:
O(f"Свойства ({len(own)}):")
write_rows(rows)
write_legend(rows)
O("")
O("Создание:")
O(f' Тип = ФабрикаXDTO.Тип("{pkg.Namespace}", "{type_name}");')
O(" Объект = ФабрикаXDTO.Создать(Тип);")
# Рецепты для вложенных и анонимных типов: имени у анонимного нет, через
# ФабрикаXDTO.Тип(ns, имя) его не получить — только от свойства владельца
named = next((r for r in rows if r.get("ObjName") and r.get("ObjName") != "(анонимный)"), None)
if named:
O(" // вложенный именованный тип — так же, как корневой:")
O(f' {named["Name"]} = ФабрикаXDTO.Создать(ФабрикаXDTO.Тип("{named["ObjNs"]}", "{named["ObjName"]}"));')
anon_row = next((r for r in rows if r.get("ObjName") == "(анонимный)"), None)
if anon_row:
O(" // у анонимного типа нет имени — только через свойство владельца:")
O(f' {anon_row["Name"]} = ФабрикаXDTO.Создать(Тип.Свойства.Получить("{anon_row["Name"]}").Тип);')
text_row = next((r for r in rows if "значение элемента" in r["Flags"]), None)
if text_row:
O(f' // собственное значение узла лежит в свойстве {text_row["Name"]}')
def show_used_by(type_name, owner_pkg):
O(f"=== Ссылки на тип: {type_name} ===")
if owner_pkg:
O(f"Объявлен в: {owner_pkg.Name} · {owner_pkg.Namespace}")
O("")
hits = []
for p in packages:
for node in p.Root.iter():
if not isinstance(node.tag, str):
continue
for a in ("type", "base", "itemType", "memberTypes"):
raw = node.get(a)
if not raw:
continue
for one in raw.split():
q = split_ref(node, one)
if not q or q[1] != type_name:
continue
if owner_pkg and q[0] and q[0] != owner_pkg.Namespace:
continue
owner = node
while owner is not None and local(owner) not in ("objectType", "valueType"):
owner = owner.getparent()
where = owner.get("name") if (owner is not None and owner.get("name")) else "(верхний уровень)"
what = node.get("name") or local(node)
hits.append(f" {p.Name}.{where}.{what} ({a})")
if not hits:
O(" Ссылок не найдено")
return
O(f"Найдено ({len(hits)}):")
for h in sorted(set(hits)):
O(h)
# ── dispatch ─────────────────────────────────────────────────
selected = None
if direct_pkg_dir:
leaf = os.path.basename(direct_pkg_dir.rstrip("\\/"))
selected = next((p for p in packages if p.Name == leaf), None)
if not selected and args.Namespace:
selected = next((p for p in packages if p.Namespace == args.Namespace), None)
if not selected:
die(f'Пакет с namespace "{args.Namespace}" не найден. '
"Список: -PackagePath <корень> без параметров")
if not selected and args.Package:
selected = next((p for p in packages if p.Name == args.Package), None)
if not selected:
die(f'Пакет "{args.Package}" не найден. Список: -PackagePath <корень> без параметров')
if args.Mode == "used-by":
if not args.Name:
die("Режим used-by требует -Name <Тип>")
owner = selected or next((p for p in packages if args.Name in p.Types), None)
show_used_by(args.Name, owner)
flush_output()
sys.exit(0)
if args.Name:
if not selected:
# Тип известен, пакет — нет: ищем по всей конфигурации
found = [p for p in packages if args.Name in p.Types]
if not found:
die(f'Тип "{args.Name}" не найден ни в одном пакете конфигурации')
if len(found) > 1:
O(f'=== Тип "{args.Name}" найден в нескольких пакетах ({len(found)}) ===')
O("Уточните через -Namespace или -Package:")
O("")
for f in found:
O(f" {f.Name} · {f.Namespace}")
flush_output()
sys.exit(0)
selected = found[0]
if args.Name not in selected.Types:
die(f'В пакете {selected.Name} нет типа "{args.Name}". Список типов: тот же вызов без -Name')
show_type(selected, args.Name)
flush_output()
sys.exit(0)
if selected:
show_package_overview(selected)
else:
show_package_list()
flush_output()
sys.exit(0)
+44
View File
@@ -0,0 +1,44 @@
---
name: xdto-validate
description: Валидация пакета XDTO 1С. Используй после создания или модификации пакета XDTO для проверки корректности
argument-hint: <PackagePath> [-ConfigDir <каталог>] [-Detailed] [-MaxErrors N] [-OutFile <файл>]
allowed-tools:
- Bash
- Read
- Glob
---
# /xdto-validate — Валидация пакета XDTO
Проверяет модель пакета, объект метаданных и его связь с конфигурацией.
Каждая находка выводится отдельной строкой с объяснением. Exit code `1` при ошибках.
## Параметры
| Параметр | Обязательный | Описание |
|----------|--------------|----------|
| `PackagePath` | да | Каталог пакета, `Ext/Package.bin` или `<Имя>.xml` объекта метаданных. Псевдоним — `-Path` |
| `ConfigDir` | нет | Корень исходников. По умолчанию определяется по расположению пакета |
| `Detailed` | нет | Показывать успешные проверки, а не только проблемы |
| `MaxErrors` | нет | Остановиться после N ошибок. По умолчанию 20 |
| `OutFile` | нет | Записать отчёт в файл |
```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/xdto-validate.ps1" -PackagePath "<путь>"
```
`[ERROR]` — платформа такой пакет не примет либо примет неправильно.
`[WARN]` — пакет рабочий, но есть риск, о котором стоит знать.
## Зачем запускать, если пакет и так грузится
Часть дефектов платформа не диагностирует: неразрешённый тип из чужого пространства
имён она молча подменяет на `xs:anyType`, и пакет выглядит загруженным, пока
`ФабрикаXDTO` не отдаст в рантайме бесструктурное значение. Такие вещи видно только
статически — до загрузки в базу.
## Типичный workflow
1. `/xdto-compile`, `/xdto-edit` или переработка через `/xdto-decompile``/xdto-compile -Force`
2. `/xdto-validate <путь>` — до загрузки в базу
3. `/db-load-xml` + `/db-update`
@@ -0,0 +1,566 @@
# xdto-validate v1.1 — 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"
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу: импортируются, но
# targetNamespace с таким значением ни у одного пакета нет)
$PLATFORM_NS = @(
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema"
)
[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 "Ожидался корневой <package>, найден <$($pkg.get_LocalName())>"
& $finalize
exit 1
}
Report-OK "Package.bin: корректный XML, корень <package>"
$targetNs = $pkg.GetAttribute("targetNamespace")
if (-not $targetNs) {
Report-Error "У <package> не задан 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]
$objectTypeNames = New-Object System.Collections.Generic.HashSet[string]
$valueTypeNames = New-Object System.Collections.Generic.HashSet[string]
$globalProps = New-Object System.Collections.Generic.HashSet[string]
$topSequence = New-Object System.Collections.ArrayList
foreach ($n in $pkg.ChildNodes) {
if ($n.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
[void]$topSequence.Add($n.get_LocalName())
switch ($n.get_LocalName()) {
"import" { [void]$imports.Add($n.GetAttribute("namespace")) }
"objectType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$objectTypeNames.Add($n.GetAttribute("name")) }
"valueType" { [void]$localTypes.Add($n.GetAttribute("name")); [void]$valueTypeNames.Add($n.GetAttribute("name")) }
"property" { if ($n.HasAttribute("name")) { [void]$globalProps.Add($n.GetAttribute("name")) } }
}
}
# --- Порядок элементов верхнего уровня ---
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
$TOP_ORDER = @("import", "property", "valueType", "objectType")
$prevRank = -1
$orderOk = $true
foreach ($t in $topSequence) {
$rank = [array]::IndexOf($TOP_ORDER, $t)
if ($rank -lt 0) { continue }
if ($rank -lt $prevRank) {
Report-Error "Нарушен порядок элементов верхнего уровня: <$t> после <$($TOP_ORDER[$prevRank])>. Модель требует import -> property -> valueType -> objectType; платформа отвергнет пакет при обновлении конфигурации"
$orderOk = $false
break
}
$prevRank = $rank
}
if ($orderOk) { Report-OK "Порядок элементов верхнего уровня корректен" }
# --- 4. Duplicate type names ---
$seen = @{}
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`", но <import namespace=`"$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 ---
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
# типовых конфигураций. Сигналом он становится только вместе с anyType — тогда это
# почти наверняка неразрешённая зависимость.
$unused = @()
foreach ($imp in $imports) { if (-not $usedNamespaces.Contains($imp)) { $unused += $imp } }
if ($unused.Count -gt 0 -and $anyTypeProps.Count -gt 0) {
Report-Warn "Импорт(ы) без единого использованного типа: $($unused -join ', ') — вместе с anyType это признак неразрешённой зависимости"
}
if ($imports.Count -gt 0 -and $unused.Count -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"))) {
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
Report-Warn "$nm : length задан вместе с minLength/maxLength — спецификация XSD считает их взаимоисключающими"
}
$minL = $t.GetAttribute("minLength"); $maxL = $t.GetAttribute("maxLength")
if ($minL -and $maxL -and ([int]$minL -gt [int]$maxL)) {
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"
}
# В модели XDTO fixed — булев признак, само значение лежит в default.
# В XML-схеме наоборот: fixed="V" совмещает признак и значение.
if ($p.HasAttribute("fixed")) {
$fx = $p.GetAttribute("fixed")
$pName = if ($p.HasAttribute("name")) { $p.GetAttribute("name") } else { $p.GetAttribute("ref") }
if (@("true", "false") -cnotcontains $fx) {
Report-Error "Свойство `"$pName`": fixed=`"$fx`" — в модели это булев признак, значение задаётся в default (в XML-схеме признак и значение совмещены в fixed)"
} elseif ($fx -ceq "true" -and -not $p.HasAttribute("default")) {
Report-Error "Отсутствует фиксированное значение свойства '$pName': есть fixed=`"true`", нет default"
}
}
if ($script:stopped) { break }
}
if (-not $script:stopped) { Report-OK "Свойства: form, кратности и фиксированные значения корректны" }
# --- 10b. Structural consistency ---
$structOk = $true
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType' or local-name()='typeDef']")) {
if ($t.get_LocalName() -eq "typeDef" -and $t.GetAttribute("type", $XSI_NS) -ne "ObjectType") { continue }
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
$propNames = @{}
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "property") { continue }
$pn = $c.GetAttribute("name")
if ($pn) {
if ($propNames.ContainsKey($pn)) {
Report-Error "$tn : дублирующееся имя свойства `"$pn`""
$structOk = $false
}
$propNames[$pn] = $true
}
}
if ($script:stopped) { break }
}
foreach ($p in $pkg.SelectNodes("//*[local-name()='property']")) {
$pn = if ($p.HasAttribute("name")) { $p.GetAttribute("name") } else { $p.GetAttribute("ref") }
if ($p.HasAttribute("name") -and $p.HasAttribute("ref")) {
Report-Error "Свойство `"$pn`": заданы одновременно name и ref — допустимо только одно"
$structOk = $false
}
$inlineTypeDef = $null
foreach ($c in $p.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $inlineTypeDef = $c; break }
}
if ($inlineTypeDef -and $p.HasAttribute("type")) {
Report-Error "Свойство `"$pn`": заданы одновременно type и вложенный <typeDef> — допустимо только одно"
$structOk = $false
}
if ($inlineTypeDef -and -not $inlineTypeDef.HasAttribute("type", $XSI_NS)) {
Report-Error "Свойство `"$pn`": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)"
$structOk = $false
}
if ($script:stopped) { break }
}
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
foreach ($vt in $pkg.SelectNodes("//*[local-name()='valueType']")) {
foreach ($c in $vt.ChildNodes) {
if ($c.NodeType -ne [System.Xml.XmlNodeType]::Element -or $c.get_LocalName() -ne "typeDef") { continue }
if ($c.HasAttribute("type", $XSI_NS)) {
Report-Warn "$($vt.GetAttribute('name')) : у <typeDef> внутри <valueType> задан xsi:type — платформа его здесь не пишет"
}
}
}
# Род базового типа должен совпадать
foreach ($t in $pkg.SelectNodes("//*[local-name()='objectType'][@base]")) {
$b = $t.GetAttribute("base")
$parts = $b.Split(":")
if ($parts.Count -ne 2) { continue }
$bns = $t.GetNamespaceOfPrefix($parts[0])
if ($bns -ne $targetNs) { continue }
if ($valueTypeNames.Contains($parts[1])) {
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на valueType, а objectType может наследоваться только от objectType"
$structOk = $false
}
}
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType'][@base]")) {
$b = $t.GetAttribute("base")
$parts = $b.Split(":")
if ($parts.Count -ne 2) { continue }
$bns = $t.GetNamespaceOfPrefix($parts[0])
if ($bns -ne $targetNs) { continue }
if ($objectTypeNames.Contains($parts[1])) {
Report-Error "$($t.GetAttribute('name')) : base=`"$b`" ссылается на objectType, а valueType может строиться только на простом типе"
$structOk = $false
}
}
# Union без состава
foreach ($t in $pkg.SelectNodes("//*[local-name()='valueType' or local-name()='typeDef'][@variety='Union']")) {
if ($t.HasAttribute("memberTypes")) { continue }
$hasMember = $false
foreach ($c in $t.ChildNodes) {
if ($c.NodeType -eq [System.Xml.XmlNodeType]::Element -and $c.get_LocalName() -eq "typeDef") { $hasMember = $true; break }
}
if (-not $hasMember) {
$tn = if ($t.HasAttribute("name")) { $t.GetAttribute("name") } else { "(анонимный тип)" }
Report-Warn "$tn : variety=`"Union`" без memberTypes и без вложенных типов — состав объединения пуст"
}
}
if ($structOk -and -not $script:stopped) { Report-OK "Структура типов и свойств согласована" }
if ($script:stopped) { & $finalize; exit 1 }
# --- 11. Metadata object ---
if ($mdPath) {
$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 "В объекте метаданных не задано <Name>"
} elseif ($mdName.InnerText -ne $fileName) {
Report-Error "<Name>$($mdName.InnerText)</Name> не совпадает с именем каталога `"$fileName`""
} else {
Report-OK "Объект метаданных: Name = $($mdName.InnerText)"
}
if ($mdNs -and $mdNs.InnerText -ne $targetNs) {
Report-Error "<Namespace>$($mdNs.InnerText)</Namespace> не совпадает с 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 "<XDTOPackage>$fileName</XDTOPackage> отсутствует в 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 {}
}
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
$knownNs = @{}
foreach ($other in (Get-ChildItem $pkgRoot -Directory -ErrorAction SilentlyContinue)) {
$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)
$knownNs[$od.DocumentElement.GetAttribute("targetNamespace")] = $other.Name
} catch {}
}
$missing = @()
foreach ($imp in $imports) { if (-not $knownNs.ContainsKey($imp) -and $PLATFORM_NS -notcontains $imp) { $missing += $imp } }
if ($missing.Count -gt 0) {
Report-Error ("Импортируемые пакеты не определены в конфигурации: " + ($missing -join ", ") +
". Платформа отвергнет пакет при обновлении конфигурации — соберите зависимости первыми")
} elseif ($imports.Count -gt 0) {
Report-OK "Все импорты разрешаются в пакеты конфигурации"
}
if ($clash.Count -gt 0) {
Report-Warn "targetNamespace `"$targetNs`" объявлен также в пакет(ах): $($clash -join ', '). Платформа это допускает, но <import> на это пространство имён становится неоднозначным"
} else {
Report-OK "targetNamespace уникален в конфигурации"
}
}
} else {
Report-Warn "Configuration.xml не найден ($ConfigDir) — проверки регистрации и уникальности namespace пропущены"
}
# --- Final ---
& $finalize
if ($script:errors -gt 0) { exit 1 }
exit 0
@@ -0,0 +1,569 @@
# xdto-validate v1.1 — 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
# Эти пространства имён предоставляет сама платформа — пакетов в конфигурации
# для них нет и быть не должно (выведено по корпусу)
PLATFORM_NS = {
"http://v8.1c.ru/8.1/data/core",
"http://v8.1c.ru/8.1/data/enterprise",
"http://v8.1c.ru/8.1/data/enterprise/current-config",
"http://v8.1c.ru/8.1/data-composition-system/settings",
"http://v8.1c.ru/8.3/data/ext",
"http://www.w3.org/2001/XMLSchema",
}
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 _parse_xml(source, from_string=False):
"""Разбор с узким отступлением для не-URI пространств имён.
Платформа допускает в targetNamespace произвольную строку (в выгрузке БП есть
пакет с кириллическим «ДопФайлУниверсальный»), .NET такое принимает, а libxml2
отвергает. Откатываемся на восстанавливающий разбор ТОЛЬКО на этой ошибке,
иначе по-настоящему битый XML перестал бы отличаться от корректного.
"""
try:
return (etree.fromstring(source) if from_string else etree.parse(source))
except etree.XMLSyntaxError as e:
if "is not a valid URI" not in str(e):
raise
p = etree.XMLParser(recover=True)
return (etree.fromstring(source, p) if from_string else etree.parse(source, p))
def local(el):
return etree.QName(el).localname
# ── 1. well-formedness ───────────────────────────────────────
try:
doc = _parse_xml(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"Ожидался корневой <package>, найден <{local(pkg)}>")
finalize()
sys.exit(1)
report_ok("Package.bin: корректный XML, корень <package>")
target_ns = pkg.get("targetNamespace")
if not target_ns:
report_error("У <package> не задан 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()
object_type_names = set()
value_type_names = set()
global_props = set()
top_sequence = []
for n in pkg:
if not isinstance(n.tag, str):
continue
ln = local(n)
top_sequence.append(ln)
if ln == "import":
imports.append(n.get("namespace"))
elif ln == "objectType":
local_types.add(n.get("name"))
object_type_names.add(n.get("name"))
elif ln == "valueType":
local_types.add(n.get("name"))
value_type_names.add(n.get("name"))
elif ln == "property" and n.get("name"):
global_props.add(n.get("name"))
# ── порядок элементов верхнего уровня ────────────────────────
# Модель требует import -> property -> valueType -> objectType. Нарушение платформа
# не прощает: db-update падает с «Ошибка преобразования данных XDTO».
TOP_ORDER = ["import", "property", "valueType", "objectType"]
prev_rank = -1
order_ok = True
for t in top_sequence:
if t not in TOP_ORDER:
continue
rank = TOP_ORDER.index(t)
if rank < prev_rank:
report_error(f"Нарушен порядок элементов верхнего уровня: <{t}> после <{TOP_ORDER[prev_rank]}>. "
"Модель требует import -> property -> valueType -> objectType; "
"платформа отвергнет пакет при обновлении конфигурации")
order_ok = False
break
prev_rank = rank
if order_ok:
report_ok("Порядок элементов верхнего уровня корректен")
# ── 4. duplicate type names ──────────────────────────────────
seen = set()
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}", но <import namespace="{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 ────────────────────────────────────────
# Сам по себе неиспользуемый импорт безвреден и встречается в четверти пакетов
# типовых конфигураций. Сигналом он становится только вместе с anyType.
unused = [i for i in imports if i not in used_namespaces]
if unused and any_type_props:
report_warn(f'Импорт(ы) без единого использованного типа: {", ".join(unused)}'
"вместе с anyType это признак неразрешённой зависимости")
if imports and not unused:
report_ok(f"{len(imports)} импорт(ов) — все используются")
# ── 8. nillable on attribute-form properties ─────────────────
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")):
# Спецификация XSD это запрещает, но платформа такие типы хранит — предупреждение, не ошибка
report_warn(f"{nm} : length задан вместе с minLength/maxLength — "
"спецификация XSD считает их взаимоисключающими")
min_l, max_l = t.get("minLength"), t.get("maxLength")
if min_l and max_l and int(min_l) > int(max_l):
report_error(f"{nm} : minLength ({min_l}) больше maxLength ({max_l})")
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")
# В модели XDTO fixed — булев признак, само значение лежит в default.
# В XML-схеме наоборот: fixed="V" совмещает признак и значение.
fx = p.get("fixed")
if fx is not None:
p_name = p.get("name") if p.get("name") is not None else p.get("ref")
if fx not in ("true", "false"):
report_error(
f'Свойство "{p_name}": fixed="{fx}" — в модели это булев признак, '
"значение задаётся в default (в XML-схеме признак и значение совмещены в fixed)"
)
elif fx == "true" and p.get("default") is None:
report_error(
f"Отсутствует фиксированное значение свойства '{p_name}': "
'есть fixed="true", нет default'
)
if state["stopped"]:
break
if not state["stopped"]:
report_ok("Свойства: form, кратности и фиксированные значения корректны")
# ── 10b. structural consistency ──────────────────────────────
struct_ok = True
for t in pkg.iter():
if not isinstance(t.tag, str) or local(t) not in ("objectType", "typeDef"):
continue
if local(t) == "typeDef" and t.get(f"{{{XSI_NS}}}type") != "ObjectType":
continue
tn = t.get("name") or "(анонимный тип)"
prop_names = set()
for c in t:
if not isinstance(c.tag, str) or local(c) != "property":
continue
pn = c.get("name")
if pn:
if pn in prop_names:
report_error(f'{tn} : дублирующееся имя свойства "{pn}"')
struct_ok = False
prop_names.add(pn)
if state["stopped"]:
break
for p_ in pkg.iter():
if not isinstance(p_.tag, str) or local(p_) != "property":
continue
pn = p_.get("name") or p_.get("ref")
if p_.get("name") is not None and p_.get("ref") is not None:
report_error(f'Свойство "{pn}": заданы одновременно name и ref — допустимо только одно')
struct_ok = False
inline = next((c for c in p_ if isinstance(c.tag, str) and local(c) == "typeDef"), None)
if inline is not None and p_.get("type") is not None:
report_error(f'Свойство "{pn}": заданы одновременно type и вложенный <typeDef> — допустимо только одно')
struct_ok = False
if inline is not None and inline.get(f"{{{XSI_NS}}}type") is None:
report_error(f'Свойство "{pn}": у вложенного <typeDef> не задан xsi:type (ValueType или ObjectType)')
struct_ok = False
if state["stopped"]:
break
# Анонимный тип внутри valueType задаёт базовый тип и xsi:type не несёт
for vt in pkg.iter():
if not isinstance(vt.tag, str) or local(vt) != "valueType":
continue
for c in vt:
if isinstance(c.tag, str) and local(c) == "typeDef" and c.get(f"{{{XSI_NS}}}type") is not None:
report_warn(f'{vt.get("name")} : у <typeDef> внутри <valueType> задан xsi:type — '
"платформа его здесь не пишет")
# Род базового типа должен совпадать
for t in pkg.iter():
if not isinstance(t.tag, str) or t.get("base") is None:
continue
kind = local(t)
if kind not in ("objectType", "valueType"):
continue
parts = t.get("base").split(":")
if len(parts) != 2:
continue
if t.nsmap.get(parts[0]) != target_ns:
continue
if kind == "objectType" and parts[1] in value_type_names:
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на valueType, '
"а objectType может наследоваться только от objectType")
struct_ok = False
if kind == "valueType" and parts[1] in object_type_names:
report_error(f'{t.get("name")} : base="{t.get("base")}" ссылается на objectType, '
"а valueType может строиться только на простом типе")
struct_ok = False
# Union без состава
for t in pkg.iter():
if not isinstance(t.tag, str) or local(t) not in ("valueType", "typeDef"):
continue
if t.get("variety") != "Union" or t.get("memberTypes") is not None:
continue
if not any(isinstance(c.tag, str) and local(c) == "typeDef" for c in t):
tn = t.get("name") or "(анонимный тип)"
report_warn(f'{tn} : variety="Union" без memberTypes и без вложенных типов — состав объединения пуст')
if struct_ok and not state["stopped"]:
report_ok("Структура типов и свойств согласована")
if state["stopped"]:
finalize()
sys.exit(1)
# ── 11. metadata object ──────────────────────────────────────
if md_path:
md = _parse_xml(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("В объекте метаданных не задано <Name>")
elif (md_name.text or "") != file_name:
report_error(f'<Name>{md_name.text}</Name> не совпадает с именем каталога "{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"<Namespace>{md_ns.text}</Namespace> не совпадает с 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 = _parse_xml(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"<XDTOPackage>{file_name}</XDTOPackage> отсутствует в 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 _parse_xml(ob).getroot().get("targetNamespace") == target_ns:
clash.append(other)
except Exception: # noqa: BLE001
pass
# Платформа отвергает пакет, если импортируемого namespace нет в конфигурации:
# «Ошибка проверки модели XDTO: xdto-package-3.3 … не определен»
known_ns = {}
for other in sorted(os.listdir(pkg_root)):
ob = os.path.join(pkg_root, other, "Ext", "Package.bin")
if not os.path.exists(ob):
continue
try:
known_ns[_parse_xml(ob).getroot().get("targetNamespace")] = other
except Exception: # noqa: BLE001
pass
missing_imports = [i for i in imports if i not in known_ns and i not in PLATFORM_NS]
if missing_imports:
report_error("Импортируемые пакеты не определены в конфигурации: "
+ ", ".join(missing_imports)
+ ". Платформа отвергнет пакет при обновлении конфигурации — "
"соберите зависимости первыми")
elif imports:
report_ok("Все импорты разрешаются в пакеты конфигурации")
if clash:
report_warn(f'targetNamespace "{target_ns}" объявлен также в пакет(ах): {", ".join(clash)}. '
"Платформа это допускает, но <import> на это пространство имён становится неоднозначным")
else:
report_ok("targetNamespace уникален в конфигурации")
else:
report_warn(f"Configuration.xml не найден ({config_dir}) — проверки регистрации и уникальности namespace пропущены")
finalize()
sys.exit(1 if state["errors"] > 0 else 0)
+5
View File
@@ -21,3 +21,8 @@
# Бинарники 1С
*.bin binary
# Package.bin пакетов XDTO — текстовый XML, несмотря на расширение. Оставляем
# под правилом *.bin binary (байты не нормализуются), но включаем текстовый diff,
# иначе изменение модели пакета в истории выглядит как «Binary files differ».
XDTOPackages/**/Package.bin diff
+3
View File
@@ -32,6 +32,9 @@ __pycache__/
.claude/skills/web-test/scripts/node_modules/
.claude/skills/web-test/.browser-session.json
# Маркер отработавшего prepare() в фикстуре _suite-root
tests/web-test/_suite-root/prepare-ran.txt
# Скриншоты и видео (артефакты тестирования web-test)
*.png
*.mp4
+1
View File
@@ -70,6 +70,7 @@ python tools/cc-1c-skills/scripts/switch.py
| Расширения (CFE) | 5 навыков `/cfe-*` | Создание, заимствование, перехват методов, валидация, анализ расширений | [Подробнее](docs/cfe-guide.md) |
| Подсистемы (Subsystem) | 4 навыка `/subsystem-*` | Анализ, создание, редактирование, валидация подсистем конфигурации | [Подробнее](docs/subsystem-guide.md) |
| Командный интерфейс (CI) | 2 навыка `/interface-*` | Редактирование и валидация CommandInterface.xml подсистем | [Подробнее](docs/subsystem-guide.md) |
| Пакеты XDTO | 5 навыков `/xdto-*` | Анализ, создание из XML-схемы, выгрузка в схему, точечное редактирование, валидация пакетов XDTO | [Подробнее](docs/xdto-guide.md) |
| Базы данных (DB) | 9 навыков `/db-*` | Создание баз, загрузка/выгрузка конфигураций, обновление БД, загрузка из Git | [Подробнее](docs/db-guide.md) |
| Веб-публикация (Web) | 4 навыка `/web-*` | Публикация баз через Apache, статус, остановка, удаление публикаций | [Подробнее](docs/web-guide.md) |
| Тестирование (Web) | `/web-test` | Взаимодействие с веб-клиентом 1С — навигация, формы, таблицы, отчёты, тестирование | [Подробнее](docs/web-test-guide.md) |
+1 -1
View File
@@ -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-ссылка) |
+329
View File
@@ -0,0 +1,329 @@
# Спецификация формата XML пакетов XDTO 1С
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
Версии формата: `2.17` (платформа 8.3.208.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
<ChildObjects>
...
<XDTOPackage>ОбменСБанком</XDTOPackage>
</ChildObjects>
```
Ни форм, ни модулей, ни макетов у пакета XDTO нет.
---
## 2. Объект метаданных `<Имя>.xml`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" ... version="2.17">
<XDTOPackage uuid="6417d7c8-6436-4907-98eb-44cd7638f3f1">
<Properties>
<Name>ApdexExport</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Apdex export</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Namespace>www.v8.1c.ru/ssl/performace-assessment/apdexExport</Namespace>
</Properties>
</XDTOPackage>
</MetaDataObject>
```
| Свойство | Описание |
|---|---|
| `Name` | Имя объекта метаданных. Должно быть валидным идентификатором 1С |
| `Synonym` | Многоязычное представление |
| `Comment` | Комментарий |
| `Namespace` | URI целевого пространства имён. Дублирует `targetNamespace` в `Package.bin` |
Других свойств у пакета XDTO нет.
---
## 3. `Ext/Package.bin` — модель пакета
Несмотря на расширение `.bin`, это **текстовый XML**: UTF-8 с BOM, перевод строки CRLF,
отступ — символы табуляции.
```xml
<package xmlns="http://v8.1c.ru/8.1/xdto"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
targetNamespace="urn:1C.ru:ClientBankExchange">
<property xmlns:d2p1="urn:1C.ru:ClientBankExchange" name="ClientBankExchange" type="d2p1:ClientBankExchange"/>
<valueType name="СуммаТип" base="xs:decimal" variety="Atomic" totalDigits="18" fractionDigits="2"/>
<objectType name="ClientBankExchange">
<property xmlns:d3p1="urn:1C.ru:ClientBankExchange" name="ВерсияФормата" type="d3p1:ВерсияСхемы"/>
<property name="Отправитель">
<typeDef xsi:type="ValueType" base="xs:string" variety="Atomic" maxLength="160"/>
</property>
</objectType>
</package>
```
### 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. `<package>`
| Атрибут | Значения | Описание |
|---|---|---|
| `targetNamespace` | URI | Целевое пространство имён |
| `elementFormQualified` | `true` \| `false` | Умолчание при отсутствии — `true` |
| `attributeFormQualified` | `true` \| `false` | Умолчание при отсутствии — `false` |
Порядок атрибутов: `targetNamespace`, `elementFormQualified`, `attributeFormQualified`.
### 3.3. `<import>`
`<import namespace="URI"/>` — объявление зависимости от другого пакета. Разрешается
по namespace среди пакетов конфигурации; `schemaLocation` в модели XDTO нет.
### 3.4. `<objectType>` и `<typeDef xsi:type="ObjectType">`
| Атрибут | Значения | Описание |
|---|---|---|
| `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. `<valueType>` и `<typeDef xsi:type="ValueType">`
| Атрибут | Значения |
|---|---|
| `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
<valueType name="INN12Type" variety="Atomic" length="12">
<typeDef base="xs:string" variety="Atomic"/>
<pattern>[0-9]{12}</pattern>
</valueType>
```
Дочерними элементами идут только `<pattern>` и `<enumeration>` — значение в тексте узла:
```xml
<valueType name="НомерСчетаТип" base="xs:string" variety="Atomic" length="20">
<pattern>[0-9]{20}</pattern>
</valueType>
```
У `<enumeration>` встречается атрибут `xsi:type` (например `xs:string`), указывающий тип литерала.
### 3.6. `<property>`
| Атрибут | Значения | Описание |
|---|---|---|
| `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
<objectType name="Error">
<property name="code" type="xs:NCName" lowerBound="1" form="Attribute"/>
<property name="__content" type="xs:string" form="Text"/>
</objectType>
```
Наличие `form="Text"` не отменяет флагов самого типа — `mixed`, `sequenced` и прочие
задаются как обычно:
```xml
<objectType name="Account" mixed="true" sequenced="true">
<property name="bic" type="d3p1:BicType" lowerBound="1" form="Attribute"/>
<property name="__content" type="d3p1:AccNumType" form="Text"/>
</objectType>
```
**`localName`** — при импорте XML-схемы имя, недопустимое как идентификатор 1С,
санируется, а оригинал сохраняется:
```xml
<property name="isFixPlaceResidence_" type="xs:boolean" localName="isFixPlaceResidence "/>
```
### 3.7. Порядок свойств внутри типа
Свойства с `form="Attribute"` обычно идут перед остальными — так записаны 96.5% типов
корпуса. Оставшиеся 3.5% содержат произвольное чередование; порядок значим и сохраняется
платформой как есть.
---
## 4. Схема префиксов пространств имён
Каждая ссылка на тип из пространства имён, отличного от `xs:`/`xsi:`, требует объявления
префикса вида `dNpM`:
```xml
<property xmlns:d3p1="urn:1C.ru:ClientBankExchange" name="ВерсияФормата" type="d3p1:ВерсияСхемы"/>
```
- `N` — глубина узла: `package` = 1, его прямые потомки = 2, свойство внутри `objectType` = 3,
свойство внутри `typeDef` = 5, глубже — 7, 9 и т.д.
- `M` — порядковый номер нового пространства имён, объявляемого на этом узле (с 1).
Префикс объявляется **на первом узле, которому он нужен**; потомки переиспользуют его из
области видимости, а не объявляют заново:
```xml
<objectType xmlns:d2p1="…/Permissions/1.0.0.1" name="InternetConnectionBase" base="d2p1:PermissionBase">
<property name="Protocol" type="d2p1:NetworkProtocols" lowerBound="0" nillable="true"/>
</objectType>
```
Целевое пространство имён самого пакета исключением не является — ссылка на собственный
тип тоже требует локального объявления.
Частоты по корпусу: `d3p1` — 109 738, `d2p1` — 13 108, `d5p1` — 9 581, далее по убыванию до `d16p1`.
### 4.1. Нотация Кларка в `memberTypes`
Значения `memberTypes` записываются нотацией Кларка `{URI}Локальное`, без префикса
(125 случаев из 135 в корпусе; остальные — обычные префиксные QName):
```xml
<valueType xmlns:d2p1="http://v8.1c.ru/8.1/data/core" name="UUID" variety="Union"
memberTypes="{http://v8.1c.ru/8.1/data/core}UUID {http://www.w3.org/2001/XMLSchema}base64Binary"/>
```
Обратите внимание: объявление `xmlns:d2p1` присутствует, хотя в значении не используется —
пространство имён всё равно проходит через общий механизм выделения префиксов. Если оно уже
объявлено предком, нового объявления не появляется.
Остальные атрибуты-QName (`type`, `base`, `ref`, `itemType`) всегда префиксные.
### 4.2. Осмысленные префиксы
Изредка вместо сгенерированного `dNpM` встречается содержательный префикс — например
`dcsset` для настроек компоновки данных:
```xml
<property xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"
name="Настройка" type="dcsset:Filter" lowerBound="0"/>
```
### 4.3. Свойство с `qualified` сериализуется с префиксом
Если у свойства задан `qualified`, платформа пишет и сам тег, и имя атрибута с явным
префиксом пространства имён модели XDTO (хотя по умолчанию оно и так объявлено как `xmlns=`):
```xml
<d3p2:property xmlns:d3p1="http://bssys.com/upg/request" xmlns:d3p2="http://v8.1c.ru/8.1/xdto"
name="numCheck" type="d3p1:BoolType" lowerBound="0" form="Attribute"
d3p2:qualified="false"/>
```
Префикс для 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
<!-- в схеме было type="ns2:contact" -->
<property name="contact" type="xs:anyType"/>
```
При этом `<import namespace="…"/>` в пакете остаётся. Признак «объявлен импорт, но
ни один тип из этого пространства имён не используется» — надёжный индикатор проблемы.
+32 -1
View File
@@ -308,7 +308,7 @@ JSON-**строка** → `xsi:type="xs:string"` (напр. год `"2000"`, к
}
```
Свойства объектной формы ТЧ: `synonym` (ML; нет ключа → авто из имени), `tooltip` (ML), `comment` (строка), `fillChecking` (`DontCheck`|`ShowError`|`ShowWarning`), `use` (`ForItem`|`ForFolder`|`ForFolderAndItem`, только Catalog/ПВХ; omit при дефолте `ForItem`), `attributes` (колонки; синоним `columns`), `lineNumber` (кастомизация стандартного реквизита НомерСтроки, см. ниже).
Свойства объектной формы ТЧ: `synonym` (ML; нет ключа → авто из имени), `tooltip` (ML), `comment` (строка), `fillChecking` (`DontCheck`|`ShowError`|`ShowWarning`), `use` (`ForItem`|`ForFolder`|`ForFolderAndItem`, только Catalog/ПВХ; omit при дефолте `ForItem`), `attributes` (колонки; синоним `columns`), `lineNumber` (кастомизация стандартного реквизита НомерСтроки, см. ниже), `lineNumberLength` (разрядность номера строки, см. §5.2).
Для Catalog/ChartOfCharacteristicTypes в Properties ТЧ пишется `<Use>` (дефолт `ForItem`; ключ `use` объектной формы → `ForFolder`/`ForFolderAndItem`). Document `<Use>` не имеет.
@@ -341,6 +341,30 @@ LineNumber дефолтные. Ключ `lineNumber` на объектной ф
Декомпилятор эмитит `lineNumber` только при отклонении ≥1 свойства от дефолта. NB: редкий хвост ТЧ (~2.5% корпуса)
блок `<StandardAttributes>` вовсе опускает — правило не выведено; компилятор такие не воспроизводит (эмитит блок всегда).
### 5.2 `lineNumberLength` — разрядность номера строки (формат 2.20)
Свойство `<LineNumberLength>` появилось в формате **2.20** (платформа 8.3.27): целое **5..9**, задаёт
предельное число строк ТЧ — `5` → 99 999 (прежний потолок), `9` → 999 999 999. Нужно прикладным решениям
с большими табличными частями (классический пример — «Показатели» в документе расчёта зарплаты).
```json
"tabularSections": {
"Показатели": { "attributes": ["Значение: Number(15,2)"], "lineNumberLength": 9 }
}
```
Ключа нет → компилятор берёт **дефолт из режима совместимости** конфигурации (`CompatibilityMode`
в `Configuration.xml`): `Version8_3_27` и выше → `9`, ниже → `5`. Это зеркалит конфигуратор, который
фиксирует значение в момент создания ТЧ и позже не пересчитывает: в одной конфигурации нормально
соседствуют ТЧ с `5` (созданные раньше) и `9` (созданные на новом режиме).
В формате 2.17 тег не эмитится вовсе. Декомпилятор захватывает значение при наличии тега — всегда,
а не только при отличии от дефолта: дефолт зависит от режима совместимости, и выводить его повторно
значило бы дублировать логику компилятора.
**Ограничение расширений:** у *заимствованной* ТЧ свойство переопределить нельзя (по документации 1С —
до версии 8.3.28); у ТЧ, добавленных самим расширением, — можно.
---
## 6. Значения перечислений
@@ -442,6 +466,13 @@ LineNumber дефолтные. Ключ `lineNumber` на объектной ф
В `standardAttributes` указывают **только отклонения** от профиля (переопределение синонима, нетиповой fillChecking и т.п.).
**Формат 2.20 — `TypeReductionMode`** (режим приведения типов). Платформа 8.3.27 пишет его каждому
стандартному реквизиту; значение выводится компилятором и в DSL обычно не указывается:
`TransformValues` для всех, кроме `Owner` — там `Deny`. Ключ `TypeReductionMode` в override оставлен
на случай отклонения от этого правила (правило выведено на корпусе acc; при расхождении в другой
конфигурации значение можно задать явно). У измерений регистра сведений действует то же свойство —
ключ реквизита `typeReductionMode` (дефолт `TransformValues`). В формате 2.17 тег не эмитится.
```json
{
"type": "Catalog", "name": "Контрагенты",
+1 -3
View File
@@ -921,15 +921,13 @@ Platform-pattern: `startDate`/`endDate`/`date` эмитятся ТОЛЬКО д
"type": "table",
"name": "Таблица",
"rows": [
{ "groupBy": ["Номенклатура"], "selection": ["Auto"], "order": ["Auto"] }
{ "groupBy": ["Номенклатура"] }
],
"columns": [
{
"name": "Период",
"groupBy": ["Период"],
"filter": ["Сумма > 0"],
"selection": ["Auto"],
"order": ["Auto"],
"outputParameters": { "РасположениеИтогов": "None" },
"userSettingID": "auto",
"userSettingPresentation": { "ru": "Колонка с периодом" }
+9
View File
@@ -254,6 +254,15 @@ export default async function({
23 passed, 1 failed, 0 skipped (3m 42s)
```
### Прогон части набора
```
> Прогони только сценарии из папки 03-приходные-накладные
> Прогони только тесты с тегом контрагенты
```
Подмножество выбирается тремя способами: путём к подпапке или отдельному файлу, тегом (`--tags=`) или фильтром по имени теста (`--grep=`). Путь к подпапке работает наравне с остальными — конфиг и подготовка стенда всё равно берутся из корня набора (папки приложения в `tests/`), движок находит его сам, поднимаясь вверх. Отдельного URL или флагов для этого не нужно.
### Подробный отчёт
```
+30 -18
View File
@@ -29,12 +29,22 @@ node run.mjs test <dir|file>... [флаги]
| `--report=path` | (нет) | Записать машинный отчёт в файл (JSON или XML для `--format=junit`) |
| `--report=-` | (нет) | Машинный отчёт в stdout (`-` = stdout); человеческий прогресс уходит в stderr |
| `--format=fmt` | json | Формат отчёта: `json` / `allure` / `junit` |
| `--report-dir=path` | dirname(report) / testDir | Каталог для скриншотов, видео, Allure-результатов |
| `--report-dir=path` | dirname(report) / корень сьюта | Каталог для скриншотов, видео, Allure-результатов |
| `--screenshot=strategy` | on-failure | `on-failure` / `every-step` / `off` |
| `--record` | false | Записывать видео для каждого теста (mp4 в `--report-dir`) |
| `-- <hookArgs…>` | — | Всё после `--` пробрасывается в `_hooks.mjs` как `hookArgs` (см. §6.1) |
URL не передаётся позиционно — он берётся из `webtest.config.mjs` в каталоге тестов, а флаг `--url=` переопределяет URL дефолтного контекста. `webtest.config.mjs` и `_hooks.mjs` резолвятся от каталога **первого** пути, поэтому перечисляемые файлы должны лежать в одной папке сьюта.
URL не передаётся позиционно — он берётся из `webtest.config.mjs`, а флаг `--url=` переопределяет URL дефолтного контекста.
### Резолв корня сьюта
`webtest.config.mjs` и `_hooks.mjs` резолвятся не от переданного пути, а от **корня сьюта**: от каталога пути движок поднимается вверх до первого каталога, где лежит `webtest.config.mjs` или `_hooks.mjs`. Именно поэтому запуск подкаталога (`test tests/myapp/sales/`) и отдельного файла работает без `--url=`. Подъём ограничен каталогом с `.git` или `.v8-project.json` (сам каталог проверяется), а если их нет — текущим рабочим каталогом; выше поиск не идёт. Не нашли маркер — корнем считается переданный каталог (тогда хуков нет, и движок пишет об этом предупреждение в stderr).
Маркером служат **оба** файла, а не только конфиг: конфиг необязателен (§7), и сьют, у которого есть только `_hooks.mjs`, иначе молча остался бы без подготовки стенда.
Если переданные пути принадлежат разным сьютам (корни не совпали) — прогон не стартует: конфиг и хуки были бы взяты от первого пути, то есть чужие. Запускайте сьюты отдельно.
Найденный корень печатается в шапке прогона, рядом — переданные пути, если они от него отличаются.
### Валидация CLI
@@ -200,7 +210,7 @@ export default async function({ clerk, manager, step }) {
ctx.testInfo = {
name, // 'Навигация по разделам' (с подставленными params)
file, // '01-navigation.test.mjs' (basename)
filePath, // '01-navigation.test.mjs' (relative к testDir, разделитель '/')
filePath, // '01-navigation.test.mjs' (relative к корню сьюта, разделитель '/')
tags, // ['nav', 'smoke']
timeout, // 60000 (ms)
attempt, // 1..maxAttempts (1-based)
@@ -296,11 +306,13 @@ await assert.throws(asyncFn, msg?) // ожидает исключение
```js
assert.formHasField(state, fieldName, msg?)
// проверяет наличие state.fields[fieldName]; в сообщении об ошибке
// перечисляются доступные поля для быстрой диагностики
// проверяет, что в массиве state.fields есть поле с таким name;
// в сообщении об ошибке перечисляются доступные поля для быстрой диагностики
assert.formTitle(state, expected, msg?)
// проверяет, что state.title содержит expected
// проверяет, что state.title СОДЕРЖИТ expected (подстрока, не строгое равенство).
// state.title — заголовок активной формы: сначала из шапки формы, при её отсутствии —
// из панели открытых окон; null, если недоступны оба (тогда ассерт падает с этим фактом)
assert.tableHasRow(table, predicate, msg?)
// predicate: объект (частичное совпадение по ===) или функция row => bool
@@ -320,7 +332,7 @@ assert.noErrors(state, msg?)
## 6. Хуки
Все хуки определяются в `_hooks.mjs` в корне каталога тестов.
Все хуки определяются в `_hooks.mjs` в корне сьюта (§1 «Резолв корня сьюта»).
### Три уровня
@@ -451,7 +463,7 @@ node run.mjs test tests/myapp/ --bail -- --rebuild-stand --reload-data
## 7. Файл конфигурации
`webtest.config.mjs` в корне каталога тестов. Необязателен — если отсутствует, URL должен быть передан через CLI.
`webtest.config.mjs` в корне сьюта (§1 «Резолв корня сьюта»). Необязателен — если отсутствует, URL должен быть передан через CLI.
```js
export default {
@@ -599,7 +611,7 @@ await step('Менеджер утверждает', async () => {
await step('Кладовщик проверяет статус', async () => {
// страница кладовщика ТА ЖЕ — форма открыта, навигация не нужна
const state = await clerk.getFormState();
assert.equal(state.fields['Статус']?.value, 'Утверждён');
assert.equal(state.fields.find(f => f.name === 'Статус')?.value, 'Утверждён');
});
```
@@ -782,7 +794,7 @@ await step('Кладовщик проверяет статус', async () => {
Движок всегда заполняет следующие метки (`labels`):
- **`tag`** — по одному на каждый элемент `mod.tags[]`. Готовая фильтрация в Allure-отчёте без дополнительной разметки.
- **`suite`** — `dirname(t.filePath)`. Тесты в корне `testDir` идут под `'root'`, тесты в подкаталоге `sales/` — под `'sales'`. Это даёт левую группировку отчёта без ручной разметки.
- **`suite`** — `dirname(t.filePath)`. Тесты в корне сьюта идут под `'root'`, тесты в подкаталоге `sales/` — под `'sales'`. Это даёт левую группировку отчёта без ручной разметки.
- **`severity`** — резолв в порядке приоритета:
1. `export const severity = 'critical'` в самом тесте, **если значение валидное** (одно из `blocker | critical | normal | minor | trivial`). Если экспорт задан, но значение невалидное — пункт пропускается и идём в (3); резолв через теги (пункт 2) при этом **не выполняется** (хотел бы автор иначе — он бы не объявлял `severity`).
2. Иначе **максимальный ранг** среди тегов теста (стандартные имена `blocker | critical | normal | minor | trivial` напрямую, либо через `config.severity`-маппинг).
@@ -792,9 +804,9 @@ await step('Кладовщик проверяет статус', async () => {
Пример: `tags: ['smoke', 'recording']` + `severity: { critical: ['smoke'], minor: ['recording'] }` → severity = `critical` (5 > 2).
#### Доп. файлы Allure через `<testDir>/_allure/`
#### Доп. файлы Allure через `<корень сьюта>/_allure/`
Движок ищет каталог `_allure/` рядом с тестами и копирует все его файлы в `reportDir` перед генерацией отчёта. Конвенция для статичной настройки Allure, для которой нет места внутри JSON-файла теста:
Движок ищет каталог `_allure/` в корне сьюта и копирует все его файлы в `reportDir` перед генерацией отчёта. Конвенция для статичной настройки Allure, для которой нет места внутри JSON-файла теста:
| Файл | Назначение |
|------|-----------|
@@ -908,7 +920,7 @@ export const params = [
export default async function({ fillFields, getFormState, assert }, { type, field, value }) {
await fillFields({ [field]: value });
const state = await getFormState();
assert.equal(state.fields[field]?.value, String(value));
assert.equal(state.fields.find(f => f.name === field)?.value, String(value));
}
```
@@ -922,7 +934,7 @@ export default async function({ fillFields, getFormState, assert }, { type, fiel
## 14. Обнаружение тестов
`testDir` (первый позиционный аргумент после URL) — каталог, в котором живут тесты. Сборщик рекурсивно обходит дерево и собирает файлы по правилам ниже.
Позиционные аргументы — пути к тестам; каталог, от которого считаются относительные пути в отчёте, — корень сьюта (§1 «Резолв корня сьюта»). Сборщик рекурсивно обходит дерево и собирает файлы по правилам ниже.
```
tests/myapp/
@@ -944,14 +956,13 @@ tests/myapp/
| Шаблон имени | Только `*.test.mjs` |
| Несколько путей | `node run.mjs test a.test.mjs b.test.mjs dir/` — наборы объединяются, дублируются и сортируются |
| Порядок | Сортировка по полному относительному пути (`sales/01` идёт до `warehouse/01`) |
| `file` в отчёте | `relative(testDir, file)` с разделителем `/`, например `sales/01-order-create.test.mjs` |
| `file` в отчёте | `relative(<корень сьюта>, file)` с разделителем `/`, например `sales/01-order-create.test.mjs` |
| Фильтр по пути с CLI | `node run.mjs test tests/myapp/sales/` запустит только подкаталог |
| Конкретный файл | `node run.mjs test tests/myapp/sales/01-order-create.test.mjs` |
### Чего НЕТ (сознательное упрощение)
- **`_hooks.mjs` на уровне подкаталога.** Движок ищет `_hooks.mjs` только в корне `testDir`. Подкаталоги свои хуки не получают.
- **`webtest.config.mjs` на уровне подкаталога.** Тоже только в корне.
- **`_hooks.mjs` / `webtest.config.mjs` на уровне подкаталога.** Оба берутся только из корня сьюта — того каталога, где лежит ближайший из них (§1 «Резолв корня сьюта»). Подкаталоги своих копий не получают; вложенный каталог со своим `webtest.config.mjs` — это уже отдельный сьют.
- **Многоуровневой Suite-разметки из дерева каталогов.** Allure-метка `suite` строится только по первому уровню (`dirname(filePath)`); более глубокую группировку делайте через `tags`.
- **Контекста по умолчанию на уровне подкаталога.** Каждый тест объявляет `context` / `contexts` сам; от пути контексты не наследуются.
@@ -1117,7 +1128,8 @@ JSON-отчёт (`tests[]`, полная структура — §9) для ка
| Термин | Определение |
|--------|-------------|
| **testDir** | Каталог тестов, переданный позиционным аргументом движку. Корень для discovery, `_hooks.mjs`, `webtest.config.mjs`, `_allure/`. |
| **Test path** | Путь к тесту или каталогу тестов, переданный позиционным аргументом. Корень только для discovery — что именно запускать. |
| **Suite root (корень сьюта)** | Каталог, найденный подъёмом от test path до первого `webtest.config.mjs` / `_hooks.mjs` (§1). От него берутся конфиг, хуки, `_allure/`, каталог отчёта по умолчанию и относительные пути `file` в отчёте. Не зависит от того, запустили сьют целиком или один его подкаталог, — поэтому ID теста в отчёте стабилен. |
| **Context (BrowserContext)** | Изолированная сессия Playwright. Куки/состояние/страница независимы. В рамках одного теста используется один или несколько контекстов. |
| **Active context** | Контекст, на котором сейчас оперируют функции browser-API. Переключается `setActiveContext`. |
| **Primary context** | Контекст, активный на входе в тест. Декларация (`mod.context` или `mod.contexts[0]`). Зафиксирован в `testInfo.primaryContext`. |
+197
View File
@@ -0,0 +1,197 @@
# Спецификация 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
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tns="urn:1C.ru:ObmenSBankom"
targetNamespace="urn:1C.ru:ObmenSBankom"
elementFormDefault="qualified">
<xs:simpleType name="СуммаТип">
<xs:restriction base="xs:decimal">
<xs:totalDigits value="18"/>
<xs:fractionDigits value="2"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="Платёж">
<xs:sequence>
<xs:element name="Дата" type="xs:date"/>
<xs:element name="Сумма" type="tns:СуммаТип"/>
<xs:element name="Назначение" type="xs:string" minOccurs="0"/>
</xs:sequence>
<xs:attribute name="Номер" type="xs:string"/>
</xs:complexType>
</xs:schema>
```
---
## 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="V"` | `@fixed="true"` + `@default="V"` — в модели признак и значение разнесены |
| `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` | `<pattern>`, `<enumeration>` — значение в тексте узла |
| `xs:list/@itemType` | `variety="List"` + `@itemType` |
| `xs:union/@memberTypes` | `variety="Union"` + `@memberTypes` |
Ловушки, на которых модель ошибается чаще всего:
- **Кратность инвертирована по смыслу**: `lowerBound="0"` = необязательный, `upperBound="-1"` = неограниченный.
- **Фасеты в XDTO — атрибуты**, а не дочерние элементы: `maxLength="30"`, не `<xs:maxLength value="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
<xs:attribute name="Представление" type="xs:string"
xmlns:xdto="http://v8.1c.ru/8.1/xdto" xdto:nillable="true"/>
```
Такая схема остаётся валидной: 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:fixed` | объявление | признак фиксированного значения отдельно от самого значения: в XSD `fixed="V"` совмещает их, в модели это `fixed="true"` + `default="V"`. Нужен только для `fixed="false"` при заданном `default` |
| `xdto:type` | `xs:enumeration` | `xsi:type` литерала перечисления |
| `xdto:prefix` | объявление | осмысленный префикс пространства имён вместо генерируемого `dNpM` (например `dcsset`) |
| `xdto:declareNs` | `xs:union` | объявить префикс пространства имён на узле (при нотации Кларка платформа иногда его пишет, иногда нет) |
### Что выводится само
Аннотация нужна только там, где вывод невозможен:
| Свойство 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
<xs:annotation>
<xs:appinfo>
<xdto:package xmlns:xdto="http://v8.1c.ru/8.1/xdto">
<xdto:name>ОбменСБанком</xdto:name>
<xdto:synonym lang="ru">Обмен с банком</xdto:synonym>
<xdto:comment>Формат 1С:Предприятие — Клиент банка</xdto:comment>
</xdto:package>
</xs:appinfo>
</xs:annotation>
```
Блок опционален. Параметры `-Name`, `-Synonym`, `-Comment` навыка `/xdto-compile`
имеют приоритет над ним. Без того и другого имя берётся из имени файла XSD.
Благодаря этому блоку пара `/xdto-decompile``/xdto-compile` замыкается без потерь,
включая свойства объекта метаданных.
---
## 4. Прощающий ввод
Компилятор принимает и не вполне канонические схемы:
- **Имя типа без префикса** (`type="Платёж"`) трактуется как тип целевого пространства имён.
- **`form="qualified"` на глобальном объявлении** — так экспортирует XML-схему сам
Конфигуратор, хотя спецификация XSD допускает `form` только у локальных объявлений.
Такие файлы читаются; сам навык пишет корректно.
- **Порядок объявлений верхнего уровня произвольный** — компилятор расставляет их
в требуемом моделью порядке `import → property → valueType → objectType`.
- **`xs:group` и `xs:attributeGroup`** раскрываются по ссылке: содержимое группы
подставляется в тип.
## 5. Конструкции без точного соответствия
XML Schema выразительнее модели XDTO. Перечисленное ниже переносится приближённо,
и компилятор об этом **предупреждает** — молча терять свойства нельзя.
| Конструкция | Что происходит |
|---|---|
| вложенная `xs:sequence` | уплощается в плоский список свойств |
| вложенная `xs:choice` | уплощается, и **ветки становятся необязательными**: иначе «одно из двух» превратилось бы в «оба обязательны» и тип нельзя было бы заполнить. Запрет «ровно один из» не сохраняется |
| `xs:all` | трактуется как последовательность |
| `minOccurs`/`maxOccurs` на самой частице | не выражается, отбрасывается |
| `substitutionGroup` | объявление сохраняется как обычное |
| `xs:key`, `xs:keyref`, `xs:unique` | отбрасываются |
| `xs:redefine` | игнорируется |
| `xs:include` | игнорируется: зависимости разрешаются только по namespace — включаемую схему нужно собрать отдельным пакетом и заменить на `xs:import` |
`ordered="false"` (выбор одного из вариантов) выводится только из **корневой** `xs:choice`
типа: модель хранит признак на типе целиком, а не на вложенной частице.
+185
View File
@@ -0,0 +1,185 @@
# Работа с пакетами XDTO
Пакет XDTO описывает XML-формат: какие есть типы, из каких свойств состоят, что
обязательно. По нему платформа умеет читать и писать XML — через `ФабрикаXDTO`.
Встречается везде, где 1С обменивается данными наружу: ЭДО, ЕГАИС, ВЕТИС, ФСС,
клиент-банк, веб-сервисы, `EnterpriseData`.
Навыки вызываются агентом сами — задачу можно ставить обычными словами. Ниже
примеры формулировок и того, что за ними происходит.
**Что стоит назвать в задаче.** Формулировка может быть сколь угодно общей, но
объект работы лучше назвать: каталог исходников конфигурации, имя пакета (а для
точечной правки — и типа), путь к файлу схемы. Чего не назвали — про то агент
спросит, прежде чем что-то делать: «добавь схему от контрагента» без пути к файлу
он не угадает, а искать по всему диску не станет.
## Навыки
| Навык | Задача |
|---|---|
| [`/xdto-info`](../.claude/skills/xdto-info/SKILL.md) | Что в пакете и как заполнять тип — в терминах 1С |
| [`/xdto-compile`](../.claude/skills/xdto-compile/SKILL.md) | Собрать пакет по XML-схеме |
| [`/xdto-decompile`](../.claude/skills/xdto-decompile/SKILL.md) | Выгрузить пакет в XML-схему |
| [`/xdto-edit`](../.claude/skills/xdto-edit/SKILL.md) | Точечно поправить существующий пакет |
| [`/xdto-validate`](../.claude/skills/xdto-validate/SKILL.md) | Проверить перед загрузкой в базу |
Формат описания — обычная XML-схема, своего DSL нет. Схема в реальных задачах
обычно уже есть: её присылает контрагент или публикует регулятор.
## Сценарии
### Разобраться, что есть в незнакомой конфигурации
> «Какие пакеты XDTO есть в конфигурации `src`
>
> «Что внутри пакета `ОбменСБанком` — какие типы и с чего начинать чтение?»
Обычный первый шаг, когда формат чужой или давно не открывался. Список пакетов
показывает имя, пространство имён и число типов; обзор пакета — типы и точки входа.
### Написать код, который заполняет объект XDTO
> «Сформируй платёжное поручение по пакету `ОбменСБанком` и запиши в `out.xml`»
>
> «Напиши обработку выгрузки заказов по пакету `ОбменМаркетплейс`, начни с типа `Заказ`»
Самая частая задача, и обычно она часть большей. Прежде чем писать код, агент
смотрит структуру типа: какой тип значения присваивать каждому свойству, что
обязательно, где нужно создать вложенный объект, какие значения допустимы.
Выводится это уже в терминах 1С — `Строка(6)`, `Число(18,2)`, `[обязательный]`, —
поэтому переводить `xs:decimal` и `lowerBound="0"` в голове не приходится.
Если тип большой, помогает срез только обязательных свойств: получается готовый
скелет заполнения.
### Разобрать входящий XML
> «Разбери `in/egais-ttn.xml` по пакету `ЕГАИС3` — с какого типа начинать?»
>
> «Какие точки входа у пакета `ЕГАИС3`
Чтобы прочитать документ, надо знать, с какого типа начинать. Это **точки входа**
пакета — его глобальные объявления; агент покажет их вместе со списком типов.
### Добавить пакет по схеме контрагента
> «Контрагент прислал `schemas/orders.xsd` — добавь пакет в конфигурацию, исходники в `src`»
>
> «Собери пакет по `schemas/fss-person.xsd`, имя `FSS_Person_01`»
Пакет собирается по схеме и сразу регистрируется в конфигурации.
**Обрати внимание на предупреждения.** XML Schema выразительнее модели XDTO, и часть
конструкций переносится приближённо: вложенный `xs:choice` уплощается (ветки при этом
становятся необязательными), `xs:all` превращается в последовательность, кратность
на частице отбрасывается. Агент об этом сообщит — если упрощение недопустимо,
схему надо менять, а не игнорировать сообщение.
Если схема ссылается на чужое пространство имён через `<xs:import>`, сначала нужен
пакет-зависимость: платформа отвергнет конфигурацию, где импортируемого пакета нет.
Об этом скажут ещё на сборке — чинить дешевле там, чем на `/db-update`. Исключение —
пространства имён самой платформы (`http://www.w3.org/2001/XMLSchema`,
`http://v8.1c.ru/8.1/data/core` и подобные): их пакетами объявлять не нужно.
### Поправить существующий пакет
> «Добавь в платёжное поручение необязательный комментарий, не длиннее 200 символов»
>
> «Убери свойство `СтарыйКод` у типа `Документ` в пакете `Обмен`»
>
> «Добавь в перечисление видов документов значение Инкассо»
Точечная правка не требует читать схему целиком — для больших пакетов вроде
`EnterpriseData` это единственный практичный путь. После правки автоматически
запускается проверка.
Перед изменением существующего типа полезно узнать, кого оно затронет:
> «Кто ссылается на тип `Адрес` из пакета `КонтактнаяИнформация`
Ответ — список типов и пакетов, включая те, что ссылаются через границу пакета.
Если переработка широкая — «перепиши обмен под новую версию формата» — схема
выгружается целиком, правится и собирается обратно. Пара выгрузка-сборка
замыкается без потерь, включая имя, синоним и комментарий объекта метаданных:
проверено на 760 пакетах типовых конфигураций — сборка даёт исходный файл модели
побайтово.
### Новая версия пакета
> «Сделай из пакета `Обмен` версию 2: новый пакет `ОбменV2` с namespace `urn:…:v2`,
> старый не трогай»
Типовой приём: рядом со старым пакетом появляется новый с другим пространством
имён (`EnterpriseData_1_19``_1_20`), а старые потребители продолжают смотреть
на прежний namespace.
Отдельной команды для этого нет: агент выгружает схему исходного пакета, меняет в ней
пространство имён и собирает под новым именем. Дополнительных действий это не требует —
достаточно назвать в задаче исходный пакет, новое имя и новый namespace. Внутренние
ссылки переводятся на него целиком, импорты чужих пакетов сохраняются, исходный пакет
остаётся как был.
Если же надо сменить namespace **у существующего** пакета, а не сделать копию, агент
перепишет все внутренние ссылки и перечислит пакеты, которые импортируют старый — но
менять их не станет, потому что при версионировании это было бы ошибкой.
### Отдать схему контрагенту
> «Выгрузи схему пакета `Обмен` в `schemas/exchange.xsd`, отправлю партнёру»
Получается валидная XSD, ничего не теряющая. Штатный «Экспорт XML-схемы»
в Конфигураторе для этого хуже: он теряет признак `nillable` у свойств-атрибутов,
а для пакетов с неквалифицированной формой элементов выдаёт XSD, которую строгий
валидатор не принимает.
### Проверить перед загрузкой в базу
> «Проверь пакет `Обмен` перед загрузкой в базу»
>
> «Я правил `Package.bin` руками — проверь, что платформа его примет»
Проверка ловит то, на чём `/db-update` отказывается принимать конфигурацию:
импорт пакета, которого в конфигурации нет; ссылку на несуществующий тип или
необъявленный префикс; нарушенный порядок элементов верхнего уровня; расхождение
объекта метаданных с моделью; отсутствие пакета в `Configuration.xml`. Дешевле
поймать здесь, чем в отказе загрузки. После правки через `/xdto-edit` проверка
запускается сама.
### Разобраться, почему обмен ведёт себя странно
> «Пакет `ФСС` загрузился, но обращение к `Смена.Сотрудник` возвращает
> что-то бесструктурное — разберись»
Такой симптом почти всегда означает, что тип не разрешился и стал «произвольным».
Платформа об этом молчит: при импорте XML-схемы неразрешённый чужой тип заменяется
без единой ошибки, пакет выглядит загруженным. Проверка называет и симптом,
и причину — объявленный, но неиспользуемый импорт.
## Структура файлов
```
XDTOPackages/
├── ОбменСБанком.xml объект метаданных: Name, Synonym, Comment, Namespace
└── ОбменСБанком/
└── Ext/
└── Package.bin модель пакета (текстовый XML, несмотря на расширение)
```
Плюс регистрация в корневом `Configuration.xml` — без неё платформа пакет не увидит.
## Рабочий цикл
1. Посмотреть, что есть — `/xdto-info`
2. Изменить — `/xdto-compile` или `/xdto-edit`
3. Проверить до загрузки — `/xdto-validate`
4. Применить — `/db-load-xml` + `/db-update`
Точный синтаксис параметров каждого навыка — в его `SKILL.md` по ссылкам выше.
## Спецификации
- Формат исходников — [1c-xdto-spec.md](1c-xdto-spec.md)
- XML Schema как формат описания, таблица соответствий и аннотации `xdto:`
[xdto-dsl-spec.md](xdto-dsl-spec.md)
+85 -5
View File
@@ -129,7 +129,11 @@ ibcmd-проход автоматически `○ skipped`, если рядом
}
```
Раннер проверит: exitCode=0 + выход совпадает со snapshot (если есть).
Раннер проверит: exitCode=0 + выход совпадает с эталоном.
Эталон **обязателен**: если его нет и кейс не объявил `noSnapshot`, тест падает. Иначе потерянный
(или не созданный при добавлении кейса) эталон неотличим от намеренного отсутствия — тест зелёный,
хотя выход не проверяется.
### С параметрами навыка
@@ -223,22 +227,98 @@ ibcmd-проход автоматически `○ skipped`, если рядом
| `setup` | нет | Переопределение setup из `_skill.json` |
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
| `args_extra` | нет | Массив дополнительных CLI-аргументов |
| `preRun` | нет | Массив шагов подготовки (создание объектов и т.п.) |
| `expect` | нет | Дополнительные проверки: `files`, `stdoutContains` (строка/массив), `stdoutNotContains` (строка/массив) |
| `preRun` | нет | Массив шагов подготовки (см. ниже) |
| `expect` | нет | Дополнительные проверки (см. ниже) |
| `expectError` | нет | `true` или строка — ожидается ошибка |
| `noSnapshot` | нет | Непустая строка с причиной — кейс объявляет, что эталон не нужен (см. «Эталоны») |
| `idempotent` | нет | `true` — повторный прогон с теми же аргументами должен дать байт-в-байт тот же `workDir` |
| `runtimeOnly` | нет | `"powershell"` / `"python"` — кейс имеет смысл только на одном порте, на другом `○ skipped` |
| `skipValidation` | нет | `true` — не запускать `postValidate` из `_skill.json` (только при `--with-validation`) |
### Ключи `expect`
| Ключ | Описание |
|---|---|
| `files` | Массив путей относительно `workDir` — каждый должен существовать после прогона |
| `stdoutContains` | Строка или массив строк — все должны присутствовать в stdout |
| `stdoutNotContains` | Строка или массив строк — ни одной не должно быть в stdout |
| `preserves` | Объект (или массив объектов) — байтовые свойства файла, которые навык обязан сохранить |
`preserves` проверяет то, что снэпшот-сравнение нормализует и потому увидеть не может:
| Ключ | Описание |
|---|---|
| `file` | Путь к файлу относительно `workDir` (обязателен) |
| `bom` | `true`/`false` — наличие UTF-8 BOM |
| `eol` | `"crlf"` / `"lf"` |
| `encoding` | Ожидаемое значение в XML-декларации, напр. `"UTF-8"` |
| `finalNewline` | `true`/`false` — перевод строки в конце файла |
| `noCR13` | `true` — в выходе не должно быть литерала `&#13;` |
`preserves` и эталон **дополняют** друг друга: первый следит за байтовым стилем файла, второй — за
структурой содержимого. Наличие одного не отменяет необходимости другого.
### Шаги `preRun`
Массив шагов, выполняемых до запуска проверяемого навыка:
| Форма шага | Описание |
|---|---|
| `{ "script": "<навык>/scripts/<файл>", "input": {...}, "args": { "-Flag": "{inputFile}" } }` | Прогон другого навыка для подготовки фикстуры. Плейсхолдеры: `{inputFile}`, `{workDir}` |
| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON) |
## Эталоны (snapshots)
Эталон — директория `snapshots/<имя-кейса>/` внутри папки навыка. Содержит ожидаемый выход навыка после нормализации.
### Когда эталон обязателен
Всегда, кроме трёх случаев:
- `expectError` — проверяется факт ошибки, выхода нет;
- `setup: "external:<path>"` — рабочая директория read-only, эталон физически не создать;
- кейс объявил `noSnapshot` (см. ниже).
Во всех остальных случаях отсутствующий (или пустой) эталон — **падение** с подсказкой, что делать.
### `noSnapshot` — когда эталон не нужен
```json
{
"name": "Валидатор: ссылочный тип разрешается",
"noSnapshot": "meta-validate только читает и печатает — эталон зафиксировал бы выход preRun, а не проверяемого навыка; проверяется stdout",
"expect": { "stdoutContains": "16. Reference types:" }
}
```
Типичный случай — навык ничего не пишет в рабочую директорию (info/validate): эталон зафиксировал бы
выход `preRun`, а не проверяемого навыка, и дублировал бы эталоны того навыка.
**Причина обязательна** — непустая строка; `true` не принимается и валит кейс. Смысл в том, что
отключение сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е: проверить
осмысленность причины рантайм не может.
Если у кейса стоит `noSnapshot`, но каталог эталона существует — тоже падение: такой эталон
не сверяется и создаёт ложное впечатление покрытия. Удалите каталог либо снимите `noSnapshot`.
### Создание / обновление эталонов
```bash
node tests/skills/runner.mjs --update-snapshots # все кейсы
node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # один кейс — предпочтительно
node tests/skills/runner.mjs cases/meta-compile --update-snapshots # один навык
node tests/skills/runner.mjs cases/meta-compile/enum --update-snapshots # один кейс
node tests/skills/runner.mjs --update-snapshots # все кейсы
```
> Прогон по навыку/сюите **перезаписывает эталоны всех** кейсов сразу: если побочно поехал вывод
> соседнего кейса, его эталон обновится вместе с целевым и непреднамеренная регрессия замаскируется.
> Поэтому по умолчанию — точечно по кейсу, а после массового пересъёма обязательно проверяйте
> `git diff` по `snapshots/`: каждая ± строка должна быть ожидаемой.
>
> Массовый пересъём легитимен, когда изменение вывода и правда затрагивает многих — например, правка
> `meta-compile` меняет фикстуры ~20 навыков, чьи кейсы строятся его `preRun`-прогоном.
>
> Кейсы с `noSnapshot` пропускаются — эталон им не создаётся.
### Когда обновлять
- После **намеренного** изменения логики навыка (новый выход — новый эталон)
@@ -0,0 +1,251 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>КрлфКонф</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>КрлфКонф</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version>1.0.0.2</Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_27</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,21 @@
{
"name": "form-compile затем form-add не дублирует регистрацию формы в ChildObjects",
"setup": "none",
"preRun": [
{
"script": "epf-init/scripts/init",
"args": { "-Name": "КонсольКода", "-SrcDir": "{workDir}" }
},
{
"script": "form-compile/scripts/form-compile",
"input": {
"title": "Форма",
"attributes": [{ "name": "Объект", "type": "ExternalDataProcessorObject.КонсольКода", "main": true }]
},
"args": { "-JsonPath": "{inputFile}", "-OutputPath": "{workDir}/КонсольКода/Forms/Форма/Ext/Form.xml" }
}
],
"params": { "objectPath": "КонсольКода.xml", "formName": "Форма" },
"expect": { "stdoutContains": "Already registered" },
"validatePath": "КонсольКода/Forms/Форма"
}
@@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<ExternalDataProcessor uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:GeneratedType name="ExternalDataProcessorObject.КонсольКода" category="Object">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>КонсольКода</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>КонсольКода</v8:content>
</v8:item>
</Synonym>
<Comment />
<DefaultForm>ExternalDataProcessor.КонсольКода.Form.Форма</DefaultForm>
<AuxiliaryForm />
</Properties>
<ChildObjects>
<Form>Форма</Form>
</ChildObjects>
</ExternalDataProcessor>
</MetaDataObject>
@@ -0,0 +1,11 @@
#Область ОписаниеПеременных
#КонецОбласти
#Область ПрограммныйИнтерфейс
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
#КонецОбласти
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Form uuid="UUID-001">
<Properties>
<Name>Форма</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Форма</v8:content>
</v8:item>
</Synonym>
<Comment/>
<FormType>Managed</FormType>
<IncludeHelpInContents>false</IncludeHelpInContents>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
</UsePurposes>
<ExtendedPresentation/>
</Properties>
</Form>
</MetaDataObject>
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcssch="http://v8.1c.ru/8.1/data-composition-system/schema" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Title>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Форма</v8:content>
</v8:item>
</Title>
<AutoTitle>false</AutoTitle>
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"/>
<Attributes>
<Attribute name="Объект" id="1">
<Type>
<v8:Type>cfg:ExternalDataProcessorObject.КонсольКода</v8:Type>
</Type>
<MainAttribute>true</MainAttribute>
</Attribute>
</Attributes>
</Form>
@@ -0,0 +1,19 @@
#Область ОбработчикиСобытийФормы
#КонецОбласти
#Область ОбработчикиСобытийЭлементовФормы
#КонецОбласти
#Область ОбработчикиКомандФормы
#КонецОбласти
#Область ОбработчикиОповещений
#КонецОбласти
#Область СлужебныеПроцедурыИФункции
#КонецОбласти
@@ -1,14 +1,14 @@
{
"name": "InputByString / DataLockFields / BasedOn каталога",
"name": "InputByString / DataLockFields / BasedOn каталога (+нормализация MDObjectRef: CatalogRef./рус. → Catalog.)",
"input": {
"type": "Catalog",
"name": "ДоговорыКонтрагентов",
"owners": ["Catalog.Контрагенты"],
"owners": ["CatalogRef.Контрагенты"],
"codeLength": 11,
"descriptionLength": 100,
"inputByString": ["Код", "Наименование", "НомерДоговора"],
"dataLockFields": ["Организация", "Контрагент", "Владелец"],
"basedOn": ["Catalog.Контрагенты", "Document.ЗаказПоставщику"],
"basedOn": ["Справочник.Контрагенты", "DocumentRef.ЗаказПоставщику"],
"attributes": [
"Организация: CatalogRef.Организации",
"Контрагент: CatalogRef.Контрагенты",
@@ -0,0 +1,16 @@
{
"name": "Формат 2.20 при режиме совместимости 8_3_24 → LineNumberLength 5 (дефолт из режима, не из формата)",
"setup": "empty-config-220-compat24",
"input": {
"type": "Catalog",
"name": "СоСтроками",
"tabularSections": {
"Строки": ["Значение: String(50)"],
"СтрокиДлинные": { "attributes": ["Значение: String(50)"], "lineNumberLength": 9 }
}
},
"validatePath": "Catalogs/СоСтроками",
"expect": {
"files": ["Catalogs/СоСтроками.xml"]
}
}
@@ -0,0 +1,31 @@
{
"name": "Формат 2.20 (8.3.27): TypeReductionMode + LineNumberLength (режим 8_3_27 → 9)",
"setup": "empty-config-220",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": { "type": "Catalog", "name": "Валюты" },
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"input": [
{
"type": "Catalog",
"name": "Договоры",
"owners": ["Catalog.Валюты"],
"standardAttributes": { "Description": { "fillChecking": "ShowError" } },
"tabularSections": { "Условия": ["Условие: String(100)"] }
},
{
"type": "InformationRegister",
"name": "КурсыВалют",
"periodicity": "Day",
"dimensions": ["Валюта: CatalogRef.Валюты | master, mainFilter"],
"resources": ["Курс: Number(15,4)"]
}
],
"validatePath": "Catalogs/Договоры",
"expect": {
"files": ["Catalogs/Договоры.xml", "InformationRegisters/КурсыВалют.xml"]
}
}
@@ -0,0 +1,288 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.СоСтроками" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.СоСтроками" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.СоСтроками" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.СоСтроками" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.СоСтроками" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>СоСтроками</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Со строками</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners/>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.СоСтроками.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.СоСтроками.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects>
<TabularSection uuid="UUID-012">
<InternalInfo>
<xr:GeneratedType name="CatalogTabularSection.СоСтроками.Строки" category="TabularSection">
<xr:TypeId>UUID-013</xr:TypeId>
<xr:ValueId>UUID-014</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogTabularSectionRow.СоСтроками.Строки" category="TabularSectionRow">
<xr:TypeId>UUID-015</xr:TypeId>
<xr:ValueId>UUID-016</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Строки</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Строки</v8:content>
</v8:item>
</Synonym>
<Comment/>
<ToolTip/>
<FillChecking>DontCheck</FillChecking>
<StandardAttributes>
<xr:StandardAttribute name="LineNumber">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<Use>ForItem</Use>
<LineNumberLength>5</LineNumberLength>
</Properties>
<ChildObjects>
<Attribute uuid="UUID-017">
<Properties>
<Name>Значение</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Значение</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>50</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Attribute>
</ChildObjects>
</TabularSection>
<TabularSection uuid="UUID-018">
<InternalInfo>
<xr:GeneratedType name="CatalogTabularSection.СоСтроками.СтрокиДлинные" category="TabularSection">
<xr:TypeId>UUID-019</xr:TypeId>
<xr:ValueId>UUID-020</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogTabularSectionRow.СоСтроками.СтрокиДлинные" category="TabularSectionRow">
<xr:TypeId>UUID-021</xr:TypeId>
<xr:ValueId>UUID-022</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>СтрокиДлинные</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Строки длинные</v8:content>
</v8:item>
</Synonym>
<Comment/>
<ToolTip/>
<FillChecking>DontCheck</FillChecking>
<StandardAttributes>
<xr:StandardAttribute name="LineNumber">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<Use>ForItem</Use>
<LineNumberLength>9</LineNumberLength>
</Properties>
<ChildObjects>
<Attribute uuid="UUID-023">
<Properties>
<Name>Значение</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Значение</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>50</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Attribute>
</ChildObjects>
</TabularSection>
</ChildObjects>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,252 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Catalog>СоСтроками</Catalog>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Валюты" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Валюты" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Валюты" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Валюты" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Валюты" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Валюты</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Валюты</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners/>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Валюты.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Валюты.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects/>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,437 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Договоры" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Договоры" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Договоры" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Договоры" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Договоры" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Договоры</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Договоры</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Валюты</xr:Item>
</Owners>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<StandardAttributes>
<xr:StandardAttribute name="PredefinedDataName">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Predefined">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Ref">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="DeletionMark">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="IsFolder">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Owner">
<xr:LinkByType/>
<xr:FillChecking>ShowError</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>true</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>Deny</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Parent">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>true</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Description">
<xr:LinkByType/>
<xr:FillChecking>ShowError</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Code">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Договоры.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Договоры.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects>
<TabularSection uuid="UUID-012">
<InternalInfo>
<xr:GeneratedType name="CatalogTabularSection.Договоры.Условия" category="TabularSection">
<xr:TypeId>UUID-013</xr:TypeId>
<xr:ValueId>UUID-014</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogTabularSectionRow.Договоры.Условия" category="TabularSectionRow">
<xr:TypeId>UUID-015</xr:TypeId>
<xr:ValueId>UUID-016</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Условия</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Условия</v8:content>
</v8:item>
</Synonym>
<Comment/>
<ToolTip/>
<FillChecking>DontCheck</FillChecking>
<StandardAttributes>
<xr:StandardAttribute name="LineNumber">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<Use>ForItem</Use>
<LineNumberLength>9</LineNumberLength>
</Properties>
<ChildObjects>
<Attribute uuid="UUID-017">
<Properties>
<Name>Условие</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Условие</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>100</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Attribute>
</ChildObjects>
</TabularSection>
</ChildObjects>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,254 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_27</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Catalog>Валюты</Catalog>
<Catalog>Договоры</Catalog>
<InformationRegister>КурсыВалют</InformationRegister>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,266 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<InformationRegister uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="InformationRegisterRecord.КурсыВалют" category="Record">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterManager.КурсыВалют" category="Manager">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterSelection.КурсыВалют" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterList.КурсыВалют" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterRecordSet.КурсыВалют" category="RecordSet">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterRecordKey.КурсыВалют" category="RecordKey">
<xr:TypeId>UUID-012</xr:TypeId>
<xr:ValueId>UUID-013</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="InformationRegisterRecordManager.КурсыВалют" category="RecordManager">
<xr:TypeId>UUID-014</xr:TypeId>
<xr:ValueId>UUID-015</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>КурсыВалют</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Курсы валют</v8:content>
</v8:item>
</Synonym>
<Comment/>
<UseStandardCommands>true</UseStandardCommands>
<EditType>InDialog</EditType>
<DefaultRecordForm/>
<DefaultListForm/>
<AuxiliaryRecordForm/>
<AuxiliaryListForm/>
<StandardAttributes>
<xr:StandardAttribute name="Active">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="LineNumber">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Recorder">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
<xr:StandardAttribute name="Period">
<xr:LinkByType/>
<xr:FillChecking>DontCheck</xr:FillChecking>
<xr:MultiLine>false</xr:MultiLine>
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
<xr:CreateOnInput>Auto</xr:CreateOnInput>
<xr:TypeReductionMode>TransformValues</xr:TypeReductionMode>
<xr:MaxValue xsi:nil="true"/>
<xr:ToolTip/>
<xr:ExtendedEdit>false</xr:ExtendedEdit>
<xr:Format/>
<xr:ChoiceForm/>
<xr:QuickChoice>Auto</xr:QuickChoice>
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
<xr:EditFormat/>
<xr:PasswordMode>false</xr:PasswordMode>
<xr:DataHistory>Use</xr:DataHistory>
<xr:MarkNegatives>false</xr:MarkNegatives>
<xr:MinValue xsi:nil="true"/>
<xr:Synonym/>
<xr:Comment/>
<xr:FullTextSearch>Use</xr:FullTextSearch>
<xr:ChoiceParameterLinks/>
<xr:FillValue xsi:nil="true"/>
<xr:Mask/>
<xr:ChoiceParameters/>
</xr:StandardAttribute>
</StandardAttributes>
<InformationRegisterPeriodicity>Day</InformationRegisterPeriodicity>
<WriteMode>Independent</WriteMode>
<MainFilterOnPeriod>false</MainFilterOnPeriod>
<IncludeHelpInContents>false</IncludeHelpInContents>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<EnableTotalsSliceFirst>false</EnableTotalsSliceFirst>
<EnableTotalsSliceLast>false</EnableTotalsSliceLast>
<RecordPresentation/>
<ExtendedRecordPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects>
<Resource uuid="UUID-016">
<Properties>
<Name>Курс</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Курс</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>4</v8:FractionDigits>
<v8:AllowedSign>Any</v8:AllowedSign>
</v8:NumberQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:decimal">0</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Resource>
<Dimension uuid="UUID-017">
<Properties>
<Name>Валюта</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Валюта</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:CatalogRef.Валюты</v8:Type>
</Type>
<PasswordMode>false</PasswordMode>
<Format/>
<EditFormat/>
<ToolTip/>
<MarkNegatives>false</MarkNegatives>
<Mask/>
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true"/>
<MaxValue xsi:nil="true"/>
<FillFromFillingValue>true</FillFromFillingValue>
<FillValue xsi:nil="true"/>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm/>
<LinkByType/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Master>true</Master>
<MainFilter>true</MainFilter>
<DenyIncompleteValues>false</DenyIncompleteValues>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
<TypeReductionMode>TransformValues</TypeReductionMode>
</Properties>
</Dimension>
</ChildObjects>
</InformationRegister>
</MetaDataObject>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,24 @@
{
"name": "Нормализация MDObjectRef в Owners/BasedOn (CatalogRef./рус. запись → Catalog.)",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": [
{ "type": "Catalog", "name": "Контрагенты" },
{ "type": "Catalog", "name": "Организации" },
{ "type": "Document", "name": "ЗаказПоставщику" },
{ "type": "Catalog", "name": "ДоговорыКонтрагентов", "owners": ["Catalog.Контрагенты"] }
],
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "Catalogs/ДоговорыКонтрагентов" },
"input": {
"modify": {
"properties": {
"Owners": ["CatalogRef.Контрагенты", "СправочникСсылка.Организации"],
"BasedOn": ["Справочник.Контрагенты", "DocumentRef.ЗаказПоставщику"]
}
}
}
}
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.ДоговорыКонтрагентов" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.ДоговорыКонтрагентов" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.ДоговорыКонтрагентов" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.ДоговорыКонтрагентов" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.ДоговорыКонтрагентов" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ДоговорыКонтрагентов</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Договоры контрагентов</v8:content>
</v8:item>
</Synonym>
<Comment />
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Контрагенты</xr:Item>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Организации</xr:Item>
</Owners>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics />
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.ДоговорыКонтрагентов.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.ДоговорыКонтрагентов.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm />
<DefaultFolderForm />
<DefaultListForm />
<DefaultChoiceForm />
<DefaultFolderChoiceForm />
<AuxiliaryObjectForm />
<AuxiliaryFolderForm />
<AuxiliaryListForm />
<AuxiliaryChoiceForm />
<AuxiliaryFolderChoiceForm />
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn>
<xr:Item xsi:type="xr:MDObjectRef">Catalog.Контрагенты</xr:Item>
<xr:Item xsi:type="xr:MDObjectRef">Document.ЗаказПоставщику</xr:Item>
</BasedOn>
<DataLockFields />
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation />
<ExtendedObjectPresentation />
<ListPresentation />
<ExtendedListPresentation />
<Explanation />
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects />
</Catalog>
</MetaDataObject>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Контрагенты" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Контрагенты" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Контрагенты" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Контрагенты" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Контрагенты" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Контрагенты</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Контрагенты</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners/>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Контрагенты.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Контрагенты.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects/>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.Организации" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.Организации" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.Организации" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.Организации" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.Организации" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>Организации</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Организации</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners/>
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.Организации.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.Организации.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultFolderForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<DefaultFolderChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryFolderForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects/>
</Catalog>
</MetaDataObject>
@@ -0,0 +1,255 @@
<?xml version="1.0" encoding="utf-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Configuration uuid="UUID-001">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>UUID-002</xr:ClassId>
<xr:ObjectId>UUID-003</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-004</xr:ClassId>
<xr:ObjectId>UUID-005</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-006</xr:ClassId>
<xr:ObjectId>UUID-007</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-008</xr:ClassId>
<xr:ObjectId>UUID-009</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-010</xr:ClassId>
<xr:ObjectId>UUID-011</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-012</xr:ClassId>
<xr:ObjectId>UUID-013</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>UUID-014</xr:ClassId>
<xr:ObjectId>UUID-015</xr:ObjectId>
</xr:ContainedObject>
</InternalInfo>
<Properties>
<Name>TestConfig</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>TestConfig</v8:content>
</v8:item>
</Synonym>
<Comment />
<NamePrefix />
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes>
<ScriptVariant>Russian</ScriptVariant>
<DefaultRoles />
<Vendor></Vendor>
<Version></Version>
<UpdateCatalogAddress />
<IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries />
<CommonSettingsStorage />
<ReportsUserSettingsStorage />
<ReportsVariantsStorage />
<FormDataSettingsStorage />
<DynamicListsUserSettingsStorage />
<URLExternalDataStorage />
<Content />
<DefaultReportForm />
<DefaultReportVariantForm />
<DefaultReportSettingsForm />
<DefaultReportAppearanceTemplate />
<DefaultDynamicListSettingsForm />
<DefaultSearchForm />
<DefaultDataHistoryChangeHistoryForm />
<DefaultDataHistoryVersionDataForm />
<DefaultDataHistoryVersionDifferencesForm />
<DefaultCollaborationSystemUsersChoiceForm />
<RequiredMobileApplicationPermissions />
<UsedMobileApplicationFunctionalities>
<app:functionality>
<app:functionality>Biometrics</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Location</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundLocation</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BluetoothPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>WiFiPrinters</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Contacts</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Calendars</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PushNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>LocalNotifications</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InAppPurchases</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PersonalComputerFileExchange</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Ads</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NumberDialing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>CallLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AutoSendSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ReceiveSMS</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SMSLog</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Camera</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Microphone</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>MusicLibrary</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>PictureAndVideoLibraries</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>InstallPackages</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>OSBackup</app:functionality>
<app:use>true</app:use>
</app:functionality>
<app:functionality>
<app:functionality>ApplicationUsageStatistics</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BarcodeScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>BackgroundAudioRecording</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllFilesAccess</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Videoconferences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>NFC</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>DocumentScanning</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>SpeechToText</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>Geofences</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>IncomingShareRequests</app:functionality>
<app:use>false</app:use>
</app:functionality>
<app:functionality>
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
<app:use>false</app:use>
</app:functionality>
</UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles />
<MobileApplicationURLs />
<AllowedIncomingShareRequestTypes />
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface />
<DefaultStyle />
<DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation />
<DetailedInformation />
<Copyright />
<VendorInformationAddress />
<ConfigurationInformationAddress />
<DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm />
</Properties>
<ChildObjects>
<Language>Русский</Language>
<Catalog>Контрагенты</Catalog>
<Catalog>Организации</Catalog>
<Catalog>ДоговорыКонтрагентов</Catalog>
<Document>ЗаказПоставщику</Document>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,82 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Document uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="DocumentObject.ЗаказПоставщику" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DocumentRef.ЗаказПоставщику" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DocumentSelection.ЗаказПоставщику" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DocumentList.ЗаказПоставщику" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="DocumentManager.ЗаказПоставщику" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>ЗаказПоставщику</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Заказ поставщику</v8:content>
</v8:item>
</Synonym>
<Comment/>
<UseStandardCommands>true</UseStandardCommands>
<Numerator/>
<NumberType>String</NumberType>
<NumberLength>11</NumberLength>
<NumberAllowedLength>Variable</NumberAllowedLength>
<NumberPeriodicity>Year</NumberPeriodicity>
<CheckUnique>true</CheckUnique>
<Autonumbering>true</Autonumbering>
<Characteristics/>
<BasedOn/>
<InputByString>
<xr:Field>Document.ЗаказПоставщику.StandardAttribute.Number</xr:Field>
</InputByString>
<CreateOnInput>Use</CreateOnInput>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<AuxiliaryObjectForm/>
<AuxiliaryListForm/>
<AuxiliaryChoiceForm/>
<Posting>Allow</Posting>
<RealTimePosting>Deny</RealTimePosting>
<RegisterRecordsDeletion>AutoDelete</RegisterRecordsDeletion>
<RegisterRecordsWritingOnPost>WriteSelected</RegisterRecordsWritingOnPost>
<SequenceFilling>AutoFill</SequenceFilling>
<RegisterRecords/>
<PostInPrivilegedMode>true</PostInPrivilegedMode>
<UnpostInPrivilegedMode>true</UnpostInPrivilegedMode>
<IncludeHelpInContents>false</IncludeHelpInContents>
<DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects/>
</Document>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Language uuid="UUID-001">
<Properties>
<Name>Русский</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Русский</v8:content>
</v8:item>
</Synonym>
<Comment/>
<LanguageCode>ru</LanguageCode>
</Properties>
</Language>
</MetaDataObject>
@@ -0,0 +1,176 @@
<?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
<Catalog uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="CatalogObject.КрлфСпр" category="Object">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogRef.КрлфСпр" category="Ref">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogSelection.КрлфСпр" category="Selection">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogList.КрлфСпр" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="CatalogManager.КрлфСпр" category="Manager">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>КрлфСпр</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>КРЛФ спр</v8:content>
</v8:item>
</Synonym>
<Comment />
<Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands>
<Owners />
<SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength>
<CodeType>String</CodeType>
<CodeAllowedLength>Variable</CodeAllowedLength>
<CodeSeries>WholeCatalog</CodeSeries>
<CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics />
<PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice>
<ChoiceMode>BothWays</ChoiceMode>
<InputByString>
<xr:Field>Catalog.КрлфСпр.StandardAttribute.Description</xr:Field>
<xr:Field>Catalog.КрлфСпр.StandardAttribute.Code</xr:Field>
</InputByString>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm />
<DefaultFolderForm />
<DefaultListForm />
<DefaultChoiceForm />
<DefaultFolderChoiceForm />
<AuxiliaryObjectForm />
<AuxiliaryFolderForm />
<AuxiliaryListForm />
<AuxiliaryChoiceForm />
<AuxiliaryFolderChoiceForm />
<IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn />
<DataLockFields />
<DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch>
<ObjectPresentation />
<ExtendedObjectPresentation />
<ListPresentation />
<ExtendedListPresentation />
<Explanation />
<CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory>
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
</Properties>
<ChildObjects>
<Attribute uuid="UUID-012">
<Properties>
<Name>База</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>База</v8:content>
</v8:item>
</Synonym>
<Comment />
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<PasswordMode>false</PasswordMode>
<Format />
<EditFormat />
<ToolTip />
<MarkNegatives>false</MarkNegatives>
<Mask />
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true" />
<MaxValue xsi:nil="true" />
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:string" />
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks />
<ChoiceParameters />
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm />
<LinkByType />
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Use>ForItem</Use>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Attribute>
<Attribute uuid="UUID-013">
<Properties>
<Name>Новый</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Новый</v8:content>
</v8:item>
</Synonym>
<Comment />
<Type>
<v8:Type>xs:boolean</v8:Type>
</Type>
<PasswordMode>false</PasswordMode>
<Format />
<EditFormat />
<ToolTip />
<MarkNegatives>false</MarkNegatives>
<Mask />
<MultiLine>false</MultiLine>
<ExtendedEdit>false</ExtendedEdit>
<MinValue xsi:nil="true" />
<MaxValue xsi:nil="true" />
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:boolean">false</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
<ChoiceParameterLinks />
<ChoiceParameters />
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceForm />
<LinkByType />
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<Use>ForItem</Use>
<Indexing>DontIndex</Indexing>
<FullTextSearch>Use</FullTextSearch>
<DataHistory>Use</DataHistory>
</Properties>
</Attribute>
</ChildObjects>
</Catalog>
</MetaDataObject>

Some files were not shown because too many files have changed in this diff Show More