mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-14 01:44:57 +03:00
Merge branch 'dev' into feature/web-test-runner
This commit is contained in:
@@ -32,7 +32,7 @@ powershell.exe -NoProfile -File .claude/skills/cf-edit/scripts/cf-edit.ps1 -Conf
|
||||
| Операция | Формат Value | Описание |
|
||||
|----------|-------------|----------|
|
||||
| `modify-property` | `Ключ=Значение` (batch `;;`) | Изменить свойство |
|
||||
| `add-childObject` | `Type.Name` (batch `;;`) | Добавить объект в ChildObjects |
|
||||
| `add-childObject` | `Type.Name` (batch `;;`) | Зарегистрировать уже существующий файл объекта в ChildObjects. Для создания нового объекта используй `/meta-compile`, `/role-compile`, `/subsystem-compile` — они регистрируют автоматически |
|
||||
| `remove-childObject` | `Type.Name` (batch `;;`) | Удалить объект из ChildObjects |
|
||||
| `add-defaultRole` | `Role.Name` или `Name` | Добавить роль по умолчанию |
|
||||
| `remove-defaultRole` | `Role.Name` или `Name` | Удалить роль по умолчанию |
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
### Enum
|
||||
| Свойство | Допустимые значения |
|
||||
|----------|---------------------|
|
||||
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_27`, `DontUse` |
|
||||
| `CompatibilityMode` | `Version8_3_20` ... `Version8_3_28`, `Version8_5_1`, `DontUse` |
|
||||
| `ConfigurationExtensionCompatibilityMode` | то же |
|
||||
| `DefaultRunMode` | `ManagedApplication`, `OrdinaryApplication`, `Auto` |
|
||||
| `ScriptVariant` | `Russian`, `English` |
|
||||
@@ -21,7 +21,7 @@
|
||||
| `ObjectAutonumerationMode` | `NotAutoFree`, `AutoFree` |
|
||||
| `ModalityUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||
| `SynchronousPlatformExtensionAndAddInCallUseMode` | `DontUse`, `Use`, `UseWithWarnings` |
|
||||
| `InterfaceCompatibilityMode` | `Taxi`, `TaxiEnableVersion8_2`, `Version8_2` |
|
||||
| `InterfaceCompatibilityMode` | `Version8_2`, `Version8_2EnableTaxi`, `Taxi`, `TaxiEnableVersion8_2`, `TaxiEnableVersion8_5`, `Version8_5EnableTaxi`, `Version8_5` |
|
||||
| `DatabaseTablespacesUseMode` | `DontUse`, `Use` |
|
||||
| `MainClientApplicationWindowMode` | `Normal`, `Fullscreen`, `Kiosk` |
|
||||
|
||||
@@ -35,6 +35,10 @@
|
||||
|
||||
Формат: `Type.Name` — XML-тип и имя объекта через точку.
|
||||
|
||||
**Важно про `add-childObject`**: операция регистрирует в `<ChildObjects>` Configuration.xml только объект, **файл которого уже существует на диске** (например `Catalogs/Товары.xml`). Если файла нет — скрипт падает с exit 1 и подсказкой. Для создания нового объекта используй профильный навык — `/meta-compile` (Catalog, Document, Enum, Report, регистры и т.д.), `/role-compile` (Role), `/subsystem-compile` (Subsystem). Они создают файл И регистрируют его в Configuration.xml за один вызов.
|
||||
|
||||
Когда `add-childObject` всё-таки нужен: откатили Configuration.xml (или перезаписали из выгрузки БД), а файлы объектов остались — нужно восстановить ссылки в `<ChildObjects>`.
|
||||
|
||||
При добавлении объект вставляется в каноническую позицию:
|
||||
1. Находит последний элемент того же типа → вставляет после
|
||||
2. Если тип отсутствует → находит последний элемент предшествующего типа → вставляет после
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cf-edit v1.0 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.1 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ConfigPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$ConfigPath,
|
||||
[string]$DefinitionFile,
|
||||
[ValidateSet("modify-property","add-childObject","remove-childObject","add-defaultRole","remove-defaultRole","set-defaultRoles")]
|
||||
[string]$Operation,
|
||||
@@ -27,6 +27,7 @@ if (Test-Path $ConfigPath -PathType Container) {
|
||||
}
|
||||
if (-not (Test-Path $ConfigPath)) { Write-Error "File not found: $ConfigPath"; exit 1 }
|
||||
$resolvedPath = (Resolve-Path $ConfigPath).Path
|
||||
$script:configDir = [System.IO.Path]::GetDirectoryName($resolvedPath)
|
||||
|
||||
# --- Load XML with PreserveWhitespace ---
|
||||
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
||||
@@ -87,6 +88,22 @@ $script:typeOrder = @(
|
||||
"BusinessProcess","Task","IntegrationService"
|
||||
)
|
||||
|
||||
# --- Type → on-disk directory name (plural) ---
|
||||
$script:typeToDir = @{
|
||||
"Language"="Languages"; "Subsystem"="Subsystems"; "StyleItem"="StyleItems"; "Style"="Styles"
|
||||
"CommonPicture"="CommonPictures"; "SessionParameter"="SessionParameters"; "Role"="Roles"; "CommonTemplate"="CommonTemplates"
|
||||
"FilterCriterion"="FilterCriteria"; "CommonModule"="CommonModules"; "CommonAttribute"="CommonAttributes"; "ExchangePlan"="ExchangePlans"
|
||||
"XDTOPackage"="XDTOPackages"; "WebService"="WebServices"; "HTTPService"="HTTPServices"; "WSReference"="WSReferences"
|
||||
"EventSubscription"="EventSubscriptions"; "ScheduledJob"="ScheduledJobs"; "SettingsStorage"="SettingsStorages"; "FunctionalOption"="FunctionalOptions"
|
||||
"FunctionalOptionsParameter"="FunctionalOptionsParameters"; "DefinedType"="DefinedTypes"; "CommonCommand"="CommonCommands"; "CommandGroup"="CommandGroups"
|
||||
"Constant"="Constants"; "CommonForm"="CommonForms"; "Catalog"="Catalogs"; "Document"="Documents"
|
||||
"DocumentNumerator"="DocumentNumerators"; "Sequence"="Sequences"; "DocumentJournal"="DocumentJournals"; "Enum"="Enums"
|
||||
"Report"="Reports"; "DataProcessor"="DataProcessors"; "InformationRegister"="InformationRegisters"; "AccumulationRegister"="AccumulationRegisters"
|
||||
"ChartOfCharacteristicTypes"="ChartsOfCharacteristicTypes"; "ChartOfAccounts"="ChartsOfAccounts"; "AccountingRegister"="AccountingRegisters"
|
||||
"ChartOfCalculationTypes"="ChartsOfCalculationTypes"; "CalculationRegister"="CalculationRegisters"
|
||||
"BusinessProcess"="BusinessProcesses"; "Task"="Tasks"; "IntegrationService"="IntegrationServices"
|
||||
}
|
||||
|
||||
# --- XML manipulation helpers (from subsystem-edit pattern) ---
|
||||
function Get-ChildIndent($container) {
|
||||
foreach ($child in $container.ChildNodes) {
|
||||
@@ -247,6 +264,29 @@ function Do-AddChildObject([string]$batchVal) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check that the referenced object actually exists on disk.
|
||||
# cf-edit add-childObject is a low-level operation for rare scenarios
|
||||
# (e.g. restoring a rolled-back Configuration.xml when object files are intact).
|
||||
# For creating NEW objects, meta-compile/role-compile/subsystem-compile already
|
||||
# auto-register in Configuration.xml — calling cf-edit add-childObject there is
|
||||
# unnecessary and error-prone.
|
||||
$typeDir = $script:typeToDir[$typeName]
|
||||
$objFile = Join-Path (Join-Path $script:configDir $typeDir) "$objNameVal.xml"
|
||||
if (-not (Test-Path $objFile)) {
|
||||
$hintSkill = switch ($typeName) {
|
||||
"Subsystem" { "subsystem-compile" }
|
||||
"Role" { "role-compile" }
|
||||
default { "meta-compile" }
|
||||
}
|
||||
Write-Error @"
|
||||
Object file not found: $typeDir/$objNameVal.xml
|
||||
cf-edit add-childObject only references objects that already exist on disk.
|
||||
To create a new $typeName, use $hintSkill (auto-registers in Configuration.xml):
|
||||
/$hintSkill with {"type":"$typeName","name":"$objNameVal"}
|
||||
"@
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Dedup check
|
||||
$existing = $false
|
||||
foreach ($child in $script:childObjsEl.ChildNodes) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-edit v1.0 — Edit 1C configuration root (Configuration.xml)
|
||||
# cf-edit v1.1 — Edit 1C configuration root (Configuration.xml)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -32,6 +32,22 @@ TYPE_ORDER = [
|
||||
"BusinessProcess", "Task", "IntegrationService",
|
||||
]
|
||||
|
||||
# Type → on-disk directory name (plural)
|
||||
TYPE_TO_DIR = {
|
||||
"Language": "Languages", "Subsystem": "Subsystems", "StyleItem": "StyleItems", "Style": "Styles",
|
||||
"CommonPicture": "CommonPictures", "SessionParameter": "SessionParameters", "Role": "Roles", "CommonTemplate": "CommonTemplates",
|
||||
"FilterCriterion": "FilterCriteria", "CommonModule": "CommonModules", "CommonAttribute": "CommonAttributes", "ExchangePlan": "ExchangePlans",
|
||||
"XDTOPackage": "XDTOPackages", "WebService": "WebServices", "HTTPService": "HTTPServices", "WSReference": "WSReferences",
|
||||
"EventSubscription": "EventSubscriptions", "ScheduledJob": "ScheduledJobs", "SettingsStorage": "SettingsStorages", "FunctionalOption": "FunctionalOptions",
|
||||
"FunctionalOptionsParameter": "FunctionalOptionsParameters", "DefinedType": "DefinedTypes", "CommonCommand": "CommonCommands", "CommandGroup": "CommandGroups",
|
||||
"Constant": "Constants", "CommonForm": "CommonForms", "Catalog": "Catalogs", "Document": "Documents",
|
||||
"DocumentNumerator": "DocumentNumerators", "Sequence": "Sequences", "DocumentJournal": "DocumentJournals", "Enum": "Enums",
|
||||
"Report": "Reports", "DataProcessor": "DataProcessors", "InformationRegister": "InformationRegisters", "AccumulationRegister": "AccumulationRegisters",
|
||||
"ChartOfCharacteristicTypes": "ChartsOfCharacteristicTypes", "ChartOfAccounts": "ChartsOfAccounts", "AccountingRegister": "AccountingRegisters",
|
||||
"ChartOfCalculationTypes": "ChartsOfCalculationTypes", "CalculationRegister": "CalculationRegisters",
|
||||
"BusinessProcess": "BusinessProcesses", "Task": "Tasks", "IntegrationService": "IntegrationServices",
|
||||
}
|
||||
|
||||
ML_PROPS = ["Synonym", "BriefInformation", "DetailedInformation", "Copyright", "VendorInformationAddress", "ConfigurationInformationAddress"]
|
||||
SCALAR_PROPS = ["Name", "Version", "Vendor", "Comment", "NamePrefix", "UpdateCatalogAddress"]
|
||||
REF_PROPS = ["DefaultLanguage"]
|
||||
@@ -143,7 +159,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Edit 1C configuration root (Configuration.xml)", allow_abbrev=False)
|
||||
parser.add_argument("-ConfigPath", required=True)
|
||||
parser.add_argument("-ConfigPath", "-Path", required=True)
|
||||
parser.add_argument("-DefinitionFile", default=None)
|
||||
parser.add_argument("-Operation", default=None, choices=["modify-property", "add-childObject", "remove-childObject", "add-defaultRole", "remove-defaultRole", "set-defaultRoles"])
|
||||
parser.add_argument("-Value", default=None)
|
||||
@@ -171,6 +187,7 @@ def main():
|
||||
print(f"File not found: {config_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
resolved_path = os.path.abspath(config_path)
|
||||
config_dir = os.path.dirname(resolved_path)
|
||||
|
||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(resolved_path, xml_parser)
|
||||
@@ -285,6 +302,25 @@ def main():
|
||||
sys.exit(1)
|
||||
type_idx = TYPE_ORDER.index(type_name)
|
||||
|
||||
# Check that the referenced object actually exists on disk.
|
||||
# cf-edit add-childObject is a low-level operation for rare scenarios
|
||||
# (e.g. restoring a rolled-back Configuration.xml when object files are intact).
|
||||
# For creating NEW objects, meta-compile/role-compile/subsystem-compile already
|
||||
# auto-register in Configuration.xml — calling cf-edit add-childObject there is
|
||||
# unnecessary and error-prone.
|
||||
type_dir = TYPE_TO_DIR.get(type_name)
|
||||
obj_file = os.path.join(config_dir, type_dir, f"{obj_name_val}.xml")
|
||||
if not os.path.exists(obj_file):
|
||||
hint_skill = {"Subsystem": "subsystem-compile", "Role": "role-compile"}.get(type_name, "meta-compile")
|
||||
print(
|
||||
f"Object file not found: {type_dir}/{obj_name_val}.xml\n"
|
||||
f"cf-edit add-childObject only references objects that already exist on disk.\n"
|
||||
f"To create a new {type_name}, use {hint_skill} (auto-registers in Configuration.xml):\n"
|
||||
f' /{hint_skill} with {{"type":"{type_name}","name":"{obj_name_val}"}}',
|
||||
file=sys.stderr
|
||||
)
|
||||
sys.exit(1)
|
||||
|
||||
# Dedup
|
||||
exists = False
|
||||
for child in child_objs_el:
|
||||
@@ -502,7 +538,7 @@ def main():
|
||||
if os.path.isfile(validate_script):
|
||||
print()
|
||||
print("--- Running cf-validate ---")
|
||||
subprocess.run([sys.executable, validate_script, "-ConfigPath", resolved_path])
|
||||
subprocess.run([sys.executable, validate_script, "-ConfigPath", "-Path", resolved_path])
|
||||
|
||||
# --- Summary ---
|
||||
print()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# cf-info v1.0 — Compact summary of 1C configuration root
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ConfigPath,
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ConfigPath,
|
||||
[ValidateSet("overview","brief","full")]
|
||||
[string]$Mode = "overview",
|
||||
[int]$Limit = 150,
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
# --- Argument parsing ---
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C configuration structure", allow_abbrev=False)
|
||||
parser.add_argument("-ConfigPath", required=True, help="Path to Configuration.xml or directory")
|
||||
parser.add_argument("-ConfigPath", "-Path", required=True, help="Path to Configuration.xml or directory")
|
||||
parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview", help="Output mode")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# cf-validate v1.1 — Validate 1C configuration root structure
|
||||
# cf-validate v1.2 — Validate 1C configuration root structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ConfigPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -145,17 +146,17 @@ $childTypeDirMap = @{
|
||||
|
||||
# Valid enum values for Configuration properties
|
||||
$validEnumValues = @{
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
|
||||
"ScriptVariant" = @("Russian","English")
|
||||
"DataLockControlMode" = @("Automatic","Managed","AutomaticAndManaged")
|
||||
"ObjectAutonumerationMode" = @("NotAutoFree","AutoFree")
|
||||
"ModalityUseMode" = @("DontUse","Use","UseWithWarnings")
|
||||
"SynchronousPlatformExtensionAndAddInCallUseMode" = @("DontUse","Use","UseWithWarnings")
|
||||
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
|
||||
"InterfaceCompatibilityMode" = @("Version8_2","Version8_2EnableTaxi","Taxi","TaxiEnableVersion8_2","TaxiEnableVersion8_5","Version8_5EnableTaxi","Version8_5")
|
||||
"DatabaseTablespacesUseMode" = @("DontUse","Use")
|
||||
"MainClientApplicationWindowMode" = @("Normal","Fullscreen","Kiosk")
|
||||
"CompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"CompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
}
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
@@ -203,8 +204,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-validate v1.1 — Validate 1C configuration XML structure
|
||||
# cf-validate v1.2 — 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
|
||||
@@ -82,7 +82,7 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
'DefaultRunMode': ['ManagedApplication', 'OrdinaryApplication', 'Auto'],
|
||||
'ScriptVariant': ['Russian', 'English'],
|
||||
@@ -90,7 +90,10 @@ VALID_ENUM_VALUES = {
|
||||
'ObjectAutonumerationMode': ['NotAutoFree', 'AutoFree'],
|
||||
'ModalityUseMode': ['DontUse', 'Use', 'UseWithWarnings'],
|
||||
'SynchronousPlatformExtensionAndAddInCallUseMode': ['DontUse', 'Use', 'UseWithWarnings'],
|
||||
'InterfaceCompatibilityMode': ['Taxi', 'TaxiEnableVersion8_2', 'Version8_2'],
|
||||
'InterfaceCompatibilityMode': [
|
||||
'Version8_2', 'Version8_2EnableTaxi', 'Taxi', 'TaxiEnableVersion8_2',
|
||||
'TaxiEnableVersion8_5', 'Version8_5EnableTaxi', 'Version8_5',
|
||||
],
|
||||
'DatabaseTablespacesUseMode': ['DontUse', 'Use'],
|
||||
'MainClientApplicationWindowMode': ['Normal', 'Fullscreen', 'Kiosk'],
|
||||
'CompatibilityMode': [
|
||||
@@ -100,7 +103,7 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
}
|
||||
|
||||
@@ -162,7 +165,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C configuration XML structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-ConfigPath', dest='ConfigPath', required=True)
|
||||
parser.add_argument('-ConfigPath', '-Path', dest='ConfigPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
@@ -228,8 +231,8 @@ def main():
|
||||
version = root.get('version', '')
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.20'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ('2.17', '2.20', '2.21'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
@@ -316,6 +316,25 @@ function Expand-SelfClosingElement($container, $parentIndent) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- 7b. Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $extDir
|
||||
|
||||
# --- 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"'
|
||||
|
||||
@@ -466,7 +485,7 @@ function Borrow-Form {
|
||||
$newFormUuid = [guid]::NewGuid().ToString()
|
||||
$formMetaSb = New-Object System.Text.StringBuilder
|
||||
$formMetaSb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||
$formMetaSb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t<Form uuid=`"${newFormUuid}`">") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<InternalInfo/>") | Out-Null
|
||||
$formMetaSb.AppendLine("`t`t<Properties>") | Out-Null
|
||||
@@ -498,7 +517,7 @@ function Borrow-Form {
|
||||
$srcFormEl = $srcFormDoc.DocumentElement
|
||||
|
||||
$formVersion = $srcFormEl.GetAttribute("version")
|
||||
if (-not $formVersion) { $formVersion = "2.17" }
|
||||
if (-not $formVersion) { $formVersion = $script:formatVersion }
|
||||
|
||||
# Find direct children: form properties, AutoCommandBar, ChildItems
|
||||
$srcAutoCmd = $null
|
||||
@@ -1529,7 +1548,7 @@ function Build-BorrowedObjectXml {
|
||||
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("<?xml version=`"1.0`" encoding=`"UTF-8`"?>") | Out-Null
|
||||
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">") | Out-Null
|
||||
$sb.AppendLine("<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">") | Out-Null
|
||||
$sb.AppendLine("`t<${typeName} uuid=`"${newUuid}`">") | Out-Null
|
||||
|
||||
# InternalInfo
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.2 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.3 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -254,6 +254,22 @@ XMLNS_DECL = (
|
||||
)
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 get_child_indent(container):
|
||||
if container.text and "\n" in container.text:
|
||||
after_nl = container.text.rsplit("\n", 1)[-1]
|
||||
@@ -365,6 +381,8 @@ def main():
|
||||
cfg_resolved = os.path.abspath(cfg_path)
|
||||
cfg_dir = os.path.dirname(cfg_resolved)
|
||||
|
||||
format_version = detect_format_version(ext_dir)
|
||||
|
||||
# --- 2. Load extension Configuration.xml ---
|
||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(ext_resolved, xml_parser)
|
||||
@@ -501,7 +519,7 @@ def main():
|
||||
|
||||
lines = []
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
lines.append(f'<MetaDataObject {XMLNS_DECL} version="2.17">')
|
||||
lines.append(f'<MetaDataObject {XMLNS_DECL} version="{format_version}">')
|
||||
lines.append(f'\t<{type_name} uuid="{new_uuid_val}">')
|
||||
lines.append(internal_info_xml)
|
||||
lines.append("\t\t<Properties>")
|
||||
@@ -1086,7 +1104,7 @@ def main():
|
||||
new_form_uuid = new_guid()
|
||||
form_meta_lines = [
|
||||
'<?xml version="1.0" encoding="UTF-8"?>',
|
||||
f'<MetaDataObject {XMLNS_DECL} version="2.17">',
|
||||
f'<MetaDataObject {XMLNS_DECL} version="{format_version}">',
|
||||
f'\t<Form uuid="{new_form_uuid}">',
|
||||
'\t\t<InternalInfo/>',
|
||||
'\t\t<Properties>',
|
||||
@@ -1113,7 +1131,7 @@ def main():
|
||||
src_form_tree = etree.parse(src_form_xml_path, src_form_parser)
|
||||
src_form_el = src_form_tree.getroot()
|
||||
|
||||
form_version = src_form_el.get("version", "2.17")
|
||||
form_version = src_form_el.get("version", format_version)
|
||||
|
||||
src_auto_cmd = None
|
||||
form_props = []
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# cfe-validate v1.3 — Validate 1C configuration extension structure (CFE)
|
||||
# cfe-validate v1.4 — Validate 1C configuration extension structure (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ExtensionPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -145,10 +146,10 @@ $childTypeDirMap = @{
|
||||
|
||||
# Valid enum values for extension properties
|
||||
$validEnumValues = @{
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28")
|
||||
"ConfigurationExtensionCompatibilityMode" = @("DontUse","Version8_1","Version8_2_13","Version8_2_16","Version8_3_1","Version8_3_2","Version8_3_3","Version8_3_4","Version8_3_5","Version8_3_6","Version8_3_7","Version8_3_8","Version8_3_9","Version8_3_10","Version8_3_11","Version8_3_12","Version8_3_13","Version8_3_14","Version8_3_15","Version8_3_16","Version8_3_17","Version8_3_18","Version8_3_19","Version8_3_20","Version8_3_21","Version8_3_22","Version8_3_23","Version8_3_24","Version8_3_25","Version8_3_26","Version8_3_27","Version8_3_28","Version8_5_1")
|
||||
"DefaultRunMode" = @("ManagedApplication","OrdinaryApplication","Auto")
|
||||
"ScriptVariant" = @("Russian","English")
|
||||
"InterfaceCompatibilityMode" = @("Taxi","TaxiEnableVersion8_2","Version8_2")
|
||||
"InterfaceCompatibilityMode" = @("Version8_2","Version8_2EnableTaxi","Taxi","TaxiEnableVersion8_2","TaxiEnableVersion8_5","Version8_5EnableTaxi","Version8_5")
|
||||
}
|
||||
|
||||
# --- 1. Parse XML ---
|
||||
@@ -196,8 +197,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-validate v1.3 — Validate 1C configuration extension XML structure (CFE)
|
||||
# cfe-validate v1.4 — 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
|
||||
@@ -82,11 +82,14 @@ VALID_ENUM_VALUES = {
|
||||
'Version8_3_11', 'Version8_3_12', 'Version8_3_13', 'Version8_3_14', 'Version8_3_15',
|
||||
'Version8_3_16', 'Version8_3_17', 'Version8_3_18', 'Version8_3_19', 'Version8_3_20',
|
||||
'Version8_3_21', 'Version8_3_22', 'Version8_3_23', 'Version8_3_24', 'Version8_3_25',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28',
|
||||
'Version8_3_26', 'Version8_3_27', 'Version8_3_28', 'Version8_5_1',
|
||||
],
|
||||
'DefaultRunMode': ['ManagedApplication', 'OrdinaryApplication', 'Auto'],
|
||||
'ScriptVariant': ['Russian', 'English'],
|
||||
'InterfaceCompatibilityMode': ['Taxi', 'TaxiEnableVersion8_2', 'Version8_2'],
|
||||
'InterfaceCompatibilityMode': [
|
||||
'Version8_2', 'Version8_2EnableTaxi', 'Taxi', 'TaxiEnableVersion8_2',
|
||||
'TaxiEnableVersion8_5', 'Version8_5EnableTaxi', 'Version8_5',
|
||||
],
|
||||
}
|
||||
|
||||
EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||
@@ -147,7 +150,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C configuration extension XML structure (CFE)', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-ExtensionPath', dest='ExtensionPath', required=True)
|
||||
parser.add_argument('-ExtensionPath', '-Path', dest='ExtensionPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
@@ -213,8 +216,8 @@ def main():
|
||||
version = root.get('version', '')
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.20'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ('2.17', '2.20', '2.21'):
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-create
|
||||
description: Создание информационной базы 1С. Используй когда пользователь просит создать базу, новую ИБ, пустую базу
|
||||
description: Создание информационной базы 1С. Используй когда нужно создать базу, новую ИБ, пустую базу
|
||||
argument-hint: <path|name>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-dump-cf
|
||||
description: Выгрузка конфигурации 1С в CF-файл. Используй когда пользователь просит выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
||||
description: Выгрузка конфигурации 1С в CF-файл. Используй когда нужно выгрузить конфигурацию в CF, сохранить конфигурацию, сделать бэкап CF
|
||||
argument-hint: "[database] [output.cf]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-dump-xml
|
||||
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда пользователь просит выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
||||
description: Выгрузка конфигурации 1С в XML-файлы. Используй когда нужно выгрузить конфигурацию в файлы, XML, исходники, DumpConfigToFiles
|
||||
argument-hint: "[database] [outputDir]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-list
|
||||
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда пользователь говорит про базы данных, список баз, "добавь базу", "какие базы есть"
|
||||
description: Управление реестром баз данных 1С (.v8-project.json). Используй когда нужно работать с реестром баз — список баз, зарегистрировать базу в реестре, какие базы есть
|
||||
argument-hint: "[add|remove|show]"
|
||||
allowed-tools:
|
||||
- Read
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-load-cf
|
||||
description: Загрузка конфигурации 1С из CF-файла. Используй когда пользователь просит загрузить конфигурацию из CF, восстановить из бэкапа CF
|
||||
description: Загрузка конфигурации 1С из CF-файла. Используй когда нужно загрузить конфигурацию из CF, восстановить из бэкапа CF
|
||||
argument-hint: <input.cf> [database]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-load-git
|
||||
description: Загрузка изменений из Git в базу 1С. Используй когда пользователь просит загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
||||
description: Загрузка изменений из Git в базу 1С. Используй когда нужно загрузить изменения из гита, обновить базу из репозитория, partial load из коммита
|
||||
argument-hint: "[database] [source]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-load-xml
|
||||
description: Загрузка конфигурации 1С из XML-файлов. Используй когда пользователь просит загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
||||
description: Загрузка конфигурации 1С из XML-файлов. Используй когда нужно загрузить конфигурацию из файлов, XML, исходников, LoadConfigFromFiles
|
||||
argument-hint: <configDir> [database]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-load-xml v1.1 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
<#
|
||||
.SYNOPSIS
|
||||
@@ -98,7 +98,10 @@ param(
|
||||
[string]$Format = "Hierarchical",
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$UpdateDB
|
||||
[switch]$UpdateDB,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$StrictLog
|
||||
)
|
||||
|
||||
$OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
@@ -213,20 +216,58 @@ try {
|
||||
$process = Start-Process -FilePath $V8Path -ArgumentList $arguments -NoNewWindow -Wait -PassThru
|
||||
$exitCode = $process.ExitCode
|
||||
|
||||
# --- Read log ---
|
||||
$logContent = $null
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
}
|
||||
|
||||
# --- Scan log for silent rejections ---
|
||||
# Platform often writes load-time rejections into /Out but exits with code 0.
|
||||
# These patterns flag cases where metadata was dropped or rejected silently.
|
||||
$fatalLogPatterns = @(
|
||||
'Неверное свойство объекта метаданных',
|
||||
'не входит в состав объекта метаданных',
|
||||
'Неизвестное имя типа',
|
||||
'Неизвестный объект метаданных',
|
||||
'Ни один из документов не является регистратором для регистра',
|
||||
'Неверное значение перечисления',
|
||||
'не может быть приведен к типу'
|
||||
)
|
||||
$silentFailures = @()
|
||||
if ($logContent) {
|
||||
foreach ($line in ($logContent -split "`r?`n")) {
|
||||
foreach ($pat in $fatalLogPatterns) {
|
||||
if ($line -match [regex]::Escape($pat)) {
|
||||
$silentFailures += $line.Trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
||||
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host "Load completed successfully" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "Error loading configuration (code: $exitCode)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($logContent) {
|
||||
Write-Host "--- Log ---"
|
||||
Write-Host $logContent
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
if ($logContent) {
|
||||
Write-Host "--- Log ---"
|
||||
Write-Host $logContent
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
|
||||
if ($silentFailures.Count -gt 0) {
|
||||
$msg = "[warning] log contains $($silentFailures.Count) rejection(s) — platform loaded config but dropped properties/refs"
|
||||
if (-not $StrictLog) { $msg += " (pass -StrictLog to treat as error)" }
|
||||
Write-Host $msg -ForegroundColor Yellow
|
||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-load-xml v1.1 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.3 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -63,6 +63,11 @@ def main():
|
||||
help="File format (default: Hierarchical)",
|
||||
)
|
||||
parser.add_argument("-UpdateDB", action="store_true", help="Also update database configuration after load")
|
||||
parser.add_argument(
|
||||
"-StrictLog",
|
||||
action="store_true",
|
||||
help="Treat silent rejection warnings in the log as errors (elevate exit code to 1)",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- Resolve V8Path ---
|
||||
@@ -157,22 +162,60 @@ def main():
|
||||
)
|
||||
exit_code = result.returncode
|
||||
|
||||
# --- Read log ---
|
||||
log_content = ""
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
log_content = f.read()
|
||||
except Exception:
|
||||
log_content = ""
|
||||
|
||||
# --- Scan log for silent rejections ---
|
||||
# Platform often writes load-time rejections into /Out but exits with code 0.
|
||||
# These patterns flag cases where metadata was dropped or rejected silently.
|
||||
fatal_log_patterns = [
|
||||
"Неверное свойство объекта метаданных",
|
||||
"не входит в состав объекта метаданных",
|
||||
"Неизвестное имя типа",
|
||||
"Неизвестный объект метаданных",
|
||||
"Ни один из документов не является регистратором для регистра",
|
||||
"Неверное значение перечисления",
|
||||
"не может быть приведен к типу",
|
||||
]
|
||||
silent_failures = []
|
||||
if log_content:
|
||||
for line in log_content.splitlines():
|
||||
for pat in fatal_log_patterns:
|
||||
if pat in line:
|
||||
silent_failures.append(line.strip())
|
||||
break
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
# rejection warnings) is always printed to stdout for visibility. With -StrictLog,
|
||||
# elevate exit code to 1 when rejection patterns are found even if platform said 0.
|
||||
if exit_code == 0:
|
||||
print("Load completed successfully")
|
||||
else:
|
||||
print(f"Error loading configuration (code: {exit_code})", file=sys.stderr)
|
||||
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
log_content = f.read()
|
||||
if log_content:
|
||||
print("--- Log ---")
|
||||
print(log_content)
|
||||
print("--- End ---")
|
||||
except Exception:
|
||||
pass
|
||||
if log_content:
|
||||
print("--- Log ---")
|
||||
print(log_content)
|
||||
print("--- End ---")
|
||||
|
||||
if silent_failures:
|
||||
suffix = "" if args.StrictLog else " (pass -StrictLog to treat as error)"
|
||||
print(
|
||||
f"[warning] log contains {len(silent_failures)} rejection(s) — "
|
||||
f"platform loaded config but dropped properties/refs{suffix}",
|
||||
file=sys.stderr,
|
||||
)
|
||||
for f in silent_failures:
|
||||
print(f" {f}", file=sys.stderr)
|
||||
if args.StrictLog and exit_code == 0:
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-run
|
||||
description: Запуск 1С:Предприятие. Используй когда пользователь просит запустить 1С, открыть базу, запустить предприятие
|
||||
description: Запуск 1С:Предприятие. Используй когда нужно запустить 1С, открыть базу, запустить предприятие
|
||||
argument-hint: "[database]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: db-update
|
||||
description: Обновление конфигурации базы данных 1С. Используй когда пользователь просит обновить БД, применить конфигурацию, UpdateDBCfg
|
||||
description: Обновление конфигурации базы данных 1С. Используй когда нужно обновить БД, применить конфигурацию, UpdateDBCfg
|
||||
argument-hint: "[database]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,60 +0,0 @@
|
||||
---
|
||||
name: epf-add-form
|
||||
description: Добавить управляемую форму к внешней обработке 1С
|
||||
argument-hint: <ProcessorName> <FormName> [Synonym]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
- Glob
|
||||
- Grep
|
||||
---
|
||||
|
||||
# /epf-add-form — Добавление формы
|
||||
|
||||
Создаёт управляемую форму и регистрирует её в корневом XML обработки.
|
||||
|
||||
## Usage
|
||||
|
||||
```
|
||||
/epf-add-form <ProcessorName> <FormName> [Synonym] [--main]
|
||||
```
|
||||
|
||||
| Параметр | Обязательный | По умолчанию | Описание |
|
||||
|---------------|:------------:|--------------|-------------------------------------------|
|
||||
| ProcessorName | да | — | Имя обработки (должна существовать) |
|
||||
| FormName | да | — | Имя формы |
|
||||
| Synonym | нет | = FormName | Синоним формы |
|
||||
| --main | нет | авто | Установить как форму по умолчанию (автоматически для первой формы) |
|
||||
| SrcDir | нет | `src` | Каталог исходников |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/epf-add-form/scripts/add-form.ps1 -ProcessorName "<ProcessorName>" -FormName "<FormName>" [-Synonym "<Synonym>"] [-Main] [-SrcDir "<SrcDir>"]
|
||||
```
|
||||
|
||||
## Что создаётся
|
||||
|
||||
```
|
||||
<SrcDir>/<ProcessorName>/Forms/
|
||||
├── <FormName>.xml # Метаданные формы (1 UUID)
|
||||
└── <FormName>/
|
||||
└── Ext/
|
||||
├── Form.xml # Описание формы (logform namespace)
|
||||
└── Form/
|
||||
└── Module.bsl # BSL-модуль с 4 регионами
|
||||
```
|
||||
|
||||
## Что модифицируется
|
||||
|
||||
- `<SrcDir>/<ProcessorName>.xml` — добавляется `<Form>` в `ChildObjects`, обновляется `DefaultForm` (автоматически если это первая форма, или явно при `--main`)
|
||||
|
||||
## Детали
|
||||
|
||||
- FormType: Managed
|
||||
- UsePurposes: PlatformApplication, MobilePlatformApplication
|
||||
- AutoCommandBar с id=-1
|
||||
- Реквизит "Объект" с MainAttribute=true
|
||||
- BSL-модуль содержит 5 регионов: ОбработчикиСобытийФормы, ОбработчикиСобытийЭлементовФормы, ОбработчикиКомандФормы, ОбработчикиОповещений, СлужебныеПроцедурыИФункции
|
||||
@@ -1,207 +0,0 @@
|
||||
# epf-add-form v1.0 — Add managed form to 1C processor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[string]$ProcessorName,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[string]$FormName,
|
||||
|
||||
[string]$Synonym = $FormName,
|
||||
|
||||
[switch]$Main,
|
||||
|
||||
[string]$SrcDir = "src"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
$rootXmlPath = Join-Path $SrcDir "$ProcessorName.xml"
|
||||
if (-not (Test-Path $rootXmlPath)) {
|
||||
Write-Error "Корневой файл обработки не найден: $rootXmlPath. Сначала выполните epf-init."
|
||||
exit 1
|
||||
}
|
||||
|
||||
$processorDir = Join-Path $SrcDir $ProcessorName
|
||||
$formsDir = Join-Path $processorDir "Forms"
|
||||
$formMetaPath = Join-Path $formsDir "$FormName.xml"
|
||||
|
||||
if (Test-Path $formMetaPath) {
|
||||
Write-Error "Форма уже существует: $formMetaPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Создание каталогов ---
|
||||
|
||||
$formDir = Join-Path $formsDir $FormName
|
||||
$formExtDir = Join-Path $formDir "Ext"
|
||||
$formModuleDir = Join-Path $formExtDir "Form"
|
||||
|
||||
New-Item -ItemType Directory -Path $formModuleDir -Force | Out-Null
|
||||
|
||||
# --- Кодировка ---
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
$encNoBom = New-Object System.Text.UTF8Encoding($false)
|
||||
|
||||
# --- 1. Метаданные формы (Forms/<FormName>.xml) ---
|
||||
|
||||
$formUuid = [guid]::NewGuid().ToString()
|
||||
|
||||
$formMetaXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Form uuid="$formUuid">
|
||||
<Properties>
|
||||
<Name>$FormName</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>$Synonym</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<FormType>Managed</FormType>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">MobilePlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ExtendedPresentation/>
|
||||
</Properties>
|
||||
</Form>
|
||||
</MetaDataObject>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formMetaPath, $formMetaXml, $encBom)
|
||||
|
||||
# --- 2. Описание формы (Forms/<FormName>/Ext/Form.xml) ---
|
||||
|
||||
$formXmlPath = Join-Path $formExtDir "Form.xml"
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:dcscor="http://v8.1c.ru/8.1/data-composition-system/core" xmlns:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Объект" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:ExternalDataProcessorObject.$ProcessorName</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($formXmlPath, $formXml, $encBom)
|
||||
|
||||
# --- 3. BSL-модуль (Forms/<FormName>/Ext/Form/Module.bsl) ---
|
||||
|
||||
$modulePath = Join-Path $formModuleDir "Module.bsl"
|
||||
|
||||
$moduleBsl = @"
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиКомандФормы
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиОповещений
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область СлужебныеПроцедурыИФункции
|
||||
|
||||
#КонецОбласти
|
||||
"@
|
||||
|
||||
[System.IO.File]::WriteAllText($modulePath, $moduleBsl, $encBom)
|
||||
|
||||
# --- 4. Модификация корневого XML ---
|
||||
|
||||
$rootXmlFull = Resolve-Path $rootXmlPath
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($rootXmlFull.Path)
|
||||
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||
|
||||
$childObjects = $xmlDoc.SelectSingleNode("//md:ChildObjects", $nsMgr)
|
||||
if (-not $childObjects) {
|
||||
Write-Error "Не найден элемент ChildObjects в $rootXmlPath"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Добавить <Form> перед первым <Template>, или в конец
|
||||
$formElem = $xmlDoc.CreateElement("Form", "http://v8.1c.ru/8.3/MDClasses")
|
||||
$formElem.InnerText = $FormName
|
||||
|
||||
$firstTemplate = $childObjects.SelectSingleNode("md:Template", $nsMgr)
|
||||
if ($firstTemplate) {
|
||||
# Вставить перед Template, добавив перенос строки + табуляцию
|
||||
$whitespace = $xmlDoc.CreateWhitespace("`n`t`t`t")
|
||||
$childObjects.InsertBefore($whitespace, $firstTemplate) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $whitespace) | Out-Null
|
||||
} else {
|
||||
# Добавить в конец ChildObjects
|
||||
# Если ChildObjects пустой (самозакрывающийся), нужно добавить форматирование
|
||||
if ($childObjects.ChildNodes.Count -eq 0) {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
} else {
|
||||
$lastChild = $childObjects.LastChild
|
||||
# Вставить перед закрывающим whitespace (если есть), или в конец
|
||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||
$childObjects.InsertBefore($xmlDoc.CreateWhitespace("`n`t`t`t"), $lastChild) | Out-Null
|
||||
$childObjects.InsertBefore($formElem, $lastChild) | Out-Null
|
||||
} else {
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||
$childObjects.AppendChild($formElem) | Out-Null
|
||||
$childObjects.AppendChild($xmlDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Обновить DefaultForm: явно при -Main, или автоматически если это первая форма
|
||||
$existingForms = $childObjects.SelectNodes("md:Form", $nsMgr)
|
||||
$isFirstForm = ($existingForms.Count -eq 1)
|
||||
|
||||
if ($Main -or $isFirstForm) {
|
||||
$defaultForm = $xmlDoc.SelectSingleNode("//md:DefaultForm", $nsMgr)
|
||||
if ($defaultForm) {
|
||||
$defaultForm.InnerText = "ExternalDataProcessor.$ProcessorName.Form.$FormName"
|
||||
}
|
||||
}
|
||||
|
||||
# Сохранить с BOM
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = $encBom
|
||||
$settings.Indent = $false # Preserve original whitespace
|
||||
|
||||
$stream = New-Object System.IO.FileStream($rootXmlFull.Path, [System.IO.FileMode]::Create)
|
||||
$writer = [System.Xml.XmlWriter]::Create($stream, $settings)
|
||||
$xmlDoc.Save($writer)
|
||||
$writer.Close()
|
||||
$stream.Close()
|
||||
|
||||
Write-Host "[OK] Создана форма: $FormName"
|
||||
Write-Host " Метаданные: $formMetaPath"
|
||||
Write-Host " Описание: $formXmlPath"
|
||||
Write-Host " Модуль: $modulePath"
|
||||
if ($Main -or $isFirstForm) {
|
||||
Write-Host " DefaultForm обновлён"
|
||||
}
|
||||
@@ -1,253 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-form v1.0 — Add managed form to 1C external data processor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from lxml import etree
|
||||
|
||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||
|
||||
|
||||
def save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
xml_bytes = xml_bytes.replace(b"<?xml version='1.0' encoding='UTF-8'?>", b'<?xml version="1.0" encoding="utf-8"?>')
|
||||
if not xml_bytes.endswith(b"\n"):
|
||||
xml_bytes += b"\n"
|
||||
with open(path, "wb") as f:
|
||||
f.write(b"\xef\xbb\xbf")
|
||||
f.write(xml_bytes)
|
||||
|
||||
|
||||
def write_text_with_bom(path, text):
|
||||
"""Write text to file with UTF-8 BOM."""
|
||||
with open(path, "w", encoding="utf-8-sig") as f:
|
||||
f.write(text)
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Add managed form to 1C processor", allow_abbrev=False)
|
||||
parser.add_argument("-ProcessorName", required=True)
|
||||
parser.add_argument("-FormName", required=True)
|
||||
parser.add_argument("-Synonym", default=None)
|
||||
parser.add_argument("-Main", action="store_true")
|
||||
parser.add_argument("-SrcDir", default="src")
|
||||
args = parser.parse_args()
|
||||
|
||||
processor_name = args.ProcessorName
|
||||
form_name = args.FormName
|
||||
synonym = args.Synonym if args.Synonym is not None else form_name
|
||||
is_main = args.Main
|
||||
src_dir = args.SrcDir
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
root_xml_path = os.path.join(src_dir, f"{processor_name}.xml")
|
||||
if not os.path.exists(root_xml_path):
|
||||
print(f"Корневой файл обработки не найден: {root_xml_path}. Сначала выполните epf-init.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
processor_dir = os.path.join(src_dir, processor_name)
|
||||
forms_dir = os.path.join(processor_dir, "Forms")
|
||||
form_meta_path = os.path.join(forms_dir, f"{form_name}.xml")
|
||||
|
||||
if os.path.exists(form_meta_path):
|
||||
print(f"Форма уже существует: {form_meta_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Create directories ---
|
||||
|
||||
form_dir = os.path.join(forms_dir, form_name)
|
||||
form_ext_dir = os.path.join(form_dir, "Ext")
|
||||
form_module_dir = os.path.join(form_ext_dir, "Form")
|
||||
|
||||
os.makedirs(form_module_dir, exist_ok=True)
|
||||
|
||||
# --- 1. Form metadata (Forms/<FormName>.xml) ---
|
||||
|
||||
form_uuid = str(uuid.uuid4())
|
||||
|
||||
form_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: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">\n'
|
||||
f'\t<Form uuid="{form_uuid}">\n'
|
||||
'\t\t<Properties>\n'
|
||||
f'\t\t\t<Name>{form_name}</Name>\n'
|
||||
'\t\t\t<Synonym>\n'
|
||||
'\t\t\t\t<v8:item>\n'
|
||||
'\t\t\t\t\t<v8:lang>ru</v8:lang>\n'
|
||||
f'\t\t\t\t\t<v8:content>{synonym}</v8:content>\n'
|
||||
'\t\t\t\t</v8:item>\n'
|
||||
'\t\t\t</Synonym>\n'
|
||||
'\t\t\t<Comment/>\n'
|
||||
'\t\t\t<FormType>Managed</FormType>\n'
|
||||
'\t\t\t<IncludeHelpInContents>false</IncludeHelpInContents>\n'
|
||||
'\t\t\t<UsePurposes>\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</UsePurposes>\n'
|
||||
'\t\t\t<ExtendedPresentation/>\n'
|
||||
'\t\t</Properties>\n'
|
||||
'\t</Form>\n'
|
||||
'</MetaDataObject>'
|
||||
)
|
||||
|
||||
write_text_with_bom(form_meta_path, form_meta_xml)
|
||||
|
||||
# --- 2. Form description (Forms/<FormName>/Ext/Form.xml) ---
|
||||
|
||||
form_xml_path = os.path.join(form_ext_dir, "Form.xml")
|
||||
|
||||
form_xml = (
|
||||
'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
'<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:dcsset="http://v8.1c.ru/8.1/data-composition-system/settings"'
|
||||
' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
||||
' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
||||
' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
||||
' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
||||
' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
||||
' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
||||
' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
||||
' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
||||
' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1">\n'
|
||||
'\t\t\t<Type>\n'
|
||||
f'\t\t\t\t<v8:Type>cfg:ExternalDataProcessorObject.{processor_name}</v8:Type>\n'
|
||||
'\t\t\t</Type>\n'
|
||||
'\t\t\t<MainAttribute>true</MainAttribute>\n'
|
||||
'\t\t</Attribute>\n'
|
||||
'\t</Attributes>\n'
|
||||
'</Form>'
|
||||
)
|
||||
|
||||
write_text_with_bom(form_xml_path, form_xml)
|
||||
|
||||
# --- 3. BSL module (Forms/<FormName>/Ext/Form/Module.bsl) ---
|
||||
|
||||
module_path = os.path.join(form_module_dir, "Module.bsl")
|
||||
|
||||
module_bsl = (
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u041a\u043e\u043c\u0430\u043d\u0434\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u041e\u043f\u043e\u0432\u0435\u0449\u0435\u043d\u0438\u0439\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u0421\u043b\u0443\u0436\u0435\u0431\u043d\u044b\u0435\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b\u0418\u0424\u0443\u043d\u043a\u0446\u0438\u0438\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438'
|
||||
)
|
||||
|
||||
write_text_with_bom(module_path, module_bsl)
|
||||
|
||||
# --- 4. Modify root XML ---
|
||||
|
||||
root_xml_full = os.path.abspath(root_xml_path)
|
||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(root_xml_full, parser_xml)
|
||||
root = tree.getroot()
|
||||
|
||||
ns = "http://v8.1c.ru/8.3/MDClasses"
|
||||
child_objects = root.find(".//md:ChildObjects", NSMAP)
|
||||
if child_objects is None:
|
||||
print(f"Не найден элемент ChildObjects в {root_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Add <Form> before first <Template>, or at end
|
||||
form_elem = etree.Element(f"{{{ns}}}Form")
|
||||
form_elem.text = form_name
|
||||
|
||||
first_template = child_objects.find("md:Template", NSMAP)
|
||||
if first_template is not None:
|
||||
# Insert before Template, adding newline + indent
|
||||
idx = list(child_objects).index(first_template)
|
||||
child_objects.insert(idx, form_elem)
|
||||
# Set whitespace: form_elem gets same tail pattern
|
||||
form_elem.tail = "\n\t\t\t"
|
||||
else:
|
||||
# Add to end of ChildObjects
|
||||
children = list(child_objects)
|
||||
if len(children) == 0 and (child_objects.text is None or child_objects.text.strip() == ""):
|
||||
# Empty ChildObjects (self-closing)
|
||||
child_objects.text = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
else:
|
||||
if len(children) > 0:
|
||||
last_child = children[-1]
|
||||
old_tail = last_child.tail
|
||||
last_child.tail = "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = old_tail if old_tail else "\n\t\t"
|
||||
else:
|
||||
child_objects.text = (child_objects.text or "") + "\n\t\t\t"
|
||||
child_objects.append(form_elem)
|
||||
form_elem.tail = "\n\t\t"
|
||||
|
||||
# Update DefaultForm: explicitly with -Main, or automatically if this is the first form
|
||||
existing_forms = child_objects.findall("md:Form", NSMAP)
|
||||
is_first_form = len(existing_forms) == 1
|
||||
|
||||
if is_main or is_first_form:
|
||||
default_form = root.find(".//md:DefaultForm", NSMAP)
|
||||
if default_form is not None:
|
||||
default_form.text = f"ExternalDataProcessor.{processor_name}.Form.{form_name}"
|
||||
|
||||
# Save with BOM
|
||||
save_xml_with_bom(tree, root_xml_full)
|
||||
|
||||
print(f"[OK] Создана форма: {form_name}")
|
||||
print(f" Метаданные: {form_meta_path}")
|
||||
print(f" Описание: {form_xml_path}")
|
||||
print(f" Модуль: {module_path}")
|
||||
if is_main or is_first_form:
|
||||
print(" DefaultForm обновлён")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-bsp-add-command
|
||||
description: Добавить команду в дополнительную обработку БСП
|
||||
description: Определить команду в БСП‑описании обработки (`СведенияОВнешнейОбработке`) — открытие формы, вызов клиентского/серверного метода, заполнение объекта и т.п. Используй когда нужно зарегистрировать команду в дополнительной обработке БСП
|
||||
argument-hint: <ProcessorName> <Идентификатор> [ТипКоманды] [Представление]
|
||||
allowed-tools:
|
||||
- Read
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-bsp-init
|
||||
description: Добавить функцию регистрации БСП (СведенияОВнешнейОбработке) в модуль объекта обработки
|
||||
description: Сформировать функцию `СведенияОВнешнейОбработке` в модуле объекта обработки — описание для подключения через подсистему БСП «Дополнительные отчёты и обработки». Используй когда нужно сделать обработку совместимой с БСП, подключаемой через «Дополнительные отчёты и обработки»
|
||||
argument-hint: <ProcessorName> <Вид>
|
||||
allowed-tools:
|
||||
- Read
|
||||
@@ -203,6 +203,6 @@ allowed-tools:
|
||||
## Дальнейшие шаги
|
||||
|
||||
- Добавить ещё команду: `/epf-bsp-add-command`
|
||||
- Добавить форму: `/epf-add-form`
|
||||
- Добавить форму: `/form-add`
|
||||
- Добавить макет: `/template-add`
|
||||
- Собрать EPF: `/epf-build`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: epf-init
|
||||
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников)
|
||||
description: Создать пустую внешнюю обработку 1С (scaffold XML-исходников). Используй когда нужно создать новую внешнюю обработку с нуля
|
||||
argument-hint: <Name> [Synonym]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -35,7 +35,7 @@ powershell.exe -NoProfile -File .claude/skills/epf-init/scripts/init.ps1 -Name "
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
- Добавить форму: `/epf-add-form`
|
||||
- Добавить форму: `/form-add`
|
||||
- Добавить макет: `/template-add`
|
||||
- Добавить справку: `/help-add`
|
||||
- Собрать EPF: `/epf-build`
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# epf-validate v1.1 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.2 — Validate 1C external data processor / report structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -184,8 +185,8 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
$version = $root.GetAttribute("version")
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17 or 2.20)"
|
||||
} elseif ($version -ne "2.17" -and $version -ne "2.20" -and $version -ne "2.21") {
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17, 2.20 or 2.21)"
|
||||
}
|
||||
|
||||
# Detect type: ExternalDataProcessor or ExternalReport
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-validate v1.1 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.2 — Validate 1C external data processor / report structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# Works for both EPF (ExternalDataProcessor) and ERF (ExternalReport) — auto-detects
|
||||
|
||||
@@ -46,7 +46,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Validate 1C external data processor/report structure", allow_abbrev=False)
|
||||
parser.add_argument("-ObjectPath", required=True)
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
parser.add_argument("-OutFile", default=None)
|
||||
@@ -165,8 +165,8 @@ def main():
|
||||
version = root.get("version", "")
|
||||
if not version:
|
||||
report_warn("1. Missing version attribute on MetaDataObject")
|
||||
elif version not in ("2.17", "2.20"):
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17 or 2.20)")
|
||||
elif version not in ("2.17", "2.20", "2.21"):
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17, 2.20 or 2.21)")
|
||||
|
||||
# Detect type
|
||||
child_elements = []
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: erf-init
|
||||
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников)
|
||||
description: Создать пустой внешний отчёт 1С (scaffold XML-исходников). Используй когда нужно создать новый внешний отчёт с нуля
|
||||
argument-hint: <Name> [Synonym] [--with-skd]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: form-add
|
||||
description: Добавить управляемую форму к объекту конфигурации 1С
|
||||
description: Добавить пустую управляемую форму к объекту 1С. Используй когда нужно создать у объекта новую форму
|
||||
argument-hint: <ObjectPath> <FormName> [Purpose] [--set-default]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-add v1.2 — Add managed form to 1C config object
|
||||
# form-add v1.4 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -16,6 +16,23 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Detect XML format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
# --- Фаза 1: Определение типа объекта ---
|
||||
|
||||
# Resolve ObjectPath (directory → .xml)
|
||||
@@ -36,6 +53,8 @@ if (-not (Test-Path $ObjectPath)) {
|
||||
}
|
||||
|
||||
$objectXmlFull = Resolve-Path $ObjectPath
|
||||
$script:formatVersion = Detect-FormatVersion (Split-Path $objectXmlFull.Path -Parent)
|
||||
|
||||
$xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$xmlDoc.PreserveWhitespace = $true
|
||||
$xmlDoc.Load($objectXmlFull.Path)
|
||||
@@ -54,7 +73,7 @@ if (-not $metaDataObject) {
|
||||
$supportedTypes = @(
|
||||
"Document", "Catalog", "DataProcessor", "Report",
|
||||
"ExternalDataProcessor", "ExternalReport",
|
||||
"InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ExchangePlan", "BusinessProcess", "Task"
|
||||
)
|
||||
|
||||
@@ -159,7 +178,7 @@ if ($objectType -in $processorLikeTypes) {
|
||||
|
||||
$formMetaXml = @"
|
||||
<?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="$($script:formatVersion)">
|
||||
<Form uuid="$formUuid">
|
||||
<Properties>
|
||||
<Name>$FormName</Name>
|
||||
@@ -196,13 +215,10 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="Список" id="1">
|
||||
@@ -224,13 +240,10 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
@@ -261,27 +274,30 @@ if ($Purpose -eq "List" -or $Purpose -eq "Choice") {
|
||||
"BusinessProcess" = "BusinessProcessObject"
|
||||
"Task" = "TaskObject"
|
||||
"InformationRegister" = "InformationRegisterRecordManager"
|
||||
"AccumulationRegister" = "AccumulationRegisterRecordSet"
|
||||
}
|
||||
|
||||
$mainAttrType = "$($attrTypeMap[$objectType]).$objectName"
|
||||
|
||||
# SavedData: standard for Catalog/Document/etc, but not for processor-like (DataProcessor/Report/External*)
|
||||
$savedDataLine = ""
|
||||
if ($objectType -notin $processorLikeTypes) {
|
||||
$savedDataLine = "`n`t`t`t<SavedData>true</SavedData>"
|
||||
}
|
||||
|
||||
$formXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Form $formNsDecl version="2.17">
|
||||
<Form $formNsDecl version="$($script:formatVersion)">
|
||||
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
|
||||
<Autofill>true</Autofill>
|
||||
</AutoCommandBar>
|
||||
<Events>
|
||||
<Event name="OnCreateAtServer">ПриСозданииНаСервере</Event>
|
||||
</Events>
|
||||
<ChildItems/>
|
||||
<Attributes>
|
||||
<Attribute name="$mainAttrName" id="1">
|
||||
<Type>
|
||||
<v8:Type>cfg:$mainAttrType</v8:Type>
|
||||
</Type>
|
||||
<MainAttribute>true</MainAttribute>
|
||||
<SavedData>true</SavedData>
|
||||
<MainAttribute>true</MainAttribute>$savedDataLine
|
||||
</Attribute>
|
||||
</Attributes>
|
||||
</Form>
|
||||
@@ -301,11 +317,6 @@ $modulePath = Join-Path $formModuleDir "Module.bsl"
|
||||
$moduleBsl = @"
|
||||
#Область ОбработчикиСобытийФормы
|
||||
|
||||
&НаСервере
|
||||
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
|
||||
|
||||
КонецПроцедуры
|
||||
|
||||
#КонецОбласти
|
||||
|
||||
#Область ОбработчикиСобытийЭлементовФормы
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-add v1.2 — Add managed form to 1C config object
|
||||
# form-add v1.4 — Add managed form to 1C config object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
@@ -15,6 +16,22 @@ NSMAP = {
|
||||
}
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
@@ -67,6 +84,8 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
object_xml_full = os.path.abspath(object_path)
|
||||
format_version = detect_format_version(os.path.dirname(object_xml_full))
|
||||
|
||||
parser_xml = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(object_xml_full, parser_xml)
|
||||
root = tree.getroot()
|
||||
@@ -74,7 +93,7 @@ def main():
|
||||
supported_types = [
|
||||
"Document", "Catalog", "DataProcessor", "Report",
|
||||
"ExternalDataProcessor", "ExternalReport",
|
||||
"InformationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"InformationRegister", "AccumulationRegister", "ChartOfAccounts", "ChartOfCharacteristicTypes",
|
||||
"ExchangePlan", "BusinessProcess", "Task",
|
||||
]
|
||||
|
||||
@@ -171,7 +190,7 @@ def main():
|
||||
' 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">\n'
|
||||
f' version="{format_version}">\n'
|
||||
f'\t<Form uuid="{form_uuid}">\n'
|
||||
'\t\t<Properties>\n'
|
||||
f'\t\t\t<Name>{form_name}</Name>\n'
|
||||
@@ -225,13 +244,10 @@ def main():
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
'\t\t<Attribute name="\u0421\u043f\u0438\u0441\u043e\u043a" id="1">\n'
|
||||
@@ -254,13 +270,10 @@ def main():
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="{main_attr_name}" id="1">\n'
|
||||
@@ -291,19 +304,22 @@ def main():
|
||||
"BusinessProcess": "BusinessProcessObject",
|
||||
"Task": "TaskObject",
|
||||
"InformationRegister": "InformationRegisterRecordManager",
|
||||
"AccumulationRegister": "AccumulationRegisterRecordSet",
|
||||
}
|
||||
|
||||
main_attr_type = f"{attr_type_map[object_type]}.{object_name}"
|
||||
|
||||
# SavedData: standard for Catalog/Document/etc, but not for processor-like (DataProcessor/Report/External*)
|
||||
saved_data_line = ''
|
||||
if object_type not in processor_like_types:
|
||||
saved_data_line = '\t\t\t<SavedData>true</SavedData>\n'
|
||||
|
||||
form_xml = (
|
||||
f'<?xml version="1.0" encoding="UTF-8"?>\n'
|
||||
f'<Form {form_ns_decl} version="2.17">\n'
|
||||
f'<Form {form_ns_decl} version="{format_version}">\n'
|
||||
'\t<AutoCommandBar name="\u0424\u043e\u0440\u043c\u0430\u041a\u043e\u043c\u0430\u043d\u0434\u043d\u0430\u044f\u041f\u0430\u043d\u0435\u043b\u044c" id="-1">\n'
|
||||
'\t\t<Autofill>true</Autofill>\n'
|
||||
'\t</AutoCommandBar>\n'
|
||||
'\t<Events>\n'
|
||||
'\t\t<Event name="OnCreateAtServer">\u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435</Event>\n'
|
||||
'\t</Events>\n'
|
||||
'\t<ChildItems/>\n'
|
||||
'\t<Attributes>\n'
|
||||
f'\t\t<Attribute name="{main_attr_name}" id="1">\n'
|
||||
@@ -311,7 +327,7 @@ def main():
|
||||
f'\t\t\t\t<v8:Type>cfg:{main_attr_type}</v8:Type>\n'
|
||||
'\t\t\t</Type>\n'
|
||||
'\t\t\t<MainAttribute>true</MainAttribute>\n'
|
||||
'\t\t\t<SavedData>true</SavedData>\n'
|
||||
f'{saved_data_line}'
|
||||
'\t\t</Attribute>\n'
|
||||
'\t</Attributes>\n'
|
||||
'</Form>'
|
||||
@@ -329,11 +345,6 @@ def main():
|
||||
module_bsl = (
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u0424\u043e\u0440\u043c\u044b\n'
|
||||
'\n'
|
||||
'&\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435\n'
|
||||
'\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u0430 \u041f\u0440\u0438\u0421\u043e\u0437\u0434\u0430\u043d\u0438\u0438\u041d\u0430\u0421\u0435\u0440\u0432\u0435\u0440\u0435(\u041e\u0442\u043a\u0430\u0437, \u0421\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u0430\u044f\u041e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0430)\n'
|
||||
'\n'
|
||||
'\u041a\u043e\u043d\u0435\u0446\u041f\u0440\u043e\u0446\u0435\u0434\u0443\u0440\u044b\n'
|
||||
'\n'
|
||||
'#\u041a\u043e\u043d\u0435\u0446\u041e\u0431\u043b\u0430\u0441\u0442\u0438\n'
|
||||
'\n'
|
||||
'#\u041e\u0431\u043b\u0430\u0441\u0442\u044c \u041e\u0431\u0440\u0430\u0431\u043e\u0442\u0447\u0438\u043a\u0438\u0421\u043e\u0431\u044b\u0442\u0438\u0439\u042d\u043b\u0435\u043c\u0435\u043d\u0442\u043e\u0432\u0424\u043e\u0440\u043c\u044b\n'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: form-compile
|
||||
description: Компиляция управляемой формы 1С из компактного JSON-определения. Используй когда нужно создать форму с нуля по описанию элементов
|
||||
argument-hint: <JsonPath> <OutputPath>
|
||||
description: Компиляция управляемой формы 1С из JSON-определения или из метаданных объекта. Используй когда нужно создать форму с нуля по описанию элементов или сгенерировать типовую форму
|
||||
argument-hint: <JsonPath> <OutputPath> | -FromObject <OutputPath>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -9,29 +9,30 @@ allowed-tools:
|
||||
- Glob
|
||||
---
|
||||
|
||||
# /form-compile — Генерация Form.xml из JSON DSL
|
||||
# /form-compile — Генерация Form.xml
|
||||
|
||||
Принимает компактное JSON-определение формы (20–50 строк) и генерирует полный корректный Form.xml (100–500+ строк) с namespace-декларациями, автогенерированными companion-элементами, последовательными ID.
|
||||
Два режима:
|
||||
1. **JSON DSL** — из JSON-определения формы
|
||||
2. **From object** (`-FromObject`) — автоматически из метаданных объекта 1С по пресету ERP
|
||||
|
||||
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника: архетипы, конвенции именования, продвинутые паттерны. Для простых форм (1–3 поля, пользователь описал что нужно) — не нужно.
|
||||
|
||||
## Использование
|
||||
|
||||
```
|
||||
/form-compile <JsonPath> <OutputPath>
|
||||
```
|
||||
> **При проектировании формы с нуля (5+ элементов или нечёткие требования)** — вызовите `/form-patterns` для загрузки справочника. Для простых форм (1–3 поля) — не нужно.
|
||||
|
||||
## Параметры
|
||||
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|-----------------------------------|
|
||||
| JsonPath | да | Путь к JSON-определению формы |
|
||||
| OutputPath | да | Путь к выходному файлу Form.xml |
|
||||
| Параметр | Обязательный | Описание |
|
||||
|------------|:------------:|---------------------------------|
|
||||
| JsonPath | режим 1 | Путь к JSON-определению формы |
|
||||
| OutputPath | да | Путь к выходному Form.xml |
|
||||
| FromObject | режим 2 | Флаг (без значения) — генерация по метаданным объекта |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -JsonPath "<json>" -OutputPath "<xml>"
|
||||
# Режим JSON DSL
|
||||
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -JsonPath "<json>" -OutputPath "<Form.xml>"
|
||||
|
||||
# Режим from-object (объект и purpose выводятся из OutputPath; Document и Catalog)
|
||||
powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile.ps1 -FromObject -OutputPath "<.../TypePlural/ObjectName/Forms/FormName/Ext/Form.xml>"
|
||||
```
|
||||
|
||||
## JSON DSL — справка
|
||||
@@ -167,6 +168,12 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
| `footer: true` | Показать подвал |
|
||||
| `commandBarLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||
| `searchStringLocation` | `"None"`, `"Top"`, `"Auto"` |
|
||||
| `choiceMode: true` | Режим выбора (для форм выбора) |
|
||||
| `initialTreeView` | `"ExpandTopLevel"` и др. (иерархические списки) |
|
||||
| `enableDrag: true` | Разрешить перетаскивание |
|
||||
| `enableStartDrag: true` | Разрешить начало перетаскивания |
|
||||
| `rowPictureDataPath` | Путь к картинке строки (напр. `"Список.DefaultPicture"`) |
|
||||
| `tableAutofill: false` | Управление Autofill внутреннего AutoCommandBar |
|
||||
|
||||
### Страницы (pages + page)
|
||||
|
||||
@@ -221,6 +228,9 @@ powershell.exe -NoProfile -File .claude/skills/form-compile/scripts/form-compile
|
||||
|
||||
```json
|
||||
{ "name": "Объект", "type": "DataProcessorObject.Загрузка", "main": true }
|
||||
{ "name": "Список", "type": "DynamicList", "main": true, "settings": {
|
||||
"mainTable": "Catalog.Номенклатура", "dynamicDataRead": true
|
||||
}}
|
||||
{ "name": "Итого", "type": "decimal(15,2)" }
|
||||
{ "name": "Таблица", "type": "ValueTable", "columns": [
|
||||
{ "name": "Номенклатура", "type": "CatalogRef.Номенклатура" },
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
# Form Presets
|
||||
|
||||
Пресеты управляют раскладкой форм, генерируемых в режиме `--from-object`.
|
||||
|
||||
## Как работает
|
||||
|
||||
Цепочка merge (каждый следующий уровень перезаписывает предыдущий через deep merge):
|
||||
|
||||
1. **Hardcoded defaults** -- встроены в скрипт, ориентированы на ERP
|
||||
2. **Built-in preset** -- файл из этой папки (`erp-standard.json` по умолчанию)
|
||||
3. **Project-level preset** -- файл `presets/skills/form/<name>.json`, поиск вверх от OutputPath
|
||||
|
||||
Имя пресета задаётся параметром `--preset` (по умолчанию `erp-standard`).
|
||||
|
||||
## Project-level пресет
|
||||
|
||||
Чтобы переопределить стандартный пресет в своём проекте, создайте файл:
|
||||
|
||||
```
|
||||
<project-root>/presets/skills/form/erp-standard.json
|
||||
```
|
||||
|
||||
Скрипт ищет этот файл, поднимаясь от OutputPath к корню. Первый найденный файл применяется поверх built-in через deep merge -- не нужно копировать весь пресет, достаточно указать только переопределяемые ключи.
|
||||
|
||||
## Секции
|
||||
|
||||
Ключи верхнего уровня в JSON -- секции вида `{тип}.{назначение}`:
|
||||
|
||||
| Секция | Тип объекта | Назначение формы |
|
||||
|--------|-------------|------------------|
|
||||
| `document.item` | Document | Форма документа |
|
||||
| `document.list` | Document | Форма списка |
|
||||
| `document.choice` | Document | Форма выбора |
|
||||
| `catalog.item` | Catalog | Форма элемента |
|
||||
| `catalog.folder` | Catalog | Форма группы |
|
||||
| `catalog.list` | Catalog | Форма списка |
|
||||
| `catalog.choice` | Catalog | Форма выбора |
|
||||
| `informationRegister.record` | InformationRegister | Форма записи |
|
||||
| `informationRegister.list` | InformationRegister | Форма списка |
|
||||
| `accumulationRegister.list` | AccumulationRegister | Форма списка |
|
||||
| `chartOfCharacteristicTypes.*` | ChartOfCharacteristicTypes | item/folder/list/choice |
|
||||
| `exchangePlan.*` | ExchangePlan | item/list/choice |
|
||||
| `chartOfAccounts.*` | ChartOfAccounts | item/folder/list/choice |
|
||||
|
||||
### basedOn
|
||||
|
||||
Секция может наследовать от другой:
|
||||
|
||||
```json
|
||||
{
|
||||
"document.choice": {
|
||||
"basedOn": "document.list",
|
||||
"properties": { "windowOpeningMode": "LockOwnerWindow" }
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Ключи секций
|
||||
|
||||
### Форма объекта (Item/Record)
|
||||
|
||||
| Ключ | Описание | Допустимые значения |
|
||||
|------|----------|---------------------|
|
||||
| `header.position` | Где размещать шапку | `"insidePage"` -- на первой странице, `"abovePages"` -- над страницами |
|
||||
| `header.layout` | Колонки шапки | `"1col"`, `"2col"` |
|
||||
| `header.distribute` | Распределение в 2 колонках | `"even"`, `"left"`, `"right"` |
|
||||
| `header.dateTitle` | Заголовок даты (Document) | строка, напр. `"от"` |
|
||||
| `footer.fields` | Поля в подвале | массив имён реквизитов, напр. `["Комментарий"]` |
|
||||
| `footer.position` | Где размещать подвал | `"insidePage"`, `"belowPages"`, `"none"` |
|
||||
| `tabularSections.container` | Контейнер табчастей | `"pages"` -- на вкладках, `"inline"` -- в корне, `"single-no-pages"` -- одна ТЧ без страниц |
|
||||
| `tabularSections.exclude` | Исключить табчасти | массив имён, напр. `["ДополнительныеРеквизиты"]` |
|
||||
| `tabularSections.lineNumber` | Колонка НомерСтроки | `true` / `false` |
|
||||
| `additional.position` | Блок доп. реквизитов | `"page"` -- отдельная вкладка, `"below"` -- под табчастями, `"none"` -- не создавать |
|
||||
| `additional.layout` | Колонки доп. блока | `"1col"`, `"2col"` |
|
||||
| `additional.bspGroup` | Группа ДополнительныеРеквизиты | `true` / `false` |
|
||||
| `codeDescription.layout` | Код + Наименование | `"horizontal"`, `"vertical"` |
|
||||
| `codeDescription.order` | Порядок Код/Наименование | `"descriptionFirst"`, `"codeFirst"` |
|
||||
| `parent.title` | Заголовок поля Родитель | строка, напр. `"Входит в группу"` |
|
||||
| `parent.position` | Позиция поля Родитель | `"beforeCodeDescription"`, `"afterCodeDescription"`, `"inHeader"` |
|
||||
| `owner.readOnly` | Владелец только для чтения | `true` / `false` |
|
||||
| `owner.position` | Позиция поля Владелец | `"first"` |
|
||||
| `fieldDefaults.ref.choiceButton` | Кнопка выбора для ссылок | `true` / `false` |
|
||||
| `fieldDefaults.boolean.element` | Элемент для Boolean | `"check"` (флажок) |
|
||||
| `commandBar` | Командная панель формы | `"auto"`, `"none"` |
|
||||
| `properties` | Свойства формы | объект: `autoTitle`, `windowOpeningMode` и др. |
|
||||
|
||||
### Форма списка (List/Choice)
|
||||
|
||||
| Ключ | Описание | Допустимые значения |
|
||||
|------|----------|---------------------|
|
||||
| `columns` | Какие колонки показывать | `"all"` -- все реквизиты, или массив имён |
|
||||
| `columnType` | Тип элемента колонки | `"labelField"`, `"input"` |
|
||||
| `hiddenRef` | Скрытая колонка Ref | `true` / `false` |
|
||||
| `tableCommandBar` | Командная панель таблицы | `"auto"`, `"none"` |
|
||||
| `commandBar` | Командная панель формы | `"auto"`, `"none"` |
|
||||
| `choiceMode` | Режим выбора (ChoiceForm) | `true` / `false` |
|
||||
| `properties` | Свойства формы | объект: `windowOpeningMode` и др. |
|
||||
|
||||
## Пример project-level пресета
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "my-project",
|
||||
"description": "Стиль форм нашего проекта",
|
||||
|
||||
"document.item": {
|
||||
"header": {
|
||||
"layout": "1col"
|
||||
},
|
||||
"tabularSections": {
|
||||
"exclude": ["ДополнительныеРеквизиты", "СведенияОСертификатах"]
|
||||
},
|
||||
"additional": {
|
||||
"position": "none"
|
||||
}
|
||||
},
|
||||
|
||||
"catalog.item": {
|
||||
"codeDescription": {
|
||||
"order": "codeFirst"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Этот файл переопределяет только указанные ключи -- остальное наследуется из built-in пресета.
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"name": "erp-standard",
|
||||
"description": "ERP 8.3.24 standard form layout",
|
||||
|
||||
"document.item": {
|
||||
"header": {
|
||||
"position": "insidePage",
|
||||
"layout": "2col",
|
||||
"distribute": "even",
|
||||
"dateTitle": "от"
|
||||
},
|
||||
"footer": {
|
||||
"fields": ["Комментарий"],
|
||||
"position": "insidePage"
|
||||
},
|
||||
"tabularSections": {
|
||||
"container": "pages",
|
||||
"exclude": ["ДополнительныеРеквизиты"],
|
||||
"lineNumber": true
|
||||
},
|
||||
"additional": {
|
||||
"position": "page",
|
||||
"layout": "2col",
|
||||
"bspGroup": true
|
||||
},
|
||||
"properties": {
|
||||
"autoTitle": false
|
||||
}
|
||||
},
|
||||
|
||||
"catalog.item": {
|
||||
"codeDescription": {
|
||||
"layout": "horizontal",
|
||||
"order": "descriptionFirst"
|
||||
},
|
||||
"parent": {
|
||||
"title": "Входит в группу",
|
||||
"position": "afterCodeDescription"
|
||||
},
|
||||
"tabularSections": {
|
||||
"exclude": ["ДополнительныеРеквизиты", "Представления"]
|
||||
}
|
||||
},
|
||||
|
||||
"informationRegister.record": {
|
||||
"properties": {
|
||||
"windowOpeningMode": "LockOwnerWindow"
|
||||
}
|
||||
},
|
||||
|
||||
"informationRegister.list": {},
|
||||
|
||||
"accumulationRegister.list": {},
|
||||
|
||||
"chartOfCharacteristicTypes.item": {
|
||||
"basedOn": "catalog.item"
|
||||
},
|
||||
|
||||
"exchangePlan.item": {
|
||||
"basedOn": "catalog.item"
|
||||
},
|
||||
|
||||
"chartOfAccounts.item": {
|
||||
"parent": {
|
||||
"title": "Подчинен счету"
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$FormPath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
|
||||
@@ -14,7 +14,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
# ── arg parsing ──────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True)
|
||||
parser.add_argument("-FormPath", "-Path", required=True)
|
||||
parser.add_argument("-JsonPath", required=True)
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias('Path')]
|
||||
[string]$FormPath,
|
||||
[int]$Limit = 150,
|
||||
[int]$Offset = 0,
|
||||
|
||||
@@ -342,7 +342,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C managed form structure", allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True, help="Path to Form.xml")
|
||||
parser.add_argument("-FormPath", "-Path", required=True, help="Path to Form.xml")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
parser.add_argument("-Offset", type=int, default=0, help="Line offset for pagination")
|
||||
parser.add_argument("-Expand", default="", help="Expand collapsed section by name, or * for all")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# form-validate v1.2 — Validate 1C managed form
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$FormPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -58,6 +59,20 @@ $nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# --- Detect context: config vs EPF/ERF ---
|
||||
# Walk up from FormPath looking for Configuration.xml → config context
|
||||
# No Configuration.xml → external data processor / report (EPF/ERF)
|
||||
$script:isConfigContext = $false
|
||||
$walkDir = Split-Path (Resolve-Path $FormPath) -Parent
|
||||
for ($i = 0; $i -lt 15; $i++) {
|
||||
if (-not $walkDir -or $walkDir -eq (Split-Path $walkDir)) { break }
|
||||
if (Test-Path (Join-Path $walkDir "Configuration.xml")) {
|
||||
$script:isConfigContext = $true
|
||||
break
|
||||
}
|
||||
$walkDir = Split-Path $walkDir
|
||||
}
|
||||
|
||||
# --- Counters ---
|
||||
|
||||
$errors = 0
|
||||
@@ -112,10 +127,10 @@ if ($root.LocalName -ne "Form") {
|
||||
Report-Error "Root element is '$($root.LocalName)', expected 'Form'"
|
||||
} else {
|
||||
$version = $root.GetAttribute("version")
|
||||
if ($version -eq "2.17") {
|
||||
if ($version -eq "2.17" -or $version -eq "2.20") {
|
||||
Report-OK "Root element: Form version=$version"
|
||||
} elseif ($version) {
|
||||
Report-Warn "Form version='$version' (expected 2.17)"
|
||||
Report-Warn "Form version='$version' (expected 2.17 or 2.20)"
|
||||
} else {
|
||||
Report-Warn "Form version attribute missing"
|
||||
}
|
||||
@@ -696,6 +711,7 @@ $validCfgPrefixes = @(
|
||||
"ChartOfCharacteristicTypesObject","ChartOfCharacteristicTypesRef"
|
||||
"ConstantsSet","DataProcessorObject","DocumentObject","DocumentRef"
|
||||
"DynamicList","EnumRef","ExchangePlanObject","ExchangePlanRef"
|
||||
"ExternalDataProcessorObject","ExternalReportObject"
|
||||
"InformationRegisterRecordManager","InformationRegisterRecordSet"
|
||||
"ReportObject","TaskObject","TaskRef"
|
||||
)
|
||||
@@ -719,7 +735,15 @@ if (-not $stopped) {
|
||||
$cfgVal = $Matches[1]
|
||||
if ($cfgVal -eq "DynamicList") { continue }
|
||||
if ($cfgVal -match '^([^.]+)\.') {
|
||||
if ($Matches[1] -in $validCfgPrefixes) { continue }
|
||||
$pfx = $Matches[1]
|
||||
if ($pfx -in $validCfgPrefixes) {
|
||||
# ExternalDataProcessorObject/ExternalReportObject valid only for EPF/ERF, not config
|
||||
if ($script:isConfigContext -and ($pfx -eq "ExternalDataProcessorObject" -or $pfx -eq "ExternalReportObject")) {
|
||||
Report-Error "12. Type '$tv': External* type in configuration context (use DataProcessorObject/ReportObject instead)"
|
||||
$typeOk = $false; $typeInvalid++
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
Report-Warn "12. Type '$tv': unrecognized cfg prefix"
|
||||
$typeOk = $false
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.2 — Validate 1C managed form
|
||||
# form-validate v1.4 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -43,6 +43,7 @@ VALID_CFG_PREFIXES = {
|
||||
'ChartOfCharacteristicTypesObject', 'ChartOfCharacteristicTypesRef',
|
||||
'ConstantsSet', 'DataProcessorObject', 'DocumentObject', 'DocumentRef',
|
||||
'DynamicList', 'EnumRef', 'ExchangePlanObject', 'ExchangePlanRef',
|
||||
'ExternalDataProcessorObject', 'ExternalReportObject',
|
||||
'InformationRegisterRecordManager', 'InformationRegisterRecordSet',
|
||||
'ReportObject', 'TaskObject', 'TaskRef',
|
||||
}
|
||||
@@ -56,7 +57,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Validate 1C managed form", allow_abbrev=False)
|
||||
parser.add_argument("-FormPath", required=True)
|
||||
parser.add_argument("-FormPath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
args = parser.parse_args()
|
||||
@@ -103,6 +104,18 @@ def main():
|
||||
|
||||
root = tree.getroot()
|
||||
|
||||
# Detect context: config vs EPF/ERF
|
||||
is_config_context = False
|
||||
walk_dir = os.path.dirname(os.path.abspath(form_path))
|
||||
for _ in range(15):
|
||||
parent = os.path.dirname(walk_dir)
|
||||
if parent == walk_dir:
|
||||
break
|
||||
if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')):
|
||||
is_config_context = True
|
||||
break
|
||||
walk_dir = parent
|
||||
|
||||
errors = 0
|
||||
warnings = 0
|
||||
ok_count = 0
|
||||
@@ -148,10 +161,10 @@ def main():
|
||||
report_error(f"Root element is '{localname(root)}', expected 'Form'")
|
||||
else:
|
||||
version = root.get("version", "")
|
||||
if version == "2.17":
|
||||
if version in ("2.17", "2.20"):
|
||||
report_ok(f"Root element: Form version={version}")
|
||||
elif version:
|
||||
report_warn(f"Form version='{version}' (expected 2.17)")
|
||||
report_warn(f"Form version='{version}' (expected 2.17 or 2.20)")
|
||||
else:
|
||||
report_warn("Form version attribute missing")
|
||||
|
||||
@@ -645,7 +658,10 @@ def main():
|
||||
suffix = tv[4:] # after "cfg:"
|
||||
prefix = suffix.split(".")[0]
|
||||
if prefix in VALID_CFG_PREFIXES or suffix == "DynamicList":
|
||||
pass # OK
|
||||
# ExternalDataProcessorObject/ExternalReportObject valid only in EPF/ERF context
|
||||
if is_config_context and prefix in ('ExternalDataProcessorObject', 'ExternalReportObject'):
|
||||
report_error(f'12. Type "{tv}": External* type in configuration context (use DataProcessorObject/ReportObject instead)')
|
||||
type_invalid += 1
|
||||
else:
|
||||
report_warn(f'12. Type "{tv}": unrecognized cfg prefix')
|
||||
type_warn_count += 1
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# help-add v1.2 — Add built-in help to 1C object
|
||||
# help-add v1.3 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -11,6 +11,25 @@ param(
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
$objectDir = Join-Path $SrcDir $ObjectName
|
||||
@@ -35,7 +54,7 @@ $encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
$helpXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="$formatVersion">
|
||||
<Page>$Lang</Page>
|
||||
</Help>
|
||||
"@
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-help v1.2 — Add built-in help to 1C object
|
||||
# add-help v1.3 — Add built-in help to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
from lxml import etree
|
||||
@@ -11,6 +12,22 @@ from lxml import etree
|
||||
NSMAP = {"md": "http://v8.1c.ru/8.3/MDClasses"}
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 save_xml_with_bom(tree, path):
|
||||
"""Save XML tree to file with UTF-8 BOM."""
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
@@ -41,6 +58,8 @@ def main():
|
||||
lang = args.Lang
|
||||
src_dir = args.SrcDir
|
||||
|
||||
format_version = detect_format_version(os.path.abspath(src_dir))
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
object_dir = os.path.join(src_dir, object_name)
|
||||
@@ -62,7 +81,7 @@ def main():
|
||||
'<Help xmlns="http://v8.1c.ru/8.3/xcf/extrnprops"'
|
||||
' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
' version="2.17">\n'
|
||||
f' version="{format_version}">\n'
|
||||
f'\t<Page>{lang}</Page>\n'
|
||||
'</Help>'
|
||||
)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: img-grid
|
||||
description: Наложить пронумерованную сетку на изображение для определения пропорций колонок
|
||||
description: Наложить пронумерованную сетку на изображение. Используй при анализе скриншота макета или печатной формы — измерить пропорции колонок перед генерацией табличного документа
|
||||
argument-hint: <ImagePath> [-c COLS]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# interface-edit v1.2 — Edit 1C CommandInterface.xml
|
||||
# interface-edit v1.3 — Edit 1C CommandInterface.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$CIPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||
[string]$DefinitionFile,
|
||||
[ValidateSet("hide","show","place","order","subsystem-order","group-order")]
|
||||
[string]$Operation,
|
||||
@@ -23,6 +23,25 @@ if (-not [System.IO.Path]::IsPathRooted($CIPath)) {
|
||||
}
|
||||
$resolvedPath = $CIPath
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion ([System.IO.Path]::GetDirectoryName($CIPath))
|
||||
|
||||
# --- Namespaces ---
|
||||
$script:ciNs = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
||||
$script:xrNs = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
@@ -42,7 +61,7 @@ if (-not (Test-Path $CIPath)) {
|
||||
xmlns:xr="$($script:xrNs)"
|
||||
xmlns:xs="$($script:xsNs)"
|
||||
xmlns:xsi="$($script:xsiNs)"
|
||||
version="2.17">
|
||||
version="$formatVersion">
|
||||
</CommandInterface>
|
||||
"@
|
||||
$utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
@@ -1,14 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
# interface-edit v1.2 — Edit 1C CommandInterface.xml
|
||||
# interface-edit v1.3 — Edit 1C CommandInterface.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from lxml import etree
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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"
|
||||
|
||||
|
||||
CI_NS = "http://v8.1c.ru/8.3/xcf/extrnprops"
|
||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
@@ -165,7 +182,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Edit 1C CommandInterface.xml", allow_abbrev=False)
|
||||
parser.add_argument("-CIPath", required=True)
|
||||
parser.add_argument("-CIPath", "-Path", required=True)
|
||||
parser.add_argument("-DefinitionFile", default=None)
|
||||
parser.add_argument("-Operation", default=None, choices=["hide", "show", "place", "order", "subsystem-order", "group-order"])
|
||||
parser.add_argument("-Value", default=None)
|
||||
@@ -181,6 +198,10 @@ def main():
|
||||
print("Either -DefinitionFile or -Operation is required", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Detect format version ---
|
||||
ci_dir = os.path.dirname(os.path.abspath(args.CIPath))
|
||||
format_version = detect_format_version(ci_dir)
|
||||
|
||||
# --- Resolve path ---
|
||||
ci_path = args.CIPath
|
||||
if not os.path.isabs(ci_path):
|
||||
@@ -199,7 +220,7 @@ def main():
|
||||
f'\txmlns:xr="{XR_NS}"\n'
|
||||
f'\txmlns:xs="{XS_NS}"\n'
|
||||
f'\txmlns:xsi="{XSI_NS}"\n'
|
||||
f'\tversion="2.17">\n'
|
||||
f'\tversion="{format_version}">\n'
|
||||
f'</CommandInterface>'
|
||||
)
|
||||
with open(ci_path, "w", encoding="utf-8-sig") as fh:
|
||||
@@ -483,7 +504,7 @@ def main():
|
||||
if os.path.isfile(validate_script):
|
||||
print()
|
||||
print("--- Running interface-validate ---")
|
||||
subprocess.run([sys.executable, validate_script, "-CIPath", resolved_path])
|
||||
subprocess.run([sys.executable, validate_script, "-CIPath", "-Path", resolved_path])
|
||||
|
||||
# --- Summary ---
|
||||
print()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# interface-validate v1.1 — Validate 1C CommandInterface.xml structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$CIPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$CIPath,
|
||||
[switch]$Detailed,
|
||||
[int]$MaxErrors = 30,
|
||||
[string]$OutFile
|
||||
|
||||
@@ -79,7 +79,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C CommandInterface.xml structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-CIPath', dest='CIPath', required=True)
|
||||
parser.add_argument('-CIPath', '-Path', dest='CIPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: meta-compile
|
||||
description: Создать объект метаданных 1С. Используй когда пользователь просит создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
|
||||
description: Создать объект метаданных 1С. Используй когда нужно создать или добавить справочник, документ, регистр, перечисление, константу, общий модуль, обработку, отчёт и др.
|
||||
argument-hint: <JsonPath> <OutputDir>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -6,13 +6,20 @@
|
||||
|-----------|----------|-------------|
|
||||
| `hierarchical` | `false` | Hierarchical |
|
||||
| `hierarchyType` | `HierarchyFoldersAndItems` | HierarchyType |
|
||||
| `limitLevelCount` | `false` | LimitLevelCount |
|
||||
| `levelCount` | `2` | LevelCount |
|
||||
| `foldersOnTop` | `true` | FoldersOnTop |
|
||||
| `codeLength` | `9` | CodeLength |
|
||||
| `codeType` | `String` | CodeType |
|
||||
| `codeAllowedLength` | `Variable` | CodeAllowedLength |
|
||||
| `codeSeries` | `WholeCatalog` | CodeSeries |
|
||||
| `descriptionLength` | `25` | DescriptionLength |
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `checkUnique` | `false` | CheckUnique |
|
||||
| `defaultPresentation` | `AsDescription` | DefaultPresentation |
|
||||
| `subordinationUse` | `ToItems` | SubordinationUse |
|
||||
| `quickChoice` | `true` | QuickChoice |
|
||||
| `choiceMode` | `BothWays` | ChoiceMode |
|
||||
| `owners` | `[]` | Owners |
|
||||
| `attributes` | `[]` | → Attribute в ChildObjects |
|
||||
| `tabularSections` | `{}` | → TabularSection в ChildObjects |
|
||||
|
||||
@@ -156,15 +156,15 @@
|
||||
| `descriptionLength` | `25` | DescriptionLength |
|
||||
| `autonumbering` | `true` | Autonumbering |
|
||||
| `checkUnique` | `false` | CheckUnique |
|
||||
| `dependenceOnCalculationTypes` | `NotUsed` | DependenceOnCalculationTypes |
|
||||
| `dependenceOnCalculationTypes` | `DontUse` | DependenceOnCalculationTypes |
|
||||
| `actionPeriodUse` | `false` | ActionPeriodUse |
|
||||
| `attributes` | `[]` | → Attribute |
|
||||
| `tabularSections` | `{}` | → TabularSection |
|
||||
|
||||
`dependenceOnCalculationTypes`: `NotUsed`, `ExclusionAndDependence`, `ExclusionOnly`.
|
||||
`dependenceOnCalculationTypes`: `DontUse`, `OnActionPeriod`.
|
||||
|
||||
```json
|
||||
{ "type": "ChartOfCalculationTypes", "name": "Начисления", "dependenceOnCalculationTypes": "ExclusionAndDependence" }
|
||||
{ "type": "ChartOfCalculationTypes", "name": "Начисления", "dependenceOnCalculationTypes": "OnActionPeriod" }
|
||||
```
|
||||
|
||||
## Зависимости
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-compile v1.5 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.10 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -87,8 +87,8 @@ $script:enumValueAliases = @{
|
||||
"RecordSubordinate" = "RecorderSubordinate"; "Subordinate" = "RecorderSubordinate"
|
||||
"ПодчинениеРегистратору" = "RecorderSubordinate"; "Независимый" = "Independent"
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"
|
||||
"Depend" = "RequireCalculationTypes"; "RequireCalculation" = "RequireCalculationTypes"
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"; "NotUsed" = "DontUse"
|
||||
"Depend" = "OnActionPeriod"; "ПоПериодуДействия" = "OnActionPeriod"
|
||||
# InformationRegisterPeriodicity
|
||||
"None" = "Nonperiodical"; "Daily" = "Day"; "Monthly" = "Month"
|
||||
"Quarterly" = "Quarter"; "Yearly" = "Year"
|
||||
@@ -117,7 +117,7 @@ $script:validEnumValues = @{
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","RequireCalculationTypes")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","OnActionPeriod")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
@@ -136,22 +136,28 @@ $script:validEnumValues = @{
|
||||
"ReuseSessions" = @("DontUse","AutoUse")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"SubordinationUse" = @("ToItems","ToFolders","ToFoldersAndItems")
|
||||
"CodeSeries" = @("WholeCatalog","WithinSubordination")
|
||||
"ChoiceMode" = @("BothWays","QuickChoice","FromForm")
|
||||
}
|
||||
|
||||
function Normalize-EnumValue {
|
||||
param([string]$propName, [string]$value)
|
||||
# 1. Check alias dictionary
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if ($script:enumValueAliases.ContainsKey($value)) {
|
||||
return $script:enumValueAliases[$value]
|
||||
}
|
||||
# 2. Case-insensitive match against valid values
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
$valid = $script:validEnumValues[$propName]
|
||||
if ($valid) {
|
||||
foreach ($v in $valid) {
|
||||
if ($v -ieq $value) { return $v }
|
||||
}
|
||||
# 3. Known property, unknown value — error with hint
|
||||
Write-Error "Invalid value '$value' for property '$propName'. Valid values: $($valid -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
# 3. Return as-is (validator will catch if wrong)
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return $value
|
||||
}
|
||||
|
||||
@@ -495,6 +501,7 @@ function Parse-AttributeShorthand {
|
||||
flags = @(if ($val.flags) { $val.flags } else { @() })
|
||||
fillChecking = if ($val.fillChecking) { "$($val.fillChecking)" } else { "" }
|
||||
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
|
||||
multiLine = if ($val.multiLine -eq $true) { $true } else { $false }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -757,7 +764,8 @@ function Emit-Attribute {
|
||||
param([string]$indent, $parsed, [string]$context)
|
||||
# $context: "catalog", "document", "object", "processor", "tabular", "processor-tabular", "register"
|
||||
$attrName = $parsed.name
|
||||
if ($script:reservedAttrNames.ContainsKey($attrName) -or $script:reservedAttrNames.ContainsValue($attrName)) {
|
||||
if ($context -notin @("tabular", "processor-tabular") -and
|
||||
($script:reservedAttrNames.ContainsKey($attrName) -or $script:reservedAttrNames.ContainsValue($attrName))) {
|
||||
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C."
|
||||
}
|
||||
$uuid = New-Guid-String
|
||||
@@ -784,18 +792,20 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
|
||||
# FillFromFillingValue — not for tabular/processor (non-stored objects don't have these)
|
||||
if ($context -notin @("tabular", "processor")) {
|
||||
# FillFromFillingValue — not for tabular/processor/chart/register-other
|
||||
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
|
||||
if ($context -notin @("tabular", "processor", "chart", "register-other")) {
|
||||
X "$indent`t`t<FillFromFillingValue>false</FillFromFillingValue>"
|
||||
}
|
||||
|
||||
# FillValue — not for tabular/processor
|
||||
if ($context -notin @("tabular", "processor")) {
|
||||
# FillValue — same restriction
|
||||
if ($context -notin @("tabular", "processor", "chart", "register-other")) {
|
||||
Emit-FillValue "$indent`t`t" $typeStr
|
||||
}
|
||||
|
||||
@@ -828,7 +838,10 @@ function Emit-Attribute {
|
||||
X "$indent`t`t<Indexing>$indexing</Indexing>"
|
||||
|
||||
X "$indent`t`t<FullTextSearch>Use</FullTextSearch>"
|
||||
X "$indent`t`t<DataHistory>Use</DataHistory>"
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if ($context -notin @("chart", "register-other")) {
|
||||
X "$indent`t`t<DataHistory>Use</DataHistory>"
|
||||
}
|
||||
}
|
||||
|
||||
X "$indent`t</Properties>"
|
||||
@@ -924,7 +937,8 @@ function Emit-Dimension {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
@@ -1017,7 +1031,8 @@ function Emit-Resource {
|
||||
X "$indent`t`t<ToolTip/>"
|
||||
X "$indent`t`t<MarkNegatives>false</MarkNegatives>"
|
||||
X "$indent`t`t<Mask/>"
|
||||
X "$indent`t`t<MultiLine>false</MultiLine>"
|
||||
$multiLine = if ($parsed.multiLine -eq $true -or $parsed.flags -contains "multiline") { "true" } else { "false" }
|
||||
X "$indent`t`t<MultiLine>$multiLine</MultiLine>"
|
||||
X "$indent`t`t<ExtendedEdit>false</ExtendedEdit>"
|
||||
X "$indent`t`t<MinValue xsi:nil=`"true`"/>"
|
||||
X "$indent`t`t<MaxValue xsi:nil=`"true`"/>"
|
||||
@@ -1071,12 +1086,25 @@ function Emit-CatalogProperties {
|
||||
$hierarchyType = Get-EnumProp "HierarchyType" "hierarchyType" "HierarchyFoldersAndItems"
|
||||
X "$i<Hierarchical>$hierarchical</Hierarchical>"
|
||||
X "$i<HierarchyType>$hierarchyType</HierarchyType>"
|
||||
X "$i<LimitLevelCount>false</LimitLevelCount>"
|
||||
X "$i<LevelCount>2</LevelCount>"
|
||||
X "$i<FoldersOnTop>true</FoldersOnTop>"
|
||||
$limitLevelCount = if ($def.limitLevelCount -eq $true) { "true" } else { "false" }
|
||||
$levelCount = if ($null -ne $def.levelCount) { "$($def.levelCount)" } else { "2" }
|
||||
$foldersOnTop = if ($def.foldersOnTop -eq $false) { "false" } else { "true" }
|
||||
X "$i<LimitLevelCount>$limitLevelCount</LimitLevelCount>"
|
||||
X "$i<LevelCount>$levelCount</LevelCount>"
|
||||
X "$i<FoldersOnTop>$foldersOnTop</FoldersOnTop>"
|
||||
X "$i<UseStandardCommands>true</UseStandardCommands>"
|
||||
X "$i<Owners/>"
|
||||
X "$i<SubordinationUse>ToItems</SubordinationUse>"
|
||||
if ($def.owners -and $def.owners.Count -gt 0) {
|
||||
X "$i<Owners>"
|
||||
foreach ($ownerRef in $def.owners) {
|
||||
$fullRef = if ("$ownerRef" -match '\.') { "$ownerRef" } else { "Catalog.$ownerRef" }
|
||||
X "$i`t<xr:Item xsi:type=`"xr:MDObjectRef`">$fullRef</xr:Item>"
|
||||
}
|
||||
X "$i</Owners>"
|
||||
} else {
|
||||
X "$i<Owners/>"
|
||||
}
|
||||
$subordinationUse = Get-EnumProp "SubordinationUse" "subordinationUse" "ToItems"
|
||||
X "$i<SubordinationUse>$subordinationUse</SubordinationUse>"
|
||||
|
||||
$codeLength = if ($null -ne $def.codeLength) { "$($def.codeLength)" } else { "9" }
|
||||
$descriptionLength = if ($null -ne $def.descriptionLength) { "$($def.descriptionLength)" } else { "25" }
|
||||
@@ -1089,7 +1117,8 @@ function Emit-CatalogProperties {
|
||||
X "$i<DescriptionLength>$descriptionLength</DescriptionLength>"
|
||||
X "$i<CodeType>$codeType</CodeType>"
|
||||
X "$i<CodeAllowedLength>$codeAllowedLength</CodeAllowedLength>"
|
||||
X "$i<CodeSeries>WholeCatalog</CodeSeries>"
|
||||
$codeSeries = Get-EnumProp "CodeSeries" "codeSeries" "WholeCatalog"
|
||||
X "$i<CodeSeries>$codeSeries</CodeSeries>"
|
||||
X "$i<CheckUnique>$checkUnique</CheckUnique>"
|
||||
X "$i<Autonumbering>$autonumbering</Autonumbering>"
|
||||
|
||||
@@ -1100,8 +1129,10 @@ function Emit-CatalogProperties {
|
||||
X "$i<Characteristics/>"
|
||||
X "$i<PredefinedDataUpdate>Auto</PredefinedDataUpdate>"
|
||||
X "$i<EditType>InDialog</EditType>"
|
||||
X "$i<QuickChoice>true</QuickChoice>"
|
||||
X "$i<ChoiceMode>BothWays</ChoiceMode>"
|
||||
$quickChoice = if ($def.quickChoice -eq $false) { "false" } else { "true" }
|
||||
$choiceMode = Get-EnumProp "ChoiceMode" "choiceMode" "BothWays"
|
||||
X "$i<QuickChoice>$quickChoice</QuickChoice>"
|
||||
X "$i<ChoiceMode>$choiceMode</ChoiceMode>"
|
||||
X "$i<InputByString>"
|
||||
X "$i`t<xr:Field>Catalog.$objName.StandardAttribute.Description</xr:Field>"
|
||||
X "$i`t<xr:Field>Catalog.$objName.StandardAttribute.Code</xr:Field>"
|
||||
@@ -2537,13 +2568,32 @@ function Emit-AddressingAttribute {
|
||||
|
||||
$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"'
|
||||
|
||||
# --- 14a. Detect format version from existing Configuration.xml ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$script:formatVersion = Detect-FormatVersion $OutputDir
|
||||
|
||||
# --- 15. Main assembler ---
|
||||
|
||||
$uuid = New-Guid-String
|
||||
|
||||
# XML declaration
|
||||
X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X "<MetaDataObject $($script:xmlnsDecl) version=`"2.17`">"
|
||||
X "<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">"
|
||||
X "`t<$objType uuid=`"$uuid`">"
|
||||
|
||||
# InternalInfo
|
||||
@@ -2633,6 +2683,7 @@ if ($objType -in $typesWithAttrTS) {
|
||||
"Catalog" { "catalog" }
|
||||
"Document" { "document" }
|
||||
{ $_ -in @("DataProcessor","Report") } { "processor" }
|
||||
{ $_ -in @("ChartOfAccounts","ChartOfCharacteristicTypes","ChartOfCalculationTypes") } { "chart" }
|
||||
default { "object" }
|
||||
}
|
||||
foreach ($a in $attrs) {
|
||||
@@ -2643,10 +2694,12 @@ if ($objType -in $typesWithAttrTS) {
|
||||
Emit-TabularSection "`t`t`t" $tsName $columns $objType $objName
|
||||
}
|
||||
foreach ($af in $acctFlags) {
|
||||
Emit-AccountingFlag "`t`t`t" "$af"
|
||||
$afName = if ($af.name) { $af.name } else { "$af" }
|
||||
Emit-AccountingFlag "`t`t`t" $afName
|
||||
}
|
||||
foreach ($edf in $extDimFlags) {
|
||||
Emit-ExtDimensionAccountingFlag "`t`t`t" "$edf"
|
||||
$edfName = if ($edf.name) { $edf.name } else { "$edf" }
|
||||
Emit-ExtDimensionAccountingFlag "`t`t`t" $edfName
|
||||
}
|
||||
foreach ($aa in $addrAttrs) {
|
||||
Emit-AddressingAttribute "`t`t`t" $aa
|
||||
@@ -2709,8 +2762,11 @@ if ($objType -in @("InformationRegister","AccumulationRegister","AccountingRegis
|
||||
foreach ($d in $dims) {
|
||||
Emit-Dimension "`t`t`t" $d $objType
|
||||
}
|
||||
# InformationRegister.Attribute supports FillFromFillingValue/FillValue/DataHistory;
|
||||
# AccumulationRegister/AccountingRegister/CalculationRegister.Attribute do NOT.
|
||||
$regCtx = if ($objType -eq "InformationRegister") { "register-info" } else { "register-other" }
|
||||
foreach ($a in $regAttrs) {
|
||||
Emit-Attribute "`t`t`t" $a "register"
|
||||
Emit-Attribute "`t`t`t" $a $regCtx
|
||||
}
|
||||
X "`t`t</ChildObjects>"
|
||||
} else {
|
||||
@@ -2904,7 +2960,7 @@ if ($objType -eq "ExchangePlan") {
|
||||
$contentPath = Join-Path $extDir "Content.xml"
|
||||
if (-not (Test-Path $contentPath)) {
|
||||
Ensure-ExtDir
|
||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/xcf/extrnprops`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" version=`"2.17`"/>`r`n"
|
||||
$contentXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<ExchangePlanContent xmlns=`"http://v8.1c.ru/8.3/xcf/extrnprops`" xmlns:xr=`"http://v8.1c.ru/8.3/xcf/readable`" version=`"$($script:formatVersion)`"/>`r`n"
|
||||
[System.IO.File]::WriteAllText($contentPath, $contentXml, $enc)
|
||||
$modulesCreated += $contentPath
|
||||
}
|
||||
@@ -2913,7 +2969,7 @@ if ($objType -eq "BusinessProcess") {
|
||||
$flowchartPath = Join-Path $extDir "Flowchart.xml"
|
||||
if (-not (Test-Path $flowchartPath)) {
|
||||
Ensure-ExtDir
|
||||
$flowchartXml = "<?xml version=`"1.0`" encoding=`"UTF-8`"?>`r`n<Flowchart xmlns=`"http://v8.1c.ru/8.3/MDClasses`" version=`"2.17`"/>`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)
|
||||
$modulesCreated += $flowchartPath
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-compile v1.5 — Compile 1C metadata object from JSON
|
||||
# meta-compile v1.10 — Compile 1C metadata object from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -147,8 +147,8 @@ enum_value_aliases = {
|
||||
'RecordSubordinate': 'RecorderSubordinate', 'Subordinate': 'RecorderSubordinate',
|
||||
'ПодчинениеРегистратору': 'RecorderSubordinate', 'Независимый': 'Independent',
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse',
|
||||
'Depend': 'RequireCalculationTypes', 'RequireCalculation': 'RequireCalculationTypes',
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse', 'NotUsed': 'DontUse',
|
||||
'Depend': 'OnActionPeriod', 'ПоПериодуДействия': 'OnActionPeriod',
|
||||
# InformationRegisterPeriodicity
|
||||
'None': 'Nonperiodical', 'Daily': 'Day', 'Monthly': 'Month',
|
||||
'Quarterly': 'Quarter', 'Yearly': 'Year',
|
||||
@@ -177,7 +177,7 @@ valid_enum_values = {
|
||||
'RegisterType': ['Balance', 'Turnovers'],
|
||||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||||
'InformationRegisterPeriodicity': ['Nonperiodical', 'Second', 'Day', 'Month', 'Quarter', 'Year', 'RecorderPosition'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'RequireCalculationTypes'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'OnActionPeriod'],
|
||||
'DataLockControlMode': ['Automatic', 'Managed'],
|
||||
'FullTextSearch': ['Use', 'DontUse'],
|
||||
'DataHistory': ['Use', 'DontUse'],
|
||||
@@ -196,19 +196,25 @@ valid_enum_values = {
|
||||
'ReuseSessions': ['DontUse', 'AutoUse'],
|
||||
'FillChecking': ['DontCheck', 'ShowError', 'ShowWarning'],
|
||||
'Indexing': ['DontIndex', 'Index', 'IndexWithAdditionalOrder'],
|
||||
'SubordinationUse': ['ToItems', 'ToFolders', 'ToFoldersAndItems'],
|
||||
'CodeSeries': ['WholeCatalog', 'WithinSubordination'],
|
||||
'ChoiceMode': ['BothWays', 'QuickChoice', 'FromForm'],
|
||||
}
|
||||
|
||||
def normalize_enum_value(prop_name, value):
|
||||
# 1. Check alias dictionary
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if value in enum_value_aliases:
|
||||
return enum_value_aliases[value]
|
||||
# 2. Case-insensitive match against valid values
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
valid = valid_enum_values.get(prop_name)
|
||||
if valid:
|
||||
for v in valid:
|
||||
if v.lower() == value.lower():
|
||||
return v
|
||||
# 3. Return as-is (validator will catch if wrong)
|
||||
# 3. Known property, unknown value — error with hint
|
||||
print(f"Invalid value '{value}' for property '{prop_name}'. Valid values: {', '.join(valid)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return value
|
||||
|
||||
def get_enum_prop(prop_name, field_name, default):
|
||||
@@ -458,6 +464,7 @@ def parse_attribute_shorthand(val):
|
||||
'flags': list(val.get('flags', [])),
|
||||
'fillChecking': str(val['fillChecking']) if val.get('fillChecking') else '',
|
||||
'indexing': str(val['indexing']) if val.get('indexing') else '',
|
||||
'multiLine': True if val.get('multiLine') is True else False,
|
||||
}
|
||||
|
||||
def parse_enum_value_shorthand(val):
|
||||
@@ -722,7 +729,7 @@ RESERVED_ATTR_NAMES_RU = {
|
||||
|
||||
def emit_attribute(indent, parsed, context):
|
||||
attr_name = parsed['name']
|
||||
if attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU:
|
||||
if context not in ('tabular', 'processor-tabular') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU):
|
||||
print(f"WARNING: Attribute '{attr_name}' conflicts with a standard attribute name. This may cause errors when loading into 1C.", file=sys.stderr)
|
||||
uid = new_uuid()
|
||||
X(f'{indent}<Attribute uuid="{uid}">')
|
||||
@@ -743,13 +750,16 @@ def emit_attribute(indent, parsed, context):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
if context not in ('tabular', 'processor'):
|
||||
# FillFromFillingValue / FillValue — not for tabular/processor/chart/register-other
|
||||
# (Chart*, AccumulationRegister/AccountingRegister/CalculationRegister don't support these)
|
||||
if context not in ('tabular', 'processor', 'chart', 'register-other'):
|
||||
X(f'{indent}\t\t<FillFromFillingValue>false</FillFromFillingValue>')
|
||||
if context not in ('tabular', 'processor'):
|
||||
if context not in ('tabular', 'processor', 'chart', 'register-other'):
|
||||
emit_fill_value(f'{indent}\t\t', type_str)
|
||||
fill_checking = 'DontCheck'
|
||||
if 'req' in parsed.get('flags', []):
|
||||
@@ -777,7 +787,9 @@ def emit_attribute(indent, parsed, context):
|
||||
indexing = parsed['indexing']
|
||||
X(f'{indent}\t\t<Indexing>{indexing}</Indexing>')
|
||||
X(f'{indent}\t\t<FullTextSearch>Use</FullTextSearch>')
|
||||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||||
# DataHistory — not for Chart* types and non-InformationRegister register family
|
||||
if context not in ('chart', 'register-other'):
|
||||
X(f'{indent}\t\t<DataHistory>Use</DataHistory>')
|
||||
X(f'{indent}\t</Properties>')
|
||||
X(f'{indent}</Attribute>')
|
||||
|
||||
@@ -857,7 +869,8 @@ def emit_dimension(indent, parsed, register_type):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
@@ -930,7 +943,8 @@ def emit_resource(indent, parsed, register_type):
|
||||
X(f'{indent}\t\t<ToolTip/>')
|
||||
X(f'{indent}\t\t<MarkNegatives>false</MarkNegatives>')
|
||||
X(f'{indent}\t\t<Mask/>')
|
||||
X(f'{indent}\t\t<MultiLine>false</MultiLine>')
|
||||
multi_line = 'true' if (parsed.get('multiLine') is True or 'multiline' in parsed.get('flags', [])) else 'false'
|
||||
X(f'{indent}\t\t<MultiLine>{multi_line}</MultiLine>')
|
||||
X(f'{indent}\t\t<ExtendedEdit>false</ExtendedEdit>')
|
||||
X(f'{indent}\t\t<MinValue xsi:nil="true"/>')
|
||||
X(f'{indent}\t\t<MaxValue xsi:nil="true"/>')
|
||||
@@ -972,12 +986,24 @@ def emit_catalog_properties(indent):
|
||||
hierarchy_type = get_enum_prop('HierarchyType', 'hierarchyType', 'HierarchyFoldersAndItems')
|
||||
X(f'{i}<Hierarchical>{hierarchical}</Hierarchical>')
|
||||
X(f'{i}<HierarchyType>{hierarchy_type}</HierarchyType>')
|
||||
X(f'{i}<LimitLevelCount>false</LimitLevelCount>')
|
||||
X(f'{i}<LevelCount>2</LevelCount>')
|
||||
X(f'{i}<FoldersOnTop>true</FoldersOnTop>')
|
||||
limit_level_count = 'true' if defn.get('limitLevelCount') is True else 'false'
|
||||
level_count = str(defn['levelCount']) if defn.get('levelCount') is not None else '2'
|
||||
folders_on_top = 'false' if defn.get('foldersOnTop') is False else 'true'
|
||||
X(f'{i}<LimitLevelCount>{limit_level_count}</LimitLevelCount>')
|
||||
X(f'{i}<LevelCount>{level_count}</LevelCount>')
|
||||
X(f'{i}<FoldersOnTop>{folders_on_top}</FoldersOnTop>')
|
||||
X(f'{i}<UseStandardCommands>true</UseStandardCommands>')
|
||||
X(f'{i}<Owners/>')
|
||||
X(f'{i}<SubordinationUse>ToItems</SubordinationUse>')
|
||||
owners = defn.get('owners', [])
|
||||
if owners:
|
||||
X(f'{i}<Owners>')
|
||||
for owner_ref in owners:
|
||||
full_ref = owner_ref if '.' in str(owner_ref) else f'Catalog.{owner_ref}'
|
||||
X(f'{i}\t<xr:Item xsi:type="xr:MDObjectRef">{full_ref}</xr:Item>')
|
||||
X(f'{i}</Owners>')
|
||||
else:
|
||||
X(f'{i}<Owners/>')
|
||||
subordination_use = get_enum_prop('SubordinationUse', 'subordinationUse', 'ToItems')
|
||||
X(f'{i}<SubordinationUse>{subordination_use}</SubordinationUse>')
|
||||
code_length = str(defn['codeLength']) if defn.get('codeLength') is not None else '9'
|
||||
description_length = str(defn['descriptionLength']) if defn.get('descriptionLength') is not None else '25'
|
||||
code_type = get_enum_prop('CodeType', 'codeType', 'String')
|
||||
@@ -988,7 +1014,8 @@ def emit_catalog_properties(indent):
|
||||
X(f'{i}<DescriptionLength>{description_length}</DescriptionLength>')
|
||||
X(f'{i}<CodeType>{code_type}</CodeType>')
|
||||
X(f'{i}<CodeAllowedLength>{code_allowed_length}</CodeAllowedLength>')
|
||||
X(f'{i}<CodeSeries>WholeCatalog</CodeSeries>')
|
||||
code_series = get_enum_prop('CodeSeries', 'codeSeries', 'WholeCatalog')
|
||||
X(f'{i}<CodeSeries>{code_series}</CodeSeries>')
|
||||
X(f'{i}<CheckUnique>{check_unique}</CheckUnique>')
|
||||
X(f'{i}<Autonumbering>{autonumbering}</Autonumbering>')
|
||||
default_presentation = get_enum_prop('DefaultPresentation', 'defaultPresentation', 'AsDescription')
|
||||
@@ -997,8 +1024,10 @@ def emit_catalog_properties(indent):
|
||||
X(f'{i}<Characteristics/>')
|
||||
X(f'{i}<PredefinedDataUpdate>Auto</PredefinedDataUpdate>')
|
||||
X(f'{i}<EditType>InDialog</EditType>')
|
||||
X(f'{i}<QuickChoice>true</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>BothWays</ChoiceMode>')
|
||||
quick_choice = 'false' if defn.get('quickChoice') is False else 'true'
|
||||
choice_mode = get_enum_prop('ChoiceMode', 'choiceMode', 'BothWays')
|
||||
X(f'{i}<QuickChoice>{quick_choice}</QuickChoice>')
|
||||
X(f'{i}<ChoiceMode>{choice_mode}</ChoiceMode>')
|
||||
X(f'{i}<InputByString>')
|
||||
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Description</xr:Field>')
|
||||
X(f'{i}\t<xr:Field>Catalog.{obj_name}.StandardAttribute.Code</xr:Field>')
|
||||
@@ -2200,6 +2229,27 @@ def emit_addressing_attribute(indent, addr_def):
|
||||
|
||||
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"'
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 14a. Detect format version from existing Configuration.xml
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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"
|
||||
|
||||
format_version = detect_format_version(output_dir)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# 15. Main assembler
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -2207,7 +2257,7 @@ xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8
|
||||
obj_uuid = new_uuid()
|
||||
|
||||
X('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
X(f'<MetaDataObject {xmlns_decl} version="2.17">')
|
||||
X(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
|
||||
X(f'\t<{obj_type} uuid="{obj_uuid}">')
|
||||
|
||||
# InternalInfo
|
||||
@@ -2305,6 +2355,8 @@ if obj_type in types_with_attr_ts:
|
||||
context = 'document'
|
||||
elif obj_type in ('DataProcessor', 'Report'):
|
||||
context = 'processor'
|
||||
elif obj_type in ('ChartOfAccounts', 'ChartOfCharacteristicTypes', 'ChartOfCalculationTypes'):
|
||||
context = 'chart'
|
||||
else:
|
||||
context = 'object'
|
||||
for a in attrs:
|
||||
@@ -2313,9 +2365,11 @@ if obj_type in types_with_attr_ts:
|
||||
columns = ts_sections[ts_name]
|
||||
emit_tabular_section('\t\t\t', ts_name, columns, obj_type, obj_name)
|
||||
for af in acct_flags:
|
||||
emit_accounting_flag('\t\t\t', str(af))
|
||||
af_name = af['name'] if isinstance(af, dict) else str(af)
|
||||
emit_accounting_flag('\t\t\t', af_name)
|
||||
for edf in ext_dim_flags:
|
||||
emit_ext_dimension_accounting_flag('\t\t\t', str(edf))
|
||||
edf_name = edf['name'] if isinstance(edf, dict) else str(edf)
|
||||
emit_ext_dimension_accounting_flag('\t\t\t', edf_name)
|
||||
for aa in addr_attrs:
|
||||
emit_addressing_attribute('\t\t\t', aa)
|
||||
X('\t\t</ChildObjects>')
|
||||
@@ -2360,8 +2414,11 @@ if obj_type in ('InformationRegister', 'AccumulationRegister', 'AccountingRegist
|
||||
emit_resource('\t\t\t', r, obj_type)
|
||||
for d in dims:
|
||||
emit_dimension('\t\t\t', d, obj_type)
|
||||
# InformationRegister.Attribute supports FillFromFillingValue/FillValue/DataHistory;
|
||||
# AccumulationRegister/AccountingRegister/CalculationRegister.Attribute do NOT.
|
||||
reg_ctx = 'register-info' if obj_type == 'InformationRegister' else 'register-other'
|
||||
for a in reg_attrs:
|
||||
emit_attribute('\t\t\t', a, 'register')
|
||||
emit_attribute('\t\t\t', a, reg_ctx)
|
||||
X('\t\t</ChildObjects>')
|
||||
else:
|
||||
X('\t\t<ChildObjects/>')
|
||||
@@ -2525,7 +2582,7 @@ if obj_type == 'ExchangePlan':
|
||||
content_path = os.path.join(ext_dir, 'Content.xml')
|
||||
if not os.path.isfile(content_path):
|
||||
ensure_ext_dir()
|
||||
content_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" version="2.17"/>\r\n'
|
||||
content_xml = f'<?xml version="1.0" encoding="UTF-8"?>\r\n<ExchangePlanContent xmlns="http://v8.1c.ru/8.3/xcf/extrnprops" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" version="{format_version}"/>\r\n'
|
||||
write_utf8_bom(content_path, content_xml)
|
||||
modules_created.append(content_path)
|
||||
|
||||
@@ -2533,7 +2590,7 @@ if obj_type == 'BusinessProcess':
|
||||
flowchart_path = os.path.join(ext_dir, 'Flowchart.xml')
|
||||
if not os.path.isfile(flowchart_path):
|
||||
ensure_ext_dir()
|
||||
flowchart_xml = '<?xml version="1.0" encoding="UTF-8"?>\r\n<Flowchart xmlns="http://v8.1c.ru/8.3/MDClasses" version="2.17"/>\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)
|
||||
modules_created.append(flowchart_path)
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
# meta-edit v1.5 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# meta-edit v1.6 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ObjectPath,
|
||||
|
||||
# Inline mode (alternative to DefinitionFile)
|
||||
@@ -48,47 +49,65 @@ $script:enumValueAliases = @{
|
||||
"Balances" = "Balance"; "Остатки" = "Balance"; "Обороты" = "Turnovers"
|
||||
"RecordSubordinate" = "RecorderSubordinate"; "Subordinate" = "RecorderSubordinate"
|
||||
"ПодчинениеРегистратору" = "RecorderSubordinate"; "Независимый" = "Independent"
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"
|
||||
"NotDependOnCalculationTypes" = "DontUse"; "NoDependence" = "DontUse"; "NotUsed" = "DontUse"
|
||||
"Depend" = "OnActionPeriod"; "ПоПериодуДействия" = "OnActionPeriod"
|
||||
"None" = "Nonperiodical"; "Daily" = "Day"; "Monthly" = "Month"
|
||||
"Quarterly" = "Quarter"; "Yearly" = "Year"
|
||||
"Непериодический" = "Nonperiodical"; "День" = "Day"; "Месяц" = "Month"
|
||||
"Непериодический" = "Nonperiodical"; "Секунда" = "Second"; "День" = "Day"; "Месяц" = "Month"
|
||||
"Квартал" = "Quarter"; "Год" = "Year"
|
||||
"ПозицияРегистратора" = "RecorderPosition"
|
||||
"Автоматический" = "Automatic"; "Управляемый" = "Managed"
|
||||
"Использовать" = "Use"; "НеИспользовать" = "DontUse"
|
||||
"Разрешить" = "Allow"; "Запретить" = "Deny"
|
||||
"ВДиалоге" = "InDialog"; "ВСписке" = "InList"; "ОбаСпособа" = "BothWays"
|
||||
"ВВидеНаименования" = "AsDescription"; "ВВидеКода" = "AsCode"
|
||||
"НеПроверять" = "DontCheck"; "Ошибка" = "ShowError"; "Предупреждение" = "ShowWarning"
|
||||
"НеИндексировать" = "DontIndex"; "Индексировать" = "Index"
|
||||
"ИндексироватьСДопУпорядочиванием" = "IndexWithAdditionalOrder"
|
||||
}
|
||||
|
||||
$script:validEnumValues = @{
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"RegisterType" = @("Balance","Turnovers")
|
||||
"WriteMode" = @("Independent","RecorderSubordinate")
|
||||
"InformationRegisterPeriodicity" = @("Nonperiodical","Second","Day","Month","Quarter","Year","RecorderPosition")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","RequireCalculationTypes")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
"DefaultPresentation" = @("AsDescription","AsCode")
|
||||
"Posting" = @("Allow","Deny")
|
||||
"RealTimePosting" = @("Allow","Deny")
|
||||
"EditType" = @("InDialog","InList","BothWays")
|
||||
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","OnActionPeriod")
|
||||
"DataLockControlMode" = @("Automatic","Managed")
|
||||
"FullTextSearch" = @("Use","DontUse")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
"DefaultPresentation" = @("AsDescription","AsCode")
|
||||
"Posting" = @("Allow","Deny")
|
||||
"RealTimePosting" = @("Allow","Deny")
|
||||
"EditType" = @("InDialog","InList","BothWays")
|
||||
"HierarchyType" = @("HierarchyFoldersAndItems","HierarchyItemsOnly")
|
||||
"CodeType" = @("String","Number")
|
||||
"CodeAllowedLength" = @("Variable","Fixed")
|
||||
"NumberType" = @("String","Number")
|
||||
"NumberAllowedLength" = @("Variable","Fixed")
|
||||
"RegisterRecordsDeletion" = @("AutoDelete","AutoDeleteOnUnpost","AutoDeleteOff")
|
||||
"RegisterRecordsWritingOnPost" = @("WriteModified","WriteSelected","WriteAll")
|
||||
"ReturnValuesReuse" = @("DontUse","DuringRequest","DuringSession")
|
||||
"ReuseSessions" = @("DontUse","AutoUse")
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
}
|
||||
|
||||
function Normalize-EnumValue {
|
||||
param([string]$propName, [string]$value)
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if ($script:enumValueAliases.ContainsKey($value)) {
|
||||
return $script:enumValueAliases[$value]
|
||||
}
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
$valid = $script:validEnumValues[$propName]
|
||||
if ($valid) {
|
||||
foreach ($v in $valid) {
|
||||
if ($v -ieq $value) { return $v }
|
||||
}
|
||||
# 3. Known property, unknown value — error with hint
|
||||
Write-Error "Invalid value '$value' for property '$propName'. Valid values: $($valid -join ', ')"
|
||||
exit 1
|
||||
}
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return $value
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-edit v1.5 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# meta-edit v1.6 — Edit existing 1C metadata object XML (inline mode + complex properties + TS attribute ops + modify-ts)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -88,8 +88,8 @@ enum_value_aliases = {
|
||||
'RecordSubordinate': 'RecorderSubordinate', 'Subordinate': 'RecorderSubordinate',
|
||||
'ПодчинениеРегистратору': 'RecorderSubordinate', 'Независимый': 'Independent',
|
||||
# DependenceOnCalculationTypes (ChartOfCalculationTypes)
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse',
|
||||
'Depend': 'RequireCalculationTypes', 'RequireCalculation': 'RequireCalculationTypes',
|
||||
'NotDependOnCalculationTypes': 'DontUse', 'NoDependence': 'DontUse', 'NotUsed': 'DontUse',
|
||||
'Depend': 'OnActionPeriod', 'ПоПериодуДействия': 'OnActionPeriod',
|
||||
# InformationRegisterPeriodicity
|
||||
'None': 'Nonperiodical', 'Daily': 'Day', 'Monthly': 'Month',
|
||||
'Quarterly': 'Quarter', 'Yearly': 'Year',
|
||||
@@ -117,7 +117,7 @@ valid_enum_values = {
|
||||
'RegisterType': ['Balance', 'Turnovers'],
|
||||
'WriteMode': ['Independent', 'RecorderSubordinate'],
|
||||
'InformationRegisterPeriodicity': ['Nonperiodical', 'Second', 'Day', 'Month', 'Quarter', 'Year', 'RecorderPosition'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'RequireCalculationTypes'],
|
||||
'DependenceOnCalculationTypes': ['DontUse', 'OnActionPeriod'],
|
||||
'DataLockControlMode': ['Automatic', 'Managed'],
|
||||
'FullTextSearch': ['Use', 'DontUse'],
|
||||
'DataHistory': ['Use', 'DontUse'],
|
||||
@@ -140,16 +140,19 @@ valid_enum_values = {
|
||||
|
||||
|
||||
def normalize_enum_value(prop_name, value):
|
||||
# 1. Check alias dictionary
|
||||
# 1. Check alias dictionary — silent auto-correct
|
||||
if value in enum_value_aliases:
|
||||
return enum_value_aliases[value]
|
||||
# 2. Case-insensitive match against valid values
|
||||
# 2. Case-insensitive match against valid values — silent
|
||||
valid = valid_enum_values.get(prop_name)
|
||||
if valid:
|
||||
for v in valid:
|
||||
if v.lower() == value.lower():
|
||||
return v
|
||||
# 3. Return as-is (validator will catch if wrong)
|
||||
# 3. Known property, unknown value — error with hint
|
||||
print(f"Invalid value '{value}' for property '{prop_name}'. Valid values: {', '.join(valid)}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
# 4. Unknown property — pass-through (no validation data)
|
||||
return value
|
||||
|
||||
|
||||
@@ -2118,7 +2121,7 @@ def main():
|
||||
|
||||
parser = argparse.ArgumentParser(description="Edit existing 1C metadata object XML", allow_abbrev=False)
|
||||
parser.add_argument("-DefinitionFile", default=None, help="JSON definition file")
|
||||
parser.add_argument("-ObjectPath", required=True, help="Path to object XML or directory")
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True, help="Path to object XML or directory")
|
||||
parser.add_argument("-Operation", default=None, choices=valid_operations, help="Inline operation")
|
||||
parser.add_argument("-Value", default=None, help="Inline value")
|
||||
parser.add_argument("-NoValidate", action="store_true", help="Skip auto-validation")
|
||||
@@ -2254,7 +2257,7 @@ def main():
|
||||
print()
|
||||
print("--- Running meta-validate ---")
|
||||
python_exe = sys.executable
|
||||
subprocess.run([python_exe, validate_script, "-ObjectPath", resolved_path])
|
||||
subprocess.run([python_exe, validate_script, "-ObjectPath", "-Path", resolved_path])
|
||||
else:
|
||||
print()
|
||||
print(f"[SKIP] meta-validate not found at: {validate_script}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# meta-info v1.1 — Compact summary of 1C metadata object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$ObjectPath,
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
|
||||
[ValidateSet("overview","brief","full")]
|
||||
[string]$Mode = "overview",
|
||||
[string]$Name,
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
# ── arg parsing ──────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-ObjectPath", required=True)
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True)
|
||||
parser.add_argument("-Mode", choices=["overview", "brief", "full"], default="overview")
|
||||
parser.add_argument("-Name", default="")
|
||||
parser.add_argument("-Limit", type=int, default=150)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: meta-remove
|
||||
description: Удалить объект метаданных из конфигурации 1С. Используй когда пользователь просит удалить, убрать объект из конфигурации
|
||||
description: Удалить объект метаданных из конфигурации 1С. Используй когда нужно удалить, убрать объект из конфигурации
|
||||
argument-hint: <ConfigDir> -Object <Type.Name>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# meta-validate v1.2 — Validate 1C metadata object structure
|
||||
# meta-validate v1.3 — Validate 1C metadata object structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$ObjectPath,
|
||||
|
||||
[switch]$Detailed,
|
||||
@@ -262,7 +263,7 @@ $validPropertyValues = @{
|
||||
"FillChecking" = @("DontCheck","ShowError","ShowWarning")
|
||||
"Indexing" = @("DontIndex","Index","IndexWithAdditionalOrder")
|
||||
"DataHistory" = @("Use","DontUse")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","RequireCalculationTypes")
|
||||
"DependenceOnCalculationTypes" = @("DontUse","OnActionPeriod")
|
||||
}
|
||||
|
||||
# Properties forbidden per type (would cause LoadConfigFromFiles error)
|
||||
@@ -998,6 +999,18 @@ if ($propsNode) {
|
||||
}
|
||||
}
|
||||
|
||||
# CalculationRegister: ActionPeriod=true requires non-empty Schedule
|
||||
if ($mdType -eq "CalculationRegister") {
|
||||
$actionPeriod = $propsNode.SelectSingleNode("md:ActionPeriod", $ns)
|
||||
if ($actionPeriod -and $actionPeriod.InnerText -eq "true") {
|
||||
$schedule = $propsNode.SelectSingleNode("md:Schedule", $ns)
|
||||
if (-not $schedule -or -not $schedule.InnerText.Trim()) {
|
||||
Report-Warn "10. CalculationRegister: ActionPeriod=true but Schedule is empty — platform requires a schedule register"
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# DocumentJournal: RegisteredDocuments should not be empty
|
||||
if ($mdType -eq "DocumentJournal") {
|
||||
$regDocs = $propsNode.SelectSingleNode("md:RegisteredDocuments", $ns)
|
||||
@@ -1025,6 +1038,48 @@ if ($propsNode) {
|
||||
}
|
||||
}
|
||||
|
||||
# Register: must have at least one Dimension or Resource (platform rejects empty registers)
|
||||
$regTypesAll = @("AccumulationRegister","AccountingRegister","CalculationRegister","InformationRegister")
|
||||
if ($regTypesAll -contains $mdType -and $childObjNode) {
|
||||
$dims = $childObjNode.SelectNodes("md:Dimension", $ns).Count
|
||||
$ress = $childObjNode.SelectNodes("md:Resource", $ns).Count
|
||||
$attrs = $childObjNode.SelectNodes("md:Attribute", $ns).Count
|
||||
if (($dims + $ress + $attrs) -eq 0) {
|
||||
Report-Warn "10. $mdType`: no Dimensions, Resources, or Attributes — platform will reject"
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
|
||||
# Document: RegisterRecords references should point to existing objects in config
|
||||
if ($mdType -eq "Document" -and $script:configDir) {
|
||||
$regRecords = $propsNode.SelectSingleNode("md:RegisterRecords", $ns)
|
||||
if ($regRecords) {
|
||||
$items = $regRecords.SelectNodes("xr:Item", $ns)
|
||||
foreach ($item in $items) {
|
||||
$refVal = $item.InnerText.Trim()
|
||||
if (-not $refVal) { continue }
|
||||
# Parse "AccumulationRegister.Name" → dir AccumulationRegisters/Name
|
||||
$parts = $refVal -split '\.',2
|
||||
if ($parts.Count -eq 2) {
|
||||
$refType = $parts[0]; $refName = $parts[1]
|
||||
$dirMap = @{
|
||||
"AccumulationRegister"="AccumulationRegisters"; "InformationRegister"="InformationRegisters"
|
||||
"AccountingRegister"="AccountingRegisters"; "CalculationRegister"="CalculationRegisters"
|
||||
}
|
||||
$refDir = $dirMap[$refType]
|
||||
if ($refDir) {
|
||||
$refPath = Join-Path $script:configDir "$refDir/$refName"
|
||||
$refXml = Join-Path $script:configDir "$refDir/$refName.xml"
|
||||
if (-not (Test-Path $refPath) -and -not (Test-Path $refXml)) {
|
||||
Report-Warn "10. Document.RegisterRecords references '$refVal' but object not found in config"
|
||||
$check10Issues++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Register: must have at least one registrar document
|
||||
$registerTypes = @("AccumulationRegister","AccountingRegister","CalculationRegister","InformationRegister")
|
||||
if ($registerTypes -contains $mdType -and $script:configDir -and $objName -ne "(unknown)") {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.2 — Validate 1C metadata object structure (Python port)
|
||||
# meta-validate v1.3 — Validate 1C metadata object structure (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -14,7 +14,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
# ── arg parsing ──────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-ObjectPath", required=True)
|
||||
parser.add_argument("-ObjectPath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=30)
|
||||
parser.add_argument("-OutFile", default="")
|
||||
@@ -31,7 +31,7 @@ if len(path_list) > 1:
|
||||
batch_ok = 0
|
||||
batch_fail = 0
|
||||
for single_path in path_list:
|
||||
cmd = [sys.executable, __file__, "-ObjectPath", single_path, "-MaxErrors", str(max_errors)]
|
||||
cmd = [sys.executable, __file__, "-ObjectPath", "-Path", single_path, "-MaxErrors", str(max_errors)]
|
||||
if detailed:
|
||||
cmd.append("-Detailed")
|
||||
if out_file:
|
||||
@@ -263,7 +263,7 @@ valid_property_values = {
|
||||
"FillChecking": ["DontCheck", "ShowError", "ShowWarning"],
|
||||
"Indexing": ["DontIndex", "Index", "IndexWithAdditionalOrder"],
|
||||
"DataHistory": ["Use", "DontUse"],
|
||||
"DependenceOnCalculationTypes": ["DontUse", "RequireCalculationTypes"],
|
||||
"DependenceOnCalculationTypes": ["DontUse", "OnActionPeriod"],
|
||||
}
|
||||
|
||||
# Properties forbidden per type (would cause LoadConfigFromFiles error)
|
||||
@@ -942,6 +942,15 @@ if props_node is not None:
|
||||
check10_issues += 1
|
||||
print('[HINT] /meta-edit -Operation modify-property -Value "Task=Task.XXX"')
|
||||
|
||||
# CalculationRegister: ActionPeriod=true requires non-empty Schedule
|
||||
if md_type == 'CalculationRegister':
|
||||
action_period = find(props_node, 'md:ActionPeriod')
|
||||
if action_period is not None and text_of(action_period) == 'true':
|
||||
schedule = find(props_node, 'md:Schedule')
|
||||
if schedule is None or not text_of(schedule):
|
||||
report_warn('10. CalculationRegister: ActionPeriod=true but Schedule is empty — platform requires a schedule register')
|
||||
check10_issues += 1
|
||||
|
||||
# DocumentJournal: RegisteredDocuments should not be empty
|
||||
if md_type == 'DocumentJournal':
|
||||
reg_docs = find(props_node, 'md:RegisteredDocuments')
|
||||
@@ -969,6 +978,43 @@ if props_node is not None:
|
||||
check10_issues += 1
|
||||
print('[HINT] /meta-edit -Operation modify-property -Value "ExtDimensionTypes=ChartOfCharacteristicTypes.XXX"')
|
||||
|
||||
# Register: must have at least one Dimension or Resource (platform rejects empty registers)
|
||||
reg_types_all = ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'InformationRegister')
|
||||
if md_type in reg_types_all and child_obj_node is not None:
|
||||
dims = len(find_all(child_obj_node, 'md:Dimension'))
|
||||
ress = len(find_all(child_obj_node, 'md:Resource'))
|
||||
attrs = len(find_all(child_obj_node, 'md:Attribute'))
|
||||
if dims + ress + attrs == 0:
|
||||
report_warn(f"10. {md_type}: no Dimensions, Resources, or Attributes \u2014 platform will reject")
|
||||
check10_issues += 1
|
||||
|
||||
# Document: RegisterRecords references should point to existing objects in config
|
||||
if md_type == 'Document' and config_dir:
|
||||
reg_records = find(props_node, 'md:RegisterRecords')
|
||||
if reg_records is not None:
|
||||
rr_items = find_all(reg_records, 'xr:Item')
|
||||
for item in rr_items:
|
||||
ref_val = (inner_text(item) or '').strip()
|
||||
if not ref_val:
|
||||
continue
|
||||
# Parse "AccumulationRegister.Name" -> dir AccumulationRegisters/Name
|
||||
parts = ref_val.split('.', 1)
|
||||
if len(parts) == 2:
|
||||
ref_type, ref_name = parts
|
||||
dir_map = {
|
||||
'AccumulationRegister': 'AccumulationRegisters',
|
||||
'InformationRegister': 'InformationRegisters',
|
||||
'AccountingRegister': 'AccountingRegisters',
|
||||
'CalculationRegister': 'CalculationRegisters',
|
||||
}
|
||||
ref_dir = dir_map.get(ref_type)
|
||||
if ref_dir:
|
||||
ref_path = os.path.join(config_dir, ref_dir, ref_name)
|
||||
ref_xml = os.path.join(config_dir, ref_dir, ref_name + '.xml')
|
||||
if not os.path.exists(ref_path) and not os.path.exists(ref_xml):
|
||||
report_warn(f"10. Document.RegisterRecords references '{ref_val}' but object not found in config")
|
||||
check10_issues += 1
|
||||
|
||||
# Register: must have at least one registrar document
|
||||
register_types = ('AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'InformationRegister')
|
||||
if md_type in register_types and config_dir and obj_name != '(unknown)':
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
|
||||
[string]$OutputPath
|
||||
|
||||
@@ -47,7 +47,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Decompile 1C spreadsheet to JSON", allow_abbrev=False)
|
||||
parser.add_argument("-TemplatePath", required=True, help="Path to Template.xml")
|
||||
parser.add_argument("-TemplatePath", "-Path", required=True, help="Path to Template.xml")
|
||||
parser.add_argument("-OutputPath", default=None, help="Output JSON path (stdout if omitted)")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# mxl-info v1.0 — Analyze 1C spreadsheet structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
[string]$ProcessorName,
|
||||
[string]$TemplateName,
|
||||
|
||||
@@ -14,7 +14,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
# --- Argument parsing ---
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C spreadsheet (MXL) structure", allow_abbrev=False)
|
||||
parser.add_argument("-TemplatePath", default="", help="Path to Template.xml")
|
||||
parser.add_argument("-TemplatePath", "-Path", default="", help="Path to Template.xml")
|
||||
parser.add_argument("-ProcessorName", default="", help="Processor name (used with -TemplateName)")
|
||||
parser.add_argument("-TemplateName", default="", help="Template name (used with -ProcessorName)")
|
||||
parser.add_argument("-SrcDir", default="src", help="Source directory (default: src)")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# mxl-validate v1.1 — Validate 1C spreadsheet
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
[string]$ProcessorName,
|
||||
[string]$TemplateName,
|
||||
|
||||
@@ -55,7 +55,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C spreadsheet document Template.xml', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-TemplatePath', dest='TemplatePath', default='')
|
||||
parser.add_argument('-TemplatePath', '-Path', dest='TemplatePath', default='')
|
||||
parser.add_argument('-ProcessorName', dest='ProcessorName', default='')
|
||||
parser.add_argument('-TemplateName', dest='TemplateName', default='')
|
||||
parser.add_argument('-SrcDir', dest='SrcDir', default='src')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# role-compile v1.3 — Compile 1C role from JSON
|
||||
# role-compile v1.5 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -505,6 +505,26 @@ if ($def.objects) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$resolvedOutputDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) { $OutputDir } else { Join-Path (Get-Location) $OutputDir }
|
||||
$formatVersion = Detect-FormatVersion $resolvedOutputDir
|
||||
|
||||
# --- 8. Generate UUID ---
|
||||
|
||||
$uuid = [guid]::NewGuid().ToString()
|
||||
@@ -531,7 +551,7 @@ 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="2.17">'
|
||||
X " version=`"$formatVersion`">"
|
||||
X " <Role uuid=`"$uuid`">"
|
||||
X ' <Properties>'
|
||||
X " <Name>$roleName</Name>"
|
||||
@@ -560,7 +580,7 @@ X '<?xml version="1.0" encoding="UTF-8"?>'
|
||||
X '<Rights xmlns="http://v8.1c.ru/8.2/roles"'
|
||||
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
||||
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
X ' xsi:type="Rights" version="2.17">'
|
||||
X " xsi:type=`"Rights`" version=`"$formatVersion`">"
|
||||
|
||||
# Global flags (defaults match typical 1C roles)
|
||||
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# role-compile v1.3 — Compile 1C role from JSON
|
||||
# role-compile v1.4 — Compile 1C role from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -9,6 +9,22 @@ import sys
|
||||
import uuid
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 esc_xml(s):
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
|
||||
@@ -459,6 +475,9 @@ def main():
|
||||
if not defn.get('objects') and defn.get('rights'):
|
||||
defn['objects'] = defn['rights']
|
||||
|
||||
out_dir_resolved = args.OutputDir if os.path.isabs(args.OutputDir) else os.path.join(os.getcwd(), args.OutputDir)
|
||||
format_version = detect_format_version(out_dir_resolved)
|
||||
|
||||
# --- 2. Parse all object entries ---
|
||||
parsed_objects = []
|
||||
if defn.get('objects'):
|
||||
@@ -490,7 +509,7 @@ def main():
|
||||
lines.append(' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"')
|
||||
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
||||
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
||||
lines.append(' version="2.17">')
|
||||
lines.append(f' version="{format_version}">')
|
||||
lines.append(f' <Role uuid="{uid}">')
|
||||
lines.append(' <Properties>')
|
||||
lines.append(f' <Name>{role_name}</Name>')
|
||||
@@ -516,7 +535,7 @@ def main():
|
||||
lines.append('<Rights xmlns="http://v8.1c.ru/8.2/roles"')
|
||||
lines.append(' xmlns:xs="http://www.w3.org/2001/XMLSchema"')
|
||||
lines.append(' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"')
|
||||
lines.append(' xsi:type="Rights" version="2.17">')
|
||||
lines.append(f' xsi:type="Rights" version="{format_version}">')
|
||||
|
||||
# Global flags
|
||||
sfno = str(defn['setForNewObjects']).lower() if defn.get('setForNewObjects') is not None else 'false'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# role-info v1.0 — Analyze 1C role rights
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$RightsPath,
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$RightsPath,
|
||||
[switch]$ShowDenied,
|
||||
[int]$Limit = 150,
|
||||
[int]$Offset = 0,
|
||||
|
||||
@@ -13,7 +13,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
# --- Argument parsing ---
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C role rights", allow_abbrev=False)
|
||||
parser.add_argument("-RightsPath", required=True, help="Path to Rights.xml")
|
||||
parser.add_argument("-RightsPath", "-Path", required=True, help="Path to Rights.xml")
|
||||
parser.add_argument("-ShowDenied", action="store_true", default=False, help="Show denied rights")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
parser.add_argument("-Offset", type=int, default=0, help="Lines to skip")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$RightsPath,
|
||||
|
||||
[string]$OutFile,
|
||||
|
||||
@@ -179,7 +179,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C role Rights.xml structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-RightsPath', dest='RightsPath', required=True)
|
||||
parser.add_argument('-RightsPath', '-Path', dest='RightsPath', required=True)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
parser.add_argument('-Detailed', dest='Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
|
||||
@@ -60,6 +60,17 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
|
||||
Запрос поддерживает `@file` — ссылку на внешний .sql файл вместо inline-текста: `"query": "@queries/sales.sql"`. Путь разрешается относительно JSON-файла, затем CWD.
|
||||
|
||||
**DataSetObject** — внешний набор данных (без источника-запроса). Поля описываются явно; данные передаются вторым параметром `ПроцессорКомпоновкиДанных.Инициализировать(Макет, Новый Структура("<objectName>", ТЗ), ...)`.
|
||||
|
||||
```json
|
||||
{ "name": "ЖурналОшибок", "objectName": "ЖурналОшибок", "fields": [
|
||||
{ "field": "ТекстСообщения", "title": "Текст сообщения", "type": "string(150)" },
|
||||
{ "field": "Расшифровка", "title": "Описание", "type": "CatalogRef.СтруктураПредприятия" }
|
||||
]}
|
||||
```
|
||||
|
||||
`name` — имя набора в схеме, `objectName` — ключ в структуре передачи данных.
|
||||
|
||||
### Поля — shorthand и объектная форма
|
||||
|
||||
```
|
||||
@@ -75,14 +86,44 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
```
|
||||
`dataPath` автоматически берётся из `field`, если не указан явно.
|
||||
|
||||
Многоязычный заголовок: `"title": { "ru": "...", "en": "..." }`. Применимо везде, где принимается title/presentation (поля, calculatedFields, parameters, settingsVariants, availableValues и пр.). Строка эквивалентна `{ "ru": "..." }`.
|
||||
|
||||
Типы: `string`, `string(N)`, `decimal(D,F)`, `boolean`, `date`, `dateTime`, `CatalogRef.X`, `DocumentRef.X`, `EnumRef.X`, `StandardPeriod`. Ссылочные типы эмитируются с inline namespace `d5p1:` (`http://v8.1c.ru/8.1/data/enterprise/current-config`). Сборка EPF со ссылочными типами требует базу с соответствующей конфигурацией.
|
||||
|
||||
**Синонимы типов** (русские и альтернативные): `число` = decimal, `строка` = string, `булево` = boolean, `дата` = date, `датаВремя` = dateTime, `СтандартныйПериод` = StandardPeriod, `СправочникСсылка.X` = CatalogRef.X, `ДокументСсылка.X` = DocumentRef.X, `int`/`number` = decimal, `bool` = boolean. Регистронезависимые.
|
||||
Составной тип (несколько типов значений) — массив в объектной форме: `"type": ["CatalogRef.A", "CatalogRef.B"]`. Квалификаторы (`(N)`, `(D,F)`) применяются к каждому элементу.
|
||||
|
||||
Роли: `@dimension`, `@account`, `@balance`, `@period`.
|
||||
|
||||
Ограничения: `#noField`, `#noFilter`, `#noGroup`, `#noOrder`.
|
||||
|
||||
В объектной форме: `"useRestriction": { "field": true, "condition": true, "group": true, "order": true }` или `"restrict": ["noField", "noFilter"]`.
|
||||
|
||||
Дополнительные ключи объектной формы:
|
||||
- `"presentationExpression": "<выражение>"` — что показывать вместо значения поля. Исходное значение остаётся «под капотом» для перехода/расшифровки.
|
||||
- `"appearance": { "<параметр>": "<значение>" }` — оформление колонки по умолчанию (применяется во всех вариантах настроек). Ключи — параметры платформы (`ГоризонтальноеПоложение`, `МинимальнаяШирина`, `Формат`, `Текст` и т.п.).
|
||||
|
||||
```json
|
||||
{ "field": "Сумма", "title": "Сумма продажи", "type": "decimal(15,2)",
|
||||
"appearance": { "ГоризонтальноеПоложение": "Right", "МинимальнаяШирина": "80" } }
|
||||
```
|
||||
|
||||
### Вычисляемые поля (calculatedFields)
|
||||
|
||||
Shorthand: `"Имя [Заголовок]: тип = Выражение #noField #noFilter #noGroup #noOrder"` — все части кроме имени опциональны.
|
||||
|
||||
```json
|
||||
"calculatedFields": [
|
||||
"Маржа = Цена - Закупка",
|
||||
"Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100",
|
||||
"Служебное: string = \"\" #noField #noFilter #noGroup #noOrder"
|
||||
]
|
||||
```
|
||||
|
||||
Объектная форма — когда нужна `appearance`:
|
||||
```json
|
||||
{ "name": "Маржа", "title": "Маржа", "expression": "Цена - Закупка", "type": "decimal(15,2)", "useRestriction": "#noField #noFilter" }
|
||||
```
|
||||
|
||||
### Итоги (shorthand)
|
||||
|
||||
```json
|
||||
@@ -93,11 +134,37 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
|
||||
```json
|
||||
"parameters": [
|
||||
"Период: StandardPeriod = LastMonth @autoDates"
|
||||
"Период [Отчетный период]: StandardPeriod = LastMonth @autoDates"
|
||||
]
|
||||
```
|
||||
|
||||
`@autoDates` — автоматически генерирует параметры `ДатаНачала` и `ДатаОкончания` с выражениями `&Период.ДатаНачала` / `&Период.ДатаОкончания` и `availableAsField=false`. Заменяет 5 строк на 1.
|
||||
Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет `<title>` (LocalStringType).
|
||||
|
||||
Флаги shorthand:
|
||||
- `@autoDates` — добавляет к параметру StandardPeriod пару дат `НачалоПериода`/`КонецПериода`, вычисляемых из него. Используй их в тексте запроса как `&НачалоПериода`/`&КонецПериода`; пользователь выбирает только сам период. По умолчанию сам параметр получает `use=Always` и `denyIncompleteValues=true` (чтобы производные даты всегда были заполнены); в объектной форме можно явно переопределить.
|
||||
- `@valueList` — `<valueListAllowed>true</valueListAllowed>` — разрешает передавать список значений
|
||||
- `@hidden` — скрытый параметр: `availableAsField=false` + исключается из `"dataParameters": "auto"`
|
||||
|
||||
Объектная форма: `title`, `hidden: true`, `valueListAllowed: true`, `availableAsField: false`, `denyIncompleteValues: true`, `use: "Always"`.
|
||||
|
||||
Список допустимых значений (availableValues):
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ПорядокОкругления",
|
||||
"type": "EnumRef.Округления",
|
||||
"value": "Перечисление.Округления.Окр1_00",
|
||||
"use": "Always",
|
||||
"denyIncompleteValues": true,
|
||||
"availableValues": [
|
||||
{"value": "Перечисление.Округления.Окр1_00", "presentation": "руб. коп"},
|
||||
{"value": "Перечисление.Округления.Окр1", "presentation": "руб."},
|
||||
{"value": "Перечисление.Округления.Окр1000", "presentation": "тыс. руб"}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
В варианте настроек `"dataParameters": "auto"` выводит все не-hidden параметры с `userSettingID`. Значения по умолчанию наследуются и остаются активными; параметры без значения по умолчанию отключаются (пользователь включит их в настройках).
|
||||
|
||||
### Фильтры — shorthand
|
||||
|
||||
@@ -144,7 +211,20 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
|
||||
`>` разделяет уровни группировки. `details` (или `детали`) = детальные записи. `selection` и `order` по умолчанию `["Auto"]` на каждом уровне.
|
||||
|
||||
Для сложных случаев (таблицы, диаграммы, фильтры на уровне группировки) используется объектная форма.
|
||||
Объектная форма — для сложных случаев (именованные группировки, selection/filter на уровне группировки, таблицы, диаграммы):
|
||||
|
||||
```json
|
||||
"structure": [
|
||||
{
|
||||
"name": "ПоОрганизациям",
|
||||
"groupFields": ["Организация"],
|
||||
"selection": ["Организация", "Сумма", "Auto"],
|
||||
"children": [{ "groupFields": [] }]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
`type` по умолчанию `"group"` (можно не указывать). `groupFields` — алиас для `groupBy`. Поддержка `name`, `selection`, `order`, `filter`, `outputParameters`, рекурсивных `children`.
|
||||
|
||||
### Варианты настроек
|
||||
|
||||
@@ -187,7 +267,13 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
]
|
||||
```
|
||||
|
||||
Типы значений appearance: `style:XXX`/`web:XXX`/`win:XXX` → Color, `true`/`false` → Boolean, параметр `Текст` → LocalStringType, прочее → String.
|
||||
Типы значений appearance: `style:XXX`/`web:XXX`/`win:XXX` → Color, `true`/`false` → Boolean, параметр `Формат`/`Текст`/`Заголовок` → LocalStringType, прочее → String.
|
||||
|
||||
Типы значений фильтра: `Перечисление.*`/`Справочник.*`/`ПланСчетов.*`/`Документ.*` → DesignTimeValue (автодетект).
|
||||
|
||||
OrGroup в фильтре: `{"group": "Or", "items": ["условие1", "условие2"]}`.
|
||||
|
||||
Folder в selection: `{"folder": "Поступление", "items": ["ПолеА", "ПолеБ"]}` → SelectedItemFolder с lwsTitle и placement=Auto.
|
||||
|
||||
### Итоги с привязкой к группировкам
|
||||
|
||||
@@ -227,23 +313,49 @@ powershell.exe -NoProfile -File .claude/skills/skd-compile/scripts/skd-compile.p
|
||||
]
|
||||
```
|
||||
|
||||
Синтаксис ячеек: `"текст"` — статика, `"{Имя}"` — параметр, `"|"` — объединение с ячейкой выше, `null` — пустая.
|
||||
Синтаксис ячеек: `"текст"` — статика, `"{Имя}"` — параметр, `"|"` — объединение с ячейкой выше, `">"` — объединение с ячейкой слева, `null` — пустая.
|
||||
|
||||
Двухуровневая шапка с горизонтальным объединением:
|
||||
```json
|
||||
"rows": [
|
||||
["Вид актива", "Остаток начало", "Поступление", ">", ">", ">", "Выбытие", ">", ">", "Остаток конец"],
|
||||
["|", "|", "из произв.", "из п/ф", "со сч.40", "прочее", "Реализ.", "отгруж.", "прочее", "|"],
|
||||
["К1", "К2", "К3", "К4", "К5", "К6", "К7", "К8", "К9", "К10"]
|
||||
]
|
||||
```
|
||||
|
||||
Встроенные стили: `header` (фон, центр, перенос), `data` (фон группы), `subheader` (без фона, центр), `total` (без фона). Все — Arial 10, рамки Solid 1px, цвета через стили платформы.
|
||||
|
||||
Пользовательские стили: файл `skd-styles.json` рядом с JSON или в корне проекта. Все допустимые ключи и формат цветов — в `examples/skd-styles.json`.
|
||||
Пользовательские стили: файл `skd-styles.json` рядом с JSON-определением, в текущей директории, или в `presets/skills/skd/skd-styles.json` (поиск вверх от OutputPath). Первый найденный файл побеждает. Все допустимые ключи и формат цветов — в `examples/skd-styles.json`.
|
||||
|
||||
Raw XML (`"template": "<...>"`) остаётся как fallback. Детект: если есть `rows` — DSL, иначе — raw.
|
||||
|
||||
### Расшифровка (drilldown) в параметрах шаблона
|
||||
|
||||
Ключ `drilldown` в параметре шаблона автоматически генерирует `DetailsAreaTemplateParameter` и привязку `Расшифровка` в appearance ячеек:
|
||||
|
||||
```json
|
||||
"parameters": [
|
||||
{ "name": "Сырье", "expression": "ПоступлениеСырья", "drilldown": "ПоступлениеСырья" }
|
||||
]
|
||||
```
|
||||
|
||||
Генерирует: `ExpressionAreaTemplateParameter` (обычный) + `DetailsAreaTemplateParameter` с именем `Расшифровка_ПоступлениеСырья`, `fieldExpression` по полю `ИмяРесурса`, `mainAction=DrillDown`. Ячейки `{Сырье}` автоматически получают appearance `Расшифровка = Расшифровка_ПоступлениеСырья`.
|
||||
|
||||
### Привязки макетов к группировкам
|
||||
|
||||
```json
|
||||
"groupTemplates": [
|
||||
{ "groupField": "Счет", "templateType": "GroupHeader", "template": "Макет1" },
|
||||
{ "groupField": "Счет", "templateType": "Header", "template": "Макет2" }
|
||||
{ "groupName": "ДанныеОтчета", "templateType": "GroupHeader", "template": "Макет1" },
|
||||
{ "groupField": "Счет", "templateType": "Header", "template": "Макет2" },
|
||||
{ "groupField": "Счет", "templateType": "OverallHeader", "template": "Макет3" }
|
||||
]
|
||||
```
|
||||
|
||||
`groupField` — привязка к полю группировки, `groupName` — к именованной группировке в структуре варианта.
|
||||
|
||||
`templateType`: `Header` (строки данных) → `<groupTemplate>`, `OverallHeader` (итоги) → `<groupTemplate>`, `GroupHeader` (шапка) → `<groupHeaderTemplate>`.
|
||||
|
||||
## Примеры
|
||||
|
||||
### Минимальный
|
||||
@@ -273,17 +385,22 @@ Raw XML (`"template": "<...>"`) остаётся как fallback. Детект:
|
||||
```json
|
||||
{
|
||||
"dataSets": [{
|
||||
"query": "ВЫБРАТЬ Продажи.Номенклатура, Продажи.Количество, Продажи.Сумма ИЗ РегистрНакопления.Продажи КАК Продажи",
|
||||
"fields": ["Номенклатура: СправочникСсылка.Номенклатура @dimension", "Количество: число(15,3)", "Сумма: число(15,2)"]
|
||||
"query": "ВЫБРАТЬ Продажи.Организация, Продажи.Номенклатура, Продажи.КоличествоОборот КАК Количество, Продажи.СуммаОборот КАК Сумма ИЗ РегистрНакопления.Продажи.Обороты(&НачалоПериода, &КонецПериода) КАК Продажи",
|
||||
"fields": [
|
||||
"Организация: СправочникСсылка.Организации @dimension",
|
||||
"Номенклатура: СправочникСсылка.Номенклатура @dimension",
|
||||
"Количество: число(15,3)",
|
||||
"Сумма: число(15,2)"
|
||||
]
|
||||
}],
|
||||
"totalFields": ["Количество: Сумма", "Сумма: Сумма"],
|
||||
"parameters": ["Период: СтандартныйПериод = LastMonth @autoDates"],
|
||||
"settingsVariants": [{
|
||||
"name": "Основной",
|
||||
"settings": {
|
||||
"selection": ["Номенклатура", "Количество", "Сумма", "Auto"],
|
||||
"selection": ["Организация", "Номенклатура", "Количество", "Сумма"],
|
||||
"filter": ["Организация = _ @off @user"],
|
||||
"dataParameters": ["Период = LastMonth @user"],
|
||||
"dataParameters": "auto",
|
||||
"structure": "Организация > details"
|
||||
}
|
||||
}]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-compile v1.3 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.21 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -80,12 +80,25 @@ function Resolve-QueryValue {
|
||||
}
|
||||
|
||||
function Emit-MLText {
|
||||
param([string]$tag, [string]$text, [string]$indent)
|
||||
param([string]$tag, $text, [string]$indent)
|
||||
X "$indent<$tag xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t<v8:item>"
|
||||
X "$indent`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t<v8:content>$(Esc-Xml $text)</v8:content>"
|
||||
X "$indent`t</v8:item>"
|
||||
# Multi-lang: object form { ru: "...", en: "..." } → one <v8:item> per language
|
||||
if ($text -is [System.Management.Automation.PSCustomObject] -or $text -is [hashtable] -or $text -is [System.Collections.IDictionary]) {
|
||||
$props = if ($text -is [System.Management.Automation.PSCustomObject]) { $text.PSObject.Properties } else { $text.GetEnumerator() | ForEach-Object { @{ Name = $_.Key; Value = $_.Value } } }
|
||||
foreach ($p in $props) {
|
||||
$lang = if ($p -is [hashtable]) { $p.Name } else { $p.Name }
|
||||
$content = if ($p -is [hashtable]) { $p.Value } else { $p.Value }
|
||||
X "$indent`t<v8:item>"
|
||||
X "$indent`t`t<v8:lang>$(Esc-Xml "$lang")</v8:lang>"
|
||||
X "$indent`t`t<v8:content>$(Esc-Xml "$content")</v8:content>"
|
||||
X "$indent`t</v8:item>"
|
||||
}
|
||||
} else {
|
||||
X "$indent`t<v8:item>"
|
||||
X "$indent`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t<v8:content>$(Esc-Xml "$text")</v8:content>"
|
||||
X "$indent`t</v8:item>"
|
||||
}
|
||||
X "$indent</$tag>"
|
||||
}
|
||||
|
||||
@@ -179,6 +192,20 @@ function Resolve-TypeStr {
|
||||
}
|
||||
|
||||
function Emit-ValueType {
|
||||
param($typeStr, [string]$indent)
|
||||
|
||||
if (-not $typeStr) { return }
|
||||
|
||||
# Multi-type: iterate and emit each type with its qualifiers
|
||||
if ($typeStr -is [array] -or $typeStr -is [System.Collections.IList]) {
|
||||
foreach ($t in $typeStr) { Emit-SingleValueType -typeStr "$t" -indent $indent }
|
||||
return
|
||||
}
|
||||
|
||||
Emit-SingleValueType -typeStr "$typeStr" -indent $indent
|
||||
}
|
||||
|
||||
function Emit-SingleValueType {
|
||||
param([string]$typeStr, [string]$indent)
|
||||
|
||||
if (-not $typeStr) { return }
|
||||
@@ -259,7 +286,7 @@ function Parse-FieldShorthand {
|
||||
|
||||
$result = @{
|
||||
dataPath = ""; field = ""; title = ""; type = ""
|
||||
roles = @(); restrict = @(); appearance = @{}
|
||||
roles = @(); restrict = @(); appearance = [ordered]@{}
|
||||
}
|
||||
|
||||
# Extract @roles
|
||||
@@ -300,12 +327,20 @@ function Parse-TotalShorthand {
|
||||
$dataPath = $parts[0].Trim()
|
||||
$funcPart = $parts[1].Trim()
|
||||
|
||||
# Known DCS aggregate functions (ru + en)
|
||||
$aggFuncs = @('Сумма','Количество','Минимум','Максимум','Среднее',
|
||||
'Sum','Count','Min','Max','Avg',
|
||||
'Minimum','Maximum','Average')
|
||||
|
||||
if ($funcPart -match '^\w+\(') {
|
||||
# Already has expression form: Func(expr)
|
||||
return @{ dataPath = $dataPath; expression = $funcPart }
|
||||
} else {
|
||||
} elseif ($funcPart -in $aggFuncs) {
|
||||
# Short: Func → Func(DataPath)
|
||||
return @{ dataPath = $dataPath; expression = "$funcPart($dataPath)" }
|
||||
} else {
|
||||
# Identity or custom expression — use as-is
|
||||
return @{ dataPath = $dataPath; expression = $funcPart }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -314,7 +349,7 @@ function Parse-TotalShorthand {
|
||||
function Parse-ParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false }
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null }
|
||||
|
||||
# Extract @autoDates flag
|
||||
if ($s -match '@autoDates') {
|
||||
@@ -322,6 +357,24 @@ function Parse-ParamShorthand {
|
||||
$s = $s -replace '\s*@autoDates', ''
|
||||
}
|
||||
|
||||
# Extract @valueList flag
|
||||
if ($s -match '@valueList') {
|
||||
$result.valueListAllowed = $true
|
||||
$s = $s -replace '\s*@valueList', ''
|
||||
}
|
||||
|
||||
# Extract @hidden flag
|
||||
if ($s -match '@hidden') {
|
||||
$result.hidden = $true
|
||||
$s = $s -replace '\s*@hidden', ''
|
||||
}
|
||||
|
||||
# Extract optional [Title] (mirrors Parse-FieldShorthand)
|
||||
if ($s -match '\[([^\]]*)\]') {
|
||||
$result.title = $Matches[1].Trim()
|
||||
$s = ($s -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
|
||||
}
|
||||
|
||||
# Split "Name: Type = Value"
|
||||
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') {
|
||||
$result.name = $Matches[1].Trim()
|
||||
@@ -341,15 +394,51 @@ function Parse-ParamShorthand {
|
||||
function Parse-CalcShorthand {
|
||||
param([string]$s)
|
||||
|
||||
# "DataPath = Expression"
|
||||
$idx = $s.IndexOf('=')
|
||||
if ($idx -gt 0) {
|
||||
return @{
|
||||
dataPath = $s.Substring(0, $idx).Trim()
|
||||
expression = $s.Substring($idx + 1).Trim()
|
||||
}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# the docs put them after `=`, and the closed flag set avoids matching
|
||||
# `#word` that happens to appear inside a string literal.
|
||||
$restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||
|
||||
$restrict = @()
|
||||
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
|
||||
$restrict += $m.Groups[1].Value
|
||||
}
|
||||
$s = [regex]::Replace($s, "\s*$restrictPattern", '')
|
||||
|
||||
$eqIdx = $s.IndexOf('=')
|
||||
if ($eqIdx -gt 0) {
|
||||
$lhs = $s.Substring(0, $eqIdx)
|
||||
$rhs = $s.Substring($eqIdx + 1).Trim()
|
||||
} else {
|
||||
$lhs = $s
|
||||
$rhs = ""
|
||||
}
|
||||
|
||||
$title = ""
|
||||
if ($lhs -match '\[([^\]]+)\]') {
|
||||
$title = $Matches[1]
|
||||
$lhs = $lhs -replace '\s*\[[^\]]+\]', ''
|
||||
}
|
||||
$lhs = $lhs.Trim()
|
||||
|
||||
$type = ""
|
||||
$dataPath = $lhs
|
||||
if ($lhs.Contains(':')) {
|
||||
$parts = $lhs -split ':', 2
|
||||
$dataPath = $parts[0].Trim()
|
||||
$type = Resolve-TypeStr ($parts[1].Trim())
|
||||
}
|
||||
|
||||
return @{
|
||||
dataPath = $dataPath
|
||||
expression = $rhs
|
||||
type = $type
|
||||
title = $title
|
||||
restrict = $restrict
|
||||
}
|
||||
return @{ dataPath = $s.Trim(); expression = "" }
|
||||
}
|
||||
|
||||
# --- 8b. DataParameter shorthand parser ---
|
||||
@@ -469,6 +558,9 @@ function Parse-FilterShorthand {
|
||||
} elseif ($valPart -match '^\d+(\.\d+)?$') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:decimal"
|
||||
} elseif ($valPart -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "dcscor:DesignTimeValue"
|
||||
} else {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:string"
|
||||
@@ -531,11 +623,17 @@ function Emit-Field {
|
||||
$f = @{
|
||||
dataPath = if ($fieldDef.dataPath) { "$($fieldDef.dataPath)" } elseif ($fieldDef.field) { "$($fieldDef.field)" } else { "" }
|
||||
field = if ($fieldDef.field) { "$($fieldDef.field)" } else { "$($fieldDef.dataPath)" }
|
||||
title = if ($fieldDef.title) { "$($fieldDef.title)" } else { "" }
|
||||
type = if ($fieldDef.type) { Resolve-TypeStr "$($fieldDef.type)" } else { "" }
|
||||
title = if ($fieldDef.title) { $fieldDef.title } else { "" }
|
||||
type = if ($fieldDef.type) {
|
||||
if ($fieldDef.type -is [array] -or $fieldDef.type -is [System.Collections.IList]) {
|
||||
@($fieldDef.type | ForEach-Object { Resolve-TypeStr "$_" })
|
||||
} else {
|
||||
Resolve-TypeStr "$($fieldDef.type)"
|
||||
}
|
||||
} else { "" }
|
||||
roles = @()
|
||||
restrict = @()
|
||||
appearance = @{}
|
||||
appearance = [ordered]@{}
|
||||
}
|
||||
# Parse role
|
||||
if ($fieldDef.role) {
|
||||
@@ -742,52 +840,90 @@ function Emit-DataSetLinks {
|
||||
# === CalculatedFields ===
|
||||
function Emit-CalcFields {
|
||||
if (-not $def.calculatedFields) { return }
|
||||
$restrictMap = @{
|
||||
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
||||
"noGroup" = "group"; "noOrder" = "order"
|
||||
}
|
||||
foreach ($cf in $def.calculatedFields) {
|
||||
# Collect dataPath/expression/title/type/restrict/appearance from either
|
||||
# shorthand string or object form. Object form accepts dataPath/field/name
|
||||
# as synonyms; useRestriction/restrict accepts object, array, or flag string.
|
||||
$title = ""
|
||||
$typeStr = ""
|
||||
$restrictTokens = @()
|
||||
$restrictObj = $null
|
||||
$appearance = $null
|
||||
|
||||
if ($cf -is [string]) {
|
||||
$parsed = Parse-CalcShorthand $cf
|
||||
$dataPath = "$($parsed.dataPath)"
|
||||
$expression = "$($parsed.expression)"
|
||||
$title = $parsed.title
|
||||
$typeStr = "$($parsed.type)"
|
||||
if ($parsed.restrict) { $restrictTokens = @($parsed.restrict) }
|
||||
} else {
|
||||
$parsed = @{
|
||||
dataPath = "$($cf.dataPath)"
|
||||
expression = "$($cf.expression)"
|
||||
$dataPath = if ($cf.dataPath) { "$($cf.dataPath)" }
|
||||
elseif ($cf.field) { "$($cf.field)" }
|
||||
else { "$($cf.name)" }
|
||||
$expression = "$($cf.expression)"
|
||||
if ($cf.title) { $title = $cf.title }
|
||||
if ($cf.type) { $typeStr = Resolve-TypeStr "$($cf.type)" }
|
||||
|
||||
$restrictVal = if ($cf.restrict) { $cf.restrict } elseif ($cf.useRestriction) { $cf.useRestriction } else { $null }
|
||||
if ($restrictVal) {
|
||||
if ($restrictVal -is [System.Management.Automation.PSCustomObject] -or $restrictVal -is [hashtable]) {
|
||||
$restrictObj = $restrictVal
|
||||
} elseif ($restrictVal -is [string]) {
|
||||
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||
foreach ($tok in ($restrictVal -split '\s+')) {
|
||||
$t = $tok.Trim().TrimStart('#')
|
||||
if ($t) { $restrictTokens += $t }
|
||||
}
|
||||
} else {
|
||||
# Array form: ["noField", "noFilter", ...]
|
||||
foreach ($r in $restrictVal) { $restrictTokens += "$r" }
|
||||
}
|
||||
}
|
||||
if ($cf.appearance) { $appearance = $cf.appearance }
|
||||
}
|
||||
|
||||
X "`t<calculatedField>"
|
||||
X "`t`t<dataPath>$(Esc-Xml $parsed.dataPath)</dataPath>"
|
||||
X "`t`t<expression>$(Esc-Xml $parsed.expression)</expression>"
|
||||
X "`t`t<dataPath>$(Esc-Xml $dataPath)</dataPath>"
|
||||
X "`t`t<expression>$(Esc-Xml $expression)</expression>"
|
||||
|
||||
if ($cf -isnot [string]) {
|
||||
if ($cf.title) {
|
||||
Emit-MLText -tag "title" -text "$($cf.title)" -indent "`t`t"
|
||||
}
|
||||
if ($cf.type) {
|
||||
$cfType = Resolve-TypeStr "$($cf.type)"
|
||||
X "`t`t<valueType>"
|
||||
Emit-ValueType -typeStr $cfType -indent "`t`t`t"
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
if ($cf.restrict) {
|
||||
$restrictMap = @{
|
||||
"noField" = "field"; "noFilter" = "condition"; "noCondition" = "condition"
|
||||
"noGroup" = "group"; "noOrder" = "order"
|
||||
if ($title) {
|
||||
Emit-MLText -tag "title" -text $title -indent "`t`t"
|
||||
}
|
||||
if ($typeStr) {
|
||||
X "`t`t<valueType>"
|
||||
Emit-ValueType -typeStr $typeStr -indent "`t`t`t"
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
if ($restrictObj -or $restrictTokens.Count -gt 0) {
|
||||
X "`t`t<useRestriction>"
|
||||
if ($restrictObj) {
|
||||
foreach ($prop in $restrictObj.PSObject.Properties) {
|
||||
if ($prop.Value -eq $true) {
|
||||
X "`t`t`t<$($prop.Name)>true</$($prop.Name)>"
|
||||
}
|
||||
}
|
||||
X "`t`t<useRestriction>"
|
||||
foreach ($r in $cf.restrict) {
|
||||
} else {
|
||||
foreach ($r in $restrictTokens) {
|
||||
$xmlName = $restrictMap["$r"]
|
||||
if ($xmlName) { X "`t`t`t<$xmlName>true</$xmlName>" }
|
||||
}
|
||||
X "`t`t</useRestriction>"
|
||||
}
|
||||
if ($cf.appearance) {
|
||||
X "`t`t<appearance>"
|
||||
foreach ($prop in $cf.appearance.PSObject.Properties) {
|
||||
X "`t`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||
X "`t`t`t`t<dcscor:parameter>$(Esc-Xml $prop.Name)</dcscor:parameter>"
|
||||
X "`t`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($prop.Value)")</dcscor:value>"
|
||||
X "`t`t`t</dcscor:item>"
|
||||
}
|
||||
X "`t`t</appearance>"
|
||||
X "`t`t</useRestriction>"
|
||||
}
|
||||
if ($appearance) {
|
||||
X "`t`t<appearance>"
|
||||
foreach ($prop in $appearance.PSObject.Properties) {
|
||||
X "`t`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||
X "`t`t`t`t<dcscor:parameter>$(Esc-Xml $prop.Name)</dcscor:parameter>"
|
||||
X "`t`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($prop.Value)")</dcscor:value>"
|
||||
X "`t`t`t</dcscor:item>"
|
||||
}
|
||||
X "`t`t</appearance>"
|
||||
}
|
||||
|
||||
X "`t</calculatedField>"
|
||||
@@ -828,8 +964,16 @@ function Emit-SingleParam {
|
||||
X "`t<parameter>"
|
||||
X "`t`t<name>$(Esc-Xml $parsed.name)</name>"
|
||||
|
||||
# Title
|
||||
$title = if ($p -isnot [string] -and $p.title) { "$($p.title)" } else { "" }
|
||||
# Title (from parsed first, then from object form; accept `presentation` as
|
||||
# a synonym — 1C UI labels a parameter's caption "Представление").
|
||||
$title = ""
|
||||
if ($parsed.title) {
|
||||
$title = $parsed.title
|
||||
} elseif ($p -isnot [string] -and $p.title) {
|
||||
$title = $p.title
|
||||
} elseif ($p -isnot [string] -and $p.presentation) {
|
||||
$title = $p.presentation
|
||||
}
|
||||
if ($title) {
|
||||
Emit-MLText -tag "title" -text $title -indent "`t`t"
|
||||
}
|
||||
@@ -844,8 +988,14 @@ function Emit-SingleParam {
|
||||
# Value
|
||||
Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
|
||||
|
||||
# Hidden implies useRestriction=true + availableAsField=false
|
||||
if ($parsed.hidden -eq $true) {
|
||||
$parsed.availableAsField = $false
|
||||
$parsed.useRestriction = $true
|
||||
}
|
||||
|
||||
# UseRestriction
|
||||
if ($p -isnot [string] -and $p.useRestriction -eq $true) {
|
||||
if ($parsed.useRestriction -eq $true -or ($p -isnot [string] -and $p.useRestriction -eq $true)) {
|
||||
X "`t`t<useRestriction>true</useRestriction>"
|
||||
}
|
||||
|
||||
@@ -859,14 +1009,50 @@ function Emit-SingleParam {
|
||||
X "`t`t<availableAsField>false</availableAsField>"
|
||||
}
|
||||
|
||||
# Use
|
||||
if ($p -isnot [string] -and $p.use) {
|
||||
X "`t`t<use>$(Esc-Xml "$($p.use)")</use>"
|
||||
# ValueListAllowed
|
||||
if ($parsed.valueListAllowed -eq $true) {
|
||||
X "`t`t<valueListAllowed>true</valueListAllowed>"
|
||||
}
|
||||
|
||||
# AvailableValues
|
||||
if ($p -isnot [string] -and $p.availableValues) {
|
||||
foreach ($av in $p.availableValues) {
|
||||
$avVal = "$($av.value)"
|
||||
$avType = "xs:string"
|
||||
if ($avVal -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$avType = "dcscor:DesignTimeValue"
|
||||
}
|
||||
X "`t`t<availableValue>"
|
||||
X "`t`t`t<value xsi:type=`"$avType`">$(Esc-Xml $avVal)</value>"
|
||||
# `title` accepted as synonym of `presentation` — both map to the same UI label.
|
||||
$avPres = if ($av.presentation) { $av.presentation } elseif ($av.title) { $av.title } else { "" }
|
||||
if ($avPres) {
|
||||
Emit-MLText -tag "presentation" -text $avPres -indent "`t`t`t"
|
||||
}
|
||||
X "`t`t</availableValue>"
|
||||
}
|
||||
}
|
||||
|
||||
# DenyIncompleteValues
|
||||
$deny = $parsed.denyIncompleteValues -eq $true -or (
|
||||
$null -ne $p -and $p -isnot [string] -and $p.denyIncompleteValues -eq $true)
|
||||
if ($deny) {
|
||||
X "`t`t<denyIncompleteValues>true</denyIncompleteValues>"
|
||||
}
|
||||
|
||||
# Use — object form wins, else parsed (set by @autoDates default)
|
||||
$useVal = $null
|
||||
if ($null -ne $p -and $p -isnot [string] -and $p.use) { $useVal = "$($p.use)" }
|
||||
elseif ($parsed.use) { $useVal = "$($parsed.use)" }
|
||||
if ($useVal) {
|
||||
X "`t`t<use>$(Esc-Xml $useVal)</use>"
|
||||
}
|
||||
|
||||
X "`t</parameter>"
|
||||
}
|
||||
|
||||
$script:allParams = @()
|
||||
|
||||
function Emit-Parameters {
|
||||
if (-not $def.parameters) { return }
|
||||
foreach ($p in $def.parameters) {
|
||||
@@ -881,22 +1067,40 @@ function Emit-Parameters {
|
||||
}
|
||||
if ($p.expression) { $parsed.expression = "$($p.expression)" }
|
||||
if ($p.availableAsField -eq $false) { $parsed.availableAsField = $false }
|
||||
if ($p.valueListAllowed -eq $true) { $parsed.valueListAllowed = $true }
|
||||
if ($p.hidden -eq $true) { $parsed.hidden = $true }
|
||||
if ($p.autoDates -eq $true) { $parsed.autoDates = $true }
|
||||
}
|
||||
|
||||
# @autoDates implies use=Always + denyIncompleteValues=true by default
|
||||
# (derived &НачалоПериода/&КонецПериода need a populated period).
|
||||
# Explicit values in object form override these defaults.
|
||||
if ($parsed.autoDates) {
|
||||
$isObj = ($p -isnot [string]) -and ($null -ne $p)
|
||||
if (-not ($isObj -and $null -ne $p.use)) { $parsed.use = 'Always' }
|
||||
if (-not ($isObj -and $null -ne $p.denyIncompleteValues)) { $parsed.denyIncompleteValues = $true }
|
||||
}
|
||||
|
||||
Emit-SingleParam -p $p -parsed $parsed
|
||||
|
||||
# @autoDates: auto-generate ДатаНачала and ДатаОкончания
|
||||
# Track parameter for auto dataParameters
|
||||
$script:allParams += @{ name = $parsed.name; hidden = [bool]$parsed.hidden; type = "$($parsed.type)"; value = $parsed.value }
|
||||
|
||||
# @autoDates: auto-generate НачалоПериода and КонецПериода (canonical БСП pattern)
|
||||
if ($parsed.autoDates) {
|
||||
$paramName = $parsed.name
|
||||
$beginParsed = @{
|
||||
name = "ДатаНачала"; type = "date"; value = $null
|
||||
expression = "&$paramName.ДатаНачала"; availableAsField = $false
|
||||
name = "НачалоПериода"; title = "Начало периода"
|
||||
type = "date"; value = "0001-01-01T00:00:00"
|
||||
useRestriction = $true
|
||||
expression = "&$paramName.ДатаНачала"
|
||||
}
|
||||
Emit-SingleParam -p $null -parsed $beginParsed
|
||||
$endParsed = @{
|
||||
name = "ДатаОкончания"; type = "date"; value = $null
|
||||
expression = "&$paramName.ДатаОкончания"; availableAsField = $false
|
||||
name = "КонецПериода"; title = "Конец периода"
|
||||
type = "date"; value = "0001-01-01T00:00:00"
|
||||
useRestriction = $true
|
||||
expression = "&$paramName.ДатаОкончания"
|
||||
}
|
||||
Emit-SingleParam -p $null -parsed $endParsed
|
||||
}
|
||||
@@ -911,9 +1115,12 @@ function Emit-ParamValue {
|
||||
$valStr = "$val"
|
||||
|
||||
if ($type -eq "StandardPeriod") {
|
||||
# val is a period variant string like "LastMonth"
|
||||
# val is a period variant string like "LastMonth" or "Custom".
|
||||
# Always emit startDate/endDate to match how 1C Designer saves the schema.
|
||||
X "$indent<value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $valStr)</v8:variant>"
|
||||
X "$indent`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
X "$indent`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
X "$indent</value>"
|
||||
} elseif ($type -match '^date') {
|
||||
X "$indent<value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</value>"
|
||||
@@ -929,6 +1136,8 @@ function Emit-ParamValue {
|
||||
X "$indent<value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($valStr -eq "true" -or $valStr -eq "false") {
|
||||
X "$indent<value xsi:type=`"xs:boolean`">$(Esc-Xml $valStr)</value>"
|
||||
} elseif ($valStr -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or $valStr -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') {
|
||||
X "$indent<value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml $valStr)</value>"
|
||||
} else {
|
||||
X "$indent<value xsi:type=`"xs:string`">$(Esc-Xml $valStr)</value>"
|
||||
}
|
||||
@@ -965,10 +1174,22 @@ $script:areaStylePresets = @{
|
||||
}
|
||||
}
|
||||
|
||||
# Load user presets from skd-styles.json (same dir as definition or cwd)
|
||||
# Load user presets from skd-styles.json
|
||||
# Search order (first found wins): 1) definition dir, 2) cwd, 3) scan-up from OutputPath for presets/skills/skd/
|
||||
$script:userStylesLoaded = $false
|
||||
foreach ($stylesDir in @($script:queryBaseDir, (Get-Location).Path)) {
|
||||
$stylesFile = Join-Path $stylesDir "skd-styles.json"
|
||||
$searchPaths = @(
|
||||
(Join-Path $script:queryBaseDir "skd-styles.json"),
|
||||
(Join-Path (Get-Location).Path "skd-styles.json")
|
||||
)
|
||||
$outResolved = if ([System.IO.Path]::IsPathRooted($OutputPath)) { $OutputPath } else { Join-Path (Get-Location).Path $OutputPath }
|
||||
$scanDir = [System.IO.Path]::GetDirectoryName($outResolved)
|
||||
while ($scanDir) {
|
||||
$searchPaths += Join-Path (Join-Path (Join-Path (Join-Path $scanDir "presets") "skills") "skd") "skd-styles.json"
|
||||
$parentDir = Split-Path $scanDir -Parent
|
||||
if ($parentDir -eq $scanDir) { break }
|
||||
$scanDir = $parentDir
|
||||
}
|
||||
foreach ($stylesFile in $searchPaths) {
|
||||
if (Test-Path $stylesFile) {
|
||||
$userStyles = Get-Content -Raw -Encoding UTF8 $stylesFile | ConvertFrom-Json
|
||||
foreach ($prop in $userStyles.PSObject.Properties) {
|
||||
@@ -1005,7 +1226,7 @@ function Emit-ColorValue {
|
||||
}
|
||||
|
||||
function Emit-CellAppearance {
|
||||
param($style, [double]$width = 0, [bool]$vMerge = $false, [double]$minHeight = 0)
|
||||
param($style, [double]$width = 0, [bool]$vMerge = $false, [bool]$hMerge = $false, [double]$minHeight = 0, $extraItems = @())
|
||||
$ind = "`t`t`t`t`t"
|
||||
X "`t`t`t`t<dcsat:appearance>"
|
||||
# Background color
|
||||
@@ -1098,6 +1319,15 @@ function Emit-CellAppearance {
|
||||
X "$ind`t<dcscor:value xsi:type=`"xs:boolean`">true</dcscor:value>"
|
||||
X "$ind</dcscor:item>"
|
||||
}
|
||||
# Horizontal merge
|
||||
if ($hMerge) {
|
||||
X "$ind<dcscor:item>"
|
||||
X "$ind`t<dcscor:parameter>ОбъединятьПоГоризонтали</dcscor:parameter>"
|
||||
X "$ind`t<dcscor:value xsi:type=`"xs:boolean`">true</dcscor:value>"
|
||||
X "$ind</dcscor:item>"
|
||||
}
|
||||
# Extra appearance items (e.g. drilldown Расшифровка)
|
||||
foreach ($ei in $extraItems) { X $ei }
|
||||
X "`t`t`t`t</dcsat:appearance>"
|
||||
}
|
||||
|
||||
@@ -1115,7 +1345,7 @@ function Emit-AreaTemplateDSL {
|
||||
$minHeight = if ($t.minHeight) { [double]$t.minHeight } else { 0 }
|
||||
$colCount = if ($widths.Count -gt 0) { $widths.Count } else { $rows[0].Count }
|
||||
|
||||
# Build merge map: vMerge[row][col] = $true if cell is merged with above
|
||||
# Build vertical merge map: vMerge[row][col] = $true if cell is merged with above
|
||||
$vMerge = @{}
|
||||
for ($r = $rows.Count - 1; $r -ge 1; $r--) {
|
||||
$vMerge[$r] = @{}
|
||||
@@ -1128,6 +1358,26 @@ function Emit-AreaTemplateDSL {
|
||||
}
|
||||
if (-not $vMerge.ContainsKey(0)) { $vMerge[0] = @{} }
|
||||
|
||||
# Build horizontal merge map: hMerge[row][col] = $true if cell is merged with left
|
||||
$hMerge = @{}
|
||||
for ($r = 0; $r -lt $rows.Count; $r++) {
|
||||
$hMerge[$r] = @{}
|
||||
for ($c = 0; $c -lt $colCount; $c++) {
|
||||
$cellVal = $rows[$r][$c]
|
||||
if ($cellVal -is [string] -and $cellVal -eq '>') {
|
||||
$hMerge[$r][$c] = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build drilldown map: param_name -> drilldown_value
|
||||
$drilldownMap = @{}
|
||||
if ($t.parameters) {
|
||||
foreach ($tp in $t.parameters) {
|
||||
if ($tp.drilldown) { $drilldownMap["$($tp.name)"] = "$($tp.drilldown)" }
|
||||
}
|
||||
}
|
||||
|
||||
X "`t<template>"
|
||||
X "`t`t<name>$(Esc-Xml "$($t.name)")</name>"
|
||||
X "`t`t<template xmlns:dcsat=`"http://v8.1c.ru/8.1/data-composition-system/area-template`" xsi:type=`"dcsat:AreaTemplate`">"
|
||||
@@ -1137,41 +1387,49 @@ function Emit-AreaTemplateDSL {
|
||||
for ($c = 0; $c -lt $colCount; $c++) {
|
||||
$cellVal = $rows[$r][$c]
|
||||
$w = if ($c -lt $widths.Count) { [double]$widths[$c] } else { 0 }
|
||||
$isMerged = $vMerge[$r][$c] -eq $true
|
||||
# Check if this cell starts a vertical merge (next row has "|" in same column)
|
||||
$startsVMerge = $false
|
||||
for ($nr = $r + 1; $nr -lt $rows.Count; $nr++) {
|
||||
if ($vMerge[$nr][$c] -eq $true) { $startsVMerge = $true } else { break }
|
||||
}
|
||||
|
||||
$isVMerged = $vMerge[$r][$c] -eq $true
|
||||
$isHMerged = $hMerge[$r][$c] -eq $true
|
||||
X "`t`t`t`t<dcsat:tableCell>"
|
||||
if ($isMerged) {
|
||||
# Merged cell — only appearance with vMerge flag + width
|
||||
if ($isVMerged) {
|
||||
# Vertically merged cell — only appearance with vMerge flag + width
|
||||
Emit-CellAppearance $style $w $true
|
||||
} elseif ($isHMerged) {
|
||||
# Horizontally merged cell — only appearance with hMerge flag + width
|
||||
Emit-CellAppearance $style $w $false $true
|
||||
} else {
|
||||
# Cell value
|
||||
if ($null -ne $cellVal -and $cellVal -ne '') {
|
||||
$cellStr = "$cellVal"
|
||||
# Unescape \| and \>
|
||||
if ($cellStr -eq '\|') { $cellStr = '|' }
|
||||
elseif ($cellStr -eq '\>') { $cellStr = '>' }
|
||||
if ($cellStr -match '^\{(.+)\}$') {
|
||||
# Parameter reference
|
||||
$paramName = $Matches[1]
|
||||
X "`t`t`t`t`t<dcsat:item xsi:type=`"dcsat:Field`">"
|
||||
X "`t`t`t`t`t`t<dcsat:value xsi:type=`"dcscor:Parameter`">$(Esc-Xml $Matches[1])</dcsat:value>"
|
||||
X "`t`t`t`t`t`t<dcsat:value xsi:type=`"dcscor:Parameter`">$(Esc-Xml $paramName)</dcsat:value>"
|
||||
X "`t`t`t`t`t</dcsat:item>"
|
||||
# Build drilldown appearance extra items
|
||||
$cellExtraItems = @()
|
||||
if ($drilldownMap.ContainsKey($paramName)) {
|
||||
$ddVal = $drilldownMap[$paramName]
|
||||
$cellExtraItems += "`t`t`t`t`t<dcscor:item>"
|
||||
$cellExtraItems += "`t`t`t`t`t`t<dcscor:parameter>Расшифровка</dcscor:parameter>"
|
||||
$cellExtraItems += "`t`t`t`t`t`t<dcscor:value xsi:type=`"dcscor:Parameter`">Расшифровка_$ddVal</dcscor:value>"
|
||||
$cellExtraItems += "`t`t`t`t`t</dcscor:item>"
|
||||
}
|
||||
} else {
|
||||
# Static text
|
||||
X "`t`t`t`t`t<dcsat:item xsi:type=`"dcsat:Field`">"
|
||||
X "`t`t`t`t`t`t<dcsat:value xsi:type=`"v8:LocalStringType`">"
|
||||
X "`t`t`t`t`t`t`t<v8:item>"
|
||||
X "`t`t`t`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "`t`t`t`t`t`t`t`t<v8:content>$(Esc-Xml $cellStr)</v8:content>"
|
||||
X "`t`t`t`t`t`t`t</v8:item>"
|
||||
X "`t`t`t`t`t`t</dcsat:value>"
|
||||
Emit-MLText -tag "dcsat:value" -text $cellStr -indent "`t`t`t`t`t`t"
|
||||
X "`t`t`t`t`t</dcsat:item>"
|
||||
}
|
||||
}
|
||||
# Appearance
|
||||
$h = if ($r -eq 0) { $minHeight } else { 0 }
|
||||
Emit-CellAppearance $style $w $startsVMerge $h
|
||||
if (-not $cellExtraItems) { $cellExtraItems = @() }
|
||||
Emit-CellAppearance $style $w $false $false $h $cellExtraItems
|
||||
$cellExtraItems = @()
|
||||
}
|
||||
X "`t`t`t`t</dcsat:tableCell>"
|
||||
}
|
||||
@@ -1186,6 +1444,18 @@ function Emit-AreaTemplateDSL {
|
||||
X "`t`t`t<dcsat:name>$(Esc-Xml "$($tp.name)")</dcsat:name>"
|
||||
X "`t`t`t<dcsat:expression>$(Esc-Xml "$($tp.expression)")</dcsat:expression>"
|
||||
X "`t`t</parameter>"
|
||||
# Drilldown parameter
|
||||
if ($tp.drilldown) {
|
||||
$ddVal = "$($tp.drilldown)"
|
||||
X "`t`t<parameter xmlns:dcsat=`"http://v8.1c.ru/8.1/data-composition-system/area-template`" xsi:type=`"dcsat:DetailsAreaTemplateParameter`">"
|
||||
X "`t`t`t<dcsat:name>Расшифровка_$(Esc-Xml $ddVal)</dcsat:name>"
|
||||
X "`t`t`t<dcsat:fieldExpression>"
|
||||
X "`t`t`t`t<dcsat:field>ИмяРесурса</dcsat:field>"
|
||||
X "`t`t`t`t<dcsat:expression>`"$(Esc-Xml $ddVal)`"</dcsat:expression>"
|
||||
X "`t`t`t</dcsat:fieldExpression>"
|
||||
X "`t`t`t<dcsat:mainAction>DrillDown</dcsat:mainAction>"
|
||||
X "`t`t</parameter>"
|
||||
}
|
||||
}
|
||||
}
|
||||
X "`t</template>"
|
||||
@@ -1211,6 +1481,18 @@ function Emit-Templates {
|
||||
X "`t`t`t<dcsat:name>$(Esc-Xml "$($tp.name)")</dcsat:name>"
|
||||
X "`t`t`t<dcsat:expression>$(Esc-Xml "$($tp.expression)")</dcsat:expression>"
|
||||
X "`t`t</parameter>"
|
||||
# Drilldown parameter
|
||||
if ($tp.drilldown) {
|
||||
$ddVal = "$($tp.drilldown)"
|
||||
X "`t`t<parameter xmlns:dcsat=`"http://v8.1c.ru/8.1/data-composition-system/area-template`" xsi:type=`"dcsat:DetailsAreaTemplateParameter`">"
|
||||
X "`t`t`t<dcsat:name>Расшифровка_$(Esc-Xml $ddVal)</dcsat:name>"
|
||||
X "`t`t`t<dcsat:fieldExpression>"
|
||||
X "`t`t`t`t<dcsat:field>ИмяРесурса</dcsat:field>"
|
||||
X "`t`t`t`t<dcsat:expression>`"$(Esc-Xml $ddVal)`"</dcsat:expression>"
|
||||
X "`t`t`t</dcsat:fieldExpression>"
|
||||
X "`t`t`t<dcsat:mainAction>DrillDown</dcsat:mainAction>"
|
||||
X "`t`t</parameter>"
|
||||
}
|
||||
}
|
||||
}
|
||||
X "`t</template>"
|
||||
@@ -1222,11 +1504,20 @@ function Emit-Templates {
|
||||
function Emit-GroupTemplates {
|
||||
if (-not $def.groupTemplates) { return }
|
||||
foreach ($gt in $def.groupTemplates) {
|
||||
X "`t<groupTemplate>"
|
||||
X "`t`t<groupField>$(Esc-Xml "$($gt.groupField)")</groupField>"
|
||||
X "`t`t<templateType>$(Esc-Xml "$($gt.templateType)")</templateType>"
|
||||
$ttype = if ($gt.templateType) { "$($gt.templateType)" } else { "Header" }
|
||||
$isHeader = ($ttype -eq 'GroupHeader')
|
||||
$tag = if ($isHeader) { 'groupHeaderTemplate' } else { 'groupTemplate' }
|
||||
$xmlTType = if ($isHeader) { 'Header' } else { $ttype }
|
||||
|
||||
X "`t<$tag>"
|
||||
if ($gt.groupName) {
|
||||
X "`t`t<groupName>$(Esc-Xml "$($gt.groupName)")</groupName>"
|
||||
} elseif ($gt.groupField) {
|
||||
X "`t`t<groupField>$(Esc-Xml "$($gt.groupField)")</groupField>"
|
||||
}
|
||||
X "`t`t<templateType>$(Esc-Xml $xmlTType)</templateType>"
|
||||
X "`t`t<template>$(Esc-Xml "$($gt.template)")</template>"
|
||||
X "`t</groupTemplate>"
|
||||
X "`t</$tag>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1249,6 +1540,22 @@ function Emit-Selection {
|
||||
X "$indent`t`t<dcsset:field>$(Esc-Xml $item)</dcsset:field>"
|
||||
X "$indent`t</dcsset:item>"
|
||||
}
|
||||
} elseif ($item.folder) {
|
||||
X "$indent`t<dcsset:item xsi:type=`"dcsset:SelectedItemFolder`">"
|
||||
X "$indent`t`t<dcsset:lwsTitle>"
|
||||
X "$indent`t`t`t<v8:item>"
|
||||
X "$indent`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t`t<v8:content>$(Esc-Xml "$($item.folder)")</v8:content>"
|
||||
X "$indent`t`t`t</v8:item>"
|
||||
X "$indent`t`t</dcsset:lwsTitle>"
|
||||
foreach ($sub in $item.items) {
|
||||
$subName = if ($sub -is [string]) { $sub } else { "$($sub.field)" }
|
||||
X "$indent`t`t<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
|
||||
X "$indent`t`t`t<dcsset:field>$(Esc-Xml $subName)</dcsset:field>"
|
||||
X "$indent`t`t</dcsset:item>"
|
||||
}
|
||||
X "$indent`t`t<dcsset:placement>Auto</dcsset:placement>"
|
||||
X "$indent`t</dcsset:item>"
|
||||
} else {
|
||||
X "$indent`t<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
|
||||
X "$indent`t`t<dcsset:field>$(Esc-Xml "$($item.field)")</dcsset:field>"
|
||||
@@ -1281,6 +1588,16 @@ function Emit-FilterItem {
|
||||
X "$indent`t<dcsset:groupType>$groupType</dcsset:groupType>"
|
||||
if ($item.items) {
|
||||
foreach ($sub in $item.items) {
|
||||
if ($sub -is [string]) {
|
||||
$parsed = Parse-FilterShorthand $sub
|
||||
$obj = @{ field = $parsed.field; op = $parsed.op }
|
||||
if ($parsed.use -eq $false) { $obj.use = $false }
|
||||
if ($null -ne $parsed.value) { $obj.value = $parsed.value }
|
||||
if ($parsed["valueType"]) { $obj.valueType = $parsed["valueType"] }
|
||||
if ($parsed.userSettingID) { $obj.userSettingID = $parsed.userSettingID }
|
||||
if ($parsed.viewMode) { $obj.viewMode = $parsed.viewMode }
|
||||
$sub = [pscustomobject]$obj
|
||||
}
|
||||
Emit-FilterItem -item $sub -indent "$indent`t"
|
||||
}
|
||||
}
|
||||
@@ -1321,12 +1638,7 @@ function Emit-FilterItem {
|
||||
}
|
||||
|
||||
if ($item.presentation) {
|
||||
X "$indent`t<dcsset:presentation xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t`t<v8:item>"
|
||||
X "$indent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t<v8:content>$(Esc-Xml "$($item.presentation)")</v8:content>"
|
||||
X "$indent`t`t</v8:item>"
|
||||
X "$indent`t</dcsset:presentation>"
|
||||
Emit-MLText -tag "dcsset:presentation" -text $item.presentation -indent "$indent`t"
|
||||
}
|
||||
|
||||
if ($item.viewMode) {
|
||||
@@ -1339,12 +1651,7 @@ function Emit-FilterItem {
|
||||
}
|
||||
|
||||
if ($item.userSettingPresentation) {
|
||||
X "$indent`t<dcsset:userSettingPresentation xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t`t<v8:item>"
|
||||
X "$indent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t<v8:content>$(Esc-Xml "$($item.userSettingPresentation)")</v8:content>"
|
||||
X "$indent`t`t</v8:item>"
|
||||
X "$indent`t</dcsset:userSettingPresentation>"
|
||||
Emit-MLText -tag "dcsset:userSettingPresentation" -text $item.userSettingPresentation -indent "$indent`t"
|
||||
}
|
||||
|
||||
X "$indent</dcsset:item>"
|
||||
@@ -1432,13 +1739,8 @@ function Emit-AppearanceValue {
|
||||
X "$indent`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-Xml $actualVal)</dcscor:value>"
|
||||
} elseif ($actualVal -eq "true" -or $actualVal -eq "false") {
|
||||
X "$indent`t<dcscor:value xsi:type=`"xs:boolean`">$actualVal</dcscor:value>"
|
||||
} elseif ($key -eq "Текст" -or $key -eq "Заголовок") {
|
||||
X "$indent`t<dcscor:value xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t`t<v8:item>"
|
||||
X "$indent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t<v8:content>$(Esc-Xml $actualVal)</v8:content>"
|
||||
X "$indent`t`t</v8:item>"
|
||||
X "$indent`t</dcscor:value>"
|
||||
} elseif ($key -eq "Текст" -or $key -eq "Заголовок" -or $key -eq "Формат") {
|
||||
Emit-MLText -tag "dcscor:value" -text $actualVal -indent "$indent`t"
|
||||
} else {
|
||||
X "$indent`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $actualVal)</dcscor:value>"
|
||||
}
|
||||
@@ -1517,12 +1819,7 @@ function Emit-OutputParameters {
|
||||
X "$indent`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||
X "$indent`t`t<dcscor:parameter>$(Esc-Xml $key)</dcscor:parameter>"
|
||||
if ($ptype -eq "mltext") {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t`t`t<v8:item>"
|
||||
X "$indent`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t`t<v8:content>$(Esc-Xml $val)</v8:content>"
|
||||
X "$indent`t`t`t</v8:item>"
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
Emit-MLText -tag "dcscor:value" -text $val -indent "$indent`t`t"
|
||||
} else {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"$ptype`">$(Esc-Xml $val)</dcscor:value>"
|
||||
}
|
||||
@@ -1567,22 +1864,35 @@ function Emit-DataParameters {
|
||||
X "$indent`t`t<dcscor:parameter>$(Esc-Xml "$($dp.parameter)")</dcscor:parameter>"
|
||||
|
||||
# Value
|
||||
if ($null -ne $dp.value) {
|
||||
if ($dp.nilValue -eq $true) {
|
||||
X "$indent`t`t<dcscor:value xsi:nil=`"true`"/>"
|
||||
} elseif ($null -ne $dp.value) {
|
||||
$vtype = "$($dp.valueType)"
|
||||
if ($dp.value -is [PSCustomObject] -and $dp.value.variant) {
|
||||
# StandardPeriod (object form from JSON)
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml "$($dp.value.variant)")</v8:variant>"
|
||||
X "$indent`t`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
X "$indent`t`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
} elseif ($dp.value -is [hashtable] -and $dp.value.variant) {
|
||||
# StandardPeriod (hashtable from shorthand parser)
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml "$($dp.value.variant)")</v8:variant>"
|
||||
X "$indent`t`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
X "$indent`t`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
} elseif ($dp.value -is [bool]) {
|
||||
} elseif ($vtype -eq 'boolean' -or $dp.value -is [bool]) {
|
||||
$bv = "$($dp.value)".ToLower()
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml $bv)</dcscor:value>"
|
||||
} elseif ("$($dp.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
} elseif ($vtype -match '^date' -or "$($dp.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
||||
} elseif ($vtype -match '^decimal') {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
||||
} elseif ($vtype -match '^string') {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
||||
} elseif ("$($dp.value)" -match '^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена)\.' -or "$($dp.value)" -match '^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.') {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
||||
} else {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$($dp.value)")</dcscor:value>"
|
||||
}
|
||||
@@ -1598,12 +1908,7 @@ function Emit-DataParameters {
|
||||
}
|
||||
|
||||
if ($dp.userSettingPresentation) {
|
||||
X "$indent`t`t<dcsset:userSettingPresentation xsi:type=`"v8:LocalStringType`">"
|
||||
X "$indent`t`t`t<v8:item>"
|
||||
X "$indent`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "$indent`t`t`t`t<v8:content>$(Esc-Xml "$($dp.userSettingPresentation)")</v8:content>"
|
||||
X "$indent`t`t`t</v8:item>"
|
||||
X "$indent`t`t</dcsset:userSettingPresentation>"
|
||||
Emit-MLText -tag "dcsset:userSettingPresentation" -text $dp.userSettingPresentation -indent "$indent`t`t"
|
||||
}
|
||||
|
||||
X "$indent`t</dcscor:item>"
|
||||
@@ -1661,6 +1966,10 @@ function Parse-StructureShorthand {
|
||||
if ($seg -match '^(?i)(details|детали)$') {
|
||||
# Empty groupBy = detailed records
|
||||
$group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @()
|
||||
} elseif ($seg -match '^(.+)\[(.+)\]$') {
|
||||
# Named group: "ИмяГруппы[Поле]"
|
||||
$group | Add-Member -NotePropertyName "name" -NotePropertyValue $Matches[1].Trim()
|
||||
$group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @($Matches[2].Trim())
|
||||
} else {
|
||||
$group | Add-Member -NotePropertyName "groupBy" -NotePropertyValue @($seg)
|
||||
}
|
||||
@@ -1678,7 +1987,7 @@ function Parse-StructureShorthand {
|
||||
function Emit-StructureItem {
|
||||
param($item, [string]$indent)
|
||||
|
||||
$type = "$($item.type)"
|
||||
$type = if ($item.type) { "$($item.type)" } else { "group" }
|
||||
|
||||
if ($type -eq "group") {
|
||||
X "$indent<dcsset:item xsi:type=`"dcsset:StructureItemGroup`">"
|
||||
@@ -1687,7 +1996,8 @@ function Emit-StructureItem {
|
||||
X "$indent`t<dcsset:name>$(Esc-Xml "$($item.name)")</dcsset:name>"
|
||||
}
|
||||
|
||||
Emit-GroupItems -groupBy $item.groupBy -indent "$indent`t"
|
||||
$gb = if ($item.groupBy) { $item.groupBy } else { $item.groupFields }
|
||||
Emit-GroupItems -groupBy $gb -indent "$indent`t"
|
||||
|
||||
# Default order to ["Auto"] if not specified
|
||||
$orderItems = $item.order
|
||||
@@ -1725,7 +2035,8 @@ function Emit-StructureItem {
|
||||
if ($item.columns) {
|
||||
foreach ($col in $item.columns) {
|
||||
X "$indent`t<dcsset:column>"
|
||||
Emit-GroupItems -groupBy $col.groupBy -indent "$indent`t`t"
|
||||
$colGb = if ($col.groupBy) { $col.groupBy } else { $col.groupFields }
|
||||
Emit-GroupItems -groupBy $colGb -indent "$indent`t`t"
|
||||
$colOrder = $col.order; if (-not $colOrder) { $colOrder = @("Auto") }
|
||||
Emit-Order -items $colOrder -indent "$indent`t`t"
|
||||
$colSel = $col.selection; if (-not $colSel) { $colSel = @("Auto") }
|
||||
@@ -1741,7 +2052,8 @@ function Emit-StructureItem {
|
||||
if ($row.name) {
|
||||
X "$indent`t`t<dcsset:name>$(Esc-Xml "$($row.name)")</dcsset:name>"
|
||||
}
|
||||
Emit-GroupItems -groupBy $row.groupBy -indent "$indent`t`t"
|
||||
$rowGb = if ($row.groupBy) { $row.groupBy } else { $row.groupFields }
|
||||
Emit-GroupItems -groupBy $rowGb -indent "$indent`t`t"
|
||||
$rowOrder = $row.order; if (-not $rowOrder) { $rowOrder = @("Auto") }
|
||||
Emit-Order -items $rowOrder -indent "$indent`t`t"
|
||||
$rowSel = $row.selection; if (-not $rowSel) { $rowSel = @("Auto") }
|
||||
@@ -1762,7 +2074,8 @@ function Emit-StructureItem {
|
||||
# Points
|
||||
if ($item.points) {
|
||||
X "$indent`t<dcsset:point>"
|
||||
Emit-GroupItems -groupBy $item.points.groupBy -indent "$indent`t`t"
|
||||
$ptGb = if ($item.points.groupBy) { $item.points.groupBy } else { $item.points.groupFields }
|
||||
Emit-GroupItems -groupBy $ptGb -indent "$indent`t`t"
|
||||
$ptOrder = $item.points.order; if (-not $ptOrder) { $ptOrder = @("Auto") }
|
||||
Emit-Order -items $ptOrder -indent "$indent`t`t"
|
||||
$ptSel = $item.points.selection; if (-not $ptSel) { $ptSel = @("Auto") }
|
||||
@@ -1773,7 +2086,8 @@ function Emit-StructureItem {
|
||||
# Series
|
||||
if ($item.series) {
|
||||
X "$indent`t<dcsset:series>"
|
||||
Emit-GroupItems -groupBy $item.series.groupBy -indent "$indent`t`t"
|
||||
$srGb = if ($item.series.groupBy) { $item.series.groupBy } else { $item.series.groupFields }
|
||||
Emit-GroupItems -groupBy $srGb -indent "$indent`t`t"
|
||||
$srOrder = $item.series.order; if (-not $srOrder) { $srOrder = @("Auto") }
|
||||
Emit-Order -items $srOrder -indent "$indent`t`t"
|
||||
$srSel = $item.series.selection; if (-not $srSel) { $srSel = @("Auto") }
|
||||
@@ -1830,13 +2144,8 @@ function Emit-SettingsVariants {
|
||||
X "`t<settingsVariant>"
|
||||
X "`t`t<dcsset:name>$(Esc-Xml "$($v.name)")</dcsset:name>"
|
||||
|
||||
$pres = if ($v.presentation) { "$($v.presentation)" } elseif ($v.title) { "$($v.title)" } else { "$($v.name)" }
|
||||
X "`t`t<dcsset:presentation xsi:type=`"v8:LocalStringType`">"
|
||||
X "`t`t`t<v8:item>"
|
||||
X "`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
X "`t`t`t`t<v8:content>$(Esc-Xml $pres)</v8:content>"
|
||||
X "`t`t`t</v8:item>"
|
||||
X "`t`t</dcsset:presentation>"
|
||||
$pres = if ($v.presentation) { $v.presentation } elseif ($v.title) { $v.title } else { "$($v.name)" }
|
||||
Emit-MLText -tag "dcsset:presentation" -text $pres -indent "`t`t"
|
||||
|
||||
X "`t`t<dcsset:settings xmlns:style=`"http://v8.1c.ru/8.1/data/ui/style`" xmlns:sys=`"http://v8.1c.ru/8.1/data/ui/fonts/system`" xmlns:web=`"http://v8.1c.ru/8.1/data/ui/colors/web`" xmlns:win=`"http://v8.1c.ru/8.1/data/ui/colors/windows`">"
|
||||
|
||||
@@ -1868,7 +2177,51 @@ function Emit-SettingsVariants {
|
||||
}
|
||||
|
||||
# DataParameters
|
||||
if ($s.dataParameters) {
|
||||
if ($s.dataParameters -eq 'auto') {
|
||||
# Auto-generate dataParameters for all non-hidden params.
|
||||
# Pattern follows 1C Designer / ERP persistence:
|
||||
# - value set (non-default) → emit value, use=true (implicit)
|
||||
# - value missing / Custom period → <use>false</use> + <value xsi:nil="true"/>
|
||||
$autoDP = @()
|
||||
foreach ($ap in $script:allParams) {
|
||||
if ($ap.hidden) { continue }
|
||||
$dpItem = New-Object PSObject
|
||||
$dpItem | Add-Member -NotePropertyName "parameter" -NotePropertyValue $ap.name
|
||||
$dpItem | Add-Member -NotePropertyName "userSettingID" -NotePropertyValue "auto"
|
||||
|
||||
$hasMeaningfulValue = $false
|
||||
|
||||
if ($ap.type -eq 'StandardPeriod') {
|
||||
# Inherit variant; Custom is treated as "empty"
|
||||
$variant = 'Custom'
|
||||
$av = $ap.value
|
||||
if ($null -ne $av) {
|
||||
if (($av -is [PSCustomObject] -or $av -is [hashtable]) -and $av.variant) {
|
||||
$variant = "$($av.variant)"
|
||||
} elseif ("$av") {
|
||||
$variant = "$av"
|
||||
}
|
||||
}
|
||||
$dpItem | Add-Member -NotePropertyName "value" -NotePropertyValue @{ variant = $variant }
|
||||
if ($variant -ne 'Custom') { $hasMeaningfulValue = $true }
|
||||
} elseif ($null -ne $ap.value -and "$($ap.value)" -ne '') {
|
||||
$dpItem | Add-Member -NotePropertyName "value" -NotePropertyValue $ap.value
|
||||
$dpItem | Add-Member -NotePropertyName "valueType" -NotePropertyValue "$($ap.type)"
|
||||
$hasMeaningfulValue = $true
|
||||
} else {
|
||||
$dpItem | Add-Member -NotePropertyName "nilValue" -NotePropertyValue $true
|
||||
}
|
||||
|
||||
if (-not $hasMeaningfulValue) {
|
||||
$dpItem | Add-Member -NotePropertyName "use" -NotePropertyValue $false
|
||||
}
|
||||
|
||||
$autoDP += $dpItem
|
||||
}
|
||||
if ($autoDP.Count -gt 0) {
|
||||
Emit-DataParameters -items $autoDP -indent "`t`t`t"
|
||||
}
|
||||
} elseif ($s.dataParameters) {
|
||||
Emit-DataParameters -items $s.dataParameters -indent "`t`t`t"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-compile v1.3 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.21 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -12,6 +12,10 @@ import uuid
|
||||
def esc_xml(s):
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
|
||||
def fmt_dec(v):
|
||||
"""Format decimal: 30.0 → '30', 16.625 → '16.625' (match PS1 output)."""
|
||||
return str(int(v)) if v == int(v) else str(v)
|
||||
|
||||
|
||||
def resolve_query_value(val, base_dir):
|
||||
if not val.startswith("@"):
|
||||
@@ -37,10 +41,18 @@ def emit_mltext(lines, indent, tag, text):
|
||||
lines.append(f"{indent}<{tag}/>")
|
||||
return
|
||||
lines.append(f'{indent}<{tag} xsi:type="v8:LocalStringType">')
|
||||
lines.append(f"{indent}\t<v8:item>")
|
||||
lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{indent}\t\t<v8:content>{esc_xml(text)}</v8:content>")
|
||||
lines.append(f"{indent}\t</v8:item>")
|
||||
# Multi-lang: object form { ru: "...", en: "..." } -- one <v8:item> per language
|
||||
if isinstance(text, dict):
|
||||
for lang, content in text.items():
|
||||
lines.append(f"{indent}\t<v8:item>")
|
||||
lines.append(f"{indent}\t\t<v8:lang>{esc_xml(str(lang))}</v8:lang>")
|
||||
lines.append(f"{indent}\t\t<v8:content>{esc_xml(str(content))}</v8:content>")
|
||||
lines.append(f"{indent}\t</v8:item>")
|
||||
else:
|
||||
lines.append(f"{indent}\t<v8:item>")
|
||||
lines.append(f"{indent}\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{indent}\t\t<v8:content>{esc_xml(str(text))}</v8:content>")
|
||||
lines.append(f"{indent}\t</v8:item>")
|
||||
lines.append(f"{indent}</{tag}>")
|
||||
|
||||
|
||||
@@ -111,7 +123,20 @@ def resolve_type_str(type_str):
|
||||
return type_str
|
||||
|
||||
|
||||
def emit_value_type(lines, type_str, indent):
|
||||
def emit_value_type(lines, type_spec, indent):
|
||||
if not type_spec:
|
||||
return
|
||||
|
||||
# Multi-type: iterate and emit each type with its qualifiers
|
||||
if isinstance(type_spec, list):
|
||||
for t in type_spec:
|
||||
emit_single_value_type(lines, str(t), indent)
|
||||
return
|
||||
|
||||
emit_single_value_type(lines, str(type_spec), indent)
|
||||
|
||||
|
||||
def emit_single_value_type(lines, type_str, indent):
|
||||
if not type_str:
|
||||
return
|
||||
|
||||
@@ -217,22 +242,46 @@ def parse_total_shorthand(s):
|
||||
data_path = parts[0].strip()
|
||||
func_part = parts[1].strip()
|
||||
|
||||
# Known DCS aggregate functions (ru + en)
|
||||
_agg_funcs = {'Сумма','Количество','Минимум','Максимум','Среднее',
|
||||
'Sum','Count','Min','Max','Avg',
|
||||
'Minimum','Maximum','Average'}
|
||||
|
||||
if re.match(r'^\w+\(', func_part):
|
||||
return {'dataPath': data_path, 'expression': func_part}
|
||||
else:
|
||||
elif func_part in _agg_funcs:
|
||||
return {'dataPath': data_path, 'expression': f'{func_part}({data_path})'}
|
||||
else:
|
||||
# Identity or custom expression — use as-is
|
||||
return {'dataPath': data_path, 'expression': func_part}
|
||||
|
||||
|
||||
# --- Parameter shorthand parser ---
|
||||
|
||||
def parse_param_shorthand(s):
|
||||
result = {'name': '', 'type': '', 'value': None, 'autoDates': False}
|
||||
result = {'name': '', 'type': '', 'value': None, 'autoDates': False, 'title': None}
|
||||
|
||||
# Extract @autoDates flag
|
||||
if '@autoDates' in s:
|
||||
result['autoDates'] = True
|
||||
s = re.sub(r'\s*@autoDates', '', s)
|
||||
|
||||
# Extract @valueList flag
|
||||
if '@valueList' in s:
|
||||
result['valueListAllowed'] = True
|
||||
s = re.sub(r'\s*@valueList', '', s)
|
||||
|
||||
# Extract @hidden flag
|
||||
if '@hidden' in s:
|
||||
result['hidden'] = True
|
||||
s = re.sub(r'\s*@hidden', '', s)
|
||||
|
||||
# Extract optional [Title] (mirrors parse_field_shorthand)
|
||||
m = re.search(r'\[([^\]]*)\]', s)
|
||||
if m:
|
||||
result['title'] = m.group(1).strip()
|
||||
s = re.sub(r'\s*\[[^\]]*\]\s*', ' ', s).strip()
|
||||
|
||||
# Split "Name: Type = Value"
|
||||
m = re.match(r'^([^:]+):\s*(\S+)(\s*=\s*(.+))?$', s)
|
||||
if m:
|
||||
@@ -249,13 +298,46 @@ def parse_param_shorthand(s):
|
||||
# --- Calculated field shorthand parser ---
|
||||
|
||||
def parse_calc_shorthand(s):
|
||||
idx = s.find('=')
|
||||
if idx > 0:
|
||||
return {
|
||||
'dataPath': s[:idx].strip(),
|
||||
'expression': s[idx + 1:].strip(),
|
||||
}
|
||||
return {'dataPath': s.strip(), 'expression': ''}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# the docs put them after `=`, and the closed flag set avoids matching
|
||||
# `#word` that happens to appear inside a string literal.
|
||||
restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||
|
||||
restrict = re.findall(restrict_pattern, s)
|
||||
s = re.sub(r'\s*' + restrict_pattern, '', s)
|
||||
|
||||
eq_idx = s.find('=')
|
||||
if eq_idx > 0:
|
||||
lhs = s[:eq_idx]
|
||||
rhs = s[eq_idx + 1:].strip()
|
||||
else:
|
||||
lhs = s
|
||||
rhs = ''
|
||||
|
||||
title = ''
|
||||
m = re.search(r'\[([^\]]+)\]', lhs)
|
||||
if m:
|
||||
title = m.group(1)
|
||||
lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
|
||||
lhs = lhs.strip()
|
||||
|
||||
type_str = ''
|
||||
data_path = lhs
|
||||
if ':' in lhs:
|
||||
colon_idx = lhs.index(':')
|
||||
data_path = lhs[:colon_idx].strip()
|
||||
type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
|
||||
|
||||
return {
|
||||
'dataPath': data_path,
|
||||
'expression': rhs,
|
||||
'type': type_str,
|
||||
'title': title,
|
||||
'restrict': restrict,
|
||||
}
|
||||
|
||||
|
||||
# --- DataParameter shorthand parser ---
|
||||
@@ -361,6 +443,9 @@ def parse_filter_shorthand(s):
|
||||
elif re.match(r'^\d+(\.\d+)?$', val_part):
|
||||
result['value'] = val_part
|
||||
result['valueType'] = 'xs:decimal'
|
||||
elif re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', val_part):
|
||||
result['value'] = val_part
|
||||
result['valueType'] = 'dcscor:DesignTimeValue'
|
||||
else:
|
||||
result['value'] = val_part
|
||||
result['valueType'] = 'xs:string'
|
||||
@@ -419,8 +504,12 @@ def emit_field(lines, field_def, indent):
|
||||
f = {
|
||||
'dataPath': str(field_def.get('dataPath', '')) or str(field_def.get('field', '')),
|
||||
'field': str(field_def.get('field', '')) or str(field_def.get('dataPath', '')),
|
||||
'title': str(field_def.get('title', '')) if field_def.get('title') else '',
|
||||
'type': resolve_type_str(str(field_def['type'])) if field_def.get('type') else '',
|
||||
'title': field_def.get('title') if field_def.get('title') else '',
|
||||
'type': (
|
||||
[resolve_type_str(str(t)) for t in field_def['type']]
|
||||
if isinstance(field_def['type'], list)
|
||||
else resolve_type_str(str(field_def['type']))
|
||||
) if field_def.get('type') else '',
|
||||
'roles': [],
|
||||
'restrict': [],
|
||||
'appearance': {},
|
||||
@@ -592,48 +681,81 @@ def emit_data_set_links(lines, defn):
|
||||
def emit_calc_fields(lines, defn):
|
||||
if not defn.get('calculatedFields'):
|
||||
return
|
||||
restrict_map = {
|
||||
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
||||
'noGroup': 'group', 'noOrder': 'order',
|
||||
}
|
||||
for cf in defn['calculatedFields']:
|
||||
# Collect dataPath/expression/title/type/restrict/appearance from either
|
||||
# shorthand string or object form. Object form accepts dataPath/field/name
|
||||
# as synonyms; useRestriction/restrict accepts object, array, or flag string.
|
||||
title = ''
|
||||
type_str = ''
|
||||
restrict_tokens = []
|
||||
restrict_obj = None
|
||||
appearance = None
|
||||
|
||||
if isinstance(cf, str):
|
||||
parsed = parse_calc_shorthand(cf)
|
||||
is_obj = False
|
||||
data_path = parsed['dataPath']
|
||||
expression = parsed['expression']
|
||||
title = parsed.get('title', '') or ''
|
||||
type_str = parsed.get('type', '') or ''
|
||||
restrict_tokens = list(parsed.get('restrict') or [])
|
||||
else:
|
||||
parsed = {
|
||||
'dataPath': str(cf.get('dataPath', '')),
|
||||
'expression': str(cf.get('expression', '')),
|
||||
}
|
||||
is_obj = True
|
||||
data_path = str(cf.get('dataPath') or cf.get('field') or cf.get('name') or '')
|
||||
expression = str(cf.get('expression', ''))
|
||||
if cf.get('title'):
|
||||
title = cf['title']
|
||||
if cf.get('type'):
|
||||
type_str = resolve_type_str(str(cf['type']))
|
||||
|
||||
restrict_val = cf.get('restrict') if cf.get('restrict') is not None else cf.get('useRestriction')
|
||||
if restrict_val:
|
||||
if isinstance(restrict_val, dict):
|
||||
restrict_obj = restrict_val
|
||||
elif isinstance(restrict_val, str):
|
||||
# Flag-string form: "#noField #noFilter #noGroup #noOrder" (or without `#`)
|
||||
for tok in restrict_val.split():
|
||||
t = tok.strip().lstrip('#')
|
||||
if t:
|
||||
restrict_tokens.append(t)
|
||||
else:
|
||||
# Array form: ["noField", "noFilter", ...]
|
||||
for r in restrict_val:
|
||||
restrict_tokens.append(str(r))
|
||||
appearance = cf.get('appearance')
|
||||
|
||||
lines.append('\t<calculatedField>')
|
||||
lines.append(f'\t\t<dataPath>{esc_xml(parsed["dataPath"])}</dataPath>')
|
||||
lines.append(f'\t\t<expression>{esc_xml(parsed["expression"])}</expression>')
|
||||
lines.append(f'\t\t<dataPath>{esc_xml(data_path)}</dataPath>')
|
||||
lines.append(f'\t\t<expression>{esc_xml(expression)}</expression>')
|
||||
|
||||
if is_obj:
|
||||
if cf.get('title'):
|
||||
emit_mltext(lines, '\t\t', 'title', str(cf['title']))
|
||||
if cf.get('type'):
|
||||
cf_type = resolve_type_str(str(cf['type']))
|
||||
lines.append('\t\t<valueType>')
|
||||
emit_value_type(lines, cf_type, '\t\t\t')
|
||||
lines.append('\t\t</valueType>')
|
||||
if cf.get('restrict'):
|
||||
restrict_map = {
|
||||
'noField': 'field', 'noFilter': 'condition', 'noCondition': 'condition',
|
||||
'noGroup': 'group', 'noOrder': 'order',
|
||||
}
|
||||
lines.append('\t\t<useRestriction>')
|
||||
for r in cf['restrict']:
|
||||
if title:
|
||||
emit_mltext(lines, '\t\t', 'title', title)
|
||||
if type_str:
|
||||
lines.append('\t\t<valueType>')
|
||||
emit_value_type(lines, type_str, '\t\t\t')
|
||||
lines.append('\t\t</valueType>')
|
||||
if restrict_obj or restrict_tokens:
|
||||
lines.append('\t\t<useRestriction>')
|
||||
if restrict_obj:
|
||||
for xml_name, flag in restrict_obj.items():
|
||||
if flag:
|
||||
lines.append(f'\t\t\t<{esc_xml(str(xml_name))}>true</{esc_xml(str(xml_name))}>')
|
||||
else:
|
||||
for r in restrict_tokens:
|
||||
xml_name = restrict_map.get(str(r))
|
||||
if xml_name:
|
||||
lines.append(f'\t\t\t<{xml_name}>true</{xml_name}>')
|
||||
lines.append('\t\t</useRestriction>')
|
||||
if cf.get('appearance'):
|
||||
lines.append('\t\t<appearance>')
|
||||
for k, v in cf['appearance'].items():
|
||||
lines.append('\t\t\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
||||
lines.append(f'\t\t\t\t<dcscor:parameter>{esc_xml(k)}</dcscor:parameter>')
|
||||
lines.append(f'\t\t\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(v))}</dcscor:value>')
|
||||
lines.append('\t\t\t</dcscor:item>')
|
||||
lines.append('\t\t</appearance>')
|
||||
lines.append('\t\t</useRestriction>')
|
||||
if appearance:
|
||||
lines.append('\t\t<appearance>')
|
||||
for k, v in appearance.items():
|
||||
lines.append('\t\t\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
||||
lines.append(f'\t\t\t\t<dcscor:parameter>{esc_xml(k)}</dcscor:parameter>')
|
||||
lines.append(f'\t\t\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(v))}</dcscor:value>')
|
||||
lines.append('\t\t\t</dcscor:item>')
|
||||
lines.append('\t\t</appearance>')
|
||||
|
||||
lines.append('\t</calculatedField>')
|
||||
|
||||
@@ -675,8 +797,11 @@ def emit_param_value(lines, type_str, val, indent):
|
||||
val_str = str(val)
|
||||
|
||||
if type_str == 'StandardPeriod':
|
||||
# Always emit startDate/endDate to match how 1C Designer saves the schema.
|
||||
lines.append(f'{indent}<value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{indent}\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(val_str)}</v8:variant>')
|
||||
lines.append(f'{indent}\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>')
|
||||
lines.append(f'{indent}\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>')
|
||||
lines.append(f'{indent}</value>')
|
||||
elif type_str and re.match(r'^date', type_str):
|
||||
lines.append(f'{indent}<value xsi:type="xs:dateTime">{esc_xml(val_str)}</value>')
|
||||
@@ -692,6 +817,8 @@ def emit_param_value(lines, type_str, val, indent):
|
||||
lines.append(f'{indent}<value xsi:type="xs:dateTime">{esc_xml(val_str)}</value>')
|
||||
elif val_str == 'true' or val_str == 'false':
|
||||
lines.append(f'{indent}<value xsi:type="xs:boolean">{esc_xml(val_str)}</value>')
|
||||
elif re.match(r'^(ПланСчетов|Справочник|Перечисление|Документ|ПланВидовХарактеристик|ПланВидовРасчета|БизнесПроцесс|Задача|РегистрСведений|ПланОбмена|ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.', val_str):
|
||||
lines.append(f'{indent}<value xsi:type="dcscor:DesignTimeValue">{esc_xml(val_str)}</value>')
|
||||
else:
|
||||
lines.append(f'{indent}<value xsi:type="xs:string">{esc_xml(val_str)}</value>')
|
||||
|
||||
@@ -700,10 +827,15 @@ def emit_single_param(lines, p, parsed):
|
||||
lines.append('\t<parameter>')
|
||||
lines.append(f'\t\t<name>{esc_xml(parsed["name"])}</name>')
|
||||
|
||||
# Title
|
||||
# Title (from parsed first, then from object form; accept `presentation` as
|
||||
# a synonym — 1C UI labels a parameter's caption "Представление").
|
||||
title = ''
|
||||
if p is not None and not isinstance(p, str) and p.get('title'):
|
||||
title = str(p['title'])
|
||||
if parsed.get('title'):
|
||||
title = parsed['title']
|
||||
elif p is not None and not isinstance(p, str) and p.get('title'):
|
||||
title = p['title']
|
||||
elif p is not None and not isinstance(p, str) and p.get('presentation'):
|
||||
title = p['presentation']
|
||||
if title:
|
||||
emit_mltext(lines, '\t\t', 'title', title)
|
||||
|
||||
@@ -716,26 +848,68 @@ def emit_single_param(lines, p, parsed):
|
||||
# Value
|
||||
emit_param_value(lines, parsed.get('type', ''), parsed.get('value'), '\t\t')
|
||||
|
||||
# Hidden implies useRestriction=true + availableAsField=false
|
||||
if parsed.get('hidden') is True:
|
||||
parsed['availableAsField'] = False
|
||||
parsed['useRestriction'] = True
|
||||
|
||||
# UseRestriction
|
||||
if p is not None and not isinstance(p, str) and p.get('useRestriction') is True:
|
||||
if parsed.get('useRestriction') is True or (p is not None and not isinstance(p, str) and p.get('useRestriction') is True):
|
||||
lines.append('\t\t<useRestriction>true</useRestriction>')
|
||||
|
||||
# Expression
|
||||
if parsed.get('expression'):
|
||||
lines.append(f'\t\t<expression>{esc_xml(parsed["expression"])}</expression>')
|
||||
if parsed.get('hidden'):
|
||||
parsed['availableAsField'] = False
|
||||
|
||||
# AvailableAsField
|
||||
if parsed.get('availableAsField') is False:
|
||||
lines.append('\t\t<availableAsField>false</availableAsField>')
|
||||
|
||||
# ValueListAllowed
|
||||
if parsed.get('valueListAllowed'):
|
||||
lines.append('\t\t<valueListAllowed>true</valueListAllowed>')
|
||||
|
||||
# AvailableValues
|
||||
if p is not None and not isinstance(p, str) and p.get('availableValues'):
|
||||
for av in p['availableValues']:
|
||||
av_val = str(av.get('value', ''))
|
||||
av_type = 'xs:string'
|
||||
if re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', av_val):
|
||||
av_type = 'dcscor:DesignTimeValue'
|
||||
lines.append('\t\t<availableValue>')
|
||||
lines.append(f'\t\t\t<value xsi:type="{av_type}">{esc_xml(av_val)}</value>')
|
||||
# `title` accepted as synonym of `presentation` — both map to the same UI label.
|
||||
av_pres = av.get('presentation') or av.get('title') or ''
|
||||
if av_pres:
|
||||
emit_mltext(lines, '\t\t\t', 'presentation', av_pres)
|
||||
lines.append('\t\t</availableValue>')
|
||||
|
||||
# DenyIncompleteValues
|
||||
deny = parsed.get('denyIncompleteValues') is True or (
|
||||
p is not None and not isinstance(p, str) and p.get('denyIncompleteValues') is True)
|
||||
if deny:
|
||||
lines.append('\t\t<denyIncompleteValues>true</denyIncompleteValues>')
|
||||
|
||||
# Use
|
||||
use_val = None
|
||||
if p is not None and not isinstance(p, str) and p.get('use'):
|
||||
lines.append(f'\t\t<use>{esc_xml(str(p["use"]))}</use>')
|
||||
use_val = str(p['use'])
|
||||
elif parsed.get('use'):
|
||||
use_val = str(parsed['use'])
|
||||
if use_val:
|
||||
lines.append(f'\t\t<use>{esc_xml(use_val)}</use>')
|
||||
|
||||
lines.append('\t</parameter>')
|
||||
|
||||
|
||||
_all_params = []
|
||||
|
||||
|
||||
def emit_parameters(lines, defn):
|
||||
global _all_params
|
||||
_all_params = []
|
||||
if not defn.get('parameters'):
|
||||
return
|
||||
for p in defn['parameters']:
|
||||
@@ -752,26 +926,50 @@ def emit_parameters(lines, defn):
|
||||
parsed['expression'] = str(p['expression'])
|
||||
if p.get('availableAsField') is False:
|
||||
parsed['availableAsField'] = False
|
||||
if p.get('valueListAllowed') is True:
|
||||
parsed['valueListAllowed'] = True
|
||||
if p.get('hidden') is True:
|
||||
parsed['hidden'] = True
|
||||
if p.get('autoDates') is True:
|
||||
parsed['autoDates'] = True
|
||||
|
||||
# @autoDates implies use=Always + denyIncompleteValues=true by default
|
||||
# (derived &НачалоПериода/&КонецПериода need a populated period).
|
||||
# Explicit values in object form override these defaults.
|
||||
if parsed.get('autoDates'):
|
||||
is_obj = p is not None and not isinstance(p, str)
|
||||
if not (is_obj and p.get('use') is not None):
|
||||
parsed['use'] = 'Always'
|
||||
if not (is_obj and p.get('denyIncompleteValues') is not None):
|
||||
parsed['denyIncompleteValues'] = True
|
||||
|
||||
emit_single_param(lines, p, parsed)
|
||||
|
||||
# @autoDates: auto-generate ДатаНачала and ДатаОкончания
|
||||
# Track parameter for auto dataParameters
|
||||
_all_params.append({
|
||||
'name': parsed['name'],
|
||||
'hidden': bool(parsed.get('hidden')),
|
||||
'type': parsed.get('type', ''),
|
||||
'value': parsed.get('value'),
|
||||
})
|
||||
|
||||
# @autoDates: auto-generate НачалоПериода and КонецПериода (canonical БСП pattern)
|
||||
if parsed.get('autoDates'):
|
||||
param_name = parsed['name']
|
||||
begin_parsed = {
|
||||
'name': '\u0414\u0430\u0442\u0430\u041d\u0430\u0447\u0430\u043b\u0430',
|
||||
'type': 'date', 'value': None,
|
||||
'name': '\u041d\u0430\u0447\u0430\u043b\u043e\u041f\u0435\u0440\u0438\u043e\u0434\u0430',
|
||||
'title': '\u041d\u0430\u0447\u0430\u043b\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0430',
|
||||
'type': 'date', 'value': '0001-01-01T00:00:00',
|
||||
'useRestriction': True,
|
||||
'expression': f'&{param_name}.\u0414\u0430\u0442\u0430\u041d\u0430\u0447\u0430\u043b\u0430',
|
||||
'availableAsField': False,
|
||||
}
|
||||
emit_single_param(lines, None, begin_parsed)
|
||||
end_parsed = {
|
||||
'name': '\u0414\u0430\u0442\u0430\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f',
|
||||
'type': 'date', 'value': None,
|
||||
'name': '\u041a\u043e\u043d\u0435\u0446\u041f\u0435\u0440\u0438\u043e\u0434\u0430',
|
||||
'title': '\u041a\u043e\u043d\u0435\u0446 \u043f\u0435\u0440\u0438\u043e\u0434\u0430',
|
||||
'type': 'date', 'value': '0001-01-01T00:00:00',
|
||||
'useRestriction': True,
|
||||
'expression': f'&{param_name}.\u0414\u0430\u0442\u0430\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f',
|
||||
'availableAsField': False,
|
||||
}
|
||||
emit_single_param(lines, None, end_parsed)
|
||||
|
||||
@@ -806,9 +1004,21 @@ AREA_STYLE_PRESETS = {
|
||||
}
|
||||
|
||||
|
||||
def load_user_styles(base_dir):
|
||||
for d in [base_dir, os.getcwd()]:
|
||||
p = os.path.join(d, 'skd-styles.json')
|
||||
def load_user_styles(base_dir, output_path=None):
|
||||
# Search order (first found wins): 1) definition dir, 2) cwd, 3) scan-up from OutputPath for presets/skills/skd/
|
||||
search_paths = [
|
||||
os.path.join(base_dir, 'skd-styles.json'),
|
||||
os.path.join(os.getcwd(), 'skd-styles.json'),
|
||||
]
|
||||
if output_path:
|
||||
scan_dir = os.path.dirname(output_path)
|
||||
while scan_dir:
|
||||
search_paths.append(os.path.join(scan_dir, 'presets', 'skills', 'skd', 'skd-styles.json'))
|
||||
parent_dir = os.path.dirname(scan_dir)
|
||||
if parent_dir == scan_dir:
|
||||
break
|
||||
scan_dir = parent_dir
|
||||
for p in search_paths:
|
||||
if os.path.isfile(p):
|
||||
with open(p, 'r', encoding='utf-8-sig') as f:
|
||||
user_styles = json.load(f)
|
||||
@@ -827,7 +1037,7 @@ def _emit_color_value(lines, color, indent):
|
||||
lines.append(f'{indent}<dcscor:value xsi:type="v8ui:Color">{esc_xml(color)}</dcscor:value>')
|
||||
|
||||
|
||||
def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0):
|
||||
def _emit_cell_appearance(lines, style, width=0, v_merge=False, h_merge=False, min_height=0, extra_items=None):
|
||||
ind = '\t\t\t\t\t'
|
||||
lines.append('\t\t\t\t<dcsat:appearance>')
|
||||
# Background color
|
||||
@@ -891,11 +1101,11 @@ def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0):
|
||||
if width and width > 0:
|
||||
lines.append(f'{ind}<dcscor:item>')
|
||||
lines.append(f'{ind}\t<dcscor:parameter>\u041c\u0438\u043d\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f\u0428\u0438\u0440\u0438\u043d\u0430</dcscor:parameter>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:decimal">{width}</dcscor:value>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:decimal">{fmt_dec(width)}</dcscor:value>')
|
||||
lines.append(f'{ind}</dcscor:item>')
|
||||
lines.append(f'{ind}<dcscor:item>')
|
||||
lines.append(f'{ind}\t<dcscor:parameter>\u041c\u0430\u043a\u0441\u0438\u043c\u0430\u043b\u044c\u043d\u0430\u044f\u0428\u0438\u0440\u0438\u043d\u0430</dcscor:parameter>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:decimal">{width}</dcscor:value>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:decimal">{fmt_dec(width)}</dcscor:value>')
|
||||
lines.append(f'{ind}</dcscor:item>')
|
||||
# Min height
|
||||
if min_height and min_height > 0:
|
||||
@@ -909,6 +1119,16 @@ def _emit_cell_appearance(lines, style, width=0, v_merge=False, min_height=0):
|
||||
lines.append(f'{ind}\t<dcscor:parameter>\u041e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u041f\u043e\u0412\u0435\u0440\u0442\u0438\u043a\u0430\u043b\u0438</dcscor:parameter>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:boolean">true</dcscor:value>')
|
||||
lines.append(f'{ind}</dcscor:item>')
|
||||
# Horizontal merge
|
||||
if h_merge:
|
||||
lines.append(f'{ind}<dcscor:item>')
|
||||
lines.append(f'{ind}\t<dcscor:parameter>\u041e\u0431\u044a\u0435\u0434\u0438\u043d\u044f\u0442\u044c\u041f\u043e\u0413\u043e\u0440\u0438\u0437\u043e\u043d\u0442\u0430\u043b\u0438</dcscor:parameter>')
|
||||
lines.append(f'{ind}\t<dcscor:value xsi:type="xs:boolean">true</dcscor:value>')
|
||||
lines.append(f'{ind}</dcscor:item>')
|
||||
# Extra appearance items (e.g. drilldown)
|
||||
if extra_items:
|
||||
for ei in extra_items:
|
||||
lines.append(ei)
|
||||
lines.append('\t\t\t\t</dcsat:appearance>')
|
||||
|
||||
|
||||
@@ -924,7 +1144,7 @@ def _emit_area_template_dsl(lines, t):
|
||||
min_height = float(t.get('minHeight', 0))
|
||||
col_count = len(widths) if widths else len(rows[0])
|
||||
|
||||
# Build merge map
|
||||
# Build vertical merge map
|
||||
v_merge = {}
|
||||
for r in range(len(rows) - 1, 0, -1):
|
||||
v_merge[r] = {}
|
||||
@@ -935,6 +1155,22 @@ def _emit_area_template_dsl(lines, t):
|
||||
if 0 not in v_merge:
|
||||
v_merge[0] = {}
|
||||
|
||||
# Build horizontal merge map
|
||||
h_merge = {}
|
||||
for r in range(len(rows)):
|
||||
h_merge[r] = {}
|
||||
for c in range(col_count):
|
||||
cell_val = rows[r][c] if c < len(rows[r]) else None
|
||||
if isinstance(cell_val, str) and cell_val == '>':
|
||||
h_merge[r][c] = True
|
||||
|
||||
# Build drilldown map: param_name -> drilldown_value
|
||||
drilldown_map = {}
|
||||
if t.get('parameters'):
|
||||
for tp in t['parameters']:
|
||||
if tp.get('drilldown'):
|
||||
drilldown_map[str(tp['name'])] = str(tp['drilldown'])
|
||||
|
||||
lines.append('\t<template>')
|
||||
lines.append(f'\t\t<name>{esc_xml(str(t["name"]))}</name>')
|
||||
lines.append('\t\t<template xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template" xsi:type="dcsat:AreaTemplate">')
|
||||
@@ -944,37 +1180,41 @@ def _emit_area_template_dsl(lines, t):
|
||||
for c in range(col_count):
|
||||
cell_val = rows[r][c] if c < len(rows[r]) else None
|
||||
w = float(widths[c]) if c < len(widths) else 0
|
||||
is_merged = v_merge.get(r, {}).get(c, False)
|
||||
# Check if this cell starts a vertical merge
|
||||
starts_v_merge = False
|
||||
for nr in range(r + 1, len(rows)):
|
||||
if v_merge.get(nr, {}).get(c, False):
|
||||
starts_v_merge = True
|
||||
else:
|
||||
break
|
||||
|
||||
is_v_merged = v_merge.get(r, {}).get(c, False)
|
||||
is_h_merged = h_merge.get(r, {}).get(c, False)
|
||||
lines.append('\t\t\t\t<dcsat:tableCell>')
|
||||
if is_merged:
|
||||
if is_v_merged:
|
||||
_emit_cell_appearance(lines, style, w, True)
|
||||
elif is_h_merged:
|
||||
_emit_cell_appearance(lines, style, w, h_merge=True)
|
||||
else:
|
||||
cell_extra_items = []
|
||||
if cell_val is not None and str(cell_val) != '':
|
||||
cell_str = str(cell_val)
|
||||
# Unescape \| and \>
|
||||
if cell_str == '\\|':
|
||||
cell_str = '|'
|
||||
elif cell_str == '\\>':
|
||||
cell_str = '>'
|
||||
m = re.match(r'^\{(.+)\}$', cell_str)
|
||||
if m:
|
||||
param_name = m.group(1)
|
||||
lines.append('\t\t\t\t\t<dcsat:item xsi:type="dcsat:Field">')
|
||||
lines.append(f'\t\t\t\t\t\t<dcsat:value xsi:type="dcscor:Parameter">{esc_xml(m.group(1))}</dcsat:value>')
|
||||
lines.append(f'\t\t\t\t\t\t<dcsat:value xsi:type="dcscor:Parameter">{esc_xml(param_name)}</dcsat:value>')
|
||||
lines.append('\t\t\t\t\t</dcsat:item>')
|
||||
# Build drilldown appearance extra items
|
||||
if param_name in drilldown_map:
|
||||
dd_val = drilldown_map[param_name]
|
||||
cell_extra_items.append('\t\t\t\t\t<dcscor:item>')
|
||||
cell_extra_items.append(f'\t\t\t\t\t\t<dcscor:parameter>\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430</dcscor:parameter>')
|
||||
cell_extra_items.append(f'\t\t\t\t\t\t<dcscor:value xsi:type="dcscor:Parameter">\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_{dd_val}</dcscor:value>')
|
||||
cell_extra_items.append('\t\t\t\t\t</dcscor:item>')
|
||||
else:
|
||||
lines.append('\t\t\t\t\t<dcsat:item xsi:type="dcsat:Field">')
|
||||
lines.append('\t\t\t\t\t\t<dcsat:value xsi:type="v8:LocalStringType">')
|
||||
lines.append('\t\t\t\t\t\t\t<v8:item>')
|
||||
lines.append('\t\t\t\t\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'\t\t\t\t\t\t\t\t<v8:content>{esc_xml(cell_str)}</v8:content>')
|
||||
lines.append('\t\t\t\t\t\t\t</v8:item>')
|
||||
lines.append('\t\t\t\t\t\t</dcsat:value>')
|
||||
emit_mltext(lines, '\t\t\t\t\t\t', 'dcsat:value', cell_str)
|
||||
lines.append('\t\t\t\t\t</dcsat:item>')
|
||||
h = min_height if r == 0 else 0
|
||||
_emit_cell_appearance(lines, style, w, starts_v_merge, h)
|
||||
_emit_cell_appearance(lines, style, w, False, False, h, cell_extra_items or None)
|
||||
lines.append('\t\t\t\t</dcsat:tableCell>')
|
||||
lines.append('\t\t\t</dcsat:item>')
|
||||
|
||||
@@ -985,6 +1225,17 @@ def _emit_area_template_dsl(lines, t):
|
||||
lines.append(f'\t\t\t<dcsat:name>{esc_xml(str(tp["name"]))}</dcsat:name>')
|
||||
lines.append(f'\t\t\t<dcsat:expression>{esc_xml(str(tp["expression"]))}</dcsat:expression>')
|
||||
lines.append('\t\t</parameter>')
|
||||
# Drilldown parameter
|
||||
if tp.get('drilldown'):
|
||||
dd_val = str(tp['drilldown'])
|
||||
lines.append('\t\t<parameter xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template" xsi:type="dcsat:DetailsAreaTemplateParameter">')
|
||||
lines.append(f'\t\t\t<dcsat:name>\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_{esc_xml(dd_val)}</dcsat:name>')
|
||||
lines.append('\t\t\t<dcsat:fieldExpression>')
|
||||
lines.append('\t\t\t\t<dcsat:field>\u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430</dcsat:field>')
|
||||
lines.append(f'\t\t\t\t<dcsat:expression>"{esc_xml(dd_val)}"</dcsat:expression>')
|
||||
lines.append('\t\t\t</dcsat:fieldExpression>')
|
||||
lines.append('\t\t\t<dcsat:mainAction>DrillDown</dcsat:mainAction>')
|
||||
lines.append('\t\t</parameter>')
|
||||
lines.append('\t</template>')
|
||||
|
||||
|
||||
@@ -1007,6 +1258,17 @@ def emit_templates(lines, defn):
|
||||
lines.append(f'\t\t\t<dcsat:name>{esc_xml(str(tp["name"]))}</dcsat:name>')
|
||||
lines.append(f'\t\t\t<dcsat:expression>{esc_xml(str(tp["expression"]))}</dcsat:expression>')
|
||||
lines.append('\t\t</parameter>')
|
||||
# Drilldown parameter
|
||||
if tp.get('drilldown'):
|
||||
dd_val = str(tp['drilldown'])
|
||||
lines.append('\t\t<parameter xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template" xsi:type="dcsat:DetailsAreaTemplateParameter">')
|
||||
lines.append(f'\t\t\t<dcsat:name>\u0420\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u0430_{esc_xml(dd_val)}</dcsat:name>')
|
||||
lines.append('\t\t\t<dcsat:fieldExpression>')
|
||||
lines.append('\t\t\t\t<dcsat:field>\u0418\u043c\u044f\u0420\u0435\u0441\u0443\u0440\u0441\u0430</dcsat:field>')
|
||||
lines.append(f'\t\t\t\t<dcsat:expression>"{esc_xml(dd_val)}"</dcsat:expression>')
|
||||
lines.append('\t\t\t</dcsat:fieldExpression>')
|
||||
lines.append('\t\t\t<dcsat:mainAction>DrillDown</dcsat:mainAction>')
|
||||
lines.append('\t\t</parameter>')
|
||||
lines.append('\t</template>')
|
||||
|
||||
|
||||
@@ -1016,11 +1278,19 @@ def emit_group_templates(lines, defn):
|
||||
if not defn.get('groupTemplates'):
|
||||
return
|
||||
for gt in defn['groupTemplates']:
|
||||
lines.append('\t<groupTemplate>')
|
||||
lines.append(f'\t\t<groupField>{esc_xml(str(gt["groupField"]))}</groupField>')
|
||||
lines.append(f'\t\t<templateType>{esc_xml(str(gt["templateType"]))}</templateType>')
|
||||
ttype = str(gt.get('templateType', '')) or 'Header'
|
||||
is_header = (ttype == 'GroupHeader')
|
||||
tag = 'groupHeaderTemplate' if is_header else 'groupTemplate'
|
||||
xml_ttype = 'Header' if is_header else ttype
|
||||
|
||||
lines.append(f'\t<{tag}>')
|
||||
if gt.get('groupName'):
|
||||
lines.append(f'\t\t<groupName>{esc_xml(str(gt["groupName"]))}</groupName>')
|
||||
elif gt.get('groupField'):
|
||||
lines.append(f'\t\t<groupField>{esc_xml(str(gt["groupField"]))}</groupField>')
|
||||
lines.append(f'\t\t<templateType>{esc_xml(xml_ttype)}</templateType>')
|
||||
lines.append(f'\t\t<template>{esc_xml(str(gt["template"]))}</template>')
|
||||
lines.append('\t</groupTemplate>')
|
||||
lines.append(f'\t</{tag}>')
|
||||
|
||||
|
||||
# === Settings Variants ===
|
||||
@@ -1039,6 +1309,21 @@ def emit_selection(lines, items, indent, skip_auto=False):
|
||||
lines.append(f'{indent}\t<dcsset:item xsi:type="dcsset:SelectedItemField">')
|
||||
lines.append(f'{indent}\t\t<dcsset:field>{esc_xml(item)}</dcsset:field>')
|
||||
lines.append(f'{indent}\t</dcsset:item>')
|
||||
elif item.get('folder'):
|
||||
lines.append(f'{indent}\t<dcsset:item xsi:type="dcsset:SelectedItemFolder">')
|
||||
lines.append(f'{indent}\t\t<dcsset:lwsTitle>')
|
||||
lines.append(f'{indent}\t\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:content>{esc_xml(str(item["folder"]))}</v8:content>')
|
||||
lines.append(f'{indent}\t\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t\t</dcsset:lwsTitle>')
|
||||
for sub in (item.get('items') or []):
|
||||
sub_name = str(sub.get('field', sub)) if isinstance(sub, dict) else str(sub)
|
||||
lines.append(f'{indent}\t\t<dcsset:item xsi:type="dcsset:SelectedItemField">')
|
||||
lines.append(f'{indent}\t\t\t<dcsset:field>{esc_xml(sub_name)}</dcsset:field>')
|
||||
lines.append(f'{indent}\t\t</dcsset:item>')
|
||||
lines.append(f'{indent}\t\t<dcsset:placement>Auto</dcsset:placement>')
|
||||
lines.append(f'{indent}\t</dcsset:item>')
|
||||
else:
|
||||
lines.append(f'{indent}\t<dcsset:item xsi:type="dcsset:SelectedItemField">')
|
||||
lines.append(f'{indent}\t\t<dcsset:field>{esc_xml(str(item["field"]))}</dcsset:field>')
|
||||
@@ -1062,6 +1347,19 @@ def emit_filter_item(lines, item, indent):
|
||||
lines.append(f'{indent}\t<dcsset:groupType>{group_type}</dcsset:groupType>')
|
||||
if item.get('items'):
|
||||
for sub in item['items']:
|
||||
if isinstance(sub, str):
|
||||
parsed = parse_filter_shorthand(sub)
|
||||
sub = {'field': parsed['field'], 'op': parsed['op']}
|
||||
if parsed['use'] is False:
|
||||
sub['use'] = False
|
||||
if parsed.get('value') is not None:
|
||||
sub['value'] = parsed['value']
|
||||
if parsed.get('valueType'):
|
||||
sub['valueType'] = parsed['valueType']
|
||||
if parsed.get('userSettingID'):
|
||||
sub['userSettingID'] = parsed['userSettingID']
|
||||
if parsed.get('viewMode'):
|
||||
sub['viewMode'] = parsed['viewMode']
|
||||
emit_filter_item(lines, sub, f'{indent}\t')
|
||||
lines.append(f'{indent}</dcsset:item>')
|
||||
return
|
||||
@@ -1097,12 +1395,7 @@ def emit_filter_item(lines, item, indent):
|
||||
lines.append(f'{indent}\t<dcsset:right xsi:type="{vt}">{v_str}</dcsset:right>')
|
||||
|
||||
if item.get('presentation'):
|
||||
lines.append(f'{indent}\t<dcsset:presentation xsi:type="v8:LocalStringType">')
|
||||
lines.append(f'{indent}\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t<v8:content>{esc_xml(str(item["presentation"]))}</v8:content>')
|
||||
lines.append(f'{indent}\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t</dcsset:presentation>')
|
||||
emit_mltext(lines, f'{indent}\t', 'dcsset:presentation', item["presentation"])
|
||||
|
||||
if item.get('viewMode'):
|
||||
lines.append(f'{indent}\t<dcsset:viewMode>{esc_xml(str(item["viewMode"]))}</dcsset:viewMode>')
|
||||
@@ -1112,12 +1405,7 @@ def emit_filter_item(lines, item, indent):
|
||||
lines.append(f'{indent}\t<dcsset:userSettingID>{esc_xml(uid)}</dcsset:userSettingID>')
|
||||
|
||||
if item.get('userSettingPresentation'):
|
||||
lines.append(f'{indent}\t<dcsset:userSettingPresentation xsi:type="v8:LocalStringType">')
|
||||
lines.append(f'{indent}\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t<v8:content>{esc_xml(str(item["userSettingPresentation"]))}</v8:content>')
|
||||
lines.append(f'{indent}\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t</dcsset:userSettingPresentation>')
|
||||
emit_mltext(lines, f'{indent}\t', 'dcsset:userSettingPresentation', item["userSettingPresentation"])
|
||||
|
||||
lines.append(f'{indent}</dcsset:item>')
|
||||
|
||||
@@ -1191,13 +1479,8 @@ def emit_appearance_value(lines, key, val, indent):
|
||||
lines.append(f'{indent}\t<dcscor:value xsi:type="v8ui:Color">{esc_xml(actual_val)}</dcscor:value>')
|
||||
elif actual_val == 'true' or actual_val == 'false':
|
||||
lines.append(f'{indent}\t<dcscor:value xsi:type="xs:boolean">{actual_val}</dcscor:value>')
|
||||
elif key == '\u0422\u0435\u043a\u0441\u0442' or key == '\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a':
|
||||
lines.append(f'{indent}\t<dcscor:value xsi:type="v8:LocalStringType">')
|
||||
lines.append(f'{indent}\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t<v8:content>{esc_xml(actual_val)}</v8:content>')
|
||||
lines.append(f'{indent}\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t</dcscor:value>')
|
||||
elif key in ('\u0422\u0435\u043a\u0441\u0442', '\u0417\u0430\u0433\u043e\u043b\u043e\u0432\u043e\u043a', '\u0424\u043e\u0440\u043c\u0430\u0442'):
|
||||
emit_mltext(lines, f'{indent}\t', 'dcscor:value', actual_val)
|
||||
else:
|
||||
lines.append(f'{indent}\t<dcscor:value xsi:type="xs:string">{esc_xml(actual_val)}</dcscor:value>')
|
||||
lines.append(f'{indent}</dcscor:item>')
|
||||
@@ -1262,12 +1545,7 @@ def emit_output_parameters(lines, params, indent):
|
||||
lines.append(f'{indent}\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
||||
lines.append(f'{indent}\t\t<dcscor:parameter>{esc_xml(key)}</dcscor:parameter>')
|
||||
if ptype == 'mltext':
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="v8:LocalStringType">')
|
||||
lines.append(f'{indent}\t\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:content>{esc_xml(val_str)}</v8:content>')
|
||||
lines.append(f'{indent}\t\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t\t</dcscor:value>')
|
||||
emit_mltext(lines, f'{indent}\t\t', 'dcscor:value', val_str)
|
||||
else:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="{ptype}">{esc_xml(val_str)}</dcscor:value>')
|
||||
lines.append(f'{indent}\t</dcscor:item>')
|
||||
@@ -1303,18 +1581,29 @@ def emit_data_parameters(lines, items, indent):
|
||||
lines.append(f'{indent}\t\t<dcscor:parameter>{esc_xml(str(dp["parameter"]))}</dcscor:parameter>')
|
||||
|
||||
# Value
|
||||
if dp.get('value') is not None:
|
||||
if dp.get('nilValue') is True:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:nil="true"/>')
|
||||
elif dp.get('value') is not None:
|
||||
val = dp['value']
|
||||
vtype = str(dp.get('valueType') or '')
|
||||
if isinstance(val, dict) and val.get('variant'):
|
||||
# StandardPeriod
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{indent}\t\t\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(str(val["variant"]))}</v8:variant>')
|
||||
lines.append(f'{indent}\t\t\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>')
|
||||
lines.append(f'{indent}\t\t\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>')
|
||||
lines.append(f'{indent}\t\t</dcscor:value>')
|
||||
elif isinstance(val, bool):
|
||||
elif vtype == 'boolean' or isinstance(val, bool):
|
||||
bv = str(val).lower()
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:boolean">{esc_xml(bv)}</dcscor:value>')
|
||||
elif re.match(r'^\d{4}-\d{2}-\d{2}T', str(val)):
|
||||
elif re.match(r'^date', vtype) or re.match(r'^\d{4}-\d{2}-\d{2}T', str(val)):
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:dateTime">{esc_xml(str(val))}</dcscor:value>')
|
||||
elif re.match(r'^decimal', vtype):
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:decimal">{esc_xml(str(val))}</dcscor:value>')
|
||||
elif re.match(r'^string', vtype):
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(val))}</dcscor:value>')
|
||||
elif re.match(r'^(\u041f\u043b\u0430\u043d\u0421\u0447\u0435\u0442\u043e\u0432|\u0421\u043f\u0440\u0430\u0432\u043e\u0447\u043d\u0438\u043a|\u041f\u0435\u0440\u0435\u0447\u0438\u0441\u043b\u0435\u043d\u0438\u0435|\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442|\u041f\u043b\u0430\u043d\u0412\u0438\u0434\u043e\u0432\u0425\u0430\u0440\u0430\u043a\u0442\u0435\u0440\u0438\u0441\u0442\u0438\u043a|\u041f\u043b\u0430\u043d\u0412\u0438\u0434\u043e\u0432\u0420\u0430\u0441\u0447\u0435\u0442\u0430|\u0411\u0438\u0437\u043d\u0435\u0441\u041f\u0440\u043e\u0446\u0435\u0441\u0441|\u0417\u0430\u0434\u0430\u0447\u0430|\u0420\u0435\u0433\u0438\u0441\u0442\u0440\u0421\u0432\u0435\u0434\u0435\u043d\u0438\u0439|\u041f\u043b\u0430\u043d\u041e\u0431\u043c\u0435\u043d\u0430)\.', str(val)) or re.match(r'^(ChartOfAccounts|Catalog|Enum|Document|ChartOfCharacteristicTypes|ChartOfCalculationTypes|BusinessProcess|Task|InformationRegister|ExchangePlan)\.', str(val)):
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="dcscor:DesignTimeValue">{esc_xml(str(val))}</dcscor:value>')
|
||||
else:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(val))}</dcscor:value>')
|
||||
|
||||
@@ -1326,12 +1615,7 @@ def emit_data_parameters(lines, items, indent):
|
||||
lines.append(f'{indent}\t\t<dcsset:userSettingID>{esc_xml(uid)}</dcsset:userSettingID>')
|
||||
|
||||
if dp.get('userSettingPresentation'):
|
||||
lines.append(f'{indent}\t\t<dcsset:userSettingPresentation xsi:type="v8:LocalStringType">')
|
||||
lines.append(f'{indent}\t\t\t<v8:item>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'{indent}\t\t\t\t<v8:content>{esc_xml(str(dp["userSettingPresentation"]))}</v8:content>')
|
||||
lines.append(f'{indent}\t\t\t</v8:item>')
|
||||
lines.append(f'{indent}\t\t</dcsset:userSettingPresentation>')
|
||||
emit_mltext(lines, f'{indent}\t\t', 'dcsset:userSettingPresentation', dp["userSettingPresentation"])
|
||||
|
||||
lines.append(f'{indent}\t</dcscor:item>')
|
||||
lines.append(f'{indent}</dcsset:dataParameters>')
|
||||
@@ -1376,7 +1660,13 @@ def parse_structure_shorthand(s):
|
||||
if re.match(r'(?i)^(details|\u0434\u0435\u0442\u0430\u043b\u0438)$', seg):
|
||||
group['groupBy'] = []
|
||||
else:
|
||||
group['groupBy'] = [seg]
|
||||
# Named group: "ИмяГруппы[Поле]"
|
||||
m_named = re.match(r'^(.+)\[(.+)\]$', seg)
|
||||
if m_named:
|
||||
group['name'] = m_named.group(1).strip()
|
||||
group['groupBy'] = [m_named.group(2).strip()]
|
||||
else:
|
||||
group['groupBy'] = [seg]
|
||||
|
||||
if innermost is not None:
|
||||
group['children'] = [innermost]
|
||||
@@ -1388,7 +1678,7 @@ def parse_structure_shorthand(s):
|
||||
|
||||
|
||||
def emit_structure_item(lines, item, indent):
|
||||
item_type = str(item.get('type', ''))
|
||||
item_type = str(item.get('type', 'group'))
|
||||
|
||||
if item_type == 'group':
|
||||
lines.append(f'{indent}<dcsset:item xsi:type="dcsset:StructureItemGroup">')
|
||||
@@ -1396,7 +1686,7 @@ def emit_structure_item(lines, item, indent):
|
||||
if item.get('name'):
|
||||
lines.append(f'{indent}\t<dcsset:name>{esc_xml(str(item["name"]))}</dcsset:name>')
|
||||
|
||||
emit_group_items(lines, item.get('groupBy'), f'{indent}\t')
|
||||
emit_group_items(lines, item.get('groupBy') or item.get('groupFields'), f'{indent}\t')
|
||||
|
||||
# Default order to ["Auto"] if not specified
|
||||
order_items = item.get('order') or ['Auto']
|
||||
@@ -1428,7 +1718,7 @@ def emit_structure_item(lines, item, indent):
|
||||
if item.get('columns'):
|
||||
for col in item['columns']:
|
||||
lines.append(f'{indent}\t<dcsset:column>')
|
||||
emit_group_items(lines, col.get('groupBy'), f'{indent}\t\t')
|
||||
emit_group_items(lines, col.get('groupBy') or col.get('groupFields'), f'{indent}\t\t')
|
||||
col_order = col.get('order') or ['Auto']
|
||||
emit_order(lines, col_order, f'{indent}\t\t')
|
||||
col_sel = col.get('selection') or ['Auto']
|
||||
@@ -1441,7 +1731,7 @@ def emit_structure_item(lines, item, indent):
|
||||
lines.append(f'{indent}\t<dcsset:row>')
|
||||
if row.get('name'):
|
||||
lines.append(f'{indent}\t\t<dcsset:name>{esc_xml(str(row["name"]))}</dcsset:name>')
|
||||
emit_group_items(lines, row.get('groupBy'), f'{indent}\t\t')
|
||||
emit_group_items(lines, row.get('groupBy') or row.get('groupFields'), f'{indent}\t\t')
|
||||
row_order = row.get('order') or ['Auto']
|
||||
emit_order(lines, row_order, f'{indent}\t\t')
|
||||
row_sel = row.get('selection') or ['Auto']
|
||||
@@ -1459,7 +1749,7 @@ def emit_structure_item(lines, item, indent):
|
||||
# Points
|
||||
if item.get('points'):
|
||||
lines.append(f'{indent}\t<dcsset:point>')
|
||||
emit_group_items(lines, item['points'].get('groupBy'), f'{indent}\t\t')
|
||||
emit_group_items(lines, item['points'].get('groupBy') or item['points'].get('groupFields'), f'{indent}\t\t')
|
||||
pt_order = item['points'].get('order') or ['Auto']
|
||||
emit_order(lines, pt_order, f'{indent}\t\t')
|
||||
pt_sel = item['points'].get('selection') or ['Auto']
|
||||
@@ -1469,7 +1759,7 @@ def emit_structure_item(lines, item, indent):
|
||||
# Series
|
||||
if item.get('series'):
|
||||
lines.append(f'{indent}\t<dcsset:series>')
|
||||
emit_group_items(lines, item['series'].get('groupBy'), f'{indent}\t\t')
|
||||
emit_group_items(lines, item['series'].get('groupBy') or item['series'].get('groupFields'), f'{indent}\t\t')
|
||||
sr_order = item['series'].get('order') or ['Auto']
|
||||
emit_order(lines, sr_order, f'{indent}\t\t')
|
||||
sr_sel = item['series'].get('selection') or ['Auto']
|
||||
@@ -1507,13 +1797,8 @@ def emit_settings_variants(lines, defn):
|
||||
lines.append('\t<settingsVariant>')
|
||||
lines.append(f'\t\t<dcsset:name>{esc_xml(str(v["name"]))}</dcsset:name>')
|
||||
|
||||
pres = str(v.get('presentation', '')) or str(v.get('title', '')) or str(v['name'])
|
||||
lines.append('\t\t<dcsset:presentation xsi:type="v8:LocalStringType">')
|
||||
lines.append('\t\t\t<v8:item>')
|
||||
lines.append('\t\t\t\t<v8:lang>ru</v8:lang>')
|
||||
lines.append(f'\t\t\t\t<v8:content>{esc_xml(pres)}</v8:content>')
|
||||
lines.append('\t\t\t</v8:item>')
|
||||
lines.append('\t\t</dcsset:presentation>')
|
||||
pres = v.get('presentation') or v.get('title') or v['name']
|
||||
emit_mltext(lines, '\t\t', 'dcsset:presentation', pres)
|
||||
|
||||
lines.append('\t\t<dcsset:settings xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows">')
|
||||
|
||||
@@ -1540,7 +1825,46 @@ def emit_settings_variants(lines, defn):
|
||||
emit_output_parameters(lines, s['outputParameters'], '\t\t\t')
|
||||
|
||||
# DataParameters
|
||||
if s.get('dataParameters'):
|
||||
if s.get('dataParameters') == 'auto':
|
||||
# Auto-generate dataParameters for all non-hidden params.
|
||||
# Pattern follows 1C Designer / ERP persistence:
|
||||
# value set (non-default) → emit value, use=true (implicit)
|
||||
# value missing / Custom period → <use>false</use> + <value xsi:nil="true"/>
|
||||
auto_dp = []
|
||||
for ap in _all_params:
|
||||
if ap['hidden']:
|
||||
continue
|
||||
item = {
|
||||
'parameter': ap['name'],
|
||||
'userSettingID': 'auto',
|
||||
}
|
||||
has_meaningful_value = False
|
||||
|
||||
if ap.get('type') == 'StandardPeriod':
|
||||
variant = 'Custom'
|
||||
av = ap.get('value')
|
||||
if av is not None:
|
||||
if isinstance(av, dict) and av.get('variant'):
|
||||
variant = str(av['variant'])
|
||||
elif str(av):
|
||||
variant = str(av)
|
||||
item['value'] = {'variant': variant}
|
||||
if variant != 'Custom':
|
||||
has_meaningful_value = True
|
||||
elif ap.get('value') is not None and str(ap.get('value')) != '':
|
||||
item['value'] = ap['value']
|
||||
item['valueType'] = str(ap.get('type') or '')
|
||||
has_meaningful_value = True
|
||||
else:
|
||||
item['nilValue'] = True
|
||||
|
||||
if not has_meaningful_value:
|
||||
item['use'] = False
|
||||
|
||||
auto_dp.append(item)
|
||||
if auto_dp:
|
||||
emit_data_parameters(lines, auto_dp, '\t\t\t')
|
||||
elif s.get('dataParameters'):
|
||||
emit_data_parameters(lines, s['dataParameters'], '\t\t\t')
|
||||
|
||||
# Structure (supports string shorthand)
|
||||
@@ -1548,6 +1872,8 @@ def emit_settings_variants(lines, defn):
|
||||
struct_items = s['structure']
|
||||
if isinstance(struct_items, str):
|
||||
struct_items = parse_structure_shorthand(struct_items)
|
||||
elif isinstance(struct_items, dict):
|
||||
struct_items = [struct_items]
|
||||
for item in struct_items:
|
||||
emit_structure_item(lines, item, '\t\t\t')
|
||||
|
||||
@@ -1595,7 +1921,8 @@ def main():
|
||||
query_base_dir = os.path.dirname(def_file) if args.DefinitionFile else os.getcwd()
|
||||
|
||||
# Load user style presets
|
||||
load_user_styles(query_base_dir)
|
||||
out_path_resolved = args.OutputPath if os.path.isabs(args.OutputPath) else os.path.join(os.getcwd(), args.OutputPath)
|
||||
load_user_styles(query_base_dir, out_path_resolved)
|
||||
|
||||
# --- 2. Resolve defaults ---
|
||||
|
||||
|
||||
@@ -61,27 +61,63 @@ Shorthand: `"Имя [Заголовок]: тип @роль #ограничени
|
||||
|
||||
### add-calculated-field — добавить вычисляемое поле
|
||||
|
||||
Shorthand: `"Имя [Заголовок]: тип = Выражение"`.
|
||||
Shorthand: `"Имя [Заголовок]: тип = Выражение #noFilter #noOrder #noGroup"`.
|
||||
|
||||
```
|
||||
"Маржа = Продажа - Закупка"
|
||||
"Наценка [Наценка, %]: decimal(10,2) = Маржа / Закупка * 100"
|
||||
"Служебное: string = \"\" #noFilter #noOrder #noGroup"
|
||||
```
|
||||
|
||||
`#noFilter`, `#noOrder`, `#noGroup`, `#noField` → `<useRestriction>` (аналогично add-field).
|
||||
|
||||
Также добавляется в selection варианта.
|
||||
|
||||
### add-parameter — добавить параметр
|
||||
|
||||
```
|
||||
"Период: StandardPeriod = LastMonth @autoDates"
|
||||
"Период [Отчетный период]: StandardPeriod = LastMonth @autoDates"
|
||||
"Организация: CatalogRef.Организации"
|
||||
```
|
||||
|
||||
`@autoDates` генерирует `ДатаНачала` и `ДатаОкончания` автоматически.
|
||||
Shorthand: `"Имя [Заголовок]: тип = значение @флаги"`. `[Заголовок]` опциональный — добавляет `<title>`.
|
||||
|
||||
`@autoDates` генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра — для БСП-отчётов, чтобы получить пару полей «Начало/Конец» в панели быстрых настроек.
|
||||
|
||||
### modify-parameter — изменить существующий параметр
|
||||
|
||||
Находит параметр по имени, добавляет/обновляет свойства.
|
||||
|
||||
```
|
||||
"ПорядокОкругления use=Always"
|
||||
"ПорядокОкругления [Округление сумм] denyIncompleteValues=true"
|
||||
"ПериодОтчета [Отчетный период]" # только title
|
||||
"ПорядокОкругления availableValue=Перечисление.Округления.Окр1 presentation=руб."
|
||||
```
|
||||
|
||||
`[Заголовок]` опциональный — устанавливает или заменяет `<title>`. Можно вызывать без других kv-пар, чтобы только обновить title.
|
||||
|
||||
`availableValue=` добавляет один элемент списка допустимых значений (можно несколько через `;;`). Тип значения определяется автоматически (DesignTimeValue для ссылок).
|
||||
|
||||
### rename-parameter — переименовать параметр
|
||||
|
||||
Shorthand: `"OldName => NewName"`. Атомарно обновляет имя параметра, ссылки `&Имя` в выражениях других параметров (только полные совпадения, `&ПериодX` не задевается), и записи в `dataParameters` всех вариантов. Текст запроса не трогает — переименование строго в области параметров.
|
||||
|
||||
```
|
||||
"Период => ПериодОтчета"
|
||||
```
|
||||
|
||||
### reorder-parameters — переставить параметры в указанном порядке
|
||||
|
||||
Shorthand: `"Имя1, Имя2, Имя3"`. Частичный список — указанные параметры идут первыми в заданном порядке, остальные сохраняют исходный порядок и идут в конце. Параметры из списка, которых нет в схеме — warning, пропуск.
|
||||
|
||||
```
|
||||
"ПериодОтчета, НачалоПериода, КонецПериода"
|
||||
```
|
||||
|
||||
### add-filter — добавить фильтр в вариант
|
||||
|
||||
Shorthand: `"Поле оператор значение @флаги"`. Флаги: `@off`, `@user`, `@quickAccess`, `@normal`, `@inaccessible`.
|
||||
Shorthand: `"Поле оператор значение @флаги"`. Флаги: `@off` (use=false), `@user` (userSettingID=auto), `@quickAccess`, `@normal`, `@inaccessible`.
|
||||
|
||||
```
|
||||
"Номенклатура = _ @off @user"
|
||||
@@ -112,6 +148,15 @@ Shorthand: `"Поле [desc]"`. По умолчанию asc. `Auto` — авто
|
||||
```
|
||||
"Номенклатура"
|
||||
"Auto"
|
||||
"Folder(Поступление: ПолеА, ПолеБ, ПолеВ)"
|
||||
```
|
||||
|
||||
`Folder(Название: поле1, поле2)` — группа полей (SelectedItemFolder) с заголовком и `placement=Auto`.
|
||||
|
||||
`@group=ИмяГруппировки` — добавить в selection именованной группировки (вместо уровня варианта):
|
||||
|
||||
```
|
||||
"Folder(Поступление: ПолеА, ПолеБ) @group=ДанныеОтчета"
|
||||
```
|
||||
|
||||
### add-dataSetLink — добавить связь наборов данных
|
||||
@@ -157,7 +202,28 @@ Shorthand: `"Параметр = значение [when условие] [for По
|
||||
"Формат = ЧДЦ=2 for Цена, Сумма"
|
||||
```
|
||||
|
||||
Типы значений (автодетект): `web:*`/`style:*`/`win:*` → цвет, `true`/`false` → boolean, иначе строка.
|
||||
Типы значений appearance (автодетект): `web:*`/`style:*`/`win:*` → Color, `true`/`false` → Boolean, параметр `Формат`/`Текст`/`Заголовок` → LocalStringType, иначе String.
|
||||
|
||||
Типы значений фильтра (автодетект): `Перечисление.*`/`Справочник.*`/`ПланСчетов.*`/`Документ.*` → DesignTimeValue, `true`/`false` → Boolean, дата → DateTime, числа → Decimal, иначе String.
|
||||
|
||||
OrGroup: несколько условий через ` or ` в `when` объединяются в FilterItemGroup/OrGroup:
|
||||
|
||||
```
|
||||
"Формат = ЧЦ=15; ЧДЦ=0 when ПараметрыДанных.Округление = Перечисление.Округления.Окр1 or ПараметрыДанных.Округление = Перечисление.Округления.Окр1000"
|
||||
```
|
||||
|
||||
**Важно**: для параметров данных используйте префикс `ПараметрыДанных.` в поле фильтра.
|
||||
|
||||
### add-drilldown — подключить расшифровку к ресурсам в шаблонах
|
||||
|
||||
Value — имена ресурсов (как в полях/вычисляемых полях СКД) через запятую.
|
||||
|
||||
```
|
||||
"ПоступлениеИзПроизводства, ВыбытиеПрочее"
|
||||
"Сумма_Дт83, Сумма_Дт99, Сумма_68, Сумма_84"
|
||||
```
|
||||
|
||||
Подключает DrillDown по `ИмяРесурса` ко всем шаблонам, содержащим указанные ресурсы. Идемпотентно.
|
||||
|
||||
### set-query — заменить текст запроса
|
||||
|
||||
@@ -188,8 +254,11 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
|
||||
```
|
||||
"Организация > Номенклатура > details"
|
||||
"details"
|
||||
"СчетМеждународногоУчета @name=ДанныеОтчета"
|
||||
```
|
||||
|
||||
`@name=Имя` — присваивает имя группировке (`<dcsset:name>`). Используется для привязки шаблонов через `groupName`.
|
||||
|
||||
### modify-field — изменить существующее поле
|
||||
|
||||
Тот же shorthand что и `add-field`. Находит по dataPath, объединяет свойства (непустые переопределяют), сохраняет позицию.
|
||||
@@ -200,11 +269,18 @@ Shorthand: `"Поле1 > Поле2 > details"`. `details`/`детали` — д
|
||||
|
||||
### modify-filter — изменить существующий фильтр
|
||||
|
||||
Тот же shorthand что и `add-filter`. Находит по полю, обновляет оператор/значение/флаги.
|
||||
Тот же shorthand что и `add-filter`. Находит по полю, обновляет оператор/значение/флаги. См. правило для `<use>` ниже.
|
||||
|
||||
### modify-dataParameter — изменить параметр данных
|
||||
|
||||
Тот же shorthand что и `add-dataParameter`. Находит по имени, обновляет значение/флаги.
|
||||
Тот же shorthand что и `add-dataParameter`. Находит по имени, обновляет значение/флаги. См. правило для `<use>` ниже.
|
||||
|
||||
#### Правило `<use>` для modify-filter / modify-dataParameter
|
||||
|
||||
В отличие от `add-*`, в `modify-*` поле `<use>` обновляется **только если флаг задан явно**:
|
||||
- `@off` — установить `<use>false</use>`
|
||||
- `@on` — убрать существующий `<use>false</use>` (включить параметр)
|
||||
- ни `@off`, ни `@on` не задано — `<use>` не трогается, существующее значение сохраняется (важно: это значит, что отключённый параметр останется отключённым после модификации других свойств)
|
||||
|
||||
### remove-* и clear-*
|
||||
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
# skd-edit v1.3 — Atomic 1C DCS editor
|
||||
# skd-edit v1.11 — Atomic 1C DCS editor
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
|
||||
[Parameter(Mandatory)]
|
||||
[ValidateSet(
|
||||
"add-field","add-total","add-calculated-field","add-parameter","add-filter",
|
||||
"add-dataParameter","add-order","add-selection","add-dataSetLink",
|
||||
"add-dataSet","add-variant","add-conditionalAppearance",
|
||||
"add-dataSet","add-variant","add-conditionalAppearance","add-drilldown",
|
||||
"set-query","patch-query","set-outputParameter","set-structure",
|
||||
"modify-field","modify-filter","modify-dataParameter",
|
||||
"modify-field","modify-filter","modify-dataParameter","modify-parameter",
|
||||
"rename-parameter","reorder-parameters",
|
||||
"clear-selection","clear-order","clear-filter",
|
||||
"remove-field","remove-total","remove-calculated-field","remove-parameter","remove-filter")]
|
||||
[string]$Operation,
|
||||
@@ -250,40 +252,64 @@ function Parse-TotalShorthand {
|
||||
function Parse-CalcShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$title = ""
|
||||
# Extract [Title] first
|
||||
if ($s -match '\[([^\]]+)\]') {
|
||||
$title = $Matches[1]
|
||||
$s = $s -replace '\s*\[[^\]]+\]', ''
|
||||
}
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# the docs put them after `=`, and the closed flag set avoids matching
|
||||
# `#word` that happens to appear inside a string literal.
|
||||
$restrictPattern = '#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||
|
||||
$restrict = @()
|
||||
foreach ($m in [regex]::Matches($s, $restrictPattern)) {
|
||||
$restrict += $m.Groups[1].Value
|
||||
}
|
||||
$s = [regex]::Replace($s, "\s*$restrictPattern", '')
|
||||
|
||||
# Support "Name: Type = Expression" and "Name = Expression"
|
||||
$eqIdx = $s.IndexOf('=')
|
||||
if ($eqIdx -gt 0) {
|
||||
$left = $s.Substring(0, $eqIdx).Trim()
|
||||
$expression = $s.Substring($eqIdx + 1).Trim()
|
||||
|
||||
if ($left.Contains(':')) {
|
||||
$colonIdx = $left.IndexOf(':')
|
||||
$dataPath = $left.Substring(0, $colonIdx).Trim()
|
||||
$type = Resolve-TypeStr ($left.Substring($colonIdx + 1).Trim())
|
||||
return @{ dataPath = $dataPath; expression = $expression; type = $type; title = $title }
|
||||
}
|
||||
return @{ dataPath = $left; expression = $expression; type = ""; title = $title }
|
||||
$lhs = $s.Substring(0, $eqIdx)
|
||||
$rhs = $s.Substring($eqIdx + 1).Trim()
|
||||
} else {
|
||||
$lhs = $s
|
||||
$rhs = $null
|
||||
}
|
||||
return @{ dataPath = $s.Trim(); expression = ""; type = ""; title = $title }
|
||||
|
||||
$title = ""
|
||||
if ($lhs -match '\[([^\]]+)\]') {
|
||||
$title = $Matches[1]
|
||||
$lhs = $lhs -replace '\s*\[[^\]]+\]', ''
|
||||
}
|
||||
$lhs = $lhs.Trim()
|
||||
|
||||
if ($null -ne $rhs) {
|
||||
if ($lhs.Contains(':')) {
|
||||
$colonIdx = $lhs.IndexOf(':')
|
||||
$dataPath = $lhs.Substring(0, $colonIdx).Trim()
|
||||
$type = Resolve-TypeStr ($lhs.Substring($colonIdx + 1).Trim())
|
||||
return @{ dataPath = $dataPath; expression = $rhs; type = $type; title = $title; restrict = $restrict }
|
||||
}
|
||||
return @{ dataPath = $lhs; expression = $rhs; type = ""; title = $title; restrict = $restrict }
|
||||
}
|
||||
return @{ dataPath = $lhs; expression = ""; type = ""; title = $title; restrict = $restrict }
|
||||
}
|
||||
|
||||
function Parse-ParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false }
|
||||
$result = @{ name = ""; type = ""; value = $null; autoDates = $false; title = $null }
|
||||
|
||||
if ($s -match '@autoDates') {
|
||||
$result.autoDates = $true
|
||||
$s = $s -replace '\s*@autoDates', ''
|
||||
}
|
||||
|
||||
# Extract optional [Title] (mirrors Parse-FieldShorthand)
|
||||
if ($s -match '\[([^\]]*)\]') {
|
||||
$result.title = $Matches[1].Trim()
|
||||
$s = ($s -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
|
||||
}
|
||||
|
||||
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') {
|
||||
$result.name = $Matches[1].Trim()
|
||||
$result.type = Resolve-TypeStr ($Matches[2].Trim())
|
||||
@@ -300,7 +326,9 @@ function Parse-ParamShorthand {
|
||||
function Parse-FilterShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ field = ""; op = "Equal"; value = $null; use = $true; userSettingID = $null; viewMode = $null }
|
||||
# use is tristate: $null = not specified (modify-* won't touch),
|
||||
# $false = @off (explicit), $true = @on (explicit). add-* writes <use>false</use> only when $false.
|
||||
$result = @{ field = ""; op = "Equal"; value = $null; use = $null; userSettingID = $null; viewMode = $null }
|
||||
|
||||
if ($s -match '@user') {
|
||||
$result.userSettingID = "auto"
|
||||
@@ -310,6 +338,10 @@ function Parse-FilterShorthand {
|
||||
$result.use = $false
|
||||
$s = $s -replace '\s*@off', ''
|
||||
}
|
||||
if ($s -match '@on\b') {
|
||||
$result.use = $true
|
||||
$s = $s -replace '\s*@on\b', ''
|
||||
}
|
||||
if ($s -match '@quickAccess') {
|
||||
$result.viewMode = "QuickAccess"
|
||||
$s = $s -replace '\s*@quickAccess', ''
|
||||
@@ -357,6 +389,9 @@ function Parse-FilterShorthand {
|
||||
} elseif ($valPart -match '^\d+(\.\d+)?$') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:decimal"
|
||||
} elseif ($valPart -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "dcscor:DesignTimeValue"
|
||||
} else {
|
||||
$result.value = $valPart
|
||||
$result["valueType"] = "xs:string"
|
||||
@@ -372,7 +407,9 @@ function Parse-FilterShorthand {
|
||||
function Parse-DataParamShorthand {
|
||||
param([string]$s)
|
||||
|
||||
$result = @{ parameter = ""; value = $null; use = $true; userSettingID = $null; viewMode = $null }
|
||||
# use is tristate: $null = not specified (modify-* won't touch),
|
||||
# $false = @off (explicit), $true = @on (explicit). add-* writes <use>false</use> only when $false.
|
||||
$result = @{ parameter = ""; value = $null; use = $null; userSettingID = $null; viewMode = $null }
|
||||
|
||||
if ($s -match '@user') {
|
||||
$result.userSettingID = "auto"
|
||||
@@ -382,6 +419,10 @@ function Parse-DataParamShorthand {
|
||||
$result.use = $false
|
||||
$s = $s -replace '\s*@off', ''
|
||||
}
|
||||
if ($s -match '@on\b') {
|
||||
$result.use = $true
|
||||
$s = $s -replace '\s*@on\b', ''
|
||||
}
|
||||
if ($s -match '@quickAccess') {
|
||||
$result.viewMode = "QuickAccess"
|
||||
$s = $s -replace '\s*@quickAccess', ''
|
||||
@@ -503,12 +544,17 @@ function Parse-ConditionalAppearanceShorthand {
|
||||
$result.fields = @($forPart -split '\s*,\s*' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
}
|
||||
|
||||
# Parse "when" filter
|
||||
# Parse "when" filter (supports " or " for OrGroup)
|
||||
if ($whenIdx -ge 0) {
|
||||
$whenEnd = $s.Length
|
||||
if ($forIdx -gt $whenIdx) { $whenEnd = $forIdx }
|
||||
$whenPart = $s.Substring($whenIdx + 6, $whenEnd - $whenIdx - 6).Trim()
|
||||
$result.filter = Parse-FilterShorthand $whenPart
|
||||
$orParts = $whenPart -split '\s+or\s+'
|
||||
if ($orParts.Count -gt 1) {
|
||||
$result.filter = @($orParts | ForEach-Object { Parse-FilterShorthand $_.Trim() })
|
||||
} else {
|
||||
$result.filter = Parse-FilterShorthand $whenPart
|
||||
}
|
||||
}
|
||||
|
||||
# Parse main part: "Param = Value"
|
||||
@@ -535,6 +581,11 @@ function Parse-StructureShorthand {
|
||||
$seg = $segments[$i].Trim()
|
||||
$group = @{ type = "group" }
|
||||
|
||||
if ($seg -match '@name=(.+)') {
|
||||
$group["name"] = $Matches[1].Trim()
|
||||
$seg = ($seg -replace '\s*@name=.+', '').Trim()
|
||||
}
|
||||
|
||||
if ($seg -match '^(?i)(details|детали)$') {
|
||||
$group["groupBy"] = @()
|
||||
} else {
|
||||
@@ -739,6 +790,10 @@ function Build-CalcFieldFragment {
|
||||
$lines += (Build-MLTextXml -tag "title" -text $parsed.title -indent "$i`t")
|
||||
}
|
||||
|
||||
if ($parsed.restrict -and $parsed.restrict.Count -gt 0) {
|
||||
$lines += (Build-RestrictionXml -restrict $parsed.restrict -indent "$i`t")
|
||||
}
|
||||
|
||||
if ($parsed.type) {
|
||||
$lines += "$i`t<valueType>"
|
||||
$lines += (Build-ValueTypeXml -typeStr $parsed.type -indent "$i`t`t")
|
||||
@@ -759,6 +814,10 @@ function Build-ParamFragment {
|
||||
$lines += "$i<parameter>"
|
||||
$lines += "$i`t<name>$(Esc-Xml $parsed.name)</name>"
|
||||
|
||||
if ($parsed.title) {
|
||||
$lines += (Build-MLTextXml -tag "title" -text $parsed.title -indent "$i`t")
|
||||
}
|
||||
|
||||
if ($parsed.type) {
|
||||
$lines += "$i`t<valueType>"
|
||||
$lines += (Build-ValueTypeXml -typeStr $parsed.type -indent "$i`t`t")
|
||||
@@ -770,6 +829,8 @@ function Build-ParamFragment {
|
||||
if ($parsed.type -eq "StandardPeriod") {
|
||||
$lines += "$i`t<value xsi:type=`"v8:StandardPeriod`">"
|
||||
$lines += "$i`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $valStr)</v8:variant>"
|
||||
$lines += "$i`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
$lines += "$i`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
$lines += "$i`t</value>"
|
||||
} elseif ($parsed.type -match '^date') {
|
||||
$lines += "$i`t<value xsi:type=`"xs:dateTime`">$(Esc-Xml $valStr)</value>"
|
||||
@@ -788,25 +849,30 @@ function Build-ParamFragment {
|
||||
if ($parsed.autoDates) {
|
||||
$paramName = $parsed.name
|
||||
|
||||
# Canonical БСП pattern: title + valueType + value + useRestriction + expression
|
||||
$bLines = @()
|
||||
$bLines += "$i<parameter>"
|
||||
$bLines += "$i`t<name>ДатаНачала</name>"
|
||||
$bLines += (Build-MLTextXml -tag "title" -text "Начало периода" -indent "$i`t")
|
||||
$bLines += "$i`t<valueType>"
|
||||
$bLines += (Build-ValueTypeXml -typeStr "date" -indent "$i`t`t")
|
||||
$bLines += "$i`t</valueType>"
|
||||
$bLines += "$i`t<value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</value>"
|
||||
$bLines += "$i`t<useRestriction>true</useRestriction>"
|
||||
$bLines += "$i`t<expression>$(Esc-Xml "&$paramName.ДатаНачала")</expression>"
|
||||
$bLines += "$i`t<availableAsField>false</availableAsField>"
|
||||
$bLines += "$i</parameter>"
|
||||
$fragments += ($bLines -join "`r`n")
|
||||
|
||||
$eLines = @()
|
||||
$eLines += "$i<parameter>"
|
||||
$eLines += "$i`t<name>ДатаОкончания</name>"
|
||||
$eLines += (Build-MLTextXml -tag "title" -text "Конец периода" -indent "$i`t")
|
||||
$eLines += "$i`t<valueType>"
|
||||
$eLines += (Build-ValueTypeXml -typeStr "date" -indent "$i`t`t")
|
||||
$eLines += "$i`t</valueType>"
|
||||
$eLines += "$i`t<value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</value>"
|
||||
$eLines += "$i`t<useRestriction>true</useRestriction>"
|
||||
$eLines += "$i`t<expression>$(Esc-Xml "&$paramName.ДатаОкончания")</expression>"
|
||||
$eLines += "$i`t<availableAsField>false</availableAsField>"
|
||||
$eLines += "$i</parameter>"
|
||||
$fragments += ($eLines -join "`r`n")
|
||||
}
|
||||
@@ -853,6 +919,32 @@ function Build-SelectionItemFragment {
|
||||
$lines = @()
|
||||
if ($fieldName -eq "Auto") {
|
||||
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemAuto`"/>"
|
||||
} elseif ($fieldName -match '^Folder\((.+)\)$') {
|
||||
$inner = $Matches[1]
|
||||
$colonIdx = $inner.IndexOf(':')
|
||||
if ($colonIdx -gt 0) {
|
||||
$title = $inner.Substring(0, $colonIdx).Trim()
|
||||
$items = $inner.Substring($colonIdx + 1) -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||
} else {
|
||||
$title = ""
|
||||
$items = $inner -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ }
|
||||
}
|
||||
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemFolder`">"
|
||||
if ($title) {
|
||||
$lines += "$i`t<dcsset:lwsTitle>"
|
||||
$lines += "$i`t`t<v8:item>"
|
||||
$lines += "$i`t`t`t<v8:lang>ru</v8:lang>"
|
||||
$lines += "$i`t`t`t<v8:content>$(Esc-Xml $title)</v8:content>"
|
||||
$lines += "$i`t`t</v8:item>"
|
||||
$lines += "$i`t</dcsset:lwsTitle>"
|
||||
}
|
||||
foreach ($item in $items) {
|
||||
$lines += "$i`t<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
|
||||
$lines += "$i`t`t<dcsset:field>$(Esc-Xml $item)</dcsset:field>"
|
||||
$lines += "$i`t</dcsset:item>"
|
||||
}
|
||||
$lines += "$i`t<dcsset:placement>Auto</dcsset:placement>"
|
||||
$lines += "$i</dcsset:item>"
|
||||
} else {
|
||||
$lines += "$i<dcsset:item xsi:type=`"dcsset:SelectedItemField`">"
|
||||
$lines += "$i`t<dcsset:field>$(Esc-Xml $fieldName)</dcsset:field>"
|
||||
@@ -878,6 +970,8 @@ function Build-DataParamFragment {
|
||||
if ($parsed.value -is [hashtable] -and $parsed.value.variant) {
|
||||
$lines += "$i`t<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
$lines += "$i`t`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $parsed.value.variant)</v8:variant>"
|
||||
$lines += "$i`t`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
$lines += "$i`t`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
$lines += "$i`t</dcscor:value>"
|
||||
} elseif ("$($parsed.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
$lines += "$i`t<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
|
||||
@@ -973,6 +1067,20 @@ function Build-VariantFragment {
|
||||
return $lines -join "`r`n"
|
||||
}
|
||||
|
||||
function Emit-FilterComparison {
|
||||
param($f, [string]$indent)
|
||||
$lines = @()
|
||||
$lines += "$indent<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
|
||||
$lines += "$indent`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-Xml $f.field)</dcsset:left>"
|
||||
$lines += "$indent`t<dcsset:comparisonType>$(Esc-Xml $f.op)</dcsset:comparisonType>"
|
||||
if ($null -ne $f.value) {
|
||||
$vt = if ($f["valueType"]) { $f["valueType"] } else { "xs:string" }
|
||||
$lines += "$indent`t<dcsset:right xsi:type=`"$vt`">$(Esc-Xml "$($f.value)")</dcsset:right>"
|
||||
}
|
||||
$lines += "$indent</dcsset:item>"
|
||||
return $lines
|
||||
}
|
||||
|
||||
function Build-ConditionalAppearanceItemFragment {
|
||||
param($parsed, [string]$indent)
|
||||
|
||||
@@ -996,15 +1104,17 @@ function Build-ConditionalAppearanceItemFragment {
|
||||
# filter
|
||||
if ($parsed.filter) {
|
||||
$lines += "$i`t<dcsset:filter>"
|
||||
$f = $parsed.filter
|
||||
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:FilterItemComparison`">"
|
||||
$lines += "$i`t`t`t<dcsset:left xsi:type=`"dcscor:Field`">$(Esc-Xml $f.field)</dcsset:left>"
|
||||
$lines += "$i`t`t`t<dcsset:comparisonType>$(Esc-Xml $f.op)</dcsset:comparisonType>"
|
||||
if ($null -ne $f.value) {
|
||||
$vt = if ($f["valueType"]) { $f["valueType"] } else { "xs:string" }
|
||||
$lines += "$i`t`t`t<dcsset:right xsi:type=`"$vt`">$(Esc-Xml "$($f.value)")</dcsset:right>"
|
||||
if ($parsed.filter -is [array]) {
|
||||
# OrGroup
|
||||
$lines += "$i`t`t<dcsset:item xsi:type=`"dcsset:FilterItemGroup`">"
|
||||
$lines += "$i`t`t`t<dcsset:groupType>OrGroup</dcsset:groupType>"
|
||||
foreach ($f in $parsed.filter) {
|
||||
$lines += Emit-FilterComparison $f "$i`t`t`t"
|
||||
}
|
||||
$lines += "$i`t`t</dcsset:item>"
|
||||
} else {
|
||||
$lines += Emit-FilterComparison $parsed.filter "$i`t`t"
|
||||
}
|
||||
$lines += "$i`t`t</dcsset:item>"
|
||||
$lines += "$i`t</dcsset:filter>"
|
||||
} else {
|
||||
$lines += "$i`t<dcsset:filter/>"
|
||||
@@ -1013,18 +1123,25 @@ function Build-ConditionalAppearanceItemFragment {
|
||||
# appearance
|
||||
$lines += "$i`t<dcsset:appearance>"
|
||||
|
||||
# Auto-detect value type
|
||||
$val = $parsed.value
|
||||
$valType = "xs:string"
|
||||
if ($val -match '^(web|style|win):') {
|
||||
$valType = "v8ui:Color"
|
||||
} elseif ($val -eq "true" -or $val -eq "false") {
|
||||
$valType = "xs:boolean"
|
||||
}
|
||||
|
||||
$lines += "$i`t`t<dcscor:item xsi:type=`"dcsset:SettingsParameterValue`">"
|
||||
$lines += "$i`t`t`t<dcscor:parameter>$(Esc-Xml $parsed.param)</dcscor:parameter>"
|
||||
$lines += "$i`t`t`t<dcscor:value xsi:type=`"$valType`">$(Esc-Xml $val)</dcscor:value>"
|
||||
|
||||
if ($val -match '^(web|style|win):') {
|
||||
$lines += "$i`t`t`t<dcscor:value xsi:type=`"v8ui:Color`">$(Esc-Xml $val)</dcscor:value>"
|
||||
} elseif ($val -eq "true" -or $val -eq "false") {
|
||||
$lines += "$i`t`t`t<dcscor:value xsi:type=`"xs:boolean`">$(Esc-Xml $val)</dcscor:value>"
|
||||
} elseif ($parsed.param -eq "Формат" -or $parsed.param -eq "Текст" -or $parsed.param -eq "Заголовок") {
|
||||
$lines += "$i`t`t`t<dcscor:value xsi:type=`"v8:LocalStringType`">"
|
||||
$lines += "$i`t`t`t`t<v8:item>"
|
||||
$lines += "$i`t`t`t`t`t<v8:lang>ru</v8:lang>"
|
||||
$lines += "$i`t`t`t`t`t<v8:content>$(Esc-Xml $val)</v8:content>"
|
||||
$lines += "$i`t`t`t`t</v8:item>"
|
||||
$lines += "$i`t`t`t</dcscor:value>"
|
||||
} else {
|
||||
$lines += "$i`t`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml $val)</dcscor:value>"
|
||||
}
|
||||
|
||||
$lines += "$i`t`t</dcscor:item>"
|
||||
$lines += "$i`t</dcsset:appearance>"
|
||||
|
||||
@@ -1039,6 +1156,11 @@ function Build-StructureItemFragment {
|
||||
$lines = @()
|
||||
$lines += "$i<dcsset:item xsi:type=`"dcsset:StructureItemGroup`">"
|
||||
|
||||
# name
|
||||
if ($item["name"]) {
|
||||
$lines += "$i`t<dcsset:name>$(Esc-Xml $item["name"])</dcsset:name>"
|
||||
}
|
||||
|
||||
# groupItems
|
||||
$groupBy = $item["groupBy"]
|
||||
if (-not $groupBy -or $groupBy.Count -eq 0) {
|
||||
@@ -1445,6 +1567,14 @@ $corNs = "http://v8.1c.ru/8.1/data-composition-system/core"
|
||||
|
||||
if ($Operation -eq "set-query" -or $Operation -eq "set-structure" -or $Operation -eq "add-dataSet") {
|
||||
$values = @($Value)
|
||||
} elseif ($Operation -eq "patch-query") {
|
||||
$values = @($Value -split ';;' | Where-Object { $_.Trim() })
|
||||
} elseif ($Operation -eq "add-drilldown") {
|
||||
if ($Value.Contains(';;')) {
|
||||
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
} else {
|
||||
$values = @($Value -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
}
|
||||
} else {
|
||||
$values = @($Value -split ';;' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
}
|
||||
@@ -1622,6 +1752,293 @@ switch ($Operation) {
|
||||
}
|
||||
}
|
||||
|
||||
"modify-parameter" {
|
||||
foreach ($val in $values) {
|
||||
# Parse: "ParamName [Title] key=value key=value"
|
||||
# Extract optional [Title] first (mirrors Parse-FieldShorthand)
|
||||
$titleVal = $null
|
||||
if ($val -match '\[([^\]]*)\]') {
|
||||
$titleVal = $Matches[1].Trim()
|
||||
$val = ($val -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
|
||||
}
|
||||
|
||||
$parts = $val -split '\s+', 2
|
||||
$paramName = $parts[0].Trim()
|
||||
$rest = if ($parts.Count -gt 1) { $parts[1].Trim() } else { "" }
|
||||
|
||||
# Find parameter element
|
||||
$paramEl = Find-ElementByChildValue $xmlDoc.DocumentElement "parameter" "name" $paramName $schNs
|
||||
if (-not $paramEl) {
|
||||
Write-Host "[WARN] Parameter `"$paramName`" not found — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
$childIndent = Get-ChildIndent $paramEl
|
||||
|
||||
# Set/replace title (must come right after <name>, before <valueType>)
|
||||
if ($null -ne $titleVal) {
|
||||
$existingTitle = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'title') {
|
||||
$existingTitle = $ch; break
|
||||
}
|
||||
}
|
||||
if ($existingTitle) {
|
||||
Remove-NodeWithWhitespace $existingTitle
|
||||
}
|
||||
# Insert before first of (valueType, value, useRestriction, expression, availableAsField, ...)
|
||||
$titleRef = $null
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -ne 'name') {
|
||||
$titleRef = $ch; break
|
||||
}
|
||||
}
|
||||
$titleFrag = Build-MLTextXml -tag "title" -text $titleVal -indent $childIndent
|
||||
$titleNodes = Import-Fragment $xmlDoc $titleFrag
|
||||
foreach ($node in $titleNodes) {
|
||||
Insert-BeforeElement $paramEl $node $titleRef $childIndent
|
||||
}
|
||||
Write-Host "[OK] Parameter `"$paramName`": title set to `"$titleVal`""
|
||||
}
|
||||
|
||||
# Separate availableValue=... from simple kv pairs
|
||||
$simpleRest = $rest
|
||||
$avPart = $null
|
||||
$avIdx = $rest.IndexOf('availableValue=')
|
||||
if ($avIdx -ge 0) {
|
||||
$simpleRest = $rest.Substring(0, $avIdx).Trim()
|
||||
$avPart = $rest.Substring($avIdx)
|
||||
}
|
||||
|
||||
# Process simple key=value pairs (use, denyIncompleteValues, etc.)
|
||||
if ($simpleRest) {
|
||||
$kvPairs = [regex]::Matches($simpleRest, '(\w+)=(\S+)')
|
||||
foreach ($kv in $kvPairs) {
|
||||
$key = $kv.Groups[1].Value
|
||||
$value = $kv.Groups[2].Value
|
||||
|
||||
$existing = $paramEl.SelectSingleNode($key)
|
||||
if ($existing) {
|
||||
$existing.InnerText = $value
|
||||
Write-Host "[OK] Parameter `"$paramName`": $key updated to $value"
|
||||
} else {
|
||||
# Schema order: ...value, useRestriction, availableValue*, denyIncompleteValues, use
|
||||
$refNode = $null
|
||||
if ($key -eq "denyIncompleteValues") {
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'use') {
|
||||
$refNode = $child; break
|
||||
}
|
||||
}
|
||||
}
|
||||
$fragXml = "$childIndent<$key>$(Esc-Xml $value)</$key>"
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $paramEl $node $refNode $childIndent
|
||||
}
|
||||
Write-Host "[OK] Parameter `"$paramName`": $key=$value added"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Process availableValue
|
||||
if ($avPart) {
|
||||
$avRest = $avPart -replace '^availableValue=', ''
|
||||
# Parse: "Перечисление...X presentation=текст с пробелами"
|
||||
$avParts = $avRest -split '\s+presentation=', 2
|
||||
$avValue = $avParts[0].Trim()
|
||||
$avPresentation = if ($avParts.Count -gt 1) { $avParts[1].Trim() } else { "" }
|
||||
|
||||
# Detect value type
|
||||
$avType = "xs:string"
|
||||
if ($avValue -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$avType = "dcscor:DesignTimeValue"
|
||||
}
|
||||
|
||||
$avLines = @()
|
||||
$avLines += "$childIndent<availableValue>"
|
||||
$avLines += "$childIndent`t<value xsi:type=`"$avType`">$(Esc-Xml $avValue)</value>"
|
||||
if ($avPresentation) {
|
||||
$avLines += "$childIndent`t<presentation xsi:type=`"v8:LocalStringType`">"
|
||||
$avLines += "$childIndent`t`t<v8:item>"
|
||||
$avLines += "$childIndent`t`t`t<v8:lang>ru</v8:lang>"
|
||||
$avLines += "$childIndent`t`t`t<v8:content>$(Esc-Xml $avPresentation)</v8:content>"
|
||||
$avLines += "$childIndent`t`t</v8:item>"
|
||||
$avLines += "$childIndent`t</presentation>"
|
||||
}
|
||||
$avLines += "$childIndent</availableValue>"
|
||||
$fragXml = $avLines -join "`r`n"
|
||||
|
||||
# Insert before first of (denyIncompleteValues, use) in document order
|
||||
$refNode = $null
|
||||
foreach ($child in $paramEl.ChildNodes) {
|
||||
if ($child.NodeType -eq 'Element' -and ($child.LocalName -eq 'denyIncompleteValues' -or $child.LocalName -eq 'use')) {
|
||||
$refNode = $child; break
|
||||
}
|
||||
}
|
||||
$nodes = Import-Fragment $xmlDoc $fragXml
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $paramEl $node $refNode $childIndent
|
||||
}
|
||||
Write-Host "[OK] Parameter `"$paramName`": availableValue added"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"rename-parameter" {
|
||||
foreach ($val in $values) {
|
||||
# Shorthand: "OldName => NewName"
|
||||
if ($val -notmatch '^\s*(.+?)\s*=>\s*(.+?)\s*$') {
|
||||
Write-Host "[WARN] rename-parameter expects 'OldName => NewName', got: $val"
|
||||
continue
|
||||
}
|
||||
$oldName = $Matches[1].Trim()
|
||||
$newName = $Matches[2].Trim()
|
||||
|
||||
if ($oldName -eq $newName) {
|
||||
Write-Host "[WARN] rename-parameter: old and new names are equal — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
# 1. Rename <parameter><name>OldName</name>
|
||||
$root = $xmlDoc.DocumentElement
|
||||
$paramEl = Find-ElementByChildValue $root "parameter" "name" $oldName $schNs
|
||||
if (-not $paramEl) {
|
||||
Write-Host "[WARN] Parameter `"$oldName`" not found — skipped"
|
||||
continue
|
||||
}
|
||||
foreach ($ch in $paramEl.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'name' -and $ch.NamespaceURI -eq $schNs) {
|
||||
$ch.InnerText = $newName
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Update <expression> in other <parameter> elements.
|
||||
# Regex matches "&OldName" only when followed by a non-identifier char (or end),
|
||||
# so "&Период" matches "&Период.ДатаНачала" but NOT "&ПериодОтчета".
|
||||
$escOld = [regex]::Escape($oldName)
|
||||
$exprRegex = "&$escOld(?=[^\w\u0400-\u04FF]|$)"
|
||||
$exprUpdated = 0
|
||||
foreach ($ch in $root.ChildNodes) {
|
||||
if ($ch.NodeType -ne 'Element' -or $ch.LocalName -ne 'parameter' -or $ch.NamespaceURI -ne $schNs) { continue }
|
||||
foreach ($gc in $ch.ChildNodes) {
|
||||
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'expression' -and $gc.NamespaceURI -eq $schNs) {
|
||||
$oldExpr = $gc.InnerText
|
||||
$newExpr = [regex]::Replace($oldExpr, $exprRegex, "&$newName")
|
||||
if ($newExpr -ne $oldExpr) {
|
||||
$gc.InnerText = $newExpr
|
||||
$exprUpdated++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# 3. Update <dcscor:parameter>OldName</dcscor:parameter> in dataParameters of all variants.
|
||||
# Note: <settingsVariant> is in schNs, but <settings> and <dataParameters> are in setNs.
|
||||
# IMPORTANT: don't use $variant — it collides with script parameter [string]$Variant
|
||||
# (PowerShell vars are case-insensitive, and the [string] type would coerce XmlNode to "").
|
||||
$dpUpdated = 0
|
||||
foreach ($variantNode in $root.ChildNodes) {
|
||||
if ($variantNode.NodeType -ne 'Element' -or $variantNode.LocalName -ne 'settingsVariant' -or $variantNode.NamespaceURI -ne $schNs) { continue }
|
||||
$settings = Find-FirstElement $variantNode @("settings") $setNs
|
||||
if (-not $settings) { continue }
|
||||
$dpEl = Find-FirstElement $settings @("dataParameters") $setNs
|
||||
if (-not $dpEl) { continue }
|
||||
foreach ($item in $dpEl.ChildNodes) {
|
||||
if ($item.NodeType -ne 'Element' -or $item.LocalName -ne 'item') { continue }
|
||||
foreach ($gc in $item.ChildNodes) {
|
||||
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'parameter' -and $gc.NamespaceURI -eq $corNs) {
|
||||
if ($gc.InnerText.Trim() -eq $oldName) {
|
||||
$gc.InnerText = $newName
|
||||
$dpUpdated++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host "[OK] Parameter renamed: `"$oldName`" => `"$newName`" (expressions updated: $exprUpdated, dataParameters updated: $dpUpdated)"
|
||||
}
|
||||
}
|
||||
|
||||
"reorder-parameters" {
|
||||
foreach ($val in $values) {
|
||||
# Shorthand: "Name1, Name2, Name3" — partial list, listed names go first in order, rest preserve original order
|
||||
$order = @($val -split ',' | ForEach-Object { $_.Trim() } | Where-Object { $_ })
|
||||
if ($order.Count -eq 0) {
|
||||
Write-Host "[WARN] reorder-parameters: empty list — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# Collect all <parameter> in document order with their child indent
|
||||
$allParams = @()
|
||||
foreach ($ch in $root.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'parameter' -and $ch.NamespaceURI -eq $schNs) {
|
||||
$allParams += $ch
|
||||
}
|
||||
}
|
||||
if ($allParams.Count -eq 0) {
|
||||
Write-Host "[WARN] reorder-parameters: no parameters in schema"
|
||||
continue
|
||||
}
|
||||
|
||||
$childIndent = Get-ChildIndent $root
|
||||
|
||||
# Build name -> element map
|
||||
$byName = @{}
|
||||
foreach ($pe in $allParams) {
|
||||
foreach ($gc in $pe.ChildNodes) {
|
||||
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
|
||||
$byName[$gc.InnerText.Trim()] = $pe
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Build new order
|
||||
$newOrder = @()
|
||||
$used = @{}
|
||||
foreach ($name in $order) {
|
||||
if ($byName.ContainsKey($name)) {
|
||||
$newOrder += $byName[$name]
|
||||
$used[$name] = $true
|
||||
} else {
|
||||
Write-Host "[WARN] reorder-parameters: parameter `"$name`" not found — skipped"
|
||||
}
|
||||
}
|
||||
foreach ($pe in $allParams) {
|
||||
$peName = $null
|
||||
foreach ($gc in $pe.ChildNodes) {
|
||||
if ($gc.NodeType -eq 'Element' -and $gc.LocalName -eq 'name' -and $gc.NamespaceURI -eq $schNs) {
|
||||
$peName = $gc.InnerText.Trim(); break
|
||||
}
|
||||
}
|
||||
if ($peName -and -not $used.ContainsKey($peName)) {
|
||||
$newOrder += $pe
|
||||
}
|
||||
}
|
||||
|
||||
# Find anchor: element right after the last parameter in original order
|
||||
$lastParam = $allParams[-1]
|
||||
$anchor = $lastParam.NextSibling
|
||||
|
||||
# Remove all parameters with surrounding whitespace
|
||||
foreach ($pe in $allParams) {
|
||||
Remove-NodeWithWhitespace $pe
|
||||
}
|
||||
|
||||
# Re-insert in new order before anchor
|
||||
foreach ($pe in $newOrder) {
|
||||
Insert-BeforeElement $root $pe $anchor $childIndent
|
||||
}
|
||||
|
||||
Write-Host "[OK] Parameters reordered ($($allParams.Count) total, $($order.Count) explicit)"
|
||||
}
|
||||
}
|
||||
|
||||
"add-filter" {
|
||||
$settings = Resolve-VariantSettings
|
||||
$varName = Get-VariantName
|
||||
@@ -1710,8 +2127,50 @@ switch ($Operation) {
|
||||
|
||||
foreach ($val in $values) {
|
||||
$fieldName = $val.Trim()
|
||||
$groupName = $null
|
||||
|
||||
# Extract @group=Name
|
||||
if ($fieldName -match '\s*@group=(\S+)') {
|
||||
$groupName = $Matches[1]
|
||||
$fieldName = ($fieldName -replace '\s*@group=\S+', '').Trim()
|
||||
}
|
||||
|
||||
if ($groupName) {
|
||||
# Find named StructureItemGroup
|
||||
$dcssetNs = "http://v8.1c.ru/8.1/data-composition-system/settings"
|
||||
$xsiNs = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("dcsset", $dcssetNs)
|
||||
$nsMgr.AddNamespace("xsi", $xsiNs)
|
||||
$groupEl = $settings.SelectSingleNode(".//dcsset:item[@xsi:type='dcsset:StructureItemGroup'][dcsset:name='$groupName']", $nsMgr)
|
||||
if (-not $groupEl) {
|
||||
Write-Host "[WARN] StructureItemGroup `"$groupName`" not found — adding to variant level"
|
||||
$targetEl = $settings
|
||||
} else {
|
||||
$targetEl = $groupEl
|
||||
}
|
||||
} else {
|
||||
$targetEl = $settings
|
||||
}
|
||||
|
||||
$selection = Ensure-SettingsChild $targetEl "selection" @()
|
||||
|
||||
# Dedup: skip if SelectedItemAuto already exists
|
||||
if ($fieldName -eq "Auto") {
|
||||
$isDup = $false
|
||||
foreach ($ch in $selection.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'item') {
|
||||
$typeAttr = $ch.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
if ($typeAttr -and $typeAttr.Contains("SelectedItemAuto")) { $isDup = $true; break }
|
||||
}
|
||||
}
|
||||
if ($isDup) {
|
||||
$target = if ($groupName) { "group `"$groupName`"" } else { "variant `"$varName`"" }
|
||||
Write-Host "[WARN] SelectedItemAuto already exists in $target — skipped"
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
$selection = Ensure-SettingsChild $settings "selection" @()
|
||||
$selIndent = Get-ContainerChildIndent $selection
|
||||
|
||||
$selXml = Build-SelectionItemFragment -fieldName $fieldName -indent $selIndent
|
||||
@@ -1720,7 +2179,8 @@ switch ($Operation) {
|
||||
Insert-BeforeElement $selection $node $null $selIndent
|
||||
}
|
||||
|
||||
Write-Host "[OK] Selection `"$fieldName`" added to variant `"$varName`""
|
||||
$target = if ($groupName) { "group `"$groupName`"" } else { "variant `"$varName`"" }
|
||||
Write-Host "[OK] Selection `"$fieldName`" added to $target"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2046,11 +2506,11 @@ switch ($Operation) {
|
||||
Set-OrCreateChildElementWithAttr $filterItem "right" $setNs "$($parsed.value)" $vt $itemIndent
|
||||
}
|
||||
|
||||
# Update use
|
||||
# Update use (only when explicitly set via @off / @on)
|
||||
if ($parsed.use -eq $false) {
|
||||
Set-OrCreateChildElement $filterItem "use" $setNs "false" $itemIndent
|
||||
} else {
|
||||
# If explicitly not @off, remove use=false if exists
|
||||
} elseif ($parsed.use -eq $true) {
|
||||
# @on: remove existing use=false if any
|
||||
$useEl = $null
|
||||
foreach ($ch in $filterItem.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $setNs) {
|
||||
@@ -2116,6 +2576,8 @@ switch ($Operation) {
|
||||
if ($parsed.value -is [hashtable] -and $parsed.value.variant) {
|
||||
$valLines += "$itemIndent<dcscor:value xsi:type=`"v8:StandardPeriod`">"
|
||||
$valLines += "$itemIndent`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">$(Esc-Xml $parsed.value.variant)</v8:variant>"
|
||||
$valLines += "$itemIndent`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
$valLines += "$itemIndent`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
$valLines += "$itemIndent</dcscor:value>"
|
||||
} elseif ("$($parsed.value)" -match '^\d{4}-\d{2}-\d{2}T') {
|
||||
$valLines += "$itemIndent<dcscor:value xsi:type=`"xs:dateTime`">$(Esc-Xml "$($parsed.value)")</dcscor:value>"
|
||||
@@ -2131,10 +2593,11 @@ switch ($Operation) {
|
||||
}
|
||||
}
|
||||
|
||||
# Update use
|
||||
# Update use (only when explicitly set via @off / @on)
|
||||
if ($parsed.use -eq $false) {
|
||||
Set-OrCreateChildElement $dpItem "use" $corNs "false" $itemIndent
|
||||
} else {
|
||||
} elseif ($parsed.use -eq $true) {
|
||||
# @on: remove existing use=false if any
|
||||
$useEl = $null
|
||||
foreach ($ch in $dpItem.ChildNodes) {
|
||||
if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'use' -and $ch.NamespaceURI -eq $corNs) {
|
||||
@@ -2331,6 +2794,155 @@ switch ($Operation) {
|
||||
Write-Host "[OK] Filter for `"$fieldName`" removed from variant `"$varName`""
|
||||
}
|
||||
}
|
||||
|
||||
"add-drilldown" {
|
||||
# String-based manipulation — templates use dcsat namespace with inline xmlns
|
||||
$rawText = [System.IO.File]::ReadAllText($resolvedPath, [System.Text.Encoding]::UTF8)
|
||||
$nl = "`r`n"
|
||||
$dcsatNsDecl = 'xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template"'
|
||||
|
||||
# Find all outer <template> blocks by nesting-aware scan
|
||||
$tplStarts = [System.Collections.ArrayList]::new()
|
||||
$nameRegex = [regex]'<template>\s*<name>([^<]+)</name>'
|
||||
foreach ($m in $nameRegex.Matches($rawText)) {
|
||||
[void]$tplStarts.Add(@{ pos = $m.Index; name = $m.Groups[1].Value })
|
||||
}
|
||||
|
||||
# For each start, find closing </template> at nesting depth 0
|
||||
$tplBlocks = [System.Collections.ArrayList]::new()
|
||||
foreach ($ts in $tplStarts) {
|
||||
$depth = 1
|
||||
$scanPos = $ts.pos + 10 # skip past opening <template>
|
||||
while ($depth -gt 0 -and $scanPos -lt $rawText.Length) {
|
||||
$nextOpen = $rawText.IndexOf("<template", $scanPos)
|
||||
$nextClose = $rawText.IndexOf("</template>", $scanPos)
|
||||
if ($nextClose -lt 0) { break }
|
||||
if ($nextOpen -ge 0 -and $nextOpen -lt $nextClose) {
|
||||
$depth++
|
||||
$scanPos = $nextOpen + 10
|
||||
} else {
|
||||
$depth--
|
||||
if ($depth -eq 0) {
|
||||
$endPos = $nextClose + "</template>".Length
|
||||
[void]$tplBlocks.Add(@{ name = $ts.name; start = $ts.pos; text = $rawText.Substring($ts.pos, $endPos - $ts.pos) })
|
||||
}
|
||||
$scanPos = $nextClose + 11
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($tplBlocks.Count -eq 0) {
|
||||
Write-Host "[WARN] No named templates found in schema"
|
||||
}
|
||||
|
||||
# Collect all insertions as (position, text) — apply in reverse order
|
||||
$insertions = [System.Collections.ArrayList]::new()
|
||||
|
||||
foreach ($tplBlock in $tplBlocks) {
|
||||
$tplName = $tplBlock.name
|
||||
$tplText = $tplBlock.text
|
||||
$tplStart = $tplBlock.start
|
||||
|
||||
# Build map: expression → paramName from ExpressionAreaTemplateParameter
|
||||
$exprMap = @{}
|
||||
$exprRegex = [regex]'(?s)<parameter[^>]*ExpressionAreaTemplateParameter[^>]*>\s*<dcsat:name>([^<]+)</dcsat:name>\s*<dcsat:expression>([^<]+)</dcsat:expression>\s*</parameter>'
|
||||
foreach ($em in $exprRegex.Matches($tplText)) {
|
||||
$pName = $em.Groups[1].Value
|
||||
$pExpr = $em.Groups[2].Value
|
||||
$exprMap[$pExpr] = $pName
|
||||
}
|
||||
|
||||
foreach ($resource in $values) {
|
||||
$drillName = "Расшифровка_$resource"
|
||||
|
||||
# Idempotency: check if already exists
|
||||
if ($tplText.Contains($drillName)) {
|
||||
Write-Host "[INFO] $drillName already exists in $tplName — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
# Find ExpressionAreaTemplateParameter by expression
|
||||
$paramName = $null
|
||||
if ($exprMap.ContainsKey($resource)) {
|
||||
$paramName = $exprMap[$resource]
|
||||
} else {
|
||||
Write-Host "[WARN] Expression `"$resource`" not found in template $tplName — skipped"
|
||||
continue
|
||||
}
|
||||
|
||||
$cellCount = 0
|
||||
|
||||
# Step 1: Insert DetailsAreaTemplateParameter after last </parameter> in template
|
||||
$lastParamEndTag = "</parameter>"
|
||||
$lastParamPos = $tplText.LastIndexOf($lastParamEndTag)
|
||||
if ($lastParamPos -ge 0) {
|
||||
$insertPos = $tplStart + $lastParamPos + $lastParamEndTag.Length
|
||||
# Detect indent from context
|
||||
$prevNewline = $tplText.LastIndexOf("`n", $lastParamPos)
|
||||
$indent = "`t`t"
|
||||
if ($prevNewline -ge 0) {
|
||||
$lineStart = $prevNewline + 1
|
||||
$indentMatch = [regex]::Match($tplText.Substring($lineStart), '^(\s*)')
|
||||
if ($indentMatch.Success) { $indent = $indentMatch.Groups[1].Value }
|
||||
}
|
||||
$detailsXml = "$nl$indent<parameter $dcsatNsDecl xsi:type=`"dcsat:DetailsAreaTemplateParameter`">" +
|
||||
"$nl$indent`t<dcsat:name>$drillName</dcsat:name>" +
|
||||
"$nl$indent`t<dcsat:fieldExpression>" +
|
||||
"$nl$indent`t`t<dcsat:field>ИмяРесурса</dcsat:field>" +
|
||||
"$nl$indent`t`t<dcsat:expression>`"$resource`"</dcsat:expression>" +
|
||||
"$nl$indent`t</dcsat:fieldExpression>" +
|
||||
"$nl$indent`t<dcsat:mainAction>DrillDown</dcsat:mainAction>" +
|
||||
"$nl$indent</parameter>"
|
||||
[void]$insertions.Add(@{ pos = $insertPos; text = $detailsXml })
|
||||
}
|
||||
|
||||
# Step 2: Insert appearance binding in cells referencing this parameter
|
||||
$cellTag = '<dcsat:value xsi:type="dcscor:Parameter">' + $paramName + '</dcsat:value>'
|
||||
$searchStart = 0
|
||||
while (($cellIdx = $tplText.IndexOf($cellTag, $searchStart)) -ge 0) {
|
||||
$cellEnd = $tplText.IndexOf("</dcsat:tableCell>", $cellIdx)
|
||||
if ($cellEnd -lt 0) { break }
|
||||
$appEnd = $tplText.LastIndexOf("</dcsat:appearance>", $cellEnd)
|
||||
if ($appEnd -lt $cellIdx) { $searchStart = $cellEnd + 1; continue }
|
||||
|
||||
# Detect indent for appearance items — insert after \n, before indent of </dcsat:appearance>
|
||||
$appPrevNl = $tplText.LastIndexOf("`n", $appEnd)
|
||||
$appIndent = "`t`t`t`t`t`t"
|
||||
if ($appPrevNl -ge 0) {
|
||||
$appLineStart = $appPrevNl + 1
|
||||
$appIndentMatch = [regex]::Match($tplText.Substring($appLineStart), '^(\s*)')
|
||||
if ($appIndentMatch.Success) { $appIndent = $appIndentMatch.Groups[1].Value }
|
||||
}
|
||||
$itemIndent = $appIndent + "`t"
|
||||
$appearanceXml = "$itemIndent<dcscor:item>$nl" +
|
||||
"$itemIndent`t<dcscor:parameter>Расшифровка</dcscor:parameter>$nl" +
|
||||
"$itemIndent`t<dcscor:value xsi:type=`"dcscor:Parameter`">$drillName</dcscor:value>$nl" +
|
||||
"$itemIndent</dcscor:item>$nl"
|
||||
# Insert after \n (before indent of closing tag), not before the tag itself
|
||||
$insertAt = if ($appPrevNl -ge 0) { $tplStart + $appPrevNl + 1 } else { $tplStart + $appEnd }
|
||||
[void]$insertions.Add(@{ pos = $insertAt; text = $appearanceXml })
|
||||
$cellCount++
|
||||
$searchStart = $cellEnd + 1
|
||||
}
|
||||
|
||||
Write-Host "[OK] $drillName → $tplName (param + $cellCount cell(s))"
|
||||
}
|
||||
}
|
||||
|
||||
# Apply insertions in reverse order to preserve offsets.
|
||||
# For same position: reverse insertion order so first resource ends up first in file.
|
||||
$idx = 0; foreach ($ins in $insertions) { $ins.seq = $idx; $idx++ }
|
||||
$sorted = $insertions | Sort-Object { $_.pos }, { $_.seq } -Descending
|
||||
foreach ($ins in $sorted) {
|
||||
$rawText = $rawText.Insert($ins.pos, $ins.text)
|
||||
}
|
||||
|
||||
# Write directly — skip DOM save
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($resolvedPath, $rawText, $enc)
|
||||
Write-Host "[OK] Saved $resolvedPath"
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# --- 9. Save ---
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.3 — Atomic 1C DCS editor (Python port)
|
||||
# skd-edit v1.11 — Atomic 1C DCS editor (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -16,15 +16,16 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
VALID_OPS = [
|
||||
"add-field", "add-total", "add-calculated-field", "add-parameter", "add-filter",
|
||||
"add-dataParameter", "add-order", "add-selection", "add-dataSetLink",
|
||||
"add-dataSet", "add-variant", "add-conditionalAppearance",
|
||||
"add-dataSet", "add-variant", "add-conditionalAppearance", "add-drilldown",
|
||||
"set-query", "patch-query", "set-outputParameter", "set-structure",
|
||||
"modify-field", "modify-filter", "modify-dataParameter",
|
||||
"modify-field", "modify-filter", "modify-dataParameter", "modify-parameter",
|
||||
"rename-parameter", "reorder-parameters",
|
||||
"clear-selection", "clear-order", "clear-filter",
|
||||
"remove-field", "remove-total", "remove-calculated-field", "remove-parameter", "remove-filter",
|
||||
]
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-TemplatePath", required=True)
|
||||
parser.add_argument("-TemplatePath", "-Path", required=True)
|
||||
parser.add_argument("-Operation", required=True, choices=VALID_OPS)
|
||||
parser.add_argument("-Value", required=True)
|
||||
parser.add_argument("-DataSet", default="")
|
||||
@@ -259,32 +260,57 @@ def parse_total_shorthand(s):
|
||||
|
||||
|
||||
def parse_calc_shorthand(s):
|
||||
title = ""
|
||||
m = re.search(r'\[([^\]]+)\]', s)
|
||||
if m:
|
||||
title = m.group(1)
|
||||
s = re.sub(r'\s*\[[^\]]+\]', '', s)
|
||||
# Pattern: "Name [Title]: type = Expression #noField #noFilter ...".
|
||||
# - `[Title]` is extracted only from the LHS of '=' so that `[...]` inside
|
||||
# an expression (e.g. index access) isn't interpreted as a title.
|
||||
# - `#restrict` flags use a known-names pattern and are extracted globally —
|
||||
# the docs put them after `=`, and the closed flag set avoids matching
|
||||
# `#word` that happens to appear inside a string literal.
|
||||
restrict_pattern = r'#(noField|noFilter|noCondition|noGroup|noOrder)\b'
|
||||
|
||||
restrict_matches = re.findall(restrict_pattern, s)
|
||||
s = re.sub(r'\s*' + restrict_pattern, '', s)
|
||||
|
||||
eq_idx = s.find("=")
|
||||
if eq_idx > 0:
|
||||
left = s[:eq_idx].strip()
|
||||
expression = s[eq_idx + 1:].strip()
|
||||
if ":" in left:
|
||||
colon_idx = left.index(":")
|
||||
data_path = left[:colon_idx].strip()
|
||||
type_str = resolve_type_str(left[colon_idx + 1:].strip())
|
||||
return {"dataPath": data_path, "expression": expression, "type": type_str, "title": title}
|
||||
return {"dataPath": left, "expression": expression, "type": "", "title": title}
|
||||
return {"dataPath": s.strip(), "expression": "", "type": "", "title": title}
|
||||
lhs = s[:eq_idx]
|
||||
rhs = s[eq_idx + 1:].strip()
|
||||
has_rhs = True
|
||||
else:
|
||||
lhs = s
|
||||
rhs = ""
|
||||
has_rhs = False
|
||||
|
||||
title = ""
|
||||
m = re.search(r'\[([^\]]+)\]', lhs)
|
||||
if m:
|
||||
title = m.group(1)
|
||||
lhs = re.sub(r'\s*\[[^\]]+\]', '', lhs)
|
||||
lhs = lhs.strip()
|
||||
|
||||
if has_rhs:
|
||||
if ":" in lhs:
|
||||
colon_idx = lhs.index(":")
|
||||
data_path = lhs[:colon_idx].strip()
|
||||
type_str = resolve_type_str(lhs[colon_idx + 1:].strip())
|
||||
return {"dataPath": data_path, "expression": rhs, "type": type_str, "title": title, "restrict": restrict_matches}
|
||||
return {"dataPath": lhs, "expression": rhs, "type": "", "title": title, "restrict": restrict_matches}
|
||||
return {"dataPath": lhs, "expression": "", "type": "", "title": title, "restrict": restrict_matches}
|
||||
|
||||
|
||||
def parse_param_shorthand(s):
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False}
|
||||
result = {"name": "", "type": "", "value": None, "autoDates": False, "title": None}
|
||||
|
||||
if re.search(r'@autoDates', s):
|
||||
result["autoDates"] = True
|
||||
s = re.sub(r'\s*@autoDates', '', s)
|
||||
|
||||
# Extract optional [Title] (mirrors parse_field_shorthand)
|
||||
m = re.search(r'\[([^\]]*)\]', s)
|
||||
if m:
|
||||
result["title"] = m.group(1).strip()
|
||||
s = re.sub(r'\s*\[[^\]]*\]\s*', ' ', s).strip()
|
||||
|
||||
m = re.match(r'^([^:]+):\s*(\S+)(\s*=\s*(.+))?$', s)
|
||||
if m:
|
||||
result["name"] = m.group(1).strip()
|
||||
@@ -298,7 +324,9 @@ def parse_param_shorthand(s):
|
||||
|
||||
|
||||
def parse_filter_shorthand(s):
|
||||
result = {"field": "", "op": "Equal", "value": None, "use": True, "userSettingID": None, "viewMode": None}
|
||||
# use is tristate: None = not specified (modify-* won't touch),
|
||||
# False = @off (explicit), True = @on (explicit). add-* writes <use>false</use> only when False.
|
||||
result = {"field": "", "op": "Equal", "value": None, "use": None, "userSettingID": None, "viewMode": None}
|
||||
|
||||
if re.search(r'@user', s):
|
||||
result["userSettingID"] = "auto"
|
||||
@@ -306,6 +334,9 @@ def parse_filter_shorthand(s):
|
||||
if re.search(r'@off', s):
|
||||
result["use"] = False
|
||||
s = re.sub(r'\s*@off', '', s)
|
||||
if re.search(r'@on\b', s):
|
||||
result["use"] = True
|
||||
s = re.sub(r'\s*@on\b', '', s)
|
||||
if re.search(r'@quickAccess', s):
|
||||
result["viewMode"] = "QuickAccess"
|
||||
s = re.sub(r'\s*@quickAccess', '', s)
|
||||
@@ -350,6 +381,9 @@ def parse_filter_shorthand(s):
|
||||
elif re.match(r'^\d+(\.\d+)?$', val_part):
|
||||
result["value"] = val_part
|
||||
result["valueType"] = "xs:decimal"
|
||||
elif re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', val_part):
|
||||
result["value"] = val_part
|
||||
result["valueType"] = "dcscor:DesignTimeValue"
|
||||
else:
|
||||
result["value"] = val_part
|
||||
result["valueType"] = "xs:string"
|
||||
@@ -360,7 +394,9 @@ def parse_filter_shorthand(s):
|
||||
|
||||
|
||||
def parse_data_param_shorthand(s):
|
||||
result = {"parameter": "", "value": None, "use": True, "userSettingID": None, "viewMode": None}
|
||||
# use is tristate: None = not specified (modify-* won't touch),
|
||||
# False = @off (explicit), True = @on (explicit). add-* writes <use>false</use> only when False.
|
||||
result = {"parameter": "", "value": None, "use": None, "userSettingID": None, "viewMode": None}
|
||||
|
||||
if re.search(r'@user', s):
|
||||
result["userSettingID"] = "auto"
|
||||
@@ -368,6 +404,9 @@ def parse_data_param_shorthand(s):
|
||||
if re.search(r'@off', s):
|
||||
result["use"] = False
|
||||
s = re.sub(r'\s*@off', '', s)
|
||||
if re.search(r'@on\b', s):
|
||||
result["use"] = True
|
||||
s = re.sub(r'\s*@on\b', '', s)
|
||||
if re.search(r'@quickAccess', s):
|
||||
result["viewMode"] = "QuickAccess"
|
||||
s = re.sub(r'\s*@quickAccess', '', s)
|
||||
@@ -408,7 +447,7 @@ def parse_order_shorthand(s):
|
||||
parts = s.split(None, 1)
|
||||
field = parts[0]
|
||||
direction = "Asc"
|
||||
if len(parts) > 1 and re.match(r'(?i)^desc$', parts[1]):
|
||||
if len(parts) > 1 and re.match(r'^desc$', parts[1], re.IGNORECASE):
|
||||
direction = "Desc"
|
||||
return {"field": field, "direction": direction}
|
||||
|
||||
@@ -480,7 +519,11 @@ def parse_conditional_appearance_shorthand(s):
|
||||
if for_idx > when_idx:
|
||||
when_end = for_idx
|
||||
when_part = s[when_idx + 6:when_end].strip()
|
||||
result["filter"] = parse_filter_shorthand(when_part)
|
||||
or_parts = re.split(r'\s+or\s+', when_part)
|
||||
if len(or_parts) > 1:
|
||||
result["filter"] = [parse_filter_shorthand(p.strip()) for p in or_parts]
|
||||
else:
|
||||
result["filter"] = parse_filter_shorthand(when_part)
|
||||
|
||||
main_part = s[:main_end].strip()
|
||||
eq_idx = main_part.find("=")
|
||||
@@ -502,7 +545,12 @@ def parse_structure_shorthand(s):
|
||||
seg = segments[i].strip()
|
||||
group = {"type": "group"}
|
||||
|
||||
if re.match(r'^(?i)(details|\u0434\u0435\u0442\u0430\u043b\u0438)$', seg):
|
||||
name_m = re.search(r'\s*@name=(.+)', seg)
|
||||
if name_m:
|
||||
group["name"] = name_m.group(1).strip()
|
||||
seg = re.sub(r'\s*@name=.+', '', seg).strip()
|
||||
|
||||
if re.match(r'^(details|\u0434\u0435\u0442\u0430\u043b\u0438)$', seg, re.IGNORECASE):
|
||||
group["groupBy"] = []
|
||||
else:
|
||||
group["groupBy"] = [seg]
|
||||
@@ -666,6 +714,8 @@ def build_calc_field_fragment(parsed, indent):
|
||||
]
|
||||
if parsed.get("title"):
|
||||
lines.append(build_mltext_xml("title", parsed["title"], f"{i}\t"))
|
||||
if parsed.get("restrict"):
|
||||
lines.append(build_restriction_xml(parsed["restrict"], f"{i}\t"))
|
||||
if parsed.get("type"):
|
||||
lines.append(f"{i}\t<valueType>")
|
||||
lines.append(build_value_type_xml(parsed["type"], f"{i}\t\t"))
|
||||
@@ -679,6 +729,10 @@ def build_param_fragment(parsed, indent):
|
||||
fragments = []
|
||||
|
||||
lines = [f"{i}<parameter>", f"{i}\t<name>{esc_xml(parsed['name'])}</name>"]
|
||||
|
||||
if parsed.get("title"):
|
||||
lines.append(build_mltext_xml("title", parsed["title"], f"{i}\t"))
|
||||
|
||||
if parsed.get("type"):
|
||||
lines.append(f"{i}\t<valueType>")
|
||||
lines.append(build_value_type_xml(parsed["type"], f"{i}\t\t"))
|
||||
@@ -689,6 +743,8 @@ def build_param_fragment(parsed, indent):
|
||||
if parsed.get("type") == "StandardPeriod":
|
||||
lines.append(f'{i}\t<value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{i}\t\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(val_str)}</v8:variant>')
|
||||
lines.append(f"{i}\t\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>")
|
||||
lines.append(f"{i}\t\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>")
|
||||
lines.append(f"{i}\t</value>")
|
||||
elif parsed.get("type", "").startswith("date"):
|
||||
lines.append(f'{i}\t<value xsi:type="xs:dateTime">{esc_xml(val_str)}</value>')
|
||||
@@ -704,14 +760,17 @@ def build_param_fragment(parsed, indent):
|
||||
|
||||
if parsed.get("autoDates"):
|
||||
param_name = parsed["name"]
|
||||
# Canonical БСП pattern: title + valueType + value + useRestriction + expression
|
||||
b_lines = [
|
||||
f"{i}<parameter>",
|
||||
f"{i}\t<name>\u0414\u0430\u0442\u0430\u041d\u0430\u0447\u0430\u043b\u0430</name>",
|
||||
build_mltext_xml("title", "\u041d\u0430\u0447\u0430\u043b\u043e \u043f\u0435\u0440\u0438\u043e\u0434\u0430", f"{i}\t"),
|
||||
f"{i}\t<valueType>",
|
||||
build_value_type_xml("date", f"{i}\t\t"),
|
||||
f"{i}\t</valueType>",
|
||||
f'{i}\t<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>',
|
||||
f"{i}\t<useRestriction>true</useRestriction>",
|
||||
f"{i}\t<expression>{esc_xml('&' + param_name + '.\u0414\u0430\u0442\u0430\u041d\u0430\u0447\u0430\u043b\u0430')}</expression>",
|
||||
f"{i}\t<availableAsField>false</availableAsField>",
|
||||
f"{i}</parameter>",
|
||||
]
|
||||
fragments.append("\r\n".join(b_lines))
|
||||
@@ -719,11 +778,13 @@ def build_param_fragment(parsed, indent):
|
||||
e_lines = [
|
||||
f"{i}<parameter>",
|
||||
f"{i}\t<name>\u0414\u0430\u0442\u0430\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f</name>",
|
||||
build_mltext_xml("title", "\u041a\u043e\u043d\u0435\u0446 \u043f\u0435\u0440\u0438\u043e\u0434\u0430", f"{i}\t"),
|
||||
f"{i}\t<valueType>",
|
||||
build_value_type_xml("date", f"{i}\t\t"),
|
||||
f"{i}\t</valueType>",
|
||||
f'{i}\t<value xsi:type="xs:dateTime">0001-01-01T00:00:00</value>',
|
||||
f"{i}\t<useRestriction>true</useRestriction>",
|
||||
f"{i}\t<expression>{esc_xml('&' + param_name + '.\u0414\u0430\u0442\u0430\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f')}</expression>",
|
||||
f"{i}\t<availableAsField>false</availableAsField>",
|
||||
f"{i}</parameter>",
|
||||
]
|
||||
fragments.append("\r\n".join(e_lines))
|
||||
@@ -760,6 +821,31 @@ def build_selection_item_fragment(field_name, indent):
|
||||
i = indent
|
||||
if field_name == "Auto":
|
||||
return f'{i}<dcsset:item xsi:type="dcsset:SelectedItemAuto"/>'
|
||||
m = re.match(r'^Folder\((.+)\)$', field_name)
|
||||
if m:
|
||||
inner = m.group(1)
|
||||
colon_idx = inner.find(':')
|
||||
if colon_idx > 0:
|
||||
title = inner[:colon_idx].strip()
|
||||
items = [x.strip() for x in inner[colon_idx + 1:].split(',') if x.strip()]
|
||||
else:
|
||||
title = ""
|
||||
items = [x.strip() for x in inner.split(',') if x.strip()]
|
||||
lines = [f'{i}<dcsset:item xsi:type="dcsset:SelectedItemFolder">']
|
||||
if title:
|
||||
lines.append(f"{i}\t<dcsset:lwsTitle>")
|
||||
lines.append(f"{i}\t\t<v8:item>")
|
||||
lines.append(f"{i}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{i}\t\t\t<v8:content>{esc_xml(title)}</v8:content>")
|
||||
lines.append(f"{i}\t\t</v8:item>")
|
||||
lines.append(f"{i}\t</dcsset:lwsTitle>")
|
||||
for item in items:
|
||||
lines.append(f'{i}\t<dcsset:item xsi:type="dcsset:SelectedItemField">')
|
||||
lines.append(f"{i}\t\t<dcsset:field>{esc_xml(item)}</dcsset:field>")
|
||||
lines.append(f"{i}\t</dcsset:item>")
|
||||
lines.append(f"{i}\t<dcsset:placement>Auto</dcsset:placement>")
|
||||
lines.append(f"{i}</dcsset:item>")
|
||||
return "\r\n".join(lines)
|
||||
lines = [
|
||||
f'{i}<dcsset:item xsi:type="dcsset:SelectedItemField">',
|
||||
f"{i}\t<dcsset:field>{esc_xml(field_name)}</dcsset:field>",
|
||||
@@ -782,6 +868,8 @@ def build_data_param_fragment(parsed, indent):
|
||||
if isinstance(val, dict) and val.get("variant"):
|
||||
lines.append(f'{i}\t<dcscor:value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{i}\t\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(val["variant"])}</v8:variant>')
|
||||
lines.append(f"{i}\t\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>")
|
||||
lines.append(f"{i}\t\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>")
|
||||
lines.append(f"{i}\t</dcscor:value>")
|
||||
elif re.match(r'^\d{4}-\d{2}-\d{2}T', str(val)):
|
||||
lines.append(f'{i}\t<dcscor:value xsi:type="xs:dateTime">{esc_xml(str(val))}</dcscor:value>')
|
||||
@@ -866,6 +954,16 @@ def build_variant_fragment(parsed, indent):
|
||||
return "\r\n".join(lines)
|
||||
|
||||
|
||||
def _emit_filter_comparison(lines, f, indent):
|
||||
lines.append(f'{indent}<dcsset:item xsi:type="dcsset:FilterItemComparison">')
|
||||
lines.append(f'{indent}\t<dcsset:left xsi:type="dcscor:Field">{esc_xml(f["field"])}</dcsset:left>')
|
||||
lines.append(f"{indent}\t<dcsset:comparisonType>{esc_xml(f['op'])}</dcsset:comparisonType>")
|
||||
if f.get("value") is not None:
|
||||
vt = f.get("valueType", "xs:string")
|
||||
lines.append(f'{indent}\t<dcsset:right xsi:type="{vt}">{esc_xml(str(f["value"]))}</dcsset:right>')
|
||||
lines.append(f"{indent}</dcsset:item>")
|
||||
|
||||
|
||||
def build_conditional_appearance_item_fragment(parsed, indent):
|
||||
i = indent
|
||||
lines = [f"{i}<dcsset:item>"]
|
||||
@@ -881,15 +979,17 @@ def build_conditional_appearance_item_fragment(parsed, indent):
|
||||
lines.append(f"{i}\t<dcsset:selection/>")
|
||||
|
||||
if parsed.get("filter"):
|
||||
f = parsed["filter"]
|
||||
flt = parsed["filter"]
|
||||
lines.append(f"{i}\t<dcsset:filter>")
|
||||
lines.append(f'{i}\t\t<dcsset:item xsi:type="dcsset:FilterItemComparison">')
|
||||
lines.append(f'{i}\t\t\t<dcsset:left xsi:type="dcscor:Field">{esc_xml(f["field"])}</dcsset:left>')
|
||||
lines.append(f"{i}\t\t\t<dcsset:comparisonType>{esc_xml(f['op'])}</dcsset:comparisonType>")
|
||||
if f.get("value") is not None:
|
||||
vt = f.get("valueType", "xs:string")
|
||||
lines.append(f'{i}\t\t\t<dcsset:right xsi:type="{vt}">{esc_xml(str(f["value"]))}</dcsset:right>')
|
||||
lines.append(f"{i}\t\t</dcsset:item>")
|
||||
if isinstance(flt, list):
|
||||
# OrGroup
|
||||
lines.append(f'{i}\t\t<dcsset:item xsi:type="dcsset:FilterItemGroup">')
|
||||
lines.append(f"{i}\t\t\t<dcsset:groupType>OrGroup</dcsset:groupType>")
|
||||
for f in flt:
|
||||
_emit_filter_comparison(lines, f, f"{i}\t\t\t")
|
||||
lines.append(f"{i}\t\t</dcsset:item>")
|
||||
else:
|
||||
_emit_filter_comparison(lines, flt, f"{i}\t\t")
|
||||
lines.append(f"{i}\t</dcsset:filter>")
|
||||
else:
|
||||
lines.append(f"{i}\t<dcsset:filter/>")
|
||||
@@ -897,15 +997,23 @@ def build_conditional_appearance_item_fragment(parsed, indent):
|
||||
# appearance
|
||||
lines.append(f"{i}\t<dcsset:appearance>")
|
||||
val = parsed["value"]
|
||||
val_type = "xs:string"
|
||||
if re.match(r'^(web|style|win):', val):
|
||||
val_type = "v8ui:Color"
|
||||
elif val in ("true", "false"):
|
||||
val_type = "xs:boolean"
|
||||
|
||||
lines.append(f'{i}\t\t<dcscor:item xsi:type="dcsset:SettingsParameterValue">')
|
||||
lines.append(f"{i}\t\t\t<dcscor:parameter>{esc_xml(parsed['param'])}</dcscor:parameter>")
|
||||
lines.append(f'{i}\t\t\t<dcscor:value xsi:type="{val_type}">{esc_xml(val)}</dcscor:value>')
|
||||
|
||||
if re.match(r'^(web|style|win):', val):
|
||||
lines.append(f'{i}\t\t\t<dcscor:value xsi:type="v8ui:Color">{esc_xml(val)}</dcscor:value>')
|
||||
elif val in ("true", "false"):
|
||||
lines.append(f'{i}\t\t\t<dcscor:value xsi:type="xs:boolean">{esc_xml(val)}</dcscor:value>')
|
||||
elif parsed["param"] in ("Формат", "Текст", "Заголовок"):
|
||||
lines.append(f'{i}\t\t\t<dcscor:value xsi:type="v8:LocalStringType">')
|
||||
lines.append(f"{i}\t\t\t\t<v8:item>")
|
||||
lines.append(f"{i}\t\t\t\t\t<v8:lang>ru</v8:lang>")
|
||||
lines.append(f"{i}\t\t\t\t\t<v8:content>{esc_xml(val)}</v8:content>")
|
||||
lines.append(f"{i}\t\t\t\t</v8:item>")
|
||||
lines.append(f"{i}\t\t\t</dcscor:value>")
|
||||
else:
|
||||
lines.append(f'{i}\t\t\t<dcscor:value xsi:type="xs:string">{esc_xml(val)}</dcscor:value>')
|
||||
|
||||
lines.append(f"{i}\t\t</dcscor:item>")
|
||||
lines.append(f"{i}\t</dcsset:appearance>")
|
||||
|
||||
@@ -917,6 +1025,9 @@ def build_structure_item_fragment(item, indent):
|
||||
i = indent
|
||||
lines = [f'{i}<dcsset:item xsi:type="dcsset:StructureItemGroup">']
|
||||
|
||||
if item.get("name"):
|
||||
lines.append(f"{i}\t<dcsset:name>{esc_xml(item['name'])}</dcsset:name>")
|
||||
|
||||
group_by = item.get("groupBy", [])
|
||||
if not group_by:
|
||||
lines.append(f"{i}\t<dcsset:groupItems/>")
|
||||
@@ -1165,7 +1276,7 @@ def resolve_variant_settings():
|
||||
break
|
||||
if sv:
|
||||
break
|
||||
if not sv:
|
||||
if sv is None:
|
||||
print(f"Variant '{variant_arg}' not found", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
@@ -1173,7 +1284,7 @@ def resolve_variant_settings():
|
||||
if isinstance(child.tag, str) and local_name(child) == "settingsVariant" and etree.QName(child.tag).namespace == SCH_NS:
|
||||
sv = child
|
||||
break
|
||||
if not sv:
|
||||
if sv is None:
|
||||
print("No settingsVariant found in DCS", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -1252,6 +1363,13 @@ xml_doc = tree.getroot()
|
||||
|
||||
if operation in ("set-query", "set-structure", "add-dataSet"):
|
||||
values = [value_arg]
|
||||
elif operation == "patch-query":
|
||||
values = [v for v in value_arg.split(";;") if v.strip()]
|
||||
elif operation == "add-drilldown":
|
||||
if ";;" in value_arg:
|
||||
values = [v.strip() for v in value_arg.split(";;") if v.strip()]
|
||||
else:
|
||||
values = [v.strip() for v in value_arg.split(",") if v.strip()]
|
||||
else:
|
||||
values = [v.strip() for v in value_arg.split(";;") if v.strip()]
|
||||
|
||||
@@ -1406,6 +1524,220 @@ elif operation == "add-parameter":
|
||||
if parsed.get("autoDates"):
|
||||
print('[OK] Auto-parameters "\u0414\u0430\u0442\u0430\u041d\u0430\u0447\u0430\u043b\u0430", "\u0414\u0430\u0442\u0430\u041e\u043a\u043e\u043d\u0447\u0430\u043d\u0438\u044f" added')
|
||||
|
||||
elif operation == "modify-parameter":
|
||||
for val in values:
|
||||
# Extract optional [Title] first (mirrors parse_field_shorthand)
|
||||
title_val = None
|
||||
m_title = re.search(r'\[([^\]]*)\]', val)
|
||||
if m_title:
|
||||
title_val = m_title.group(1).strip()
|
||||
val = re.sub(r'\s*\[[^\]]*\]\s*', ' ', val).strip()
|
||||
|
||||
parts = val.split(None, 1)
|
||||
param_name = parts[0].strip()
|
||||
rest = parts[1].strip() if len(parts) > 1 else ""
|
||||
|
||||
param_el = find_element_by_child_value(xml_doc, "parameter", "name", param_name, SCH_NS)
|
||||
if param_el is None:
|
||||
print(f'[WARN] Parameter "{param_name}" not found -- skipped')
|
||||
continue
|
||||
|
||||
child_indent = get_child_indent(param_el)
|
||||
|
||||
# Set/replace title (must come right after <name>, before <valueType>)
|
||||
if title_val is not None:
|
||||
existing_title = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "title"), None)
|
||||
if existing_title is not None:
|
||||
remove_node_with_whitespace(existing_title)
|
||||
# Insert before the first child after <name>
|
||||
title_ref = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) != "name"), None)
|
||||
title_frag = build_mltext_xml("title", title_val, child_indent)
|
||||
for node in import_fragment(xml_doc, title_frag):
|
||||
insert_before_element(param_el, node, title_ref, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": title set to "{title_val}"')
|
||||
|
||||
# Separate availableValue=... from simple kv pairs
|
||||
simple_rest = rest
|
||||
av_part = None
|
||||
av_idx = rest.find("availableValue=")
|
||||
if av_idx >= 0:
|
||||
simple_rest = rest[:av_idx].strip()
|
||||
av_part = rest[av_idx:]
|
||||
|
||||
# Process simple key=value pairs (use, denyIncompleteValues, etc.)
|
||||
if simple_rest:
|
||||
for m in re.finditer(r'(\w+)=(\S+)', simple_rest):
|
||||
key, value = m.group(1), m.group(2)
|
||||
existing = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == key), None)
|
||||
if existing is not None:
|
||||
existing.text = value
|
||||
print(f'[OK] Parameter "{param_name}": {key} updated to {value}')
|
||||
else:
|
||||
# Schema order: ...value, useRestriction, availableValue*, denyIncompleteValues, use
|
||||
ref_node = None
|
||||
if key == "denyIncompleteValues":
|
||||
ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "use"), None)
|
||||
frag_xml = f"{child_indent}<{key}>{esc_xml(value)}</{key}>"
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": {key}={value} added')
|
||||
|
||||
# Process availableValue
|
||||
if av_part:
|
||||
av_rest = av_part[len("availableValue="):]
|
||||
# Parse: "Перечисление...X presentation=текст с пробелами"
|
||||
av_parts = re.split(r'\s+presentation=', av_rest, 1)
|
||||
av_value = av_parts[0].strip()
|
||||
av_presentation = av_parts[1].strip() if len(av_parts) > 1 else ""
|
||||
|
||||
av_type = "xs:string"
|
||||
if re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', av_value):
|
||||
av_type = "dcscor:DesignTimeValue"
|
||||
|
||||
av_lines = [f"{child_indent}<availableValue>"]
|
||||
av_lines.append(f'{child_indent}\t<value xsi:type="{av_type}">{esc_xml(av_value)}</value>')
|
||||
if av_presentation:
|
||||
av_lines.append(f'{child_indent}\t<presentation xsi:type="v8:LocalStringType">')
|
||||
av_lines.append(f"{child_indent}\t\t<v8:item>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:lang>ru</v8:lang>")
|
||||
av_lines.append(f"{child_indent}\t\t\t<v8:content>{esc_xml(av_presentation)}</v8:content>")
|
||||
av_lines.append(f"{child_indent}\t\t</v8:item>")
|
||||
av_lines.append(f"{child_indent}\t</presentation>")
|
||||
av_lines.append(f"{child_indent}</availableValue>")
|
||||
frag_xml = "\r\n".join(av_lines)
|
||||
|
||||
# Insert before first of (denyIncompleteValues, use) in document order
|
||||
ref_node = None
|
||||
for child in param_el:
|
||||
if isinstance(child.tag, str) and local_name(child) in ("denyIncompleteValues", "use"):
|
||||
ref_node = child
|
||||
break
|
||||
nodes = import_fragment(xml_doc, frag_xml)
|
||||
for node in nodes:
|
||||
insert_before_element(param_el, node, ref_node, child_indent)
|
||||
print(f'[OK] Parameter "{param_name}": availableValue added')
|
||||
|
||||
elif operation == "rename-parameter":
|
||||
root = xml_doc
|
||||
for val in values:
|
||||
m_rn = re.match(r'^\s*(.+?)\s*=>\s*(.+?)\s*$', val)
|
||||
if not m_rn:
|
||||
print(f'[WARN] rename-parameter expects "OldName => NewName", got: {val}')
|
||||
continue
|
||||
old_name = m_rn.group(1).strip()
|
||||
new_name = m_rn.group(2).strip()
|
||||
|
||||
if old_name == new_name:
|
||||
print('[WARN] rename-parameter: old and new names are equal -- skipped')
|
||||
continue
|
||||
|
||||
# 1. Rename <parameter><name>OldName</name>
|
||||
param_el = find_element_by_child_value(root, "parameter", "name", old_name, SCH_NS)
|
||||
if param_el is None:
|
||||
print(f'[WARN] Parameter "{old_name}" not found -- skipped')
|
||||
continue
|
||||
for ch in param_el:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "name" and etree.QName(ch.tag).namespace == SCH_NS:
|
||||
ch.text = new_name
|
||||
break
|
||||
|
||||
# 2. Update <expression> in other <parameter> elements.
|
||||
# Regex matches "&OldName" only when followed by a non-identifier char (or end),
|
||||
# so "&Период" matches "&Период.ДатаНачала" but NOT "&ПериодОтчета".
|
||||
esc_old = re.escape(old_name)
|
||||
expr_regex = re.compile(rf'&{esc_old}(?=[^\w\u0400-\u04FF]|$)')
|
||||
expr_updated = 0
|
||||
for ch in root:
|
||||
if not (isinstance(ch.tag, str) and local_name(ch) == "parameter" and etree.QName(ch.tag).namespace == SCH_NS):
|
||||
continue
|
||||
for gc in ch:
|
||||
if isinstance(gc.tag, str) and local_name(gc) == "expression" and etree.QName(gc.tag).namespace == SCH_NS:
|
||||
old_expr = gc.text or ""
|
||||
new_expr = expr_regex.sub(f'&{new_name}', old_expr)
|
||||
if new_expr != old_expr:
|
||||
gc.text = new_expr
|
||||
expr_updated += 1
|
||||
|
||||
# 3. Update <dcscor:parameter>OldName</dcscor:parameter> in dataParameters of all variants.
|
||||
dp_updated = 0
|
||||
for variant_node in root:
|
||||
if not (isinstance(variant_node.tag, str) and local_name(variant_node) == "settingsVariant" and etree.QName(variant_node.tag).namespace == SCH_NS):
|
||||
continue
|
||||
settings_node = find_first_element(variant_node, ["settings"], SET_NS)
|
||||
if settings_node is None:
|
||||
continue
|
||||
dp_el = find_first_element(settings_node, ["dataParameters"], SET_NS)
|
||||
if dp_el is None:
|
||||
continue
|
||||
for item in dp_el:
|
||||
if not (isinstance(item.tag, str) and local_name(item) == "item"):
|
||||
continue
|
||||
for gc in item:
|
||||
if isinstance(gc.tag, str) and local_name(gc) == "parameter" and etree.QName(gc.tag).namespace == COR_NS:
|
||||
if (gc.text or "").strip() == old_name:
|
||||
gc.text = new_name
|
||||
dp_updated += 1
|
||||
|
||||
print(f'[OK] Parameter renamed: "{old_name}" => "{new_name}" (expressions updated: {expr_updated}, dataParameters updated: {dp_updated})')
|
||||
|
||||
elif operation == "reorder-parameters":
|
||||
root = xml_doc
|
||||
for val in values:
|
||||
order = [s.strip() for s in val.split(",") if s.strip()]
|
||||
if not order:
|
||||
print('[WARN] reorder-parameters: empty list -- skipped')
|
||||
continue
|
||||
|
||||
all_params = []
|
||||
for ch in root:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "parameter" and etree.QName(ch.tag).namespace == SCH_NS:
|
||||
all_params.append(ch)
|
||||
if not all_params:
|
||||
print('[WARN] reorder-parameters: no parameters in schema')
|
||||
continue
|
||||
|
||||
child_indent = get_child_indent(root)
|
||||
|
||||
by_name = {}
|
||||
for pe in all_params:
|
||||
for gc in pe:
|
||||
if isinstance(gc.tag, str) and local_name(gc) == "name" and etree.QName(gc.tag).namespace == SCH_NS:
|
||||
by_name[(gc.text or "").strip()] = pe
|
||||
break
|
||||
|
||||
new_order = []
|
||||
used = set()
|
||||
for name in order:
|
||||
if name in by_name:
|
||||
new_order.append(by_name[name])
|
||||
used.add(name)
|
||||
else:
|
||||
print(f'[WARN] reorder-parameters: parameter "{name}" not found -- skipped')
|
||||
|
||||
for pe in all_params:
|
||||
pe_name = None
|
||||
for gc in pe:
|
||||
if isinstance(gc.tag, str) and local_name(gc) == "name" and etree.QName(gc.tag).namespace == SCH_NS:
|
||||
pe_name = (gc.text or "").strip()
|
||||
break
|
||||
if pe_name and pe_name not in used:
|
||||
new_order.append(pe)
|
||||
|
||||
# Anchor: element right after the last parameter in original order
|
||||
last_param = all_params[-1]
|
||||
anchor = last_param.getnext()
|
||||
|
||||
# Remove all parameters with surrounding whitespace
|
||||
for pe in all_params:
|
||||
remove_node_with_whitespace(pe)
|
||||
|
||||
# Re-insert in new order before anchor
|
||||
for pe in new_order:
|
||||
insert_before_element(root, pe, anchor, child_indent)
|
||||
|
||||
print(f'[OK] Parameters reordered ({len(all_params)} total, {len(order)} explicit)')
|
||||
|
||||
elif operation == "add-filter":
|
||||
settings = resolve_variant_settings()
|
||||
var_name = get_variant_name()
|
||||
@@ -1470,13 +1802,53 @@ elif operation == "add-selection":
|
||||
var_name = get_variant_name()
|
||||
for val in values:
|
||||
field_name = val.strip()
|
||||
selection = ensure_settings_child(settings, "selection", [])
|
||||
group_name = None
|
||||
|
||||
# Extract @group=Name
|
||||
gm = re.search(r'\s*@group=(\S+)', field_name)
|
||||
if gm:
|
||||
group_name = gm.group(1)
|
||||
field_name = re.sub(r'\s*@group=\S+', '', field_name).strip()
|
||||
|
||||
if group_name:
|
||||
# Find named StructureItemGroup
|
||||
target_el = None
|
||||
for item in settings.iter(f"{{{SET_NS}}}item"):
|
||||
xsi_type = item.get(f"{{{XSI_NS}}}type", "")
|
||||
if "StructureItemGroup" in xsi_type:
|
||||
name_el = item.find(f"{{{SET_NS}}}name")
|
||||
if name_el is not None and name_el.text == group_name:
|
||||
target_el = item
|
||||
break
|
||||
if target_el is None:
|
||||
print(f'[WARN] StructureItemGroup "{group_name}" not found -- adding to variant level')
|
||||
target_el = settings
|
||||
else:
|
||||
target_el = settings
|
||||
|
||||
selection = ensure_settings_child(target_el, "selection", [])
|
||||
|
||||
# Dedup: skip if SelectedItemAuto already exists
|
||||
if field_name == "Auto":
|
||||
is_dup = False
|
||||
for ch in selection:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "item":
|
||||
type_attr = ch.get(XSI_TYPE, "")
|
||||
if "SelectedItemAuto" in type_attr:
|
||||
is_dup = True
|
||||
break
|
||||
if is_dup:
|
||||
target = f'group "{group_name}"' if group_name else f'variant "{var_name}"'
|
||||
print(f'[WARN] SelectedItemAuto already exists in {target} -- skipped')
|
||||
continue
|
||||
|
||||
sel_indent = get_container_child_indent(selection)
|
||||
sel_xml = build_selection_item_fragment(field_name, sel_indent)
|
||||
sel_nodes = import_fragment(xml_doc, sel_xml)
|
||||
for node in sel_nodes:
|
||||
insert_before_element(selection, node, None, sel_indent)
|
||||
print(f'[OK] Selection "{field_name}" added to variant "{var_name}"')
|
||||
target = f'group "{group_name}"' if group_name else f'variant "{var_name}"'
|
||||
print(f'[OK] Selection "{field_name}" added to {target}')
|
||||
|
||||
elif operation == "set-query":
|
||||
ds_node = resolve_data_set()
|
||||
@@ -1674,7 +2046,11 @@ elif operation == "add-conditionalAppearance":
|
||||
|
||||
desc = f"{parsed['param']} = {parsed['value']}"
|
||||
if parsed.get("filter"):
|
||||
desc += f" when {parsed['filter']['field']} {parsed['filter']['op']}"
|
||||
flt = parsed["filter"]
|
||||
if isinstance(flt, list):
|
||||
desc += f" when OrGroup({len(flt)} conditions)"
|
||||
else:
|
||||
desc += f" when {flt['field']} {flt['op']}"
|
||||
if parsed.get("fields"):
|
||||
desc += f" for {', '.join(parsed['fields'])}"
|
||||
print(f'[OK] ConditionalAppearance "{desc}" added to variant "{var_name}"')
|
||||
@@ -1731,9 +2107,11 @@ elif operation == "modify-filter":
|
||||
vt = parsed.get("valueType", "xs:string")
|
||||
set_or_create_child_element_with_attr(filter_item, "right", SET_NS, str(parsed["value"]), vt, item_indent)
|
||||
|
||||
# Update use (only when explicitly set via @off / @on)
|
||||
if parsed.get("use") is False:
|
||||
set_or_create_child_element(filter_item, "use", SET_NS, "false", item_indent)
|
||||
else:
|
||||
elif parsed.get("use") is True:
|
||||
# @on: remove existing use=false if any
|
||||
for ch in filter_item:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "use" and etree.QName(ch.tag).namespace == SET_NS:
|
||||
if (ch.text or "").strip() == "false":
|
||||
@@ -1780,6 +2158,8 @@ elif operation == "modify-dataParameter":
|
||||
if isinstance(pv, dict) and pv.get("variant"):
|
||||
val_lines.append(f'{item_indent}<dcscor:value xsi:type="v8:StandardPeriod">')
|
||||
val_lines.append(f'{item_indent}\t<v8:variant xsi:type="v8:StandardPeriodVariant">{esc_xml(pv["variant"])}</v8:variant>')
|
||||
val_lines.append(f"{item_indent}\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>")
|
||||
val_lines.append(f"{item_indent}\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>")
|
||||
val_lines.append(f"{item_indent}</dcscor:value>")
|
||||
elif re.match(r'^\d{4}-\d{2}-\d{2}T', str(pv)):
|
||||
val_lines.append(f'{item_indent}<dcscor:value xsi:type="xs:dateTime">{esc_xml(str(pv))}</dcscor:value>')
|
||||
@@ -1793,9 +2173,11 @@ elif operation == "modify-dataParameter":
|
||||
for node in val_nodes:
|
||||
insert_before_element(dp_item, node, None, item_indent)
|
||||
|
||||
# Update use (only when explicitly set via @off / @on)
|
||||
if parsed.get("use") is False:
|
||||
set_or_create_child_element(dp_item, "use", COR_NS, "false", item_indent)
|
||||
else:
|
||||
elif parsed.get("use") is True:
|
||||
# @on: remove existing use=false if any
|
||||
for ch in dp_item:
|
||||
if isinstance(ch.tag, str) and local_name(ch) == "use" and etree.QName(ch.tag).namespace == COR_NS:
|
||||
if (ch.text or "").strip() == "false":
|
||||
@@ -1937,6 +2319,151 @@ elif operation == "remove-filter":
|
||||
remove_node_with_whitespace(filter_item)
|
||||
print(f'[OK] Filter for "{field_name}" removed from variant "{var_name}"')
|
||||
|
||||
elif operation == "add-drilldown":
|
||||
# String-based manipulation — templates use dcsat namespace with inline xmlns
|
||||
with open(resolved_path, "r", encoding="utf-8-sig") as f:
|
||||
raw_text = f.read()
|
||||
nl = "\r\n"
|
||||
dcsat_ns_decl = 'xmlns:dcsat="http://v8.1c.ru/8.1/data-composition-system/area-template"'
|
||||
|
||||
# Find all outer <template> blocks by nesting-aware scan
|
||||
name_regex = re.compile(r'<template>\s*<name>([^<]+)</name>')
|
||||
tpl_starts = [(m.start(), m.group(1)) for m in name_regex.finditer(raw_text)]
|
||||
|
||||
# For each start, find closing </template> at nesting depth 0
|
||||
tpl_blocks = []
|
||||
for ts_pos, ts_name in tpl_starts:
|
||||
depth = 1
|
||||
scan_pos = ts_pos + 10 # skip past opening <template>
|
||||
while depth > 0 and scan_pos < len(raw_text):
|
||||
next_open = raw_text.find("<template", scan_pos)
|
||||
next_close = raw_text.find("</template>", scan_pos)
|
||||
if next_close < 0:
|
||||
break
|
||||
if next_open >= 0 and next_open < next_close:
|
||||
depth += 1
|
||||
scan_pos = next_open + 10
|
||||
else:
|
||||
depth -= 1
|
||||
if depth == 0:
|
||||
end_pos = next_close + len("</template>")
|
||||
tpl_blocks.append((ts_name, ts_pos, raw_text[ts_pos:end_pos]))
|
||||
scan_pos = next_close + 11
|
||||
|
||||
if not tpl_blocks:
|
||||
print("[WARN] No named templates found in schema")
|
||||
|
||||
# Collect all insertions as (position, text) — apply in reverse order
|
||||
insertions = []
|
||||
|
||||
expr_regex = re.compile(
|
||||
r'(?s)<parameter[^>]*ExpressionAreaTemplateParameter[^>]*>\s*'
|
||||
r'<dcsat:name>([^<]+)</dcsat:name>\s*'
|
||||
r'<dcsat:expression>([^<]+)</dcsat:expression>\s*</parameter>'
|
||||
)
|
||||
|
||||
for tpl_name, tpl_start, tpl_text in tpl_blocks:
|
||||
|
||||
# Build map: expression → paramName from ExpressionAreaTemplateParameter
|
||||
expr_map = {}
|
||||
for em in expr_regex.finditer(tpl_text):
|
||||
p_name = em.group(1)
|
||||
p_expr = em.group(2)
|
||||
expr_map[p_expr] = p_name
|
||||
|
||||
for resource in values:
|
||||
drill_name = f"Расшифровка_{resource}"
|
||||
|
||||
# Idempotency: check if already exists
|
||||
if drill_name in tpl_text:
|
||||
print(f"[INFO] {drill_name} already exists in {tpl_name} — skipped")
|
||||
continue
|
||||
|
||||
# Find ExpressionAreaTemplateParameter by expression
|
||||
param_name = expr_map.get(resource)
|
||||
if param_name is None:
|
||||
print(f'[WARN] Expression "{resource}" not found in template {tpl_name} — skipped')
|
||||
continue
|
||||
|
||||
cell_count = 0
|
||||
|
||||
# Step 1: Insert DetailsAreaTemplateParameter after last </parameter> in template
|
||||
last_param_end_tag = "</parameter>"
|
||||
last_param_pos = tpl_text.rfind(last_param_end_tag)
|
||||
if last_param_pos >= 0:
|
||||
insert_pos = tpl_start + last_param_pos + len(last_param_end_tag)
|
||||
# Detect indent from context
|
||||
prev_nl = tpl_text.rfind("\n", 0, last_param_pos)
|
||||
indent = "\t\t"
|
||||
if prev_nl >= 0:
|
||||
line_start = prev_nl + 1
|
||||
indent_match = re.match(r'^(\s*)', tpl_text[line_start:])
|
||||
if indent_match:
|
||||
indent = indent_match.group(1)
|
||||
details_xml = (
|
||||
f'{nl}{indent}<parameter {dcsat_ns_decl} xsi:type="dcsat:DetailsAreaTemplateParameter">'
|
||||
f'{nl}{indent}\t<dcsat:name>{drill_name}</dcsat:name>'
|
||||
f'{nl}{indent}\t<dcsat:fieldExpression>'
|
||||
f'{nl}{indent}\t\t<dcsat:field>ИмяРесурса</dcsat:field>'
|
||||
f'{nl}{indent}\t\t<dcsat:expression>"{resource}"</dcsat:expression>'
|
||||
f'{nl}{indent}\t</dcsat:fieldExpression>'
|
||||
f'{nl}{indent}\t<dcsat:mainAction>DrillDown</dcsat:mainAction>'
|
||||
f'{nl}{indent}</parameter>'
|
||||
)
|
||||
insertions.append((insert_pos, details_xml))
|
||||
|
||||
# Step 2: Insert appearance binding in cells referencing this parameter
|
||||
cell_tag = f'<dcsat:value xsi:type="dcscor:Parameter">{param_name}</dcsat:value>'
|
||||
search_start = 0
|
||||
while True:
|
||||
cell_idx = tpl_text.find(cell_tag, search_start)
|
||||
if cell_idx < 0:
|
||||
break
|
||||
cell_end = tpl_text.find("</dcsat:tableCell>", cell_idx)
|
||||
if cell_end < 0:
|
||||
break
|
||||
app_end = tpl_text.rfind("</dcsat:appearance>", cell_idx, cell_end)
|
||||
if app_end < cell_idx:
|
||||
search_start = cell_end + 1
|
||||
continue
|
||||
|
||||
# Detect indent for appearance items — insert after \n, before indent of </dcsat:appearance>
|
||||
app_prev_nl = tpl_text.rfind("\n", 0, app_end)
|
||||
app_indent = "\t\t\t\t\t\t"
|
||||
if app_prev_nl >= 0:
|
||||
app_line_start = app_prev_nl + 1
|
||||
app_indent_match = re.match(r'^(\s*)', tpl_text[app_line_start:])
|
||||
if app_indent_match:
|
||||
app_indent = app_indent_match.group(1)
|
||||
item_indent = app_indent + "\t"
|
||||
appearance_xml = (
|
||||
f'{item_indent}<dcscor:item>{nl}'
|
||||
f'{item_indent}\t<dcscor:parameter>Расшифровка</dcscor:parameter>{nl}'
|
||||
f'{item_indent}\t<dcscor:value xsi:type="dcscor:Parameter">{drill_name}</dcscor:value>{nl}'
|
||||
f'{item_indent}</dcscor:item>{nl}'
|
||||
)
|
||||
# Insert after \n (before indent of closing tag), not before the tag itself
|
||||
insert_at = (tpl_start + app_prev_nl + 1) if app_prev_nl >= 0 else (tpl_start + app_end)
|
||||
insertions.append((insert_at, appearance_xml))
|
||||
cell_count += 1
|
||||
search_start = cell_end + 1
|
||||
|
||||
print(f"[OK] {drill_name} \u2192 {tpl_name} (param + {cell_count} cell(s))")
|
||||
|
||||
# Apply insertions in reverse order to preserve offsets.
|
||||
# For same position: reverse insertion order so first resource ends up first in file.
|
||||
insertions = [(pos, text, seq) for seq, (pos, text) in enumerate(insertions)]
|
||||
insertions.sort(key=lambda x: (x[0], x[2]), reverse=True)
|
||||
for pos, text, _seq in insertions:
|
||||
raw_text = raw_text[:pos] + text + raw_text[pos:]
|
||||
|
||||
# Write directly — skip lxml save
|
||||
with open(resolved_path, "wb") as f:
|
||||
f.write(b'\xef\xbb\xbf')
|
||||
f.write(raw_text.encode("utf-8"))
|
||||
print(f"[OK] Saved {resolved_path}")
|
||||
sys.exit(0)
|
||||
|
||||
# ── 9. Save ─────────────────────────────────────────────────
|
||||
|
||||
xml_bytes = etree.tostring(tree, xml_declaration=True, encoding="UTF-8")
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
# skd-info v1.0 — Analyze 1C DCS structure
|
||||
# skd-info v1.3 — Analyze 1C DCS structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
[ValidateSet("overview", "query", "fields", "links", "calculated", "resources", "params", "variant", "trace", "templates", "full")]
|
||||
[string]$Mode = "overview",
|
||||
@@ -17,6 +18,8 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Resolve path ---
|
||||
|
||||
$originalPath = $TemplatePath
|
||||
|
||||
if (-not $TemplatePath.EndsWith(".xml")) {
|
||||
$candidate = Join-Path (Join-Path $TemplatePath "Ext") "Template.xml"
|
||||
if (Test-Path $candidate) {
|
||||
@@ -24,7 +27,48 @@ if (-not $TemplatePath.EndsWith(".xml")) {
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $TemplatePath)) {
|
||||
# If still not a file, try resolving from object directory (Reports/X, DataProcessors/X)
|
||||
if (-not (Test-Path $TemplatePath -PathType Leaf)) {
|
||||
$templatesDir = Join-Path $originalPath "Templates"
|
||||
if (Test-Path $templatesDir) {
|
||||
$dcsTemplates = @()
|
||||
foreach ($metaXml in (Get-ChildItem $templatesDir -Filter "*.xml" -File)) {
|
||||
[xml]$meta = Get-Content $metaXml.FullName -Encoding UTF8
|
||||
$tt = $meta.SelectSingleNode("//*[local-name()='TemplateType']")
|
||||
if ($tt -and $tt.InnerText -eq "DataCompositionSchema") {
|
||||
$tplName = [System.IO.Path]::GetFileNameWithoutExtension($metaXml.Name)
|
||||
$tplPath = Join-Path (Join-Path (Join-Path $templatesDir $tplName) "Ext") "Template.xml"
|
||||
if (Test-Path $tplPath) {
|
||||
$dcsTemplates += $tplPath
|
||||
}
|
||||
}
|
||||
}
|
||||
if ($dcsTemplates.Count -eq 1) {
|
||||
$TemplatePath = $dcsTemplates[0]
|
||||
$resolvedMsg = (Resolve-Path $TemplatePath).Path
|
||||
$cwd = (Get-Location).Path
|
||||
if ($resolvedMsg.StartsWith($cwd)) {
|
||||
$resolvedMsg = $resolvedMsg.Substring($cwd.Length + 1)
|
||||
}
|
||||
Write-Host "[i] Resolved: $resolvedMsg"
|
||||
} elseif ($dcsTemplates.Count -gt 1) {
|
||||
Write-Host "Multiple DCS templates found in: $originalPath"
|
||||
$cwd = (Get-Location).Path
|
||||
for ($i = 0; $i -lt $dcsTemplates.Count; $i++) {
|
||||
$p = (Resolve-Path $dcsTemplates[$i]).Path
|
||||
if ($p.StartsWith($cwd)) { $p = $p.Substring($cwd.Length + 1) }
|
||||
Write-Host " $($i+1). $p"
|
||||
}
|
||||
Write-Host "Specify the template path."
|
||||
exit 1
|
||||
} else {
|
||||
Write-Error "No DCS templates found in: $originalPath"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not (Test-Path $TemplatePath -PathType Leaf)) {
|
||||
Write-Error "File not found: $TemplatePath"
|
||||
exit 1
|
||||
}
|
||||
@@ -402,7 +446,11 @@ function Show-Overview {
|
||||
if ($fieldTpls.Count -gt 0) { $parts += "$($fieldTpls.Count) field" }
|
||||
$grpCount = $groupTpls.Count + $groupHeaderTpls.Count + $groupFooterTpls.Count
|
||||
if ($grpCount -gt 0) { $parts += "$grpCount group" }
|
||||
$lines.Add("Templates: $($tplDefs.Count) defined ($($parts -join ', ') bindings)")
|
||||
if ($parts.Count -gt 0) {
|
||||
$lines.Add("Templates: $($tplDefs.Count) defined ($($parts -join ', ') bindings)")
|
||||
} else {
|
||||
$lines.Add("Templates: $($tplDefs.Count) defined")
|
||||
}
|
||||
}
|
||||
|
||||
# Parameters — split visible/hidden
|
||||
@@ -1305,7 +1353,18 @@ if ($Mode -eq "variant") {
|
||||
elseif ($Mode -eq "full") {
|
||||
Show-Overview
|
||||
$lines.Add(""); $lines.Add("--- query ---"); $lines.Add("")
|
||||
Show-Query
|
||||
$hasQuery = $root.SelectNodes("descendant::s:dataSet[@xsi:type='DataSetQuery']", $ns).Count -gt 0
|
||||
if ($hasQuery) {
|
||||
Show-Query
|
||||
} else {
|
||||
$objNodes = $root.SelectNodes("descendant::s:dataSet[@xsi:type='DataSetObject']/s:objectName", $ns)
|
||||
if ($objNodes.Count -gt 0) {
|
||||
$names = @(); foreach ($n in $objNodes) { $names += $n.InnerText }
|
||||
$lines.Add("(no query datasets; external datasets: $($names -join ', '))")
|
||||
} else {
|
||||
$lines.Add("(no query datasets)")
|
||||
}
|
||||
}
|
||||
$lines.Add(""); $lines.Add("--- fields ---"); $lines.Add("")
|
||||
Show-Fields
|
||||
$lines.Add(""); $lines.Add("--- resources ---"); $lines.Add("")
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-info v1.0 — Analyze 1C DCS structure
|
||||
# skd-info v1.3 — Analyze 1C DCS structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -269,7 +269,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C DCS structure", allow_abbrev=False)
|
||||
parser.add_argument("-TemplatePath", required=True)
|
||||
parser.add_argument("-TemplatePath", "-Path", required=True)
|
||||
parser.add_argument("-Mode", default="overview",
|
||||
choices=["overview", "query", "fields", "links", "calculated",
|
||||
"resources", "params", "variant", "trace", "templates", "full"])
|
||||
@@ -281,12 +281,48 @@ def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
# --- Resolve path ---
|
||||
template_path = args.TemplatePath
|
||||
original_path = args.TemplatePath
|
||||
template_path = original_path
|
||||
if not template_path.endswith(".xml"):
|
||||
candidate = os.path.join(template_path, "Ext", "Template.xml")
|
||||
if os.path.isfile(candidate):
|
||||
template_path = candidate
|
||||
|
||||
# If still not found, try resolving from object directory (Reports/X, DataProcessors/X)
|
||||
if not os.path.isfile(template_path) and not template_path.endswith(".xml"):
|
||||
templates_dir = os.path.join(original_path, "Templates")
|
||||
if os.path.isdir(templates_dir):
|
||||
dcs_templates = []
|
||||
for fname in os.listdir(templates_dir):
|
||||
if not fname.endswith(".xml"):
|
||||
continue
|
||||
meta_path = os.path.join(templates_dir, fname)
|
||||
if not os.path.isfile(meta_path):
|
||||
continue
|
||||
try:
|
||||
meta_tree = etree.parse(meta_path, etree.XMLParser(remove_blank_text=True))
|
||||
tt_nodes = meta_tree.xpath("//*[local-name()='TemplateType']")
|
||||
if tt_nodes and (tt_nodes[0].text or "").strip() == "DataCompositionSchema":
|
||||
tpl_name = os.path.splitext(fname)[0]
|
||||
tpl_path = os.path.join(templates_dir, tpl_name, "Ext", "Template.xml")
|
||||
if os.path.isfile(tpl_path):
|
||||
dcs_templates.append(tpl_path)
|
||||
except Exception:
|
||||
continue
|
||||
if len(dcs_templates) == 1:
|
||||
template_path = dcs_templates[0]
|
||||
resolved_display = os.path.relpath(os.path.abspath(template_path))
|
||||
print(f"[i] Resolved: {resolved_display}")
|
||||
elif len(dcs_templates) > 1:
|
||||
print(f"Multiple DCS templates found in: {original_path}")
|
||||
for i, p in enumerate(dcs_templates):
|
||||
print(f" {i+1}. {os.path.relpath(os.path.abspath(p))}")
|
||||
print("Specify the template path.")
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"No DCS templates found in: {original_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
if not os.path.isabs(template_path):
|
||||
template_path = os.path.join(os.getcwd(), template_path)
|
||||
|
||||
@@ -417,7 +453,10 @@ def main():
|
||||
grp_count = len(group_tpls) + len(group_header_tpls) + len(group_footer_tpls)
|
||||
if grp_count > 0:
|
||||
parts.append(f"{grp_count} group")
|
||||
lines.append(f"Templates: {len(tpl_defs)} defined ({', '.join(parts)} bindings)")
|
||||
if parts:
|
||||
lines.append(f"Templates: {len(tpl_defs)} defined ({', '.join(parts)} bindings)")
|
||||
else:
|
||||
lines.append(f"Templates: {len(tpl_defs)} defined")
|
||||
|
||||
# Parameters -- split visible/hidden
|
||||
params = root.findall("s:parameter", NSMAP)
|
||||
@@ -1626,7 +1665,14 @@ def main():
|
||||
lines.append("")
|
||||
lines.append("--- query ---")
|
||||
lines.append("")
|
||||
show_query()
|
||||
if root.findall(".//s:dataSet[@xsi:type='DataSetQuery']", NSMAP):
|
||||
show_query()
|
||||
else:
|
||||
obj_names = [n.text for n in root.findall(".//s:dataSet[@xsi:type='DataSetObject']/s:objectName", NSMAP) if n.text]
|
||||
if obj_names:
|
||||
lines.append(f"(no query datasets; external datasets: {', '.join(obj_names)})")
|
||||
else:
|
||||
lines.append("(no query datasets)")
|
||||
lines.append("")
|
||||
lines.append("--- fields ---")
|
||||
lines.append("")
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
[Alias('Path')]
|
||||
[string]$TemplatePath,
|
||||
|
||||
[switch]$Detailed,
|
||||
|
||||
@@ -12,7 +12,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
# ── arg parsing ──────────────────────────────────────────────
|
||||
|
||||
parser = argparse.ArgumentParser(allow_abbrev=False)
|
||||
parser.add_argument("-TemplatePath", required=True)
|
||||
parser.add_argument("-TemplatePath", "-Path", required=True)
|
||||
parser.add_argument("-Detailed", action="store_true")
|
||||
parser.add_argument("-MaxErrors", type=int, default=20)
|
||||
parser.add_argument("-OutFile", default="")
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: subsystem-compile
|
||||
description: Создать подсистему 1С — XML-исходники из JSON-определения. Используй когда пользователь просит добавить подсистему (раздел) в конфигурацию
|
||||
description: Создать подсистему 1С — XML-исходники из JSON-определения. Используй когда нужно добавить подсистему (раздел) в конфигурацию
|
||||
argument-hint: "[-DefinitionFile <json> | -Value <json-string>] -OutputDir <ConfigDir> [-Parent <path>]"
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -38,8 +38,7 @@ powershell.exe -NoProfile -File '.claude/skills/subsystem-compile/scripts/subsys
|
||||
"useOneCommand": false,
|
||||
"explanation": "Описание раздела",
|
||||
"picture": "CommonPicture.МояКартинка",
|
||||
"content": ["Catalog.Товары", "Document.Заказ"],
|
||||
"children": ["ДочерняяА", "ДочерняяБ"]
|
||||
"content": ["Catalog.Товары", "Document.Заказ"]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -58,8 +57,3 @@ powershell.exe -NoProfile -File '.claude/skills/subsystem-compile/scripts/subsys
|
||||
... -Value '{"name":"Дочерняя"}' -OutputDir config/ -Parent config/Subsystems/Продажи.xml
|
||||
```
|
||||
|
||||
## Что генерируется
|
||||
|
||||
- `{OutputDir}/Subsystems/{Name}.xml` — определение подсистемы
|
||||
- `{OutputDir}/Subsystems/{Name}/` — каталог (если есть children)
|
||||
- `Configuration.xml` или родительская подсистема — регистрация в `<ChildObjects>`
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# subsystem-compile v1.2 — Create 1C subsystem from JSON definition
|
||||
# subsystem-compile v1.5 — Create 1C subsystem from JSON definition
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -85,6 +85,29 @@ function New-Guid-String {
|
||||
return [System.Guid]::NewGuid().ToString()
|
||||
}
|
||||
|
||||
function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [string]$formatVersion, [System.Text.Encoding]$utf8Bom) {
|
||||
$childUuid = New-Guid-String
|
||||
$sb = New-Object System.Text.StringBuilder 2048
|
||||
[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("`t<Subsystem uuid=`"$childUuid`">")
|
||||
[void]$sb.AppendLine("`t`t<Properties>")
|
||||
[void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>")
|
||||
[void]$sb.AppendLine("`t`t`t<Synonym/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Comment/>")
|
||||
[void]$sb.AppendLine("`t`t`t<IncludeHelpInContents>true</IncludeHelpInContents>")
|
||||
[void]$sb.AppendLine("`t`t`t<IncludeInCommandInterface>true</IncludeInCommandInterface>")
|
||||
[void]$sb.AppendLine("`t`t`t<UseOneCommand>false</UseOneCommand>")
|
||||
[void]$sb.AppendLine("`t`t`t<Explanation/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Picture/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Content/>")
|
||||
[void]$sb.AppendLine("`t`t</Properties>")
|
||||
[void]$sb.AppendLine("`t`t<ChildObjects/>")
|
||||
[void]$sb.AppendLine("`t</Subsystem>")
|
||||
[void]$sb.AppendLine('</MetaDataObject>')
|
||||
[System.IO.File]::WriteAllText($childPath, $sb.ToString(), $utf8Bom)
|
||||
}
|
||||
|
||||
# --- 3. Content type normalization (plural→singular, Russian→English) ---
|
||||
$script:contentTypeMap = @{
|
||||
# Plural English → Singular
|
||||
@@ -267,12 +290,31 @@ if ($def.children) {
|
||||
foreach ($ch in $def.children) { $children += "$ch" }
|
||||
}
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion $OutputDir
|
||||
|
||||
# --- 4. Build XML ---
|
||||
$uuid = New-Guid-String
|
||||
$indent = "`t`t`t"
|
||||
|
||||
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="2.17">'
|
||||
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 "`t<Subsystem uuid=`"$uuid`">"
|
||||
X "`t`t<Properties>"
|
||||
|
||||
@@ -366,13 +408,23 @@ $utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($targetXml, $xmlContent, $utf8Bom)
|
||||
Write-Host "[OK] Created: $targetXml"
|
||||
|
||||
# Create subdirectory if children exist
|
||||
# Create subdirectory and stub files for children if they exist
|
||||
if ($children.Count -gt 0) {
|
||||
$childSubsDir = Join-Path (Join-Path $subsDir $objName) "Subsystems"
|
||||
if (-not (Test-Path $childSubsDir)) {
|
||||
New-Item -ItemType Directory -Path $childSubsDir -Force | Out-Null
|
||||
Write-Host "[OK] Created directory: $childSubsDir"
|
||||
}
|
||||
$seen = @{}
|
||||
foreach ($ch in $children) {
|
||||
if ($seen.ContainsKey($ch)) { continue }
|
||||
$seen[$ch] = $true
|
||||
$childXml = Join-Path $childSubsDir "$ch.xml"
|
||||
if (-not (Test-Path $childXml)) {
|
||||
Write-ChildSubsystemStub $childXml $ch $formatVersion $utf8Bom
|
||||
Write-Host "[OK] Created stub: $childXml"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- 6. Register in parent ---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# subsystem-compile v1.2 — Create 1C subsystem from JSON definition
|
||||
# subsystem-compile v1.5 — Create 1C subsystem from JSON definition
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -10,6 +10,22 @@ import uuid
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 esc_xml(s):
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
|
||||
@@ -44,6 +60,48 @@ def split_camel_case(name):
|
||||
return result
|
||||
|
||||
|
||||
def write_child_subsystem_stub(child_path, child_name, format_version):
|
||||
child_uuid = new_uuid()
|
||||
lines = []
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
lines.append(
|
||||
'<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('\t\t<Properties>')
|
||||
lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>')
|
||||
lines.append('\t\t\t<Synonym/>')
|
||||
lines.append('\t\t\t<Comment/>')
|
||||
lines.append('\t\t\t<IncludeHelpInContents>true</IncludeHelpInContents>')
|
||||
lines.append('\t\t\t<IncludeInCommandInterface>true</IncludeInCommandInterface>')
|
||||
lines.append('\t\t\t<UseOneCommand>false</UseOneCommand>')
|
||||
lines.append('\t\t\t<Explanation/>')
|
||||
lines.append('\t\t\t<Picture/>')
|
||||
lines.append('\t\t\t<Content/>')
|
||||
lines.append('\t\t</Properties>')
|
||||
lines.append('\t\t<ChildObjects/>')
|
||||
lines.append('\t</Subsystem>')
|
||||
lines.append('</MetaDataObject>')
|
||||
write_utf8_bom(child_path, '\n'.join(lines) + '\n')
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -169,6 +227,8 @@ def main():
|
||||
type_part = CONTENT_TYPE_MAP[type_part]
|
||||
return f'{type_part}.{name_part}'
|
||||
|
||||
format_version = detect_format_version(output_dir)
|
||||
|
||||
# --- 3. Resolve defaults ---
|
||||
synonym = str(defn['synonym']) if defn.get('synonym') else split_camel_case(obj_name)
|
||||
comment = str(defn['comment']) if defn.get('comment') else ''
|
||||
@@ -205,7 +265,7 @@ def main():
|
||||
lines = []
|
||||
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
lines.append('<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">')
|
||||
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'\t<Subsystem uuid="{uid}">')
|
||||
lines.append('\t\t<Properties>')
|
||||
|
||||
@@ -287,12 +347,21 @@ def main():
|
||||
write_utf8_bom(target_xml, xml_content)
|
||||
print(f"[OK] Created: {target_xml}")
|
||||
|
||||
# Create subdirectory if children exist
|
||||
# Create subdirectory and stub files for children if they exist
|
||||
if len(children) > 0:
|
||||
child_subs_dir = os.path.join(subs_dir, obj_name, 'Subsystems')
|
||||
if not os.path.exists(child_subs_dir):
|
||||
os.makedirs(child_subs_dir, exist_ok=True)
|
||||
print(f"[OK] Created directory: {child_subs_dir}")
|
||||
seen = set()
|
||||
for ch in children:
|
||||
if ch in seen:
|
||||
continue
|
||||
seen.add(ch)
|
||||
child_xml = os.path.join(child_subs_dir, f'{ch}.xml')
|
||||
if not os.path.exists(child_xml):
|
||||
write_child_subsystem_stub(child_xml, ch, format_version)
|
||||
print(f"[OK] Created stub: {child_xml}")
|
||||
|
||||
# --- 5. Register in parent ---
|
||||
parent_xml_path = None
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# subsystem-edit v1.1 — Edit existing 1C subsystem XML
|
||||
# subsystem-edit v1.2 — Edit existing 1C subsystem XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$SubsystemPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath,
|
||||
[string]$DefinitionFile,
|
||||
[ValidateSet("add-content","remove-content","add-child","remove-child","set-property")]
|
||||
[string]$Operation,
|
||||
@@ -119,12 +119,17 @@ if (-not (Test-Path $SubsystemPath)) {
|
||||
}
|
||||
if (-not (Test-Path $SubsystemPath)) { Write-Error "File not found: $SubsystemPath"; exit 1 }
|
||||
$resolvedPath = (Resolve-Path $SubsystemPath).Path
|
||||
$script:resolvedPath = $resolvedPath
|
||||
|
||||
# --- Load XML with PreserveWhitespace ---
|
||||
$script:xmlDoc = New-Object System.Xml.XmlDocument
|
||||
$script:xmlDoc.PreserveWhitespace = $true
|
||||
$script:xmlDoc.Load($resolvedPath)
|
||||
|
||||
$script:formatVersion = $script:xmlDoc.DocumentElement.GetAttribute("version")
|
||||
if (-not $script:formatVersion) { $script:formatVersion = "2.17" }
|
||||
$script:utf8Bom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
$script:addCount = 0
|
||||
$script:removeCount = 0
|
||||
$script:modifyCount = 0
|
||||
@@ -164,6 +169,37 @@ foreach ($child in $script:propsEl.ChildNodes) {
|
||||
Info "Subsystem: $($script:objName)"
|
||||
|
||||
# --- XML manipulation helpers (from meta-edit pattern) ---
|
||||
function Esc-Xml([string]$s) {
|
||||
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
||||
}
|
||||
|
||||
function New-Guid-String {
|
||||
return [System.Guid]::NewGuid().ToString()
|
||||
}
|
||||
|
||||
function Write-ChildSubsystemStub([string]$childPath, [string]$childName, [string]$formatVersion, [System.Text.Encoding]$utf8Bom) {
|
||||
$childUuid = New-Guid-String
|
||||
$sb = New-Object System.Text.StringBuilder 2048
|
||||
[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("`t<Subsystem uuid=`"$childUuid`">")
|
||||
[void]$sb.AppendLine("`t`t<Properties>")
|
||||
[void]$sb.AppendLine("`t`t`t<Name>$(Esc-Xml $childName)</Name>")
|
||||
[void]$sb.AppendLine("`t`t`t<Synonym/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Comment/>")
|
||||
[void]$sb.AppendLine("`t`t`t<IncludeHelpInContents>true</IncludeHelpInContents>")
|
||||
[void]$sb.AppendLine("`t`t`t<IncludeInCommandInterface>true</IncludeInCommandInterface>")
|
||||
[void]$sb.AppendLine("`t`t`t<UseOneCommand>false</UseOneCommand>")
|
||||
[void]$sb.AppendLine("`t`t`t<Explanation/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Picture/>")
|
||||
[void]$sb.AppendLine("`t`t`t<Content/>")
|
||||
[void]$sb.AppendLine("`t`t</Properties>")
|
||||
[void]$sb.AppendLine("`t`t<ChildObjects/>")
|
||||
[void]$sb.AppendLine("`t</Subsystem>")
|
||||
[void]$sb.AppendLine('</MetaDataObject>')
|
||||
[System.IO.File]::WriteAllText($childPath, $sb.ToString(), $utf8Bom)
|
||||
}
|
||||
|
||||
function Import-Fragment([string]$xmlString) {
|
||||
$wrapper = "<_W xmlns=`"$($script:mdNs)`" xmlns:xsi=`"$($script:xsiNs)`" xmlns:v8=`"$($script:v8Ns)`" xmlns:xr=`"$($script:xrNs)`" xmlns:xs=`"http://www.w3.org/2001/XMLSchema`">$xmlString</_W>"
|
||||
$frag = New-Object System.Xml.XmlDocument
|
||||
@@ -337,6 +373,20 @@ function Do-AddChild([string]$childName) {
|
||||
Insert-BeforeElement $script:childObjsEl $newEl $null $childIndent
|
||||
$script:addCount++
|
||||
Info "Added child subsystem: $childName"
|
||||
|
||||
# Write stub XML for the new child if it doesn't exist yet
|
||||
$parentDir = [System.IO.Path]::GetDirectoryName($script:resolvedPath)
|
||||
$parentBaseName = [System.IO.Path]::GetFileNameWithoutExtension($script:resolvedPath)
|
||||
$childSubsDir = Join-Path (Join-Path $parentDir $parentBaseName) "Subsystems"
|
||||
if (-not (Test-Path $childSubsDir)) {
|
||||
New-Item -ItemType Directory -Path $childSubsDir -Force | Out-Null
|
||||
Info "Created directory: $childSubsDir"
|
||||
}
|
||||
$childXml = Join-Path $childSubsDir "$childName.xml"
|
||||
if (-not (Test-Path $childXml)) {
|
||||
Write-ChildSubsystemStub $childXml $childName $script:formatVersion $script:utf8Bom
|
||||
Info "Created stub: $childXml"
|
||||
}
|
||||
}
|
||||
|
||||
function Do-RemoveChild([string]$childName) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# subsystem-edit v1.1 — Edit existing 1C subsystem XML
|
||||
# subsystem-edit v1.2 — Edit existing 1C subsystem XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -7,8 +7,64 @@ import json
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import uuid
|
||||
from lxml import etree
|
||||
|
||||
|
||||
def new_uuid():
|
||||
return str(uuid.uuid4())
|
||||
|
||||
|
||||
def esc_xml(s):
|
||||
return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"')
|
||||
|
||||
|
||||
def write_utf8_bom(path, content):
|
||||
with open(path, 'w', encoding='utf-8-sig', newline='') as f:
|
||||
f.write(content)
|
||||
|
||||
|
||||
def write_child_subsystem_stub(child_path, child_name, format_version):
|
||||
child_uuid = new_uuid()
|
||||
lines = []
|
||||
lines.append('<?xml version="1.0" encoding="UTF-8"?>')
|
||||
lines.append(
|
||||
'<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('\t\t<Properties>')
|
||||
lines.append(f'\t\t\t<Name>{esc_xml(child_name)}</Name>')
|
||||
lines.append('\t\t\t<Synonym/>')
|
||||
lines.append('\t\t\t<Comment/>')
|
||||
lines.append('\t\t\t<IncludeHelpInContents>true</IncludeHelpInContents>')
|
||||
lines.append('\t\t\t<IncludeInCommandInterface>true</IncludeInCommandInterface>')
|
||||
lines.append('\t\t\t<UseOneCommand>false</UseOneCommand>')
|
||||
lines.append('\t\t\t<Explanation/>')
|
||||
lines.append('\t\t\t<Picture/>')
|
||||
lines.append('\t\t\t<Content/>')
|
||||
lines.append('\t\t</Properties>')
|
||||
lines.append('\t\t<ChildObjects/>')
|
||||
lines.append('\t</Subsystem>')
|
||||
lines.append('</MetaDataObject>')
|
||||
write_utf8_bom(child_path, '\n'.join(lines) + '\n')
|
||||
|
||||
MD_NS = "http://v8.1c.ru/8.3/MDClasses"
|
||||
XR_NS = "http://v8.1c.ru/8.3/xcf/readable"
|
||||
XSI_NS = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
@@ -214,7 +270,7 @@ def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
parser = argparse.ArgumentParser(description="Edit existing 1C subsystem XML", allow_abbrev=False)
|
||||
parser.add_argument("-SubsystemPath", required=True)
|
||||
parser.add_argument("-SubsystemPath", "-Path", required=True)
|
||||
parser.add_argument("-DefinitionFile", default=None)
|
||||
parser.add_argument("-Operation", default=None, choices=["add-content", "remove-content", "add-child", "remove-child", "set-property"])
|
||||
parser.add_argument("-Value", default=None)
|
||||
@@ -264,6 +320,7 @@ def main():
|
||||
xml_parser = etree.XMLParser(remove_blank_text=False)
|
||||
tree = etree.parse(resolved_path, xml_parser)
|
||||
xml_root = tree.getroot()
|
||||
format_version = xml_root.get("version") or "2.17"
|
||||
|
||||
add_count = 0
|
||||
remove_count = 0
|
||||
@@ -381,6 +438,18 @@ def main():
|
||||
add_count += 1
|
||||
info(f"Added child subsystem: {child_name}")
|
||||
|
||||
# Write stub XML for the new child if it doesn't exist yet
|
||||
parent_dir = os.path.dirname(resolved_path)
|
||||
parent_base_name = os.path.splitext(os.path.basename(resolved_path))[0]
|
||||
child_subs_dir = os.path.join(parent_dir, parent_base_name, 'Subsystems')
|
||||
if not os.path.exists(child_subs_dir):
|
||||
os.makedirs(child_subs_dir, exist_ok=True)
|
||||
info(f"Created directory: {child_subs_dir}")
|
||||
child_xml = os.path.join(child_subs_dir, f'{child_name}.xml')
|
||||
if not os.path.exists(child_xml):
|
||||
write_child_subsystem_stub(child_xml, child_name, format_version)
|
||||
info(f"Created stub: {child_xml}")
|
||||
|
||||
def do_remove_child(child_name):
|
||||
nonlocal remove_count
|
||||
if child_objs_el is None:
|
||||
@@ -533,7 +602,7 @@ def main():
|
||||
if os.path.isfile(validate_script):
|
||||
print()
|
||||
print("--- Running subsystem-validate ---")
|
||||
subprocess.run([sys.executable, validate_script, "-SubsystemPath", resolved_path])
|
||||
subprocess.run([sys.executable, validate_script, "-SubsystemPath", "-Path", resolved_path])
|
||||
|
||||
# --- Summary ---
|
||||
print()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# subsystem-info v1.0 — Compact summary of 1C subsystem structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SubsystemPath,
|
||||
[Parameter(Mandatory=$true)][Alias('Path')][string]$SubsystemPath,
|
||||
[ValidateSet("overview","content","ci","tree","full")]
|
||||
[string]$Mode = "overview",
|
||||
[string]$Name,
|
||||
|
||||
@@ -14,7 +14,7 @@ sys.stderr.reconfigure(encoding="utf-8")
|
||||
|
||||
# --- Argument parsing ---
|
||||
parser = argparse.ArgumentParser(description="Analyze 1C subsystem structure", allow_abbrev=False)
|
||||
parser.add_argument("-SubsystemPath", required=True, help="Path to subsystem XML or Subsystems/ directory")
|
||||
parser.add_argument("-SubsystemPath", "-Path", required=True, help="Path to subsystem XML or Subsystems/ directory")
|
||||
parser.add_argument("-Mode", choices=["overview", "content", "ci", "tree", "full"], default="overview", help="Output mode")
|
||||
parser.add_argument("-Name", default="", help="Filter by name/type")
|
||||
parser.add_argument("-Limit", type=int, default=150, help="Max lines to show")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# subsystem-validate v1.2 — Validate 1C subsystem XML structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$SubsystemPath,
|
||||
[Parameter(Mandatory)][Alias('Path')][string]$SubsystemPath,
|
||||
[switch]$Detailed,
|
||||
[int]$MaxErrors = 30,
|
||||
[string]$OutFile
|
||||
|
||||
@@ -82,7 +82,7 @@ def main():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Validate 1C subsystem XML structure', allow_abbrev=False
|
||||
)
|
||||
parser.add_argument('-SubsystemPath', dest='SubsystemPath', required=True)
|
||||
parser.add_argument('-SubsystemPath', '-Path', dest='SubsystemPath', required=True)
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: template-add
|
||||
description: Добавить макет к объекту 1С (обработка, отчёт, справочник, документ и др.)
|
||||
description: Добавить пустой макет к объекту 1С. Используй когда нужно создать у объекта новый макет
|
||||
argument-hint: <ObjectName> <TemplateName> <TemplateType>
|
||||
allowed-tools:
|
||||
- Bash
|
||||
@@ -27,8 +27,8 @@ allowed-tools:
|
||||
| TemplateName | да | — | Имя макета |
|
||||
| TemplateType | да | — | Тип: HTML, Text, SpreadsheetDocument, BinaryData, DataCompositionSchema |
|
||||
| Synonym | нет | = TemplateName | Синоним макета |
|
||||
| SrcDir | нет | `src` | Каталог исходников |
|
||||
| --SetMainSKD | нет | — | Принудительно установить MainDataCompositionSchema |
|
||||
| SrcDir | нет | `src` | Путь к папке типа объектов (`Reports`, `DataProcessors`, `Catalogs`, `Documents`...), внутри которой лежит `<ObjectName>.xml`. Дефолт `src` подходит для каталогов с внешними обработками/отчётами, лежащими рядом |
|
||||
| -SetMainSKD | нет | — | Принудительно установить MainDataCompositionSchema |
|
||||
|
||||
## Команда
|
||||
|
||||
@@ -36,6 +36,14 @@ allowed-tools:
|
||||
powershell.exe -NoProfile -File .claude/skills/template-add/scripts/add-template.ps1 -ObjectName "<ObjectName>" -TemplateName "<TemplateName>" -TemplateType "<TemplateType>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-SetMainSKD]
|
||||
```
|
||||
|
||||
## Пример
|
||||
|
||||
Добавить основную СКД к отчёту в расширении:
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File .claude/skills/template-add/scripts/add-template.ps1 -ObjectName "ОтчётПродажи" -TemplateName "ОсновнаяСхемаКомпоновкиДанных" -TemplateType "DataCompositionSchema" -SrcDir "src/cfe/МоёРасширение/Reports"
|
||||
```
|
||||
|
||||
## Маппинг типов
|
||||
|
||||
Пользователь может указать тип в свободной форме. Определи нужный по контексту:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# template-add v1.1 — Add template to 1C object
|
||||
# template-add v1.4 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -35,10 +35,31 @@ $tmpl = $typeMap[$TemplateType]
|
||||
|
||||
# --- Проверки ---
|
||||
|
||||
$objectTypeFolders = @(
|
||||
"Reports", "DataProcessors", "Documents", "Catalogs",
|
||||
"InformationRegisters", "AccumulationRegisters",
|
||||
"ChartsOfCharacteristicTypes", "ChartsOfAccounts", "ChartsOfCalculationTypes",
|
||||
"BusinessProcesses", "Tasks", "ExchangePlans"
|
||||
)
|
||||
|
||||
$rootXmlPath = Join-Path $SrcDir "$ObjectName.xml"
|
||||
if (-not (Test-Path $rootXmlPath)) {
|
||||
Write-Error "Корневой файл обработки не найден: $rootXmlPath"
|
||||
exit 1
|
||||
$candidates = @()
|
||||
foreach ($folder in $objectTypeFolders) {
|
||||
$probe = Join-Path (Join-Path $SrcDir $folder) "$ObjectName.xml"
|
||||
if (Test-Path $probe) { $candidates += (Join-Path $SrcDir $folder) }
|
||||
}
|
||||
if ($candidates.Count -eq 1) {
|
||||
$SrcDir = $candidates[0]
|
||||
$rootXmlPath = Join-Path $SrcDir "$ObjectName.xml"
|
||||
Write-Host "[INFO] SrcDir расширен до: $SrcDir"
|
||||
} elseif ($candidates.Count -gt 1) {
|
||||
Write-Error "Объект '$ObjectName' найден в нескольких подпапках: $($candidates -join ', ')`nУкажи SrcDir явно"
|
||||
exit 1
|
||||
} else {
|
||||
Write-Error "Корневой файл объекта не найден: $rootXmlPath`nОжидается: <SrcDir>/<ObjectName>.xml`nПодсказка: SrcDir должен указывать на папку типа объектов (например Reports), а не на корень конфигурации"
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
$processorDir = Join-Path $SrcDir $ObjectName
|
||||
@@ -59,13 +80,32 @@ New-Item -ItemType Directory -Path $templateExtDir -Force | Out-Null
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$head = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8).Substring(0, [Math]::Min(2000, (Get-Item $cfgPath).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"
|
||||
}
|
||||
|
||||
$formatVersion = Detect-FormatVersion (Resolve-Path $SrcDir).Path
|
||||
|
||||
# --- 1. Метаданные макета (Templates/<TemplateName>.xml) ---
|
||||
|
||||
$templateUuid = [guid]::NewGuid().ToString()
|
||||
|
||||
$templateMetaXml = @"
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version=`"$formatVersion`">
|
||||
<Template uuid="$templateUuid">
|
||||
<Properties>
|
||||
<Name>$TemplateName</Name>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
# add-template v1.0 — Add template to 1C object
|
||||
# add-template v1.4 — Add template to 1C object
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
@@ -37,6 +38,22 @@ def write_text_with_bom(path, text):
|
||||
f.write(text)
|
||||
|
||||
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(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 main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -59,12 +76,37 @@ def main():
|
||||
|
||||
tmpl = TYPE_MAP[template_type]
|
||||
|
||||
format_version = detect_format_version(os.path.abspath(src_dir))
|
||||
|
||||
# --- Checks ---
|
||||
|
||||
object_type_folders = [
|
||||
"Reports", "DataProcessors", "Documents", "Catalogs",
|
||||
"InformationRegisters", "AccumulationRegisters",
|
||||
"ChartsOfCharacteristicTypes", "ChartsOfAccounts", "ChartsOfCalculationTypes",
|
||||
"BusinessProcesses", "Tasks", "ExchangePlans",
|
||||
]
|
||||
|
||||
root_xml_path = os.path.join(src_dir, f"{object_name}.xml")
|
||||
if not os.path.exists(root_xml_path):
|
||||
print(f"Корневой файл обработки не найден: {root_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
candidates = []
|
||||
for folder in object_type_folders:
|
||||
probe = os.path.join(src_dir, folder, f"{object_name}.xml")
|
||||
if os.path.exists(probe):
|
||||
candidates.append(os.path.join(src_dir, folder))
|
||||
if len(candidates) == 1:
|
||||
src_dir = candidates[0]
|
||||
root_xml_path = os.path.join(src_dir, f"{object_name}.xml")
|
||||
print(f"[INFO] SrcDir расширен до: {src_dir}")
|
||||
elif len(candidates) > 1:
|
||||
print(f"Объект '{object_name}' найден в нескольких подпапках: {', '.join(candidates)}", file=sys.stderr)
|
||||
print(f"Укажи SrcDir явно", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
print(f"Корневой файл объекта не найден: {root_xml_path}", file=sys.stderr)
|
||||
print(f"Ожидается: <SrcDir>/<ObjectName>.xml", file=sys.stderr)
|
||||
print(f"Подсказка: SrcDir должен указывать на папку типа объектов (например Reports), а не на корень конфигурации", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
processor_dir = os.path.join(src_dir, object_name)
|
||||
templates_dir = os.path.join(processor_dir, "Templates")
|
||||
@@ -102,7 +144,7 @@ def main():
|
||||
' 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">\n'
|
||||
f' version="{format_version}">\n'
|
||||
f'\t<Template uuid="{template_uuid}">\n'
|
||||
'\t\t<Properties>\n'
|
||||
f'\t\t\t<Name>{template_name}</Name>\n'
|
||||
|
||||
@@ -155,6 +155,8 @@ const form = await getFormState();
|
||||
|
||||
**confirmation** — if present, a Yes/No dialog is shown. Call `clickElement('Да')` or `clickElement('Нет')`.
|
||||
|
||||
**errors.stateText** — array of SpreadsheetDocument state messages (e.g. `"Не установлено значение параметра \"X\""`, `"Отчет не сформирован..."`, `"Изменились настройки..."`). Present when the report area shows an info bar instead of data.
|
||||
|
||||
### Reading data
|
||||
|
||||
#### `readTable({ maxRows?, offset?, table? })` → `{ columns, rows, total, shown, offset }`
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user