Compare commits

..
44 Commits
Author SHA1 Message Date
Nick ShirokovandClaude Opus 5 fc2d460b57 docs(web-info,web-publish): убрать детали реализации из инструкций
В «Как читать вывод» протекли внутренности фильтра журнала (коды сообщений
Apache, что именно отсеивается и почему) и обоснование правки. Модели,
использующей навык, нужны сигналы и действие по ним, а не устройство.
В web-publish по той же причине убрано имя атрибута дескриптора.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:48:32 +03:00
Nick ShirokovandClaude Opus 5 0d4933f135 feat(web-info): проверка TCP-порта Apache
Статус строился только по наличию процесса httpd: при живом процессе и
не поднявшемся сервере (занятый порт, битый конфиг) навык рапортовал
«Запущен». Теперь порт из Listen проверяется TCP-коннектом на 127.0.0.1
с таймаутом 1 с — «(слушается)» / «(не отвечает)».

Сами публикации намеренно не опрашиваются: запрос к /{AppName} поднимает
сеанс веб-клиента 1С и занимает лицензию, а публикаций может быть много.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:46:22 +03:00
Nick ShirokovandClaude Opus 5 b8cbb0111a feat(web-info): в секции ошибок только error и выше + путь к журналу
Печатались последние 5 строк журнала как есть, поэтому секция «Последние
ошибки» показывала notice старта, а на Windows ещё и штатный шум
winnt_accept — содержательные записи вытеснялись. Теперь фильтр по уровню
error/crit/alert/emerg; AH02538 отсеивается отдельно как след нашего же
рестарта Apache при публикации.

Путь к журналу печатается всегда — за отсеянными уровнями и полным
контекстом читается файл.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:37:24 +03:00
Nick ShirokovandClaude Opus 5 88535a759b fix(web-info): имя журнала ошибок из httpd.conf + описание вывода вместо примера
Секция «Последние ошибки» всегда печатала «(нет файла)»: путь был захардкожен
как logs/error.log, а сборка Apache пишет logs/error_log. Имя берём из директивы
ErrorLog, с фолбэком на оба общепринятых имени.

В SKILL.md дословный пример вывода заменён легендой: модель и так видит реальный
stdout, ей нужна семантика тегов и признаков, а не их копия (и копия дрейфует).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:28:20 +03:00
Nick ShirokovandClaude Opus 5 fbf4d4f986 fix(web-info): детект OData по <standardOdata enable> + метка сервисов расширений
Тег [OData] искался по атрибуту enableStandardOdata на <point> — форма до
8.3.9, которую web-publish не пишет с перехода на <standardOdata enable="true"/>,
поэтому в сводке не появлялся никогда. Старая форма оставлена как fallback
для дескрипторов не нашего происхождения.

Плюс суффикс +ext на WS/HTTP, когда в дескрипторе publishExtensionsByDefault.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:20:20 +03:00
Nick ShirokovandClaude Opus 5 1eeea204c3 feat(web-publish): публикация сервисов расширений в default.vrd
publishExtensionsByDefault="true" на <ws> и <httpServices>: сервисы,
поставляемые расширениями конфигурации, публикуются вместе с сервисами
самой конфигурации. Без атрибута HTTP-сервис расширения отдаёт 404,
web-сервис — 500 «Сервис не найден».

Проверено E2E на 8.3.24 (A/B на одном дескрипторе, оба порта).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 15:19:08 +03:00
Nick ShirokovandClaude Opus 5 27e78859e8 fix(tests): отпечаток фикстуры рядом с каталогом, а не внутри
Файл .fixture-stamp лежал внутри каталога фикстуры, а фикстура копируется в
рабочий каталог кейса целиком — и отпечаток попал в 11 снэпшотов. Перенесён в
<cache>/<setup>.stamp, из снэпшотов удалён.

663/663 ps1, 660/663 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 13:53:25 +03:00
Nick ShirokovandClaude Opus 5 aa32fb84dc test: снята мёртвая нормализация регистра encoding в снэпшотах
Третья маска подряд, на которую не опирался ни один кейс: py-набор без неё
зелёный целиком (660/663). Порты пишут XML-декларацию одинаково.

Осталась одна нормализация — схлопывание пробелов между тегами. Она живая:
без неё падают 58 кейсов в 15 навыках, то есть порты реально отступают
по-разному. Это отдельная задача, комментарий в раннере теперь называет цифру,
чтобы маска не выглядела безобидной.

663/663 ps1, 660/663 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 13:40:53 +03:00
Nick ShirokovandClaude Opus 5 4c84d840df fix(tests): инвалидация кэша фикстур empty-config по содержимому cf-init
Каталог фикстуры кэшировался навсегда: правка cf-init до него не доходила,
снэпшоты записывались со старой фикстурой, и расхождение всплывало только на
машине с пустым кэшем. Поймано прогоном на маке — там фикстура собралась заново
и принесла TextToSpeech, которого нет в снэпшотах, записанных на Windows.

Теперь рядом с фикстурой лежит отпечаток: аргументы плюс хэши обоих портов
cf-init. Не совпал — каталог пересобирается.

Снэпшоты кейсов 2.21 перезаписаны со свежей фикстурой (дрейф — только
TextToSpeech). У mxl-compile снэпшот кейса снят через noSnapshot: у навыка
normalizeUuids=false, поэтому UUID фикстуры дрожали бы при каждой пересборке,
а предмет кейса проверяет fileContains.

663/663 ps1, 660/663 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-08 12:53:38 +03:00
Nick ShirokovandClaude Opus 5 57c8cb6d23 feat(cfe-init): Caption и ShortCaption в корне расширения на формате 2.21
Свойства корня, добавленные форматом 2.21, между Version и DefaultLanguage.
В cf-init они уже были, в cfe-init — нет. Нашёл раундтрип расширения через базу
8.5 по сценарию из issue #57: загрузка расширения и обратная выгрузка давали
диff ровно на этих двух элементах.

После правки выгрузка расширения из базы 8.5 совпадает с исходниками полностью —
и по составу файлов, и побайтово.

663/663 ps1, 660/663 py; 1С-сертификация cfe-init и cfe-borrow на 8.5 — 2/2.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 23:08:18 +03:00
Nick ShirokovandClaude Opus 5 3b1a2f64d5 feat(cf-init,form-add): TextToSpeech и UseInInterfaceCompatibilityMode в формате 2.21
Оба пробела нашёл цикл через базу 8.5 (собрать навыками → загрузить → выгрузить):

- cf-init не писал возможность мобильного приложения TextToSpeech, добавленную
  форматом 2.21 последней в списке UsedMobileApplicationFunctionalities;
- form-add не писал <UseInInterfaceCompatibilityMode>Any</…> в метаданных формы.
  meta-compile эмитил это свойство только для общей формы, а платформа 8.5 пишет
  его у форм любых объектов — сразу после UsePurposes, до ExtendedPresentation.

После правки выгрузка платформы совпала с исходниками везде, кроме
ConfigurationExtensionCompatibilityMode (платформа поднимает значение до своего
максимума — так и задумано), скелета Ext/Form.xml и тела MXL: оба расхождения
предсуществующие, к формату 2.21 не относятся.

663/663 ps1, 660/663 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 22:25:50 +03:00
Nick ShirokovandClaude Opus 5 6315a706b5 test: кейсы формата 2.21 для навыков-эмиттеров
Фикстура empty-config-221 (cf-init -FormatVersion 2.21) и по кейсу на навык:
cf-init, meta-compile, meta-edit, form-add, form-compile, template-add,
mxl-compile, subsystem-compile, subsystem-edit, role-compile, xdto-compile,
cfe-init, cfe-borrow, epf-init, erf-init.

Проверки через expect.fileContains по сырым байтам, а не только снэпшотом:
позиция xmlns:pal (после lf, перед style) и его отсутствие там, где платформа
его не пишет (Rights.xml роли, Ext/ClientApplicationInterface.xml).

663/663 ps1, 660/663 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:56:26 +03:00
Nick ShirokovandClaude Opus 5 3ef9e76f03 feat(epf-init,erf-init,form-add,form-compile,template-add,mxl-compile,help-add): версия формата у автономных EPF/ERF
Детекторы версии искали только Configuration.xml, поэтому внутри автономной
внешней обработки или отчёта всегда получалось 2.17 — независимо от version в
корне самого объекта. Теперь версия наследуется от корня EPF/ERF: подъём по
дереву проверяет и <каталог>.xml с корнем ExternalDataProcessor/ExternalReport,
а form-add и template-add читают её прямо из файла объекта, с которым работают.

Только после этого у epf-init/erf-init появился параметр -FormatVersion
(2.17…2.21, дефолт 2.17 — прежнее поведение). Без наследования он дал бы
обработку 2.21, внутри которой формы и макеты остаются 2.17.

Проверено сквозным прогоном: epf-init -FormatVersion 2.21 → form-add →
template-add → mxl-compile, все файлы 2.21 с xmlns:pal, порты идентичны.

Снэпшоты role-info/role-validate/cf-edit обновлены — их фикстуры строит
role-compile, дрейф только от смены его формата на платформенный.

648/648 ps1, 645/648 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:41:42 +03:00
Nick ShirokovandClaude Opus 5 5bcf6d08cd fix(role-compile): формат Role.xml и Rights.xml как у платформы
Навык писал шапку многострочно (каждое объявление своей строкой) и отступы
четырьмя пробелами, тогда как платформа пишет шапку одной строкой и отступы
табами. На каждой роли это давало диff во всю длину файла — шум, из-за которого
реальные расхождения не видны.

Сверено с выгрузкой УНФ 8.5: скелет Role.xml совпал с платформенным полностью
(с точностью до uuid и текстов). 1С-сертификация: 10/10 кейсов загружаются.
Снэпшоты обновлены — дрейф только ожидаемый (склейка шапки и пробелы→табы).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:13:35 +03:00
Nick ShirokovandClaude Opus 5 5c3449d6c2 feat(mxl-compile): определение версии формата + xmlns:pal в 2.21
У корня <document> нет атрибута version, поэтому версия берётся из конфигурации,
в дерево которой пишется макет (подъём до Configuration.xml, как в остальных
навыках); вне конфигурации остаётся прежнее поведение — 2.17.

Платформа 8.5 объявляет палитру и в теле MXL: в выгрузке УНФ 8.5 xmlns:pal стоит
у всех 3471 файлов с корнем document.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 21:01:45 +03:00
Nick ShirokovandClaude Opus 5 53c1d59ec1 feat(cfe-borrow,cfe-init,form-add,form-compile,role-compile,subsystem-compile,subsystem-edit,template-add,xdto-compile): xmlns:pal в формате 2.21
Платформа 8.5 объявляет пространство палитры в шапках MetaDataObject и Form.
Радиус снят по выгрузке УНФ 8.5, а не угадан: pal есть у MetaDataObject, Form,
document (тело MXL), Style, AppearanceTemplate, GraphicalSchema; у Rights, Help,
CommandInterface, DataCompositionSchema и корней extrnprops его нет. Поэтому
role-compile правит шапку Role.xml и не трогает Rights.xml.

Вставка идёт на место — после lf, перед style: объявления платформа держит по
алфавиту, дописать в конец нельзя.

Попутно в cfe-borrow тег <Form> больше не копируется из исходной формы целиком:
из него берутся только объявления пространств, а version подставляется своя.
Раньше версия источника молча побеждала — вопреки комментарию рядом.

Тесты: снята нормализация xmlns в снэпшотах py-прогона (она прятала ровно этот
класс расхождений и к моменту снятия была мёртвой — ни один кейс на неё не
опирался); добавлены expect.fileContains/fileNotContains по сырым байтам и гейт
на неизвестные ключи expect.* — он вскрыл два кейса-пустышки (skd-compile,
skd-edit), они починены.

648/648 ps1, 645/648 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 20:57:55 +03:00
Nick ShirokovandClaude Opus 5 d60dd47f0b refactor(cfe-init,form-add,form-compile,subsystem-compile,subsystem-edit,template-add,xdto-compile): шапка XML одной переменной
Объявления пространств имён вынесены в переменную рядом с определением версии
формата; места эмиссии её только интерполируют. Раньше шапка была вклеена в
литерал в каждой точке записи, и любая правка шапки ложилась в каждый навык
по-своему.

Попутно в form-compile убрана мёртвая эмиссия Title и шапки перед пересозданием
буфера (её результат затирался).

Вывод байт-в-байт: 648/648 ps1, 645/648 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 20:33:42 +03:00
Nick ShirokovandClaude Opus 5 34b533a730 feat(meta-compile,meta-edit,cf-init,meta-validate): формат выгрузки 2.21 (8.5)
Дельта 2.20 → 2.21 снята синтетическим экспериментом: одна и та же минимальная
конфигурация загружена в две пустые базы, 8.3.27.1859 и 8.5.1.1302, и выгружена
из каждой (стенд debug/fmt221). Всё, что нашлось раундтрипом по УНФ,
воспроизвелось на синтетике — значит это ступень формата, а не особенность
конфигурации.

Версию формата задаёт ПЛАТФОРМА ВЫГРУЗКИ, а не режим совместимости:
CompatibilityMode в обеих выгрузках остался Version8_3_24, но 8.5 всё равно
написала 2.21. Поэтому нигде не появляется привязки к режиму совместимости —
только к версии из Configuration.xml, как и было устроено.

Реализовано:
- xmlns:pal (палитра) в шапках MetaDataObject и Form у meta-compile и cf-init.
  Вставка НА МЕСТО (после lf, перед style) — платформа держит объявления по
  алфавиту. В файлы с корнем extrnprops (Ext/ClientApplicationInterface.xml)
  платформа его не пишет, и мы не пишем.
- meta-compile: <Color>auto</Color> у значения перечисления, <AuxiliaryVariantForm/>
  у отчёта, <UseInInterfaceCompatibilityMode>Any</…> у общей формы. Позиции взяты
  из эталонной выгрузки, а не угаданы.
- meta-edit: <Color> при добавлении значения перечисления — навык сам эмитит
  EnumValue, и без этого добавленное значение отличалось бы от соседних.
- cf-init: 14 новых свойств корня при -FormatVersion 2.21.
- meta-validate: лестница версий (был единственным валидатором без 2.21) и три
  строки в реестре versionedProps — механизм для этого и заводился.

Проверка:
- наш вывод на 2.21 совпал с выгрузкой платформы ПОСТРОЧНО по Enums, Reports и
  CommonForms; порты идентичны modulo UUID;
- на 2.17-2.20 вывод не изменился ни в одном байте;
- юнит-тесты 648/648 ps1, 645/648 py.

Известное отклонение, НЕ относящееся к 2.21: скелет Ext/Form.xml общей формы
отличается от платформенного (dcssch в шапке, форма AutoCommandBar, Attributes) —
то же расхождение есть и на 2.20, разбирать отдельно.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 19:39:31 +03:00
Nick ShirokovandClaude Opus 5 7892c9fbfa fix(meta-compile): позиция условных стандартных реквизитов бухрегистра
У бухрегистра условные стандартные реквизиты стоят по обе стороны фикс-списка:
PeriodAdjustment — перед Account, пары субконто — после Period. Эмиттер же клал
все ключи, которых нет в списке типа, скопом вперёд, поэтому порядок расходился
с выгрузкой.

Разделено на три части: ключи вне списка по умолчанию идут ПЕРЕД ним (легаси
вроде ExchangeDate у части планов обмена), подходящие под хвостовой шаблон типа —
ПОСЛЕ, а именованные условные (PeriodAdjustment) занимают своё место в самом
списке и эмитятся при наличии в DSL.

Пары субконто заданы ШАБЛОНОМ, а не списком имён: их количество определяется
свойством MaxExtDimensionCount плана счетов. В корпусе везде 3, но это
однородность выборки, а не правило, и список из ExtDimension1..3 закрыл бы
только наблюдаемый случай. Хвост сортируется по номеру, внутри номера
ExtDimensionN идёт перед ExtDimensionTypeN.

Проверка:
- синтетика с ПЯТЬЮ парами субконто и перемешанными ключами на входе даёт
  канонический порядок с пропусками номеров; порты байт-в-байт (modulo UUID);
- раундтрип «все виды» (112 объектов, 42 вида): порядок ≠ 0, TOTAL diff 0;
- юнит-тесты 648/648 ps1, 645/648 py; 1С-сертификация accounting-register ✓.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 18:00:31 +03:00
Nick ShirokovandClaude Opus 5 0fc2d738a2 fix(meta-compile): порядок стандартных реквизитов ещё четырёх типов + Use у ПВХ
Раундтрип покрывал пять видов объектов из сорока с лишним, поэтому расхождения
порядка в остальных были не видны. Списки расширены до «по три объекта из
каждого вида» (112 объектов, 42 вида) — содержательных расхождений нет нигде,
но всплыли перестановки.

1. Порядок стандартных реквизитов, снят с корпуса acc+erp:
   - ChartOfCharacteristicTypes: PredefinedDataName, ValueType, Description,
     Code, IsFolder, Parent, Predefined, DeletionMark, Ref;
   - BusinessProcess: Started, HeadTask, Completed, Ref, DeletionMark, Date, Number;
   - Task: Executed, Description, RoutePoint, BusinessProcess, Ref, DeletionMark,
     Date, Number;
   - AccountingRegister: Account, Active, LineNumber, Recorder, Period.
   IsFolder добавлен в фикс-список ПВХ: он есть во всех 47 объектных блоках
   корпуса (5 выгрузок) и НЕ связан с иерархичностью — плоских ПВХ вдвое больше
   иерархических (36 против 15), и у них он тоже есть.

2. Новый контекст реквизита `cct` для ПВХ. Структурно реквизит ПВХ совпадает со
   справочником, но <Use> у него идёт ПОСЛЕ <Indexing>, а у справочника — перед
   (корпус: Catalog `Use,Indexing,FullTextSearch`, ПВХ
   `Indexing,Use,FullTextSearch,DataHistory`). Раньше оба шли одним контекстом.

Проверка:
- ПВХ (24 объекта): порядок ≠ 23 → 0; все виды (112): 7 → 1;
- шесть основных списков (4897 объектов) без регрессии: порядок ≠ 0,
  TOTAL diff lines 0, match не просел;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация на 8.3.24: accounting-register, business-process, task,
  chart-of-characteristic-types — все приняты;
- дрейф эталонов 6 файлов: перестановки плюс единственное новое содержимое —
  блок IsFolder (проверено вырезанием: без него мультимножества совпадают).

Остаётся известным: у бухрегистра условные стандартные реквизиты стоят по обе
стороны фикс-списка (PeriodAdjustment перед, ExtDimension1..3 и
ExtDimensionType1..3 после), а эмиттер кладёт все «лишние» ключи вперёд. Это
правка механизма, а не таблицы — отдельной задачей.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 17:37:25 +03:00
Nick ShirokovandClaude Opus 5 0a2dba33cf fix(meta-compile): в Predefined.xml ссылочный тип остаётся локальным
Регрессия предыдущего коммита: cfg: применялся безусловно, а шапка
Ext/Predefined.xml объявляет только predef/v8/xr/xs/xsi — cfg там нет.
Получался неразрешимый префикс, и платформа отказывалась грузить конфигурацию
(1С-сертификация meta-compile/chart-of-characteristic-types: FAIL на
LoadConfigFromFiles; на коде до правки кейс проходил).

Правило платформы, снятое с корпуса, ровно то же, что уже заложено в meta-edit:
префикс из корня, если он там объявлен, иначе локальное объявление. В самом
Predefined.xml платформа пишет
`<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты</v8:Type>`.

Типизированные предопределённые есть только у ПВХ, а ПВХ не входит ни в один
из шести списков раундтрипа — поэтому корпус эту поломку поймать не мог, её
поймала только 1С-сертификация. Добавлен список list-cct-all.txt (24 объекта
acc+erp), чтобы пробел закрылся: content match 24/24, TOTAL diff 0.

Проверка: 1С-сертификация кейса проходит, runner 648/648 ps1 и 645/648 py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 16:50:45 +03:00
Nick ShirokovandClaude Opus 5 683e370938 fix(meta-compile,meta-edit): ссылочный тип корневым cfg:, как пишет платформа
Эмитился локальный xmlns:d5p1 на тот же URI, что уже объявлен в шапке файла.
Формально эквивалентно (значим URI, а не префикс), платформа принимала — но
первый же цикл «загрузить в базу → выгрузить» переписывал КАЖДЫЙ ссылочный тип
в cfg:, то есть давал diff-шум при неизменной семантике. Форма пришла из СКД,
где cfg: действительно не работает; в метаданных такого ограничения нет.

Радиус оказался втрое меньше прежней оценки: form-compile в него НЕ входил —
он уже писал cfg:, а все его d5p1 относятся к чужим пространствам
(txtedt/chart/geo/graphscheme/data-analysis), которых в корне нет и где
локальное объявление законно.

meta-edit берёт префикс из объявлений КОРНЯ правимого файла, а если URI там не
объявлен — остаётся на самодостаточной локальной форме. Это не перестраховка:
ссылочный тип живёт в ТЕКСТЕ узла, поэтому XML-слой про префикс не знает и сам
объявление не добавит — проверено, .NET на документе без xmlns:cfg молча пишет
неразрешимый префикс. Та же природа объясняет удалённый костыль в meta-edit.py:
lxml выбрасывал xmlns:d5p1 как «неиспользуемый», и его возвращали регуляркой
после сериализации.

Синхронно снята нормализация ref-префикса в раундтрип-харнесе (debug/, вне git):
она сводила cfg: и d5p1: к одному REF: и делала харнес слепым ровно к тому,
ради чего затевался переход.

Проверка:
- живой цикл через базу (db-create → db-load-xml → db-dump-xml): выгрузка
  платформы совпала с нашими исходниками ПОСТРОЧНО, расхождений ноль;
- корпусный раундтрип 4897 объектов уже БЕЗ маски префикса: match не просел
  (150/1640/151/2600/314/42), порядок ≠ 0, TOTAL diff lines 0;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация meta-edit 17/17 на платформе 8.3.24;
- дрейф эталонов 48 файлов, в диффе нет ни одной строки кроме d5p1: → cfg:;
  skd-* не затронуты ни одним файлом (там cfg: не работает).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 16:28:47 +03:00
Nick ShirokovandClaude Opus 5 a3aa8fe87c fix(meta-compile): порядок элементов по канону выгрузки
Детект порядка, добавленный в раундтрип-харнес, показал 1242 расхождения на
4897 объектах (25%). Раньше они были невидимы: дифф строился через
Compare-Object, то есть по мультимножеству строк, и позиция не проверялась.
Ложных срабатываний там нет по построению — проверка включается только когда
мультимножества уже совпали.

Три категории, все — порядок эмиссии; DSL и декомпилятор не затронуты.

1. Виды детей регистров (1140 объектов). Компилятор печатал
   Resource, Dimension, Attribute. Канон, снятый с корпуса acc+erp (разброса
   внутри типа нет): Resource, Attribute, Dimension у информационного,
   накопления и расчёта; Dimension, Resource, Attribute — у бухгалтерского.
   Команды у платформы идут последними, как и было.

2. Квалификаторы в составном типе (61 объект). Платформа пишет сначала ВСЕ
   <v8:Type>/<v8:TypeSet>, потом блоки квалификаторов, а Emit-TypeContent
   рекурсивно печатал каждую часть целиком. На одиночном типе оба порядка
   совпадают — потому и не всплывало. Порядок самих блоков тоже канонический
   и НЕ зеркалит порядок типов: Number, String, Date (при типах
   boolean,string,dateTime,decimal квалификаторы идут Number,String,Date;
   контрпримеров в корпусе нет).

3. Стандартные реквизиты плана обмена (41 объект, весь список): блок
   начинается с ThisNode, а не с Ref.

Проверка:
- корпусный раундтрип 4897 объектов: порядок ≠ 1242 → 0, match не просел
  (150/1640/151/2600/314/42), TOTAL diff lines 0;
- юнит-тесты 648/648 ps1, 645/648 py;
- 1С-сертификация на платформе 8.3.24: регистры информационный, накопления,
  бухгалтерский, расчёта и план обмена — все приняты;
- дрейф снэпшотов (10 файлов, 9 кейсов) сверен как чистая перестановка строк.
  NB: сверять нужно с нормализацией UUID-\d+ — раннер нумерует плейсхолдеры по
  порядку появления, и перестановка детей меняет, кому достанется UUID-015.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 15:22:18 +03:00
Nick ShirokovandClaude Opus 5 4833c3b707 fix(mxl-compile): PS-порт не создавал каталог назначения
Наполнение макета по свежему пути (Templates/<Имя>/Ext/Template.xml) на
PowerShell падало с «Could not find a part of the path». py-порт каталог
создаёт, form-compile и skd-compile — тоже; не создавал только этот порт.

Тестами не ловилось: во всех кейсах mxl-compile писал в уже существующий
каталог. Добавлен кейс с записью по несуществующему пути — проверено, что на
коде до правки он падает.

Найдено при разборе вопроса, нужна ли заглушка тела макета в template-add:
оказалось, заглушка держала не замысел, а этот дефект.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 14:11:11 +03:00
Nick ShirokovandClaude Opus 5 a7406d0982 test(8 навыков): байтовые проверки канона там, где их не было (#57)
Из ~46 навыков-эмиттеров XML восемь не имели ни одной проверки `preserves`:
form-edit, form-remove, meta-remove, skd-edit, template-remove, xdto-edit,
support-edit, cfe-patch-method. Шесть из них правились в предыдущем коммите,
и канон у них держался на том, что правка была механической, а не на проверке.

Ожидания писались ПО КАНОНУ, а не по текущему поведению — и это сразу вскрыло
три дефекта:

1. form-remove очищал слот формы через InnerText="" / text="" — получалась
   пустая пара <DefaultObjectForm></DefaultObjectForm>. Платформа пустых пар
   не пишет (0 на 476 942 XML). Теперь IsEmpty / text=None.

2. Опустевший <ChildObjects> оставался парой, разнесённой по строкам, —
   в PS-порту form-remove и template-remove. Корпус: 1394 самозакрывающихся
   <ChildObjects/> на acc+erp, пустых пар 0 в обеих формах.

3. template-add писал пустой макет парой <SpreadsheetDocument></...>.
   Во всей выгрузке acc_8.3.27 (65 040 XML) многострочных пустых пар нет
   ни для одного тега.

Заодно усилена сама проверка noEmptyPairs: она ловила только СМЕЖНЫЕ теги,
поэтому дефект №2 проходил мимо неё. Добавлен вариант с переводом строки
внутри; дискриминатором служит сам перевод строки — значащий пробельный
текст-узел (<xr:FillValue xsi:type="xs:string">   </xr:FillValue>) его не
содержит и под проверку не попадает.

support-edit покрыт частично (BOM у ParentConfigurations.bin — проверено, что
платформа пишет его с BOM во всех трёх выгрузках), cfe-patch-method — BOM+EOL
у .bsl: хвостовой перевод строки у модулей неканоничен (1235 с ним, 766 без),
поэтому не утверждается.

Регресс: 647/647 ps1, 644/647 py (3 skipped). Эталоны переснятые: три места,
каждое — ровно ожидаемая пара строк.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-06 13:05:27 +03:00
Nick ShirokovandClaude Opus 5 a3f2a18dbf test(runner): кейсы round-trip на LF-исходнике (#57)
Все 23 проверки preserves.eol ожидали crlf — ни одного LF-исходника в наборе
не было, поэтому расхождение портов по переводам строк жило целиком в
непокрытой ветке и не могло быть замечено.

Добавлены LF-фикстуры и кейсы roundtrip-lf-preserve:
- cf-edit, meta-edit, subsystem-edit — правка существующего файла через
  XmlWriter (наследование стиля исходника);
- role-compile, meta-compile, xdto-compile — регистрация в существующем
  Configuration.xml. Эти кейсы проверяют обе половины правила разом: правка
  наследует LF, а создаваемый рядом файл получает канон CRLF.

Проверено в требуемом порядке: на коде ДО правки кейсы падают
(role-compile/meta-compile/xdto-compile — «expected lf, got 251 CRLF»,
meta-edit — «40 CRLF + 136 lone LF», subsystem-edit — «1 CRLF + 24 lone LF»),
после правки проходят на обоих рантаймах.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 21:42:08 +03:00
Nick ShirokovandClaude Opus 5 3ef0d74158 fix(19 навыков): единая финализация XML — стиль исходника и корректный CDATA (#57)
Три остатка волны #57, все в одной точке — блоке записи XML.

1. Порты расходились по EOL. PS гонял документ через XmlWriter с дефолтным
   NewLineHandling.Replace и принудительно переводил весь файл в CRLF, а py
   сохранял стиль исходника: role-compile на LF-ном Configuration.xml давал
   10311 байт против 10062. Ещё в пяти навыках None стоял, но финальной
   нормализации не было, и на LF-исходнике выходил смешанный EOL (meta-edit:
   40 CRLF + 136 одиночных LF) — та же форма, что у исходного дефекта #57.
   Приведено к форме cf-edit.ps1: None + канонизация к LF + целевой перевод
   строки (стиль файла-назначения, для создаваемого файла — канон CRLF).
   Replace не годится как замена: он превращает переводы строк внутри значений
   атрибутов в &#13;.

2. Гард `if (-notmatch CDATA)` не обрабатывал CDATA, а отказывался от канона
   во всём файле — то есть деградировал до «не сделал ничего». Заменён
   альтернацией: участки CDATA и комментариев возвращаются как есть, замена
   идёт только вне них. На реальных данных поведение не меняется — в корпусе
   из 476 942 XML нет ни одного CDATA и ни одного комментария.

3. py: три реализации одного правила детекта EOL сведены к одной. Мажоритарное
   правило в role-compile/subsystem-compile давало ДРУГОЙ ответ на смешанном
   входе. Дефолты _finalize_xml_bytes для нового файла приведены к канону
   (UTF-8, CRLF, без хвостового перевода); meta-edit срезает хвост в обеих
   ветках, а не только при создании файла.

Попутно, найдено байтовой сверкой портов:
- py вставлял <Role>/<Form>/<Subsystem> с пятью табами вместо трёх —
  подстановка по голому </ChildObjects> удваивала отступ строки; снэпшоты
  этого не видели, так как схлопывают пробелы между тегами;
- py-порты xdto-* писали XML-декларацию одинарными кавычками (так отдаёт
  lxml), платформа и PS пишут двойные.

Регресс: runner 647/647 ps1, 644/647 py (3 skipped), дрейфа снэпшотов нет.
Корпусный раундтрип метаданных: 4897 объектов, match 100%, совпадает с
эталонами захода #57.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 21:41:55 +03:00
Nick ShirokovandClaude Opus 5 750ba3bb13 refactor(эмиттеры): одна формулировка на повторяющийся комментарий
Код нормализации пустого тега повторён в 24 местах, а комментарий к нему за время
работы над #57 расплодился в шесть вариантов формулировки. Это то же расхождение
копий, что и в коде, только в тексте: читающий не понимает, значима разница или нет.

Сведено к одной паре формулировок — про сам тег с гардом и про запись через
MemoryStream. Комментарий теперь начинается с ПРАВИЛА, а не с причины: раньше
верхняя строка рассказывала, что here-string наследует LF от .ps1, и читалось это
как «переводы строк берём из скрипта навыка» — тогда как правило обратное:
создаём файл — пишем канон, правим существующий — наследуем его стиль.

Два пояснения в cfe-borrow унификация чуть не стёрла, они возвращены: источником
там служит OuterXml, а не XmlWriter, и нормализация стоит после вставки реквизитов,
чтобы накрыть и их. Это причина местного отличия, а не дубль.

Ссылки на ишью прорежены: #44/#46/#47 осталась по одной на файл — там, где правило
формулируется, — и убрана оттуда, где была эхом. #57 в скриптах не было вовсе.

Заодно выровнены две мелочи, найденные той же сверкой копий: interface-edit в
py-порте не обрезал хвостовой перевод там, где PS обрезает (поведение совпадало,
код — нет), а cf-edit нормализовал EOL инлайном вместо общей двухшаговой формы.

Версии подняты в ОБОИХ портах всех затронутых навыков, включая те, где текстуально
менялся только .ps1: номер версии в этом проекте читается как «одно и то же
поведение», и разводить его из-за комментария нельзя. Паритет проверен — 69 пар,
ноль расхождений.

Windows 641/641 обоими портами.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:06:38 +03:00
Nick ShirokovandClaude Opus 5 4c64b97cec fix(skd-edit): гард на CDATA при нормализации пустого тега
skd-edit нормализовал ` />` в `/>` без проверки на CDATA и комментарии, в отличие
от остальных 23 мест, где такая нормализация есть. Вскрылось сверкой копий:
конструкция повторяется в 24 местах, и вариантов формы должно быть ровно два —
с гардом и никак иначе.

Гард нужен потому, что `>` не экранируется только внутри CDATA и комментариев —
там ` />` может оказаться содержимым, а не концом тега. В 1С-метаданных ни того,
ни другого не встречается (0 из 476 943 XML корпуса), так что практического
расхождения поведения нет — но полагаться на это в одном месте и защищаться
в остальных двадцати трёх непоследовательно.

Правка в обоих портах: у py-порта нормализация тоже была без гарда (она там
защитная — lxml и так пишет плотно).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 20:05:47 +03:00
Nick ShirokovandClaude Opus 5 67185657c1 refactor(эмиттеры): копии канон-writer'а приведены к одному виду
Навыки автономны, общей библиотеки нет — значит копии функции записи должны быть
буквально одинаковыми, иначе теряется смысл копипасты. PS-порт дисциплину держал
(7 копий байт-в-байт), а py разъехались: три варианта имени параметра, два стиля
кавычек, у cfe-borrow ещё и другое имя функции (save_xml_file).

Сведено к одной форме: имя write_xml_file, параметр content, одинаковое тело.
Различаются ровно две вещи, и обе осмысленные: вызов локального базового writer'а
(имена исторические — write_utf8_bom / write_text_with_bom / save_text_bom) и одна
строка примечания там, где у навыка есть исключение (модуль .bsl, HTML).

Текст комментария в PS выровнен с docstring'ом py: раньше он описывал ПРИЧИНУ
(here-string наследует LF), а не правило, и после правки py ports разъехались по
смыслу. Синтаксис остаётся разным по природе языков: в py docstring (в портах их
421 против 73 функций с #-комментарием — это местная норма), в PS — #.

Windows 641/641 обоими портами.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 19:22:43 +03:00
Nick ShirokovandClaude Opus 5 d05c91be03 fix(epf-init,erf-init,form-add): CRLF в разделителях строк модуля
Скелетные генераторы писали Module.bsl с LF, а платформа пишет модули с CRLF:
в выгрузке БП 2643 CRLF и чисто-LF 0 из 3001 модуля (358 «смешанных» — LF внутри
строковых литералов кода), BOM 2001/2001. По тому же доводу сходимости, что и для
XML: платформа перепишет модуль в CRLF при первой выгрузке, значит наш LF — дифф,
созданный нами.

В репозитории уже была встречная конвенция: meta-compile (модуль команды) и
cfe-patch-method (base/local/remote.bsl) пишут .bsl явным CRLF в обоих портах.
Выбивались только эти три скелета.

Хвостовой перевод строки НЕ трогаем: у платформы он неканоничен — 1235 модулей
с ним, 766 без; это след автора кода, а не правило. Меняем только разделители.

Правка в точке записи, а не в шаблоне: так одна строка на скрипт и не трогаются
\u-экранированные кириллические литералы в py (Edit по ним переэкранирует).

Снэпшоты нормализуют EOL и увидеть это не могут — добавлен preserves на модуль
в epf-init/basic, erf-init/basic, form-add/basic.

Windows 641/641 обоими портами, мак 590/0/51, дрейфа снэпшотов нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:53:50 +03:00
Nick ShirokovandClaude Opus 5 caf038a192 fix(cfe-borrow,template-add,help-add,interface-edit): вывод py-порта не зависит от ОС (#57)
Тот же writer без newline="", что чинился в form-add.py, остался ещё в четырёх
py-портах, и пишут они не модули, а 1С XML: заимствованные объекты и метаданные
формы (cfe-borrow), Template.xml и XML-макеты (template-add), Help.xml (help-add),
вновь создаваемый CommandInterface.xml (interface-edit).

Контент там собирается литералами с \n, поэтому текстовый режим Python давал CRLF
на Windows — «случайно правильно» — и LF на macOS, ломая канон #57 ровно там, где он
только что установлен. Проверено на маке ДО фикса: cfe-borrow 0 CRLF + 33 одиночных
LF, template-add 0 + 2, interface-edit 0 + 5. Порчи \r\r\n не было, но это везение:
подай такой writer CRLF-контент — каждая строка стала бы \r\r\n.

Почему не поймали раньше: аудит гонялся на Windows, где текстовый режим даёт канон;
снэпшоты нормализуют EOL; байтовый preserves ни разу не проверялся на маке.

Разделены два пути, которые я сперва смешал в cfe-borrow:

- save_xml_file — канон (CRLF, без хвоста) для файлов, которые СОЗДАЁМ;
- save_text_bom — пишет как есть, для файлов, которые ПРАВИМ: переводы строк уже
  пришли из самого файла и менять их нельзя (контракт #44/#46/#47). Туда же
  добавлено чтение с newline="" — иначе CRLF терялся ещё на входе.

interface-edit чинится и в PS-порте: ветка -CreateIfMissing давала смешанный EOL
(3 CRLF + 2 одиночных LF) уже на Windows. Аудит #57 её пропустил, потому что
фильтровал файлы по расширению .xml, а кейс создаёт файл с именем без расширения.

Канон, как выяснилось, зависит от типа артефакта: XML — CRLF, текстовый макет —
CRLF, а HTML-макет платформа хранит с LF (корпус: 399 LF из 400). Поэтому HTML
через канон-writer НЕ идёт.

preserves добавлен четырём навыкам (у них его не было вовсе) + отдельно на Help.xml.
Именно прогон на маке и даёт покрытие: на Windows эти кейсы зелены и без фикса.

Windows 641/641 обоими портами, мак 590/0/51, дрейфа снэпшотов нет.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 18:53:17 +03:00
Nick ShirokovandClaude Opus 5 668b145010 docs(form-remove,help-add,template-add,template-remove): единое имя навыка в шапках обоих портов
В .ps1 шапка называла НАВЫК, в .py — ФАЙЛ (help-add v1.12 против add-help v1.12).
Канон — имя навыка: так называется каталог, так навык вызывается, так он записан
в SKILL.md. Расхождение было только у этих четырёх: у них имя файла отличается от
имени навыка. Версии совпадали, поменялось только имя.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:29:54 +03:00
Nick ShirokovandClaude Opus 5 d7fd6d79d3 test(runner): байтовые проверки канона вместо масок (#57)
Раннер сам прятал дефекты, которые чинит #57. normalizeXmlContent при
runtime=python снимал ровно четыре измерения: пробел перед `/>`, whitespace между
тегами, пустую пару <Tag></Tag> и хвостовой пробельный мусор. Паритет PS<->PY по
этим измерениям проверялся ЧЕРЕЗ маску — расхождение физически не могло упасть.

Снято три из четырёх (пробел, пустая пара, хвост). Схлопывание whitespace между
тегами оставлено: порты кое-где расставляют отступы иначе, это форматирование, а
не байтовый канон.

Ужесточён checkPreserves: eol теперь считает ОДИНОЧНЫЕ LF, а не «есть ли хоть один
CR». Прежняя проверка пропускала смешанный выход — cfe-init давал 10 CR на 70
строк и проходил её, то есть головной дефект тикета был ей невидим. Добавлены
ключи selfClose:"tight" и noEmptyPairs; preserves проставлен в 13 кейсах
навыков-эмиттеров (по одному на навык, на его СОБСТВЕННЫЙ артефакт).

Снятие масок сразу вскрыло четыре реальных расхождения портов:

- form-edit собирает выход из OuterXml и писал `<a />`; в списке 17 навыков его не
  было, потому что искал по вызовам Save — здесь другой путь. Тот же случай, что
  с Form.xml в cfe-borrow;
- meta-edit py дописывал хвостовой перевод в создаваемый Ext/Predefined.xml и
  читал существующий без newline='' (терял CRLF);
- skd-info py писал отчёт -OutFile без хвостового перевода, PS через WriteAllLines
  — с ним. Это текстовый отчёт, канон Конфигуратора к нему не относится, поэтому
  выровнял py по существующему эталону;
- фикстуры кейсов содержали <Vendor></Vendor> — снимок нашего же старого вывода.
  Конфигуратор пустых пар не пишет, .NET их сохраняет, lxml схлопывает. Поправлены
  10 фикстур: пары → самозакрывающиеся, EOL и BOM не тронуты.

Результат: PS 641/641, python 638/641 (+3 runtimeOnly-скипа) — со снятыми масками.
Дрейф снэпшотов: 10 файлов, только пробельные теги и пустые пары.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 16:23:13 +03:00
Nick ShirokovandClaude Opus 5 11e4e4301f fix(cf-init,cfe-init,meta-compile): пустой элемент вместо пустой пары (#57)
Конфигуратор пишет <Vendor/>, а не <Vendor></Vendor>: в 8 выгрузках cfsrc
(476 943 XML) пустых пар ноль. Шаблоны подставляли значение ВНУТРЬ пары, поэтому
при пустом значении получалась пара.

Собираем элемент целиком: <Vendor>/<Version> (cf-init, cfe-init),
<DefaultRoles> (cfe-init при -NoRole), <Key> и <Namespace> (meta-compile —
план обмена и веб-сервис). Образец рядом же: <Comment> и <Description> в
meta-compile так делали и раньше.

Дефект был в ОБОИХ портах одинаково — здесь дело не в сериализаторе, а в шаблоне.

Пустая пара <Namespace> в cfe-borrow была не его: он копирует свойства
заимствуемого объекта, а порождал их meta-compile.

Дрейф снэпшотов: 340 <Vendor/>, 339 <Version/>, по одному <Key/>, <Namespace/>,
<DefaultRoles/>. Содержательных изменений ноль. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:55:31 +03:00
Nick ShirokovandClaude Opus 5 0b8231c467 fix(17 навыков): CRLF в разделителях строк XML (#57)
Головной дефект тикета: cfe-init выдавал Configuration.xml с 10 CR на 70 строк, а
роль и язык — вовсе без CR. Теперь 70/70, последний байт `>`.

Правило: меняем только РАЗДЕЛИТЕЛИ строк, содержимое текстовых узлов не трогаем.
Платформа его не трогает тоже — в запросах СКД из чистой выгрузки встречаются и
CRLF, и одиночный LF. Поэтому сплошная нормализация файла применяется только там,
где многострочных текстовых узлов нет по построению (скелеты), а в объектном XML
и формах разделители правятся в точке сборки.

Источников оказалось четыре, а не один:

1. Скелеты (cf-init, cfe-init, epf-init, erf-init, form-add, help-add,
   template-add) собираются here-string'ами, а .ps1/.py в репозитории хранятся с
   LF — отсюда LF и смешанный EOL.
2. py-порты склеивали документ через '\n'.join(lines); PS в тех же местах давал
   CRLF через AppendLine — порты расходились побайтово.
3. Билдеры Predefined в meta-compile собирались с явным LF и даже сворачивали
   CRLF→LF из общего эмиттера типов.
4. Чтение существующего файла в python БЕЗ newline='' молча схлопывает CRLF в LF
   (универсальные переводы строк), и запись потом кладёт LF. Так role-compile и
   subsystem-compile переписывали в LF весь Configuration.xml. meta-compile это
   уже делал правильно — там newline='' стоял с фикса #44/#46/#47.

Отдельно: XML-парсер по спецификации схлопывает CRLF при разборе, поэтому
lxml-порты (xdto-compile, xdto-edit) отдавали LF-документ там, где .NET возвращал
CRLF через NewLineHandling. Восстанавливаем EOL исходного файла.

Разделение канона и сохранения стиля:

- файл СОЗДАЁМ — канон (CRLF, без хвоста);
- существующий ПРАВИМ — наследуем его EOL, включая перевод строки вставки
  (контракт #44/#46/#47). Иначе LF-проект получал бы смешанные файлы — ровно то,
  на что заведён #57. Кейсы roundtrip-crlf-preserve остаются зелёными.

Хелпер записи скопирован в каждый навык (навыки автономны). У meta-compile он
называется Write-XmlFileKeepEol / write_xml_file_keep_eol: там нормализовать EOL
НЕЛЬЗЯ (многострочные запрос, синоним, значение заполнения), и одинаковое имя при
разном поведении было бы ловушкой.

Заодно: имя временного файла батча в meta-compile.ps1 получило GUID. Фиксированное
"meta-compile-batch-$idx.json" в общем %TEMP% сталкивало два параллельных запуска
(«file is being used by another process») — из-за этого полный набор приходилось
гонять с урезанной параллельностью. py-порт уже брал mkstemp.

Аудит: было ~250 файлов с дефектом EOL, стало 0 в обоих портах. Осталось четыре
законных случая — фикстуры roundtrip-crlf-preserve (сохранение стиля) и запрос
динсписка с многострочным текстом. Дрейф снэпшотов — 1 файл. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 15:37:26 +03:00
Nick ShirokovandClaude Opus 5 abe9431d8f fix(9 навыков): без хвостового перевода строки в конце XML (#57)
Конфигуратор не пишет перевод строки в конце файла — последний байт `>`. Это видно
на чистой выгрузке пустой ИБ (Windows и macOS) и на всех 8 выгрузках в cfsrc.
Наши эмиттеры добавляли лишний, причём оба порта и по разным причинам:

- PS: StringBuilder.AppendLine дописывает Environment.NewLine после последней строки;
- PY: '\n'.join(lines) + '\n' — хвост добавлен явно.

В meta-compile заведена единая точка записи Write-XmlFile: там документ собирают
и AppendLine, и явные `n в билдерах Predefined/Content/Flowchart, а выходов семь.
В остальных обрезка стоит в самом вызове записи.

Модули .bsl, JSON-выход form-compile, HTML-справка help-add и XSD из xdto-decompile
сюда НЕ входят: канон Конфигуратора описывает XML метаданных, а у этих артефактов
свои правила. Результаты XmlDocument.Save тоже не трогаются — у них хвоста нет.

Навыки редактирования продолжают сохранять стиль ВХОДНОГО файла: кейс
subsystem-edit/roundtrip-crlf-preserve, где фикстура намеренно с хвостом, остаётся
зелёным. Это контракт #44/#46/#47, и он не отменяется.

Дрейф снэпшотов: 547 файлов, у каждого только последняя строка, содержательных
изменений ноль. Тесты 641/641.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:53:05 +03:00
Nick ShirokovandClaude Opus 5 39cf8e49cf fix(17 навыков): самозакрывающийся тег без пробела и UTF-8 в декларации (#57)
.NET XmlWriter пишет `<a />`, а Конфигуратор — `<a/>`. Поскольку Save переписывает
файл целиком, meta-edit при добавлении одного реквизита переводил в пробельную форму
все пустые теги документа. Дефект только у PS-порта: lxml (45 из 49 py-портов) уже
пишет плотно, то есть порты расходились побайтово.

Канон измерен, а не предположен: чистая выгрузка пустой ИБ на Windows и macOS плюс
сплошной скан 8 выгрузок в cfsrc — 476 943 XML, 21 294 119 самозакрывающихся тегов,
пробельных 0. Форма не зависит от ОС, версии платформы (8.3.20-8.5) и наличия
атрибутов. Правило уже было реализовано в skd-edit — оттуда и взято.

Замена безопасна доказуемо: .NET экранирует `>` как `&gt;` и в тексте, и в атрибутах,
поэтому ` />` после Save — только конец тега. Лазейки (CDATA, комментарии) в
1С-метаданных не встречаются — 0 из 476 943 файлов; гард на них всё равно стоит.

Одиннадцать скриптов писали прямо в FileStream без пост-обработки — переведены на
MemoryStream, что заодно чинит `encoding="utf-8"` строчными (335 файлов в снэпшотах).
В cfe-borrow правится и сборка Form.xml: куски берутся из OuterXml, а он спацовывает
так же, как XmlWriter.

Дрейф снэпшотов: 12 838 строк тегов + 335 деклараций, содержательных изменений ноль.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-05 13:28:58 +03:00
Nick ShirokovandClaude Opus 5 ccd860ae89 docs(tests): эталон при preRun фиксирует вход теста
README называл info/validate-навыки типичным случаем для noSnapshot: эталон
зафиксировал бы выход preRun, а не проверяемого навыка. Факт верный, вывод — нет.

Когда preRun собирает фикстуру, эталон фиксирует ВХОД теста. Без него дрейф
навыка-генератора меняет фикстуру молча, и ожидание вроде «Составной (6)»
начинает проверяться на другом объекте — либо падает без внятной причины, либо
сходится случайно и перестаёт что-либо проверять. meta-compile такой дрейф даёт
регулярно. Поэтому ни один из семи info-навыков noSnapshot не использует —
у всех эталоны, и это осознанно, а не упущение.

Правило сформулировано явно: есть preRun с генерацией фикстуры → эталон;
нет preRun или фикстура тривиальна → noSnapshot.

В шапке verify-snapshots отмечено, что он работает и с кейсами навыков, которые
сами ничего не пишут, — там он проверяет, что платформа принимает собранную
preRun фикстуру. Плюс предупреждение, что на кейсах setup: external проверка
вырождается в загрузку 1С собственной выгрузки: ~3 минуты на кейс без пользы.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 14:13:58 +03:00
Nick ShirokovandClaude Opus 5 5141cf546d fix(meta-info): иерархия не только у справочника
Строка «Иерархический: …» печаталась в ветке Catalog, хотя свойство
Hierarchical есть и у планов видов характеристик — в корпусе acc/erp/ut/unf
таких 13. У них иерархия не выводилась ни в overview, ни в full.

Хуже того, вывод противоречил сам себе: блок стандартных реквизитов в full
показывал Родитель (он определяется по тому же Hierarchical), а строки про
иерархию рядом не было — родитель появлялся ниоткуда.

Иерархия вынесена из ветки Catalog и печатается по наличию свойства;
подчинение осталось специфичным для справочника. Отсутствие иерархии
по-прежнему не печатается: сообщаем о наличии, молчим об отсутствии —
тихую ошибку в коде даёт только необнаруженное наличие.

Свойство Hierarchical проверено по корпусу: оно есть только у Catalog и
ChartOfCharacteristicTypes (у плана счетов иерархия по природе, свойства нет).
Прогон по корпусу: ровно 13 изменённых строк, побочных эффектов нет.

Находка из отклонённого PR #59 — там иерархия тоже была вынесена из ветки
Catalog. Реализация не заимствована: PR печатал «Иерархический: нет» для
неиерархических объектов, то есть строку-шум примерно у 4000 объектов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:54:43 +03:00
Nick ShirokovandClaude Opus 5 66807e02c7 test(meta-info): кроссплатформенные кейсы на свёртку ТипЗначения
Свёртку составного типа и её разворот через drill-down проверял только кейс
на выгрузке acc_8.3.24, а он привязан к локальному пути и на macOS
пропускается — то есть ключевая новая логика там не проверялась вовсе.

Добавлены два синтетических кейса на фикстуре от meta-compile: ПВХ с шестью
типами сворачивается в «Составной (6)» и печатает подсказку, а -Name
ТипЗначения разворачивает список. В составе фикстуры два v8:TypeSet
(cfg:CatalogRef, cfg:DocumentRef), поэтому кейсы заодно покрывают разбор
обобщённых ссылочных типов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 12:09:52 +03:00
Nick ShirokovandClaude Opus 5 50f8b497b6 feat(meta-info): блок стандартных реквизитов
Ишью #58: сводка не показывала владельца подчинённого справочника, и код,
написанный по ней, падал на Записать(). Владелец оказался частным случаем:
шапка «Код(9) | Наименование(100)» смешивала свойства объекта с полями и не
давала по полям ни типа, ни обязательности. По acc_8.3.24 из 690 справочников
23 имеют числовой код, 122 — фиксированной длины, 426 — вовсе без кода; всё
это было невидимо. У ПВХ, планов счетов и обмена, бизнес-процессов и задач
шапки не было вообще — включая ТипЗначения у ПВХ.

Поля вынесены в блок «Стандартные реквизиты» на языке блока «Реквизиты»
(имя — тип — [флаги]), в шапке остались свойства объекта. В overview
показываются те, что нельзя вывести из остального вывода: Владелец, Код,
Наименование, Дата, Номер, ТипЗначения. Ссылка, ПометкаУдаления, Родитель,
ЭтоГруппа следуют из типа объекта и строки «Иерархический» — они в full.

Обязательность берётся из StandardAttributes/FillChecking. Блок в XML
опционален (meta-dsl-spec §7.1.1), поэтому при его отсутствии действует
профиль платформенных дефолтов, синхронный с meta-compile — иначе объекты,
созданные meta-compile, теряли бы флаг, то есть ровно кейс из ишью.

Составной ТипЗначения ПВХ доходит до 151 типа и сворачивается в счётчик
«Составной (110)»; полный состав даёт drill-down -Name ТипЗначения, о чём
сообщает единственная строка в конце вывода. brief получил строку
«Подчинён: …» — факт структуры без обещаний про обязательность, которых
brief выполнить не может.

Проверено сравнением с эталонным прогоном по 4768 объектам acc/erp/ut/unf:
все расхождения классифицированы, необъяснённых нет, ненулевых кодов возврата
нет. Владелец появился у 242 объектов, числовой код — у 56.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 11:57:32 +03:00
Nick ShirokovandClaude Opus 5 c687860535 fix(meta-info): обобщённые ссылочные типы вместо сырого cfg:
Ветка TypeSet в Format-Type распознавала только DefinedType и Characteristic,
а остальные наборы отдавала в вывод как есть. Голое имя метатипа без «.Имя»
означает все ссылки этого класса (docs/meta-dsl-spec.md), и такие типы
печатались сырыми — 254 вхождения в 82 строках на корпусе acc/erp/ut/unf:

  ЗначениеПустойСсылки   cfg:ExchangePlanRef | cfg:BusinessProcessRef | …
  Свойство1              Булево | Строка(128) | ДатаВремя | cfg:AnyIBRef

Разбор вынесен в Format-SingleTypeSet: AnyRef → ЛюбаяСсылка, AnyIBRef →
ЛюбаяСсылкаИБ, голые XxxRef — через существующий refTypeMap. Конкретный тип
всегда пишется с точкой, поэтому «СправочникСсылка» без точки однозначно
читается как обобщённый.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-04 10:52:39 +03:00
Nick ShirokovandClaude Opus 5 cdf0944b2f fix(meta-info): имя команды вместо склеенного XML
Get-SimpleChildren брала InnerText дочернего узла ChildObjects. Для Form и
Template это верно — имя лежит прямо в тексте узла. Command же имеет вложенный
Properties, и InnerText склеивал всё его содержимое в одну строку:

  Команды: БанковскиеСчета_ИнтерфейсИнтеграцииСБанкомruБанковские счетаForm
  NavigationPanelImportantcfg:CatalogRef.ОрганизацииSinglefalseAutoAuto

Теперь имя берётся из Properties/Name, если оно есть, иначе — прежний InnerText.
Задеты обе точки вывода: overview у отчётов и обработок и full у всех типов.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 22:59:30 +03:00
1230 changed files with 32901 additions and 16934 deletions
+17 -3
View File
@@ -1,4 +1,4 @@
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml) # cf-edit v1.16 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath, [Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
@@ -163,6 +163,8 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
Assert-EditAllowed $resolvedPath 'editable' Assert-EditAllowed $resolvedPath 'editable'
# --- Load XML with PreserveWhitespace --- # --- Load XML with PreserveWhitespace ---
# NB: парсер XML по спецификации схлопывает CRLF в LF, а вставки ниже собираются с
# явным CRLF — поэтому EOL приводится к целевому в точке записи (см. финализацию).
$script:xmlDoc = New-Object System.Xml.XmlDocument $script:xmlDoc = New-Object System.Xml.XmlDocument
$script:xmlDoc.PreserveWhitespace = $true $script:xmlDoc.PreserveWhitespace = $true
$script:xmlDoc.Load($resolvedPath) $script:xmlDoc.Load($resolvedPath)
@@ -691,7 +693,9 @@ $bodyBlock$declarations
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null } if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
$caiPath = Join-Path $extDir "ClientApplicationInterface.xml" $caiPath = Join-Path $extDir "ClientApplicationInterface.xml"
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($caiPath, $caiXml, $utf8Bom) # Файл создаём мы — канон: CRLF в разделителях, без перевода строки в конце.
$caiXml = ($caiXml -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($caiPath, $caiXml.TrimEnd("`r", "`n"), $utf8Bom)
$script:modifyCount++ $script:modifyCount++
Info "Wrote panel layout: $caiPath" Info "Wrote panel layout: $caiPath"
} }
@@ -880,7 +884,9 @@ $rightXml
if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null } if (-not (Test-Path $extDir)) { New-Item -ItemType Directory -Path $extDir -Force | Out-Null }
$hpPath = Join-Path $extDir "HomePageWorkArea.xml" $hpPath = Join-Path $extDir "HomePageWorkArea.xml"
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($hpPath, $hpXml, $utf8Bom) # Файл создаём мы — канон: CRLF в разделителях, без перевода строки в конце.
$hpXml = ($hpXml -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($hpPath, $hpXml.TrimEnd("`r", "`n"), $utf8Bom)
$script:modifyCount++ $script:modifyCount++
Info "Wrote home page layout: $hpPath" Info "Wrote home page layout: $hpPath"
} }
@@ -982,6 +988,14 @@ $memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes) $text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
+8 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cf-edit v1.11 — Edit 1C configuration root (Configuration.xml) # cf-edit v1.16 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -341,21 +341,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
+56 -15
View File
@@ -1,4 +1,4 @@
# cf-init v1.4 — Create empty 1C configuration scaffold # cf-init v1.8 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -43,6 +43,9 @@ $co6 = [guid]::NewGuid().ToString()
$co7 = [guid]::NewGuid().ToString() $co7 = [guid]::NewGuid().ToString()
# --- Mobile functionalities --- # --- Mobile functionalities ---
# Версия формата как число — по ней ниже включаются вставки 2.21.
$is221 = (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221)
$mobileFuncs = @( $mobileFuncs = @(
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"), @("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
@("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"), @("BluetoothPrinters","false"), @("WiFiPrinters","false"), @("Contacts","false"),
@@ -59,6 +62,9 @@ $mobileFuncs = @(
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"), @("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false") @("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
) )
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
# последней в списке. На младших форматах платформа её не пишет.
if ($is221) { $mobileFuncs += ,@("TextToSpeech","false") }
$mobileXml = "" $mobileXml = ""
foreach ($mf in $mobileFuncs) { foreach ($mf in $mobileFuncs) {
@@ -72,13 +78,39 @@ if ($Synonym) {
} }
# --- Optional properties --- # --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" } # Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" } # пишет <Vendor/>, а не <Vendor></Vendor>.
$vendorEl = if ($Vendor) { "<Vendor>$([System.Security.SecurityElement]::Escape($Vendor))</Vendor>" } else { "<Vendor/>" }
$versionEl = if ($Version) { "<Version>$([System.Security.SecurityElement]::Escape($Version))</Version>" } else { "<Version/>" }
# --- Свойства и пространство имён формата 2.21 (платформа 8.5) ---
# Значения и ПОЗИЦИИ сняты с выгрузки 8.5.1 (debug/fmt221/dump_v851): те же исходники,
# выгруженные с 8.3.27 и с 8.5.1, различаются ровно этим. Порядок важен — вставки идут
# на своё место, а не в конец.
$nl = "`r`n"
$f221AuxForms = ""; $f221WindowVariant = ""; $f221OpenVariant = ""; $f221Captions = ""; $f221Migration = ""
$palNs = ""
if ($is221) {
$palNs = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
# Скобки вокруг -join обязательны: без них `$nl + (массив) -join $nl` разбирается как
# `($nl + массив) -join $nl`, массив склеивается пробелами и все теги уезжают в одну строку.
$f221AuxForms = $nl + ((@(
"<AuxiliaryReportForm/>", "<AuxiliaryReportVariantForm/>", "<AuxiliaryReportSettingsForm/>",
"<AuxiliaryDynamicListSettingsForm/>", "<AuxiliaryDataHistoryChangeHistoryForm/>",
"<AuxiliaryDataHistoryVersionDataForm/>", "<AuxiliaryDataHistoryVersionDifferencesForm/>",
"<AuxiliaryCollaborationSystemUsersChoiceForm/>"
) | ForEach-Object { "`t`t`t$_" }) -join $nl)
$f221WindowVariant = $nl + "`t`t`t<MainClientApplicationWindowInterfaceVariant>NavigationLeft</MainClientApplicationWindowInterfaceVariant>" +
$nl + "`t`t`t<ClientApplicationTheme>Auto</ClientApplicationTheme>"
$f221OpenVariant = $nl + "`t`t`t<ClientApplicationWindowsOpenVariant>OpenDataInDialogs</ClientApplicationWindowsOpenVariant>"
$f221Captions = $nl + "`t`t`t<Caption/>" + $nl + "`t`t`t<ShortCaption/>"
$f221Migration = $nl + "`t`t`t<Version85InterfaceMigrationMode>DontUse</Version85InterfaceMigrationMode>"
}
# --- Configuration.xml --- # --- Configuration.xml ---
$cfgXml = @" $cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$FormatVersion"> <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"$palNs 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"> <Configuration uuid="$uuidCfg">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
@@ -122,8 +154,8 @@ $cfgXml = @"
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/> <DefaultRoles/>
<Vendor>$vendorXml</Vendor> $vendorEl
<Version>$versionXml</Version> $versionEl
<UpdateCatalogAddress/> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
@@ -145,15 +177,15 @@ $cfgXml = @"
<DefaultDataHistoryChangeHistoryForm/> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm/> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm/> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm/> <DefaultCollaborationSystemUsersChoiceForm/>$f221AuxForms
<RequiredMobileApplicationPermissions/> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities>$mobileXml <UsedMobileApplicationFunctionalities>$mobileXml
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles/> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs/> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes/> <AllowedIncomingShareRequestTypes/>$f221WindowVariant
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>$f221OpenVariant
<DefaultInterface/> <DefaultInterface/>$f221Captions
<DefaultStyle/> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/> <BriefInformation/>
@@ -165,7 +197,7 @@ $cfgXml = @"
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode> <SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>$f221Migration
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>$CompatibilityMode</CompatibilityMode> <CompatibilityMode>$CompatibilityMode</CompatibilityMode>
<DefaultConstantsForm/> <DefaultConstantsForm/>
@@ -180,7 +212,7 @@ $cfgXml = @"
# --- Languages/Русский.xml --- # --- Languages/Русский.xml ---
$langXml = @" $langXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$FormatVersion"> <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"$palNs 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"> <Language uuid="$uuidLang">
<Properties> <Properties>
<Name>Русский</Name> <Name>Русский</Name>
@@ -240,11 +272,20 @@ if (-not (Test-Path $extDir)) {
# --- Write files with UTF-8 BOM --- # --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile $cfgFile $cfgXml $enc
$langFile = Join-Path $langDir "Русский.xml" $langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc) Write-XmlFile $langFile $langXml $enc
$caiFile = Join-Path $extDir "ClientApplicationInterface.xml" $caiFile = Join-Path $extDir "ClientApplicationInterface.xml"
[System.IO.File]::WriteAllText($caiFile, $caiXml, $enc) Write-XmlFile $caiFile $caiXml $enc
# --- Output --- # --- Output ---
Write-Host "[OK] Создана конфигурация: $Name" Write-Host "[OK] Создана конфигурация: $Name"
+58 -16
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cf-init v1.4 — Create empty 1C configuration scaffold # cf-init v1.8 — Create empty 1C configuration scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C configuration.""" """Generates minimal XML source files for a 1C configuration."""
import sys, os, argparse, uuid import sys, os, argparse, re, uuid
def esc_xml(s): def esc_xml(s):
return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;') return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')
@@ -14,6 +14,16 @@ def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f: with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content) f.write(content)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_utf8_bom(path, text)
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -54,6 +64,10 @@ def main():
co = [new_uuid() for _ in range(7)] co = [new_uuid() for _ in range(7)]
# --- Mobile functionalities --- # --- Mobile functionalities ---
# Версия формата как число — по ней ниже включаются вставки 2.21.
_fm = re.match(r'^(\d+)\.(\d+)$', args.FormatVersion)
is_221 = bool(_fm) and int(_fm.group(1)) * 100 + int(_fm.group(2)) >= 221
mobile_funcs = [ mobile_funcs = [
("Biometrics","true"), ("Location","false"), ("BackgroundLocation","false"), ("Biometrics","true"), ("Location","false"), ("BackgroundLocation","false"),
("BluetoothPrinters","false"), ("WiFiPrinters","false"), ("Contacts","false"), ("BluetoothPrinters","false"), ("WiFiPrinters","false"), ("Contacts","false"),
@@ -70,6 +84,10 @@ def main():
("DocumentScanning","false"), ("SpeechToText","false"), ("Geofences","false"), ("DocumentScanning","false"), ("SpeechToText","false"), ("Geofences","false"),
("IncomingShareRequests","false"), ("AllIncomingShareRequestsTypesProcessing","false"), ("IncomingShareRequests","false"), ("AllIncomingShareRequestsTypesProcessing","false"),
] ]
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
# последней в списке. На младших форматах платформа её не пишет.
if is_221:
mobile_funcs.append(("TextToSpeech", "false"))
mobile_xml = "" mobile_xml = ""
for func_name, func_use in mobile_funcs: for func_name, func_use in mobile_funcs:
@@ -80,8 +98,10 @@ def main():
if synonym: if synonym:
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t" synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
vendor_xml = esc_xml(vendor) if vendor else "" # Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
version_xml = esc_xml(version) if version else "" # пишет <Vendor/>, а не <Vendor></Vendor>.
vendor_el = f"<Vendor>{esc_xml(vendor)}</Vendor>" if vendor else "<Vendor/>"
version_el = f"<Version>{esc_xml(version)}</Version>" if version else "<Version/>"
class_ids = [ class_ids = [
"9cd510cd-abfc-11d4-9434-004095e12fc7", "9cd510cd-abfc-11d4-9434-004095e12fc7",
@@ -93,6 +113,28 @@ def main():
"fb282519-d103-4dd3-bc12-cb271d631dfc", "fb282519-d103-4dd3-bc12-cb271d631dfc",
] ]
# Свойства и пространство имён формата 2.21 (платформа 8.5). Значения и ПОЗИЦИИ сняты
# с выгрузки 8.5.1 (debug/fmt221/dump_v851): те же исходники, выгруженные с 8.3.27 и
# с 8.5.1, различаются ровно этим. Порядок важен — вставки идут на своё место.
pal_ns = ""
f221_aux_forms = f221_window_variant = f221_open_variant = f221_captions = f221_migration = ""
if is_221:
pal_ns = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
f221_aux_forms = "\r\n" + "\r\n".join(
f"\t\t\t{t}" for t in (
"<AuxiliaryReportForm/>", "<AuxiliaryReportVariantForm/>", "<AuxiliaryReportSettingsForm/>",
"<AuxiliaryDynamicListSettingsForm/>", "<AuxiliaryDataHistoryChangeHistoryForm/>",
"<AuxiliaryDataHistoryVersionDataForm/>", "<AuxiliaryDataHistoryVersionDifferencesForm/>",
"<AuxiliaryCollaborationSystemUsersChoiceForm/>"))
f221_window_variant = ("\r\n\t\t\t<MainClientApplicationWindowInterfaceVariant>NavigationLeft"
"</MainClientApplicationWindowInterfaceVariant>"
"\r\n\t\t\t<ClientApplicationTheme>Auto</ClientApplicationTheme>")
f221_open_variant = ("\r\n\t\t\t<ClientApplicationWindowsOpenVariant>OpenDataInDialogs"
"</ClientApplicationWindowsOpenVariant>")
f221_captions = "\r\n\t\t\t<Caption/>\r\n\t\t\t<ShortCaption/>"
f221_migration = ("\r\n\t\t\t<Version85InterfaceMigrationMode>DontUse"
"</Version85InterfaceMigrationMode>")
contained_objects = "" contained_objects = ""
for i in range(7): for i in range(7):
contained_objects += f"""\t\t\t<xr:ContainedObject> contained_objects += f"""\t\t\t<xr:ContainedObject>
@@ -101,7 +143,7 @@ def main():
\t\t\t</xr:ContainedObject>\n""" \t\t\t</xr:ContainedObject>\n"""
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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="{args.FormatVersion}"> <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"{pal_ns} 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<Configuration uuid="{uuid_cfg}">
\t\t<InternalInfo> \t\t<InternalInfo>
{contained_objects}\t\t</InternalInfo> {contained_objects}\t\t</InternalInfo>
@@ -117,8 +159,8 @@ def main():
\t\t\t</UsePurposes> \t\t\t</UsePurposes>
\t\t\t<ScriptVariant>Russian</ScriptVariant> \t\t\t<ScriptVariant>Russian</ScriptVariant>
\t\t\t<DefaultRoles/> \t\t\t<DefaultRoles/>
\t\t\t<Vendor>{vendor_xml}</Vendor> \t\t\t{vendor_el}
\t\t\t<Version>{version_xml}</Version> \t\t\t{version_el}
\t\t\t<UpdateCatalogAddress/> \t\t\t<UpdateCatalogAddress/>
\t\t\t<IncludeHelpInContents>false</IncludeHelpInContents> \t\t\t<IncludeHelpInContents>false</IncludeHelpInContents>
\t\t\t<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> \t\t\t<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
@@ -140,15 +182,15 @@ def main():
\t\t\t<DefaultDataHistoryChangeHistoryForm/> \t\t\t<DefaultDataHistoryChangeHistoryForm/>
\t\t\t<DefaultDataHistoryVersionDataForm/> \t\t\t<DefaultDataHistoryVersionDataForm/>
\t\t\t<DefaultDataHistoryVersionDifferencesForm/> \t\t\t<DefaultDataHistoryVersionDifferencesForm/>
\t\t\t<DefaultCollaborationSystemUsersChoiceForm/> \t\t\t<DefaultCollaborationSystemUsersChoiceForm/>{f221_aux_forms}
\t\t\t<RequiredMobileApplicationPermissions/> \t\t\t<RequiredMobileApplicationPermissions/>
\t\t\t<UsedMobileApplicationFunctionalities>{mobile_xml} \t\t\t<UsedMobileApplicationFunctionalities>{mobile_xml}
\t\t\t</UsedMobileApplicationFunctionalities> \t\t\t</UsedMobileApplicationFunctionalities>
\t\t\t<StandaloneConfigurationRestrictionRoles/> \t\t\t<StandaloneConfigurationRestrictionRoles/>
\t\t\t<MobileApplicationURLs/> \t\t\t<MobileApplicationURLs/>
\t\t\t<AllowedIncomingShareRequestTypes/> \t\t\t<AllowedIncomingShareRequestTypes/>{f221_window_variant}
\t\t\t<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> \t\t\t<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>{f221_open_variant}
\t\t\t<DefaultInterface/> \t\t\t<DefaultInterface/>{f221_captions}
\t\t\t<DefaultStyle/> \t\t\t<DefaultStyle/>
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage> \t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
\t\t\t<BriefInformation/> \t\t\t<BriefInformation/>
@@ -160,7 +202,7 @@ def main():
\t\t\t<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> \t\t\t<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
\t\t\t<ModalityUseMode>DontUse</ModalityUseMode> \t\t\t<ModalityUseMode>DontUse</ModalityUseMode>
\t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode> \t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
\t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> \t\t\t<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>{f221_migration}
\t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> \t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
\t\t\t<CompatibilityMode>{compat}</CompatibilityMode> \t\t\t<CompatibilityMode>{compat}</CompatibilityMode>
\t\t\t<DefaultConstantsForm/> \t\t\t<DefaultConstantsForm/>
@@ -173,7 +215,7 @@ def main():
# --- Languages/Русский.xml --- # --- Languages/Русский.xml ---
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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="{args.FormatVersion}"> <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"{pal_ns} 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<Language uuid="{uuid_lang}">
\t\t<Properties> \t\t<Properties>
\t\t\t<Name>Русский</Name> \t\t\t<Name>Русский</Name>
@@ -222,11 +264,11 @@ def main():
os.makedirs(ext_dir, exist_ok=True) os.makedirs(ext_dir, exist_ok=True)
# --- Write files --- # --- Write files ---
write_utf8_bom(cfg_file, cfg_xml) write_xml_file(cfg_file, cfg_xml)
lang_file = os.path.join(lang_dir, "Русский.xml") lang_file = os.path.join(lang_dir, "Русский.xml")
write_utf8_bom(lang_file, lang_xml) write_xml_file(lang_file, lang_xml)
cai_file = os.path.join(ext_dir, "ClientApplicationInterface.xml") cai_file = os.path.join(ext_dir, "ClientApplicationInterface.xml")
write_utf8_bom(cai_file, cai_xml) write_xml_file(cai_file, cai_xml)
print(f"[OK] Создана конфигурация: {name}") print(f"[OK] Создана конфигурация: {name}")
print(f" Каталог: {output_dir}") print(f" Каталог: {output_dir}")
@@ -1,4 +1,4 @@
# cfe-borrow v1.11 — Borrow objects from configuration into extension (CFE) # cfe-borrow v1.16 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][string]$ExtensionPath, [Parameter(Mandatory)][string]$ExtensionPath,
@@ -388,6 +388,20 @@ $script:formatVersion = Detect-FormatVersion $extDir
# --- 8. Namespaces declaration for object XML --- # --- 8. Namespaces declaration for object XML ---
$script:xmlnsDecl = '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"' $script:xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
# --- 9. Parse -Object into items --- # --- 9. Parse -Object into items ---
$items = @() $items = @()
foreach ($part in $Object.Split(";;")) { foreach ($part in $Object.Split(";;")) {
@@ -824,11 +838,22 @@ function Borrow-Form {
} }
} }
# Extract the <Form ...> opening tag from source text (preserves namespace declarations) # Открывающий тег <Form ...> берём из исходной формы — ради её объявлений пространств имён,
# но version подставляем СВОЮ: форма обязана нести версию расширения, иначе платформа
# отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег копировался целиком,
# и версия источника молча побеждала.
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>' $xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
$formTag = "<Form version=`"${formVersion}`">" $formTag = "<Form version=`"${formVersion}`">"
if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] } if ($srcFormContent -match '(?s)^(<\?xml[^?]*\?>)') { $xmlDecl = $Matches[1] }
if ($srcFormContent -match '(<Form[^>]*>)') { $formTag = $Matches[1] } if ($srcFormContent -match '(<Form[^>]*>)') {
$srcTag = $Matches[1]
$srcNs = $srcTag -replace '^<Form\s*', '' -replace '\s*/?>$', '' -replace '\s*version="[^"]*"', ''
# 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style.
if ((Get-FormatRank $formVersion) -ge 221 -and $srcNs -notmatch 'xmlns:pal=') {
$srcNs = $srcNs -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
$formTag = if ($srcNs) { "<Form $srcNs version=`"${formVersion}`">" } else { "<Form version=`"${formVersion}`">" }
}
# Build output Form.xml # Build output Form.xml
$formXmlSb = New-Object System.Text.StringBuilder $formXmlSb = New-Object System.Text.StringBuilder
@@ -917,7 +942,15 @@ function Borrow-Form {
New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null New-Item -ItemType Directory -Path $formXmlDir -Force | Out-Null
} }
$formXmlFile = Join-Path $formXmlDir "Form.xml" $formXmlFile = Join-Path $formXmlDir "Form.xml"
[System.IO.File]::WriteAllText($formXmlFile, $formXmlSb.ToString(), $enc) # Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
# Здесь источник не XmlWriter, а OuterXml исходного документа — спацовывает так же.
$formXmlText = $formXmlSb.ToString()
$formXmlText = [regex]::Replace($formXmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Файл создаём мы — канон выгрузки: CRLF в разделителях строк.
$formXmlText = ($formXmlText -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($formXmlFile, $formXmlText, $enc)
Info " Created: $formXmlFile" Info " Created: $formXmlFile"
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must # 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
@@ -1023,8 +1056,16 @@ function Register-FormInObject {
$text2 = [System.Text.Encoding]::UTF8.GetString($bytes2) $text2 = [System.Text.Encoding]::UTF8.GetString($bytes2)
if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) } if ($text2.Length -gt 0 -and $text2[0] -eq [char]0xFEFF) { $text2 = $text2.Substring(1) }
$text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text2 = $text2.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text2 = [regex]::Replace($text2, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$utf8Bom2 = New-Object System.Text.UTF8Encoding($true) $utf8Bom2 = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text2 = ($text2 -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2) [System.IO.File]::WriteAllText($objFile, $text2, $utf8Bom2)
Info " Registered form in: $objFile" Info " Registered form in: $objFile"
} }
@@ -1413,7 +1454,17 @@ function Merge-AttributesIntoObject {
# Insert attributes before </ChildObjects> # Insert attributes before </ChildObjects>
$text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>" $text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>"
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
# Стоит ПОСЛЕ вставки реквизитов, чтобы накрыть и их.
$text3 = [regex]::Replace($text3, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$utf8Bom3 = New-Object System.Text.UTF8Encoding($true) $utf8Bom3 = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $objFile) -and ([System.IO.File]::ReadAllText($objFile) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text3 = ($text3 -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3) [System.IO.File]::WriteAllText($objFile, $text3, $utf8Bom3)
Info " Merged $added attribute(s) into: $objFile" Info " Merged $added attribute(s) into: $objFile"
} }
@@ -1877,8 +1928,16 @@ $memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes) $text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $extResolvedPath) -and ([System.IO.File]::ReadAllText($extResolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($extResolvedPath, $text, $utf8Bom)
Info "Saved: $extResolvedPath" Info "Saved: $extResolvedPath"
+75 -22
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-borrow v1.11 — Borrow objects from configuration into extension (CFE) # cfe-borrow v1.16 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -323,6 +323,23 @@ def detect_format_version(d):
return "2.17" return "2.17"
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
def apply_pal_ns(format_version):
"""2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
дописать в конец нельзя."""
global XMLNS_DECL
if format_rank(format_version) >= 221:
XMLNS_DECL = XMLNS_DECL.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
def get_child_indent(container): def get_child_indent(container):
if container.text and "\n" in container.text: if container.text and "\n" in container.text:
after_nl = container.text.rsplit("\n", 1)[-1] after_nl = container.text.rsplit("\n", 1)[-1]
@@ -389,21 +406,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -419,10 +437,29 @@ def save_xml_bom(tree, path):
def save_text_bom(path, text): def save_text_bom(path, text):
with open(path, "w", encoding="utf-8-sig") as fh: """Записать текст как есть, ничего не нормализуя.
Для файлов, которые мы ПРАВИМ: переводы строк уже пришли из самого файла, и
менять их нельзя (контракт #44/#46/#47). newline="" обязателен — без него
текстовый режим Python дал бы CRLF на Windows и LF на macOS, то есть вывод
навыка зависел бы от ОС.
"""
with open(path, "w", encoding="utf-8-sig", newline="") as fh:
fh.write(text) fh.write(text)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
Только для файлов, которые СОЗДАЁМ: правка существующего наследует его стиль.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
save_text_bom(path, text)
def new_guid(): def new_guid():
return str(uuid.uuid4()) return str(uuid.uuid4())
@@ -471,6 +508,7 @@ def main():
cfg_dir = os.path.dirname(cfg_resolved) cfg_dir = os.path.dirname(cfg_resolved)
format_version = detect_format_version(ext_dir) format_version = detect_format_version(ext_dir)
apply_pal_ns(format_version)
# --- 2. Load extension Configuration.xml --- # --- 2. Load extension Configuration.xml ---
xml_parser = etree.XMLParser(remove_blank_text=False) xml_parser = etree.XMLParser(remove_blank_text=False)
@@ -997,7 +1035,9 @@ def main():
warn(f"Cannot merge attributes: {obj_file} not found") warn(f"Cannot merge attributes: {obj_file} not found")
return return
with open(obj_file, "r", encoding="utf-8-sig") as fh: # newline="" => без трансляции: иначе CRLF молча схлопнется в LF при чтении
# и файл будет переписан в LF.
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
obj_content = fh.read() obj_content = fh.read()
# Collect existing attribute names for dedup (text-based) # Collect existing attribute names for dedup (text-based)
@@ -1056,7 +1096,9 @@ def main():
obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml") obj_file = os.path.join(ext_dir, dir_name, f"{obj_name}.xml")
# Read existing object XML (needed for dedup + enrichment) # Read existing object XML (needed for dedup + enrichment)
with open(obj_file, "r", encoding="utf-8-sig") as fh: # newline="" => без трансляции: иначе CRLF молча схлопнется в LF при чтении
# и файл будет переписан в LF.
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
obj_content = fh.read() obj_content = fh.read()
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow) # Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
@@ -1133,7 +1175,7 @@ def main():
target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]]) target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[rt["TypeName"]])
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{rt['ObjName']}.xml") target_file = os.path.join(target_dir, f"{rt['ObjName']}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
add_to_child_objects(rt["TypeName"], rt["ObjName"]) add_to_child_objects(rt["TypeName"], rt["ObjName"])
borrowed_files.append(target_file) borrowed_files.append(target_file)
info(f" Auto-borrowed: {rt['TypeName']}.{rt['ObjName']}") info(f" Auto-borrowed: {rt['TypeName']}.{rt['ObjName']}")
@@ -1198,7 +1240,7 @@ def main():
t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name]) t_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[target_type_name])
os.makedirs(t_target_dir, exist_ok=True) os.makedirs(t_target_dir, exist_ok=True)
t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml") t_target_file = os.path.join(t_target_dir, f"{target_obj_name}.xml")
save_text_bom(t_target_file, t_borrowed_xml) write_xml_file(t_target_file, t_borrowed_xml)
add_to_child_objects(target_type_name, target_obj_name) add_to_child_objects(target_type_name, target_obj_name)
borrowed_files.append(t_target_file) borrowed_files.append(t_target_file)
info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}") info(f" Auto-borrowed for deep path: {target_type_name}.{target_obj_name}")
@@ -1222,7 +1264,7 @@ def main():
s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]]) s_target_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[srt["TypeName"]])
os.makedirs(s_target_dir, exist_ok=True) os.makedirs(s_target_dir, exist_ok=True)
s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml") s_target_file = os.path.join(s_target_dir, f"{srt['ObjName']}.xml")
save_text_bom(s_target_file, s_borrowed_xml) write_xml_file(s_target_file, s_borrowed_xml)
add_to_child_objects(srt["TypeName"], srt["ObjName"]) add_to_child_objects(srt["TypeName"], srt["ObjName"])
borrowed_files.append(s_target_file) borrowed_files.append(s_target_file)
info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}") info(f" Auto-borrowed (deep): {srt['TypeName']}.{srt['ObjName']}")
@@ -1279,7 +1321,7 @@ def main():
os.makedirs(form_meta_dir, exist_ok=True) os.makedirs(form_meta_dir, exist_ok=True)
form_meta_file = os.path.join(form_meta_dir, f"{form_name}.xml") form_meta_file = os.path.join(form_meta_dir, f"{form_name}.xml")
save_text_bom(form_meta_file, "\n".join(form_meta_lines)) write_xml_file(form_meta_file, "\n".join(form_meta_lines))
info(f" Created: {form_meta_file}") info(f" Created: {form_meta_file}")
# 5. Generate Form.xml with BaseForm # 5. Generate Form.xml with BaseForm
@@ -1365,7 +1407,7 @@ def main():
target_dir = os.path.join(ext_dir, "CommonPictures") target_dir = os.path.join(ext_dir, "CommonPictures")
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{pic_name}.xml") target_file = os.path.join(target_dir, f"{pic_name}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
add_to_child_objects("CommonPicture", pic_name) add_to_child_objects("CommonPicture", pic_name)
auto_borrowed_pics.append(pic_name) auto_borrowed_pics.append(pic_name)
borrowed_files.append(target_file) borrowed_files.append(target_file)
@@ -1414,7 +1456,7 @@ def main():
target_dir = os.path.join(ext_dir, "StyleItems") target_dir = os.path.join(ext_dir, "StyleItems")
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{style_name}.xml") target_file = os.path.join(target_dir, f"{style_name}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
add_to_child_objects("StyleItem", style_name) add_to_child_objects("StyleItem", style_name)
borrowed_files.append(target_file) borrowed_files.append(target_file)
info(f" Auto-borrowed: StyleItem.{style_name}") info(f" Auto-borrowed: StyleItem.{style_name}")
@@ -1478,14 +1520,17 @@ def main():
target_dir = os.path.join(ext_dir, "Enums") target_dir = os.path.join(ext_dir, "Enums")
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{enum_name}.xml") target_file = os.path.join(target_dir, f"{enum_name}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
add_to_child_objects("Enum", enum_name) add_to_child_objects("Enum", enum_name)
borrowed_files.append(target_file) borrowed_files.append(target_file)
info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))") info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))")
else: else:
warn(f" Enum.{enum_name} not found in source config") warn(f" Enum.{enum_name} not found in source config")
# Extract the <Form ...> opening tag from source text # Открывающий тег <Form ...> берём из исходной формы — ради её объявлений пространств
# имён, но version подставляем СВОЮ: форма обязана нести версию расширения, иначе
# платформа отвергает импорт (форма 2.13 внутри расширения 2.17). Раньше тег
# копировался целиком, и версия источника молча побеждала.
xml_decl = '<?xml version="1.0" encoding="UTF-8"?>' xml_decl = '<?xml version="1.0" encoding="UTF-8"?>'
form_tag = f'<Form version="{form_version}">' form_tag = f'<Form version="{form_version}">'
m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content) m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content)
@@ -1493,7 +1538,15 @@ def main():
xml_decl = m_decl.group(1) xml_decl = m_decl.group(1)
m_tag = re.search(r'(<Form[^>]*>)', src_form_content) m_tag = re.search(r'(<Form[^>]*>)', src_form_content)
if m_tag: if m_tag:
form_tag = m_tag.group(1) src_ns = re.sub(r'^<Form\s*', '', m_tag.group(1))
src_ns = re.sub(r'\s*/?>$', '', src_ns)
src_ns = re.sub(r'\s*version="[^"]*"', '', src_ns)
# 2.21 (8.5): пространство палитры. Место строгое — после lf, перед style.
if format_rank(form_version) >= 221 and 'xmlns:pal=' not in src_ns:
src_ns = src_ns.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
form_tag = f'<Form {src_ns} version="{form_version}">' if src_ns else f'<Form version="{form_version}">'
# Build output # Build output
parts = [] parts = []
@@ -1572,7 +1625,7 @@ def main():
form_xml_dir = os.path.join(form_meta_dir, form_name, "Ext") form_xml_dir = os.path.join(form_meta_dir, form_name, "Ext")
os.makedirs(form_xml_dir, exist_ok=True) os.makedirs(form_xml_dir, exist_ok=True)
form_xml_file = os.path.join(form_xml_dir, "Form.xml") form_xml_file = os.path.join(form_xml_dir, "Form.xml")
save_text_bom(form_xml_file, "".join(parts)) write_xml_file(form_xml_file, "".join(parts))
info(f" Created: {form_xml_file}") info(f" Created: {form_xml_file}")
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must # 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
@@ -1656,7 +1709,7 @@ def main():
target_dir = os.path.join(ext_dir, dir_name) target_dir = os.path.join(ext_dir, dir_name)
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{obj_name}.xml") target_file = os.path.join(target_dir, f"{obj_name}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
info(f" Created: {target_file}") info(f" Created: {target_file}")
add_to_child_objects(type_name, obj_name) add_to_child_objects(type_name, obj_name)
@@ -1683,7 +1736,7 @@ def main():
os.makedirs(target_dir, exist_ok=True) os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{obj_name}.xml") target_file = os.path.join(target_dir, f"{obj_name}.xml")
save_text_bom(target_file, borrowed_xml) write_xml_file(target_file, borrowed_xml)
info(f" Created: {target_file}") info(f" Created: {target_file}")
add_to_child_objects(type_name, obj_name) add_to_child_objects(type_name, obj_name)
+48 -14
View File
@@ -1,4 +1,4 @@
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE) # cfe-init v1.7 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -125,16 +125,19 @@ if ($Synonym) {
} }
# --- Optional properties --- # --- Optional properties ---
$vendorXml = if ($Vendor) { [System.Security.SecurityElement]::Escape($Vendor) } else { "" } # Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
$versionXml = if ($Version) { [System.Security.SecurityElement]::Escape($Version) } else { "" } # пишет <Vendor/>, а не <Vendor></Vendor>.
$vendorEl = if ($Vendor) { "<Vendor>$([System.Security.SecurityElement]::Escape($Vendor))</Vendor>" } else { "<Vendor/>" }
$versionEl = if ($Version) { "<Version>$([System.Security.SecurityElement]::Escape($Version))</Version>" } else { "<Version/>" }
# --- Role name --- # --- Role name ---
$roleName = "${NamePrefix}ОсновнаяРоль" $roleName = "${NamePrefix}ОсновнаяРоль"
# --- DefaultRoles XML --- # --- DefaultRoles XML ---
$defaultRolesXml = "" # Элемент целиком: без роли Конфигуратор пишет <DefaultRoles/>, а не пустую пару.
$defaultRolesEl = "<DefaultRoles/>"
if (-not $NoRole) { if (-not $NoRole) {
$defaultRolesXml = "`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t" $defaultRolesEl = "<DefaultRoles>`r`n`t`t`t`t<xr:Item xsi:type=`"xr:MDObjectRef`">Role.$roleName</xr:Item>`r`n`t`t`t</DefaultRoles>"
} }
# --- ChildObjects --- # --- ChildObjects ---
@@ -144,10 +147,32 @@ if (-not $NoRole) {
} }
$childObjectsXml += "`r`n`t`t" $childObjectsXml += "`r`n`t`t"
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
# Caption/ShortCaption — свойства корня из того же формата 2.21, между Version и
# DefaultLanguage (позиция снята с выгрузки расширения из базы 8.5).
$f221Captions = ""
if ((Get-FormatRank $formatVersion) -ge 221) {
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
$f221Captions = "`r`n`t`t`t<Caption/>`r`n`t`t`t<ShortCaption/>"
}
# --- Configuration.xml --- # --- Configuration.xml ---
$cfgXml = @" $cfgXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$formatVersion"> <MetaDataObject $xmlnsDecl version="$formatVersion">
<Configuration uuid="$uuidCfg"> <Configuration uuid="$uuidCfg">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
@@ -193,9 +218,9 @@ $cfgXml = @"
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles>$defaultRolesXml</DefaultRoles> $defaultRolesEl
<Vendor>$vendorXml</Vendor> $vendorEl
<Version>$versionXml</Version> $versionEl$f221Captions
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation/> <BriefInformation/>
<DetailedInformation/> <DetailedInformation/>
@@ -212,7 +237,7 @@ $cfgXml = @"
# --- Languages/Русский.xml (adopted format) --- # --- Languages/Русский.xml (adopted format) ---
$langXml = @" $langXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$formatVersion"> <MetaDataObject $xmlnsDecl version="$formatVersion">
<Language uuid="$uuidLang"> <Language uuid="$uuidLang">
<InternalInfo/> <InternalInfo/>
<Properties> <Properties>
@@ -229,7 +254,7 @@ $langXml = @"
# --- Role XML --- # --- Role XML ---
$roleXml = @" $roleXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$formatVersion"> <MetaDataObject $xmlnsDecl version="$formatVersion">
<Role uuid="$uuidRole"> <Role uuid="$uuidRole">
<Properties> <Properties>
<Name>$([System.Security.SecurityElement]::Escape($roleName))</Name> <Name>$([System.Security.SecurityElement]::Escape($roleName))</Name>
@@ -252,9 +277,18 @@ if (-not (Test-Path $langDir)) {
# --- Write files with UTF-8 BOM --- # --- Write files with UTF-8 BOM ---
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($cfgFile, $cfgXml, $enc) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile $cfgFile $cfgXml $enc
$langFile = Join-Path $langDir "Русский.xml" $langFile = Join-Path $langDir "Русский.xml"
[System.IO.File]::WriteAllText($langFile, $langXml, $enc) Write-XmlFile $langFile $langXml $enc
# --- Role --- # --- Role ---
if (-not $NoRole) { if (-not $NoRole) {
@@ -263,7 +297,7 @@ if (-not $NoRole) {
New-Item -ItemType Directory -Path $roleDir -Force | Out-Null New-Item -ItemType Directory -Path $roleDir -Force | Out-Null
} }
$roleFile = Join-Path $roleDir "$roleName.xml" $roleFile = Join-Path $roleDir "$roleName.xml"
[System.IO.File]::WriteAllText($roleFile, $roleXml, $enc) Write-XmlFile $roleFile $roleXml $enc
} }
# --- Output --- # --- Output ---
+70 -15
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# cfe-init v1.2 — Create 1C configuration extension scaffold (CFE) # cfe-init v1.7 — Create 1C configuration extension scaffold (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C configuration extension.""" """Generates minimal XML source files for a 1C configuration extension."""
import sys, os, argparse, uuid import sys, os, re, argparse, uuid
from xml.etree import ElementTree as ET from xml.etree import ElementTree as ET
def esc_xml(s): def esc_xml(s):
@@ -15,6 +15,22 @@ def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f: with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content) f.write(content)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_utf8_bom(path, text)
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
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -128,16 +144,21 @@ def main():
if synonym: if synonym:
synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t" synonym_xml = f"\r\n\t\t\t\t<v8:item>\r\n\t\t\t\t\t<v8:lang>ru</v8:lang>\r\n\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>\r\n\t\t\t\t</v8:item>\r\n\t\t\t"
vendor_xml = esc_xml(vendor) if vendor else "" # Элемент целиком, а не значение внутри пары: при пустом значении Конфигуратор
version_xml = esc_xml(version) if version else "" # пишет <Vendor/>, а не <Vendor></Vendor>.
vendor_el = f"<Vendor>{esc_xml(vendor)}</Vendor>" if vendor else "<Vendor/>"
version_el = f"<Version>{esc_xml(version)}</Version>" if version else "<Version/>"
# --- Role name --- # --- Role name ---
role_name = f"{name_prefix}ОсновнаяРоль" role_name = f"{name_prefix}ОсновнаяРоль"
# --- DefaultRoles XML --- # --- DefaultRoles XML ---
default_roles_xml = "" # Элемент целиком: без роли Конфигуратор пишет <DefaultRoles/>, а не пустую пару.
default_roles_el = "<DefaultRoles/>"
if not args.NoRole: if not args.NoRole:
default_roles_xml = f'\r\n\t\t\t\t<xr:Item xsi:type="xr:MDObjectRef">Role.{role_name}</xr:Item>\r\n\t\t\t' default_roles_el = ('<DefaultRoles>\r\n\t\t\t\t'
f'<xr:Item xsi:type="xr:MDObjectRef">Role.{role_name}</xr:Item>'
'\r\n\t\t\t</DefaultRoles>')
# --- ChildObjects --- # --- ChildObjects ---
child_objects_xml = f"\r\n\t\t\t<Language>Русский</Language>" child_objects_xml = f"\r\n\t\t\t<Language>Русский</Language>"
@@ -156,6 +177,40 @@ def main():
] ]
contained_objects = "" contained_objects = ""
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
xmlns_decl = (
'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"'
)
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
# Caption/ShortCaption — свойства корня из того же формата 2.21, между Version и
# DefaultLanguage (позиция снята с выгрузки расширения из базы 8.5).
f221_captions = ""
if format_rank(format_version) >= 221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
f221_captions = "\r\n\t\t\t<Caption/>\r\n\t\t\t<ShortCaption/>"
for i in range(7): for i in range(7):
contained_objects += f"""\t\t\t<xr:ContainedObject> contained_objects += f"""\t\t\t<xr:ContainedObject>
\t\t\t\t<xr:ClassId>{class_ids[i]}</xr:ClassId> \t\t\t\t<xr:ClassId>{class_ids[i]}</xr:ClassId>
@@ -163,7 +218,7 @@ def main():
\t\t\t</xr:ContainedObject>\n""" \t\t\t</xr:ContainedObject>\n"""
cfg_xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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="{format_version}"> <MetaDataObject {xmlns_decl} version="{format_version}">
\t<Configuration uuid="{uuid_cfg}"> \t<Configuration uuid="{uuid_cfg}">
\t\t<InternalInfo> \t\t<InternalInfo>
{contained_objects}\t\t</InternalInfo> {contained_objects}\t\t</InternalInfo>
@@ -181,9 +236,9 @@ def main():
\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> \t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
\t\t\t</UsePurposes> \t\t\t</UsePurposes>
\t\t\t<ScriptVariant>Russian</ScriptVariant> \t\t\t<ScriptVariant>Russian</ScriptVariant>
\t\t\t<DefaultRoles>{default_roles_xml}</DefaultRoles> \t\t\t{default_roles_el}
\t\t\t<Vendor>{vendor_xml}</Vendor> \t\t\t{vendor_el}
\t\t\t<Version>{version_xml}</Version> \t\t\t{version_el}{f221_captions}
\t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage> \t\t\t<DefaultLanguage>Language.Русский</DefaultLanguage>
\t\t\t<BriefInformation/> \t\t\t<BriefInformation/>
\t\t\t<DetailedInformation/> \t\t\t<DetailedInformation/>
@@ -198,7 +253,7 @@ def main():
# --- Languages/Русский.xml (adopted format) --- # --- Languages/Русский.xml (adopted format) ---
lang_xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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="{format_version}"> <MetaDataObject {xmlns_decl} version="{format_version}">
\t<Language uuid="{uuid_lang}"> \t<Language uuid="{uuid_lang}">
\t\t<InternalInfo/> \t\t<InternalInfo/>
\t\t<Properties> \t\t<Properties>
@@ -213,7 +268,7 @@ def main():
# --- Role XML --- # --- Role XML ---
role_xml = f'''<?xml version="1.0" encoding="UTF-8"?> role_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="{format_version}"> <MetaDataObject {xmlns_decl} version="{format_version}">
\t<Role uuid="{uuid_role}"> \t<Role uuid="{uuid_role}">
\t\t<Properties> \t\t<Properties>
\t\t\t<Name>{esc_xml(role_name)}</Name> \t\t\t<Name>{esc_xml(role_name)}</Name>
@@ -229,9 +284,9 @@ def main():
os.makedirs(lang_dir, exist_ok=True) os.makedirs(lang_dir, exist_ok=True)
# --- Write files --- # --- Write files ---
write_utf8_bom(cfg_file, cfg_xml) write_xml_file(cfg_file, cfg_xml)
lang_file = os.path.join(lang_dir, "Русский.xml") lang_file = os.path.join(lang_dir, "Русский.xml")
write_utf8_bom(lang_file, lang_xml) write_xml_file(lang_file, lang_xml)
# --- Role --- # --- Role ---
role_file = None role_file = None
@@ -239,7 +294,7 @@ def main():
role_dir = os.path.join(output_dir, "Roles") role_dir = os.path.join(output_dir, "Roles")
os.makedirs(role_dir, exist_ok=True) os.makedirs(role_dir, exist_ok=True)
role_file = os.path.join(role_dir, f"{role_name}.xml") role_file = os.path.join(role_dir, f"{role_name}.xml")
write_utf8_bom(role_file, role_xml) write_xml_file(role_file, role_xml)
# --- Output --- # --- Output ---
print(f"[OK] Создано расширение: {name}") print(f"[OK] Создано расширение: {name}")
+8 -7
View File
@@ -18,19 +18,20 @@ allowed-tools:
## Usage ## Usage
``` ```
/epf-init <Name> [Synonym] [SrcDir] /epf-init <Name> [Synonym] [SrcDir] [FormatVersion]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|-------------------------------------| |---------------|:------------:|--------------|------------------------------------------------|
| Name | да | — | Имя обработки (латиница/кириллица) | | Name | да | — | Имя обработки (латиница/кириллица) |
| Synonym | нет | = Name | Синоним (отображаемое имя) | | Synonym | нет | = Name | Синоним (отображаемое имя) |
| SrcDir | нет | `src` | Каталог исходников относительно CWD | | SrcDir | нет | `src` | Каталог исходников относительно CWD |
| FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
+32 -4
View File
@@ -1,4 +1,4 @@
# epf-init v1.1 — Init 1C external data processor scaffold # epf-init v1.4 — Init 1C external data processor scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -6,7 +6,13 @@ param(
[string]$Synonym = $Name, [string]$Synonym = $Name,
[string]$SrcDir = "src" [string]$SrcDir = "src",
# Версия формата выгрузки. Своей конфигурации у автономной обработки нет, наследовать
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри обработки
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
[string]$FormatVersion = "2.17"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -18,9 +24,17 @@ $uuid2 = [guid]::NewGuid().ToString()
$uuid3 = [guid]::NewGuid().ToString() $uuid3 = [guid]::NewGuid().ToString()
$uuid4 = [guid]::NewGuid().ToString() $uuid4 = [guid]::NewGuid().ToString()
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
$xmlnsDecl = '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"'
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
# платформа держит объявления по алфавиту, дописать в конец нельзя.
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
$xml = @" $xml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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 $xmlnsDecl version="$FormatVersion">
<ExternalDataProcessor uuid="$uuid1"> <ExternalDataProcessor uuid="$uuid1">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
@@ -64,7 +78,16 @@ $extDir = Join-Path $processorDir "Ext"
New-Item -ItemType Directory -Path $extDir -Force | Out-Null New-Item -ItemType Directory -Path $extDir -Force | Out-Null
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile (Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml") $xml $enc
# --- Модуль объекта --- # --- Модуль объекта ---
@@ -83,6 +106,11 @@ $moduleBsl = @"
"@ "@
$modulePath = Join-Path $extDir "ObjectModule.bsl" $modulePath = Join-Path $extDir "ObjectModule.bsl"
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
# самого скрипта, а он в репозитории хранится с LF.
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc) [System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
Write-Host "[OK] Создана обработка: $rootFile" Write-Host "[OK] Создана обработка: $rootFile"
+57 -5
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# epf-init v1.1 — Init 1C external data processor scaffold # epf-init v1.4 — Init 1C external data processor scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C external data processor.""" """Generates minimal XML source files for a 1C external data processor."""
import sys, os, argparse, uuid import sys, os, re, argparse, uuid
def esc_xml(s): def esc_xml(s):
return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;') return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')
@@ -14,6 +14,22 @@ def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f: with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content) f.write(content)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_utf8_bom(path, text)
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
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -21,6 +37,11 @@ def main():
parser.add_argument('-Name', dest='Name', required=True) parser.add_argument('-Name', dest='Name', required=True)
parser.add_argument('-Synonym', dest='Synonym', default=None) parser.add_argument('-Synonym', dest='Synonym', default=None)
parser.add_argument('-SrcDir', dest='SrcDir', default='src') parser.add_argument('-SrcDir', dest='SrcDir', default='src')
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
args = parser.parse_args() args = parser.parse_args()
name = args.Name name = args.Name
@@ -32,8 +53,36 @@ def main():
uuid3 = new_uuid() uuid3 = new_uuid()
uuid4 = new_uuid() uuid4 = new_uuid()
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
xmlns_decl = (
'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"'
)
format_version = args.FormatVersion
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
# style): платформа держит объявления по алфавиту, дописать в конец нельзя.
if format_rank(format_version) >= 221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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_decl} version="{format_version}">
\t<ExternalDataProcessor uuid="{uuid1}"> \t<ExternalDataProcessor uuid="{uuid1}">
\t\t<InternalInfo> \t\t<InternalInfo>
\t\t\t<xr:ContainedObject> \t\t\t<xr:ContainedObject>
@@ -72,7 +121,7 @@ def main():
ext_dir = os.path.join(processor_dir, "Ext") ext_dir = os.path.join(processor_dir, "Ext")
os.makedirs(ext_dir, exist_ok=True) os.makedirs(ext_dir, exist_ok=True)
write_utf8_bom(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml) write_xml_file(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
# --- Модуль объекта --- # --- Модуль объекта ---
module_bsl = """\ module_bsl = """\
@@ -89,7 +138,10 @@ def main():
#КонецОбласти""" #КонецОбласти"""
module_path = os.path.join(ext_dir, "ObjectModule.bsl") module_path = os.path.join(ext_dir, "ObjectModule.bsl")
write_utf8_bom(module_path, module_bsl) # Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без).
write_utf8_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
print(f"[OK] Создана обработка: {root_file}") print(f"[OK] Создана обработка: {root_file}")
print(f" Каталог: {processor_dir}") print(f" Каталог: {processor_dir}")
+9 -8
View File
@@ -18,20 +18,21 @@ allowed-tools:
## Usage ## Usage
``` ```
/erf-init <Name> [Synonym] [SrcDir] [--with-skd] /erf-init <Name> [Synonym] [SrcDir] [FormatVersion] [--with-skd]
``` ```
| Параметр | Обязательный | По умолчанию | Описание | | Параметр | Обязательный | По умолчанию | Описание |
|-----------|:------------:|--------------|---------------------------------------| |---------------|:------------:|--------------|---------------------------------------|
| Name | да | — | Имя отчёта (латиница/кириллица) | | Name | да | — | Имя отчёта (латиница/кириллица) |
| Synonym | нет | = Name | Синоним (отображаемое имя) | | Synonym | нет | = Name | Синоним (отображаемое имя) |
| SrcDir | нет | `src` | Каталог исходников относительно CWD | | SrcDir | нет | `src` | Каталог исходников относительно CWD |
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema | | FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
## Команда ## Команда
```powershell ```powershell
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-WithSKD] powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] [-WithSKD]
``` ```
## Дальнейшие шаги ## Дальнейшие шаги
+35 -7
View File
@@ -1,4 +1,4 @@
# erf-init v1.1 — Init 1C external report scaffold # erf-init v1.4 — Init 1C external report scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -8,7 +8,13 @@ param(
[string]$SrcDir = "src", [string]$SrcDir = "src",
[switch]$WithSKD [switch]$WithSKD,
# Версия формата выгрузки. Своей конфигурации у автономного отчёта нет, наследовать
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри отчёта
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
[string]$FormatVersion = "2.17"
) )
$ErrorActionPreference = "Stop" $ErrorActionPreference = "Stop"
@@ -20,6 +26,14 @@ $uuid2 = [guid]::NewGuid().ToString()
$uuid3 = [guid]::NewGuid().ToString() $uuid3 = [guid]::NewGuid().ToString()
$uuid4 = [guid]::NewGuid().ToString() $uuid4 = [guid]::NewGuid().ToString()
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
$xmlnsDecl = '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"'
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
# платформа держит объявления по алфавиту, дописать в конец нельзя.
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
# --- Формируем Properties --- # --- Формируем Properties ---
$mainDCSValue = "" $mainDCSValue = ""
@@ -48,7 +62,7 @@ $childObjectsXml = if ($childObjectsContent) {
$xml = @" $xml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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 $xmlnsDecl version="$FormatVersion">
<ExternalReport uuid="$uuid1"> <ExternalReport uuid="$uuid1">
<InternalInfo> <InternalInfo>
<xr:ContainedObject> <xr:ContainedObject>
@@ -98,7 +112,16 @@ $extDir = Join-Path $reportDir "Ext"
New-Item -ItemType Directory -Path $extDir -Force | Out-Null New-Item -ItemType Directory -Path $extDir -Force | Out-Null
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText((Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml"), $xml, $enc) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile (Resolve-Path $SrcDir | Join-Path -ChildPath "$Name.xml") $xml $enc
# --- Модуль объекта --- # --- Модуль объекта ---
@@ -117,6 +140,11 @@ $moduleBsl = @"
"@ "@
$modulePath = Join-Path $extDir "ObjectModule.bsl" $modulePath = Join-Path $extDir "ObjectModule.bsl"
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
# самого скрипта, а он в репозитории хранится с LF.
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc) [System.IO.File]::WriteAllText($modulePath, $moduleBsl, $enc)
Write-Host "[OK] Создан отчёт: $rootFile" Write-Host "[OK] Создан отчёт: $rootFile"
@@ -136,7 +164,7 @@ if ($WithSKD) {
$skdMetaXml = @" $skdMetaXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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 $xmlnsDecl version="$FormatVersion">
<Template uuid="$skdUuid"> <Template uuid="$skdUuid">
<Properties> <Properties>
<Name>$skdName</Name> <Name>$skdName</Name>
@@ -153,7 +181,7 @@ if ($WithSKD) {
</MetaDataObject> </MetaDataObject>
"@ "@
[System.IO.File]::WriteAllText($skdMetaPath, $skdMetaXml, $enc) Write-XmlFile $skdMetaPath $skdMetaXml $enc
$skdContent = @" $skdContent = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
@@ -173,7 +201,7 @@ if ($WithSKD) {
"@ "@
$skdFilePath = Join-Path $skdExtDir "Template.xml" $skdFilePath = Join-Path $skdExtDir "Template.xml"
[System.IO.File]::WriteAllText($skdFilePath, $skdContent, $enc) Write-XmlFile $skdFilePath $skdContent $enc
Write-Host " СКД: $skdMetaPath" Write-Host " СКД: $skdMetaPath"
Write-Host " Тело: $skdFilePath" Write-Host " Тело: $skdFilePath"
+60 -8
View File
@@ -1,8 +1,8 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# erf-init v1.1 — Init 1C external report scaffold # erf-init v1.4 — Init 1C external report scaffold
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Generates minimal XML source files for a 1C external report.""" """Generates minimal XML source files for a 1C external report."""
import sys, os, argparse, uuid import sys, os, re, argparse, uuid
def esc_xml(s): def esc_xml(s):
return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;') return s.replace('&','&amp;').replace('<','&lt;').replace('>','&gt;').replace('"','&quot;')
@@ -14,6 +14,22 @@ def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f: with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content) f.write(content)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_utf8_bom(path, text)
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
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -21,6 +37,11 @@ def main():
parser.add_argument('-Name', dest='Name', required=True) parser.add_argument('-Name', dest='Name', required=True)
parser.add_argument('-Synonym', dest='Synonym', default=None) parser.add_argument('-Synonym', dest='Synonym', default=None)
parser.add_argument('-SrcDir', dest='SrcDir', default='src') parser.add_argument('-SrcDir', dest='SrcDir', default='src')
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
parser.add_argument('-WithSKD', dest='WithSKD', action='store_true') parser.add_argument('-WithSKD', dest='WithSKD', action='store_true')
args = parser.parse_args() args = parser.parse_args()
@@ -33,6 +54,34 @@ def main():
uuid3 = new_uuid() uuid3 = new_uuid()
uuid4 = new_uuid() uuid4 = new_uuid()
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
xmlns_decl = (
'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"'
)
format_version = args.FormatVersion
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
# style): платформа держит объявления по алфавиту, дописать в конец нельзя.
if format_rank(format_version) >= 221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
# --- Properties --- # --- Properties ---
main_dcs_value = "" main_dcs_value = ""
child_objects_content = "" child_objects_content = ""
@@ -45,7 +94,7 @@ def main():
child_objects_xml = f"<ChildObjects>{child_objects_content}\t\t</ChildObjects>" if child_objects_content else "<ChildObjects/>" child_objects_xml = f"<ChildObjects>{child_objects_content}\t\t</ChildObjects>" if child_objects_content else "<ChildObjects/>"
xml = f'''<?xml version="1.0" encoding="UTF-8"?> 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_decl} version="{format_version}">
\t<ExternalReport uuid="{uuid1}"> \t<ExternalReport uuid="{uuid1}">
\t\t<InternalInfo> \t\t<InternalInfo>
\t\t\t<xr:ContainedObject> \t\t\t<xr:ContainedObject>
@@ -90,7 +139,7 @@ def main():
ext_dir = os.path.join(report_dir, "Ext") ext_dir = os.path.join(report_dir, "Ext")
os.makedirs(ext_dir, exist_ok=True) os.makedirs(ext_dir, exist_ok=True)
write_utf8_bom(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml) write_xml_file(os.path.join(os.path.abspath(src_dir), f"{name}.xml"), xml)
# --- Модуль объекта --- # --- Модуль объекта ---
module_bsl = """\ module_bsl = """\
@@ -107,7 +156,10 @@ def main():
#КонецОбласти""" #КонецОбласти"""
module_path = os.path.join(ext_dir, "ObjectModule.bsl") module_path = os.path.join(ext_dir, "ObjectModule.bsl")
write_utf8_bom(module_path, module_bsl) # Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без).
write_utf8_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
print(f"[OK] Создан отчёт: {root_file}") print(f"[OK] Создан отчёт: {root_file}")
print(f" Каталог: {report_dir}") print(f" Каталог: {report_dir}")
@@ -124,7 +176,7 @@ def main():
skd_uuid = new_uuid() skd_uuid = new_uuid()
skd_meta_xml = f'''<?xml version="1.0" encoding="UTF-8"?> skd_meta_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_decl} version="{format_version}">
\t<Template uuid="{skd_uuid}"> \t<Template uuid="{skd_uuid}">
\t\t<Properties> \t\t<Properties>
\t\t\t<Name>{skd_name}</Name> \t\t\t<Name>{skd_name}</Name>
@@ -140,7 +192,7 @@ def main():
\t</Template> \t</Template>
</MetaDataObject>''' </MetaDataObject>'''
write_utf8_bom(skd_meta_path, skd_meta_xml) write_xml_file(skd_meta_path, skd_meta_xml)
skd_content = '''<?xml version="1.0" encoding="UTF-8"?> skd_content = '''<?xml version="1.0" encoding="UTF-8"?>
<DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema" <DataCompositionSchema xmlns="http://v8.1c.ru/8.1/data-composition-system/schema"
@@ -158,7 +210,7 @@ def main():
</DataCompositionSchema>''' </DataCompositionSchema>'''
skd_file_path = os.path.join(skd_ext_dir, "Template.xml") skd_file_path = os.path.join(skd_ext_dir, "Template.xml")
write_utf8_bom(skd_file_path, skd_content) write_xml_file(skd_file_path, skd_content)
print(f" СКД: {skd_meta_path}") print(f" СКД: {skd_meta_path}")
print(f" Тело: {skd_file_path}") print(f" Тело: {skd_file_path}")
+85 -15
View File
@@ -1,4 +1,4 @@
# form-add v1.12 — Add managed form to 1C config object # form-add v1.23 — Add managed form to 1C config object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -154,6 +154,14 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
function Detect-FormatVersion([string]$dir) { function Detect-FormatVersion([string]$dir) {
$d = $dir $d = $dir
while ($d) { while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml" $cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) { if (Test-Path $cfgPath) {
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8) $cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
@@ -169,6 +177,13 @@ function Detect-FormatVersion([string]$dir) {
return "2.17" return "2.17"
} }
# Версия формата как число для сравнений: "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
}
# --- Фаза 1: Определение типа объекта --- # --- Фаза 1: Определение типа объекта ---
# Resolve ObjectPath (directory → .xml) # Resolve ObjectPath (directory → .xml)
@@ -190,7 +205,26 @@ if (-not (Test-Path $ObjectPath)) {
$objectXmlFull = Resolve-Path $ObjectPath $objectXmlFull = Resolve-Path $ObjectPath
Assert-EditAllowed $objectXmlFull.Path 'editable' Assert-EditAllowed $objectXmlFull.Path 'editable'
$script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent) # Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
$script:formatVersion = $null
$objHead = [System.IO.File]::ReadAllText($objectXmlFull.Path, [System.Text.Encoding]::UTF8)
$objHead = $objHead.Substring(0, [Math]::Min(2000, $objHead.Length))
if ($objHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { $script:formatVersion = $Matches[1] }
if (-not $script:formatVersion) { $script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent) }
# Объявления пространств имён — одной переменной на корень: места эмиссии их только
# интерполируют. Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$script:xmlnsDecl = '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"'
$script:formNsDecl = '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: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"'
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
$script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
$xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true $xmlDoc.PreserveWhitespace = $true
@@ -313,9 +347,16 @@ if ($objectType -in $processorLikeTypes) {
$extPresentationLine = "`n`t`t`t<ExtendedPresentation/>" $extPresentationLine = "`n`t`t`t<ExtendedPresentation/>"
} }
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
# сразу после UsePurposes (проверено по выгрузке 8.5, до ExtendedPresentation).
$useInIfcLine = ""
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$useInIfcLine = "`n`t`t`t<UseInInterfaceCompatibilityMode>Any</UseInInterfaceCompatibilityMode>"
}
$formMetaXml = @" $formMetaXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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="$($script:formatVersion)"> <MetaDataObject $($script:xmlnsDecl) version="$($script:formatVersion)">
<Form uuid="$formUuid"> <Form uuid="$formUuid">
<Properties> <Properties>
<Name>$FormName</Name> <Name>$FormName</Name>
@@ -331,20 +372,29 @@ $formMetaXml = @"
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
</UsePurposes>$extPresentationLine </UsePurposes>$useInIfcLine$extPresentationLine
</Properties> </Properties>
</Form> </Form>
</MetaDataObject> </MetaDataObject>
"@ "@
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
#
# Модуль .bsl сюда НЕ идёт — он пишется отдельно.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile $formMetaPath $formMetaXml $encBom
# --- 3b. Form.xml --- # --- 3b. Form.xml ---
$formXmlPath = Join-Path $formExtDir "Form.xml" $formXmlPath = Join-Path $formExtDir "Form.xml"
$formNsDecl = '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: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"'
if ($Purpose -eq "List" -or $Purpose -eq "Choice") { if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
# Динамический список # Динамический список
# MainTable: тип.имя # MainTable: тип.имя
@@ -352,7 +402,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
$formXml = @" $formXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Form $formNsDecl version="$($script:formatVersion)"> <Form $($script:formNsDecl) version="$($script:formatVersion)">
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"> <AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>true</Autofill> <Autofill>true</Autofill>
</AutoCommandBar> </AutoCommandBar>
@@ -377,7 +427,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
$formXml = @" $formXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Form $formNsDecl version="$($script:formatVersion)"> <Form $($script:formNsDecl) version="$($script:formatVersion)">
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"> <AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>true</Autofill> <Autofill>true</Autofill>
</AutoCommandBar> </AutoCommandBar>
@@ -424,7 +474,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
$formXml = @" $formXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Form $formNsDecl version="$($script:formatVersion)"> <Form $($script:formNsDecl) version="$($script:formatVersion)">
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1"> <AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>true</Autofill> <Autofill>true</Autofill>
</AutoCommandBar> </AutoCommandBar>
@@ -444,7 +494,7 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
if (Test-Path $formXmlPath) { if (Test-Path $formXmlPath) {
Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting" Write-Host "[SKIP] Form.xml already exists: $formXmlPath — not overwriting"
} else { } else {
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom) Write-XmlFile $formXmlPath $formXml $encBom
} }
# --- 3c. Module.bsl --- # --- 3c. Module.bsl ---
@@ -476,6 +526,11 @@ $moduleBsl = @"
if (Test-Path $modulePath) { if (Test-Path $modulePath) {
Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting" Write-Host "[SKIP] Module.bsl already exists: $modulePath — not overwriting"
} else { } else {
# Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без). Шаблон берёт переводы строк из
# самого скрипта, а он в репозитории хранится с LF.
$moduleBsl = ($moduleBsl -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom) [System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
} }
@@ -585,12 +640,27 @@ if ($SetDefault -or $isFirstFormForPurpose) {
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$stream = New-Object System.IO.FileStream($objectXmlFull.Path, [System.IO.FileMode]::Create) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) $memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer) $xmlDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $objectXmlFull.Path) -and ([System.IO.File]::ReadAllText($objectXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($objectXmlFull.Path, $xmlText, $encBom)
# --- Фаза 5: Вывод --- # --- Фаза 5: Вывод ---
+108 -49
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-add v1.12 — Add managed form to 1C config object # form-add v1.23 — Add managed form to 1C config object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -196,6 +196,16 @@ NSMAP = {
def detect_format_version(d): def detect_format_version(d):
while d: while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
cfg_path = os.path.join(d, "Configuration.xml") cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path): if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f: with open(cfg_path, "r", encoding="utf-8-sig") as f:
@@ -210,6 +220,12 @@ def detect_format_version(d):
return "2.17" return "2.17"
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
def _detect_xml_style(path): def _detect_xml_style(path):
"""Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding / """Стиль существующего файла для round-trip-сохранения: BOM / EOL / регистр encoding /
финальный перенос. None → файл новый (сохранить текущее поведение).""" финальный перенос. None → файл новый (сохранить текущее поведение)."""
@@ -227,21 +243,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -259,10 +276,24 @@ def save_xml_with_bom(tree, path):
def write_text_with_bom(path, text): def write_text_with_bom(path, text):
"""Write text to file with UTF-8 BOM.""" """Write text to file with UTF-8 BOM."""
with open(path, "w", encoding="utf-8-sig") as f: # newline="" => без трансляции: в текстовом режиме Python на Windows превратил
# бы \n в \r\n, а на macOS оставил \n — вывод зависел бы от ОС.
with open(path, "w", encoding="utf-8-sig", newline="") as f:
f.write(text) f.write(text)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
Модуль .bsl сюда НЕ идёт — он пишется отдельно.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_text_with_bom(path, text)
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -299,7 +330,64 @@ def main():
object_xml_full = os.path.abspath(object_path) object_xml_full = os.path.abspath(object_path)
assert_edit_allowed(object_xml_full, "editable") assert_edit_allowed(object_xml_full, "editable")
format_version = detect_format_version(os.path.dirname(object_xml_full)) # Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
format_version = None
with open(object_xml_full, "r", encoding="utf-8-sig") as f:
obj_head = f.read(2000)
m_ver = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', obj_head)
if m_ver:
format_version = m_ver.group(1)
if not format_version:
format_version = detect_format_version(os.path.dirname(object_xml_full))
# Объявления пространств имён — одной переменной на корень: места эмиссии их только
# подставляют. Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
xmlns_decl = (
'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"'
)
form_ns_decl = (
'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: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"'
)
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if format_rank(format_version) >= 221:
pal = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
xmlns_decl = xmlns_decl.replace(' xmlns:style=', pal)
form_ns_decl = form_ns_decl.replace(' xmlns:style=', pal)
parser_xml = etree.XMLParser(remove_blank_text=False) parser_xml = etree.XMLParser(remove_blank_text=False)
tree = etree.parse(object_xml_full, parser_xml) tree = etree.parse(object_xml_full, parser_xml)
@@ -388,24 +476,7 @@ def main():
form_meta_xml = ( form_meta_xml = (
'<?xml version="1.0" encoding="UTF-8"?>\n' '<?xml version="1.0" encoding="UTF-8"?>\n'
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"' f'<MetaDataObject {xmlns_decl} version="{format_version}">\n'
' 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"'
f' version="{format_version}">\n'
f'\t<Form uuid="{form_uuid}">\n' f'\t<Form uuid="{form_uuid}">\n'
'\t\t<Properties>\n' '\t\t<Properties>\n'
f'\t\t\t<Name>{form_name}</Name>\n' f'\t\t\t<Name>{form_name}</Name>\n'
@@ -422,37 +493,22 @@ def main():
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n' '\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>\n'
'\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n' '\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>\n'
'\t\t\t</UsePurposes>\n' '\t\t\t</UsePurposes>\n'
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
# сразу после UsePurposes (проверено по выгрузке 8.5, до ExtendedPresentation).
+ ('\t\t\t<UseInInterfaceCompatibilityMode>Any</UseInInterfaceCompatibilityMode>\n'
if format_rank(format_version) >= 221 else '')
+ ('\t\t\t<ExtendedPresentation/>\n' if object_type in processor_like_types else '') + ('\t\t\t<ExtendedPresentation/>\n' if object_type in processor_like_types else '')
+ '\t\t</Properties>\n' + '\t\t</Properties>\n'
'\t</Form>\n' '\t</Form>\n'
'</MetaDataObject>' '</MetaDataObject>'
) )
write_text_with_bom(form_meta_path, form_meta_xml) write_xml_file(form_meta_path, form_meta_xml)
# --- 3b. Form.xml --- # --- 3b. Form.xml ---
form_xml_path = os.path.join(form_ext_dir, "Form.xml") form_xml_path = os.path.join(form_ext_dir, "Form.xml")
form_ns_decl = (
'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: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"'
)
if purpose in ("List", "Choice"): if purpose in ("List", "Choice"):
# Dynamic list # Dynamic list
main_table = f"{object_type}.{object_name}" main_table = f"{object_type}.{object_name}"
@@ -551,7 +607,7 @@ def main():
if os.path.exists(form_xml_path): if os.path.exists(form_xml_path):
print(f"[SKIP] Form.xml already exists: {form_xml_path} — not overwriting") print(f"[SKIP] Form.xml already exists: {form_xml_path} — not overwriting")
else: else:
write_text_with_bom(form_xml_path, form_xml) write_xml_file(form_xml_path, form_xml)
# --- 3c. Module.bsl --- # --- 3c. Module.bsl ---
@@ -582,7 +638,10 @@ def main():
if os.path.exists(module_path): if os.path.exists(module_path):
print(f"[SKIP] Module.bsl already exists: {module_path} — not overwriting") print(f"[SKIP] Module.bsl already exists: {module_path} — not overwriting")
else: else:
write_text_with_bom(module_path, module_bsl) # Модуль пишем в каноне платформы: CRLF в разделителях строк (корпус: 2643 CRLF,
# чисто-LF 0 из 3001). Хвостовой перевод НЕ навязываем — у платформы он
# неканоничен (1235 модулей с ним, 766 без).
write_text_with_bom(module_path, module_bsl.replace('\r\n', '\n').replace('\n', '\r\n'))
# --- Phase 4: Register in parent object --- # --- Phase 4: Register in parent object ---
@@ -1,4 +1,4 @@
# form-compile v1.176 — Compile 1C managed form from JSON or object metadata # form-compile v1.185 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$JsonPath, [string]$JsonPath,
@@ -1335,6 +1335,14 @@ function Generate-ChartOfAccountsChoiceDSL($meta, [hashtable]$presetData) {
function Detect-FormatVersion([string]$dir) { function Detect-FormatVersion([string]$dir) {
$d = $dir $d = $dir
while ($d) { while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml" $cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) { if (Test-Path $cfgPath) {
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8) $cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
@@ -1350,6 +1358,13 @@ function Detect-FormatVersion([string]$dir) {
return "2.17" return "2.17"
} }
# Версия формата как число для сравнений: "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
}
# --- Support guard (Ext/ParentConfigurations.bin) --- # --- Support guard (Ext/ParentConfigurations.bin) ---
# See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" / # See docs/1c-support-state-spec.md. Blocks edits of vendor objects "на замке" /
# read-only configs unless allowed. Trigger = bin present; reaction from # read-only configs unless allowed. Trigger = bin present; reaction from
@@ -1485,6 +1500,17 @@ $script:outPathResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $Ou
Assert-EditAllowed $script:outPathResolved 'editable' Assert-EditAllowed $script:outPathResolved 'editable'
$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved)) $script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved))
# Объявления пространств имён — одной переменной: место эмиссии её только интерполирует.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$script:formNsDecl = '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"'
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$script:formNsDecl = $script:formNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
# --- 0. Path normalization and mode dispatch --- # --- 0. Path normalization and mode dispatch ---
# Form name → purpose mapping # Form name → purpose mapping
@@ -6463,25 +6489,14 @@ function Compute-MainAcbAutofill {
# --- 12. Main compilation --- # --- 12. Main compilation ---
# Title # Буфер и счётчики — с чистого листа: до этой точки они могли быть тронуты режимом from-object.
if ($def.title) {
Emit-MLText -tag "Title" -text $def.title -indent "`t"
}
# Header
X '<?xml version="1.0" encoding="UTF-8"?>'
X "<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=`"$($script:formatVersion)`">"
# Oops — Title was emitted before header. Need to fix the order.
# Actually, let me restructure: build the body into a separate buffer, then assemble
# Reset and rebuild properly
$script:xml = New-Object System.Text.StringBuilder 8192 $script:xml = New-Object System.Text.StringBuilder 8192
$script:nextId = 1 $script:nextId = 1
$script:seenElementNames = @{} # пул имён элементов (глобально по всей форме) $script:seenElementNames = @{} # пул имён элементов (глобально по всей форме)
# Header
X '<?xml version="1.0" encoding="UTF-8"?>' X '<?xml version="1.0" encoding="UTF-8"?>'
X "<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=`"$($script:formatVersion)`">" X "<Form $($script:formNsDecl) version=`"$($script:formatVersion)`">"
# 12a. Title (from def.title or properties.title — must be multilingual XML) # 12a. Title (from def.title or properties.title — must be multilingual XML)
$formTitle = $def.title $formTitle = $def.title
@@ -6622,7 +6637,7 @@ if (-not (Test-Path $outDir)) {
} }
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($outPath, $xml.ToString(), $enc) [System.IO.File]::WriteAllText($outPath, $xml.ToString().TrimEnd("`r", "`n"), $enc)
# --- 13b. Auto-register form in parent object XML --- # --- 13b. Auto-register form in parent object XML ---
@@ -6677,11 +6692,26 @@ if ($formsLeaf -eq 'Forms') {
$regSettings = New-Object System.Xml.XmlWriterSettings $regSettings = New-Object System.Xml.XmlWriterSettings
$regSettings.Encoding = $regEnc $regSettings.Encoding = $regEnc
$regSettings.Indent = $false $regSettings.Indent = $false
$regStream = New-Object System.IO.FileStream($objectXmlPath, [System.IO.FileMode]::Create) $regSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
$regWriter = [System.Xml.XmlWriter]::Create($regStream, $regSettings) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$regMem = New-Object System.IO.MemoryStream
$regWriter = [System.Xml.XmlWriter]::Create($regMem, $regSettings)
$objDoc.Save($regWriter) $objDoc.Save($regWriter)
$regWriter.Close() $regWriter.Flush(); $regWriter.Close()
$regStream.Close()
$regText = [System.Text.Encoding]::UTF8.GetString($regMem.ToArray())
$regMem.Close()
if ($regText.Length -gt 0 -and $regText[0] -eq [char]0xFEFF) { $regText = $regText.Substring(1) }
$regText = $regText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$regText = [regex]::Replace($regText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $objectXmlPath) -and ([System.IO.File]::ReadAllText($objectXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$regText = ($regText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($objectXmlPath, $regText, $regEnc)
Write-Host " Registered: <Form>$formName</Form> in $objectName.xml" Write-Host " Registered: <Form>$formName</Form> in $objectName.xml"
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# form-compile v1.176 — Compile 1C managed form from JSON or object metadata # form-compile v1.185 — Compile 1C managed form from JSON or object metadata
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import copy import copy
@@ -6061,6 +6061,16 @@ def emit_properties(lines, props, indent):
def detect_format_version(d): def detect_format_version(d):
while d: while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
cfg_path = os.path.join(d, "Configuration.xml") cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path): if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f: with open(cfg_path, "r", encoding="utf-8-sig") as f:
@@ -6075,6 +6085,12 @@ def detect_format_version(d):
return "2.17" return "2.17"
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
def _normalize_elements(defn): def _normalize_elements(defn):
"""Convert dict-style elements from --from-object generators to list-style expected by compiler. """Convert dict-style elements from --from-object generators to list-style expected by compiler.
Generator format: elements = {"ИмяЭлемента": {"element": "input", "path": "..."}, ...} Generator format: elements = {"ИмяЭлемента": {"element": "input", "path": "..."}, ...}
@@ -6214,6 +6230,36 @@ def main():
assert_edit_allowed(out_path_resolved, "editable") assert_edit_allowed(out_path_resolved, "editable")
format_version = detect_format_version(os.path.dirname(out_path_resolved)) format_version = detect_format_version(os.path.dirname(out_path_resolved))
# Объявления пространств имён — одной переменной: место эмиссии её только подставляет.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
form_ns_decl = (
'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"'
)
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if format_rank(format_version) >= 221:
form_ns_decl = form_ns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
# --- 0. From-object mode --- # --- 0. From-object mode ---
if args.FromObject: if args.FromObject:
# Resolve object path and purpose from OutputPath convention: # Resolve object path and purpose from OutputPath convention:
@@ -6464,7 +6510,7 @@ def main():
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append(f'<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="{format_version}">') lines.append(f'<Form {form_ns_decl} version="{format_version}">')
# Title # Title
form_title = defn.get('title') form_title = defn.get('title')
@@ -6580,7 +6626,7 @@ def main():
if out_dir and not os.path.exists(out_dir): if out_dir and not os.path.exists(out_dir):
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
content = '\n'.join(lines) + '\n' content = '\r\n'.join(lines)
write_utf8_bom(out_path, content) write_utf8_bom(out_path, content)
# --- 4. Auto-register form in parent object XML --- # --- 4. Auto-register form in parent object XML ---
@@ -6598,17 +6644,26 @@ def main():
if forms_leaf == 'Forms': if forms_leaf == 'Forms':
object_xml_path = os.path.join(type_plural_dir, f'{object_name}.xml') object_xml_path = os.path.join(type_plural_dir, f'{object_name}.xml')
if os.path.exists(object_xml_path): if os.path.exists(object_xml_path):
with open(object_xml_path, 'r', encoding='utf-8-sig') as f: # newline='' => без трансляции переводов строк: иначе CRLF молча
# схлопнется в LF при чтении и файл будет переписан в LF (#44/#46/#47).
with open(object_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
raw_text = f.read() raw_text = f.read()
# Перевод строки вставки берём из самого файла, а не из канона:
# правка существующего файла сохраняет его стиль.
eol = '\r\n' if '\r\n' in raw_text else '\n'
# Check if already registered # Check if already registered
if f'<Form>{form_name}</Form>' not in raw_text: if f'<Form>{form_name}</Form>' not in raw_text:
# Insert before </ChildObjects> # Insert before </ChildObjects>
if '</ChildObjects>' in raw_text: if '</ChildObjects>' in raw_text:
insert_line = f'\t\t\t<Form>{form_name}</Form>\n' # Отступ вставки берём у закрывающего тега +1 уровень: подстановка
raw_text = raw_text.replace('</ChildObjects>', insert_line + '\t\t</ChildObjects>', 1) # по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
lambda m: m.group(1) + '\t' + f'<Form>{form_name}</Form>' + eol + m.group(1) + '</ChildObjects>',
raw_text, count=1)
elif '<ChildObjects/>' in raw_text: elif '<ChildObjects/>' in raw_text:
replacement = f'<ChildObjects>\n\t\t\t<Form>{form_name}</Form>\n\t\t</ChildObjects>' replacement = ('<ChildObjects>' + eol + f'\t\t\t<Form>{form_name}</Form>' + eol + '\t\t</ChildObjects>')
raw_text = raw_text.replace('<ChildObjects/>', replacement, 1) raw_text = raw_text.replace('<ChildObjects/>', replacement, 1)
write_utf8_bom(object_xml_path, raw_text) write_utf8_bom(object_xml_path, raw_text)
@@ -1,4 +1,4 @@
# form-edit v1.6 — Edit 1C managed form elements # form-edit v1.9 — Edit 1C managed form elements
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -1387,8 +1387,16 @@ if ($def.elementEvents -and $def.elementEvents.Count -gt 0) {
$content = $xmlDoc.OuterXml $content = $xmlDoc.OuterXml
# Ensure encoding declaration is uppercase UTF-8 # Ensure encoding declaration is uppercase UTF-8
$content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', '<?xml version="1.0" encoding="UTF-8"?>' $content = $content -replace '^<\?xml version="1.0" encoding="utf-8"\?>', '<?xml version="1.0" encoding="UTF-8"?>'
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$content = [regex]::Replace($content, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $resolvedFormPath) -and ([System.IO.File]::ReadAllText($resolvedFormPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$content = ($content -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc) [System.IO.File]::WriteAllText($resolvedFormPath, $content, $enc)
# === 14. Summary === # === 14. Summary ===
@@ -1,4 +1,4 @@
# form-edit v1.6 — Edit 1C managed form elements (Python port) # form-edit v1.9 — Edit 1C managed form elements (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -1,4 +1,4 @@
# form-remove v1.4 — Remove form from 1C object # form-remove v1.8 — Remove form from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -64,6 +64,10 @@ foreach ($node in $formNodes) {
$parent.RemoveChild($prev) | Out-Null $parent.RemoveChild($prev) | Out-Null
} }
$parent.RemoveChild($node) | Out-Null $parent.RemoveChild($node) | Out-Null
# Опустевший контейнер: остаётся отступ-whitespace, и XmlWriter пишет пару
# <ChildObjects>\n\t\t</ChildObjects>. Платформа пишет только <ChildObjects/>
# (1394 самозакрывающихся на acc+erp, пустых пар ни в одной форме — 0).
if ($parent.SelectNodes("*").Count -eq 0) { $parent.IsEmpty = $true }
break break
} }
} }
@@ -74,7 +78,9 @@ foreach ($node in $formNodes) {
$formRefRe = "Form\.$([regex]::Escape($FormName))$" $formRefRe = "Form\.$([regex]::Escape($FormName))$"
foreach ($node in $xmlDoc.SelectNodes("//md:*", $nsMgr)) { foreach ($node in $xmlDoc.SelectNodes("//md:*", $nsMgr)) {
if ($node.LocalName -like "*Form" -and $node.InnerText -and $node.InnerText -match $formRefRe) { if ($node.LocalName -like "*Form" -and $node.InnerText -and $node.InnerText -match $formRefRe) {
$node.InnerText = "" # IsEmpty, а не InnerText="": пустая строка сериализуется парой <Tag></Tag>, а
# Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен <Tag/>.
$node.IsEmpty = $true
} }
} }
@@ -83,11 +89,26 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) $memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer) $xmlDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom)
Write-Host "[OK] Форма $FormName удалена из $rootXmlPath" Write-Host "[OK] Форма $FormName удалена из $rootXmlPath"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# remove-form v1.4 — Remove form from 1C object # form-remove v1.8 — Remove form from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -30,21 +30,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -119,6 +120,10 @@ def main():
if parent.text and parent.text.strip() == "": if parent.text and parent.text.strip() == "":
parent.text = "" parent.text = ""
parent.remove(node) parent.remove(node)
# Опустевший контейнер: text="" сериализуется парой <ChildObjects></ChildObjects>,
# а нужен <ChildObjects/> — PS-порт через DOM даёт именно его.
if len(parent) == 0 and not (parent.text or "").strip():
parent.text = None
break break
# Clear any Default*/Auxiliary* form slot that pointed to the removed form # Clear any Default*/Auxiliary* form slot that pointed to the removed form
@@ -129,7 +134,9 @@ def main():
if not isinstance(el.tag, str): if not isinstance(el.tag, str):
continue continue
if etree.QName(el).localname.endswith("Form") and el.text and ref_re.search(el.text): if etree.QName(el).localname.endswith("Form") and el.text and ref_re.search(el.text):
el.text = "" # text=None, а не "": пустая строка сериализуется парой <Tag></Tag>, а
# Конфигуратор пустых пар не пишет (0 на 476 942 XML корпуса) — нужен <Tag/>.
el.text = None
# Save with BOM # Save with BOM
save_xml_with_bom(tree, root_xml_full) save_xml_with_bom(tree, root_xml_full)
+40 -6
View File
@@ -1,4 +1,4 @@
# help-add v1.9 — Add built-in help to 1C object # help-add v1.16 — Add built-in help to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -149,6 +149,14 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
function Detect-FormatVersion([string]$dir) { function Detect-FormatVersion([string]$dir) {
$d = $dir $d = $dir
while ($d) { while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml" $cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) { if (Test-Path $cfgPath) {
$content = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8) $content = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
@@ -195,7 +203,18 @@ $helpXml = @"
</Help> </Help>
"@ "@
[System.IO.File]::WriteAllText($helpXmlPath, $helpXml, $encBom) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
#
# HTML-страница сюда НЕ идёт — платформа хранит её с LF.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile $helpXmlPath $helpXml $encBom
# --- 2. Help/<lang>.html --- # --- 2. Help/<lang>.html ---
@@ -255,11 +274,26 @@ if (Test-Path $formsDir) {
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$stream = New-Object System.IO.FileStream($formMeta.FullName, [System.IO.FileMode]::Create) $settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer) $xmlDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $formMeta.FullName) -and ([System.IO.File]::ReadAllText($formMeta.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($formMeta.FullName, $xmlText, $encBom)
Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)" Write-Host " IncludeHelpInContents добавлен: $($formMeta.Name)"
} }
+38 -10
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# add-help v1.9 — Add built-in help to 1C object # help-add v1.16 — Add built-in help to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -191,6 +191,16 @@ def assert_edit_allowed(target_path, require):
def detect_format_version(d): def detect_format_version(d):
while d: while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
cfg_path = os.path.join(d, "Configuration.xml") cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path): if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f: with open(cfg_path, "r", encoding="utf-8-sig") as f:
@@ -222,21 +232,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -253,11 +264,28 @@ def save_xml_with_bom(tree, path):
def write_text_with_bom(path, text): def write_text_with_bom(path, text):
"""Write text to file with UTF-8 BOM.""" """Write text to file with UTF-8 BOM.
with open(path, "w", encoding="utf-8-sig") as f:
newline="" обязателен: в текстовом режиме Python на Windows превратил бы \\n в
\\r\\n, а на macOS оставил \\n вывод навыка зависел бы от ОС. Через эту функцию
идёт HTML-страница справки, а её платформа хранит именно с LF (корпус: 399 LF из 400).
"""
with open(path, "w", encoding="utf-8-sig", newline="") as f:
f.write(text) f.write(text)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми сознательно: разошедшиеся копии сводят на нет весь смысл.
HTML-страница сюда НЕ идёт платформа хранит её с LF.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_text_with_bom(path, text)
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -301,7 +329,7 @@ def main():
'</Help>' '</Help>'
) )
write_text_with_bom(help_xml_path, help_xml) write_xml_file(help_xml_path, help_xml)
# --- 2. Help/<lang>.html --- # --- 2. Help/<lang>.html ---
@@ -1,4 +1,4 @@
# interface-edit v1.9 — Edit 1C CommandInterface.xml # interface-edit v1.13 — Edit 1C CommandInterface.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$CIPath, [Parameter(Mandatory)][Alias('Path')][string]$CIPath,
@@ -202,7 +202,12 @@ if (-not (Test-Path $CIPath)) {
</CommandInterface> </CommandInterface>
"@ "@
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($CIPath, $emptyCI, $utf8Bom) # Файл СОЗДАЁМ — пишем канон выгрузки: CRLF, без перевода строки в конце.
# (Правка существующего файла, наоборот, наследует его стиль — это делает
# основной путь сохранения ниже.) Нормализация нужна потому, что here-string
# берёт переводы строк из самого .ps1, а он в репозитории хранится с LF.
$emptyCI = ($emptyCI -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($CIPath, $emptyCI.TrimEnd("`r", "`n"), $utf8Bom)
Write-Host "[INFO] Created new CommandInterface.xml: $CIPath" Write-Host "[INFO] Created new CommandInterface.xml: $CIPath"
} else { } else {
Write-Error "File not found: $CIPath (use -CreateIfMissing to create)" Write-Error "File not found: $CIPath (use -CreateIfMissing to create)"
@@ -674,8 +679,16 @@ $memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes) $text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath" Info "Saved: $resolvedPath"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# interface-edit v1.9 — Edit 1C CommandInterface.xml # interface-edit v1.13 — Edit 1C CommandInterface.xml
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -304,21 +304,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -438,7 +439,12 @@ def main():
f'\tversion="{format_version}">\n' f'\tversion="{format_version}">\n'
f'</CommandInterface>' f'</CommandInterface>'
) )
with open(ci_path, "w", encoding="utf-8-sig") as fh: # Файл СОЗДАЁМ — пишем канон выгрузки: CRLF в разделителях. (Правка
# существующего файла, наоборот, наследует его стиль — это делает
# save_xml_bom через _detect_xml_style.) newline="" обязателен: без него
# текстовый режим дал бы CRLF на Windows и LF на macOS.
empty_ci = empty_ci.replace("\r\n", "\n").replace("\n", "\r\n").rstrip("\r\n")
with open(ci_path, "w", encoding="utf-8-sig", newline="") as fh:
fh.write(empty_ci) fh.write(empty_ci)
print(f"[INFO] Created new CommandInterface.xml: {ci_path}") print(f"[INFO] Created new CommandInterface.xml: {ci_path}")
else: else:
@@ -1,4 +1,4 @@
# meta-compile v1.76 — Compile 1C metadata object from JSON # meta-compile v1.88 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -161,7 +161,10 @@ if ($def -is [array] -or ($null -ne $def -and $def.GetType().BaseType.Name -eq '
$idx = 0 $idx = 0
foreach ($item in $def) { foreach ($item in $def) {
$idx++ $idx++
$tmpJson = Join-Path ([System.IO.Path]::GetTempPath()) "meta-compile-batch-$idx.json" # Имя с GUID, а не "batch-$idx": фиксированное имя в общем %TEMP% сталкивало
# два параллельных запуска навыка на одной машине — Set-Content падал с
# «file is being used by another process». py-порт уже брал mkstemp.
$tmpJson = Join-Path ([System.IO.Path]::GetTempPath()) "meta-compile-batch-$idx-$([guid]::NewGuid().ToString('N')).json"
try { try {
$item | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpJson $item | ConvertTo-Json -Depth 20 | Set-Content -Encoding UTF8 $tmpJson
$proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$PSCommandPath`" -JsonPath `"$tmpJson`" -OutputDir `"$OutputDir`"" -NoNewWindow -Wait -PassThru $proc = Start-Process -FilePath "powershell.exe" -ArgumentList "-NoProfile -File `"$PSCommandPath`" -JsonPath `"$tmpJson`" -OutputDir `"$OutputDir`"" -NoNewWindow -Wait -PassThru
@@ -544,6 +547,10 @@ $script:typeNamespaceMap = @{
} }
# Типы current-config пространства (cfg:, объявлено в корне): объектные (CatalogObject.X/DataProcessorObject.X/…) # Типы current-config пространства (cfg:, объявлено в корне): объектные (CatalogObject.X/DataProcessorObject.X/…)
# и голые (ConstantsSet/ReportBuilder). Ссылочные (*Ref.X/DefinedType.X) идут ОТДЕЛЬНО через локальный d5p1 (§memory). # и голые (ConstantsSet/ReportBuilder). Ссылочные (*Ref.X/DefinedType.X) идут ОТДЕЛЬНО через локальный d5p1 (§memory).
# Префикс current-config для ссылочных типов. 'cfg' — для файлов, чья шапка его объявляет
# (объектный XML, Ext/Form.xml общей формы). $null на время сборки Ext/Predefined.xml, чья
# шапка его НЕ объявляет: там и платформа уходит на локальное объявление.
$script:cfgPrefix = 'cfg'
$script:cfgBareTypes = @("ConstantsSet", "ReportBuilder", "FilterCriterion") $script:cfgBareTypes = @("ConstantsSet", "ReportBuilder", "FilterCriterion")
$script:cfgObjectKinds = @("Catalog","Document","Enum","ChartOfAccounts","ChartOfCharacteristicTypes", $script:cfgObjectKinds = @("Catalog","Document","Enum","ChartOfAccounts","ChartOfCharacteristicTypes",
"ChartOfCalculationTypes","ExchangePlan","BusinessProcess","Task","InformationRegister","AccumulationRegister", "ChartOfCalculationTypes","ExchangePlan","BusinessProcess","Task","InformationRegister","AccumulationRegister",
@@ -605,10 +612,44 @@ function Emit-TypeContent {
if (-not $typeStr) { return } if (-not $typeStr) { return }
# Composite type: "Type1 + Type2 + Type3" # Composite type: "Type1 + Type2 + Type3"
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
# расхождение вылезало только на составном.
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
if ($typeStr.Contains(' + ')) { if ($typeStr.Contains(' + ')) {
$parts = $typeStr -split '\s*\+\s*' $parts = $typeStr -split '\s*\+\s*'
$typeLines = New-Object System.Collections.ArrayList
$qualBlocks = @{} # 'Number'|'String'|'Date' → строки блока
foreach ($part in $parts) { foreach ($part in $parts) {
# X пишет в StringBuilder, поэтому «перехват» — это запомнить длину, вызвать
# эмиттер и откатить добавленное. В py-порту X добавляет в список, и там тот
# же алгоритм выражен срезом — различие рантаймов, не логики.
$before = $script:xml.Length
Emit-TypeContent $indent $part.Trim() Emit-TypeContent $indent $part.Trim()
$chunk = $script:xml.ToString($before, $script:xml.Length - $before)
[void]$script:xml.Remove($before, $script:xml.Length - $before)
$curQual = $null
foreach ($line in ($chunk -split "`r?`n")) {
if ($line -eq '') { continue }
if ($line -match '<v8:(String|Number|Date)Qualifiers>') {
$curQual = $Matches[1]
$qualBlocks[$curQual] = New-Object System.Collections.ArrayList
}
if ($curQual) {
[void]$qualBlocks[$curQual].Add($line)
if ($line -match '</v8:(String|Number|Date)Qualifiers>') { $curQual = $null }
} else {
[void]$typeLines.Add($line)
}
}
}
foreach ($line in $typeLines) { X $line }
foreach ($q in @('Number', 'String', 'Date')) {
if ($qualBlocks.ContainsKey($q)) { foreach ($line in $qualBlocks[$q]) { X $line } }
} }
return return
} }
@@ -723,9 +764,22 @@ function Emit-TypeContent {
return return
} }
# Reference types — use local xmlns declaration for 1C compatibility # Ссылочные типы — корневой cfg:, как пишет платформа. Раньше здесь объявлялся
# ЛОКАЛЬНЫЙ xmlns:d5p1 на тот же URI, что уже объявлен в шапке ($script:xmlnsDecl):
# формально эквивалентно (значим URI, не префикс) и платформой принималось, но
# первый же цикл «загрузить в базу → выгрузить» переписывал каждый ссылочный тип
# в cfg: — то есть давал diff-шум на ровном месте. Форма пришла из СКД, где cfg:
# действительно не работает; в метаданных такого ограничения нет.
# NB: локальная xmlns остаётся законной для ЧУЖИХ пространств — см. $script:typeNamespaceMap.
# $script:cfgPrefix = $null означает «пишем файл, корень которого cfg НЕ объявляет»
# (Ext/Predefined.xml — его шапка это predef/v8/xr/xs/xsi). Там платформа сама уходит
# на локальную форму: в корпусе `<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты`.
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$') { if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$') {
X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>" if ($script:cfgPrefix) {
X "$indent<v8:Type>$($script:cfgPrefix):$typeStr</v8:Type>"
} else {
X "$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>"
}
return return
} }
@@ -1246,14 +1300,20 @@ $script:standardAttributesByType = @{
"Enum" = @("Order","Ref") "Enum" = @("Order","Ref")
"InformationRegister" = @("Active","LineNumber","Recorder","Period") "InformationRegister" = @("Active","LineNumber","Recorder","Period")
"AccumulationRegister" = @("Active","LineNumber","Recorder","Period") "AccumulationRegister" = @("Active","LineNumber","Recorder","Period")
"AccountingRegister" = @("Active","Period","Recorder","LineNumber","Account") "AccountingRegister" = @("PeriodAdjustment","Account","Active","LineNumber","Recorder","Period")
"CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry") "CalculationRegister" = @("Active","Recorder","LineNumber","RegistrationPeriod","CalculationType","ReversingEntry")
"ChartOfAccounts" = @("PredefinedDataName","Order","OffBalance","Type","Description","Code","Parent","Predefined","DeletionMark","Ref") "ChartOfAccounts" = @("PredefinedDataName","Order","OffBalance","Type","Description","Code","Parent","Predefined","DeletionMark","Ref")
"ChartOfCharacteristicTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","Description","Code","Parent","ValueType") "ChartOfCharacteristicTypes" = @("PredefinedDataName","ValueType","Description","Code","IsFolder","Parent","Predefined","DeletionMark","Ref")
"ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","ActionPeriodIsBasic","Description","Code") "ChartOfCalculationTypes" = @("PredefinedDataName","Predefined","Ref","DeletionMark","ActionPeriodIsBasic","Description","Code")
"BusinessProcess" = @("Ref","DeletionMark","Date","Number","Started","Completed","HeadTask") "BusinessProcess" = @("Started","HeadTask","Completed","Ref","DeletionMark","Date","Number")
"Task" = @("Ref","DeletionMark","Date","Number","Executed","Description","RoutePoint","BusinessProcess") "Task" = @("Executed","Description","RoutePoint","BusinessProcess","Ref","DeletionMark","Date","Number")
"ExchangePlan" = @("Ref","DeletionMark","Code","Description","ThisNode","SentNo","ReceivedNo") # Порядок в каждом списке — канон выгрузки, снят с корпуса acc+erp (внутри типа разброса нет).
# Условные члены перечислены в $script:stdAttrOptional — они эмитятся только при наличии
# ключа в DSL, но позицию берут отсюда.
# У ПВХ IsFolder входит в фикс-список: он есть у всех 23 объектов корпуса с этим блоком.
# У бухрегистра PeriodAdjustment, наоборот, условен (1 из 3) — он приходит как «лишний»
# ключ и эмитится ПЕРЕД фикс-списком, что совпадает с платформой.
"ExchangePlan" = @("ThisNode","ReceivedNo","SentNo","Ref","DeletionMark","Description","Code")
"DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number") "DocumentJournal" = @("Type","Ref","Date","Posted","DeletionMark","Number")
} }
@@ -1372,6 +1432,20 @@ function Emit-StandardAttribute {
# - stdAttrProfile[тип]: профиль материализованного блока (пусто = schema-дефолт), поверх — DSL-override. # - stdAttrProfile[тип]: профиль материализованного блока (пусто = schema-дефолт), поверх — DSL-override.
# Миграция типа = добавить его в stdAttrConditionalTypes + stdAttrProfile и переснять снэпшоты; КОД НЕ ТРОГАЕМ. # Миграция типа = добавить его в stdAttrConditionalTypes + stdAttrProfile и переснять снэпшоты; КОД НЕ ТРОГАЕМ.
$script:stdAttrConditionalTypes = @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document') $script:stdAttrConditionalTypes = @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document')
# Условные члены списка типа: позиция берётся из $script:standardAttributesByType, а сам
# реквизит эмитится только при наличии ключа в DSL (у бухрегистра PeriodAdjustment — 2 из 5).
$script:stdAttrOptional = @{
"AccountingRegister" = @("PeriodAdjustment")
}
# Хвостовая группа: реквизиты, которых нет в списке типа и которые идут ПОСЛЕ него.
# У бухрегистра это пары субконто. Именами их не перечислить: их количество задаётся
# свойством MaxExtDimensionCount плана счетов, а не константой (в корпусе везде 3, но
# это однородность выборки, а не правило). Поэтому — шаблон, а не список.
$script:stdAttrTailPattern = @{
"AccountingRegister" = '^ExtDimension(Type)?\d+$'
}
function Emit-StandardAttributes { function Emit-StandardAttributes {
param([string]$indent, [string]$objectType) param([string]$indent, [string]$objectType)
$attrs = $script:standardAttributesByType[$objectType] $attrs = $script:standardAttributesByType[$objectType]
@@ -1381,12 +1455,27 @@ function Emit-StandardAttributes {
if ($conditional -and $null -eq $sa) { return } # условный тип без кастомизации → блока нет if ($conditional -and $null -eq $sa) { return } # условный тип без кастомизации → блока нет
if ($sa -is [string] -and $sa -eq '') { return } # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок — правило не выводимо) if ($sa -is [string] -and $sa -eq '') { return } # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок — правило не выводимо)
$profile = $script:stdAttrProfile[$objectType]; if (-not $profile) { $profile = @{} } $profile = $script:stdAttrProfile[$objectType]; if (-not $profile) { $profile = @{} }
# Доп. (опциональные) стандартные реквизиты вне фикс-списка типа — напр. ExchangeDate у части ПланОбмена # Список типа задаёт ПОРЯДОК всех известных стандартных реквизитов, включая условные:
# (легаси, присутствие не выводится из свойств). Эмитим по факту наличия ключа в DSL, ПЕРЕД фикс-списком (их позиция). # их позиция бывает и до, и после обязательных (у бухрегистра PeriodAdjustment идёт
$extra = @() # перед Account, а ExtDimension1..3/ExtDimensionType1..3 — после Period), поэтому
if ($sa) { foreach ($k in $sa.PSObject.Properties.Name) { if ($attrs -notcontains $k) { $extra += $k } } } # «условные скопом вперёд» не выражает канон. Условный эмитим по факту наличия в DSL.
$optional = $script:stdAttrOptional[$objectType]; if (-not $optional) { $optional = @() }
# Ключи, которых нет в списке типа ВООБЩЕ. По умолчанию их позиция — ПЕРЕД списком
# (легаси вроде ExchangeDate у части планов обмена). Подходящие под хвостовой шаблон
# типа идут ПОСЛЕ, в порядке номера, а внутри номера — сначала ExtDimensionN, затем
# ExtDimensionTypeN (порядок платформы).
$tailRe = $script:stdAttrTailPattern[$objectType]
$extra = @(); $tail = @()
if ($sa) {
foreach ($k in $sa.PSObject.Properties.Name) {
if ($attrs -contains $k) { continue }
if ($tailRe -and $k -match $tailRe) { $tail += $k } else { $extra += $k }
}
}
$tail = @($tail | Sort-Object @{e={[int]([regex]::Match($_, '\d+').Value)}}, @{e={ if ($_ -match 'Type\d+$') { 1 } else { 0 } }})
X "$indent<StandardAttributes>" X "$indent<StandardAttributes>"
foreach ($a in ($extra + $attrs)) { foreach ($a in ($extra + $attrs + $tail)) {
if (($optional -contains $a) -and (-not ($sa -and $sa.PSObject.Properties[$a]))) { continue }
$ov = @{} $ov = @{}
if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } } if ($profile.ContainsKey($a)) { foreach ($k in $profile[$a].Keys) { $ov[$k] = $profile[$a][$k] } }
if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan) if ($sa) { # DSL-override применяем всегда при наличии ключа (для не-условных типов тоже, напр. ExchangePlan)
@@ -1946,9 +2035,12 @@ function Emit-Attribute {
} }
} }
# Use — only for catalog top-level attributes # Use — у реквизитов справочника и ПВХ. Позиция РАЗНАЯ: справочник пишет Use ПЕРЕД
# Indexing, ПВХ — ПОСЛЕ него (корпус acc+erp: Catalog `Use,Indexing,FullTextSearch`,
# ПВХ `Indexing,Use,FullTextSearch,DataHistory`). Отсюда отдельный контекст "cct":
# структурно реквизит ПВХ совпадает со справочником, расходится только этим порядком.
$use = if ($parsed.use) { $parsed.use } else { "ForItem" }
if ($context -eq "catalog") { if ($context -eq "catalog") {
$use = if ($parsed.use) { $parsed.use } else { "ForItem" }
X "$indent`t`t<Use>$use</Use>" X "$indent`t`t<Use>$use</Use>"
} }
@@ -1964,6 +2056,7 @@ function Emit-Attribute {
if ($parsed.indexing) { $indexing = $parsed.indexing } if ($parsed.indexing) { $indexing = $parsed.indexing }
X "$indent`t`t<Indexing>$indexing</Indexing>" X "$indent`t`t<Indexing>$indexing</Indexing>"
} }
if ($context -eq "cct") { X "$indent`t`t<Use>$use</Use>" }
# Реквизит адресации задачи: AddressingDimension (ссылка на измерение регистра исполнителей), между Indexing и FullTextSearch. # Реквизит адресации задачи: AddressingDimension (ссылка на измерение регистра исполнителей), между Indexing и FullTextSearch.
if ($context -eq "task-addressing" -and $elemTag -eq "AddressingAttribute") { if ($context -eq "task-addressing" -and $elemTag -eq "AddressingAttribute") {
@@ -2131,6 +2224,11 @@ function Emit-EnumValue {
X "$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>" X "$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>"
Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym Emit-MLText "$indent`t`t" "Synonym" $parsed.synonym
if ($parsed.comment) { X "$indent`t`t<Comment>$(Esc-XmlText $parsed.comment)</Comment>" } else { X "$indent`t`t<Comment/>" } if ($parsed.comment) { X "$indent`t`t<Comment>$(Esc-XmlText $parsed.comment)</Comment>" } else { X "$indent`t`t<Comment/>" }
# Цвет значения перечисления — свойство формата 2.21 (8.5), последним в Properties.
if ($script:isFormat221) {
$color = if ($parsed.color) { "$($parsed.color)" } else { "auto" }
X "$indent`t`t<Color>$(Esc-XmlText $color)</Color>"
}
X "$indent`t</Properties>" X "$indent`t</Properties>"
X "$indent</EnumValue>" X "$indent</EnumValue>"
} }
@@ -2797,6 +2895,11 @@ function Emit-CommonFormProperties {
} else { } else {
X "$i<UsePurposes/>" X "$i<UsePurposes/>"
} }
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
# между UsePurposes и UseStandardCommands.
if ($script:isFormat221) {
X "$i<UseInInterfaceCompatibilityMode>$(Get-EnumProp 'UseInInterfaceCompatibilityMode' 'useInInterfaceCompatibilityMode' 'Any')</UseInInterfaceCompatibilityMode>"
}
$useStdCmds = if (Get-BoolProp "useStandardCommands" $false) { "true" } else { "false" } $useStdCmds = if (Get-BoolProp "useStandardCommands" $false) { "true" } else { "false" }
X "$i<UseStandardCommands>$useStdCmds</UseStandardCommands>" X "$i<UseStandardCommands>$useStdCmds</UseStandardCommands>"
Emit-MLText $i "ExtendedPresentation" $def.extendedPresentation Emit-MLText $i "ExtendedPresentation" $def.extendedPresentation
@@ -3040,7 +3143,9 @@ function Emit-ScheduledJobProperties {
if ($description) { X "$i<Description>$(Esc-XmlText $description)</Description>" } else { X "$i<Description/>" } if ($description) { X "$i<Description>$(Esc-XmlText $description)</Description>" } else { X "$i<Description/>" }
$key = if ($def.key) { "$($def.key)" } else { "" } $key = if ($def.key) { "$($def.key)" } else { "" }
X "$i<Key>$(Esc-XmlText $key)</Key>" # Пустое значение → самозакрывающийся, как у <Description> выше: Конфигуратор
# не пишет пустых пар.
if ($key) { X "$i<Key>$(Esc-XmlText $key)</Key>" } else { X "$i<Key/>" }
$use = if ($def.use -eq $true) { "true" } else { "false" } $use = if ($def.use -eq $true) { "true" } else { "false" }
X "$i<Use>$use</Use>" X "$i<Use>$use</Use>"
@@ -3105,6 +3210,8 @@ function Emit-ReportProperties {
Emit-VerbatimRef $i "DefaultSettingsForm" $def.defaultSettingsForm Emit-VerbatimRef $i "DefaultSettingsForm" $def.defaultSettingsForm
Emit-VerbatimRef $i "AuxiliarySettingsForm" $def.auxiliarySettingsForm Emit-VerbatimRef $i "AuxiliarySettingsForm" $def.auxiliarySettingsForm
Emit-VerbatimRef $i "DefaultVariantForm" $def.defaultVariantForm Emit-VerbatimRef $i "DefaultVariantForm" $def.defaultVariantForm
# Вспомогательная форма варианта отчёта — свойство формата 2.21 (8.5).
if ($script:isFormat221) { Emit-VerbatimRef $i "AuxiliaryVariantForm" $def.auxiliaryVariantForm }
Emit-VerbatimRef $i "VariantsStorage" $def.variantsStorage Emit-VerbatimRef $i "VariantsStorage" $def.variantsStorage
Emit-VerbatimRef $i "SettingsStorage" $def.settingsStorage Emit-VerbatimRef $i "SettingsStorage" $def.settingsStorage
$inclHelp = if (Get-BoolProp "includeHelpInContents" $false) { "true" } else { "false" } $inclHelp = if (Get-BoolProp "includeHelpInContents" $false) { "true" } else { "false" }
@@ -3840,7 +3947,8 @@ function Emit-WebServiceProperties {
if ($def.comment) { X "$i<Comment>$(Esc-XmlText "$($def.comment)")</Comment>" } else { X "$i<Comment/>" } if ($def.comment) { X "$i<Comment>$(Esc-XmlText "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
$namespace = if ($def.namespace) { "$($def.namespace)" } else { "" } $namespace = if ($def.namespace) { "$($def.namespace)" } else { "" }
X "$i<Namespace>$(Esc-XmlText $namespace)</Namespace>" # Пустое значение → самозакрывающийся, как у <Comment> выше.
if ($namespace) { X "$i<Namespace>$(Esc-XmlText $namespace)</Namespace>" } else { X "$i<Namespace/>" }
# XDTOPackages — СПИСОК элементов, а не скаляр: значение либо ссылка на пакет конфигурации # XDTOPackages — СПИСОК элементов, а не скаляр: значение либо ссылка на пакет конфигурации
# (xr:MDObjectRef "XDTOPackage.Имя"), либо URI внешнего пространства имён (xs:string). # (xr:MDObjectRef "XDTOPackage.Имя"), либо URI внешнего пространства имён (xs:string).
@@ -4143,6 +4251,15 @@ $script:compatMode = Detect-CompatibilityMode $OutputDir
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался. # глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
$script:isFormat218 = (Get-FormatRank $script:formatVersion) -ge 218 $script:isFormat218 = (Get-FormatRank $script:formatVersion) -ge 218
$script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220 $script:isFormat220 = (Get-FormatRank $script:formatVersion) -ge 220
$script:isFormat221 = (Get-FormatRank $script:formatVersion) -ge 221
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту.
# Только для шапок MetaDataObject и Form — в файлах с корнем extrnprops
# (Ext/ClientApplicationInterface.xml и т.п.) платформа его не пишет.
if ($script:isFormat221) {
$palNs = ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette"'
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', "$palNs xmlns:style="
}
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами. # Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
$script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 } $script:lineNumberLengthDefault = if ((Get-CompatModeRank $script:compatMode) -ge 80327) { 9 } else { 5 }
@@ -4268,7 +4385,7 @@ if ($objType -in $typesWithAttrTS) {
"Catalog" { "catalog" } "Catalog" { "catalog" }
"Document" { "document" } "Document" { "document" }
{ $_ -in @("DataProcessor","Report") } { "processor" } { $_ -in @("DataProcessor","Report") } { "processor" }
"ChartOfCharacteristicTypes" { "catalog" } # реквизиты ПВХ структурно как у справочника (Use/FillFromFillingValue/DataHistory) "ChartOfCharacteristicTypes" { "cct" } # как catalog (Use/FillFromFillingValue/DataHistory), но Use ПОСЛЕ Indexing
{ $_ -in @("ChartOfAccounts","ChartOfCalculationTypes") } { "account" } # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник) { $_ -in @("ChartOfAccounts","ChartOfCalculationTypes") } { "account" } # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
default { "object" } default { "object" }
} }
@@ -4356,16 +4473,31 @@ if ($objType -in @("InformationRegister","AccumulationRegister","AccountingRegis
$regCtx = switch ($objType) { "InformationRegister" { "register-info" } "CalculationRegister" { "register-calc" } default { "register-other" } } $regCtx = switch ($objType) { "InformationRegister" { "register-info" } "CalculationRegister" { "register-calc" } default { "register-other" } }
# Все семейства регистров: ресурсы/измерения — через богатый Emit-Attribute (общий слой object-свойств). # Все семейства регистров: ресурсы/измерения — через богатый Emit-Attribute (общий слой object-свойств).
$dimResCtx = switch ($objType) { "InformationRegister" { "register-info" } "AccumulationRegister" { "register-accum" } "CalculationRegister" { "register-calc" } "AccountingRegister" { "register-account" } default { $null } } $dimResCtx = switch ($objType) { "InformationRegister" { "register-info" } "AccumulationRegister" { "register-accum" } "CalculationRegister" { "register-calc" } "AccountingRegister" { "register-account" } default { $null } }
foreach ($r in $resources) { # Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" } # типа нет): у большинства регистров Resource, Attribute, Dimension, а у
else { Emit-Resource "`t`t`t" $r $objType } # бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
} # последними, как и здесь.
foreach ($d in $dims) { $kindOrder = if ($objType -eq "AccountingRegister") { @('dim','res','attr') } else { @('res','attr','dim') }
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" } foreach ($kind in $kindOrder) {
else { Emit-Dimension "`t`t`t" $d $objType } switch ($kind) {
} 'res' {
foreach ($a in $regAttrs) { foreach ($r in $resources) {
Emit-Attribute "`t`t`t" $a $regCtx if ($dimResCtx) { Emit-Attribute "`t`t`t" $r $dimResCtx "Resource" }
else { Emit-Resource "`t`t`t" $r $objType }
}
}
'dim' {
foreach ($d in $dims) {
if ($dimResCtx) { Emit-Attribute "`t`t`t" $d $dimResCtx "Dimension" }
else { Emit-Dimension "`t`t`t" $d $objType }
}
}
'attr' {
foreach ($a in $regAttrs) {
Emit-Attribute "`t`t`t" $a $regCtx
}
}
}
} }
foreach ($cmd in $regCommands) { foreach ($cmd in $regCommands) {
Emit-Command "`t`t`t" $cmd.name $cmd.def Emit-Command "`t`t`t" $cmd.name $cmd.def
@@ -4609,9 +4741,13 @@ function Build-PredefinedXml {
$sb = New-Object System.Text.StringBuilder $sb = New-Object System.Text.StringBuilder
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n") [void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"$xsiType`" version=`"$($script:formatVersion)`">`n") [void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"$xsiType`" version=`"$($script:formatVersion)`">`n")
foreach ($it in $items) { Emit-PredefItem $sb $it "`t" $codeType } # Шапка Predefined.xml не объявляет cfg (predef/v8/xr/xs/xsi) — на время сборки этого
# файла ссылочный тип уходит на локальную форму, как делает и платформа.
$savedCfgPrefix = $script:cfgPrefix; $script:cfgPrefix = $null
try { foreach ($it in $items) { Emit-PredefItem $sb $it "`t" $codeType } }
finally { $script:cfgPrefix = $savedCfgPrefix }
[void]$sb.Append("</PredefinedData>`n") [void]$sb.Append("</PredefinedData>`n")
return $sb.ToString() return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
} }
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/ # --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
@@ -4697,9 +4833,12 @@ function Build-PredefinedAccountXml {
$sb = New-Object System.Text.StringBuilder $sb = New-Object System.Text.StringBuilder
[void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n") [void]$sb.Append("<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n")
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"ChartOfAccountsPredefinedItems`" version=`"$($script:formatVersion)`">`n") [void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"ChartOfAccountsPredefinedItems`" version=`"$($script:formatVersion)`">`n")
foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef } # См. Build-PredefinedXml: шапка этого файла cfg не объявляет.
$savedCfgPrefix = $script:cfgPrefix; $script:cfgPrefix = $null
try { foreach ($it in $items) { Emit-PredefAccount $sb $it "`t" $objName $acctFlagNames $extDimFlagNames $extDimTypesRef } }
finally { $script:cfgPrefix = $savedCfgPrefix }
[void]$sb.Append("</PredefinedData>`n") [void]$sb.Append("</PredefinedData>`n")
return $sb.ToString() return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
} }
# --- Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase). Строка "(Код) Имя [Наим]" # --- Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase). Строка "(Код) Имя [Наим]"
@@ -4726,7 +4865,7 @@ function Build-PredefinedCalcTypeXml {
[void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"CalculationTypePredefinedItems`" version=`"$($script:formatVersion)`">`n") [void]$sb.Append("<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"CalculationTypePredefinedItems`" version=`"$($script:formatVersion)`">`n")
foreach ($it in $items) { Emit-PredefCalcType $sb $it "`t" } foreach ($it in $items) { Emit-PredefCalcType $sb $it "`t" }
[void]$sb.Append("</PredefinedData>`n") [void]$sb.Append("</PredefinedData>`n")
return $sb.ToString() return ($sb.ToString() -replace "`r`n", "`n") -replace "`n", "`r`n"
} }
$extDir = Join-Path $objSubDir "Ext" $extDir = Join-Path $objSubDir "Ext"
@@ -4741,7 +4880,20 @@ if ($objType -notin $typesNoSubDir) {
} }
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($mainXmlPath, $metadataXml, $enc)
# Единая точка записи XML. Конфигуратор не пишет перевод строки в конце файла —
# последний байт `>`; сборка через AppendLine добавляла лишний.
# KeepEol в имени — отличие от одноимённой функции в скелетных навыках
# (cf-init и др.): та ЕЩЁ и нормализует EOL к CRLF, а здесь этого делать
# НЕЛЬЗЯ — в объектном XML бывают многострочные текстовые узлы (запрос,
# синоним, значение заполнения), и сплошная нормализация меняла бы содержимое.
# Разделители тут и так CRLF: документ собран через AppendLine.
# Модули .bsl сюда НЕ идут: у них свой хвост.
function Write-XmlFileKeepEol([string]$path, [string]$text, $encoding) {
[System.IO.File]::WriteAllText($path, $text.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFileKeepEol $mainXmlPath $metadataXml $enc
# Module files # Module files
$modulesCreated = @() $modulesCreated = @()
@@ -4822,8 +4974,10 @@ if ($objType -eq "CommonForm") {
$cfFormXmlPath = Join-Path $extDir "Form.xml" $cfFormXmlPath = Join-Path $extDir "Form.xml"
if (-not (Test-Path $cfFormXmlPath)) { if (-not (Test-Path $cfFormXmlPath)) {
$cfFormNs = '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: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"' $cfFormNs = '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: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"'
$cfFormXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`n<Form $cfFormNs version=`"$($script:formatVersion)`">`n`t<AutoCommandBar name=`"ФормаКоманднаяПанель`" id=`"-1`">`n`t`t<Autofill>true</Autofill>`n`t</AutoCommandBar>`n`t<ChildItems/>`n</Form>`n" # Шапка Form на 2.21 тоже несёт палитру — см. комментарий у $script:xmlnsDecl.
[System.IO.File]::WriteAllText($cfFormXmlPath, $cfFormXml, $enc) if ($script:isFormat221) { $cfFormNs = $cfFormNs -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=' }
$cfFormXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Form $cfFormNs version=`"$($script:formatVersion)`">`r`n`t<AutoCommandBar name=`"ФормаКоманднаяПанель`" id=`"-1`">`r`n`t`t<Autofill>true</Autofill>`r`n`t</AutoCommandBar>`r`n`t<ChildItems/>`r`n</Form>`r`n"
Write-XmlFileKeepEol $cfFormXmlPath $cfFormXml $enc
$modulesCreated += $cfFormXmlPath $modulesCreated += $cfFormXmlPath
} }
$cfModuleDir = Join-Path $extDir "Form" $cfModuleDir = Join-Path $extDir "Form"
@@ -4876,7 +5030,7 @@ if ($objType -eq "ExchangePlan") {
[void]$sbC.Append("`t</Item>`r`n") [void]$sbC.Append("`t</Item>`r`n")
} }
[void]$sbC.Append("</ExchangePlanContent>`r`n") [void]$sbC.Append("</ExchangePlanContent>`r`n")
[System.IO.File]::WriteAllText($contentPath, $sbC.ToString(), $enc) Write-XmlFileKeepEol $contentPath $sbC.ToString() $enc
$modulesCreated += $contentPath $modulesCreated += $contentPath
} elseif (-not (Test-Path $contentPath)) { } elseif (-not (Test-Path $contentPath)) {
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на # При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
@@ -4884,7 +5038,7 @@ if ($objType -eq "ExchangePlan") {
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.) # (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
Ensure-ExtDir Ensure-ExtDir
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent $xepNs version=`"$($script:formatVersion)`"/>`r`n" $contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent $xepNs version=`"$($script:formatVersion)`"/>`r`n"
[System.IO.File]::WriteAllText($contentPath, $contentXml, $enc) Write-XmlFileKeepEol $contentPath $contentXml $enc
$modulesCreated += $contentPath $modulesCreated += $contentPath
} }
} }
@@ -4893,7 +5047,7 @@ if ($objType -eq "BusinessProcess") {
if (-not (Test-Path $flowchartPath)) { if (-not (Test-Path $flowchartPath)) {
Ensure-ExtDir Ensure-ExtDir
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"$($script:formatVersion)`"/>`r`n" $flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"$($script:formatVersion)`"/>`r`n"
[System.IO.File]::WriteAllText($flowchartPath, $flowchartXml, $enc) Write-XmlFileKeepEol $flowchartPath $flowchartXml $enc
$modulesCreated += $flowchartPath $modulesCreated += $flowchartPath
} }
} }
@@ -4908,20 +5062,20 @@ if ($objType -eq 'ChartOfAccounts' -and $def.predefined -and @($def.predefined).
$edtRef = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { '' } $edtRef = if ($def.extDimensionTypes) { Resolve-TypePrefixSyn "$($def.extDimensionTypes)" } else { '' }
$predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames $edtRef $predefXml = Build-PredefinedAccountXml @($def.predefined) $objName $afNames $edfNames $edtRef
$predefPath = Join-Path $extDir "Predefined.xml" $predefPath = Join-Path $extDir "Predefined.xml"
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc) Write-XmlFileKeepEol $predefPath $predefXml $enc
$modulesCreated += $predefPath $modulesCreated += $predefPath
} elseif ($objType -eq 'ChartOfCalculationTypes' -and $def.predefined -and @($def.predefined).Count -gt 0) { } elseif ($objType -eq 'ChartOfCalculationTypes' -and $def.predefined -and @($def.predefined).Count -gt 0) {
Ensure-ExtDir Ensure-ExtDir
$predefXml = Build-PredefinedCalcTypeXml @($def.predefined) $predefXml = Build-PredefinedCalcTypeXml @($def.predefined)
$predefPath = Join-Path $extDir "Predefined.xml" $predefPath = Join-Path $extDir "Predefined.xml"
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc) Write-XmlFileKeepEol $predefPath $predefXml $enc
$modulesCreated += $predefPath $modulesCreated += $predefPath
} elseif ($predefRootByType.ContainsKey($objType) -and $def.predefined -and @($def.predefined).Count -gt 0) { } elseif ($predefRootByType.ContainsKey($objType) -and $def.predefined -and @($def.predefined).Count -gt 0) {
Ensure-ExtDir Ensure-ExtDir
$catCodeType = if ($def.codeType) { "$($def.codeType)" } else { 'String' } $catCodeType = if ($def.codeType) { "$($def.codeType)" } else { 'String' }
$predefXml = Build-PredefinedXml @($def.predefined) $predefRootByType[$objType] $catCodeType $predefXml = Build-PredefinedXml @($def.predefined) $predefRootByType[$objType] $catCodeType
$predefPath = Join-Path $extDir "Predefined.xml" $predefPath = Join-Path $extDir "Predefined.xml"
[System.IO.File]::WriteAllText($predefPath, $predefXml, $enc) Write-XmlFileKeepEol $predefPath $predefXml $enc
$modulesCreated += $predefPath $modulesCreated += $predefPath
} }
@@ -4990,15 +5144,31 @@ if (Test-Path $configXmlPath) {
} }
} }
# Save # Save. Пишем через MemoryStream, а не прямо в файл: нужен шаг пост-обработки
# строки — XmlWriter отдаёт `encoding="utf-8"` и `<a />`, Конфигуратор пишет
# `encoding="UTF-8"` и `<a/>`.
$cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings = New-Object System.Xml.XmlWriterSettings
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
$cfgSettings.Indent = $false $cfgSettings.Indent = $false
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) $memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
$configDoc.Save($writer) $configDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
$regResult = "added" $regResult = "added"
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-compile v1.76 — Compile 1C metadata object from JSON # meta-compile v1.88 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -209,6 +209,16 @@ def write_utf8_bom(path, content):
with open(path, 'w', encoding='utf-8-sig', newline='') as f: with open(path, 'w', encoding='utf-8-sig', newline='') as f:
f.write(content) f.write(content)
def write_xml_file_keep_eol(path, content):
# Единая точка записи XML. Конфигуратор не пишет перевод строки в конце файла —
# последний байт `>`.
# keep_eol в имени — отличие от одноимённой функции в скелетных навыках
# (cf-init и др.): та ЕЩЁ и нормализует EOL к CRLF, а здесь этого делать
# НЕЛЬЗЯ — в объектном XML бывают многострочные текстовые узлы (запрос,
# синоним, значение заполнения). Разделители и так CRLF — их даёт join строк.
# Модули .bsl сюда НЕ идут: у них свой хвост.
write_utf8_bom(path, content.rstrip('\r\n'))
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# XML builder (lines list) # XML builder (lines list)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
@@ -627,6 +637,10 @@ type_namespace_map = {
"SpreadsheetDocument": {"ns": "http://v8.1c.ru/8.2/data/spreadsheet", "prefix": "mxl"}, "SpreadsheetDocument": {"ns": "http://v8.1c.ru/8.2/data/spreadsheet", "prefix": "mxl"},
} }
# Типы current-config пространства (cfg:, объявлено в корне): голые и объектные. Ссылочные — отдельно (d5p1). # Типы current-config пространства (cfg:, объявлено в корне): голые и объектные. Ссылочные — отдельно (d5p1).
# Префикс current-config для ссылочных типов. 'cfg' — для файлов, чья шапка его объявляет
# (объектный XML, Ext/Form.xml общей формы). None на время сборки Ext/Predefined.xml, чья
# шапка его НЕ объявляет: там и платформа уходит на локальное объявление.
cfg_prefix = 'cfg'
cfg_bare_types = {"ConstantsSet", "ReportBuilder", "FilterCriterion"} cfg_bare_types = {"ConstantsSet", "ReportBuilder", "FilterCriterion"}
cfg_object_kinds = {"Catalog", "Document", "Enum", "ChartOfAccounts", "ChartOfCharacteristicTypes", cfg_object_kinds = {"Catalog", "Document", "Enum", "ChartOfAccounts", "ChartOfCharacteristicTypes",
"ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "InformationRegister", "ChartOfCalculationTypes", "ExchangePlan", "BusinessProcess", "Task", "InformationRegister",
@@ -664,10 +678,46 @@ def emit_type_content(indent, type_str):
if not type_str: if not type_str:
return return
# Composite type: "Type1 + Type2 + Type3" # Composite type: "Type1 + Type2 + Type3"
# Платформа пишет сначала ВСЕ <v8:Type>/<v8:TypeSet>, и только потом блоки
# квалификаторов — а рекурсия ниже печатала бы каждую часть целиком (тип вместе со
# своими квалификаторами). На одиночном типе оба порядка совпадают, поэтому
# расхождение вылезало только на составном.
# Порядок самих блоков квалификаторов тоже канонический и НЕ зеркалит порядок типов:
# Number, String, Date (корпус acc+erp, контрпримеров нет — при типах
# boolean,string,dateTime,decimal квалификаторы идут Number,String,Date).
# Порядок типов при этом сохраняем как в DSL: он и есть порядок источника.
if ' + ' in type_str: if ' + ' in type_str:
parts = [p.strip() for p in type_str.split('+')] parts = [p.strip() for p in type_str.split('+')]
type_lines = []
qual_blocks = {}
for part in parts: for part in parts:
# X добавляет в список lines, поэтому «перехват» — это срез и откат хвоста.
# В PS-порту X пишет в StringBuilder и тот же алгоритм выражен через
# Length/Remove — различие рантаймов, не логики.
before = len(lines)
emit_type_content(indent, part) emit_type_content(indent, part)
chunk = lines[before:]
del lines[before:]
cur_qual = None
for line in chunk:
if not line:
continue
m = re.search(r'<v8:(String|Number|Date)Qualifiers>', line)
if m:
cur_qual = m.group(1)
qual_blocks[cur_qual] = []
if cur_qual:
qual_blocks[cur_qual].append(line)
if re.search(r'</v8:(String|Number|Date)Qualifiers>', line):
cur_qual = None
else:
type_lines.append(line)
for line in type_lines:
X(line)
for q in ('Number', 'String', 'Date'):
if q in qual_blocks:
for line in qual_blocks[q]:
X(line)
return return
type_str = resolve_type_str(type_str) type_str = resolve_type_str(type_str)
# Boolean # Boolean
@@ -760,10 +810,22 @@ def emit_type_content(indent, type_str):
X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>') X(f'{indent}<v8:Type>cfg:{type_str}</v8:Type>')
return return
# Reference types — use local xmlns declaration for 1C compatibility # Ссылочные типы — корневой cfg:, как пишет платформа. Раньше здесь объявлялся
# ЛОКАЛЬНЫЙ xmlns:d5p1 на тот же URI, что уже объявлен в шапке: формально
# эквивалентно (значим URI, не префикс) и платформой принималось, но первый же
# цикл «загрузить в базу → выгрузить» переписывал каждый ссылочный тип в cfg: —
# то есть давал diff-шум на ровном месте. Форма пришла из СКД, где cfg:
# действительно не работает; в метаданных такого ограничения нет.
# NB: локальная xmlns остаётся законной для ЧУЖИХ пространств — см. type_namespace_map.
# cfg_prefix = None означает «пишем файл, корень которого cfg НЕ объявляет»
# (Ext/Predefined.xml — его шапка это predef/v8/xr/xs/xsi). Там платформа сама уходит
# на локальную форму: в корпусе `<v8:Type xmlns:d6p1="…current-config">d6p1:CatalogRef.Валюты`.
m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$', type_str) m = re.match(r'^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|BusinessProcessRoutePointRef|TaskRef)\.(.+)$', type_str)
if m: if m:
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>') if cfg_prefix:
X(f'{indent}<v8:Type>{cfg_prefix}:{type_str}</v8:Type>')
else:
X(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>')
return return
# Fallback # Fallback
X(f'{indent}<v8:Type>{type_str}</v8:Type>') X(f'{indent}<v8:Type>{type_str}</v8:Type>')
@@ -1271,14 +1333,21 @@ standard_attributes_by_type = {
'Enum': ['Order', 'Ref'], 'Enum': ['Order', 'Ref'],
'InformationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'], 'InformationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
'AccumulationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'], 'AccumulationRegister': ['Active', 'LineNumber', 'Recorder', 'Period'],
'AccountingRegister': ['Active', 'Period', 'Recorder', 'LineNumber', 'Account'], 'AccountingRegister': ['PeriodAdjustment', 'Account', 'Active', 'LineNumber', 'Recorder', 'Period'],
'CalculationRegister': ['Active', 'Recorder', 'LineNumber', 'RegistrationPeriod', 'CalculationType', 'ReversingEntry'], 'CalculationRegister': ['Active', 'Recorder', 'LineNumber', 'RegistrationPeriod', 'CalculationType', 'ReversingEntry'],
'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'], 'ChartOfAccounts': ['PredefinedDataName', 'Order', 'OffBalance', 'Type', 'Description', 'Code', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
'ChartOfCharacteristicTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'Description', 'Code', 'Parent', 'ValueType'], 'ChartOfCharacteristicTypes': ['PredefinedDataName', 'ValueType', 'Description', 'Code', 'IsFolder', 'Parent', 'Predefined', 'DeletionMark', 'Ref'],
'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'], 'ChartOfCalculationTypes': ['PredefinedDataName', 'Predefined', 'Ref', 'DeletionMark', 'ActionPeriodIsBasic', 'Description', 'Code'],
'BusinessProcess': ['Ref', 'DeletionMark', 'Date', 'Number', 'Started', 'Completed', 'HeadTask'], # Порядок в каждом списке — канон выгрузки, снят с корпуса acc+erp (внутри типа разброса нет).
'Task': ['Ref', 'DeletionMark', 'Date', 'Number', 'Executed', 'Description', 'RoutePoint', 'BusinessProcess'], # У ПВХ IsFolder входит в фикс-список: он есть у всех 23 объектов корпуса с этим блоком.
'ExchangePlan': ['Ref', 'DeletionMark', 'Code', 'Description', 'ThisNode', 'SentNo', 'ReceivedNo'], # У бухрегистра PeriodAdjustment, наоборот, условен (1 из 3) — он приходит как «лишний»
# ключ и эмитится ПЕРЕД фикс-списком, что совпадает с платформой.
'BusinessProcess': ['Started', 'HeadTask', 'Completed', 'Ref', 'DeletionMark', 'Date', 'Number'],
'Task': ['Executed', 'Description', 'RoutePoint', 'BusinessProcess', 'Ref', 'DeletionMark', 'Date', 'Number'],
# Порядок снят с выгрузки: у плана обмена блок начинается с ThisNode, а не с Ref
# (acc+erp, 8 объектов, разброса нет). Прочие типы в этой таблице совпадают с
# платформой — расхождений порядка по ним корпусный раундтрип не показал.
'ExchangePlan': ['ThisNode', 'ReceivedNo', 'SentNo', 'Ref', 'DeletionMark', 'Description', 'Code'],
'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'], 'DocumentJournal': ['Type', 'Ref', 'Date', 'Posted', 'DeletionMark', 'Number'],
} }
@@ -1398,6 +1467,20 @@ def emit_standard_attribute(indent, attr_name, ov=None):
# std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes). # std_attr_conditional_types: типы, где блок только при кастомизации (DSL-ключ standardAttributes).
# Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты. # Прочие типы → блок всегда (текущее поведение). Миграция типа = +строчка в оба справочника + снэпшоты.
std_attr_conditional_types = {'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'} std_attr_conditional_types = {'Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document'}
# Условные члены списка типа: позиция берётся из standard_attributes_by_type, а сам
# реквизит эмитится только при наличии ключа в DSL (у бухрегистра PeriodAdjustment — 2 из 5).
std_attr_optional = {
'AccountingRegister': ('PeriodAdjustment',),
}
# Хвостовая группа: реквизиты, которых нет в списке типа и которые идут ПОСЛЕ него.
# У бухрегистра это пары субконто. Именами их не перечислить: их количество задаётся
# свойством MaxExtDimensionCount плана счетов, а не константой (в корпусе везде 3, но
# это однородность выборки, а не правило). Поэтому — шаблон, а не список.
std_attr_tail_pattern = {
'AccountingRegister': r'^ExtDimension(Type)?\d+$',
}
def emit_standard_attributes(indent, object_type): def emit_standard_attributes(indent, object_type):
attrs = standard_attributes_by_type.get(object_type) attrs = standard_attributes_by_type.get(object_type)
if not attrs: if not attrs:
@@ -1409,11 +1492,30 @@ def emit_standard_attributes(indent, object_type):
if isinstance(sa, str) and sa == '': if isinstance(sa, str) and sa == '':
return # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок) return # opt-out `standardAttributes:""` (дом-конвенция суппресса, ~5% регистров опускают all-default блок)
profile = std_attr_profile.get(object_type, {}) profile = std_attr_profile.get(object_type, {})
# Доп. (опциональные) стандартные реквизиты вне фикс-списка — напр. ExchangeDate у части ПланОбмена # Список типа задаёт ПОРЯДОК всех известных стандартных реквизитов, включая условные:
# (легаси, присутствие не выводится). Эмитим по факту ключа в DSL, ПЕРЕД фикс-списком (их позиция). # их позиция бывает и до, и после обязательных (у бухрегистра PeriodAdjustment идёт
extra = [k for k in sa if k not in attrs] if isinstance(sa, dict) else [] # перед Account, а ExtDimension1..3/ExtDimensionType1..3 — после Period), поэтому
# «условные скопом вперёд» не выражает канон. Условный эмитим по факту наличия в DSL.
optional = std_attr_optional.get(object_type, ())
# Ключи, которых нет в списке типа ВООБЩЕ. По умолчанию их позиция — ПЕРЕД списком
# (легаси вроде ExchangeDate у части планов обмена). Подходящие под хвостовой шаблон
# типа идут ПОСЛЕ, в порядке номера, а внутри номера — сначала ExtDimensionN, затем
# ExtDimensionTypeN (порядок платформы).
tail_re = std_attr_tail_pattern.get(object_type)
extra, tail = [], []
if isinstance(sa, dict):
for k in sa:
if k in attrs:
continue
if tail_re and re.match(tail_re, k):
tail.append(k)
else:
extra.append(k)
tail.sort(key=lambda k: (int(re.search(r'\d+', k).group()), 1 if re.search(r'Type\d+$', k) else 0))
X(f'{indent}<StandardAttributes>') X(f'{indent}<StandardAttributes>')
for a in extra + list(attrs): for a in extra + list(attrs) + tail:
if a in optional and not (isinstance(sa, dict) and a in sa):
continue
ov = dict(profile.get(a, {})) ov = dict(profile.get(a, {}))
if isinstance(sa, dict): if isinstance(sa, dict):
d = sa.get(a) d = sa.get(a)
@@ -2016,8 +2118,13 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
X(f'{indent}\t\t<ExtDimensionAccountingFlag>{esc_xml_text(str(parsed["extDimensionAccountingFlag"]))}</ExtDimensionAccountingFlag>') X(f'{indent}\t\t<ExtDimensionAccountingFlag>{esc_xml_text(str(parsed["extDimensionAccountingFlag"]))}</ExtDimensionAccountingFlag>')
else: else:
X(f'{indent}\t\t<ExtDimensionAccountingFlag/>') X(f'{indent}\t\t<ExtDimensionAccountingFlag/>')
# Use — у реквизитов справочника и ПВХ. Позиция РАЗНАЯ: справочник пишет Use ПЕРЕД
# Indexing, ПВХ — ПОСЛЕ него (корпус acc+erp: Catalog `Use,Indexing,FullTextSearch`,
# ПВХ `Indexing,Use,FullTextSearch,DataHistory`). Отсюда отдельный контекст 'cct':
# структурно реквизит ПВХ совпадает со справочником, расходится только этим порядком.
use_value = parsed.get("use") or "ForItem"
if context == 'catalog': if context == 'catalog':
X(f'{indent}\t\t<Use>{parsed.get("use") or "ForItem"}</Use>') X(f'{indent}\t\t<Use>{use_value}</Use>')
if context not in ('processor', 'processor-tabular'): if context not in ('processor', 'processor-tabular'):
# Признаки учёта ПС (account-flag) не имеют <Indexing>/<FullTextSearch>, но имеют <DataHistory>. # Признаки учёта ПС (account-flag) не имеют <Indexing>/<FullTextSearch>, но имеют <DataHistory>.
if context != 'account-flag': if context != 'account-flag':
@@ -2031,6 +2138,8 @@ def emit_attribute(indent, parsed, context, elem_tag='Attribute'):
if parsed.get('indexing'): if parsed.get('indexing'):
indexing = parsed['indexing'] indexing = parsed['indexing']
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>') X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
if context == 'cct':
X(f'{indent}\t\t<Use>{use_value}</Use>')
# Реквизит адресации задачи: AddressingDimension (между Indexing и FullTextSearch). # Реквизит адресации задачи: AddressingDimension (между Indexing и FullTextSearch).
if context == 'task-addressing' and elem_tag == 'AddressingAttribute': if context == 'task-addressing' and elem_tag == 'AddressingAttribute':
if parsed.get('addressingDimension'): if parsed.get('addressingDimension'):
@@ -2187,6 +2296,10 @@ def emit_enum_value(indent, parsed):
X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>') X(f'{indent}\t\t<Comment>{esc_xml_text(parsed["comment"])}</Comment>')
else: else:
X(f'{indent}\t\t<Comment/>') X(f'{indent}\t\t<Comment/>')
# Цвет значения перечисления — свойство формата 2.21 (8.5), последним в Properties.
if is_format_221:
color = str(parsed['color']) if parsed.get('color') else 'auto'
X(f'{indent}\t\t<Color>{esc_xml_text(color)}</Color>')
X(f'{indent}\t</Properties>') X(f'{indent}\t</Properties>')
X(f'{indent}</EnumValue>') X(f'{indent}</EnumValue>')
@@ -2811,6 +2924,12 @@ def emit_common_form_properties(indent):
X(f'{i}</UsePurposes>') X(f'{i}</UsePurposes>')
else: else:
X(f'{i}<UsePurposes/>') X(f'{i}<UsePurposes/>')
# Использование в режиме совместимости интерфейса — свойство формата 2.21 (8.5),
# между UsePurposes и UseStandardCommands.
if is_format_221:
X(f'{i}<UseInInterfaceCompatibilityMode>'
f'{get_enum_prop("UseInInterfaceCompatibilityMode", "useInInterfaceCompatibilityMode", "Any")}'
f'</UseInInterfaceCompatibilityMode>')
use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false' use_std_cmds = 'true' if get_bool_prop('useStandardCommands', False) else 'false'
X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>') X(f'{i}<UseStandardCommands>{use_std_cmds}</UseStandardCommands>')
emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation')) emit_mltext(i, 'ExtendedPresentation', defn.get('extendedPresentation'))
@@ -3069,7 +3188,12 @@ def emit_scheduled_job_properties(indent):
else: else:
X(f'{i}<Description/>') X(f'{i}<Description/>')
key = str(defn['key']) if defn.get('key') else '' key = str(defn['key']) if defn.get('key') else ''
X(f'{i}<Key>{esc_xml_text(key)}</Key>') # Пустое значение → самозакрывающийся, как у <Description> выше: Конфигуратор
# не пишет пустых пар.
if key:
X(f'{i}<Key>{esc_xml_text(key)}</Key>')
else:
X(f'{i}<Key/>')
use = 'true' if defn.get('use') is True else 'false' use = 'true' if defn.get('use') is True else 'false'
X(f'{i}<Use>{use}</Use>') X(f'{i}<Use>{use}</Use>')
predefined = 'true' if defn.get('predefined') is True else 'false' predefined = 'true' if defn.get('predefined') is True else 'false'
@@ -3123,6 +3247,9 @@ def emit_report_properties(indent):
emit_verbatim_ref(i, 'DefaultSettingsForm', defn.get('defaultSettingsForm')) emit_verbatim_ref(i, 'DefaultSettingsForm', defn.get('defaultSettingsForm'))
emit_verbatim_ref(i, 'AuxiliarySettingsForm', defn.get('auxiliarySettingsForm')) emit_verbatim_ref(i, 'AuxiliarySettingsForm', defn.get('auxiliarySettingsForm'))
emit_verbatim_ref(i, 'DefaultVariantForm', defn.get('defaultVariantForm')) emit_verbatim_ref(i, 'DefaultVariantForm', defn.get('defaultVariantForm'))
# Вспомогательная форма варианта отчёта — свойство формата 2.21 (8.5).
if is_format_221:
emit_verbatim_ref(i, 'AuxiliaryVariantForm', defn.get('auxiliaryVariantForm'))
emit_verbatim_ref(i, 'VariantsStorage', defn.get('variantsStorage')) emit_verbatim_ref(i, 'VariantsStorage', defn.get('variantsStorage'))
emit_verbatim_ref(i, 'SettingsStorage', defn.get('settingsStorage')) emit_verbatim_ref(i, 'SettingsStorage', defn.get('settingsStorage'))
incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false' incl_help = 'true' if get_bool_prop('includeHelpInContents', False) else 'false'
@@ -3760,7 +3887,8 @@ def emit_web_service_properties(indent):
emit_mltext(i, 'Synonym', synonym) emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>') X(f'{i}<Comment>{esc_xml_text(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>')
namespace = str(defn['namespace']) if defn.get('namespace') else '' namespace = str(defn['namespace']) if defn.get('namespace') else ''
X(f'{i}<Namespace>{esc_xml_text(namespace)}</Namespace>') # Пустое значение → самозакрывающийся, как у <Comment> выше.
X(f'{i}<Namespace>{esc_xml_text(namespace)}</Namespace>' if namespace else f'{i}<Namespace/>')
# XDTOPackages — СПИСОК элементов: ссылка на пакет конфигурации (xr:MDObjectRef) либо URI # XDTOPackages — СПИСОК элементов: ссылка на пакет конфигурации (xr:MDObjectRef) либо URI
# внешнего пространства имён (xs:string). Presentation пуст, CheckState 0 (корпус: 19/19). # внешнего пространства имён (xs:string). Presentation пуст, CheckState 0 (корпус: 19/19).
pkgs = defn.get('xdtoPackages') or [] pkgs = defn.get('xdtoPackages') or []
@@ -4034,6 +4162,15 @@ compat_mode = detect_compatibility_mode(output_dir)
# глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался. # глушил бы TypeReductionMode, который платформа этих версий пишет, и роундтрип бы разъезжался.
is_format_218 = format_rank(format_version) >= 218 is_format_218 = format_rank(format_version) >= 218
is_format_220 = format_rank(format_version) >= 220 is_format_220 = format_rank(format_version) >= 220
is_format_221 = format_rank(format_version) >= 221
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту.
# Только для шапок MetaDataObject и Form — в файлах с корнем extrnprops
# (Ext/ClientApplicationInterface.xml и т.п.) платформа его не пишет.
if is_format_221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
# Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами. # Дефолт длины номера строки ТЧ: с режима 8.3.27 платформа заводит новые ТЧ с 9 разрядами.
line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5 line_number_length_default = 9 if compat_mode_rank(compat_mode) >= 80327 else 5
@@ -4174,7 +4311,7 @@ if obj_type in types_with_attr_ts:
elif obj_type in ('DataProcessor', 'Report'): elif obj_type in ('DataProcessor', 'Report'):
context = 'processor' context = 'processor'
elif obj_type == 'ChartOfCharacteristicTypes': elif obj_type == 'ChartOfCharacteristicTypes':
context = 'catalog' # реквизиты ПВХ структурно как у справочника (Use/FillFromFillingValue/DataHistory) context = 'cct' # как catalog (Use/FillFromFillingValue/DataHistory), но Use ПОСЛЕ Indexing
elif obj_type in ('ChartOfAccounts', 'ChartOfCalculationTypes'): elif obj_type in ('ChartOfAccounts', 'ChartOfCalculationTypes'):
context = 'account' # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник) context = 'account' # как catalog, но БЕЗ <Use> (реквизиты ПС/ПВР не иерархичны как справочник)
else: else:
@@ -4245,18 +4382,27 @@ if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegist
# Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств). # Все семейства регистров: ресурсы/измерения — через богатый emit_attribute (общий слой object-свойств).
dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum', dim_res_ctx = {'InformationRegister': 'register-info', 'AccumulationRegister': 'register-accum',
'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type) 'CalculationRegister': 'register-calc', 'AccountingRegister': 'register-account'}.get(obj_type)
for r in resources: # Порядок видов детей — канон выгрузки, снят с корпуса (acc+erp, разброса внутри
if dim_res_ctx: # типа нет): у большинства регистров Resource, Attribute, Dimension, а у
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource') # бухгалтерского — Dimension, Resource, Attribute. Команды у платформы идут
# последними, как и здесь.
kind_order = ['dim', 'res', 'attr'] if obj_type == 'AccountingRegister' else ['res', 'attr', 'dim']
for kind in kind_order:
if kind == 'res':
for r in resources:
if dim_res_ctx:
emit_attribute('\t\t\t', r, dim_res_ctx, 'Resource')
else:
emit_resource('\t\t\t', r, obj_type)
elif kind == 'dim':
for d in dims:
if dim_res_ctx:
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
else:
emit_dimension('\t\t\t', d, obj_type)
else: else:
emit_resource('\t\t\t', r, obj_type) for a in reg_attrs:
for d in dims: emit_attribute('\t\t\t', a, reg_ctx)
if dim_res_ctx:
emit_attribute('\t\t\t', d, dim_res_ctx, 'Dimension')
else:
emit_dimension('\t\t\t', d, obj_type)
for a in reg_attrs:
emit_attribute('\t\t\t', a, reg_ctx)
for cmd in reg_commands: for cmd in reg_commands:
emit_command('\t\t\t', cmd['name'], cmd['def']) emit_command('\t\t\t', cmd['name'], cmd['def'])
X('\t\t</ChildObjects>') X('\t\t</ChildObjects>')
@@ -4360,7 +4506,7 @@ if obj_type == 'WebService':
X(f'\t</{obj_type}>') X(f'\t</{obj_type}>')
X('</MetaDataObject>') X('</MetaDataObject>')
metadata_xml = '\n'.join(lines) + '\n' metadata_xml = '\r\n'.join(lines)
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# 16. Write files # 16. Write files
@@ -4422,7 +4568,7 @@ os.makedirs(type_dir, exist_ok=True)
if obj_type not in types_no_sub_dir: if obj_type not in types_no_sub_dir:
os.makedirs(obj_sub_dir, exist_ok=True) os.makedirs(obj_sub_dir, exist_ok=True)
write_utf8_bom(main_xml_path, metadata_xml) write_xml_file_keep_eol(main_xml_path, metadata_xml)
# Module files # Module files
modules_created = [] modules_created = []
@@ -4498,10 +4644,14 @@ if obj_type == 'CommonForm':
'xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" ' '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: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"') 'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
cf_form_xml = ('<?xml version="1.0" encoding="UTF-8"?>\n<Form ' + cf_ns + ' version="' + format_version + '">\n' # Шапка Form на 2.21 тоже несёт палитру — см. комментарий у xmlns_decl.
'\t<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">\n\t\t<Autofill>true</Autofill>\n\t</AutoCommandBar>\n' if is_format_221:
'\t<ChildItems/>\n</Form>\n') cf_ns = cf_ns.replace(' xmlns:style=',
write_utf8_bom(cf_form_xml_path, cf_form_xml) ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
cf_form_xml = ('<?xml version="1.0" encoding="UTF-8"?>\r\n<Form ' + cf_ns + ' version="' + format_version + '">\r\n'
'\t<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">\r\n\t\t<Autofill>true</Autofill>\r\n\t</AutoCommandBar>\r\n'
'\t<ChildItems/>\r\n</Form>\r\n')
write_xml_file_keep_eol(cf_form_xml_path, cf_form_xml)
modules_created.append(cf_form_xml_path) modules_created.append(cf_form_xml_path)
cf_module_dir = os.path.join(ext_dir, 'Form') cf_module_dir = os.path.join(ext_dir, 'Form')
os.makedirs(cf_module_dir, exist_ok=True) os.makedirs(cf_module_dir, exist_ok=True)
@@ -4589,10 +4739,18 @@ def emit_predef_item(out, val, indent, code_type):
def build_predefined_xml(items, xsi_type, code_type): def build_predefined_xml(items, xsi_type, code_type):
out = ['<?xml version="1.0" encoding="UTF-8"?>'] out = ['<?xml version="1.0" encoding="UTF-8"?>']
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" 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" xsi:type="{xsi_type}" version="{format_version}">') out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" 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" xsi:type="{xsi_type}" version="{format_version}">')
for it in items: # Шапка Predefined.xml не объявляет cfg (predef/v8/xr/xs/xsi) — на время сборки этого
emit_predef_item(out, it, '\t', code_type) # файла ссылочный тип уходит на локальную форму, как делает и платформа.
global cfg_prefix
saved_cfg_prefix = cfg_prefix
cfg_prefix = None
try:
for it in items:
emit_predef_item(out, it, '\t', code_type)
finally:
cfg_prefix = saved_cfg_prefix
out.append('</PredefinedData>') out.append('</PredefinedData>')
return '\n'.join(out) + '\n' return '\r\n'.join(out)
# --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/ # --- Предопределённые СЧЕТА Плана счетов (отдельная грамматика: AccountType/OffBalance/Order/AccountingFlags/
# ExtDimensionTypes/ChildItems). Флаги перечисляем по def-порядку признаков плана; в DSL — только TRUE. --- # ExtDimensionTypes/ChildItems). Флаги перечисляем по def-порядку признаков плана; в DSL — только TRUE. ---
@@ -4687,10 +4845,17 @@ def emit_predef_account(out, val, indent, obj_nm, acct_flag_names, ext_dim_flag_
def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''): def build_predefined_account_xml(items, obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref=''):
out = ['<?xml version="1.0" encoding="UTF-8"?>'] out = ['<?xml version="1.0" encoding="UTF-8"?>']
out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" 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" xsi:type="ChartOfAccountsPredefinedItems" version="{format_version}">') out.append(f'<PredefinedData xmlns="http://v8.1c.ru/8.3/xcf/predef" xmlns:v8="http://v8.1c.ru/8.1/data/core" 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" xsi:type="ChartOfAccountsPredefinedItems" version="{format_version}">')
for it in items: # См. build_predefined_xml: шапка этого файла cfg не объявляет.
emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref) global cfg_prefix
saved_cfg_prefix = cfg_prefix
cfg_prefix = None
try:
for it in items:
emit_predef_account(out, it, '\t', obj_nm, acct_flag_names, ext_dim_flag_names, ext_dim_types_ref)
finally:
cfg_prefix = saved_cfg_prefix
out.append('</PredefinedData>') out.append('</PredefinedData>')
return '\n'.join(out) + '\n' return '\r\n'.join(out)
# Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase). # Предопределённые ВИДЫ РАСЧЁТА (плоские: Name/Code/Description/ActionPeriodIsBase).
def emit_predef_calc_type(out, val, indent): def emit_predef_calc_type(out, val, indent):
@@ -4712,7 +4877,7 @@ def build_predefined_calc_type_xml(items):
for it in items: for it in items:
emit_predef_calc_type(out, it, '\t') emit_predef_calc_type(out, it, '\t')
out.append('</PredefinedData>') out.append('</PredefinedData>')
return '\n'.join(out) + '\n' return '\r\n'.join(out)
# Special files # Special files
# --- Состав плана обмена (ExchangePlan, Ext/Content.xml). Ключ `content`/`Состав`: # --- Состав плана обмена (ExchangePlan, Ext/Content.xml). Ключ `content`/`Состав`:
@@ -4766,7 +4931,7 @@ if obj_type == 'ExchangePlan':
parts.append(f'\t\t<AutoRecord>{it["autoRecord"]}</AutoRecord>\r\n') parts.append(f'\t\t<AutoRecord>{it["autoRecord"]}</AutoRecord>\r\n')
parts.append('\t</Item>\r\n') parts.append('\t</Item>\r\n')
parts.append('</ExchangePlanContent>\r\n') parts.append('</ExchangePlanContent>\r\n')
write_utf8_bom(content_path, ''.join(parts)) write_xml_file_keep_eol(content_path, ''.join(parts))
modules_created.append(content_path) modules_created.append(content_path)
elif not os.path.isfile(content_path): elif not os.path.isfile(content_path):
# При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на # При пустом составе платформа всё равно пишет пустой <ExchangePlanContent/> — проверено на
@@ -4774,7 +4939,7 @@ if obj_type == 'ExchangePlan':
# (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.) # (Единственный план обмена БЕЗ файла найден в УТ — хвостовая аномалия конфигурации, не правило.)
ensure_ext_dir() ensure_ext_dir()
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent {xep_ns} version="{format_version}"/>\r\n' content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent {xep_ns} version="{format_version}"/>\r\n'
write_utf8_bom(content_path, content_xml) write_xml_file_keep_eol(content_path, content_xml)
modules_created.append(content_path) modules_created.append(content_path)
if obj_type == 'BusinessProcess': if obj_type == 'BusinessProcess':
@@ -4782,7 +4947,7 @@ if obj_type == 'BusinessProcess':
if not os.path.isfile(flowchart_path): if not os.path.isfile(flowchart_path):
ensure_ext_dir() ensure_ext_dir()
flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n' flowchart_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="{format_version}"/>\r\n'
write_utf8_bom(flowchart_path, flowchart_xml) write_xml_file_keep_eol(flowchart_path, flowchart_xml)
modules_created.append(flowchart_path) modules_created.append(flowchart_path)
# Предопределённые элементы (Ext/Predefined.xml). Root-элемент по типу. # Предопределённые элементы (Ext/Predefined.xml). Root-элемент по типу.
@@ -4795,20 +4960,20 @@ if obj_type == 'ChartOfAccounts' and defn.get('predefined'):
edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else '' edt_ref = resolve_type_prefix_syn(str(defn['extDimensionTypes'])) if defn.get('extDimensionTypes') else ''
predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref) predef_xml = build_predefined_account_xml(defn['predefined'], obj_name, af_names, edf_names, edt_ref)
predef_path = os.path.join(ext_dir, 'Predefined.xml') predef_path = os.path.join(ext_dir, 'Predefined.xml')
write_utf8_bom(predef_path, predef_xml) write_xml_file_keep_eol(predef_path, predef_xml)
modules_created.append(predef_path) modules_created.append(predef_path)
elif obj_type == 'ChartOfCalculationTypes' and defn.get('predefined'): elif obj_type == 'ChartOfCalculationTypes' and defn.get('predefined'):
ensure_ext_dir() ensure_ext_dir()
predef_xml = build_predefined_calc_type_xml(defn['predefined']) predef_xml = build_predefined_calc_type_xml(defn['predefined'])
predef_path = os.path.join(ext_dir, 'Predefined.xml') predef_path = os.path.join(ext_dir, 'Predefined.xml')
write_utf8_bom(predef_path, predef_xml) write_xml_file_keep_eol(predef_path, predef_xml)
modules_created.append(predef_path) modules_created.append(predef_path)
elif obj_type in predef_root_by_type and defn.get('predefined'): elif obj_type in predef_root_by_type and defn.get('predefined'):
ensure_ext_dir() ensure_ext_dir()
cat_code_type = str(defn['codeType']) if defn.get('codeType') else 'String' cat_code_type = str(defn['codeType']) if defn.get('codeType') else 'String'
predef_xml = build_predefined_xml(defn['predefined'], predef_root_by_type[obj_type], cat_code_type) predef_xml = build_predefined_xml(defn['predefined'], predef_root_by_type[obj_type], cat_code_type)
predef_path = os.path.join(ext_dir, 'Predefined.xml') predef_path = os.path.join(ext_dir, 'Predefined.xml')
write_utf8_bom(predef_path, predef_xml) write_xml_file_keep_eol(predef_path, predef_xml)
modules_created.append(predef_path) modules_created.append(predef_path)
# Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика. # Модули команд (Commands/<Имя>/Ext/CommandModule.bsl) — заготовка обработчика.
+43 -4
View File
@@ -1,4 +1,4 @@
# meta-edit v1.24 — Edit existing 1C metadata object XML # meta-edit v1.31 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -313,6 +313,23 @@ function Info($msg) {
# ============================================================ # ============================================================
$root = $script:xmlDoc.DocumentElement $root = $script:xmlDoc.DocumentElement
# Префикс пространства current-config, объявленный в КОРНЕ файла (у платформы — cfg).
# Ищем по объявлениям корня, а не через GetPrefixOfNamespace: ссылочный тип живёт в
# ТЕКСТЕ узла, поэтому XML-слой этот префикс не отслеживает. $null = корень URI не
# объявляет → эмиттер остаётся на самодостаточной локальной форме.
# Версия формата правимого файла — из его же корня. Нужна эмиттерам: часть свойств
# появилась в поздних версиях (напр. <Color> у значения перечисления — в 2.21).
$script:formatVersion = $root.GetAttribute("version")
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
$script:isFormat221 = ($script:formatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221
$script:cfgUri = 'http://v8.1c.ru/8.1/data/enterprise/current-config'
$script:cfgPrefix = $null
foreach ($a in $root.Attributes) {
if ($a.Prefix -eq 'xmlns' -and $a.Value -eq $script:cfgUri) { $script:cfgPrefix = $a.LocalName; break }
}
if ($root.LocalName -ne "MetaDataObject") { if ($root.LocalName -ne "MetaDataObject") {
Write-Error "Root element must be MetaDataObject, got: $($root.LocalName)" Write-Error "Root element must be MetaDataObject, got: $($root.LocalName)"
exit 1 exit 1
@@ -557,9 +574,19 @@ function Build-TypeContentXml {
return $sb.ToString().TrimEnd("`r","`n") return $sb.ToString().TrimEnd("`r","`n")
} }
# Reference types — use local xmlns declaration for 1C compatibility # Ссылочные типы — префиксом, объявленным в КОРНЕ файла (у платформы это cfg).
# Раньше здесь всегда объявлялся локальный xmlns:d5p1 на тот же URI, что уже есть
# в шапке: платформа принимала, но при цикле «загрузить в базу → выгрузить»
# переписывала каждый ссылочный тип в cfg: — diff-шум на ровном месте.
# Если корень URI не объявляет (файл не от платформы), остаёмся на самодостаточной
# локальной форме: префикс тут — ТЕКСТ узла, XML-слой про него не знает и сам
# объявление не добавит, так что иначе получился бы неразрешимый префикс.
if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') { if ($typeStr -match '^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$') {
$sb.AppendLine("$indent<v8:Type xmlns:d5p1=`"http://v8.1c.ru/8.1/data/enterprise/current-config`">d5p1:$typeStr</v8:Type>") | Out-Null if ($script:cfgPrefix) {
$sb.AppendLine("$indent<v8:Type>$($script:cfgPrefix):$typeStr</v8:Type>") | Out-Null
} else {
$sb.AppendLine("$indent<v8:Type xmlns:d5p1=`"$script:cfgUri`">d5p1:$typeStr</v8:Type>") | Out-Null
}
return $sb.ToString().TrimEnd("`r","`n") return $sb.ToString().TrimEnd("`r","`n")
} }
@@ -1279,6 +1306,9 @@ function Build-EnumValueFragment {
$sb.AppendLine("$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>") | Out-Null $sb.AppendLine("$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>") | Out-Null
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null $sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
$sb.AppendLine("$indent`t`t<Comment/>") | Out-Null $sb.AppendLine("$indent`t`t<Comment/>") | Out-Null
# Цвет значения — свойство формата 2.21 (8.5). Без него добавленное значение
# отличалось бы от соседних, написанных платформой.
if ($script:isFormat221) { $sb.AppendLine("$indent`t`t<Color>auto</Color>") | Out-Null }
$sb.AppendLine("$indent`t</Properties>") | Out-Null $sb.AppendLine("$indent`t</Properties>") | Out-Null
$sb.Append("$indent</EnumValue>") | Out-Null $sb.Append("$indent</EnumValue>") | Out-Null
return $sb.ToString() return $sb.ToString()
@@ -3138,7 +3168,8 @@ function Add-PredefinedItems($items) {
$hdr = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"$xsiType`" version=`"$version`">`r`n" $hdr = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<PredefinedData xmlns=`"http://v8.1c.ru/8.3/xcf/predef`" xmlns:v8=`"http://v8.1c.ru/8.1/data/core`" 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`" xsi:type=`"$xsiType`" version=`"$version`">`r`n"
$text = "$hdr$itemsXml</PredefinedData>`r`n" $text = "$hdr$itemsXml</PredefinedData>`r`n"
} }
[System.IO.File]::WriteAllText($path, $text, $utf8Bom) # Создаваемый файл — по канону: без перевода строки в конце.
[System.IO.File]::WriteAllText($path, $text.TrimEnd("`r", "`n"), $utf8Bom)
$n = @($items).Count $n = @($items).Count
Info "Added $n predefined item(s) → $path" Info "Added $n predefined item(s) → $path"
$script:addCount += $n $script:addCount += $n
@@ -3208,9 +3239,17 @@ if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) {
$text = $text.Substring(1) $text = $text.Substring(1)
} }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Write with BOM # Write with BOM
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath" Info "Saved: $resolvedPath"
+46 -18
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-edit v1.24 — Edit existing 1C metadata object XML # meta-edit v1.31 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -198,6 +198,12 @@ V8_NS = "http://v8.1c.ru/8.1/data/core"
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance" XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
XS_NS = "http://www.w3.org/2001/XMLSchema" XS_NS = "http://www.w3.org/2001/XMLSchema"
CFG_NS = "http://v8.1c.ru/8.1/data/enterprise/current-config" CFG_NS = "http://v8.1c.ru/8.1/data/enterprise/current-config"
# Версия формата правимого файла — из его же корня. Нужна эмиттерам: часть свойств
# появилась в поздних версиях (напр. <Color> у значения перечисления — в 2.21).
is_format_221 = False
# Префикс current-config, объявленный в КОРНЕ правимого файла (у платформы — cfg).
# None = корень его не объявляет → эмиттер ссылочных типов остаётся на локальной форме.
cfg_prefix = None
NSMAP_WRAPPER = { NSMAP_WRAPPER = {
None: MD_NS, None: MD_NS,
@@ -525,14 +531,23 @@ def build_type_content_xml(indent, type_str):
lines.append(f"{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>") lines.append(f"{indent}<v8:TypeSet>cfg:DefinedType.{dt_name}</v8:TypeSet>")
return "\r\n".join(lines) return "\r\n".join(lines)
# Reference types — use local xmlns declaration for 1C compatibility # Ссылочные типы — префиксом, объявленным в КОРНЕ файла (у платформы это cfg).
# Раньше здесь всегда объявлялся локальный xmlns:d5p1 на тот же URI, что уже есть
# в шапке: платформа принимала, но при цикле «загрузить в базу → выгрузить»
# переписывала каждый ссылочный тип в cfg: — diff-шум на ровном месте.
# Если корень URI не объявляет (файл не от платформы), остаёмся на самодостаточной
# локальной форме: префикс тут — ТЕКСТ узла, XML-слой про него не знает и сам
# объявление не добавит, так что иначе получился бы неразрешимый префикс.
m = re.match( m = re.match(
r"^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|" r"^(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|"
r"ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$", r"ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.(.+)$",
type_str, type_str,
) )
if m: if m:
lines.append(f'{indent}<v8:Type xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d5p1:{type_str}</v8:Type>') if cfg_prefix:
lines.append(f'{indent}<v8:Type>{cfg_prefix}:{type_str}</v8:Type>')
else:
lines.append(f'{indent}<v8:Type xmlns:d5p1="{CFG_NS}">d5p1:{type_str}</v8:Type>')
return "\r\n".join(lines) return "\r\n".join(lines)
# Fallback # Fallback
@@ -1254,6 +1269,10 @@ def build_enum_value_fragment(parsed, indent):
lines.append(f"{indent}\t\t<Name>{esc_xml_text(parsed['name'])}</Name>") lines.append(f"{indent}\t\t<Name>{esc_xml_text(parsed['name'])}</Name>")
lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"])) lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"]))
lines.append(f"{indent}\t\t<Comment/>") lines.append(f"{indent}\t\t<Comment/>")
# Цвет значения — свойство формата 2.21 (8.5). Без него добавленное значение
# отличалось бы от соседних, написанных платформой.
if is_format_221:
lines.append(f"{indent}\t\t<Color>auto</Color>")
lines.append(f"{indent}\t</Properties>") lines.append(f"{indent}\t</Properties>")
lines.append(f"{indent}</EnumValue>") lines.append(f"{indent}</EnumValue>")
return "\r\n".join(lines) return "\r\n".join(lines)
@@ -2962,21 +2981,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -2985,13 +3005,9 @@ def save_xml(tree, path):
"""Save XML tree preserving the existing file's BOM/EOL/encoding-case/final-newline.""" """Save XML tree preserving the existing file's BOM/EOL/encoding-case/final-newline."""
style = _detect_xml_style(path) style = _detect_xml_style(path)
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8") xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
# Fix d5p1 namespace declarations stripped by lxml (it treats them as unused # Костыль, возвращавший xmlns:d5p1 (lxml выбрасывал его как неиспользуемый, ведь
# because d5p1: appears only in text content, not in element/attribute names) # префикс встречается только в тексте узла), удалён вместе с переходом на корневой
xml_bytes = re.sub( # cfg: — объявление теперь берётся из шапки самого файла и не требует починки.
b'(<v8:Type)(?! xmlns:d5p1)(>d5p1:)',
b'\\1 xmlns:d5p1="http://v8.1c.ru/8.1/data/enterprise/current-config"\\2',
xml_bytes
)
xml_bytes = _finalize_xml_bytes(xml_bytes, style) xml_bytes = _finalize_xml_bytes(xml_bytes, style)
with open(path, "wb") as f: with open(path, "wb") as f:
if style is None or style["bom"]: if style is None or style["bom"]:
@@ -3094,7 +3110,9 @@ def add_predefined_items(items):
item_list = items if isinstance(items, list) else [items] item_list = items if isinstance(items, list) else [items]
items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list) items_xml = ''.join(build_predef_item_xml('\t', it, code_type) for it in item_list)
if os.path.exists(path): if os.path.exists(path):
with open(path, 'r', encoding='utf-8-sig') as f: # newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
# в LF при чтении и файл будет переписан в LF (#44/#46/#47).
with open(path, 'r', encoding='utf-8-sig', newline='') as f:
text = f.read() text = f.read()
text = text.replace('</PredefinedData>', items_xml + '</PredefinedData>') text = text.replace('</PredefinedData>', items_xml + '</PredefinedData>')
else: else:
@@ -3103,7 +3121,10 @@ def add_predefined_items(items):
'xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" ' 'xmlns:v8="http://v8.1c.ru/8.1/data/core" 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" ' 'xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" '
f'xsi:type="{xsi_type}" version="{version}">\r\n') f'xsi:type="{xsi_type}" version="{version}">\r\n')
text = hdr + items_xml + '</PredefinedData>\r\n' text = hdr + items_xml + '</PredefinedData>'
# Без перевода строки в конце — канон #57. Срезаем в ОБЕИХ ветках: файл, созданный
# прежней версией навыка, мог унести хвост, а PS-порт срезает безусловно.
text = text.rstrip('\r\n')
with open(path, 'wb') as f: with open(path, 'wb') as f:
f.write(b'\xef\xbb\xbf') f.write(b'\xef\xbb\xbf')
f.write(text.encode('utf-8')) f.write(text.encode('utf-8'))
@@ -3192,6 +3213,13 @@ def main():
xml_tree = etree.parse(resolved_path, xml_parser) xml_tree = etree.parse(resolved_path, xml_parser)
xml_root = xml_tree.getroot() xml_root = xml_tree.getroot()
# Префикс current-config берём из объявлений корня — им и пишем ссылочные типы.
global cfg_prefix, is_format_221
cfg_prefix = next((p for p, u in (xml_root.nsmap or {}).items() if u == CFG_NS and p), None)
_fv = xml_root.get("version") or "2.17"
_m = re.match(r'^(\d+)\.(\d+)$', _fv)
is_format_221 = bool(_m) and int(_m.group(1)) * 100 + int(_m.group(2)) >= 221
# --- Detect object type --- # --- Detect object type ---
if localname(xml_root) != "MetaDataObject": if localname(xml_root) != "MetaDataObject":
die(f"Root element must be MetaDataObject, got: {localname(xml_root)}") die(f"Root element must be MetaDataObject, got: {localname(xml_root)}")
+3 -1
View File
@@ -34,7 +34,9 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/meta-info.ps1" -Obj
| `brief` | Всё одной-двумя строками: имена полей, счётчики | | `brief` | Всё одной-двумя строками: имена полей, счётчики |
| `full` | Всё раскрыто: колонки ТЧ, список источников подписки, движения, формы | | `full` | Всё раскрыто: колонки ТЧ, список источников подписки, движения, формы |
`-Name` — drill-down: раскрыть конкретный элемент объекта (ТЧ, реквизит, шаблон URL, операцию веб-сервиса). `-Name` — drill-down: раскрыть конкретный элемент объекта (ТЧ, реквизит, стандартный реквизит,
шаблон URL, операцию веб-сервиса). Составной тип в сводке свёрнут в счётчик — полный список
типов даёт drill-down: `-Name ТипЗначения` у ПВХ, `-Name Владелец` у подчинённого справочника.
## Поддерживаемые типы (23) ## Поддерживаемые типы (23)
+316 -44
View File
@@ -1,4 +1,4 @@
# meta-info v1.4 — Compact summary of 1C metadata object # meta-info v1.8 — Compact summary of 1C metadata object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath, [Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
@@ -170,6 +170,22 @@ function Get-MLText($node) {
return "" return ""
} }
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
function Format-SingleTypeSet([string]$raw) {
$raw = $raw -replace '^d\d+p\d+:', 'cfg:'
if ($raw -match '^cfg:DefinedType\.(.+)$') { return "ОпределяемыйТип.$($Matches[1])" }
if ($raw -match '^cfg:Characteristic\.(.+)$') { return "Характеристика.$($Matches[1])" }
if ($raw -eq 'cfg:AnyRef') { return "ЛюбаяСсылка" }
if ($raw -eq 'cfg:AnyIBRef') { return "ЛюбаяСсылкаИБ" }
if ($raw -match '^cfg:(\w+Ref)$' -and $refTypeMap.ContainsKey($Matches[1])) {
return $refTypeMap[$Matches[1]]
}
if ($raw -match '^cfg:(.+)$') { return $Matches[1] }
return $raw
}
function Format-Type($typeNode) { function Format-Type($typeNode) {
if (-not $typeNode) { return "" } if (-not $typeNode) { return "" }
$types = @() $types = @()
@@ -178,14 +194,7 @@ function Format-Type($typeNode) {
$types += Format-SingleType $raw $typeNode $types += Format-SingleType $raw $typeNode
} }
foreach ($t in $typeNode.SelectNodes("v8:TypeSet", $ns)) { foreach ($t in $typeNode.SelectNodes("v8:TypeSet", $ns)) {
$raw = $t.InnerText $types += Format-SingleTypeSet $t.InnerText
if ($raw -match '^cfg:DefinedType\.(.+)$') {
$types += "ОпределяемыйТип.$($Matches[1])"
} elseif ($raw -match '^cfg:Characteristic\.(.+)$') {
$types += "Характеристика.$($Matches[1])"
} else {
$types += $raw
}
} }
if ($types.Count -eq 0) { return "" } if ($types.Count -eq 0) { return "" }
if ($types.Count -eq 1) { return $types[0] } if ($types.Count -eq 1) { return $types[0] }
@@ -287,6 +296,202 @@ function Format-Flags($propsNode, [bool]$isDimension = $false) {
return " [$($flags -join ', ')]" return " [$($flags -join ', ')]"
} }
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
function Format-MDObjectRef([string]$raw) {
if ($raw -match '^(\w+)\.(.+)$') {
$key = "$($Matches[1])Ref"
if ($refTypeMap.ContainsKey($key)) { return "$($refTypeMap[$key]).$($Matches[2])" }
}
return $raw
}
# --- Стандартные реквизиты ---
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
$script:composedTypeThreshold = 5
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
# единственная подсказка, чем состав развернуть.
$script:collapsedNames = @()
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
$stdAttrRequiredDefault = @{
"Catalog" = @{ "Owner" = $true; "Description" = $true }
"Document" = @{ "Date" = $true }
"ExchangePlan" = @{ "Description" = $true; "Code" = $true }
"ChartOfAccounts" = @{ "Description" = $true; "Code" = $true }
"ChartOfCharacteristicTypes" = @{ "Description" = $true }
"ChartOfCalculationTypes" = @{ "Description" = $true }
}
function Test-StdAttrRequired($propsNode, [string]$attrName, [string]$objType) {
$fc = $propsNode.SelectSingleNode("md:StandardAttributes/xr:StandardAttribute[@name='$attrName']/xr:FillChecking", $ns)
if ($fc) { return ($fc.InnerText -eq "ShowError") }
if ($stdAttrRequiredDefault.ContainsKey($objType) -and $stdAttrRequiredDefault[$objType].ContainsKey($attrName)) {
return $stdAttrRequiredDefault[$objType][$attrName]
}
return $false
}
function New-StdAttr([string]$name, [string]$type, $flags) {
$f = @($flags | Where-Object { $_ })
$flagStr = if ($f.Count -gt 0) { " [$($f -join ', ')]" } else { "" }
return @{ Name = $name; Type = $type; Flags = $flagStr }
}
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
# они только в full (см. Get-StandardAttributesFull).
function Get-StandardAttributes($propsNode, [string]$objType, [string]$mode) {
$result = @()
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
if ($objType -eq "Catalog") {
$ownersNode = $propsNode.SelectSingleNode("md:Owners", $ns)
$ownerTypes = @()
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
$ownerTypes += Format-MDObjectRef $it.InnerText
}
}
if ($ownerTypes.Count -gt 0) {
$req = if (Test-StdAttrRequired $propsNode "Owner" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Владелец" ($ownerTypes -join ", ") @($req)
}
}
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
if ($objType -eq "ChartOfCharacteristicTypes") {
$vt = $propsNode.SelectSingleNode("md:Type", $ns)
if ($vt) {
$cnt = $vt.SelectNodes("v8:Type", $ns).Count + $vt.SelectNodes("v8:TypeSet", $ns).Count
$typeStr = if ($cnt -gt $script:composedTypeThreshold) {
$script:collapsedNames += "ТипЗначения"
"Составной ($cnt)"
} else { Format-Type $vt }
if ($typeStr) { $result += New-StdAttr "ТипЗначения" $typeStr @() }
}
}
# Дата — есть всегда, ценна флагом обязательности
if ($objType -in @("Document", "BusinessProcess", "Task")) {
$req = if (Test-StdAttrRequired $propsNode "Date" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Дата" "Дата" @($req)
}
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
if ($objType -in @("Document", "BusinessProcess", "Task")) {
$numLen = $propsNode.SelectSingleNode("md:NumberLength", $ns)
if ($numLen -and [int]$numLen.InnerText -gt 0) {
$numType = $propsNode.SelectSingleNode("md:NumberType", $ns)
$ntRu = if ($numType -and $numType.InnerText -eq "Number") { "Число" } else { "Строка" }
$flags = @()
if (Test-StdAttrRequired $propsNode "Number" $objType) { $flags += "обязательный" }
$numPer = $propsNode.SelectSingleNode("md:NumberPeriodicity", $ns)
if ($numPer -and $numberPeriodMap.ContainsKey($numPer.InnerText)) { $flags += $numberPeriodMap[$numPer.InnerText] }
$numAllowed = $propsNode.SelectSingleNode("md:NumberAllowedLength", $ns)
if ($numAllowed -and $numAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
$autoNum = $propsNode.SelectSingleNode("md:Autonumbering", $ns)
if ($autoNum -and $autoNum.InnerText -eq "true") { $flags += "авто" }
$result += New-StdAttr "Номер" "$ntRu($($numLen.InnerText))" $flags
}
}
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
$codeLen = $propsNode.SelectSingleNode("md:CodeLength", $ns)
if ($codeLen -and [int]$codeLen.InnerText -gt 0) {
$codeType = $propsNode.SelectSingleNode("md:CodeType", $ns)
$ctRu = if ($codeType -and $codeType.InnerText -eq "Number") { "Число" } else { "Строка" }
$flags = @()
if (Test-StdAttrRequired $propsNode "Code" $objType) { $flags += "обязательный" }
$codeAllowed = $propsNode.SelectSingleNode("md:CodeAllowedLength", $ns)
if ($codeAllowed -and $codeAllowed.InnerText -eq "Fixed") { $flags += "фикс. длина" }
$result += New-StdAttr "Код" "$ctRu($($codeLen.InnerText))" $flags
}
# Наименование — при DescriptionLength=0 наименования нет
$descLen = $propsNode.SelectSingleNode("md:DescriptionLength", $ns)
if ($descLen -and [int]$descLen.InnerText -gt 0) {
$req = if (Test-StdAttrRequired $propsNode "Description" $objType) { "обязательный" } else { $null }
$result += New-StdAttr "Наименование" "Строка($($descLen.InnerText))" @($req)
}
return $result
}
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
# как перечень доступных полей для запроса.
function Get-StandardAttributesFull($propsNode, [string]$objType, [string]$objName) {
$selfRef = switch ($objType) {
"Catalog" { "СправочникСсылка.$objName" }
"ChartOfCharacteristicTypes" { "ПВХСсылка.$objName" }
"ChartOfAccounts" { "ПланСчетовСсылка.$objName" }
"ChartOfCalculationTypes" { "ПВРСсылка.$objName" }
"ExchangePlan" { "ПланОбменаСсылка.$objName" }
"Document" { "ДокументСсылка.$objName" }
"BusinessProcess" { "БизнесПроцессСсылка.$objName" }
"Task" { "ЗадачаСсылка.$objName" }
default { "" }
}
$result = @()
if ($selfRef) { $result += New-StdAttr "Ссылка" $selfRef @() }
$result += New-StdAttr "ПометкаУдаления" "Булево" @()
# Родитель и ЭтоГруппа существуют только у иерархических объектов
$hier = $propsNode.SelectSingleNode("md:Hierarchical", $ns)
$isHier = ($hier -and $hier.InnerText -eq "true") -or $objType -eq "ChartOfAccounts"
if ($isHier -and $selfRef) {
$result += New-StdAttr "Родитель" $selfRef @()
$ht = $propsNode.SelectSingleNode("md:HierarchyType", $ns)
if ($objType -eq "Catalog" -and (-not $ht -or $ht.InnerText -eq "HierarchyFoldersAndItems")) {
$result += New-StdAttr "ЭтоГруппа" "Булево" @()
}
}
switch ($objType) {
"Document" {
$result += New-StdAttr "Проведен" "Булево" @()
}
"ExchangePlan" {
$result += New-StdAttr "ЭтотУзел" "Булево" @()
$result += New-StdAttr "НомерОтправленного" "Число" @()
$result += New-StdAttr "НомерПринятого" "Число" @()
}
"BusinessProcess" {
$result += New-StdAttr "Стартован" "Булево" @()
$result += New-StdAttr "Завершен" "Булево" @()
$result += New-StdAttr "ВедущаяЗадача" "ЗадачаСсылка" @()
}
"Task" {
$result += New-StdAttr "Выполнена" "Булево" @()
$result += New-StdAttr "БизнесПроцесс" "БизнесПроцессСсылка" @()
$result += New-StdAttr "ТочкаМаршрута" "БизнесПроцессТочкаМаршрутаСсылка" @()
}
"ChartOfAccounts" {
$result += New-StdAttr "Вид" "ВидСчета" @()
$result += New-StdAttr "Забалансовый" "Булево" @()
$result += New-StdAttr "Порядок" "Число" @()
}
"ChartOfCalculationTypes" {
$result += New-StdAttr "ПериодДействияБазовый" "Булево" @()
}
}
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
if ($objType -in @("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes")) {
$result += New-StdAttr "Предопределенный" "Булево" @()
$result += New-StdAttr "ИмяПредопределенныхДанных" "Строка" @()
}
return $result
}
function Get-Attributes($parentNode, [string]$childTag = "Attribute", [bool]$isDimension = $false) { function Get-Attributes($parentNode, [string]$childTag = "Attribute", [bool]$isDimension = $false) {
$result = @() $result = @()
foreach ($attr in $parentNode.SelectNodes("md:$childTag", $ns)) { foreach ($attr in $parentNode.SelectNodes("md:$childTag", $ns)) {
@@ -329,7 +534,10 @@ function Get-MaxNameLen($attrs) {
function Get-SimpleChildren($parentNode, [string]$tag) { function Get-SimpleChildren($parentNode, [string]$tag) {
$result = @() $result = @()
foreach ($child in $parentNode.SelectNodes("md:$tag", $ns)) { foreach ($child in $parentNode.SelectNodes("md:$tag", $ns)) {
$result += $child.InnerText # Form/Template в ChildObjects — простые узлы с именем в тексте, а Command — узел
# с вложенным Properties: у него InnerText склеил бы всё содержимое в одну строку.
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $ns)
if ($nameNode) { $result += $nameNode.InnerText } else { $result += $child.InnerText }
} }
return $result return $result
} }
@@ -650,6 +858,43 @@ if ($Name -and $childObjs) {
} }
} }
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
}
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
# отдельной веткой и работает даже у объектов без ChildObjects.
if ($Name -and -not $drillDone) {
$stdAll = @(Get-StandardAttributes $props $mdType "full") + @(Get-StandardAttributesFull $props $mdType $objName)
foreach ($s in $stdAll) {
if ($s.Name -ne $Name) { continue }
Out "Стандартный реквизит: $($s.Name)"
# Состав типов разворачиваем списком: ради этого drill-down и нужен
$typeSrc = $null
if ($Name -eq "ТипЗначения") { $typeSrc = $props.SelectSingleNode("md:Type", $ns) }
$typeList = @()
if ($typeSrc) {
foreach ($t in $typeSrc.SelectNodes("v8:Type", $ns)) { $typeList += Format-SingleType $t.InnerText $typeSrc }
foreach ($t in $typeSrc.SelectNodes("v8:TypeSet", $ns)) { $typeList += Format-SingleTypeSet $t.InnerText }
} elseif ($Name -eq "Владелец") {
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) { $typeList += Format-MDObjectRef $it.InnerText }
}
}
if ($typeList.Count -gt 0) {
Out " Типы ($($typeList.Count)):"
foreach ($t in $typeList) { Out " $t" }
} else {
Out " Тип: $($s.Type)"
}
$flagText = $s.Flags.Trim()
if ($flagText) { Out " Свойства: $($flagText.Trim('[', ']'))" }
$drillDone = $true
break
}
if (-not $drillDone) { if (-not $drillDone) {
Write-Host "[ERROR] '$Name' not found in $objName" Write-Host "[ERROR] '$Name' not found in $objName"
exit 1 exit 1
@@ -680,6 +925,20 @@ if (-not $drillDone) {
# --- Mode: brief --- # --- Mode: brief ---
if ($Mode -eq "brief") { if ($Mode -eq "brief") {
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
if ($mdType -eq "Catalog") {
$ownersNode = $props.SelectSingleNode("md:Owners", $ns)
$ownerNames = @()
if ($ownersNode) {
foreach ($it in $ownersNode.SelectNodes("xr:Item", $ns)) {
$ownerNames += ($it.InnerText -replace '^\w+\.', '')
}
}
if ($ownerNames.Count -gt 0) { Out "Подчинён: $($ownerNames -join ', ')" }
}
# Attributes # Attributes
$attrs = @() $attrs = @()
if ($childObjs) { $attrs = @(Get-Attributes $childObjs) } if ($childObjs) { $attrs = @(Get-Attributes $childObjs) }
@@ -828,51 +1087,47 @@ if (-not $drillDone) {
# Document-specific header properties # Document-specific header properties
if ($mdType -eq "Document") { if ($mdType -eq "Document") {
$numType = $props.SelectSingleNode("md:NumberType", $ns)
$numLen = $props.SelectSingleNode("md:NumberLength", $ns)
$numPer = $props.SelectSingleNode("md:NumberPeriodicity", $ns)
$autoNum = $props.SelectSingleNode("md:Autonumbering", $ns)
$posting = $props.SelectSingleNode("md:Posting", $ns) $posting = $props.SelectSingleNode("md:Posting", $ns)
$parts = @() $parts = @()
if ($numType -and $numLen) { # Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
$nt = if ($numType.InnerText -eq "String") { "Строка" } else { "Число" }
$piece = "Номер: $nt($($numLen.InnerText))"
if ($numPer) {
$perRu = if ($numberPeriodMap.ContainsKey($numPer.InnerText)) { $numberPeriodMap[$numPer.InnerText] } else { $numPer.InnerText }
$piece += ", $perRu"
}
if ($autoNum -and $autoNum.InnerText -eq "true") { $piece += ", авто" }
$parts += $piece
}
if ($posting) { if ($posting) {
$parts += "Проведение: $(if ($posting.InnerText -eq 'Allow') { 'да' } else { 'нет' })" $parts += "Проведение: $(if ($posting.InnerText -eq 'Allow') { 'да' } else { 'нет' })"
} }
if ($parts.Count -gt 0) { Out ($parts -join " | ") } if ($parts.Count -gt 0) { Out ($parts -join " | ") }
} }
# Catalog-specific header properties # Свойства иерархии и подчинения. Иерархия — не только у справочников: свойство
if ($mdType -eq "Catalog") { # Hierarchical есть и у ПВХ (в корпусе acc/erp/ut/unf их 13), а без этой строки
$parts = @() # в full появлялся Родитель, происхождение которого было ниоткуда не видно.
$hier = $props.SelectSingleNode("md:Hierarchical", $ns) $parts = @()
if ($hier -and $hier.InnerText -eq "true") { $hier = $props.SelectSingleNode("md:Hierarchical", $ns)
$ht = $props.SelectSingleNode("md:HierarchyType", $ns) if ($hier -and $hier.InnerText -eq "true") {
$htText = if ($ht -and $ht.InnerText -eq "HierarchyFoldersAndItems") { "группы и элементы" } else { "элементы" } $ht = $props.SelectSingleNode("md:HierarchyType", $ns)
$limitNode = $props.SelectSingleNode("md:LimitLevelCount", $ns) $htText = if ($ht -and $ht.InnerText -eq "HierarchyFoldersAndItems") { "группы и элементы" } else { "элементы" }
$levelNode = $props.SelectSingleNode("md:LevelCount", $ns) $limitNode = $props.SelectSingleNode("md:LimitLevelCount", $ns)
if ($limitNode -and $limitNode.InnerText -eq "true" -and $levelNode) { $levelNode = $props.SelectSingleNode("md:LevelCount", $ns)
$htText += ", уровней: $($levelNode.InnerText)" if ($limitNode -and $limitNode.InnerText -eq "true" -and $levelNode) {
} else { $htText += ", уровней: $($levelNode.InnerText)"
$htText += ", без ограничения уровней" } else {
} $htText += ", без ограничения уровней"
$parts += "Иерархический: $htText"
} }
$codeLen = $props.SelectSingleNode("md:CodeLength", $ns) $parts += "Иерархический: $htText"
$descLen = $props.SelectSingleNode("md:DescriptionLength", $ns)
if ($codeLen -and [int]$codeLen.InnerText -gt 0) { $parts += "Код($($codeLen.InnerText))" }
if ($descLen -and [int]$descLen.InnerText -gt 0) { $parts += "Наименование($($descLen.InnerText))" }
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
} }
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
if ($mdType -eq "Catalog") {
$sub = $props.SelectSingleNode("md:SubordinationUse", $ns)
if ($sub -and $sub.InnerText -ne "ToItems") {
$subRu = switch ($sub.InnerText) {
"ToFolders" { "группам" }
"ToFoldersAndItems" { "группам и элементам" }
default { $sub.InnerText }
}
$parts += "Подчинение: $subRu"
}
}
if ($parts.Count -gt 0) { Out ($parts -join " | ") }
# Register-specific header properties # Register-specific header properties
if ($mdType -match "Register$") { if ($mdType -match "Register$") {
@@ -1059,6 +1314,16 @@ if (-not $drillDone) {
} }
} }
# --- Standard attributes ---
$stdAttrs = @(Get-StandardAttributes $props $mdType $Mode)
if ($Mode -eq "full") { $stdAttrs += @(Get-StandardAttributesFull $props $mdType $objName) }
if ($stdAttrs.Count -gt 0) {
Out ""
Out "Стандартные реквизиты:"
$ml = Get-MaxNameLen $stdAttrs
foreach ($s in $stdAttrs) { Out (Format-AttrLine $s $ml) }
}
# --- Dimensions (registers) --- # --- Dimensions (registers) ---
if ($mdType -match "Register$" -and $childObjs) { if ($mdType -match "Register$" -and $childObjs) {
$dims = @(Get-Attributes $childObjs "Dimension" $true) $dims = @(Get-Attributes $childObjs "Dimension" $true)
@@ -1177,6 +1442,13 @@ if (-not $drillDone) {
} }
} }
} }
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
if ($script:collapsedNames.Count -gt 0) {
$hints = ($script:collapsedNames | ForEach-Object { "-Name $_" }) -join ", "
Out ""
Out "Полный состав типов: $hints"
}
} }
# --- Pagination and output --- # --- Pagination and output ---
+306 -46
View File
@@ -1,4 +1,4 @@
# meta-info v1.4 — Compact summary of 1C metadata object (Python port) # meta-info v1.8 — Compact summary of 1C metadata object (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
@@ -227,6 +227,30 @@ def get_ml_text(node):
return "" return ""
# Тип-множество: голое имя метатипа без `.Имя` означает ВСЕ ссылки этого класса
# (см. docs/meta-dsl-spec.md §«Тип-множество»). Конкретный тип всегда пишется с точкой,
# поэтому «СправочникСсылка» без точки читается однозначно как обобщённый.
def format_single_type_set(raw):
raw = re.sub(r'^d\d+p\d+:', 'cfg:', raw)
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
if m:
return f"ОпределяемыйТип.{m.group(1)}"
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
if m:
return f"Характеристика.{m.group(1)}"
if raw == "cfg:AnyRef":
return "ЛюбаяСсылка"
if raw == "cfg:AnyIBRef":
return "ЛюбаяСсылкаИБ"
m = re.match(r'^cfg:(\w+Ref)$', raw)
if m and m.group(1) in ref_type_map:
return ref_type_map[m.group(1)]
m = re.match(r'^cfg:(.+)$', raw)
if m:
return m.group(1)
return raw
def format_type(type_node_el): def format_type(type_node_el):
if type_node_el is None: if type_node_el is None:
return "" return ""
@@ -234,16 +258,7 @@ def format_type(type_node_el):
for t in find_all(type_node_el, "v8:Type"): for t in find_all(type_node_el, "v8:Type"):
types.append(format_single_type(inner_text(t), type_node_el)) types.append(format_single_type(inner_text(t), type_node_el))
for t in find_all(type_node_el, "v8:TypeSet"): for t in find_all(type_node_el, "v8:TypeSet"):
raw = inner_text(t) types.append(format_single_type_set(inner_text(t)))
m = re.match(r'^cfg:DefinedType\.(.+)$', raw)
if m:
types.append(f"ОпределяемыйТип.{m.group(1)}")
continue
m = re.match(r'^cfg:Characteristic\.(.+)$', raw)
if m:
types.append(f"Характеристика.{m.group(1)}")
continue
types.append(raw)
if len(types) == 0: if len(types) == 0:
return "" return ""
if len(types) == 1: if len(types) == 1:
@@ -341,6 +356,190 @@ def format_flags(a_props, is_dimension=False):
return f" [{', '.join(flags)}]" return f" [{', '.join(flags)}]"
# Ссылка на объект метаданных (`Catalog.Валюты` в Owners и подобных) — не тип значения,
# но в выводе показываем именно тип, который присваивается: СправочникСсылка.Валюты.
def format_md_object_ref(raw):
m = re.match(r'^(\w+)\.(.+)$', raw)
if m:
key = f"{m.group(1)}Ref"
if key in ref_type_map:
return f"{ref_type_map[key]}.{m.group(2)}"
return raw
# ── Стандартные реквизиты ────────────────────────────────────
# Сколько типов состава печатать списком, прежде чем свернуть в счётчик. По корпусу
# acc/erp/ut/unf состав ПВХ доходит до 151 типа, при этом 20 из 42 ПВХ укладываются в 5.
COMPOSED_TYPE_THRESHOLD = 5
# Имена полей, состав которых свернули в счётчик, — по ним в конце вывода печатается
# единственная подсказка, чем состав развернуть.
collapsed_names = []
# Блок StandardAttributes в XML опционален: платформа материализует его только когда хотя бы
# один стандартный реквизит кастомизирован (docs/meta-dsl-spec.md §7.1.1). Когда блока нет,
# действуют платформенные дефолты — профиль ниже совпадает с $stdAttrProfile в meta-compile
# (выведен из корпуса acc+erp). Правки держать синхронными, иначе навыки разъедутся молча.
std_attr_required_default = {
"Catalog": {"Owner": True, "Description": True},
"Document": {"Date": True},
"ExchangePlan": {"Description": True, "Code": True},
"ChartOfAccounts": {"Description": True, "Code": True},
"ChartOfCharacteristicTypes": {"Description": True},
"ChartOfCalculationTypes": {"Description": True},
}
def test_std_attr_required(props_node, attr_name, obj_type):
fc = find(props_node, f"md:StandardAttributes/xr:StandardAttribute[@name='{attr_name}']/xr:FillChecking")
if fc is not None:
return inner_text(fc) == "ShowError"
return std_attr_required_default.get(obj_type, {}).get(attr_name, False)
def new_std_attr(name, type_str, flags):
f = [x for x in flags if x]
flag_str = f" [{', '.join(f)}]" if f else ""
return {"Name": name, "Type": type_str, "Flags": flag_str}
# Стандартные реквизиты, наличие и характеристики которых задаются настройками объекта, —
# их нельзя вывести из остального вывода, поэтому они попадают и в overview. Ссылка,
# ПометкаУдаления, Родитель и прочее следуют из типа объекта и строки «Иерархический» —
# они только в full (см. get_standard_attributes_full).
def get_standard_attributes(props_node, obj_type, mode_name):
result = []
# Владелец — только у подчинённого справочника; состав типов ниоткуда не выводится
if obj_type == "Catalog":
owners_node = find(props_node, "md:Owners")
owner_types = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_types.append(format_md_object_ref(inner_text(it)))
if owner_types:
req = "обязательный" if test_std_attr_required(props_node, "Owner", obj_type) else None
result.append(new_std_attr("Владелец", ", ".join(owner_types), [req]))
# ТипЗначения ПВХ — до полутора сотен типов, полный список раздул бы сводку в обоих
# режимах, поэтому сворачиваем в счётчик. Состав смотреть через -Name ТипЗначения.
if obj_type == "ChartOfCharacteristicTypes":
vt = find(props_node, "md:Type")
if vt is not None:
cnt = len(find_all(vt, "v8:Type")) + len(find_all(vt, "v8:TypeSet"))
if cnt > COMPOSED_TYPE_THRESHOLD:
type_str = f"Составной ({cnt})"
collapsed_names.append("ТипЗначения")
else:
type_str = format_type(vt)
if type_str:
result.append(new_std_attr("ТипЗначения", type_str, []))
# Дата — есть всегда, ценна флагом обязательности
if obj_type in ("Document", "BusinessProcess", "Task"):
req = "обязательный" if test_std_attr_required(props_node, "Date", obj_type) else None
result.append(new_std_attr("Дата", "Дата", [req]))
# Номер — тип и длина задаются объектом, при NumberLength=0 номера нет
if obj_type in ("Document", "BusinessProcess", "Task"):
num_len = find(props_node, "md:NumberLength")
if num_len is not None and inner_text(num_len).isdigit() and int(inner_text(num_len)) > 0:
num_type = find(props_node, "md:NumberType")
nt_ru = "Число" if num_type is not None and inner_text(num_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Number", obj_type):
flags.append("обязательный")
num_per = find(props_node, "md:NumberPeriodicity")
if num_per is not None and inner_text(num_per) in number_period_map:
flags.append(number_period_map[inner_text(num_per)])
num_allowed = find(props_node, "md:NumberAllowedLength")
if num_allowed is not None and inner_text(num_allowed) == "Fixed":
flags.append("фикс. длина")
auto_num = find(props_node, "md:Autonumbering")
if auto_num is not None and inner_text(auto_num) == "true":
flags.append("авто")
result.append(new_std_attr("Номер", f"{nt_ru}({inner_text(num_len)})", flags))
# Код — при CodeLength=0 кода у объекта нет вовсе, строку не печатаем
code_len = find(props_node, "md:CodeLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
code_type = find(props_node, "md:CodeType")
ct_ru = "Число" if code_type is not None and inner_text(code_type) == "Number" else "Строка"
flags = []
if test_std_attr_required(props_node, "Code", obj_type):
flags.append("обязательный")
code_allowed = find(props_node, "md:CodeAllowedLength")
if code_allowed is not None and inner_text(code_allowed) == "Fixed":
flags.append("фикс. длина")
result.append(new_std_attr("Код", f"{ct_ru}({inner_text(code_len)})", flags))
# Наименование — при DescriptionLength=0 наименования нет
desc_len = find(props_node, "md:DescriptionLength")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
req = "обязательный" if test_std_attr_required(props_node, "Description", obj_type) else None
result.append(new_std_attr("Наименование", f"Строка({inner_text(desc_len)})", [req]))
return result
# Остальные стандартные реквизиты — предсказуемы по типу объекта, но в full полезны
# как перечень доступных полей для запроса.
def get_standard_attributes_full(props_node, obj_type, obj_name):
self_ref_map = {
"Catalog": "СправочникСсылка",
"ChartOfCharacteristicTypes": "ПВХСсылка",
"ChartOfAccounts": "ПланСчетовСсылка",
"ChartOfCalculationTypes": "ПВРСсылка",
"ExchangePlan": "ПланОбменаСсылка",
"Document": "ДокументСсылка",
"BusinessProcess": "БизнесПроцессСсылка",
"Task": "ЗадачаСсылка",
}
self_ref = f"{self_ref_map[obj_type]}.{obj_name}" if obj_type in self_ref_map else ""
result = []
if self_ref:
result.append(new_std_attr("Ссылка", self_ref, []))
result.append(new_std_attr("ПометкаУдаления", "Булево", []))
# Родитель и ЭтоГруппа существуют только у иерархических объектов
hier = find(props_node, "md:Hierarchical")
is_hier = (hier is not None and inner_text(hier) == "true") or obj_type == "ChartOfAccounts"
if is_hier and self_ref:
result.append(new_std_attr("Родитель", self_ref, []))
ht = find(props_node, "md:HierarchyType")
if obj_type == "Catalog" and (ht is None or inner_text(ht) == "HierarchyFoldersAndItems"):
result.append(new_std_attr("ЭтоГруппа", "Булево", []))
if obj_type == "Document":
result.append(new_std_attr("Проведен", "Булево", []))
elif obj_type == "ExchangePlan":
result.append(new_std_attr("ЭтотУзел", "Булево", []))
result.append(new_std_attr("НомерОтправленного", "Число", []))
result.append(new_std_attr("НомерПринятого", "Число", []))
elif obj_type == "BusinessProcess":
result.append(new_std_attr("Стартован", "Булево", []))
result.append(new_std_attr("Завершен", "Булево", []))
result.append(new_std_attr("ВедущаяЗадача", "ЗадачаСсылка", []))
elif obj_type == "Task":
result.append(new_std_attr("Выполнена", "Булево", []))
result.append(new_std_attr("БизнесПроцесс", "БизнесПроцессСсылка", []))
result.append(new_std_attr("ТочкаМаршрута", "БизнесПроцессТочкаМаршрутаСсылка", []))
elif obj_type == "ChartOfAccounts":
result.append(new_std_attr("Вид", "ВидСчета", []))
result.append(new_std_attr("Забалансовый", "Булево", []))
result.append(new_std_attr("Порядок", "Число", []))
elif obj_type == "ChartOfCalculationTypes":
result.append(new_std_attr("ПериодДействияБазовый", "Булево", []))
# Предопределённые данные есть у всех перечисленных типов, кроме документов и задач
if obj_type in ("Catalog", "ChartOfCharacteristicTypes", "ChartOfAccounts", "ChartOfCalculationTypes"):
result.append(new_std_attr("Предопределенный", "Булево", []))
result.append(new_std_attr("ИмяПредопределенныхДанных", "Строка", []))
return result
def get_attributes(parent_node, child_tag="Attribute", is_dimension=False): def get_attributes(parent_node, child_tag="Attribute", is_dimension=False):
result = [] result = []
for attr in find_all(parent_node, f"md:{child_tag}"): for attr in find_all(parent_node, f"md:{child_tag}"):
@@ -381,7 +580,10 @@ def get_max_name_len(attrs):
def get_simple_children(parent_node, tag): def get_simple_children(parent_node, tag):
result = [] result = []
for child in find_all(parent_node, f"md:{tag}"): for child in find_all(parent_node, f"md:{tag}"):
result.append(inner_text(child)) # Form/Template в ChildObjects — простые узлы с именем в тексте, а Command — узел
# с вложенным Properties: у него inner_text склеил бы всё содержимое в одну строку.
name_node = find(child, "md:Properties/md:Name")
result.append(inner_text(name_node) if name_node is not None else inner_text(child))
return result return result
@@ -705,6 +907,44 @@ if drill_name and child_objs is not None:
drill_done = True drill_done = True
break break
# Не нашли среди дочерних объектов — пробуем стандартные реквизиты ниже
# Drill-down по стандартному реквизиту — единственный способ увидеть состав типов целиком,
# когда в сводке он свёрнут в счётчик. Живёт в Properties, а не в ChildObjects, поэтому идёт
# отдельной веткой и работает даже у объектов без ChildObjects.
if drill_name and not drill_done:
std_all = get_standard_attributes(props, md_type, "full") + get_standard_attributes_full(props, md_type, obj_name)
for s in std_all:
if s["Name"] != drill_name:
continue
out(f"Стандартный реквизит: {s['Name']}")
# Состав типов разворачиваем списком: ради этого drill-down и нужен
type_list = []
type_src = find(props, "md:Type") if drill_name == "ТипЗначения" else None
if type_src is not None:
for t in find_all(type_src, "v8:Type"):
type_list.append(format_single_type(inner_text(t), type_src))
for t in find_all(type_src, "v8:TypeSet"):
type_list.append(format_single_type_set(inner_text(t)))
elif drill_name == "Владелец":
owners_node = find(props, "md:Owners")
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
type_list.append(format_md_object_ref(inner_text(it)))
if type_list:
out(f" Типы ({len(type_list)}):")
for t in type_list:
out(f" {t}")
else:
out(f" Тип: {s['Type']}")
flag_text = s["Flags"].strip()
if flag_text:
out(f" Свойства: {flag_text.strip('[]')}")
drill_done = True
break
if not drill_done: if not drill_done:
print(f"[ERROR] '{drill_name}' not found in {obj_name}") print(f"[ERROR] '{drill_name}' not found in {obj_name}")
sys.exit(1) sys.exit(1)
@@ -736,6 +976,18 @@ if not drill_done:
out(f"Расширенное представление списка: {ext_list_presentation}") out(f"Расширенное представление списка: {ext_list_presentation}")
if mode == "brief": if mode == "brief":
# Подчинённость — единственный факт структуры, который из brief не выводится никак,
# а без него код записи элемента падает. Про обязательность здесь намеренно молчим:
# обязательными бывают и обычные реквизиты, а их brief не разбирает.
if md_type == "Catalog":
owners_node = find(props, "md:Owners")
owner_names = []
if owners_node is not None:
for it in find_all(owners_node, "xr:Item"):
owner_names.append(re.sub(r'^\w+\.', '', inner_text(it)))
if owner_names:
out(f"Подчинён: {', '.join(owner_names)}")
# Attributes # Attributes
attrs = get_attributes(child_objs) if child_objs is not None else [] attrs = get_attributes(child_objs) if child_objs is not None else []
if attrs: if attrs:
@@ -865,48 +1117,39 @@ if not drill_done:
# Document-specific header # Document-specific header
if md_type == "Document": if md_type == "Document":
num_type = find(props, "md:NumberType")
num_len = find(props, "md:NumberLength")
num_per = find(props, "md:NumberPeriodicity")
auto_num = find(props, "md:Autonumbering")
posting = find(props, "md:Posting") posting = find(props, "md:Posting")
parts = [] parts = []
if num_type is not None and num_len is not None: # Номер уехал в блок стандартных реквизитов — здесь остаются свойства объекта
nt = "Строка" if inner_text(num_type) == "String" else "Число"
piece = f"Номер: {nt}({inner_text(num_len)})"
if num_per is not None:
per_ru = number_period_map.get(inner_text(num_per), inner_text(num_per))
piece += f", {per_ru}"
if auto_num is not None and inner_text(auto_num) == "true":
piece += ", авто"
parts.append(piece)
if posting is not None: if posting is not None:
parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}") parts.append(f"Проведение: {'да' if inner_text(posting) == 'Allow' else 'нет'}")
if parts: if parts:
out(" | ".join(parts)) out(" | ".join(parts))
# Catalog-specific header # Свойства иерархии и подчинения. Иерархия — не только у справочников: свойство
# Hierarchical есть и у ПВХ (в корпусе acc/erp/ut/unf их 13), а без этой строки
# в full появлялся Родитель, происхождение которого было ниоткуда не видно.
parts = []
hier = find(props, "md:Hierarchical")
if hier is not None and inner_text(hier) == "true":
ht = find(props, "md:HierarchyType")
ht_text = "группы и элементы" if ht is not None and inner_text(ht) == "HierarchyFoldersAndItems" else "элементы"
limit_node = find(props, "md:LimitLevelCount")
level_node = find(props, "md:LevelCount")
if limit_node is not None and inner_text(limit_node) == "true" and level_node is not None:
ht_text += f", уровней: {inner_text(level_node)}"
else:
ht_text += ", без ограничения уровней"
parts.append(f"Иерархический: {ht_text}")
# Код и Наименование уехали в блок стандартных реквизитов — здесь только свойства объекта.
# Подчинение печатаем лишь когда оно отличается от дефолта ToItems.
if md_type == "Catalog": if md_type == "Catalog":
parts = [] sub = find(props, "md:SubordinationUse")
hier = find(props, "md:Hierarchical") if sub is not None and inner_text(sub) != "ToItems":
if hier is not None and inner_text(hier) == "true": sub_ru = {"ToFolders": "группам", "ToFoldersAndItems": "группам и элементам"}.get(
ht = find(props, "md:HierarchyType") inner_text(sub), inner_text(sub))
ht_text = "группы и элементы" if ht is not None and inner_text(ht) == "HierarchyFoldersAndItems" else "элементы" parts.append(f"Подчинение: {sub_ru}")
limit_node = find(props, "md:LimitLevelCount") if parts:
level_node = find(props, "md:LevelCount") out(" | ".join(parts))
if limit_node is not None and inner_text(limit_node) == "true" and level_node is not None:
ht_text += f", уровней: {inner_text(level_node)}"
else:
ht_text += ", без ограничения уровней"
parts.append(f"Иерархический: {ht_text}")
code_len = find(props, "md:CodeLength")
desc_len = find(props, "md:DescriptionLength")
if code_len is not None and inner_text(code_len).isdigit() and int(inner_text(code_len)) > 0:
parts.append(f"Код({inner_text(code_len)})")
if desc_len is not None and inner_text(desc_len).isdigit() and int(inner_text(desc_len)) > 0:
parts.append(f"Наименование({inner_text(desc_len)})")
if parts:
out(" | ".join(parts))
# Register-specific header # Register-specific header
if md_type.endswith("Register"): if md_type.endswith("Register"):
@@ -1067,6 +1310,17 @@ if not drill_done:
syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else "" syn_text = f'"{v["Synonym"]}"' if v["Synonym"] and v["Synonym"] != v["Name"] else ""
out(f" {padded} {syn_text}") out(f" {padded} {syn_text}")
# Standard attributes
std_attrs = get_standard_attributes(props, md_type, mode)
if mode == "full":
std_attrs += get_standard_attributes_full(props, md_type, obj_name)
if std_attrs:
out("")
out("Стандартные реквизиты:")
ml = get_max_name_len(std_attrs)
for s in std_attrs:
out(format_attr_line(s, ml))
# Dimensions (registers) # Dimensions (registers)
if md_type.endswith("Register") and child_objs is not None: if md_type.endswith("Register") and child_objs is not None:
dims = get_attributes(child_objs, "Dimension", True) dims = get_attributes(child_objs, "Dimension", True)
@@ -1174,6 +1428,12 @@ if not drill_done:
if commands: if commands:
out(f"Команды: {', '.join(commands)}") out(f"Команды: {', '.join(commands)}")
# Единственная подсказка на весь вывод — чем развернуть свёрнутый в счётчик состав
if collapsed_names:
hints = ", ".join(f"-Name {n}" for n in collapsed_names)
out("")
out(f"Полный состав типов: {hints}")
# ── Pagination and output ──────────────────────────────────── # ── Pagination and output ────────────────────────────────────
total_lines = len(lines) total_lines = len(lines)
@@ -1,4 +1,4 @@
# meta-remove v1.5 — Remove metadata object from 1C configuration dump # meta-remove v1.8 — Remove metadata object from 1C configuration dump
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -493,9 +493,29 @@ if (-not $cfgNode) {
# Save Configuration.xml # Save Configuration.xml
if ($actions -gt 0 -and -not $DryRun) { if ($actions -gt 0 -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true $enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($configXml, $false, $enc) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$xmlDoc.Save($sw) $settings = New-Object System.Xml.XmlWriterSettings
$sw.Close() $settings.Encoding = $enc
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer)
$writer.Flush(); $writer.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($configXml, $xmlText, $enc)
Write-Host "[OK] Configuration.xml saved" Write-Host "[OK] Configuration.xml saved"
} }
} }
@@ -559,9 +579,29 @@ function Remove-FromSubsystems {
if ($modified -and -not $DryRun) { if ($modified -and -not $DryRun) {
$enc = New-Object System.Text.UTF8Encoding $true $enc = New-Object System.Text.UTF8Encoding $true
$sw = New-Object System.IO.StreamWriter($xmlFile.FullName, $false, $enc) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$ssDoc.Save($sw) $settings = New-Object System.Xml.XmlWriterSettings
$sw.Close() $settings.Encoding = $enc
$settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$ssDoc.Save($writer)
$writer.Flush(); $writer.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $xmlFile.FullName) -and ([System.IO.File]::ReadAllText($xmlFile.FullName) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($xmlFile.FullName, $xmlText, $enc)
} }
# Recurse into child subsystems # Recurse into child subsystems
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# meta-remove v1.5 — Remove metadata object from 1C configuration dump # meta-remove v1.8 — Remove metadata object from 1C configuration dump
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -292,21 +292,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -1,4 +1,4 @@
# meta-validate v1.13 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка) # meta-validate v1.14 — Validate 1C metadata object structure (+корневой <Type>: скаляр без структуры = ошибка)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -344,9 +344,10 @@ if ($root.NamespaceURI -ne $expectedNs) {
$version = $root.GetAttribute("version") $version = $root.GetAttribute("version")
if (-not $version) { if (-not $version) {
Report-Warn "1. Missing version attribute on MetaDataObject" Report-Warn "1. Missing version attribute on MetaDataObject"
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20")) { } elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27). # Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
Report-Warn "1. Unusual version '$version' (expected 2.17-2.20)" # 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
Report-Warn "1. Unusual version '$version' (expected 2.17-2.21)"
} }
# Detect type element — exactly one child element in md namespace # Detect type element — exactly one child element in md namespace
@@ -1502,6 +1503,10 @@ if ($script:configDir) {
$versionedProps = @{ $versionedProps = @{
"TypeReductionMode" = "2.18" # режим приведения типов (стандартные реквизиты, измерения РС) "TypeReductionMode" = "2.18" # режим приведения типов (стандартные реквизиты, измерения РС)
"LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9) "LineNumberLength" = "2.20" # длина номера строки ТЧ (5..9)
# 2.21 (8.5): подтверждено синтетикой — одни исходники, выгрузка с 8.3.27 и с 8.5.1.
"Color" = "2.21" # цвет значения перечисления
"AuxiliaryVariantForm" = "2.21" # вспомогательная форма варианта отчёта
"UseInInterfaceCompatibilityMode" = "2.21" # использование общей формы в режиме совместимости интерфейса
} }
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17"). # Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
function Get-FormatRank([string]$v) { function Get-FormatRank([string]$v) {
@@ -1,4 +1,4 @@
# meta-validate v1.13 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка) # meta-validate v1.14 — Validate 1C metadata object structure (Python port) (+корневой <Type>: скаляр без структуры = ошибка)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import os import os
@@ -371,9 +371,10 @@ if root_ns != expected_ns:
version = root.get("version", "") version = root.get("version", "")
if not version: if not version:
report_warn("1. Missing version attribute on MetaDataObject") report_warn("1. Missing version attribute on MetaDataObject")
elif version not in ("2.17", "2.18", "2.19", "2.20"): elif version not in ("2.17", "2.18", "2.19", "2.20", "2.21"):
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27). # Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.20)") # 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.21)")
# Detect type element -- exactly one child element in md namespace # Detect type element -- exactly one child element in md namespace
type_node = None type_node = None
@@ -1404,6 +1405,10 @@ if config_dir:
versioned_props = { versioned_props = {
"TypeReductionMode": "2.18", # режим приведения типов (стандартные реквизиты, измерения РС) "TypeReductionMode": "2.18", # режим приведения типов (стандартные реквизиты, измерения РС)
"LineNumberLength": "2.20", # длина номера строки ТЧ (5..9) "LineNumberLength": "2.20", # длина номера строки ТЧ (5..9)
# 2.21 (8.5): подтверждено синтетикой — одни исходники, выгрузка с 8.3.27 и с 8.5.1.
"Color": "2.21", # цвет значения перечисления
"AuxiliaryVariantForm": "2.21", # вспомогательная форма варианта отчёта
"UseInInterfaceCompatibilityMode": "2.21", # использование общей формы в режиме совместимости интерфейса
} }
@@ -1,4 +1,4 @@
# mxl-compile v1.5 — Compile 1C spreadsheet from JSON # mxl-compile v1.10 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -142,6 +142,46 @@ function Assert-EditAllowed([string]$targetPath, [string]$require) {
} catch { return } } catch { return }
} }
# --- Detect XML format version ---
# У корня <document> нет атрибута version, поэтому версию берём из конфигурации, в дерево
# которой пишем макет. Вне конфигурации (автономный .xml, исходники EPF) остаётся 2.17.
function Detect-FormatVersion([string]$dir) {
$d = $dir
while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) {
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$parent = Split-Path $d -Parent
if ($parent -eq $d) { break }
$d = $parent
}
return "2.17"
}
# Версия формата как число для сравнений: "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:outPathResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath }
$script:formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($script:outPathResolved))
# --- 1. Load and validate JSON --- # --- 1. Load and validate JSON ---
if (-not (Test-Path $JsonPath)) { if (-not (Test-Path $JsonPath)) {
@@ -512,8 +552,14 @@ function X {
} }
# 7a. Header # 7a. Header
$docNsDecl = 'xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (перед style):
# платформа держит объявления по алфавиту, дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$docNsDecl = $docNsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
X '<?xml version="1.0" encoding="UTF-8"?>' X '<?xml version="1.0" encoding="UTF-8"?>'
X '<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">' X "<document $docNsDecl>"
# 7b. Language settings # 7b. Language settings
X "`t<languageSettings>" X "`t<languageSettings>"
@@ -854,8 +900,13 @@ X '</document>'
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
$resolvedPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath } $resolvedPath = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location) $OutputPath }
# Каталог назначения создаём сами: типовой путь — Templates/<Имя>/Ext/Template.xml,
# и его может ещё не быть. Так делают и form-compile, и skd-compile, и py-порт этого
# навыка; без этого PS-порт падал на «Could not find a part of the path».
$outDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
if ($outDir -and -not (Test-Path $outDir)) { New-Item -ItemType Directory -Path $outDir -Force | Out-Null }
Assert-EditAllowed $resolvedPath 'editable' Assert-EditAllowed $resolvedPath 'editable'
[System.IO.File]::WriteAllText($resolvedPath, $xml.ToString(), $enc) [System.IO.File]::WriteAllText($resolvedPath, $xml.ToString().TrimEnd("`r", "`n"), $enc)
# --- 9. Summary --- # --- 9. Summary ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# mxl-compile v1.5 — Compile 1C spreadsheet from JSON # mxl-compile v1.10 — Compile 1C spreadsheet from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -198,6 +198,38 @@ def write_utf8_bom(path, content):
f.write(content) f.write(content)
def detect_format_version(d):
while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
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(2000)
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
if m:
return m.group(1)
parent = os.path.dirname(d)
if parent == d:
break
d = parent
return "2.17"
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
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
sys.stderr.reconfigure(encoding="utf-8") sys.stderr.reconfigure(encoding="utf-8")
@@ -206,6 +238,12 @@ def main():
parser.add_argument('-OutputPath', type=str, required=True) parser.add_argument('-OutputPath', type=str, required=True)
args = parser.parse_args() args = parser.parse_args()
# --- Detect XML format version ---
# У корня <document> нет атрибута version, поэтому версию берём из конфигурации, в дерево
# которой пишем макет. Вне конфигурации (автономный .xml, исходники EPF) остаётся 2.17.
out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath)
format_version = detect_format_version(os.path.dirname(out_path_resolved))
# --- 1. Load and validate JSON --- # --- 1. Load and validate JSON ---
json_path = args.JsonPath json_path = args.JsonPath
if not os.path.exists(json_path): if not os.path.exists(json_path):
@@ -496,7 +534,16 @@ def main():
# 7a. Header # 7a. Header
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append('<document xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">') doc_ns_decl = ('xmlns="http://v8.1c.ru/8.2/data/spreadsheet" xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
' xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
' xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (перед style):
# платформа держит объявления по алфавиту, дописать в конец нельзя.
if format_rank(format_version) >= 221:
doc_ns_decl = doc_ns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
lines.append(f'<document {doc_ns_decl}>')
# 7b. Language settings # 7b. Language settings
lines.append('\t<languageSettings>') lines.append('\t<languageSettings>')
@@ -802,7 +849,7 @@ def main():
if out_dir and not os.path.exists(out_dir): if out_dir and not os.path.exists(out_dir):
os.makedirs(out_dir, exist_ok=True) os.makedirs(out_dir, exist_ok=True)
content = '\n'.join(lines) + '\n' content = '\r\n'.join(lines)
write_utf8_bom(out_path, content) write_utf8_bom(out_path, content)
# --- 9. Summary --- # --- 9. Summary ---
@@ -1,4 +1,4 @@
# role-compile v1.10 — Compile 1C role from JSON # role-compile v1.18 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -659,7 +659,14 @@ function Detect-FormatVersion([string]$dir) {
return "2.17" return "2.17"
} }
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir } # Версия формата как число для сравнений: "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
}
$resolvedOutputDir =if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
Assert-EditAllowed $resolvedOutputDir 'editable' Assert-EditAllowed $resolvedOutputDir 'editable'
$formatVersion = Detect-FormatVersion $resolvedOutputDir $formatVersion = Detect-FormatVersion $resolvedOutputDir
@@ -672,40 +679,30 @@ $uuid = [guid]::NewGuid().ToString()
$script:xmlBuf = New-Object System.Text.StringBuilder 4096 $script:xmlBuf = New-Object System.Text.StringBuilder 4096
X '<?xml version="1.0" encoding="UTF-8"?>' X '<?xml version="1.0" encoding="UTF-8"?>'
X '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"' # Объявления пространств имён — одной переменной и одной строкой, как пишет платформа.
X ' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"' $xmlnsDecl = '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"'
X ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"' # 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
X ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"' # платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт (проверено по выгрузке 8.5).
X ' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"' if ((Get-FormatRank $formatVersion) -ge 221) {
X ' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"' $xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
X ' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
X ' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
X ' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
X ' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
X ' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
X ' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
X ' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
X ' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
X ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
X " version=`"$formatVersion`">"
X " <Role uuid=`"$uuid`">"
X ' <Properties>'
X " <Name>$roleName</Name>"
X ' <Synonym>'
X ' <v8:item>'
X ' <v8:lang>ru</v8:lang>'
X " <v8:content>$(Esc-Xml $synonym)</v8:content>"
X ' </v8:item>'
X ' </Synonym>'
if ($comment) {
X " <Comment>$(Esc-Xml $comment)</Comment>"
} else {
X ' <Comment/>'
} }
X ' </Properties>' X "<MetaDataObject $xmlnsDecl version=`"$formatVersion`">"
X ' </Role>' X "`t<Role uuid=`"$uuid`">"
X "`t`t<Properties>"
X "`t`t`t<Name>$roleName</Name>"
X "`t`t`t<Synonym>"
X "`t`t`t`t<v8:item>"
X "`t`t`t`t`t<v8:lang>ru</v8:lang>"
X "`t`t`t`t`t<v8:content>$(Esc-Xml $synonym)</v8:content>"
X "`t`t`t`t</v8:item>"
X "`t`t`t</Synonym>"
if ($comment) {
X "`t`t`t<Comment>$(Esc-Xml $comment)</Comment>"
} else {
X "`t`t`t<Comment/>"
}
X "`t`t</Properties>"
X "`t</Role>"
X '</MetaDataObject>' X '</MetaDataObject>'
$metadataXml = $script:xmlBuf.ToString() $metadataXml = $script:xmlBuf.ToString()
@@ -715,48 +712,45 @@ $metadataXml = $script:xmlBuf.ToString()
$script:xmlBuf = New-Object System.Text.StringBuilder 8192 $script:xmlBuf = New-Object System.Text.StringBuilder 8192
X '<?xml version="1.0" encoding="UTF-8"?>' X '<?xml version="1.0" encoding="UTF-8"?>'
X '<Rights xmlns="http://v8.1c.ru/8.2/roles"' X "<Rights xmlns=`"http://v8.1c.ru/8.2/roles`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`" xmlns:xsi=`"http://www.w3.org/2001/XMLSchema-instance`" xsi:type=`"Rights`" version=`"$formatVersion`">"
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
X " xsi:type=`"Rights`" version=`"$formatVersion`">"
# Global flags (defaults match typical 1C roles) # Global flags (defaults match typical 1C roles)
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" } $sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" } $sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" }
$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" } $irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" }
X " <setForNewObjects>$sfno</setForNewObjects>" X "`t<setForNewObjects>$sfno</setForNewObjects>"
X " <setForAttributesByDefault>$sfab</setForAttributesByDefault>" X "`t<setForAttributesByDefault>$sfab</setForAttributesByDefault>"
X " <independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>" X "`t<independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
# Object blocks # Object blocks
$totalRights = 0 $totalRights = 0
foreach ($obj in $parsedObjects) { foreach ($obj in $parsedObjects) {
X ' <object>' X "`t<object>"
X " <name>$($obj.Name)</name>" X "`t`t<name>$($obj.Name)</name>"
foreach ($right in $obj.Rights) { foreach ($right in $obj.Rights) {
X ' <right>' X "`t`t<right>"
X " <name>$($right.Name)</name>" X "`t`t`t<name>$($right.Name)</name>"
X " <value>$($right.Value)</value>" X "`t`t`t<value>$($right.Value)</value>"
if ($right.Condition) { if ($right.Condition) {
X ' <restrictionByCondition>' X "`t`t`t<restrictionByCondition>"
X " <condition>$(Esc-Xml $right.Condition)</condition>" X "`t`t`t`t<condition>$(Esc-Xml $right.Condition)</condition>"
X ' </restrictionByCondition>' X "`t`t`t</restrictionByCondition>"
} }
X ' </right>' X "`t`t</right>"
$totalRights++ $totalRights++
} }
X ' </object>' X "`t</object>"
} }
# RLS restriction templates # RLS restriction templates
$templateCount = 0 $templateCount = 0
if ($def.templates) { if ($def.templates) {
foreach ($tpl in $def.templates) { foreach ($tpl in $def.templates) {
X ' <restrictionTemplate>' X "`t<restrictionTemplate>"
X " <name>$(Esc-Xml "$($tpl.name)")</name>" X "`t`t<name>$(Esc-Xml "$($tpl.name)")</name>"
X " <condition>$(Esc-Xml "$($tpl.condition)")</condition>" X "`t`t<condition>$(Esc-Xml "$($tpl.condition)")</condition>"
X ' </restrictionTemplate>' X "`t</restrictionTemplate>"
$templateCount++ $templateCount++
} }
} }
@@ -799,8 +793,8 @@ if (-not (Test-Path $extDir)) {
} }
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc) [System.IO.File]::WriteAllText($metadataPath, $metadataXml.TrimEnd("`r", "`n"), $enc)
[System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc) [System.IO.File]::WriteAllText($rightsPath, $rightsXml.TrimEnd("`r", "`n"), $enc)
# --- 12. Register in Configuration.xml --- # --- 12. Register in Configuration.xml ---
@@ -856,11 +850,26 @@ if (Test-Path $configXmlPath) {
$cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings = New-Object System.Xml.XmlWriterSettings
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
$cfgSettings.Indent = $false $cfgSettings.Indent = $false
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
$configDoc.Save($writer) $configDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
$regResult = "added" $regResult = "added"
} }
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# role-compile v1.10 — Compile 1C role from JSON # role-compile v1.18 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -203,6 +203,20 @@ def detect_format_version(d):
return "2.17" return "2.17"
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
def detect_eol(text):
# Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам,
# которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47).
# Семантика та же, что у _detect_xml_style в остальных портах: есть CRLF → CRLF.
# Мажоритарное правило здесь было расхождением — на смешанном входе оно давало
# другой ответ, чем канон, при том же назначении.
return '\r\n' if '\r\n' in text else '\n'
def esc_xml(s): def esc_xml(s):
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует """Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
(92142 сырых кавычки на корпус, ни одной &quot;); &quot; она принимает, но нормализует обратно.""" (92142 сырых кавычки на корпус, ни одной &quot;); &quot; она принимает, но нормализует обратно."""
@@ -673,90 +687,80 @@ def main():
# --- 4. Emit metadata XML (Roles/Name.xml) --- # --- 4. Emit metadata XML (Roles/Name.xml) ---
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append('<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"') # Объявления пространств имён — одной переменной и одной строкой, как пишет платформа.
lines.append(' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"') xmlns_decl = ('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"')
lines.append(' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"') # 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед
lines.append(' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"') # style): платформа держит объявления по алфавиту. В Rights.xml палитра НЕ идёт.
lines.append(' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"') if format_rank(format_version) >= 221:
lines.append(' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"') xmlns_decl = xmlns_decl.replace(
lines.append(' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"') ' xmlns:style=',
lines.append(' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"') ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
lines.append(' xmlns:v8="http://v8.1c.ru/8.1/data/core"') lines.append(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
lines.append(' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"') lines.append(f'\t<Role uuid="{uid}">')
lines.append(' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"') lines.append('\t\t<Properties>')
lines.append(' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"') lines.append(f'\t\t\t<Name>{role_name}</Name>')
lines.append(' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"') lines.append('\t\t\t<Synonym>')
lines.append(' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"') lines.append('\t\t\t\t<v8:item>')
lines.append(' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"') lines.append('\t\t\t\t\t<v8:lang>ru</v8:lang>')
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"') lines.append(f'\t\t\t\t\t<v8:content>{esc_xml(synonym)}</v8:content>')
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') lines.append('\t\t\t\t</v8:item>')
lines.append(f' version="{format_version}">') lines.append('\t\t\t</Synonym>')
lines.append(f' <Role uuid="{uid}">')
lines.append(' <Properties>')
lines.append(f' <Name>{role_name}</Name>')
lines.append(' <Synonym>')
lines.append(' <v8:item>')
lines.append(' <v8:lang>ru</v8:lang>')
lines.append(f' <v8:content>{esc_xml(synonym)}</v8:content>')
lines.append(' </v8:item>')
lines.append(' </Synonym>')
if comment: if comment:
lines.append(f' <Comment>{esc_xml(comment)}</Comment>') lines.append(f'\t\t\t<Comment>{esc_xml(comment)}</Comment>')
else: else:
lines.append(' <Comment/>') lines.append('\t\t\t<Comment/>')
lines.append(' </Properties>') lines.append('\t\t</Properties>')
lines.append(' </Role>') lines.append('\t</Role>')
lines.append('</MetaDataObject>') lines.append('</MetaDataObject>')
metadata_xml = '\n'.join(lines) + '\n' metadata_xml = '\r\n'.join(lines)
# --- 5. Emit Rights XML (Roles/Name/Ext/Rights.xml) --- # --- 5. Emit Rights XML (Roles/Name/Ext/Rights.xml) ---
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append('<Rights xmlns="http://v8.1c.ru/8.2/roles"') lines.append('<Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema"'
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"') ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights"'
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"') f' version="{format_version}">')
lines.append(f' xsi:type="Rights" version="{format_version}">')
# Global flags # Global flags
sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false' sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false'
sfab = str(defn['setForAttributesByDefault']).lower() if defn.get('setForAttributesByDefault') is not None else 'true' sfab = str(defn['setForAttributesByDefault']).lower() if defn.get('setForAttributesByDefault') is not None else 'true'
irco = str(defn['independentRightsOfChildObjects']).lower() if defn.get('independentRightsOfChildObjects') is not None else 'false' irco = str(defn['independentRightsOfChildObjects']).lower() if defn.get('independentRightsOfChildObjects') is not None else 'false'
lines.append(f' <setForNewObjects>{sfno}</setForNewObjects>') lines.append(f'\t<setForNewObjects>{sfno}</setForNewObjects>')
lines.append(f' <setForAttributesByDefault>{sfab}</setForAttributesByDefault>') lines.append(f'\t<setForAttributesByDefault>{sfab}</setForAttributesByDefault>')
lines.append(f' <independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>') lines.append(f'\t<independentRightsOfChildObjects>{irco}</independentRightsOfChildObjects>')
# Object blocks # Object blocks
total_rights = 0 total_rights = 0
for obj in parsed_objects: for obj in parsed_objects:
lines.append(' <object>') lines.append('\t<object>')
lines.append(f' <name>{obj["Name"]}</name>') lines.append(f'\t\t<name>{obj["Name"]}</name>')
for right in obj['Rights']: for right in obj['Rights']:
lines.append(' <right>') lines.append('\t\t<right>')
lines.append(f' <name>{right["Name"]}</name>') lines.append(f'\t\t\t<name>{right["Name"]}</name>')
lines.append(f' <value>{right["Value"]}</value>') lines.append(f'\t\t\t<value>{right["Value"]}</value>')
if right['Condition']: if right['Condition']:
lines.append(' <restrictionByCondition>') lines.append('\t\t\t<restrictionByCondition>')
lines.append(f' <condition>{esc_xml(right["Condition"])}</condition>') lines.append(f'\t\t\t\t<condition>{esc_xml(right["Condition"])}</condition>')
lines.append(' </restrictionByCondition>') lines.append('\t\t\t</restrictionByCondition>')
lines.append(' </right>') lines.append('\t\t</right>')
total_rights += 1 total_rights += 1
lines.append(' </object>') lines.append('\t</object>')
# RLS restriction templates # RLS restriction templates
template_count = 0 template_count = 0
if defn.get('templates'): if defn.get('templates'):
for tpl in defn['templates']: for tpl in defn['templates']:
lines.append(' <restrictionTemplate>') lines.append('\t<restrictionTemplate>')
lines.append(f' <name>{esc_xml(str(tpl["name"]))}</name>') lines.append(f'\t\t<name>{esc_xml(str(tpl["name"]))}</name>')
lines.append(f' <condition>{esc_xml(str(tpl["condition"]))}</condition>') lines.append(f'\t\t<condition>{esc_xml(str(tpl["condition"]))}</condition>')
lines.append(' </restrictionTemplate>') lines.append('\t</restrictionTemplate>')
template_count += 1 template_count += 1
lines.append('</Rights>') lines.append('</Rights>')
rights_xml = '\n'.join(lines) + '\n' rights_xml = '\r\n'.join(lines)
# --- 6. Write output files --- # --- 6. Write output files ---
out_dir = args.OutputDir out_dir = args.OutputDir
@@ -791,9 +795,13 @@ def main():
reg_result = None reg_result = None
if os.path.exists(config_xml_path): if os.path.exists(config_xml_path):
with open(config_xml_path, 'r', encoding='utf-8-sig') as f: # newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
# в LF при чтении и файл будет переписан в LF.
with open(config_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
raw_text = f.read() raw_text = f.read()
eol = detect_eol(raw_text)
# Check if already registered # Check if already registered
if f'<Role>{role_name}</Role>' in raw_text: if f'<Role>{role_name}</Role>' in raw_text:
reg_result = 'already' reg_result = 'already'
@@ -807,10 +815,15 @@ def main():
# Insert after last existing <Role> # Insert after last existing <Role>
last_match = matches[-1] last_match = matches[-1]
insert_pos = last_match.end() insert_pos = last_match.end()
raw_text = raw_text[:insert_pos] + f'\n\t\t\t{new_role_tag}' + raw_text[insert_pos:] raw_text = raw_text[:insert_pos] + eol + f'\t\t\t{new_role_tag}' + raw_text[insert_pos:]
else: else:
# No existing roles — insert before </ChildObjects> # No existing roles — insert before </ChildObjects>
raw_text = raw_text.replace('</ChildObjects>', f'\t\t\t{new_role_tag}\n\t\t</ChildObjects>') # Отступ вставки берём у закрывающего тега +1 уровень: подстановка
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
lambda m: m.group(1) + '\t' + new_role_tag + eol + m.group(1) + '</ChildObjects>',
raw_text, count=1)
write_utf8_bom(config_xml_path, raw_text) write_utf8_bom(config_xml_path, raw_text)
reg_result = 'added' reg_result = 'added'
@@ -1,4 +1,4 @@
# skd-compile v1.109 — Compile 1C DCS from JSON # skd-compile v1.111 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -3687,7 +3687,7 @@ if ($parentDir -and -not (Test-Path $parentDir)) {
$content = $script:xml.ToString() $content = $script:xml.ToString()
$utf8Bom = New-Object System.Text.UTF8Encoding $true $utf8Bom = New-Object System.Text.UTF8Encoding $true
[System.IO.File]::WriteAllText($OutputPath, $content, $utf8Bom) [System.IO.File]::WriteAllText($OutputPath, $content.TrimEnd("`r", "`n"), $utf8Bom)
# --- 14. Statistics --- # --- 14. Statistics ---
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# skd-compile v1.109 — Compile 1C DCS from JSON # skd-compile v1.111 — Compile 1C DCS from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -3069,7 +3069,7 @@ def main():
if parent_dir and not os.path.exists(parent_dir): if parent_dir and not os.path.exists(parent_dir):
os.makedirs(parent_dir, exist_ok=True) os.makedirs(parent_dir, exist_ok=True)
content = '\n'.join(lines) + '\n' content = '\r\n'.join(lines)
write_utf8_bom(output_path, content) write_utf8_bom(output_path, content)
# --- 5. Statistics --- # --- 5. Statistics ---
+10 -12
View File
@@ -1,4 +1,4 @@
# skd-edit v1.30 — Atomic 1C DCS editor # skd-edit v1.32 — Atomic 1C DCS editor
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
# NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701). # NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701).
param( param(
@@ -2192,7 +2192,6 @@ if ($rootOpenMatch.Success) { $script:RawRootOpening = $rootOpenMatch.Value } el
# Detect line ending convention so save can normalize back to whatever the source used. # Detect line ending convention so save can normalize back to whatever the source used.
# 1С Designer writes CRLF on Windows; LF-edited files should stay LF. # 1С Designer writes CRLF on Windows; LF-edited files should stay LF.
$script:LineEnding = if ($script:RawOriginal.Contains("`r`n")) { "`r`n" } else { "`n" }
$xmlDoc = New-Object System.Xml.XmlDocument $xmlDoc = New-Object System.Xml.XmlDocument
$xmlDoc.PreserveWhitespace = $true $xmlDoc.PreserveWhitespace = $true
@@ -4045,17 +4044,16 @@ if ($script:RawRootOpening) {
$content = [regex]::Replace($content, '<DataCompositionSchema\b[^>]*>', { param($m) $script:RawRootOpening }) $content = [regex]::Replace($content, '<DataCompositionSchema\b[^>]*>', { param($m) $script:RawRootOpening })
} }
# (2) normalize self-closing tags: `.NET XmlDocument` adds a space before `/>` # (2) Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# (`<foo bar="x" />`) but 1C-Designer writes `<foo bar="x"/>`. Strip the space. # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
$content = [regex]::Replace($content, '(?<=\S) />', '/>') # поэтому они идут первыми ветками альтернации и возвращаются как есть.
$content = [regex]::Replace($content, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# (3) normalize line endings to match source — operations may mix LF (from new # (3) Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# fragments) with whatever the source used (CRLF on Windows, LF on Linux/git). # новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
if ($script:LineEnding -eq "`r`n") { # Нужно потому, что операции подмешивают LF (новые фрагменты) к стилю источника.
$content = $content -replace '(?<!\r)\n', "`r`n" $targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
} else { $content = ($content -replace "`r`n", "`n") -replace "`n", $targetEol
$content = $content -replace "`r`n", "`n"
}
$enc = New-Object System.Text.UTF8Encoding($true) $enc = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($resolvedPath, $content, $enc) [System.IO.File]::WriteAllText($resolvedPath, $content, $enc)
+7 -4
View File
@@ -1,4 +1,4 @@
# skd-edit v1.30 — Atomic 1C DCS editor (Python port) # skd-edit v1.32 — Atomic 1C DCS editor (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -3440,9 +3440,12 @@ xml_bytes = xml_bytes.replace(
xml_text = xml_bytes.decode("utf-8") xml_text = xml_bytes.decode("utf-8")
if raw_root_opening: if raw_root_opening:
xml_text = re.sub(r"<DataCompositionSchema\b[^>]*>", lambda m: raw_root_opening, xml_text, count=1, flags=re.DOTALL) xml_text = re.sub(r"<DataCompositionSchema\b[^>]*>", lambda m: raw_root_opening, xml_text, count=1, flags=re.DOTALL)
# Normalize self-closing tags: lxml writes `<foo bar="x"/>` already (no space), but be # Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>` (lxml и так
# defensive — strip any space before `/>` so PS and PY ports stay byte-equivalent. # пишет плотно — правка защитная, чтобы порты оставались байт-эквивалентны). Внутри
xml_text = re.sub(r"(?<=\S) />", "/>", xml_text) # CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
xml_text = re.sub(r"(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />",
lambda m: "/>" if m.group(0) == " />" else m.group(0), xml_text)
# Канонизировать переносы к LF (убирает возможный &#13;), затем к стилю источника. # Канонизировать переносы к LF (убирает возможный &#13;), затем к стилю источника.
xml_text = xml_text.replace("&#13;\n", "\n").replace("&#13;", "").replace("\r\n", "\n").replace("\r", "\n") xml_text = xml_text.replace("&#13;\n", "\n").replace("&#13;", "").replace("\r\n", "\n").replace("\r", "\n")
+1 -1
View File
@@ -1,4 +1,4 @@
# skd-info v1.8 — Analyze 1C DCS structure # skd-info v1.9 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
+5 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# skd-info v1.8 — Analyze 1C DCS structure # skd-info v1.9 — Analyze 1C DCS structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -1802,7 +1802,10 @@ def main():
if not os.path.isabs(out_path): if not os.path.isabs(out_path):
out_path = os.path.join(os.getcwd(), out_path) out_path = os.path.join(os.getcwd(), out_path)
with open(out_path, "w", encoding="utf-8-sig") as fh: with open(out_path, "w", encoding="utf-8-sig") as fh:
fh.write("\n".join(result)) # Хвостовой перевод строки — как у PS-порта (WriteAllLines его добавляет).
# Это текстовый отчёт, а не XML метаданных: канон Конфигуратора сюда не
# относится, важен лишь паритет портов.
fh.write("\n".join(result) + "\n")
print(f"Written {total_lines} lines to {args.OutFile}") print(f"Written {total_lines} lines to {args.OutFile}")
sys.exit(0) sys.exit(0)
@@ -1,4 +1,4 @@
# subsystem-compile v1.11 — Create 1C subsystem from JSON definition # subsystem-compile v1.19 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[string]$DefinitionFile, [string]$DefinitionFile,
@@ -224,7 +224,7 @@ function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [strin
$childUuid = New-Guid-String $childUuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder 2048 $sb = New-Object System.Text.StringBuilder 2048
[void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>') [void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>')
[void]$sb.AppendLine("<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`">") [void]$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$formatVersion`">")
[void]$sb.AppendLine("`t<Subsystem uuid=`"$childUuid`">") [void]$sb.AppendLine("`t<Subsystem uuid=`"$childUuid`">")
[void]$sb.AppendLine("`t`t<Properties>") [void]$sb.AppendLine("`t`t<Properties>")
[void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>") [void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>")
@@ -240,7 +240,7 @@ function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [strin
[void]$sb.AppendLine("`t`t<ChildObjects/>") [void]$sb.AppendLine("`t`t<ChildObjects/>")
[void]$sb.AppendLine("`t</Subsystem>") [void]$sb.AppendLine("`t</Subsystem>")
[void]$sb.AppendLine('</MetaDataObject>') [void]$sb.AppendLine('</MetaDataObject>')
[System.IO.File]::WriteAllText($childPath, $sb.ToString(), $utf8Bom) [System.IO.File]::WriteAllText($childPath, $sb.ToString().TrimEnd("`r", "`n"), $utf8Bom)
} }
# --- 3. Content type normalization (plural→singular, Russian→English) --- # --- 3. Content type normalization (plural→singular, Russian→English) ---
@@ -447,12 +447,30 @@ function Detect-FormatVersion([string]$dir) {
$formatVersion = Detect-FormatVersion $OutputDir $formatVersion = Detect-FormatVersion $OutputDir
# Объявления пространств имён — одной переменной: места эмиссии её только интерполируют.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$script:xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $formatVersion) -ge 221) {
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
# --- 4. Build XML --- # --- 4. Build XML ---
$uuid = New-Guid-String $uuid = New-Guid-String
$indent = "`t`t`t" $indent = "`t`t`t"
X '<?xml version="1.0" encoding="UTF-8"?>' X '<?xml version="1.0" encoding="UTF-8"?>'
X "<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`">" X "<MetaDataObject $($script:xmlnsDecl) version=`"$formatVersion`">"
X "`t<Subsystem uuid=`"$uuid`">" X "`t<Subsystem uuid=`"$uuid`">"
X "`t`t<Properties>" X "`t`t<Properties>"
@@ -543,7 +561,7 @@ $targetXml = Join-Path $subsDir "$objName.xml"
# Write XML # Write XML
$xmlContent = $script:xml.ToString() $xmlContent = $script:xml.ToString()
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetXml, $xmlContent, $utf8Bom) [System.IO.File]::WriteAllText($targetXml, $xmlContent.TrimEnd("`r", "`n"), $utf8Bom)
Write-Host "[OK] Created: $targetXml" Write-Host "[OK] Created: $targetXml"
# Create subdirectory and stub files for children if they exist # Create subdirectory and stub files for children if they exist
@@ -657,6 +675,14 @@ if ($parentXmlPath -and (Test-Path $parentXmlPath)) {
$text = [System.Text.Encoding]::UTF8.GetString($bytes) $text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $parentXmlPath) -and ([System.IO.File]::ReadAllText($parentXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($parentXmlPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($parentXmlPath, $text, $utf8Bom)
Write-Host "[OK] Registered in: $parentXmlPath" Write-Host "[OK] Registered in: $parentXmlPath"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# subsystem-compile v1.11 — Create 1C subsystem from JSON definition # subsystem-compile v1.19 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -203,6 +203,20 @@ def detect_format_version(d):
d = parent d = parent
return "2.17" return "2.17"
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
def detect_eol(text):
# Перевод строки ВСТАВКИ берём из самого файла: канон CRLF относится к файлам,
# которые мы создаём, а правка существующего сохраняет его стиль (#44/#46/#47).
# Семантика та же, что у _detect_xml_style в остальных портах: есть CRLF → CRLF.
# Мажоритарное правило здесь было расхождением — на смешанном входе оно давало
# другой ответ, чем канон, при том же назначении.
return '\r\n' if '\r\n' in text else '\n'
def esc_xml(s): def esc_xml(s):
"""Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует """Экранирование ТЕКСТА элемента: только & < > . Кавычки платформа в тексте не экранирует
@@ -240,30 +254,45 @@ def split_camel_case(name):
return result return result
# Объявления пространств имён — одной переменной: места эмиссии её только подставляют.
# Правки шапки (как xmlns:pal в формате 2.21) делаются в одном месте, в main.
XMLNS_DECL = (
'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"'
)
def apply_pal_ns(format_version):
"""2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
дописать в конец нельзя."""
global XMLNS_DECL
if format_rank(format_version) >= 221:
XMLNS_DECL = XMLNS_DECL.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
def write_child_subsystem_stub(child_path, child_name, format_version): def write_child_subsystem_stub(child_path, child_name, format_version):
child_uuid = new_uuid() child_uuid = new_uuid()
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append( lines.append(f'<MetaDataObject {XMLNS_DECL} version="{format_version}">')
'<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" '
f'version="{format_version}">'
)
lines.append(f'\t<Subsystem uuid="{child_uuid}">') lines.append(f'\t<Subsystem uuid="{child_uuid}">')
lines.append('\t\t<Properties>') lines.append('\t\t<Properties>')
lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>') lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>')
@@ -279,7 +308,7 @@ def write_child_subsystem_stub(child_path, child_name, format_version):
lines.append('\t\t<ChildObjects/>') lines.append('\t\t<ChildObjects/>')
lines.append('\t</Subsystem>') lines.append('\t</Subsystem>')
lines.append('</MetaDataObject>') lines.append('</MetaDataObject>')
write_utf8_bom(child_path, '\n'.join(lines) + '\n') write_utf8_bom(child_path, '\r\n'.join(lines))
def main(): def main():
@@ -410,6 +439,8 @@ def main():
return f'{type_part}.{name_part}' return f'{type_part}.{name_part}'
format_version = detect_format_version(output_dir) format_version = detect_format_version(output_dir)
apply_pal_ns(format_version)
xmlns_decl = XMLNS_DECL
# --- 3. Resolve defaults --- # --- 3. Resolve defaults ---
synonym = str(defn['synonym']) if defn.get('synonym') else split_camel_case(obj_name) synonym = str(defn['synonym']) if defn.get('synonym') else split_camel_case(obj_name)
@@ -447,7 +478,7 @@ def main():
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append(f'<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="{format_version}">') lines.append(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
lines.append(f'\t<Subsystem uuid="{uid}">') lines.append(f'\t<Subsystem uuid="{uid}">')
lines.append('\t\t<Properties>') lines.append('\t\t<Properties>')
@@ -525,7 +556,7 @@ def main():
target_xml = os.path.join(subs_dir, f'{obj_name}.xml') target_xml = os.path.join(subs_dir, f'{obj_name}.xml')
# Write XML # Write XML
xml_content = '\n'.join(lines) + '\n' xml_content = '\r\n'.join(lines)
write_utf8_bom(target_xml, xml_content) write_utf8_bom(target_xml, xml_content)
print(f"[OK] Created: {target_xml}") print(f"[OK] Created: {target_xml}")
@@ -555,9 +586,12 @@ def main():
parent_xml_path = config_xml parent_xml_path = config_xml
if parent_xml_path and os.path.exists(parent_xml_path): if parent_xml_path and os.path.exists(parent_xml_path):
with open(parent_xml_path, 'r', encoding='utf-8-sig') as f: # newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
# в LF при чтении и файл будет переписан в LF.
with open(parent_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
raw_text = f.read() raw_text = f.read()
eol = detect_eol(raw_text)
doc = ET.ElementTree(ET.fromstring(raw_text)) doc = ET.ElementTree(ET.fromstring(raw_text))
root = doc.getroot() root = doc.getroot()
md_ns = 'http://v8.1c.ru/8.3/MDClasses' md_ns = 'http://v8.1c.ru/8.3/MDClasses'
@@ -593,11 +627,15 @@ def main():
if not already_exists: if not already_exists:
# Use raw text manipulation to preserve formatting # Use raw text manipulation to preserve formatting
if '<ChildObjects/>' in raw_text: if '<ChildObjects/>' in raw_text:
replacement = f'<ChildObjects>\n\t\t\t<Subsystem>{esc_xml(obj_name)}</Subsystem>\n\t\t</ChildObjects>' replacement = ('<ChildObjects>' + eol + f'\t\t\t<Subsystem>{esc_xml(obj_name)}</Subsystem>' + eol + '\t\t</ChildObjects>')
raw_text = raw_text.replace('<ChildObjects/>', replacement, 1) raw_text = raw_text.replace('<ChildObjects/>', replacement, 1)
elif '</ChildObjects>' in raw_text: elif '</ChildObjects>' in raw_text:
insert_line = f'\t\t\t<Subsystem>{esc_xml(obj_name)}</Subsystem>\n' # Отступ вставки берём у закрывающего тега +1 уровень: подстановка
raw_text = raw_text.replace('</ChildObjects>', insert_line + '\t\t</ChildObjects>', 1) # по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
lambda m: m.group(1) + '\t' + f'<Subsystem>{esc_xml(obj_name)}</Subsystem>' + eol + m.group(1) + '</ChildObjects>',
raw_text, count=1)
write_utf8_bom(parent_xml_path, raw_text) write_utf8_bom(parent_xml_path, raw_text)
print(f"[OK] Registered in: {parent_xml_path}") print(f"[OK] Registered in: {parent_xml_path}")
@@ -1,4 +1,4 @@
# subsystem-edit v1.8 — Edit existing 1C subsystem XML # subsystem-edit v1.15 — Edit existing 1C subsystem XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath, [Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath,
@@ -261,6 +261,24 @@ $script:xmlDoc.Load($resolvedPath)
$script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version") $script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version")
if (-not $script:formatVersion) { $script:formatVersion = "2.17" } if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
# Объявления пространств имён — одной переменной: место эмиссии её только интерполирует.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$script:xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
$script:utf8Bom = New-Object System.Text.UTF8Encoding($true) $script:utf8Bom = New-Object System.Text.UTF8Encoding($true)
$script:addCount = 0 $script:addCount = 0
@@ -316,7 +334,7 @@ function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [strin
$childUuid = New-Guid-String $childUuid = New-Guid-String
$sb = New-Object System.Text.StringBuilder 2048 $sb = New-Object System.Text.StringBuilder 2048
[void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>') [void]$sb.AppendLine('<?xml version="1.0" encoding="UTF-8"?>')
[void]$sb.AppendLine("<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`">") [void]$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$formatVersion`">")
[void]$sb.AppendLine("`t<Subsystem uuid=`"$childUuid`">") [void]$sb.AppendLine("`t<Subsystem uuid=`"$childUuid`">")
[void]$sb.AppendLine("`t`t<Properties>") [void]$sb.AppendLine("`t`t<Properties>")
[void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>") [void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>")
@@ -332,7 +350,7 @@ function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [strin
[void]$sb.AppendLine("`t`t<ChildObjects/>") [void]$sb.AppendLine("`t`t<ChildObjects/>")
[void]$sb.AppendLine("`t</Subsystem>") [void]$sb.AppendLine("`t</Subsystem>")
[void]$sb.AppendLine('</MetaDataObject>') [void]$sb.AppendLine('</MetaDataObject>')
[System.IO.File]::WriteAllText($childPath, $sb.ToString(), $utf8Bom) [System.IO.File]::WriteAllText($childPath, $sb.ToString().TrimEnd("`r", "`n"), $utf8Bom)
} }
function Import-Fragment([string]$xmlString) { function Import-Fragment([string]$xmlString) {
@@ -655,8 +673,16 @@ $memStream.Close()
$text = [System.Text.Encoding]::UTF8.GetString($bytes) $text = [System.Text.Encoding]::UTF8.GetString($bytes)
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) } if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"') $text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
$utf8Bom = New-Object System.Text.UTF8Encoding($true) $utf8Bom = New-Object System.Text.UTF8Encoding($true)
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $resolvedPath) -and ([System.IO.File]::ReadAllText($resolvedPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom) [System.IO.File]::WriteAllText($resolvedPath, $text, $utf8Bom)
Info "Saved: $resolvedPath" Info "Saved: $resolvedPath"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# subsystem-edit v1.8 — Edit existing 1C subsystem XML # subsystem-edit v1.15 — Edit existing 1C subsystem XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -203,30 +203,51 @@ def write_utf8_bom(path, content):
f.write(content) f.write(content)
# Объявления пространств имён — одной переменной: место эмиссии её только подставляет.
# Правки шапки (как xmlns:pal в формате 2.21) делаются в одном месте, в main.
XMLNS_DECL = (
'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"'
)
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
def apply_pal_ns(format_version):
"""2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
дописать в конец нельзя."""
global XMLNS_DECL
if format_rank(format_version) >= 221:
XMLNS_DECL = XMLNS_DECL.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
def write_child_subsystem_stub(child_path, child_name, format_version): def write_child_subsystem_stub(child_path, child_name, format_version):
child_uuid = new_uuid() child_uuid = new_uuid()
lines = [] lines = []
lines.append('<?xml version="1.0" encoding="UTF-8"?>') lines.append('<?xml version="1.0" encoding="UTF-8"?>')
lines.append( lines.append(f'<MetaDataObject {XMLNS_DECL} version="{format_version}">')
'<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" '
f'version="{format_version}">'
)
lines.append(f'\t<Subsystem uuid="{child_uuid}">') lines.append(f'\t<Subsystem uuid="{child_uuid}">')
lines.append('\t\t<Properties>') lines.append('\t\t<Properties>')
lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>') lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>')
@@ -242,7 +263,7 @@ def write_child_subsystem_stub(child_path, child_name, format_version):
lines.append('\t\t<ChildObjects/>') lines.append('\t\t<ChildObjects/>')
lines.append('\t</Subsystem>') lines.append('\t</Subsystem>')
lines.append('</MetaDataObject>') lines.append('</MetaDataObject>')
write_utf8_bom(child_path, '\n'.join(lines) + '\n') write_utf8_bom(child_path, '\r\n'.join(lines))
MD_NS = "http://v8.1c.ru/8.3/MDClasses" MD_NS = "http://v8.1c.ru/8.3/MDClasses"
XR_NS = "http://v8.1c.ru/8.3/xcf/readable" XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
@@ -452,21 +473,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -538,6 +560,7 @@ def main():
tree = etree.parse(resolved_path, xml_parser) tree = etree.parse(resolved_path, xml_parser)
xml_root = tree.getroot() xml_root = tree.getroot()
format_version = xml_root.get("version") or "2.17" format_version = xml_root.get("version") or "2.17"
apply_pal_ns(format_version)
add_count = 0 add_count = 0
remove_count = 0 remove_count = 0
@@ -1,4 +1,4 @@
# template-add v1.11 — Add template to 1C object # template-add v1.21 — Add template to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -220,6 +220,14 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
function Detect-FormatVersion([string]$dir) { function Detect-FormatVersion([string]$dir) {
$d = $dir $d = $dir
while ($d) { while ($d) {
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
$extPath = "$d.xml"
if (Test-Path $extPath) {
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
}
$cfgPath = Join-Path $d "Configuration.xml" $cfgPath = Join-Path $d "Configuration.xml"
if (Test-Path $cfgPath) { if (Test-Path $cfgPath) {
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8) $cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
@@ -235,7 +243,31 @@ function Detect-FormatVersion([string]$dir) {
return "2.17" return "2.17"
} }
$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path # Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
$formatVersion = $null
$objHead = [System.IO.File]::ReadAllText((Resolve-Path $rootXmlPath).Path, [System.Text.Encoding]::UTF8)
$objHead = $objHead.Substring(0, [Math]::Min(2000, $objHead.Length))
if ($objHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { $formatVersion = $Matches[1] }
if (-not $formatVersion) { $formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path }
# Объявления пространств имён — одной переменной: место эмиссии её только интерполирует.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $formatVersion) -ge 221) {
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
# --- 1. Метаданные макета (Templates/<TemplateName>.xml) --- # --- 1. Метаданные макета (Templates/<TemplateName>.xml) ---
@@ -243,7 +275,7 @@ $templateUuid = [guid]::NewGuid().ToString()
$templateMetaXml = @" $templateMetaXml = @"
<?xml version="1.0" encoding="UTF-8"?> <?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=`"$formatVersion`"> <MetaDataObject $xmlnsDecl version="$formatVersion">
<Template uuid="$templateUuid"> <Template uuid="$templateUuid">
<Properties> <Properties>
<Name>$TemplateName</Name> <Name>$TemplateName</Name>
@@ -260,7 +292,18 @@ $templateMetaXml = @"
</MetaDataObject> </MetaDataObject>
"@ "@
[System.IO.File]::WriteAllText($templateMetaPath, $templateMetaXml, $encBom) # XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
#
# Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
# копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
#
# HTML-макет сюда НЕ идёт — платформа хранит его с LF.
function Write-XmlFile([string]$path, [string]$text, $encoding) {
$t = ($text -replace "`r`n", "`n") -replace "`n", "`r`n"
[System.IO.File]::WriteAllText($path, $t.TrimEnd("`r", "`n"), $encoding)
}
Write-XmlFile $templateMetaPath $templateMetaXml $encBom
# --- 2. Содержимое макета (Templates/<TemplateName>/Ext/Template.<ext>) --- # --- 2. Содержимое макета (Templates/<TemplateName>/Ext/Template.<ext>) ---
@@ -285,12 +328,14 @@ switch ($TemplateType) {
[System.IO.File]::WriteAllText($templateFilePath, "", $encBom) [System.IO.File]::WriteAllText($templateFilePath, "", $encBom)
} }
"SpreadsheetDocument" { "SpreadsheetDocument" {
# Пустой макет — самозакрывающимся корнем: пустых пар платформа не пишет
# ни в одной форме (0 на 65 040 XML выгрузки acc_8.3.27, включая разнесённые
# по строкам). Для XML `<A/>` и `<A></A>` тождественны по спецификации.
$content = @" $content = @"
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<SpreadsheetDocument xmlns="http://v8.1c.ru/spreadsheet/document" xmlns:ss="http://v8.1c.ru/spreadsheet/document" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema"> <SpreadsheetDocument xmlns="http://v8.1c.ru/spreadsheet/document" xmlns:ss="http://v8.1c.ru/spreadsheet/document" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:xs="http://www.w3.org/2001/XMLSchema"/>
</SpreadsheetDocument>
"@ "@
[System.IO.File]::WriteAllText($templateFilePath, $content, $encBom) Write-XmlFile $templateFilePath $content $encBom
} }
"BinaryData" { "BinaryData" {
[System.IO.File]::WriteAllBytes($templateFilePath, @()) [System.IO.File]::WriteAllBytes($templateFilePath, @())
@@ -312,7 +357,7 @@ switch ($TemplateType) {
</dataSource> </dataSource>
</DataCompositionSchema> </DataCompositionSchema>
"@ "@
[System.IO.File]::WriteAllText($templateFilePath, $content, $encBom) Write-XmlFile $templateFilePath $content $encBom
} }
} }
@@ -391,12 +436,27 @@ if ($TemplateType -eq "DataCompositionSchema") {
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) $memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer) $xmlDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom)
Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)" Write-Host "[OK] Создан макет: $TemplateName ($TemplateType)"
if ($alreadyRegistered) { if ($alreadyRegistered) {
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# add-template v1.11 — Add template to 1C object # template-add v1.21 — Add template to 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -215,21 +215,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -246,13 +247,40 @@ def save_xml_with_bom(tree, path):
def write_text_with_bom(path, text): def write_text_with_bom(path, text):
"""Write text to file with UTF-8 BOM.""" """Write text to file with UTF-8 BOM.
with open(path, "w", encoding="utf-8-sig") as f:
newline="" обязателен: в текстовом режиме Python на Windows превратил бы \\n в
\\r\\n, а на macOS оставил \\n вывод навыка зависел бы от ОС. Через эту функцию
идёт HTML-макет, а его платформа хранит именно с LF (корпус: 399 LF из 400).
"""
with open(path, "w", encoding="utf-8-sig", newline="") as f:
f.write(text) f.write(text)
def write_xml_file(path, content):
"""XML в каноне выгрузки Конфигуратора: CRLF в разделителях, без перевода в конце.
Копия этой функции есть в каждом навыке-эмиттере (навыки автономны). Держать
копии одинаковыми сознательно: разошедшиеся копии сводят на нет весь смысл.
HTML-макет сюда НЕ идёт платформа хранит его с LF.
"""
text = content.replace('\r\n', '\n').replace('\n', '\r\n').rstrip('\r\n')
write_text_with_bom(path, text)
def detect_format_version(d): def detect_format_version(d):
while d: while d:
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
ext_path = d + ".xml"
if os.path.isfile(ext_path):
with open(ext_path, "r", encoding="utf-8-sig") as f:
ext_head = f.read(2000)
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
if m:
return m.group(1)
cfg_path = os.path.join(d, "Configuration.xml") cfg_path = os.path.join(d, "Configuration.xml")
if os.path.isfile(cfg_path): if os.path.isfile(cfg_path):
with open(cfg_path, "r", encoding="utf-8-sig") as f: with open(cfg_path, "r", encoding="utf-8-sig") as f:
@@ -266,6 +294,12 @@ def detect_format_version(d):
d = parent d = parent
return "2.17" return "2.17"
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
def main(): def main():
sys.stdout.reconfigure(encoding="utf-8") sys.stdout.reconfigure(encoding="utf-8")
@@ -289,7 +323,6 @@ def main():
tmpl = TYPE_MAP[template_type] tmpl = TYPE_MAP[template_type]
format_version = detect_format_version(os.path.abspath(src_dir))
# --- Checks --- # --- Checks ---
@@ -331,18 +364,21 @@ def main():
assert_edit_allowed(root_xml_path, "editable") assert_edit_allowed(root_xml_path, "editable")
# --- Create directories --- # Версию берём прежде всего из корня самого объекта — он её несёт всегда, а у автономной
# внешней обработки/отчёта подниматься к Configuration.xml просто некуда.
format_version = None
with open(root_xml_path, "r", encoding="utf-8-sig") as f:
obj_head = f.read(2000)
m_ver = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', obj_head)
if m_ver:
format_version = m_ver.group(1)
if not format_version:
format_version = detect_format_version(os.path.abspath(src_dir))
template_ext_dir = os.path.join(templates_dir, template_name, "Ext") # Объявления пространств имён — одной переменной: место эмиссии её только подставляет.
os.makedirs(template_ext_dir, exist_ok=True) # Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
xmlns_decl = (
# --- 1. Template metadata (Templates/<TemplateName>.xml) --- 'xmlns="http://v8.1c.ru/8.3/MDClasses"'
template_uuid = str(uuid.uuid4())
template_meta_xml = (
'<?xml version="1.0" encoding="UTF-8"?>\n'
'<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"' ' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"' ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"' ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
@@ -359,7 +395,28 @@ def main():
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"' ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
' xmlns:xs="http://www.w3.org/2001/XMLSchema"' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
f' version="{format_version}">\n' )
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if format_rank(format_version) >= 221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
# --- Create directories ---
template_ext_dir = os.path.join(templates_dir, template_name, "Ext")
os.makedirs(template_ext_dir, exist_ok=True)
# --- 1. Template metadata (Templates/<TemplateName>.xml) ---
template_uuid = str(uuid.uuid4())
template_meta_xml = (
'<?xml version="1.0" encoding="UTF-8"?>\n'
f'<MetaDataObject {xmlns_decl} version="{format_version}">\n'
f'\t<Template uuid="{template_uuid}">\n' f'\t<Template uuid="{template_uuid}">\n'
'\t\t<Properties>\n' '\t\t<Properties>\n'
f'\t\t\t<Name>{template_name}</Name>\n' f'\t\t\t<Name>{template_name}</Name>\n'
@@ -376,7 +433,7 @@ def main():
'</MetaDataObject>' '</MetaDataObject>'
) )
write_text_with_bom(template_meta_path, template_meta_xml) write_xml_file(template_meta_path, template_meta_xml)
# --- 2. Template content (Templates/<TemplateName>/Ext/Template.<ext>) --- # --- 2. Template content (Templates/<TemplateName>/Ext/Template.<ext>) ---
@@ -400,15 +457,17 @@ def main():
write_text_with_bom(template_file_path, "") write_text_with_bom(template_file_path, "")
elif template_type == "SpreadsheetDocument": elif template_type == "SpreadsheetDocument":
# Пустой макет — самозакрывающимся корнем: пустых пар платформа не пишет
# ни в одной форме (0 на 65 040 XML выгрузки acc_8.3.27, включая разнесённые
# по строкам). Для XML `<A/>` и `<A></A>` тождественны по спецификации.
content = ( content = (
'<?xml version="1.0" encoding="UTF-8"?>\n' '<?xml version="1.0" encoding="UTF-8"?>\n'
'<SpreadsheetDocument xmlns="http://v8.1c.ru/spreadsheet/document"' '<SpreadsheetDocument xmlns="http://v8.1c.ru/spreadsheet/document"'
' xmlns:ss="http://v8.1c.ru/spreadsheet/document"' ' xmlns:ss="http://v8.1c.ru/spreadsheet/document"'
' xmlns:v8="http://v8.1c.ru/8.1/data/core"' ' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
' xmlns:xs="http://www.w3.org/2001/XMLSchema">\n' ' xmlns:xs="http://www.w3.org/2001/XMLSchema"/>'
'</SpreadsheetDocument>'
) )
write_text_with_bom(template_file_path, content) write_xml_file(template_file_path, content)
elif template_type == "BinaryData": elif template_type == "BinaryData":
with open(template_file_path, "wb") as f: with open(template_file_path, "wb") as f:
@@ -431,7 +490,7 @@ def main():
'\t</dataSource>\n' '\t</dataSource>\n'
'</DataCompositionSchema>' '</DataCompositionSchema>'
) )
write_text_with_bom(template_file_path, content) write_xml_file(template_file_path, content)
# --- 3. Modify root XML --- # --- 3. Modify root XML ---
@@ -1,4 +1,4 @@
# template-remove v1.3 — Remove template from 1C object # template-remove v1.7 — Remove template from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory)] [Parameter(Mandatory)]
@@ -64,6 +64,10 @@ foreach ($node in $templateNodes) {
$parent.RemoveChild($prev) | Out-Null $parent.RemoveChild($prev) | Out-Null
} }
$parent.RemoveChild($node) | Out-Null $parent.RemoveChild($node) | Out-Null
# Опустевший контейнер: остаётся отступ-whitespace, и XmlWriter пишет пару
# <ChildObjects>\n\t\t</ChildObjects>. Платформа пишет только <ChildObjects/>
# (1394 самозакрывающихся на acc+erp, пустых пар ни в одной форме — 0).
if ($parent.SelectNodes("*").Count -eq 0) { $parent.IsEmpty = $true }
break break
} }
} }
@@ -80,11 +84,26 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) $memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$xmlDoc.Save($writer) $xmlDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$xmlText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($xmlText.Length -gt 0 -and $xmlText[0] -eq [char]0xFEFF) { $xmlText = $xmlText.Substring(1) }
$xmlText = $xmlText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$xmlText = [regex]::Replace($xmlText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $rootXmlFull.Path) -and ([System.IO.File]::ReadAllText($rootXmlFull.Path) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$xmlText = ($xmlText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($rootXmlFull.Path, $xmlText, $encBom)
Write-Host "[OK] Макет $TemplateName удалён из $rootXmlPath" Write-Host "[OK] Макет $TemplateName удалён из $rootXmlPath"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# remove-template v1.3 — Remove template from 1C object # template-remove v1.7 — Remove template from 1C object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
@@ -30,21 +30,22 @@ def _detect_xml_style(path):
def _finalize_xml_bytes(xml_bytes, style): def _finalize_xml_bytes(xml_bytes, style):
"""Привести сериализованные байты к стилю оригинала (или к дефолту, если style is None).""" """Привести байты к стилю оригинала; для НОВОГО файла (style is None) — к канону
enc_decl = style["enc"] if style else "utf-8" выгрузки Конфигуратора: encoding="UTF-8", CRLF в разделителях, без перевода в конце."""
enc_decl = style["enc"] if style else "UTF-8"
xml_bytes = xml_bytes.replace( xml_bytes = xml_bytes.replace(
b"<?xml version='1.0' encoding='UTF-8'?>", b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>') b'<?xml version="1.0" encoding="' + enc_decl.encode("ascii") + b'"?>')
# Канонизировать переносы к LF (убирает &#13; от \r в tail'ах) # Канонизировать переносы к LF (убирает &#13; от \r в tail'ах)
xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"") xml_bytes = (xml_bytes.replace(b"&#13;\n", b"\n").replace(b"&#13;", b"")
.replace(b"\r\n", b"\n").replace(b"\r", b"\n")) .replace(b"\r\n", b"\n").replace(b"\r", b"\n"))
# Финальный перенос — как в оригинале (новый файл → есть) # Финальный перенос — как в оригинале (новый файл → нет, канон #57)
want_final_nl = style["final_nl"] if style else True want_final_nl = style["final_nl"] if style else False
xml_bytes = xml_bytes.rstrip(b"\n") xml_bytes = xml_bytes.rstrip(b"\n")
if want_final_nl: if want_final_nl:
xml_bytes += b"\n" xml_bytes += b"\n"
# EOL — как в оригинале (новый файл → LF, текущее поведение) # EOL — как в оригинале (новый файл → CRLF, канон #57)
if style and style["crlf"]: if (style["crlf"] if style else True):
xml_bytes = xml_bytes.replace(b"\n", b"\r\n") xml_bytes = xml_bytes.replace(b"\n", b"\r\n")
return xml_bytes return xml_bytes
@@ -119,6 +120,10 @@ def main():
if parent.text and parent.text.strip() == "": if parent.text and parent.text.strip() == "":
parent.text = "" parent.text = ""
parent.remove(node) parent.remove(node)
# Опустевший контейнер: text="" сериализуется парой <ChildObjects></ChildObjects>,
# а нужен <ChildObjects/> — PS-порт через DOM даёт именно его.
if len(parent) == 0 and not (parent.text or "").strip():
parent.text = None
break break
# Clear MainDataCompositionSchema if it pointed to this template # Clear MainDataCompositionSchema if it pointed to this template
+19 -12
View File
@@ -35,21 +35,28 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-info.ps1" <па
|----------|:------------:|----------| |----------|:------------:|----------|
| `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) | | `-ApachePath <путь>` | нет | Корень Apache (по умолчанию `tools/apache24`) |
## Формат вывода ## Как читать вывод
``` Три секции: состояние сервера, список публикаций (имя, URL, строка соединения, теги сервисов),
=== Apache Web Server === хвост журнала Apache.
Status: Запущен (PID: 12345)
Path: C:\...\tools\apache24
Port: 8081
Module: C:/Program Files/1cv8/8.3.24.1691/bin/wsap24.dll
=== Опубликованные базы === | В выводе | Что значит |
mydb http://localhost:8081/mydb File="C:\Bases\MyDB"; |----------|------------|
| `Status: Остановлен` | Apache не запущен — публикации в `httpd.conf` есть, но URL-ы не отвечают. Поднимается повторным `/web-publish` |
| `Port: 9191 (не отвечает)` | Порт не слушается — URL-ы не откроются. Вместе с `Status: Запущен` значит, что сервер не поднялся (занятый порт, битый конфиг): смотри секцию ошибок |
| `[WARN] Обнаружен сторонний Apache` | На машине работает чужой экземпляр — может держать порт; наши команды им не управляют |
| `[WS HTTP OData]` | Что опубликовано для базы: SOAP-сервисы, HTTP-сервисы, стандартный OData |
| `+ext` (`WS+ext`) | Публикуются и сервисы, поставляемые расширениями конфигурации |
| `(нет публикаций)` | Apache установлен, но ни одна база не опубликована — нужен `/web-publish` |
| `(ошибок нет)` / `(нет файла)` | Норма, а не сбой: ошибок в журнале нет или он ещё не создан |
=== Последние ошибки === В ошибках — последние записи уровня `error` и выше, информационные строки отфильтрованы. Журнал не
(пусто) ротируется, поэтому запись может быть полугодовой давности: смотри дату, прежде чем считать её
``` причиной текущей проблемы. Рядом печатается путь к журналу — нужен полный контекст, прочитай файл.
Строка публикации говорит «так настроено», а не «база отвечает»: проверяется доступность самого
Apache, но не публикаций. Убедиться в конкретной базе можно запросом к её URL — но учти, что такой
запрос поднимает сеанс 1С и занимает лицензию.
## Примеры ## Примеры
+59 -10
View File
@@ -1,4 +1,4 @@
# web-info v1.0 — Apache & 1C publication status # web-info v1.3 — Apache & 1C publication status
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
@@ -82,7 +82,24 @@ $port = "—"
if ($confContent -match '(?m)^Listen\s+(\d+)') { if ($confContent -match '(?m)^Listen\s+(\d+)') {
$port = $Matches[1] $port = $Matches[1]
} }
Write-Host "Port: $port" # Проверяем именно TCP-порт: запрос к публикации поднял бы сеанс 1С и занял лицензию
$portState = ""
if ($port -ne "") {
$client = New-Object System.Net.Sockets.TcpClient
try {
$async = $client.BeginConnect("127.0.0.1", [int]$port, $null, $null)
if ($async.AsyncWaitHandle.WaitOne(1000) -and $client.Connected) {
$portState = " (слушается)"
} else {
$portState = " (не отвечает)"
}
} catch {
$portState = " (не отвечает)"
} finally {
$client.Close()
}
}
Write-Host "Port: $port$portState"
# Extract wsap24 path # Extract wsap24 path
if ($confContent -match 'LoadModule\s+_1cws_module\s+"([^"]+)"') { if ($confContent -match 'LoadModule\s+_1cws_module\s+"([^"]+)"') {
@@ -115,9 +132,20 @@ if ($pubMatches.Count -eq 0) {
# Detect published services # Detect published services
$svcTags = @() $svcTags = @()
if (Test-Path $vrdPath) { if (Test-Path $vrdPath) {
if ($vrdContent -match '<ws\s') { $svcTags += "WS" } # "+ext" — publishExtensionsByDefault: сервисы расширений тоже опубликованы
if ($vrdContent -match '<httpServices\s') { $svcTags += "HTTP" } if ($vrdContent -match '<ws\s[^>]*>') {
if ($vrdContent -match 'enableStandardOdata\s*=\s*"true"') { $svcTags += "OData" } $wsTag = "WS"
if ($Matches[0] -match 'publishExtensionsByDefault\s*=\s*"true"') { $wsTag += "+ext" }
$svcTags += $wsTag
}
if ($vrdContent -match '<httpServices\s[^>]*>') {
$hsTag = "HTTP"
if ($Matches[0] -match 'publishExtensionsByDefault\s*=\s*"true"') { $hsTag += "+ext" }
$svcTags += $hsTag
}
# Актуальная форма — <standardOdata enable="true"/>; enableStandardOdata — до 8.3.9
if ($vrdContent -match '<standardOdata\s[^>]*enable\s*=\s*"true"' -or
$vrdContent -match 'enableStandardOdata\s*=\s*"true"') { $svcTags += "OData" }
} }
$svcLabel = if ($svcTags.Count -gt 0) { " [" + ($svcTags -join " ") + "]" } else { "" } $svcLabel = if ($svcTags.Count -gt 0) { " [" + ($svcTags -join " ") + "]" } else { "" }
@@ -133,15 +161,36 @@ if ($pubMatches.Count -eq 0) {
Write-Host "" Write-Host ""
Write-Host "=== Последние ошибки ===" -ForegroundColor Cyan Write-Host "=== Последние ошибки ===" -ForegroundColor Cyan
$errorLog = Join-Path (Join-Path $ApachePath "logs") "error.log" # Имя файла берём из httpd.conf: сборки Apache расходятся (error.log / error_log)
if (Test-Path $errorLog) { $errorLog = $null
$lines = Get-Content $errorLog -Tail 5 -ErrorAction SilentlyContinue if ($confContent -match '(?m)^\s*ErrorLog\s+"?([^"\r\n]+)"?') {
if ($lines -and $lines.Count -gt 0) { $logPath = $Matches[1].Trim()
if ([System.IO.Path]::IsPathRooted($logPath)) {
$errorLog = $logPath
} else {
$errorLog = Join-Path $ApachePath ($logPath -replace '/','\')
}
}
if (-not $errorLog -or -not (Test-Path $errorLog)) {
$errorLog = @("error_log", "error.log") |
ForEach-Object { Join-Path (Join-Path $ApachePath "logs") $_ } |
Where-Object { Test-Path $_ } |
Select-Object -First 1
}
if ($errorLog -and (Test-Path $errorLog)) {
Write-Host "Журнал: $errorLog" -ForegroundColor DarkGray
# Только error и выше: warn забит штатным шумом winnt_accept, notice — строки старта.
# AH02538 — след нашего же рестарта (родитель убит), пишется как crit при каждой публикации
$lines = Get-Content $errorLog -ErrorAction SilentlyContinue |
Where-Object { $_ -match '\[[a-z_]+:(error|crit|alert|emerg)\]' -and $_ -notmatch 'AH02538' } |
Select-Object -Last 5
if ($lines) {
foreach ($line in $lines) { foreach ($line in $lines) {
Write-Host " $line" -ForegroundColor DarkGray Write-Host " $line" -ForegroundColor DarkGray
} }
} else { } else {
Write-Host "(пусто)" -ForegroundColor Green Write-Host "(ошибок нет)" -ForegroundColor Green
} }
} else { } else {
Write-Host "(нет файла)" -ForegroundColor Green Write-Host "(нет файла)" -ForegroundColor Green
+45 -13
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# web-info v1.0 — Apache & 1C publication status # web-info v1.3 — Apache & 1C publication status
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
""" """
@@ -11,6 +11,7 @@
import argparse import argparse
import os import os
import re import re
import socket
import sys import sys
import psutil import psutil
@@ -92,7 +93,15 @@ def main():
m = re.search(r'(?m)^Listen\s+(\d+)', conf_content) m = re.search(r'(?m)^Listen\s+(\d+)', conf_content)
if m: if m:
port = m.group(1) port = m.group(1)
print(f'Port: {port}') # Проверяем именно TCP-порт: запрос к публикации поднял бы сеанс 1С и занял лицензию
port_state = ''
if port != '':
try:
with socket.create_connection(('127.0.0.1', int(port)), timeout=1):
port_state = ' (слушается)'
except OSError:
port_state = ' (не отвечает)'
print(f'Port: {port}{port_state}')
# Extract wsap24 path # Extract wsap24 path
m = re.search(r'LoadModule\s+_1cws_module\s+"([^"]+)"', conf_content) m = re.search(r'LoadModule\s+_1cws_module\s+"([^"]+)"', conf_content)
@@ -124,11 +133,18 @@ def main():
# Detect published services # Detect published services
svc_tags = [] svc_tags = []
if vrd_content: if vrd_content:
if re.search(r'<ws\s', vrd_content): # "+ext" — publishExtensionsByDefault: сервисы расширений тоже опубликованы
svc_tags.append('WS') m_ws = re.search(r'<ws\s[^>]*>', vrd_content)
if re.search(r'<httpServices\s', vrd_content): if m_ws:
svc_tags.append('HTTP') svc_tags.append('WS+ext' if re.search(
if re.search(r'enableStandardOdata\s*=\s*"true"', vrd_content): r'publishExtensionsByDefault\s*=\s*"true"', m_ws.group(0)) else 'WS')
m_hs = re.search(r'<httpServices\s[^>]*>', vrd_content)
if m_hs:
svc_tags.append('HTTP+ext' if re.search(
r'publishExtensionsByDefault\s*=\s*"true"', m_hs.group(0)) else 'HTTP')
# Актуальная форма — <standardOdata enable="true"/>; enableStandardOdata — до 8.3.9
if (re.search(r'<standardOdata\s[^>]*enable\s*=\s*"true"', vrd_content)
or re.search(r'enableStandardOdata\s*=\s*"true"', vrd_content)):
svc_tags.append('OData') svc_tags.append('OData')
svc_label = ' [' + ' '.join(svc_tags) + ']' if svc_tags else '' svc_label = ' [' + ' '.join(svc_tags) + ']' if svc_tags else ''
@@ -139,17 +155,33 @@ def main():
print('') print('')
print('=== Последние ошибки ===') print('=== Последние ошибки ===')
error_log = os.path.join(apache_path, 'logs', 'error.log') # Имя файла берём из httpd.conf: сборки Apache расходятся (error.log / error_log)
if os.path.exists(error_log): error_log = None
m_log = re.search(r'(?m)^\s*ErrorLog\s+"?([^"\r\n]+)"?', conf_content)
if m_log:
log_path = m_log.group(1).strip()
error_log = os.path.normpath(
log_path if os.path.isabs(log_path) else os.path.join(apache_path, log_path))
if not error_log or not os.path.exists(error_log):
error_log = next((p for p in (os.path.join(apache_path, 'logs', n)
for n in ('error_log', 'error.log'))
if os.path.exists(p)), None)
if error_log and os.path.exists(error_log):
print(f'Журнал: {error_log}')
try: try:
with open(error_log, 'r', encoding='utf-8-sig', errors='replace') as f: with open(error_log, 'r', encoding='utf-8-sig', errors='replace') as f:
all_lines = f.readlines() all_lines = f.readlines()
tail_lines = all_lines[-5:] if len(all_lines) >= 5 else all_lines # Только error и выше: warn забит штатным шумом winnt_accept, notice — строки старта.
if tail_lines: # AH02538 — след нашего же рестарта (родитель убит), пишется как crit при каждой публикации
for line in tail_lines: err_lines = [ln for ln in all_lines
if re.search(r'\[[a-z_]+:(error|crit|alert|emerg)\]', ln)
and 'AH02538' not in ln]
if err_lines:
for line in err_lines[-5:]:
print(f' {line.rstrip()}') print(f' {line.rstrip()}')
else: else:
print('(пусто)') print('(ошибок нет)')
except Exception: except Exception:
print('(ошибка чтения)') print('(ошибка чтения)')
else: else:
+4
View File
@@ -84,6 +84,10 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/web-publish.ps1" <
3. Если нужно протестировать сервис — помоги составить запрос 3. Если нужно протестировать сервис — помоги составить запрос
4. Если база не зарегистрирована — предложи `/db-list add` 4. Если база не зарегистрирована — предложи `/db-list add`
Сервисы расширений публикуются наравне с сервисами конфигурации. Если сервис расширения всё же
отдаёт 404 (HTTP) или 500 «Сервис не найден» (SOAP) — расширение не применено к базе данных:
`/db-update -Extension <имя>`.
## Примеры ## Примеры
```powershell ```powershell
@@ -1,4 +1,4 @@
# web-publish v1.4 — Publish 1C infobase via Apache # web-publish v1.5 — Publish 1C infobase via Apache
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
<# <#
.SYNOPSIS .SYNOPSIS
@@ -280,8 +280,8 @@ $vrdContent = @"
base="/$AppName" base="/$AppName"
ib="$ibString"> ib="$ibString">
<standardOdata enable="true"/> <standardOdata enable="true"/>
<ws pointEnableCommon="true"/> <ws pointEnableCommon="true" publishExtensionsByDefault="true"/>
<httpServices publishByDefault="true"/> <httpServices publishByDefault="true" publishExtensionsByDefault="true"/>
</point> </point>
"@ "@
@@ -1,5 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# web-publish v1.4 — Publish 1C infobase via Apache # web-publish v1.5 — Publish 1C infobase via Apache
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
""" """
@@ -299,8 +299,8 @@ def main():
base="/{app_name}" base="/{app_name}"
ib="{ib_string}"> ib="{ib_string}">
<standardOdata enable="true"/> <standardOdata enable="true"/>
<ws pointEnableCommon="true"/> <ws pointEnableCommon="true" publishExtensionsByDefault="true"/>
<httpServices publishByDefault="true"/> <httpServices publishByDefault="true" publishExtensionsByDefault="true"/>
</point>''' </point>'''
with open(vrd_path, 'wb') as f: with open(vrd_path, 'wb') as f:
@@ -1,4 +1,4 @@
# xdto-compile v1.2 — Build a 1C XDTO package from an XML Schema (XSD) # xdto-compile v1.8 — Build a 1C XDTO package from an XML Schema (XSD)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory=$true, ParameterSetName='File')] [Parameter(Mandatory=$true, ParameterSetName='File')]
@@ -834,6 +834,24 @@ Assert-EditAllowed $OutputDir
$script:formatVersion = Detect-FormatVersion $OutputDir $script:formatVersion = Detect-FormatVersion $OutputDir
# Объявления пространств имён — одной переменной: место эмиссии её только интерполирует.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
$script:xmlnsDecl = '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"'
# Версия формата как число для сравнений: "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
}
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if ((Get-FormatRank $script:formatVersion) -ge 221) {
$script:xmlnsDecl = $script:xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
}
$pkgRoot = Join-Path $OutputDir "XDTOPackages" $pkgRoot = Join-Path $OutputDir "XDTOPackages"
$pkgDir = Join-Path $pkgRoot $Name $pkgDir = Join-Path $pkgRoot $Name
$extDir = Join-Path $pkgDir "Ext" $extDir = Join-Path $pkgDir "Ext"
@@ -866,7 +884,7 @@ $uuid = [guid]::NewGuid().ToString()
$md = New-Object System.Text.StringBuilder $md = New-Object System.Text.StringBuilder
function M([string]$s) { [void]$md.Append($s); [void]$md.Append("`r`n") } function M([string]$s) { [void]$md.Append($s); [void]$md.Append("`r`n") }
M '<?xml version="1.0" encoding="UTF-8"?>' 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=`"$script:formatVersion`">") M ("<MetaDataObject $($script:xmlnsDecl) version=`"$script:formatVersion`">")
M "`t<XDTOPackage uuid=`"$uuid`">" M "`t<XDTOPackage uuid=`"$uuid`">"
M "`t`t<Properties>" M "`t`t<Properties>"
M "`t`t`t<Name>$(EscText $Name)</Name>" M "`t`t`t<Name>$(EscText $Name)</Name>"
@@ -948,11 +966,26 @@ if (Test-Path $configXmlPath) {
$cfgSettings = New-Object System.Xml.XmlWriterSettings $cfgSettings = New-Object System.Xml.XmlWriterSettings
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true) $cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
$cfgSettings.Indent = $false $cfgSettings.Indent = $false
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create) $cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
$configDoc.Save($writer) $configDoc.Save($writer)
$writer.Close() $writer.Flush(); $writer.Close()
$stream.Close()
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
$regResult = "added" $regResult = "added"
} }
} }
@@ -1,4 +1,4 @@
# xdto-compile v1.2 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) # xdto-compile v1.8 — Build a 1C XDTO package from an XML Schema (XSD) (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -119,6 +119,12 @@ def detect_format_version(d):
d = parent d = parent
return "2.17" return "2.17"
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
def assert_edit_allowed(target_path): def assert_edit_allowed(target_path):
d = os.path.abspath(target_path) d = os.path.abspath(target_path)
@@ -861,6 +867,36 @@ assert_edit_allowed(args.OutputDir)
format_version = detect_format_version(os.path.abspath(args.OutputDir)) format_version = detect_format_version(os.path.abspath(args.OutputDir))
# Объявления пространств имён — одной переменной: место эмиссии её только подставляет.
# Правки шапки (как xmlns:pal в формате 2.21) делаются здесь, в одном месте.
xmlns_decl = (
'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"'
)
# 2.21 (8.5) добавила в шапку пространство палитры — ради <Color> у значений перечисления.
# Вставляем НА МЕСТО (после lf, перед style): платформа держит объявления по алфавиту,
# дописать в конец нельзя.
if format_rank(format_version) >= 221:
xmlns_decl = xmlns_decl.replace(
' xmlns:style=',
' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style=')
pkg_root = os.path.join(args.OutputDir, "XDTOPackages") pkg_root = os.path.join(args.OutputDir, "XDTOPackages")
pkg_dir = os.path.join(pkg_root, name) pkg_dir = os.path.join(pkg_root, name)
ext_dir = os.path.join(pkg_dir, "Ext") ext_dir = os.path.join(pkg_dir, "Ext")
@@ -887,15 +923,7 @@ comment = args.Comment or meta_comment or ""
md_lines = [ md_lines = [
'<?xml version="1.0" encoding="UTF-8"?>', '<?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" ' f'<MetaDataObject {xmlns_decl} version="{format_version}">',
'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" '
f'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="{format_version}">',
f'\t<XDTOPackage uuid="{uuid.uuid4()}">', f'\t<XDTOPackage uuid="{uuid.uuid4()}">',
"\t\t<Properties>", "\t\t<Properties>",
f"\t\t\t<Name>{esc_text(name)}</Name>", f"\t\t\t<Name>{esc_text(name)}</Name>",
@@ -960,6 +988,15 @@ if os.path.exists(config_xml):
else: else:
new_elem.tail = child_objects.text new_elem.tail = child_objects.text
data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8") data = etree.tostring(cfg_doc, xml_declaration=True, encoding="UTF-8")
# lxml пишет декларацию в ОДИНАРНЫХ кавычках, платформа и PS-порт — в двойных.
data = data.replace(b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="UTF-8"?>')
# Парсер XML по спецификации схлопывает CRLF в LF, поэтому tostring отдаёт
# LF-документ. Возвращаем EOL исходного файла: правка существующего файла
# сохраняет его стиль (#44/#46/#47). Правило то же, что у _detect_xml_style
# и у $targetEol в PS-порту: есть CRLF → CRLF.
src_eol = b"\r\n" if b"\r\n" in raw else b"\n"
data = data.replace(b"\r\n", b"\n").replace(b"\n", src_eol)
if had_bom: if had_bom:
data = b"\xef\xbb\xbf" + data data = b"\xef\xbb\xbf" + data
with open(config_xml, "wb") as f: with open(config_xml, "wb") as f:
+38 -7
View File
@@ -1,4 +1,4 @@
# xdto-edit v1.0 — Point edits of a 1C XDTO package # xdto-edit v1.4 — Point edits of a 1C XDTO package
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param( param(
[Parameter(Mandatory=$true)] [Parameter(Mandatory=$true)]
@@ -221,10 +221,26 @@ function Edit-Metadata([string]$field, [string]$newValue) {
$settings = New-Object System.Xml.XmlWriterSettings $settings = New-Object System.Xml.XmlWriterSettings
$settings.Encoding = $encBom $settings.Encoding = $encBom
$settings.Indent = $false $settings.Indent = $false
$stream = New-Object System.IO.FileStream($mdFile, [System.IO.FileMode]::Create) $settings.NewLineHandling = [System.Xml.NewLineHandling]::None
$writer = [System.Xml.XmlWriter]::Create($stream, $settings) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$memStream = New-Object System.IO.MemoryStream
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
$doc.Save($writer) $doc.Save($writer)
$writer.Close(); $stream.Close() $writer.Flush(); $writer.Close()
$mdText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
$memStream.Close()
if ($mdText.Length -gt 0 -and $mdText[0] -eq [char]0xFEFF) { $mdText = $mdText.Substring(1) }
$mdText = $mdText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$mdText = [regex]::Replace($mdText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $mdFile) -and ([System.IO.File]::ReadAllText($mdFile) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$mdText = ($mdText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($mdFile, $mdText, $encBom)
} }
function Rename-Package([string]$newName) { function Rename-Package([string]$newName) {
@@ -252,9 +268,24 @@ function Rename-Package([string]$newName) {
if ($found) { if ($found) {
$s = New-Object System.Xml.XmlWriterSettings $s = New-Object System.Xml.XmlWriterSettings
$s.Encoding = $encBom; $s.Indent = $false $s.Encoding = $encBom; $s.Indent = $false
$st = New-Object System.IO.FileStream($configXml, [System.IO.FileMode]::Create) $s.NewLineHandling = [System.Xml.NewLineHandling]::None
$w = [System.Xml.XmlWriter]::Create($st, $s) # Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
$cfg.Save($w); $w.Close(); $st.Close() $mem = New-Object System.IO.MemoryStream
$w = [System.Xml.XmlWriter]::Create($mem, $s)
$cfg.Save($w); $w.Flush(); $w.Close()
$cfgText = [System.Text.Encoding]::UTF8.GetString($mem.ToArray())
$mem.Close()
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
$targetEol = if ((Test-Path -LiteralPath $configXml) -and ([System.IO.File]::ReadAllText($configXml) -notmatch "`r`n")) { "`n" } else { "`r`n" }
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
[System.IO.File]::WriteAllText($configXml, $cfgText, $encBom)
Write-Host " Configuration.xml: <XDTOPackage> переименован в $newName" Write-Host " Configuration.xml: <XDTOPackage> переименован в $newName"
} else { } else {
Write-Warning "В Configuration.xml не найдена запись <XDTOPackage>$pkgName</XDTOPackage> — зарегистрируйте пакет вручную" Write-Warning "В Configuration.xml не найдена запись <XDTOPackage>$pkgName</XDTOPackage> — зарегистрируйте пакет вручную"
+15 -1
View File
@@ -1,4 +1,4 @@
# xdto-edit v1.0 — Point edits of a 1C XDTO package (Python port) # xdto-edit v1.4 — Point edits of a 1C XDTO package (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse import argparse
import json import json
@@ -194,6 +194,20 @@ def invoke_sibling(script, argv, what):
def save_xml(doc, path): def save_xml(doc, path):
raw = etree.tostring(doc, xml_declaration=True, encoding="UTF-8") raw = etree.tostring(doc, xml_declaration=True, encoding="UTF-8")
# lxml пишет декларацию в ОДИНАРНЫХ кавычках, платформа и PS-порт — в двойных.
raw = raw.replace(b"<?xml version='1.0' encoding='UTF-8'?>",
b'<?xml version="1.0" encoding="UTF-8"?>')
# Парсер XML по спецификации схлопывает CRLF в LF, поэтому tostring отдаёт
# LF-документ. Возвращаем EOL файла-назначения: правка существующего файла
# сохраняет его стиль (#44/#46/#47), новый получает канон CRLF. Правило то же,
# что у _detect_xml_style и у $targetEol в PS-порту: есть CRLF → CRLF.
src_eol = b"\r\n"
try:
with open(path, "rb") as f:
src_eol = b"\r\n" if b"\r\n" in f.read() else b"\n"
except OSError:
pass
raw = raw.replace(b"\r\n", b"\n").replace(b"\n", src_eol)
with open(path, "wb") as f: with open(path, "wb") as f:
f.write(b"\xef\xbb\xbf" + raw) f.write(b"\xef\xbb\xbf" + raw)
+18 -2
View File
@@ -16,8 +16,8 @@
base="/appname" base="/appname"
ib="connection-string"> ib="connection-string">
<standardOdata enable="true"/> <standardOdata enable="true"/>
<ws pointEnableCommon="true"/> <ws pointEnableCommon="true" publishExtensionsByDefault="true"/>
<httpServices publishByDefault="true"/> <httpServices publishByDefault="true" publishExtensionsByDefault="true"/>
</point> </point>
``` ```
@@ -62,6 +62,22 @@ URL: `/{AppName}/ws/{WebServiceName}?wsdl`
Публикация HTTP-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации. Публикация HTTP-сервисов. `publishByDefault="true"` публикует все сервисы из конфигурации.
URL: `/{AppName}/hs/{RootUrl}/...` URL: `/{AppName}/hs/{RootUrl}/...`
#### Сервисы расширений — `publishExtensionsByDefault`
Атрибут `publishExtensionsByDefault="true"` на `<ws>` и `<httpServices>` публикует сервисы,
поставляемые **расширениями** конфигурации. По умолчанию платформа их не публикует: без атрибута
HTTP-сервис расширения отдаёт **404**, web-сервис — **500 «Сервис не найден»**. Атрибуты
`pointEnableCommon` / `publishByDefault` на сервисы расширений не распространяются.
Проверено E2E на 8.3.24: A/B на одном и том же дескрипторе — с атрибутами `/hs/...` и `/ws/...?wsdl`
отдают 200, без них 404/500. Перезапуск Apache для смены дескриптора не нужен, vrd читается на запрос.
> Расширение должно быть применено к базе данных (`/db-update -Extension <имя>`), иначе его сервисы
> недоступны независимо от дескриптора.
> **Побочный эффект.** Атрибут действует на все подключённые расширения: любое расширение,
> загруженное в ИБ, автоматически выставляет свои сервисы наружу.
### Расположение ### Расположение
`{ApachePath}/publish/{AppName}/default.vrd` `{ApachePath}/publish/{AppName}/default.vrd`
+22 -3
View File
@@ -252,10 +252,18 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|---|---| |---|---|
| `file` | Путь к файлу относительно `workDir` (обязателен) | | `file` | Путь к файлу относительно `workDir` (обязателен) |
| `bom` | `true`/`false` — наличие UTF-8 BOM | | `bom` | `true`/`false` — наличие UTF-8 BOM |
| `eol` | `"crlf"` / `"lf"` | | `eol` | `"crlf"` / `"lf"` — проверяются ОДИНОЧНЫЕ переводы строк, поэтому смешанный выход падает |
| `encoding` | Ожидаемое значение в XML-декларации, напр. `"UTF-8"` | | `encoding` | Ожидаемое значение в XML-декларации, напр. `"UTF-8"` |
| `finalNewline` | `true`/`false` — перевод строки в конце файла | | `finalNewline` | `true`/`false` — перевод строки в конце файла |
| `noCR13` | `true` — в выходе не должно быть литерала `&#13;` | | `noCR13` | `true` — в выходе не должно быть литерала `&#13;` |
| `selfClose` | `"tight"` — пустой элемент только как `<a/>`, без пробела перед `/>` |
| `noEmptyPairs` | `true` — пустого элемента в форме `<a></a>` быть не должно |
Канон выгрузки Конфигуратора (issue #57), измеренный на чистой выгрузке пустой ИБ на Windows
и macOS и на 8 выгрузках в `cfsrc/`: **CRLF, BOM, последний байт `>` (без перевода строки),
`<a/>` без пробела, ноль пустых пар, `encoding="UTF-8"`.** Для файла, который навык СОЗДАЁТ,
ожидается канон; для файла, который он ПРАВИТ, — стиль входного файла (контракт #44/#46/#47),
поэтому в кейсах `roundtrip-crlf-preserve` ожидания могут отличаться от канона.
`preserves` и эталон **дополняют** друг друга: первый следит за байтовым стилем файла, второй — за `preserves` и эталон **дополняют** друг друга: первый следит за байтовым стилем файла, второй — за
структурой содержимого. Наличие одного не отменяет необходимости другого. структурой содержимого. Наличие одного не отменяет необходимости другого.
@@ -293,8 +301,19 @@ ibcmd-проход автоматически `○ skipped`, если рядом
} }
``` ```
Типичный случай — навык ничего не пишет в рабочую директорию (info/validate): эталон зафиксировал бы Типичный случай — навык ничего не пишет в рабочую директорию (info/validate) **и фикстуру не
выход `preRun`, а не проверяемого навыка, и дублировал бы эталоны того навыка. собирает**: сверять нечего, проверяется stdout.
**Но если у такого кейса есть `preRun`, собирающий фикстуру, — эталон нужен.** Он фиксирует не
выход проверяемого навыка, а **вход теста**. Без него дрейф навыка-генератора меняет фикстуру
молча: ожидание вида `"stdoutContains": "Составной (6)"` начинает проверяться уже на другом
объекте — в лучшем случае кейс падает с необъяснимой причиной, в худшем сходится случайно и
перестаёт что-либо проверять. `meta-compile` такой дрейф даёт регулярно, задевая эталоны
десятков навыков, и ловят его именно эталоны. Поэтому info-навыки в `cases/*-info/` эталоны
имеют — это осознанно.
Правило: **есть `preRun` с генерацией фикстуры → эталон; нет `preRun` (или фикстура тривиальна)
`noSnapshot`.**
**Причина обязательна** — непустая строка; `true` не принимается и валит кейс. Смысл в том, что **Причина обязательна** — непустая строка; `true` не принимается и валит кейс. Смысл в том, что
отключение сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е: проверить отключение сверки должно стоить автору формулировки, а ревьюеру быть видно в diff'е: проверить
@@ -48,8 +48,8 @@
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
@@ -48,8 +48,8 @@
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles/> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress/> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
@@ -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="f1472007-9f1d-4330-ade4-5835d7ed83ea">
<InternalInfo>
<xr:ContainedObject>
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
<xr:ObjectId>d41d3bac-c6c1-4a8d-afcd-81fc29f496d0</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
<xr:ObjectId>c8e78ba2-51c5-45bb-a0ef-8c3c7ff76053</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
<xr:ObjectId>342f337b-45a8-422b-965d-11330d52b334</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
<xr:ObjectId>a3448b91-cbda-4e31-a0f2-e32bc1884e7c</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
<xr:ObjectId>41d207a0-776a-40af-bcd1-925d3acffb82</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
<xr:ObjectId>d73b5446-4f92-46db-a779-91d4e78c664a</xr:ObjectId>
</xr:ContainedObject>
<xr:ContainedObject>
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
<xr:ObjectId>c5472dbb-d2be-4f93-a745-564e39298ef2</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/>
<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="b0882825-037f-44dc-81db-667e780d4d78">
<uuid>cbab57f2-a0f3-4f0a-89ea-4cb19570ab75</uuid>
</panel>
</top>
<left>
<panel id="ca80bf27-af4d-48a0-8d8f-8bdfc29af463">
<uuid>b553047f-c9aa-4157-978d-448ecad24248</uuid>
</panel>
</left>
<panelDef id="b553047f-c9aa-4157-978d-448ecad24248"/>
<panelDef id="13322b22-3960-4d68-93a6-fe2dd7f28ca3"/>
<panelDef id="c933ac92-92cd-459d-81cc-e0c8a83ced99"/>
<panelDef id="cbab57f2-a0f3-4f0a-89ea-4cb19570ab75"/>
<panelDef id="b2735bd3-d822-4430-ba59-c9e869693b24"/>
</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="df8e1579-6f42-4e6a-9e2d-a655b750a7ed">
<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,19 @@
{
"name": "round-trip: modify-property на LF-исходнике сохраняет LF (порты расходились здесь: PS форсировал CRLF)",
"setup": "fixture:lf-config",
"input": [
{ "operation": "modify-property", "value": "Version=1.0.0.2" }
],
"expect": {
"preserves": {
"file": "Configuration.xml",
"bom": true,
"eol": "lf",
"encoding": "UTF-8",
"finalNewline": false,
"noCR13": true,
"selfClose": "tight",
"noEmptyPairs": true
}
}
}
@@ -39,17 +39,17 @@
<v8:content>Бот демо</v8:content> <v8:content>Бот демо</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
@@ -58,7 +58,7 @@
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode> <SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -39,8 +39,8 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
@@ -50,31 +50,31 @@
<DefaultRoles> <DefaultRoles>
<xr:Item xsi:type="xr:MDObjectRef">Role.ПолныеПрава</xr:Item> <xr:Item xsi:type="xr:MDObjectRef">Role.ПолныеПрава</xr:Item>
</DefaultRoles> </DefaultRoles>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -225,18 +225,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -244,7 +244,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -1,32 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" <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">
xmlns:app="http://v8.1c.ru/8.2/managed-application/core" <Role uuid="UUID-001">
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" <Properties>
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" <Name>ПолныеПрава</Name>
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" <Synonym>
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" <v8:item>
xmlns:style="http://v8.1c.ru/8.1/data/ui/style" <v8:lang>ru</v8:lang>
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" <v8:content>ПолныеПрава</v8:content>
xmlns:v8="http://v8.1c.ru/8.1/data/core" </v8:item>
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" </Synonym>
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" <Comment/>
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" </Properties>
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" </Role>
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" </MetaDataObject>
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">
<Role uuid="UUID-001">
<Properties>
<Name>ПолныеПрава</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ПолныеПрава</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" <Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.17">
xmlns:xs="http://www.w3.org/2001/XMLSchema" <setForNewObjects>false</setForNewObjects>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <setForAttributesByDefault>true</setForAttributesByDefault>
xsi:type="Rights" version="2.17"> <independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<setForNewObjects>false</setForNewObjects> </Rights>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
</Rights>
@@ -88,4 +88,4 @@
</Properties> </Properties>
<ChildObjects/> <ChildObjects/>
</Catalog> </Catalog>
</MetaDataObject> </MetaDataObject>
@@ -39,40 +39,40 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -223,18 +223,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -242,7 +242,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -79,4 +79,4 @@
</Properties> </Properties>
<ChildObjects/> <ChildObjects/>
</Document> </Document>
</MetaDataObject> </MetaDataObject>
@@ -39,40 +39,40 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor>ТестПоставщик</Vendor> <Vendor>ТестПоставщик</Vendor>
<Version>1.2.3.4</Version> <Version>1.2.3.4</Version>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -223,18 +223,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -242,7 +242,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -88,4 +88,4 @@
</Properties> </Properties>
<ChildObjects/> <ChildObjects/>
</Catalog> </Catalog>
</MetaDataObject> </MetaDataObject>
@@ -88,4 +88,4 @@
</Properties> </Properties>
<ChildObjects/> <ChildObjects/>
</Catalog> </Catalog>
</MetaDataObject> </MetaDataObject>
@@ -39,40 +39,40 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -223,18 +223,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -242,7 +242,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -39,40 +39,40 @@
<v8:content>КрлфКонф</v8:content> <v8:content>КрлфКонф</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version>1.0.0.2</Version> <Version>1.0.0.2</Version>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -223,18 +223,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -242,7 +242,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_27</CompatibilityMode> <CompatibilityMode>Version8_3_27</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -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/>
<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>
@@ -39,8 +39,8 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
@@ -51,31 +51,31 @@
<xr:Item xsi:type="xr:MDObjectRef">Role.ПолныеПрава</xr:Item> <xr:Item xsi:type="xr:MDObjectRef">Role.ПолныеПрава</xr:Item>
<xr:Item xsi:type="xr:MDObjectRef">Role.Администратор</xr:Item> <xr:Item xsi:type="xr:MDObjectRef">Role.Администратор</xr:Item>
</DefaultRoles> </DefaultRoles>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -226,18 +226,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -245,7 +245,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -1,32 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" <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">
xmlns:app="http://v8.1c.ru/8.2/managed-application/core" <Role uuid="UUID-001">
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" <Properties>
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" <Name>Администратор</Name>
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" <Synonym>
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" <v8:item>
xmlns:style="http://v8.1c.ru/8.1/data/ui/style" <v8:lang>ru</v8:lang>
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" <v8:content>Администратор</v8:content>
xmlns:v8="http://v8.1c.ru/8.1/data/core" </v8:item>
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" </Synonym>
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" <Comment/>
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" </Properties>
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" </Role>
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" </MetaDataObject>
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">
<Role uuid="UUID-001">
<Properties>
<Name>Администратор</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Администратор</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" <Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.17">
xmlns:xs="http://www.w3.org/2001/XMLSchema" <setForNewObjects>false</setForNewObjects>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <setForAttributesByDefault>true</setForAttributesByDefault>
xsi:type="Rights" version="2.17"> <independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<setForNewObjects>false</setForNewObjects> </Rights>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
</Rights>
@@ -1,32 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" <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">
xmlns:app="http://v8.1c.ru/8.2/managed-application/core" <Role uuid="UUID-001">
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" <Properties>
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" <Name>ПолныеПрава</Name>
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" <Synonym>
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" <v8:item>
xmlns:style="http://v8.1c.ru/8.1/data/ui/style" <v8:lang>ru</v8:lang>
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" <v8:content>ПолныеПрава</v8:content>
xmlns:v8="http://v8.1c.ru/8.1/data/core" </v8:item>
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" </Synonym>
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" <Comment/>
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" </Properties>
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" </Role>
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" </MetaDataObject>
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">
<Role uuid="UUID-001">
<Properties>
<Name>ПолныеПрава</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ПолныеПрава</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" <Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.17">
xmlns:xs="http://www.w3.org/2001/XMLSchema" <setForNewObjects>false</setForNewObjects>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <setForAttributesByDefault>true</setForAttributesByDefault>
xsi:type="Rights" version="2.17"> <independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<setForNewObjects>false</setForNewObjects> </Rights>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
</Rights>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?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="2.17">
<Catalog uuid="UUID-001"> <Catalog uuid="UUID-001">
<InternalInfo> <InternalInfo>
@@ -31,14 +31,14 @@
<v8:content>Контрагенты</v8:content> <v8:content>Контрагенты</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<Hierarchical>false</Hierarchical> <Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType> <HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount> <LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount> <LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop> <FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands> <UseStandardCommands>true</UseStandardCommands>
<Owners /> <Owners/>
<SubordinationUse>ToItems</SubordinationUse> <SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength> <CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength> <DescriptionLength>25</DescriptionLength>
@@ -48,7 +48,7 @@
<CheckUnique>false</CheckUnique> <CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering> <Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation> <DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics /> <Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate> <PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType> <EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice> <QuickChoice>false</QuickChoice>
@@ -61,25 +61,25 @@
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString> <FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString> <ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm>Catalog.Контрагенты.Form.ФормаОбъекта</DefaultObjectForm> <DefaultObjectForm>Catalog.Контрагенты.Form.ФормаОбъекта</DefaultObjectForm>
<DefaultFolderForm /> <DefaultFolderForm/>
<DefaultListForm>Catalog.Контрагенты.Form.ФормаСписка</DefaultListForm> <DefaultListForm>Catalog.Контрагенты.Form.ФормаСписка</DefaultListForm>
<DefaultChoiceForm /> <DefaultChoiceForm/>
<DefaultFolderChoiceForm /> <DefaultFolderChoiceForm/>
<AuxiliaryObjectForm /> <AuxiliaryObjectForm/>
<AuxiliaryFolderForm /> <AuxiliaryFolderForm/>
<AuxiliaryListForm /> <AuxiliaryListForm/>
<AuxiliaryChoiceForm /> <AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm /> <AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn /> <BasedOn/>
<DataLockFields /> <DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch> <FullTextSearch>Use</FullTextSearch>
<ObjectPresentation /> <ObjectPresentation/>
<ExtendedObjectPresentation /> <ExtendedObjectPresentation/>
<ListPresentation /> <ListPresentation/>
<ExtendedListPresentation /> <ExtendedListPresentation/>
<Explanation /> <Explanation/>
<CreateOnInput>Use</CreateOnInput> <CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput> <ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory> <DataHistory>DontUse</DataHistory>
@@ -91,4 +91,4 @@
<Form>ФормаОбъекта</Form> <Form>ФормаОбъекта</Form>
</ChildObjects> </ChildObjects>
</Catalog> </Catalog>
</MetaDataObject> </MetaDataObject>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?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="2.17">
<Catalog uuid="UUID-001"> <Catalog uuid="UUID-001">
<InternalInfo> <InternalInfo>
@@ -31,14 +31,14 @@
<v8:content>Товары</v8:content> <v8:content>Товары</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<Hierarchical>false</Hierarchical> <Hierarchical>false</Hierarchical>
<HierarchyType>HierarchyFoldersAndItems</HierarchyType> <HierarchyType>HierarchyFoldersAndItems</HierarchyType>
<LimitLevelCount>false</LimitLevelCount> <LimitLevelCount>false</LimitLevelCount>
<LevelCount>2</LevelCount> <LevelCount>2</LevelCount>
<FoldersOnTop>true</FoldersOnTop> <FoldersOnTop>true</FoldersOnTop>
<UseStandardCommands>true</UseStandardCommands> <UseStandardCommands>true</UseStandardCommands>
<Owners /> <Owners/>
<SubordinationUse>ToItems</SubordinationUse> <SubordinationUse>ToItems</SubordinationUse>
<CodeLength>9</CodeLength> <CodeLength>9</CodeLength>
<DescriptionLength>25</DescriptionLength> <DescriptionLength>25</DescriptionLength>
@@ -48,7 +48,7 @@
<CheckUnique>false</CheckUnique> <CheckUnique>false</CheckUnique>
<Autonumbering>true</Autonumbering> <Autonumbering>true</Autonumbering>
<DefaultPresentation>AsDescription</DefaultPresentation> <DefaultPresentation>AsDescription</DefaultPresentation>
<Characteristics /> <Characteristics/>
<PredefinedDataUpdate>Auto</PredefinedDataUpdate> <PredefinedDataUpdate>Auto</PredefinedDataUpdate>
<EditType>InDialog</EditType> <EditType>InDialog</EditType>
<QuickChoice>false</QuickChoice> <QuickChoice>false</QuickChoice>
@@ -61,25 +61,25 @@
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString> <FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString> <ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<DefaultObjectForm>Catalog.Товары.Form.ФормаЭлемента</DefaultObjectForm> <DefaultObjectForm>Catalog.Товары.Form.ФормаЭлемента</DefaultObjectForm>
<DefaultFolderForm /> <DefaultFolderForm/>
<DefaultListForm>Catalog.Товары.Form.ФормаСписка</DefaultListForm> <DefaultListForm>Catalog.Товары.Form.ФормаСписка</DefaultListForm>
<DefaultChoiceForm /> <DefaultChoiceForm/>
<DefaultFolderChoiceForm /> <DefaultFolderChoiceForm/>
<AuxiliaryObjectForm /> <AuxiliaryObjectForm/>
<AuxiliaryFolderForm /> <AuxiliaryFolderForm/>
<AuxiliaryListForm /> <AuxiliaryListForm/>
<AuxiliaryChoiceForm /> <AuxiliaryChoiceForm/>
<AuxiliaryFolderChoiceForm /> <AuxiliaryFolderChoiceForm/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<BasedOn /> <BasedOn/>
<DataLockFields /> <DataLockFields/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<FullTextSearch>Use</FullTextSearch> <FullTextSearch>Use</FullTextSearch>
<ObjectPresentation /> <ObjectPresentation/>
<ExtendedObjectPresentation /> <ExtendedObjectPresentation/>
<ListPresentation /> <ListPresentation/>
<ExtendedListPresentation /> <ExtendedListPresentation/>
<Explanation /> <Explanation/>
<CreateOnInput>Use</CreateOnInput> <CreateOnInput>Use</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput> <ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DataHistory>DontUse</DataHistory> <DataHistory>DontUse</DataHistory>
@@ -91,4 +91,4 @@
<Form>ФормаЭлемента</Form> <Form>ФормаЭлемента</Form>
</ChildObjects> </ChildObjects>
</Catalog> </Catalog>
</MetaDataObject> </MetaDataObject>
@@ -39,40 +39,40 @@
<v8:content>TestConfig</v8:content> <v8:content>TestConfig</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<NamePrefix /> <NamePrefix/>
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode> <ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
<DefaultRunMode>ManagedApplication</DefaultRunMode> <DefaultRunMode>ManagedApplication</DefaultRunMode>
<UsePurposes> <UsePurposes>
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value> <v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
</UsePurposes> </UsePurposes>
<ScriptVariant>Russian</ScriptVariant> <ScriptVariant>Russian</ScriptVariant>
<DefaultRoles /> <DefaultRoles/>
<Vendor></Vendor> <Vendor/>
<Version></Version> <Version/>
<UpdateCatalogAddress /> <UpdateCatalogAddress/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication> <UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication> <UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
<AdditionalFullTextSearchDictionaries /> <AdditionalFullTextSearchDictionaries/>
<CommonSettingsStorage /> <CommonSettingsStorage/>
<ReportsUserSettingsStorage /> <ReportsUserSettingsStorage/>
<ReportsVariantsStorage /> <ReportsVariantsStorage/>
<FormDataSettingsStorage /> <FormDataSettingsStorage/>
<DynamicListsUserSettingsStorage /> <DynamicListsUserSettingsStorage/>
<URLExternalDataStorage /> <URLExternalDataStorage/>
<Content /> <Content/>
<DefaultReportForm /> <DefaultReportForm/>
<DefaultReportVariantForm /> <DefaultReportVariantForm/>
<DefaultReportSettingsForm /> <DefaultReportSettingsForm/>
<DefaultReportAppearanceTemplate /> <DefaultReportAppearanceTemplate/>
<DefaultDynamicListSettingsForm /> <DefaultDynamicListSettingsForm/>
<DefaultSearchForm /> <DefaultSearchForm/>
<DefaultDataHistoryChangeHistoryForm /> <DefaultDataHistoryChangeHistoryForm/>
<DefaultDataHistoryVersionDataForm /> <DefaultDataHistoryVersionDataForm/>
<DefaultDataHistoryVersionDifferencesForm /> <DefaultDataHistoryVersionDifferencesForm/>
<DefaultCollaborationSystemUsersChoiceForm /> <DefaultCollaborationSystemUsersChoiceForm/>
<RequiredMobileApplicationPermissions /> <RequiredMobileApplicationPermissions/>
<UsedMobileApplicationFunctionalities> <UsedMobileApplicationFunctionalities>
<app:functionality> <app:functionality>
<app:functionality>Biometrics</app:functionality> <app:functionality>Biometrics</app:functionality>
@@ -223,18 +223,18 @@
<app:use>false</app:use> <app:use>false</app:use>
</app:functionality> </app:functionality>
</UsedMobileApplicationFunctionalities> </UsedMobileApplicationFunctionalities>
<StandaloneConfigurationRestrictionRoles /> <StandaloneConfigurationRestrictionRoles/>
<MobileApplicationURLs /> <MobileApplicationURLs/>
<AllowedIncomingShareRequestTypes /> <AllowedIncomingShareRequestTypes/>
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode> <MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
<DefaultInterface /> <DefaultInterface/>
<DefaultStyle /> <DefaultStyle/>
<DefaultLanguage>Language.Русский</DefaultLanguage> <DefaultLanguage>Language.Русский</DefaultLanguage>
<BriefInformation /> <BriefInformation/>
<DetailedInformation /> <DetailedInformation/>
<Copyright /> <Copyright/>
<VendorInformationAddress /> <VendorInformationAddress/>
<ConfigurationInformationAddress /> <ConfigurationInformationAddress/>
<DataLockControlMode>Managed</DataLockControlMode> <DataLockControlMode>Managed</DataLockControlMode>
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode> <ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
<ModalityUseMode>DontUse</ModalityUseMode> <ModalityUseMode>DontUse</ModalityUseMode>
@@ -242,7 +242,7 @@
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode> <InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode> <DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
<CompatibilityMode>Version8_3_24</CompatibilityMode> <CompatibilityMode>Version8_3_24</CompatibilityMode>
<DefaultConstantsForm /> <DefaultConstantsForm/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Language>Русский</Language> <Language>Русский</Language>
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <?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="2.17">
<DataProcessor uuid="UUID-001"> <DataProcessor uuid="UUID-001">
<InternalInfo> <InternalInfo>
@@ -19,16 +19,16 @@
<v8:content>Поиск</v8:content> <v8:content>Поиск</v8:content>
</v8:item> </v8:item>
</Synonym> </Synonym>
<Comment /> <Comment/>
<UseStandardCommands>true</UseStandardCommands> <UseStandardCommands>true</UseStandardCommands>
<DefaultForm>DataProcessor.Поиск.Form.ФормаПоиска</DefaultForm> <DefaultForm>DataProcessor.Поиск.Form.ФормаПоиска</DefaultForm>
<AuxiliaryForm /> <AuxiliaryForm/>
<IncludeHelpInContents>false</IncludeHelpInContents> <IncludeHelpInContents>false</IncludeHelpInContents>
<ExtendedPresentation /> <ExtendedPresentation/>
<Explanation /> <Explanation/>
</Properties> </Properties>
<ChildObjects> <ChildObjects>
<Form>ФормаПоиска</Form> <Form>ФормаПоиска</Form>
</ChildObjects> </ChildObjects>
</DataProcessor> </DataProcessor>
</MetaDataObject> </MetaDataObject>
@@ -1,32 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" <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">
xmlns:app="http://v8.1c.ru/8.2/managed-application/core" <Role uuid="UUID-001">
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" <Properties>
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" <Name>Оператор</Name>
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" <Synonym>
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" <v8:item>
xmlns:style="http://v8.1c.ru/8.1/data/ui/style" <v8:lang>ru</v8:lang>
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" <v8:content>Оператор</v8:content>
xmlns:v8="http://v8.1c.ru/8.1/data/core" </v8:item>
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" </Synonym>
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" <Comment/>
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" </Properties>
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" </Role>
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" </MetaDataObject>
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">
<Role uuid="UUID-001">
<Properties>
<Name>Оператор</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Оператор</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>
@@ -1,9 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Rights xmlns="http://v8.1c.ru/8.2/roles" <Rights xmlns="http://v8.1c.ru/8.2/roles" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Rights" version="2.17">
xmlns:xs="http://www.w3.org/2001/XMLSchema" <setForNewObjects>false</setForNewObjects>
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <setForAttributesByDefault>true</setForAttributesByDefault>
xsi:type="Rights" version="2.17"> <independentRightsOfChildObjects>false</independentRightsOfChildObjects>
<setForNewObjects>false</setForNewObjects> </Rights>
<setForAttributesByDefault>true</setForAttributesByDefault>
<independentRightsOfChildObjects>false</independentRightsOfChildObjects>
</Rights>
@@ -1,32 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" <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">
xmlns:app="http://v8.1c.ru/8.2/managed-application/core" <Role uuid="UUID-001">
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" <Properties>
xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" <Name>ПолныеПрава</Name>
xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" <Synonym>
xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" <v8:item>
xmlns:style="http://v8.1c.ru/8.1/data/ui/style" <v8:lang>ru</v8:lang>
xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" <v8:content>ПолныеПрава</v8:content>
xmlns:v8="http://v8.1c.ru/8.1/data/core" </v8:item>
xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" </Synonym>
xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" <Comment/>
xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" </Properties>
xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" </Role>
xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" </MetaDataObject>
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">
<Role uuid="UUID-001">
<Properties>
<Name>ПолныеПрава</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>ПолныеПрава</v8:content>
</v8:item>
</Synonym>
<Comment/>
</Properties>
</Role>
</MetaDataObject>

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