mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-07 02:30:53 +03:00
When working with existing configs dumped from newer platforms (8.3.27+), XML files use version="2.20" instead of "2.17". Skills now detect the version from the nearest Configuration.xml walking up the directory tree, falling back to "2.17" if not found. This prevents format version mismatch errors during LoadConfigFromFiles. Updated skills (11): meta-compile, form-compile, form-add, template-add, cfe-borrow, epf-add-form, help-add, role-compile, subsystem-compile, interface-edit. Also fixed form-validate to accept version 2.20. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
749 lines
28 KiB
PowerShell
749 lines
28 KiB
PowerShell
# role-compile v1.4 — Compile 1C role from JSON
|
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
|
param(
|
|
[Parameter(Mandatory)]
|
|
[string]$JsonPath,
|
|
|
|
[Parameter(Mandatory)]
|
|
[string]$OutputDir
|
|
)
|
|
|
|
$ErrorActionPreference = "Stop"
|
|
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
|
|
|
# --- 1. Load and validate JSON ---
|
|
|
|
if (-not (Test-Path $JsonPath)) {
|
|
Write-Error "File not found: $JsonPath"
|
|
exit 1
|
|
}
|
|
|
|
$json = Get-Content -Raw -Encoding UTF8 $JsonPath
|
|
$def = $json | ConvertFrom-Json
|
|
|
|
if (-not $def.name) {
|
|
Write-Error "JSON must have 'name' field (role programmatic name)"
|
|
exit 1
|
|
}
|
|
|
|
$roleName = "$($def.name)"
|
|
$synonym = if ($def.synonym) { "$($def.synonym)" } else { $roleName }
|
|
$comment = if ($def.comment) { "$($def.comment)" } else { "" }
|
|
|
|
# --- 2. XML helpers ---
|
|
|
|
$script:xmlBuf = $null
|
|
|
|
function X {
|
|
param([string]$text)
|
|
$script:xmlBuf.AppendLine($text) | Out-Null
|
|
}
|
|
|
|
function Esc-Xml {
|
|
param([string]$s)
|
|
return $s.Replace('&','&').Replace('<','<').Replace('>','>').Replace('"','"')
|
|
}
|
|
|
|
# --- 3. Russian synonyms → canonical English names ---
|
|
|
|
$script:typeAliases = @{
|
|
"Справочник" = "Catalog"
|
|
"Документ" = "Document"
|
|
"РегистрСведений" = "InformationRegister"
|
|
"РегистрНакопления" = "AccumulationRegister"
|
|
"РегистрБухгалтерии" = "AccountingRegister"
|
|
"РегистрРасчета" = "CalculationRegister"
|
|
"Константа" = "Constant"
|
|
"ПланСчетов" = "ChartOfAccounts"
|
|
"ПланВидовХарактеристик" = "ChartOfCharacteristicTypes"
|
|
"ПланВидовРасчета" = "ChartOfCalculationTypes"
|
|
"ПланОбмена" = "ExchangePlan"
|
|
"БизнесПроцесс" = "BusinessProcess"
|
|
"Задача" = "Task"
|
|
"Обработка" = "DataProcessor"
|
|
"Отчет" = "Report"
|
|
"ОбщаяФорма" = "CommonForm"
|
|
"ОбщаяКоманда" = "CommonCommand"
|
|
"Подсистема" = "Subsystem"
|
|
"КритерийОтбора" = "FilterCriterion"
|
|
"ЖурналДокументов" = "DocumentJournal"
|
|
"Последовательность" = "Sequence"
|
|
"ВебСервис" = "WebService"
|
|
"HTTPСервис" = "HTTPService"
|
|
"СервисИнтеграции" = "IntegrationService"
|
|
"ПараметрСеанса" = "SessionParameter"
|
|
"ОбщийРеквизит" = "CommonAttribute"
|
|
"Конфигурация" = "Configuration"
|
|
"Перечисление" = "Enum"
|
|
# Nested
|
|
"Реквизит" = "Attribute"
|
|
"СтандартныйРеквизит" = "StandardAttribute"
|
|
"ТабличнаяЧасть" = "TabularSection"
|
|
"Измерение" = "Dimension"
|
|
"Ресурс" = "Resource"
|
|
"Команда" = "Command"
|
|
"РеквизитАдресации" = "AddressingAttribute"
|
|
}
|
|
|
|
$script:rightAliases = @{
|
|
"Чтение" = "Read"
|
|
"Добавление" = "Insert"
|
|
"Изменение" = "Update"
|
|
"Удаление" = "Delete"
|
|
"Просмотр" = "View"
|
|
"Редактирование" = "Edit"
|
|
"ВводПоСтроке" = "InputByString"
|
|
"Проведение" = "Posting"
|
|
"ОтменаПроведения" = "UndoPosting"
|
|
"ИнтерактивноеДобавление" = "InteractiveInsert"
|
|
"ИнтерактивнаяПометкаУдаления" = "InteractiveSetDeletionMark"
|
|
"ИнтерактивноеСнятиеПометкиУдаления" = "InteractiveClearDeletionMark"
|
|
"ИнтерактивноеУдаление" = "InteractiveDelete"
|
|
"ИнтерактивноеУдалениеПомеченных" = "InteractiveDeleteMarked"
|
|
"ИнтерактивноеПроведение" = "InteractivePosting"
|
|
"ИнтерактивноеПроведениеНеоперативное" = "InteractivePostingRegular"
|
|
"ИнтерактивнаяОтменаПроведения" = "InteractiveUndoPosting"
|
|
"ИнтерактивноеИзменениеПроведенных" = "InteractiveChangeOfPosted"
|
|
"Использование" = "Use"
|
|
"Получение" = "Get"
|
|
"Установка" = "Set"
|
|
"Старт" = "Start"
|
|
"ИнтерактивныйСтарт" = "InteractiveStart"
|
|
"ИнтерактивнаяАктивация" = "InteractiveActivate"
|
|
"Выполнение" = "Execute"
|
|
"ИнтерактивноеВыполнение" = "InteractiveExecute"
|
|
"УправлениеИтогами" = "TotalsControl"
|
|
"Администрирование" = "Administration"
|
|
"АдминистрированиеДанных" = "DataAdministration"
|
|
"ТонкийКлиент" = "ThinClient"
|
|
"ВебКлиент" = "WebClient"
|
|
"ТолстыйКлиент" = "ThickClient"
|
|
"ВнешнееСоединение" = "ExternalConnection"
|
|
"Вывод" = "Output"
|
|
"СохранениеДанныхПользователя" = "SaveUserData"
|
|
"МобильныйКлиент" = "MobileClient"
|
|
}
|
|
|
|
# Translate Russian object name to English (e.g. "Справочник.Контрагенты" → "Catalog.Контрагенты")
|
|
function Translate-ObjectName {
|
|
param([string]$name)
|
|
$parts = $name.Split(".")
|
|
$result = @()
|
|
foreach ($p in $parts) {
|
|
if ($script:typeAliases.ContainsKey($p)) {
|
|
$result += $script:typeAliases[$p]
|
|
} else {
|
|
$result += $p
|
|
}
|
|
}
|
|
return $result -join "."
|
|
}
|
|
|
|
# Translate Russian right name to English (e.g. "Чтение" → "Read")
|
|
function Translate-RightName {
|
|
param([string]$name)
|
|
if ($script:rightAliases.ContainsKey($name)) {
|
|
return $script:rightAliases[$name]
|
|
}
|
|
return $name
|
|
}
|
|
|
|
# --- 4. Known rights per object type (source: docs/1c-role-spec.md) ---
|
|
|
|
$script:knownRights = @{
|
|
"Configuration" = @(
|
|
"Administration","DataAdministration","UpdateDataBaseConfiguration",
|
|
"ConfigurationExtensionsAdministration","ActiveUsers","EventLog","ExclusiveMode",
|
|
"ThinClient","ThickClient","WebClient","MobileClient","ExternalConnection",
|
|
"Automation","Output","SaveUserData","TechnicalSpecialistMode",
|
|
"InteractiveOpenExtDataProcessors","InteractiveOpenExtReports",
|
|
"AnalyticsSystemClient","CollaborationSystemInfoBaseRegistration",
|
|
"MainWindowModeNormal","MainWindowModeWorkplace",
|
|
"MainWindowModeEmbeddedWorkplace","MainWindowModeFullscreenWorkplace","MainWindowModeKiosk"
|
|
)
|
|
"Catalog" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveDeleteMarked",
|
|
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
|
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"Document" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"Posting","UndoPosting",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveDeleteMarked",
|
|
"InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting",
|
|
"InteractiveChangeOfPosted",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"InformationRegister" = @(
|
|
"Read","Update","View","Edit","TotalsControl",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"UpdateDataHistoryOfMissingData","ReadDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"AccumulationRegister" = @("Read","Update","View","Edit","TotalsControl")
|
|
"AccountingRegister" = @("Read","Update","View","Edit","TotalsControl")
|
|
"CalculationRegister" = @("Read","View")
|
|
"Constant" = @(
|
|
"Read","Update","View","Edit",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"ChartOfAccounts" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete",
|
|
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
|
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
|
"ReadDataHistory","ReadDataHistoryOfMissingData",
|
|
"UpdateDataHistory","UpdateDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment"
|
|
)
|
|
"ChartOfCharacteristicTypes" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveDeleteMarked",
|
|
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
|
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"ChartOfCalculationTypes" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete",
|
|
"InteractiveDeletePredefinedData","InteractiveSetDeletionMarkPredefinedData",
|
|
"InteractiveClearDeletionMarkPredefinedData","InteractiveDeleteMarkedPredefinedData"
|
|
)
|
|
"ExchangePlan" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveDeleteMarked",
|
|
"ReadDataHistory","ViewDataHistory","UpdateDataHistory",
|
|
"ReadDataHistoryOfMissingData","UpdateDataHistoryOfMissingData",
|
|
"UpdateDataHistorySettings","UpdateDataHistoryVersionComment",
|
|
"EditDataHistoryVersionComment","SwitchToDataHistoryVersion"
|
|
)
|
|
"BusinessProcess" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveActivate","InteractiveStart"
|
|
)
|
|
"Task" = @(
|
|
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
|
"Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark",
|
|
"InteractiveDelete","InteractiveActivate","InteractiveExecute"
|
|
)
|
|
"DataProcessor" = @("Use","View")
|
|
"Report" = @("Use","View")
|
|
"CommonForm" = @("View")
|
|
"CommonCommand" = @("View")
|
|
"Subsystem" = @("View")
|
|
"FilterCriterion" = @("View")
|
|
"DocumentJournal" = @("Read","View")
|
|
"Sequence" = @("Read","Update")
|
|
"WebService" = @("Use")
|
|
"HTTPService" = @("Use")
|
|
"IntegrationService" = @("Use")
|
|
"SessionParameter" = @("Get","Set")
|
|
"CommonAttribute" = @("View","Edit")
|
|
}
|
|
|
|
# Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute
|
|
$script:nestedRights = @("View","Edit")
|
|
$script:commandRights = @("View")
|
|
|
|
# --- 4. Presets (@view, @edit) ---
|
|
|
|
$script:presets = @{
|
|
"view" = @{
|
|
"Catalog" = @("Read","View","InputByString")
|
|
"ExchangePlan" = @("Read","View","InputByString")
|
|
"Document" = @("Read","View","InputByString")
|
|
"ChartOfAccounts" = @("Read","View","InputByString")
|
|
"ChartOfCharacteristicTypes" = @("Read","View","InputByString")
|
|
"ChartOfCalculationTypes" = @("Read","View","InputByString")
|
|
"BusinessProcess" = @("Read","View","InputByString")
|
|
"Task" = @("Read","View","InputByString")
|
|
"InformationRegister" = @("Read","View")
|
|
"AccumulationRegister" = @("Read","View")
|
|
"AccountingRegister" = @("Read","View")
|
|
"CalculationRegister" = @("Read","View")
|
|
"Constant" = @("Read","View")
|
|
"DocumentJournal" = @("Read","View")
|
|
"Sequence" = @("Read")
|
|
"CommonForm" = @("View")
|
|
"CommonCommand" = @("View")
|
|
"Subsystem" = @("View")
|
|
"FilterCriterion" = @("View")
|
|
"SessionParameter" = @("Get")
|
|
"CommonAttribute" = @("View")
|
|
"DataProcessor" = @("Use","View")
|
|
"Report" = @("Use","View")
|
|
"Configuration" = @("ThinClient","WebClient","Output","SaveUserData","MainWindowModeNormal")
|
|
}
|
|
"edit" = @{
|
|
"Catalog" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
|
"ExchangePlan" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
|
"Document" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Posting","UndoPosting","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractivePosting","InteractivePostingRegular","InteractiveUndoPosting","InteractiveChangeOfPosted")
|
|
"ChartOfAccounts" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
|
"ChartOfCharacteristicTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
|
"ChartOfCalculationTypes" = @("Read","Insert","Update","Delete","View","Edit","InputByString","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark")
|
|
"BusinessProcess" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Start","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveStart")
|
|
"Task" = @("Read","Insert","Update","Delete","View","Edit","InputByString","Execute","InteractiveInsert","InteractiveSetDeletionMark","InteractiveClearDeletionMark","InteractiveActivate","InteractiveExecute")
|
|
"InformationRegister" = @("Read","Update","View","Edit")
|
|
"AccumulationRegister" = @("Read","Update","View","Edit")
|
|
"AccountingRegister" = @("Read","Update","View","Edit")
|
|
"Constant" = @("Read","Update","View","Edit")
|
|
"DocumentJournal" = @("Read","View")
|
|
"Sequence" = @("Read","Update")
|
|
"SessionParameter" = @("Get","Set")
|
|
"CommonAttribute" = @("View","Edit")
|
|
}
|
|
}
|
|
|
|
# --- 5. Helpers ---
|
|
|
|
function Get-ObjectType {
|
|
param([string]$objectName)
|
|
$dotIdx = $objectName.IndexOf(".")
|
|
if ($dotIdx -lt 0) { return $objectName }
|
|
return $objectName.Substring(0, $dotIdx)
|
|
}
|
|
|
|
function Is-NestedObject {
|
|
param([string]$objectName)
|
|
return ($objectName.Split(".").Count -ge 3)
|
|
}
|
|
|
|
function Resolve-Preset {
|
|
param([string]$objectType, [string]$presetName)
|
|
|
|
$preset = $presetName.TrimStart('@')
|
|
|
|
if (-not $script:presets.ContainsKey($preset)) {
|
|
Write-Warning "Unknown preset '@$preset'. Known: @view, @edit"
|
|
return @()
|
|
}
|
|
|
|
$typeMap = $script:presets[$preset]
|
|
if (-not $typeMap.ContainsKey($objectType)) {
|
|
$available = @()
|
|
foreach ($k in $script:presets.Keys) {
|
|
if ($script:presets[$k].ContainsKey($objectType)) {
|
|
$available += "@$k"
|
|
}
|
|
}
|
|
$availStr = if ($available.Count -gt 0) { $available -join ", " } else { "none" }
|
|
Write-Warning "Preset '@$preset' not defined for type '$objectType'. Available: $availStr"
|
|
return @()
|
|
}
|
|
|
|
return @($typeMap[$objectType])
|
|
}
|
|
|
|
function Validate-RightName {
|
|
param([string]$objectName, [string]$rightName)
|
|
|
|
$objectType = Get-ObjectType $objectName
|
|
|
|
if (Is-NestedObject $objectName) {
|
|
if ($objectName -match '\.Command\.') {
|
|
if ($rightName -notin $script:commandRights) {
|
|
Write-Warning "${objectName}: '$rightName' not valid for commands (only: View)"
|
|
return $false
|
|
}
|
|
} else {
|
|
if ($rightName -notin $script:nestedRights) {
|
|
Write-Warning "${objectName}: '$rightName' not valid for nested objects (only: View, Edit)"
|
|
return $false
|
|
}
|
|
}
|
|
return $true
|
|
}
|
|
|
|
if (-not $script:knownRights.ContainsKey($objectType)) {
|
|
Write-Warning "${objectName}: unknown object type '$objectType'"
|
|
return $true
|
|
}
|
|
|
|
$validRights = $script:knownRights[$objectType]
|
|
if ($rightName -notin $validRights) {
|
|
$suggestions = @($validRights | Where-Object {
|
|
$_ -like "*$rightName*" -or $rightName -like "*$_*"
|
|
})
|
|
$sugStr = if ($suggestions.Count -gt 0) { " Did you mean: $($suggestions -join ', ')?" } else { "" }
|
|
Write-Warning "${objectName}: unknown right '$rightName'.$sugStr"
|
|
return $false
|
|
}
|
|
|
|
return $true
|
|
}
|
|
|
|
# --- 6. Parse object entries ---
|
|
|
|
function Parse-ObjectEntry {
|
|
param($entry)
|
|
|
|
# --- String shorthand ---
|
|
if ($entry -is [string]) {
|
|
$colonIdx = $entry.IndexOf(':')
|
|
if ($colonIdx -lt 0) {
|
|
Write-Warning "Invalid string '$entry' -- expected 'Object.Name: @preset' or 'Object.Name: Right1, Right2'"
|
|
return $null
|
|
}
|
|
$objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim())
|
|
$rightsStr = $entry.Substring($colonIdx + 1).Trim()
|
|
$objectType = Get-ObjectType $objName
|
|
|
|
if ($rightsStr.StartsWith('@')) {
|
|
$rightNames = @(Resolve-Preset -objectType $objectType -presetName $rightsStr)
|
|
} else {
|
|
$rightNames = @($rightsStr -split ',\s*' | ForEach-Object { Translate-RightName $_.Trim() } | Where-Object { $_ })
|
|
foreach ($r in $rightNames) {
|
|
Validate-RightName -objectName $objName -rightName $r | Out-Null
|
|
}
|
|
}
|
|
|
|
$rights = @()
|
|
foreach ($r in $rightNames) {
|
|
$rights += ,@{Name=$r; Value="true"; Condition=$null}
|
|
}
|
|
return @{ Name = $objName; Rights = $rights }
|
|
}
|
|
|
|
# --- Object form ---
|
|
$objName = Translate-ObjectName "$($entry.name)"
|
|
if (-not $objName) {
|
|
Write-Warning "Object entry missing 'name' field"
|
|
return $null
|
|
}
|
|
|
|
$objectType = Get-ObjectType $objName
|
|
$rightsMap = [ordered]@{}
|
|
|
|
# 1) Start with preset
|
|
if ($entry.preset) {
|
|
$presetRights = @(Resolve-Preset -objectType $objectType -presetName "$($entry.preset)")
|
|
foreach ($r in $presetRights) {
|
|
$rightsMap[$r] = @{Value="true"; Condition=$null}
|
|
}
|
|
}
|
|
|
|
# 2) Apply explicit rights
|
|
if ($entry.rights) {
|
|
if ($entry.rights -is [array]) {
|
|
foreach ($r in $entry.rights) {
|
|
$rName = Translate-RightName "$r"
|
|
Validate-RightName -objectName $objName -rightName $rName | Out-Null
|
|
$rightsMap[$rName] = @{Value="true"; Condition=$null}
|
|
}
|
|
} else {
|
|
foreach ($p in $entry.rights.PSObject.Properties) {
|
|
$rName = Translate-RightName $p.Name
|
|
Validate-RightName -objectName $objName -rightName $rName | Out-Null
|
|
$boolVal = $p.Value
|
|
if ($boolVal -eq $true -or "$boolVal" -eq "True") {
|
|
$rightsMap[$rName] = @{Value="true"; Condition=$null}
|
|
} else {
|
|
$rightsMap[$rName] = @{Value="false"; Condition=$null}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
# 3) Apply RLS conditions
|
|
if ($entry.rls) {
|
|
foreach ($p in $entry.rls.PSObject.Properties) {
|
|
$rlsRight = Translate-RightName $p.Name
|
|
if ($rightsMap.Contains($rlsRight)) {
|
|
$rightsMap[$rlsRight].Condition = "$($p.Value)"
|
|
} else {
|
|
Write-Warning "${objName}: RLS for '$rlsRight' but this right is not in the rights list"
|
|
}
|
|
}
|
|
}
|
|
|
|
# Convert to array
|
|
$rights = @()
|
|
foreach ($k in $rightsMap.Keys) {
|
|
$rights += ,@{
|
|
Name = $k
|
|
Value = $rightsMap[$k].Value
|
|
Condition = $rightsMap[$k].Condition
|
|
}
|
|
}
|
|
|
|
return @{ Name = $objName; Rights = $rights }
|
|
}
|
|
|
|
# --- 7. Parse all object entries ---
|
|
|
|
# Synonym: accept "rights" as alias for "objects"
|
|
if (-not $def.objects -and $def.rights) { $def | Add-Member -NotePropertyName objects -NotePropertyValue $def.rights }
|
|
|
|
$parsedObjects = @()
|
|
if ($def.objects) {
|
|
foreach ($entry in $def.objects) {
|
|
$parsed = Parse-ObjectEntry -entry $entry
|
|
if ($parsed) {
|
|
$parsedObjects += ,$parsed
|
|
}
|
|
}
|
|
}
|
|
|
|
# --- 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()
|
|
|
|
# --- 9. Emit metadata XML (Roles/Name.xml) ---
|
|
|
|
$script:xmlBuf = New-Object System.Text.StringBuilder 4096
|
|
|
|
X '<?xml version="1.0" encoding="UTF-8"?>'
|
|
X '<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses"'
|
|
X ' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
|
|
X ' xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"'
|
|
X ' xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi"'
|
|
X ' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
|
|
X ' xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform"'
|
|
X ' xmlns:style="http://v8.1c.ru/8.1/data/ui/style"'
|
|
X ' xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"'
|
|
X ' xmlns:v8="http://v8.1c.ru/8.1/data/core"'
|
|
X ' xmlns:v8ui="http://v8.1c.ru/8.1/data/ui"'
|
|
X ' xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web"'
|
|
X ' xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows"'
|
|
X ' xmlns:xen="http://v8.1c.ru/8.3/xcf/enums"'
|
|
X ' xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef"'
|
|
X ' xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"'
|
|
X ' xmlns:xs="http://www.w3.org/2001/XMLSchema"'
|
|
X ' xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
|
X ' version="$formatVersion">'
|
|
X " <Role uuid=`"$uuid`">"
|
|
X ' <Properties>'
|
|
X " <Name>$roleName</Name>"
|
|
X ' <Synonym>'
|
|
X ' <v8:item>'
|
|
X ' <v8:lang>ru</v8:lang>'
|
|
X " <v8:content>$(Esc-Xml $synonym)</v8:content>"
|
|
X ' </v8:item>'
|
|
X ' </Synonym>'
|
|
if ($comment) {
|
|
X " <Comment>$(Esc-Xml $comment)</Comment>"
|
|
} else {
|
|
X ' <Comment/>'
|
|
}
|
|
X ' </Properties>'
|
|
X ' </Role>'
|
|
X '</MetaDataObject>'
|
|
|
|
$metadataXml = $script:xmlBuf.ToString()
|
|
|
|
# --- 10. Emit Rights XML (Roles/Name/Ext/Rights.xml) ---
|
|
|
|
$script:xmlBuf = New-Object System.Text.StringBuilder 8192
|
|
|
|
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=`"$formatVersion`">"
|
|
|
|
# Global flags (defaults match typical 1C roles)
|
|
$sfno = if ($null -ne $def.setForNewObjects) { "$($def.setForNewObjects)".ToLower() } else { "false" }
|
|
$sfab = if ($null -ne $def.setForAttributesByDefault) { "$($def.setForAttributesByDefault)".ToLower() } else { "true" }
|
|
$irco = if ($null -ne $def.independentRightsOfChildObjects) { "$($def.independentRightsOfChildObjects)".ToLower() } else { "false" }
|
|
|
|
X " <setForNewObjects>$sfno</setForNewObjects>"
|
|
X " <setForAttributesByDefault>$sfab</setForAttributesByDefault>"
|
|
X " <independentRightsOfChildObjects>$irco</independentRightsOfChildObjects>"
|
|
|
|
# Object blocks
|
|
$totalRights = 0
|
|
foreach ($obj in $parsedObjects) {
|
|
X ' <object>'
|
|
X " <name>$($obj.Name)</name>"
|
|
foreach ($right in $obj.Rights) {
|
|
X ' <right>'
|
|
X " <name>$($right.Name)</name>"
|
|
X " <value>$($right.Value)</value>"
|
|
if ($right.Condition) {
|
|
X ' <restrictionByCondition>'
|
|
X " <condition>$(Esc-Xml $right.Condition)</condition>"
|
|
X ' </restrictionByCondition>'
|
|
}
|
|
X ' </right>'
|
|
$totalRights++
|
|
}
|
|
X ' </object>'
|
|
}
|
|
|
|
# RLS restriction templates
|
|
$templateCount = 0
|
|
if ($def.templates) {
|
|
foreach ($tpl in $def.templates) {
|
|
X ' <restrictionTemplate>'
|
|
X " <name>$(Esc-Xml "$($tpl.name)")</name>"
|
|
X " <condition>$(Esc-Xml "$($tpl.condition)")</condition>"
|
|
X ' </restrictionTemplate>'
|
|
$templateCount++
|
|
}
|
|
}
|
|
|
|
X '</Rights>'
|
|
|
|
$rightsXml = $script:xmlBuf.ToString()
|
|
|
|
# --- 11. Write output files ---
|
|
|
|
$outDir = if ([System.IO.Path]::IsPathRooted($OutputDir)) {
|
|
$OutputDir
|
|
} else {
|
|
Join-Path (Get-Location) $OutputDir
|
|
}
|
|
|
|
# Determine Roles dir and config root
|
|
# Back-compat: if OutputDir leaf is "Roles", use as-is; otherwise treat as config root
|
|
$leaf = Split-Path $outDir -Leaf
|
|
if ($leaf -eq "Roles") {
|
|
$rolesDir = $outDir
|
|
$configDir = Split-Path $outDir -Parent
|
|
} else {
|
|
$rolesDir = Join-Path $outDir "Roles"
|
|
$configDir = $outDir
|
|
}
|
|
|
|
# Metadata: Roles/RoleName.xml
|
|
$metadataPath = Join-Path $rolesDir "$roleName.xml"
|
|
if (-not (Test-Path $rolesDir)) {
|
|
New-Item -ItemType Directory -Path $rolesDir -Force | Out-Null
|
|
}
|
|
|
|
# Rights: Roles/RoleName/Ext/Rights.xml
|
|
$roleSubDir = Join-Path $rolesDir $roleName
|
|
$extDir = Join-Path $roleSubDir "Ext"
|
|
$rightsPath = Join-Path $extDir "Rights.xml"
|
|
if (-not (Test-Path $extDir)) {
|
|
New-Item -ItemType Directory -Path $extDir -Force | Out-Null
|
|
}
|
|
|
|
$enc = New-Object System.Text.UTF8Encoding($true)
|
|
[System.IO.File]::WriteAllText($metadataPath, $metadataXml, $enc)
|
|
[System.IO.File]::WriteAllText($rightsPath, $rightsXml, $enc)
|
|
|
|
# --- 12. Register in Configuration.xml ---
|
|
|
|
$configXmlPath = Join-Path $configDir "Configuration.xml"
|
|
$regResult = $null
|
|
|
|
if (Test-Path $configXmlPath) {
|
|
$configDoc = New-Object System.Xml.XmlDocument
|
|
$configDoc.PreserveWhitespace = $true
|
|
$configDoc.Load($configXmlPath)
|
|
|
|
$nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable)
|
|
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
|
|
|
$childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr)
|
|
if ($childObjects) {
|
|
$existing = $childObjects.SelectNodes("md:Role", $nsMgr)
|
|
$alreadyExists = $false
|
|
foreach ($r in $existing) {
|
|
if ($r.InnerText -eq $roleName) {
|
|
$alreadyExists = $true
|
|
break
|
|
}
|
|
}
|
|
|
|
if ($alreadyExists) {
|
|
$regResult = "already"
|
|
} else {
|
|
$roleElem = $configDoc.CreateElement("Role", "http://v8.1c.ru/8.3/MDClasses")
|
|
$roleElem.InnerText = $roleName
|
|
|
|
if ($existing.Count -gt 0) {
|
|
# Insert after last existing <Role>
|
|
$lastRole = $existing[$existing.Count - 1]
|
|
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
$childObjects.InsertAfter($newWs, $lastRole) | Out-Null
|
|
$childObjects.InsertAfter($roleElem, $newWs) | Out-Null
|
|
} else {
|
|
# No existing roles — insert before closing whitespace
|
|
$lastChild = $childObjects.LastChild
|
|
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
|
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
|
$childObjects.InsertBefore($roleElem, $lastChild) | Out-Null
|
|
} else {
|
|
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
|
$childObjects.AppendChild($roleElem) | Out-Null
|
|
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
|
}
|
|
}
|
|
|
|
# Save
|
|
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
|
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
$cfgSettings.Indent = $false
|
|
$stream = New-Object System.IO.FileStream($configXmlPath, [System.IO.FileMode]::Create)
|
|
$writer = [System.Xml.XmlWriter]::Create($stream, $cfgSettings)
|
|
$configDoc.Save($writer)
|
|
$writer.Close()
|
|
$stream.Close()
|
|
|
|
$regResult = "added"
|
|
}
|
|
} else {
|
|
$regResult = "no-childobj"
|
|
}
|
|
} else {
|
|
$regResult = "no-config"
|
|
}
|
|
|
|
# --- 13. Summary ---
|
|
|
|
Write-Host "[OK] Role '$roleName' compiled"
|
|
Write-Host " UUID: $uuid"
|
|
Write-Host " Metadata: $metadataPath"
|
|
Write-Host " Rights: $rightsPath"
|
|
Write-Host " Objects: $($parsedObjects.Count), Rights: $totalRights, Templates: $templateCount"
|
|
switch ($regResult) {
|
|
"added" { Write-Host " Configuration.xml: <Role>$roleName</Role> added to ChildObjects" }
|
|
"already" { Write-Host " Configuration.xml: <Role>$roleName</Role> already registered" }
|
|
"no-childobj" { Write-Warning "Configuration.xml found but <ChildObjects> not found" }
|
|
"no-config" { Write-Warning "Configuration.xml not found at $configXmlPath — register manually" }
|
|
}
|