feat(skills): новая группа вида встаёт в канонический порядок, а не в конец блока

Навыки-создатели дописывали первый объект нового вида перед </ChildObjects>. Собранная
ими конфигурация выходила в неканоническом порядке видов, и первая же загрузка-выгрузка
давала диф: на стенде подали Language -> Catalog -> Document -> CommandGroup ->
CommonCommand, платформа вернула Language -> CommonCommand -> CommandGroup -> Catalog ->
Document. При этом cf-edit add-childObject и cfe-borrow канонический порядок уже держали —
очередная «одна работа, разные реализации».

Логика перенесена из cf-edit в семью Register-InChildObjects и в вариант nested-parent:
запись встаёт перед первой группой вида старше по CHILD_OBJECT_TYPES. Для этого список
канонического порядка добавлен в meta-compile, role-compile, xdto-compile и
subsystem-compile (оба порта) и заведён в check-type-maps — копий стало 9 вместо 5, зато
все под гардом.

У подсистем нашлось больше, чем планировалось: вставка шла в конец ВСЕГО блока, поэтому
подсистема покидала и собственную группу, если ниже были другие виды. Одно правило
закрывает оба случая. Во вложенном Subsystem.xml порядок видов неприменим — потомок там
всегда один, поведение не изменилось.

Сдвиг эталонов широкий, но проверяемый: 42 файла в 12 навыках, 53 добавленные строки
против 53 удалённых с совпадающим мультимножеством — чистая перестановка, все изменённые
строки вида <Тег>Имя</Тег>. Из 425 эталонов с ChildObjects неканоничными осталось 15: 11
фикстур сортировки (неканоничны по замыслу) и 4 статические фикстуры на сохранение EOL и
разбор форм, которые мы намеренно не переупорядочиваем.

Версии подняты в обоих портах и за эту правку, и за предыдущую (карты типов).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QoAJmoNbgWKobA7JGgN5S3
This commit is contained in:
Nick Shirokov
2026-08-29 21:53:52 +03:00
co-authored by Claude Opus 5
parent bdd2ed63f9
commit 1bc943e846
89 changed files with 1772 additions and 116 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
# cf-edit v1.24 — Edit 1C configuration root (Configuration.xml)
# cf-edit v1.25 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-edit v1.24 — Edit 1C configuration root (Configuration.xml)
# cf-edit v1.25 — Edit 1C configuration root (Configuration.xml)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
+1 -1
View File
@@ -1,4 +1,4 @@
# cf-info v1.7 — Compact summary of 1C configuration root
# cf-info v1.8 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-info v1.7 — Compact summary of 1C configuration root
# cf-info v1.8 — Compact summary of 1C configuration root
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,4 +1,4 @@
# cf-validate v1.8 — Validate 1C configuration root structure
# cf-validate v1.9 — Validate 1C configuration root structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cf-validate v1.8 — Validate 1C configuration XML structure
# cf-validate v1.9 — Validate 1C configuration XML structure
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates Configuration.xml: root structure, InternalInfo, properties, ChildObjects, languages."""
import sys, os, argparse, re
@@ -1,4 +1,4 @@
# cfe-borrow v1.34 — Borrow objects from configuration into extension (CFE)
# cfe-borrow v1.35 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-borrow v1.34 — Borrow objects from configuration into extension (CFE)
# cfe-borrow v1.35 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
+1 -1
View File
@@ -1,4 +1,4 @@
# cfe-diff v1.3 — Analyze and compare 1C configuration extension (CFE)
# cfe-diff v1.4 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
+1 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-diff v1.3 — Analyze and compare 1C configuration extension (CFE)
# cfe-diff v1.4 — Analyze and compare 1C configuration extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -1,4 +1,4 @@
# cfe-validate v1.14 — Validate 1C configuration extension structure (CFE)
# cfe-validate v1.15 — Validate 1C configuration extension structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-validate v1.14 — Validate 1C configuration extension XML structure (CFE)
# cfe-validate v1.15 — Validate 1C configuration extension XML structure (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
"""Validates extension Configuration.xml: root, InternalInfo, extension properties, ChildObjects, borrowed objects."""
import sys, os, argparse, re
@@ -1,4 +1,4 @@
# meta-compile v1.100 — Compile 1C metadata object from JSON
# meta-compile v1.101 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -5311,6 +5311,25 @@ function Compare-MetadataNames([string]$a, [string]$b) {
# Регистрация объекта в <ChildObjects> родительского XML. Общая реализация: эталон —
# meta-compile, копии — role-compile, xdto-compile. Реестр семьи: tests/skills/check-inline-drift.mjs.
# Возвращает исход: added | already | no-childobj | no-config.
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","Bot","PaletteColor","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
@@ -5372,10 +5391,28 @@ function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [st
$insertAt = $lastSame + $closeSame.Length
$newContent = $configContent.Substring(0, $insertAt) + "$eol`t`t`t$entry" + $configContent.Substring($insertAt)
} else {
# Объектов этого вида ещё нет: новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по $childObjectTypes. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
$ownIdx = $childObjectTypes.IndexOf($ChildTag)
$anchor = $null
if ($ownIdx -ge 0) {
$typeRx = [regex]"(?m)^([ \t]*)<(\w+)>[^<]*</\2>"
$tm = $typeRx.Match($configContent, $block.Index, $block.Length)
while ($tm.Success) {
$otherIdx = $childObjectTypes.IndexOf($tm.Groups[2].Value)
if ($otherIdx -gt $ownIdx) { $anchor = $tm; break }
$tm = $tm.NextMatch()
}
}
if ($anchor) {
$newContent = $configContent.Substring(0, $anchor.Index) + $anchor.Groups[1].Value + $entry + $eol + $configContent.Substring($anchor.Index)
} else {
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
}
}
[System.IO.File]::WriteAllText($ParentXmlPath, $newContent, $enc)
return "added"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.100 — Compile 1C metadata object from JSON
# meta-compile v1.101 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -5285,6 +5285,26 @@ def compare_metadata_names(a, b):
return 0
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
CHILD_OBJECT_TYPES = [
'Language', 'Subsystem', 'StyleItem', 'Style',
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
'FilterCriterion', 'CommonModule', 'CommonAttribute', 'ExchangePlan',
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
'FunctionalOptionsParameter', 'DefinedType', 'Bot', 'PaletteColor', 'CommonCommand', 'CommandGroup',
'Constant', 'CommonForm', 'Catalog', 'Document',
'DocumentNumerator', 'Sequence', 'DocumentJournal', 'Enum',
'Report', 'DataProcessor', 'InformationRegister', 'AccumulationRegister',
'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'AccountingRegister',
'ChartOfCalculationTypes', 'CalculationRegister',
'BusinessProcess', 'Task', 'IntegrationService',
]
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
"""Регистрация объекта в <ChildObjects> родительского XML.
@@ -5361,12 +5381,29 @@ def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name)
+ f'{eol}\t\t\t{entry}'
+ config_content[insert_at:])
else:
# No element of this type yet: new line before </ChildObjects>,
# reusing the block's existing closing indent for </ChildObjects>.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по CHILD_OBJECT_TYPES. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
anchor = None
if child_tag in CHILD_OBJECT_TYPES:
own_idx = CHILD_OBJECT_TYPES.index(child_tag)
type_rx = re.compile(r'(?m)^([ \t]*)<(\w+)>[^<]*</\2>')
for m in type_rx.finditer(config_content, block.start(), block.end()):
other = m.group(2)
if other in CHILD_OBJECT_TYPES and CHILD_OBJECT_TYPES.index(other) > own_idx:
anchor = m
break
if anchor is not None:
new_content = (config_content[:anchor.start()]
+ f'{anchor.group(1)}{entry}{eol}'
+ config_content[anchor.start():])
else:
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
write_utf8_bom(parent_xml_path, new_content)
return 'added'
@@ -1,4 +1,4 @@
# role-compile v1.33 — Compile 1C role from JSON
# role-compile v1.34 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -1262,6 +1262,25 @@ function Compare-MetadataNames([string]$a, [string]$b) {
# Регистрация объекта в <ChildObjects> родительского XML. Общая реализация: эталон —
# meta-compile, копии — role-compile, xdto-compile. Реестр семьи: tests/skills/check-inline-drift.mjs.
# Возвращает исход: added | already | no-childobj | no-config.
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","Bot","PaletteColor","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
@@ -1323,10 +1342,28 @@ function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [st
$insertAt = $lastSame + $closeSame.Length
$newContent = $configContent.Substring(0, $insertAt) + "$eol`t`t`t$entry" + $configContent.Substring($insertAt)
} else {
# Объектов этого вида ещё нет: новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по $childObjectTypes. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
$ownIdx = $childObjectTypes.IndexOf($ChildTag)
$anchor = $null
if ($ownIdx -ge 0) {
$typeRx = [regex]"(?m)^([ \t]*)<(\w+)>[^<]*</\2>"
$tm = $typeRx.Match($configContent, $block.Index, $block.Length)
while ($tm.Success) {
$otherIdx = $childObjectTypes.IndexOf($tm.Groups[2].Value)
if ($otherIdx -gt $ownIdx) { $anchor = $tm; break }
$tm = $tm.NextMatch()
}
}
if ($anchor) {
$newContent = $configContent.Substring(0, $anchor.Index) + $anchor.Groups[1].Value + $entry + $eol + $configContent.Substring($anchor.Index)
} else {
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
}
}
[System.IO.File]::WriteAllText($ParentXmlPath, $newContent, $enc)
return "added"
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# role-compile v1.33 — Compile 1C role from JSON
# role-compile v1.34 — Compile 1C role from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -1132,6 +1132,26 @@ def compare_metadata_names(a, b):
return 0
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
CHILD_OBJECT_TYPES = [
'Language', 'Subsystem', 'StyleItem', 'Style',
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
'FilterCriterion', 'CommonModule', 'CommonAttribute', 'ExchangePlan',
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
'FunctionalOptionsParameter', 'DefinedType', 'Bot', 'PaletteColor', 'CommonCommand', 'CommandGroup',
'Constant', 'CommonForm', 'Catalog', 'Document',
'DocumentNumerator', 'Sequence', 'DocumentJournal', 'Enum',
'Report', 'DataProcessor', 'InformationRegister', 'AccumulationRegister',
'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'AccountingRegister',
'ChartOfCalculationTypes', 'CalculationRegister',
'BusinessProcess', 'Task', 'IntegrationService',
]
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
"""Регистрация объекта в <ChildObjects> родительского XML.
@@ -1208,12 +1228,29 @@ def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name)
+ f'{eol}\t\t\t{entry}'
+ config_content[insert_at:])
else:
# No element of this type yet: new line before </ChildObjects>,
# reusing the block's existing closing indent for </ChildObjects>.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по CHILD_OBJECT_TYPES. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
anchor = None
if child_tag in CHILD_OBJECT_TYPES:
own_idx = CHILD_OBJECT_TYPES.index(child_tag)
type_rx = re.compile(r'(?m)^([ \t]*)<(\w+)>[^<]*</\2>')
for m in type_rx.finditer(config_content, block.start(), block.end()):
other = m.group(2)
if other in CHILD_OBJECT_TYPES and CHILD_OBJECT_TYPES.index(other) > own_idx:
anchor = m
break
if anchor is not None:
new_content = (config_content[:anchor.start()]
+ f'{anchor.group(1)}{entry}{eol}'
+ config_content[anchor.start():])
else:
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
write_utf8_bom(parent_xml_path, new_content)
return 'added'
@@ -1,4 +1,4 @@
# subsystem-compile v1.31 — Create 1C subsystem from JSON definition
# subsystem-compile v1.32 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -677,6 +677,25 @@ if ($children.Count -gt 0) {
# внутри подсистемы нечего — потомок там всегда один и тот же.
# Реестр семьи: tests/skills/check-inline-drift.mjs.
# Возвращает исход: added | already | no-childobj | no-config.
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","Bot","PaletteColor","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
@@ -708,11 +727,33 @@ function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [st
$replacement = "<ChildObjects>$eol`t`t`t$entry$eol`t`t</ChildObjects>"
$rawText = $rawText.Substring(0, $empty.Index) + $replacement + $rawText.Substring($empty.Index + $empty.Length)
} else {
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка по голому
# '</ChildObjects>' удваивала бы уже присутствующий отступ строки.
$cm = [regex]::Match($rawText, '([ ]*)</ChildObjects>')
if (-not $cm.Success) { return "no-childobj" }
$rawText = $rawText.Substring(0, $cm.Index) + $cm.Groups[1].Value + "`t" + $entry + $eol + $cm.Groups[1].Value + "</ChildObjects>" + $rawText.Substring($cm.Index + $cm.Length)
# В корне подсистема встаёт перед первой группой вида старше по $childObjectTypes:
# так она попадает и в канонический порядок видов, и в конец своей группы, если та уже
# есть. Дописать в конец блока нельзя вдвойне: платформа переставит новую группу при
# первой же выгрузке, а существующую подсистема покинула бы, уехав за виды ниже.
# Во вложенном Subsystem.xml порядок видов неприменим — потомок там всегда один.
$anchor = $null
if ($ParentTag -eq "Configuration") {
$ownIdx = $childObjectTypes.IndexOf($ChildTag)
if ($ownIdx -ge 0) {
$typeRx = [regex]"(?m)^([ \t]*)<(\w+)>[^<]*</\2>"
$tm = $typeRx.Match($rawText)
while ($tm.Success) {
$otherIdx = $childObjectTypes.IndexOf($tm.Groups[2].Value)
if ($otherIdx -gt $ownIdx) { $anchor = $tm; break }
$tm = $tm.NextMatch()
}
}
}
if ($anchor) {
$rawText = $rawText.Substring(0, $anchor.Index) + $anchor.Groups[1].Value + $entry + $eol + $rawText.Substring($anchor.Index)
} else {
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка по голому
# '</ChildObjects>' удваивала бы уже присутствующий отступ строки.
$cm = [regex]::Match($rawText, '([ ]*)</ChildObjects>')
if (-not $cm.Success) { return "no-childobj" }
$rawText = $rawText.Substring(0, $cm.Index) + $cm.Groups[1].Value + "`t" + $entry + $eol + $cm.Groups[1].Value + "</ChildObjects>" + $rawText.Substring($cm.Index + $cm.Length)
}
}
[System.IO.File]::WriteAllText($ParentXmlPath, $rawText, (New-Object System.Text.UTF8Encoding($true)))
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# subsystem-compile v1.31 — Create 1C subsystem from JSON definition
# subsystem-compile v1.32 — Create 1C subsystem from JSON definition
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -450,6 +450,26 @@ def write_child_subsystem_stub(child_path, child_name, format_version):
write_utf8_bom(child_path, '\r\n'.join(lines))
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
CHILD_OBJECT_TYPES = [
'Language', 'Subsystem', 'StyleItem', 'Style',
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
'FilterCriterion', 'CommonModule', 'CommonAttribute', 'ExchangePlan',
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
'FunctionalOptionsParameter', 'DefinedType', 'Bot', 'PaletteColor', 'CommonCommand', 'CommandGroup',
'Constant', 'CommonForm', 'Catalog', 'Document',
'DocumentNumerator', 'Sequence', 'DocumentJournal', 'Enum',
'Report', 'DataProcessor', 'InformationRegister', 'AccumulationRegister',
'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'AccountingRegister',
'ChartOfCalculationTypes', 'CalculationRegister',
'BusinessProcess', 'Task', 'IntegrationService',
]
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
"""Регистрация объекта в <ChildObjects> родительского XML.
@@ -494,14 +514,32 @@ def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name)
replacement = '<ChildObjects>' + eol + f'\t\t\t{entry}' + eol + '\t\t</ChildObjects>'
raw_text = raw_text[:empty.start()] + replacement + raw_text[empty.end():]
else:
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
cm = re.search(r'([ \t]*)</ChildObjects>', raw_text)
if cm is None:
return 'no-childobj'
raw_text = (raw_text[:cm.start()] + cm.group(1) + '\t' + entry + eol
+ cm.group(1) + '</ChildObjects>' + raw_text[cm.end():])
# В корне подсистема встаёт перед первой группой вида старше по CHILD_OBJECT_TYPES:
# так она попадает и в канонический порядок видов, и в конец своей группы, если та уже
# есть. Дописать в конец блока нельзя вдвойне: платформа переставит новую группу при
# первой же выгрузке, а существующую подсистема покинула бы, уехав за виды ниже.
# Во вложенном Subsystem.xml порядок видов неприменим — потомок там всегда один.
anchor = None
if parent_tag == 'Configuration' and child_tag in CHILD_OBJECT_TYPES:
own_idx = CHILD_OBJECT_TYPES.index(child_tag)
type_rx = re.compile(r'(?m)^([ \t]*)<(\w+)>[^<]*</\2>')
for m in type_rx.finditer(raw_text):
other = m.group(2)
if other in CHILD_OBJECT_TYPES and CHILD_OBJECT_TYPES.index(other) > own_idx:
anchor = m
break
if anchor is not None:
raw_text = (raw_text[:anchor.start()] + anchor.group(1) + entry + eol
+ raw_text[anchor.start():])
else:
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
cm = re.search(r'([ \t]*)</ChildObjects>', raw_text)
if cm is None:
return 'no-childobj'
raw_text = (raw_text[:cm.start()] + cm.group(1) + '\t' + entry + eol
+ cm.group(1) + '</ChildObjects>' + raw_text[cm.end():])
write_utf8_bom(parent_xml_path, raw_text)
return 'added'
@@ -1,4 +1,4 @@
# xdto-compile v1.13 — Build a 1C XDTO package from an XML Schema (XSD) (+support-guard: общая реализация вместо урезанной)
# xdto-compile v1.14 — Build a 1C XDTO package from an XML Schema (XSD) (+support-guard: общая реализация вместо урезанной)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -1070,6 +1070,25 @@ function Compare-MetadataNames([string]$a, [string]$b) {
# Регистрация объекта в <ChildObjects> родительского XML. Общая реализация: эталон —
# meta-compile, копии — role-compile, xdto-compile. Реестр семьи: tests/skills/check-inline-drift.mjs.
# Возвращает исход: added | already | no-childobj | no-config.
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
$childObjectTypes = @(
"Language","Subsystem","StyleItem","Style",
"CommonPicture","SessionParameter","Role","CommonTemplate",
"FilterCriterion","CommonModule","CommonAttribute","ExchangePlan",
"XDTOPackage","WebService","HTTPService","WSReference",
"EventSubscription","ScheduledJob","SettingsStorage","FunctionalOption",
"FunctionalOptionsParameter","DefinedType","Bot","PaletteColor","CommonCommand","CommandGroup",
"Constant","CommonForm","Catalog","Document",
"DocumentNumerator","Sequence","DocumentJournal","Enum",
"Report","DataProcessor","InformationRegister","AccumulationRegister",
"ChartOfCharacteristicTypes","ChartOfAccounts","AccountingRegister",
"ChartOfCalculationTypes","CalculationRegister",
"BusinessProcess","Task","IntegrationService"
)
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
@@ -1131,10 +1150,28 @@ function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [st
$insertAt = $lastSame + $closeSame.Length
$newContent = $configContent.Substring(0, $insertAt) + "$eol`t`t`t$entry" + $configContent.Substring($insertAt)
} else {
# Объектов этого вида ещё нет: новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по $childObjectTypes. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
$ownIdx = $childObjectTypes.IndexOf($ChildTag)
$anchor = $null
if ($ownIdx -ge 0) {
$typeRx = [regex]"(?m)^([ \t]*)<(\w+)>[^<]*</\2>"
$tm = $typeRx.Match($configContent, $block.Index, $block.Length)
while ($tm.Success) {
$otherIdx = $childObjectTypes.IndexOf($tm.Groups[2].Value)
if ($otherIdx -gt $ownIdx) { $anchor = $tm; break }
$tm = $tm.NextMatch()
}
}
if ($anchor) {
$newContent = $configContent.Substring(0, $anchor.Index) + $anchor.Groups[1].Value + $entry + $eol + $configContent.Substring($anchor.Index)
} else {
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется
$closeAt = $configContent.LastIndexOf("</ChildObjects>", $blockEnd - 1, $block.Length, [System.StringComparison]::Ordinal)
$newContent = $configContent.Substring(0, $closeAt) + "`t$entry$eol`t`t" + $configContent.Substring($closeAt)
}
}
[System.IO.File]::WriteAllText($ParentXmlPath, $newContent, $enc)
return "added"
@@ -1,4 +1,4 @@
# xdto-compile v1.13 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) (+support-guard: общая реализация вместо урезанной)
# xdto-compile v1.14 — Build a 1C XDTO package from an XML Schema (XSD) (Python port) (+support-guard: общая реализация вместо урезанной)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import json
@@ -1182,6 +1182,26 @@ def compare_metadata_names(a, b):
return 0
# Канонический порядок видов в <ChildObjects> — эталон в docs/1c-configuration-spec.md,
# таблица «Порядок типов в ChildObjects». Нужен, чтобы новая группа вида вставала на своё
# место: иначе платформа переставит её при первой же выгрузке и даст диф на ровном месте.
# Реестр карт: tests/skills/check-type-maps.mjs.
CHILD_OBJECT_TYPES = [
'Language', 'Subsystem', 'StyleItem', 'Style',
'CommonPicture', 'SessionParameter', 'Role', 'CommonTemplate',
'FilterCriterion', 'CommonModule', 'CommonAttribute', 'ExchangePlan',
'XDTOPackage', 'WebService', 'HTTPService', 'WSReference',
'EventSubscription', 'ScheduledJob', 'SettingsStorage', 'FunctionalOption',
'FunctionalOptionsParameter', 'DefinedType', 'Bot', 'PaletteColor', 'CommonCommand', 'CommandGroup',
'Constant', 'CommonForm', 'Catalog', 'Document',
'DocumentNumerator', 'Sequence', 'DocumentJournal', 'Enum',
'Report', 'DataProcessor', 'InformationRegister', 'AccumulationRegister',
'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'AccountingRegister',
'ChartOfCalculationTypes', 'CalculationRegister',
'BusinessProcess', 'Task', 'IntegrationService',
]
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
"""Регистрация объекта в <ChildObjects> родительского XML.
@@ -1258,12 +1278,29 @@ def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name)
+ f'{eol}\t\t\t{entry}'
+ config_content[insert_at:])
else:
# No element of this type yet: new line before </ChildObjects>,
# reusing the block's existing closing indent for </ChildObjects>.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
# Группы своего вида ещё нет: ставим её в канонический порядок видов — перед первой
# группой вида старше по CHILD_OBJECT_TYPES. Дописать в конец блока нельзя: платформа
# переставит группу при первой же выгрузке и даст диф на ровном месте.
anchor = None
if child_tag in CHILD_OBJECT_TYPES:
own_idx = CHILD_OBJECT_TYPES.index(child_tag)
type_rx = re.compile(r'(?m)^([ \t]*)<(\w+)>[^<]*</\2>')
for m in type_rx.finditer(config_content, block.start(), block.end()):
other = m.group(2)
if other in CHILD_OBJECT_TYPES and CHILD_OBJECT_TYPES.index(other) > own_idx:
anchor = m
break
if anchor is not None:
new_content = (config_content[:anchor.start()]
+ f'{anchor.group(1)}{entry}{eol}'
+ config_content[anchor.start():])
else:
# Видов старше в файле нет — новая строка перед </ChildObjects>,
# отступ закрывающего тега переиспользуется.
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
new_content = (config_content[:close_at]
+ f'\t{entry}{eol}\t\t'
+ config_content[close_at:])
write_utf8_bom(parent_xml_path, new_content)
return 'added'