mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-17 16:50:30 +03:00
Compare commits
120
Commits
@@ -22,6 +22,21 @@ allowed-tools:
|
||||
| `Version` | Версия конфигурации |
|
||||
| `Vendor` | Поставщик |
|
||||
| `CompatibilityMode` | Режим совместимости (default: `Version8_3_24`) |
|
||||
| `FormatVersion` | Версия формата выгрузки (default: `2.17`) |
|
||||
|
||||
Оба параметра задаются от **целевой платформы** — той, на которой конфигурация будет работать, — но по
|
||||
разным правилам.
|
||||
|
||||
`FormatVersion` — **не выше** версии формата целевой платформы: 8.3.24 — `2.17`, 8.3.25 — `2.18`,
|
||||
8.3.26 — `2.19`, 8.3.27 — `2.20`, 8.5 — `2.21`. Ниже брать можно: платформа читает свой формат и любой
|
||||
более старый, поэтому дефолт `2.17` подходит для всей линейки 8.3.24 и выше.
|
||||
|
||||
`CompatibilityMode` влияет на доступную функциональность. Если в задаче не оговорено иное — **равен
|
||||
целевой платформе** (`Version8_3_27` для 8.3.27); это то же самое, что «Не использовать» в
|
||||
Конфигураторе, и именно такое значение платформа проставляет новой базе. Более низкий режим берут
|
||||
осознанно — когда конфигурация должна работать и на старых платформах; он отключает возможности,
|
||||
появившиеся позже. Выше целевой платформы — нельзя: такая конфигурация загрузится, но работать на ней
|
||||
не будет.
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name "МояКонфигурация"
|
||||
@@ -36,8 +51,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cf-init.ps1" -Name
|
||||
# С версией и поставщиком
|
||||
... -Name TestCfg -Synonym "Тестовая" -Version "1.0.0.1" -Vendor "Фирма 1С" -OutputDir test-tmp/cf2
|
||||
|
||||
# Другой режим совместимости
|
||||
... -Name TestCfg -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
|
||||
# Под платформу 8.3.27 — версия формата и режим совместимости вместе
|
||||
... -Name TestCfg -FormatVersion 2.20 -CompatibilityMode Version8_3_27 -OutputDir test-tmp/cf3
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cf-init v1.11 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# cf-init v1.14 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -9,14 +9,26 @@ param(
|
||||
[string]$Vendor,
|
||||
[string]$CompatibilityMode = "Version8_3_24",
|
||||
# Версия формата выгрузки (MDClasses). Её задаёт ПЛАТФОРМА, которой выгружают, и от режима
|
||||
# совместимости она не зависит: 8.3.20-8.3.24 пишут 2.17, 8.3.25 — 2.18, 8.3.26 — 2.19,
|
||||
# 8.3.27 — 2.20. Дефолт консервативный: 2.17 читается всеми поддерживаемыми платформами.
|
||||
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
|
||||
# совместимости она не зависит. Дефолт 2.17 — нижняя граница проверенного диапазона.
|
||||
[string]$FormatVersion = "2.17"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница — docs/1c-configuration-spec.md,
|
||||
# «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и старше) реальны, поэтому запретом их не
|
||||
# закрываем: за пределами диапазона — ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только
|
||||
# на нечисловое значение: это опечатка, а не версия.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
$formatRank = Get-FormatRank $FormatVersion
|
||||
|
||||
function Esc-XmlText {
|
||||
param([string]$s)
|
||||
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||
@@ -24,6 +36,27 @@ function Esc-XmlText {
|
||||
}
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# Проверка версии — ПОСЛЕ настройки кодировки консоли: иначе em-dash в сообщении уедет в вопросы.
|
||||
# Пишем прямо в stderr, а не Write-Warning: в PS 5.1 предупреждение уходит в stdout, получает
|
||||
# локализованный префикс и переносится по 80 символов — подстрока в тесте перестаёт находиться.
|
||||
if ($formatRank -eq 0) {
|
||||
[Console]::Error.WriteLine("Malformed -FormatVersion '$FormatVersion' (expected N.N, e.g. 2.17)")
|
||||
exit 1
|
||||
}
|
||||
if ($formatRank -lt (Get-FormatRank $formatVerifiedMin) -or $formatRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
[Console]::Error.WriteLine("WARNING: Format version '$FormatVersion' is outside the tested range $formatVerifiedMin-$formatVerifiedMax — the scaffold is emitted as requested but was not verified on that platform")
|
||||
}
|
||||
|
||||
# «Не использовать» в Конфигураторе хранится как версия ТЕКУЩЕЙ платформы, а не как DontUse:
|
||||
# свежая база получает Version8_3_<своя>, и ни одна типовая в корпусе DontUse не содержит.
|
||||
# Само значение легально — платформа принимает его без ошибок, — но не выживает: замерено на
|
||||
# 8.3.25 и 8.3.27, выгрузка обоих возвращает Version8_3_8. Поэтому предупреждение, а не запрет.
|
||||
# Сравнение регистронезависимо ЯВНО: в PS -eq таков по умолчанию, в py — нет, и молчаливое
|
||||
# расхождение портов началось бы прямо здесь.
|
||||
if ($CompatibilityMode -and $CompatibilityMode.ToLowerInvariant() -eq 'dontuse') {
|
||||
[Console]::Error.WriteLine("WARNING: CompatibilityMode 'DontUse' is not `"no restrictions`" — the platform stores it as Version8_3_8. For no compatibility restrictions use the target platform version (e.g. Version8_3_27 for 8.3.27).")
|
||||
}
|
||||
|
||||
# --- Resolve output dir ---
|
||||
if (-not [System.IO.Path]::IsPathRooted($OutputDir)) {
|
||||
$OutputDir = Join-Path (Get-Location).Path $OutputDir
|
||||
@@ -50,7 +83,9 @@ $co7 = [guid]::NewGuid().ToString()
|
||||
|
||||
# --- Mobile functionalities ---
|
||||
# Версия формата как число — по ней ниже включаются вставки 2.21.
|
||||
$is221 = (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221)
|
||||
$is221 = ($formatRank -ge 221)
|
||||
# TextToSpeech приехал раньше остальных вставок 8.5 — своей ступенью, поэтому гейт отдельный.
|
||||
$is218 = ($formatRank -ge 218)
|
||||
|
||||
$mobileFuncs = @(
|
||||
@("Biometrics","true"), @("Location","false"), @("BackgroundLocation","false"),
|
||||
@@ -68,9 +103,12 @@ $mobileFuncs = @(
|
||||
@("DocumentScanning","false"), @("SpeechToText","false"), @("Geofences","false"),
|
||||
@("IncomingShareRequests","false"), @("AllIncomingShareRequestsTypesProcessing","false")
|
||||
)
|
||||
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
|
||||
# последней в списке. На младших форматах платформа её не пишет.
|
||||
if ($is221) { $mobileFuncs += ,@("TextToSpeech","false") }
|
||||
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.18 (8.3.25),
|
||||
# последней в списке; в 2.21 список не менялся. Замерено выгрузками пустой ИБ шести платформ:
|
||||
# 2.13/2.17 — 37 записей без неё, 2.18-2.21 — 38 с ней. Гейт обязателен и в обе стороны:
|
||||
# на 2.17 тег ломает загрузку XDTO-ошибкой (проверено на 8.3.24), без тега на 2.18+ платформа
|
||||
# подставит дефолт false и допишет его при выгрузке — то есть разойдётся роундтрип.
|
||||
if ($is218) { $mobileFuncs += ,@("TextToSpeech","false") }
|
||||
|
||||
$mobileXml = ""
|
||||
foreach ($mf in $mobileFuncs) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-init v1.11 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# cf-init v1.14 — Create empty 1C configuration scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C configuration."""
|
||||
import sys, os, argparse, re, uuid
|
||||
@@ -50,6 +50,16 @@ def write_xml_file(path, content):
|
||||
write_utf8_bom(path, text)
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -60,13 +70,33 @@ def main():
|
||||
parser.add_argument('-Version', dest='Version', default='')
|
||||
parser.add_argument('-Vendor', dest='Vendor', default='')
|
||||
parser.add_argument('-CompatibilityMode', dest='CompatibilityMode', default='Version8_3_24')
|
||||
# Версия формата выгрузки (MDClasses) — её задаёт ПЛАТФОРМА, а не режим совместимости:
|
||||
# 8.3.20-8.3.24 пишут 2.17, 8.3.25 — 2.18, 8.3.26 — 2.19, 8.3.27 — 2.20.
|
||||
# Дефолт консервативный: 2.17 читается всеми платформами.
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||
# Версия формата выгрузки (MDClasses) — её задаёт ПЛАТФОРМА, а не режим совместимости.
|
||||
# Дефолт 2.17 — нижняя граница проверенного диапазона.
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17')
|
||||
args = ci_parse_args(parser)
|
||||
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и
|
||||
# старше) реальны, поэтому запретом их не закрываем: за пределами диапазона —
|
||||
# ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только на нечисловое значение.
|
||||
format_rank_value = format_rank(args.FormatVersion)
|
||||
if format_rank_value == 0:
|
||||
print(f"Malformed -FormatVersion '{args.FormatVersion}' (expected N.N, e.g. 2.17)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not (format_rank(FORMAT_VERIFIED_MIN) <= format_rank_value <= format_rank(FORMAT_VERIFIED_MAX)):
|
||||
print(f"WARNING: Format version '{args.FormatVersion}' is outside the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — the scaffold is emitted as requested "
|
||||
f"but was not verified on that platform", file=sys.stderr)
|
||||
|
||||
# «Не использовать» в Конфигураторе хранится как версия ТЕКУЩЕЙ платформы, а не как DontUse:
|
||||
# свежая база получает Version8_3_<своя>, и ни одна типовая в корпусе DontUse не содержит.
|
||||
# Само значение легально — платформа принимает его без ошибок, — но не выживает: замерено на
|
||||
# 8.3.25 и 8.3.27, выгрузка обоих возвращает Version8_3_8. Поэтому предупреждение, а не запрет.
|
||||
# Сравнение регистронезависимо ЯВНО: в PS -eq таков по умолчанию, в py — нет, и молчаливое
|
||||
# расхождение портов началось бы прямо здесь.
|
||||
if (args.CompatibilityMode or "").lower() == "dontuse":
|
||||
print("WARNING: CompatibilityMode 'DontUse' is not \"no restrictions\" — the platform stores it as Version8_3_8. For no compatibility restrictions use the target platform version (e.g. Version8_3_27 for 8.3.27).", file=sys.stderr)
|
||||
|
||||
name = args.Name
|
||||
synonym = args.Synonym if args.Synonym else name
|
||||
output_dir = args.OutputDir
|
||||
@@ -91,8 +121,9 @@ def main():
|
||||
|
||||
# --- Mobile functionalities ---
|
||||
# Версия формата как число — по ней ниже включаются вставки 2.21.
|
||||
_fm = re.match(r'^(\d+)\.(\d+)$', args.FormatVersion)
|
||||
is_221 = bool(_fm) and int(_fm.group(1)) * 100 + int(_fm.group(2)) >= 221
|
||||
is_221 = format_rank_value >= 221
|
||||
# TextToSpeech приехал раньше остальных вставок 8.5 — своей ступенью, поэтому гейт отдельный.
|
||||
is_218 = format_rank_value >= 218
|
||||
|
||||
mobile_funcs = [
|
||||
("Biometrics","true"), ("Location","false"), ("BackgroundLocation","false"),
|
||||
@@ -110,9 +141,12 @@ def main():
|
||||
("DocumentScanning","false"), ("SpeechToText","false"), ("Geofences","false"),
|
||||
("IncomingShareRequests","false"), ("AllIncomingShareRequestsTypesProcessing","false"),
|
||||
]
|
||||
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.21 (8.5),
|
||||
# последней в списке. На младших форматах платформа её не пишет.
|
||||
if is_221:
|
||||
# TextToSpeech — возможность мобильного приложения, добавленная форматом 2.18 (8.3.25),
|
||||
# последней в списке; в 2.21 список не менялся. Замерено выгрузками пустой ИБ шести платформ:
|
||||
# 2.13/2.17 — 37 записей без неё, 2.18-2.21 — 38 с ней. Гейт обязателен и в обе стороны:
|
||||
# на 2.17 тег ломает загрузку XDTO-ошибкой (проверено на 8.3.24), без тега на 2.18+ платформа
|
||||
# подставит дефолт false и допишет его при выгрузке — то есть разойдётся роундтрип.
|
||||
if is_218:
|
||||
mobile_funcs.append(("TextToSpeech", "false"))
|
||||
|
||||
mobile_xml = ""
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cf-validate v1.6 — Validate 1C configuration root structure
|
||||
# cf-validate v1.7 — Validate 1C configuration root structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -89,6 +89,19 @@ $finalize = {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Reference tables ---
|
||||
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
||||
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
|
||||
@@ -203,11 +216,15 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
}
|
||||
|
||||
$version = $root.GetAttribute("version")
|
||||
$versionRank = Get-FormatRank $version
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17-2.20 or 2.21)"
|
||||
} elseif ($versionRank -eq 0) {
|
||||
Report-Error "1. Malformed version '$version' (expected N.N)"
|
||||
} elseif ($versionRank -lt (Get-FormatRank $formatVerifiedMin)) {
|
||||
Report-Warn "1. Format version '$version' is below the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} elseif ($versionRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
Report-Warn "1. Format version '$version' is above the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cf-validate v1.6 — Validate 1C configuration XML structure
|
||||
# cf-validate v1.7 — 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
|
||||
@@ -132,6 +132,20 @@ VALID_ENUM_VALUES = {
|
||||
|
||||
EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
class Reporter:
|
||||
def __init__(self, max_errors, detailed=False):
|
||||
@@ -252,11 +266,17 @@ def main():
|
||||
check1_ok = False
|
||||
|
||||
version = root.get('version', '')
|
||||
version_rank = format_rank(version)
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.18', '2.19', '2.20', '2.21'):
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17-2.20 or 2.21)")
|
||||
elif version_rank == 0:
|
||||
r.error(f"1. Malformed version '{version}' (expected N.N)")
|
||||
elif version_rank < format_rank(FORMAT_VERIFIED_MIN):
|
||||
r.warn(f"1. Format version '{version}' is below the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
elif version_rank > format_rank(FORMAT_VERIFIED_MAX):
|
||||
r.warn(f"1. Format version '{version}' is above the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
|
||||
@@ -70,31 +70,33 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-borrow.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Заимствовать один объект
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты"
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты"
|
||||
|
||||
# Заимствовать форму (автоматически заимствует родительский объект)
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты.Form.ФормаЭлемента"
|
||||
|
||||
# Несколько объектов за раз
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Контрагенты ;; CommonModule.ОбщийМодуль ;; Enum.ВидыОплат"
|
||||
|
||||
# Заимствовать форму с основным реквизитом (реквизиты по DataPath формы)
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute
|
||||
|
||||
# Заимствовать форму с ВСЕМИ реквизитами объекта
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Object "Catalog.Номенклатура.Form.ФормаЭлемента" -BorrowMainAttribute All
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/cfe-validate <ExtensionPath>
|
||||
/cfe-validate <ExtensionPath> -ConfigPath <ConfigPath>
|
||||
```
|
||||
|
||||
Конфигурацию-источник передавай и валидатору: заимствованные формы он проверяет по ней.
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||
# cfe-borrow v1.31 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
@@ -16,18 +16,38 @@ function Warn([string]$msg) { Write-Host "[WARN] $msg" }
|
||||
# Form data-binding tags (value = attribute path). A binding survives only if its root
|
||||
# attribute is borrowed into the form's <Attributes>; otherwise it must be stripped or the
|
||||
# platform rejects the form with "Неверный путь к данным" on load.
|
||||
$script:formBindingDataTags = @('DataPath','TitleDataPath','FooterDataPath','HeaderDataPath','MultipleValueDataPath','MultipleValuePresentDataPath')
|
||||
# RowPictureDataPath тоже путь к данным («Объект.Товары.РасхождениеЗаказ», «Список.DefaultPicture»),
|
||||
# а не индекс картинки: эталон Конфигуратора сохраняет его с заимствованным основным реквизитом
|
||||
# и выбрасывает без него — то же правило, что у остальных путей.
|
||||
$script:formBindingDataTags = @('DataPath','TitleDataPath','FooterDataPath','HeaderDataPath','MultipleValueDataPath','MultipleValuePresentDataPath','RowPictureDataPath')
|
||||
# Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton.
|
||||
$script:formBindingPictureTags = @('RowPictureDataPath','MultipleValuePictureDataPath')
|
||||
$script:formBindingPictureTags = @('MultipleValuePictureDataPath')
|
||||
|
||||
# Пути ссылок параметров выбора, которые пришлось вырезать (для предупреждения в конце)
|
||||
$script:droppedLinks = @()
|
||||
|
||||
# id основного реквизита в заимствованной форме — как у Конфигуратора
|
||||
$script:mainAttrId = "1000001"
|
||||
|
||||
# Виды дочерних объектов, которые заимствуются в оболочку поимённо (табличные части — отдельно)
|
||||
$script:childObjectKinds = @('Attribute','Dimension','Resource','AddressingAttribute')
|
||||
|
||||
# Прямые дети <Form>, которые в заимствованную форму не переносятся.
|
||||
# Структурные секции: AutoCommandBar и ChildItems забираются отдельно, остальные выбрасываются целиком.
|
||||
$script:formStructuralSections = @('Events','Attributes','Commands','Parameters','CommandInterface')
|
||||
# Свойства формы, значение которых — имя реквизита формы (реквизиты не заимствуются, ссылка повиснет).
|
||||
$script:formAttributeRefProps = @('ReportResult','DetailsData','VariantAppearance','GroupList')
|
||||
|
||||
# Strip data-binding tags whose root attribute isn't borrowed.
|
||||
# $keepObjekt=$true (BorrowMainAttribute): keep Объект.* data bindings, strip the rest.
|
||||
# $keepObjekt=$false (default skeleton): strip all bindings. Picture-path tags are always stripped.
|
||||
# $mainAttrName задан (BorrowMainAttribute): оставить привязки от его имени, остальные снять.
|
||||
# Пусто (скелет без основного реквизита): снять все. Картиночные пути снимаются всегда.
|
||||
function Strip-FormBindings {
|
||||
param([string]$xml, [bool]$keepObjekt)
|
||||
param([string]$xml, [string]$mainAttrName)
|
||||
foreach ($tag in $script:formBindingDataTags) {
|
||||
if ($keepObjekt) {
|
||||
$xml = [regex]::Replace($xml, "\s*<$tag>(?!Объект\.)[^<]*</$tag>", '')
|
||||
if ($mainAttrName) {
|
||||
# Оставить и «Список.Поле», и путь ровно на сам реквизит («Список» у таблицы формы)
|
||||
$root = [regex]::Escape($mainAttrName)
|
||||
$xml = [regex]::Replace($xml, "\s*<$tag>(?!$root(\.|<))[^<]*</$tag>", '')
|
||||
} else {
|
||||
$xml = [regex]::Replace($xml, "\s*<$tag>[^<]*</$tag>", '')
|
||||
}
|
||||
@@ -38,6 +58,119 @@ function Strip-FormBindings {
|
||||
return $xml
|
||||
}
|
||||
|
||||
# Ссылки параметров выбора (<ChoiceParameterLinks>/<xr:Link>) — привязка особого рода: путь лежит
|
||||
# в <xr:DataPath> и обычным стриппингом не снимается. Текстовое имя в расширении разрешается только
|
||||
# если его корень объявлен в <Attributes> самой заимствованной формы; иначе платформа отвергает
|
||||
# загрузку — «Неверный путь к полю - X». Реквизиты формы не заимствуются никогда, поэтому ссылка на
|
||||
# них разрешима только через id: Конфигуратор подставляет id реквизита ИСХОДНОЙ формы (эталоны
|
||||
# Issue66Example4/5/6, JR2433, JR2976, JR49904 — совпадение на шести расширениях). Именно id
|
||||
# исходной, а не заимствованной: при заимствовании реквизиты перенумеровываются в 1000000+, а
|
||||
# ссылка продолжает указывать в нумерацию базовой формы.
|
||||
# Путь на основной реквизит («Объект.X») при заимствованном основном реквизите разрешается текстом
|
||||
# и остаётся читаемым; без заимствования переводится в «<id>/0:<uuid реквизита объекта>».
|
||||
# Реквизит, которого в источнике нет, недоступен и по uuid: такую связь вырезаем целиком.
|
||||
# Пути вида «Items.<Элемент>.CurrentData.<Поле>» не трогаем — их кодировка отдельная.
|
||||
function Rewrite-ChoiceParameterLinks {
|
||||
param([string]$xml, $attrUuids, $formAttrIds, [string]$mainAttrName, [bool]$mainAttrBorrowed)
|
||||
|
||||
if ($xml -notmatch '<ChoiceParameterLinks>') { return $xml }
|
||||
|
||||
$mainPat = if ($mainAttrName) { [regex]::Escape($mainAttrName) } else { $null }
|
||||
$mainId = if ($mainAttrName -and $formAttrIds.ContainsKey($mainAttrName)) { $formAttrIds[$mainAttrName] } else { "1" }
|
||||
|
||||
$xml = [regex]::Replace($xml, '(?s)\s*<xr:Link>.*?</xr:Link>', {
|
||||
param($m)
|
||||
$link = $m.Value
|
||||
$dp = [regex]::Match($link, '<xr:DataPath[^>]*>([^<]+)</xr:DataPath>')
|
||||
if (-not $dp.Success) { return $link }
|
||||
$path = $dp.Groups[1].Value
|
||||
|
||||
# Путь на основной реквизит формы
|
||||
if ($mainPat -and $path -match "^${mainPat}\.(.+)$") {
|
||||
$attrName = $Matches[1]
|
||||
if ($mainAttrBorrowed) {
|
||||
# Реквизит объекта разрешается текстом и остаётся читаемым. Стандартное поле
|
||||
# («Объект.Owner», «Объект.Date») — нет: платформа отвергает «Неверный путь к данным».
|
||||
# Конфигуратор в этом случае оставляет ссылку на сам реквизит (эталон Issue66Example7_1).
|
||||
if ($attrUuids.ContainsKey($attrName)) { return $link }
|
||||
return [regex]::Replace($link, '(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)', "`${1}${mainId}`${2}")
|
||||
}
|
||||
if ($attrUuids.ContainsKey($attrName)) {
|
||||
return [regex]::Replace($link, '(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)', "`${1}${mainId}/0:$($attrUuids[$attrName])`${2}")
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
# Односегментный путь на реквизит формы — только по id исходной формы
|
||||
if ($path -notmatch '\.' -and $formAttrIds.ContainsKey($path)) {
|
||||
return [regex]::Replace($link, '(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)', "`${1}$($formAttrIds[$path])`${2}")
|
||||
}
|
||||
|
||||
# Уже непрозрачный путь (форма-источник сама из расширения) — не трогаем
|
||||
if ($path -match '^\d') { return $link }
|
||||
|
||||
# С заимствованным основным реквизитом текстовый путь разрешается: элементы формы на месте,
|
||||
# а их данные доступны через основной реквизит. Конфигуратор такие пути и оставляет текстом
|
||||
# (эталон Issue66Example7_1: «Items.Товары.CurrentData.Характеристика» перенесён как есть).
|
||||
if ($mainAttrBorrowed) { return $link }
|
||||
|
||||
# Прочее текстом не разрешается: платформа отвергает загрузку «Неверный путь к полю».
|
||||
# Сюда попадают «Items.<Элемент>.CurrentData.<Поле>» — их кодировка непрозрачна и по
|
||||
# имеющимся эталонам не воспроизводима. Связь параметров выбора — удобство подбора, а не
|
||||
# данные: без неё форма заимствуется и работает, с ней — не грузится вовсе.
|
||||
$script:droppedLinks += $path
|
||||
return ''
|
||||
})
|
||||
|
||||
# Опустевший контейнер платформе не нужен
|
||||
$xml = [regex]::Replace($xml, '(?s)\s*<ChoiceParameterLinks>\s*</ChoiceParameterLinks>', '')
|
||||
return $xml
|
||||
}
|
||||
|
||||
# Имена ПРЯМЫХ детей собственного <ChildObjects> объекта — для дедупа при повторном
|
||||
# заимствовании. Текстом это не снять: regex «первый <ChildObjects> до первого </ChildObjects>»
|
||||
# у объекта с табличными частями обрывается на закрытии первой ТЧ, забирает имена её колонок и
|
||||
# теряет то, что идёт после неё.
|
||||
function Get-OwnChildObjectNames {
|
||||
param([string]$objFile)
|
||||
|
||||
$names = @{}
|
||||
if (-not (Test-Path -LiteralPath $objFile)) { return $names }
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
try { $doc.Load($objFile) } catch { return $names }
|
||||
$objEl = $null
|
||||
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||
}
|
||||
if (-not $objEl) { return $names }
|
||||
$childObjs = $objEl.SelectSingleNode("*[local-name()='ChildObjects']")
|
||||
if (-not $childObjs) { return $names }
|
||||
foreach ($child in $childObjs.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
$nameNode = $child.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
|
||||
if ($nameNode) { $names[$nameNode.InnerText.Trim()] = $true }
|
||||
}
|
||||
return $names
|
||||
}
|
||||
|
||||
# Вставка в СОБСТВЕННЫЙ <ChildObjects> объекта. Свой контейнер закрывается в файле последним:
|
||||
# объект в файле один, а вложенные <ChildObjects> табличных частей закрываются раньше. Замена по
|
||||
# всем вхождениям раскидывала реквизиты по каждой ТЧ — ps1 рвал XML, py прятал ТЧ внутрь ТЧ.
|
||||
function Insert-IntoOwnChildObjects {
|
||||
param([string]$text, [string]$content)
|
||||
|
||||
$closeIdx = $text.LastIndexOf('</ChildObjects>')
|
||||
if ($closeIdx -ge 0) {
|
||||
return $text.Substring(0, $closeIdx) + "${content}`r`n`t`t" + $text.Substring($closeIdx)
|
||||
}
|
||||
# Своего закрывающего тега нет — значит контейнер самозакрытый (детей у него нет, вложенных тоже)
|
||||
$selfMatches = [regex]::Matches($text, '<ChildObjects\s*/>')
|
||||
if ($selfMatches.Count -eq 0) { return $text }
|
||||
$m = $selfMatches[$selfMatches.Count - 1]
|
||||
return $text.Substring(0, $m.Index) + "<ChildObjects>${content}`r`n`t`t</ChildObjects>" + $text.Substring($m.Index + $m.Length)
|
||||
}
|
||||
|
||||
# --- 1. Resolve paths ---
|
||||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||||
@@ -209,7 +342,8 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "AccumulationRegisterRecordKey"; category = "RecordKey" }
|
||||
)
|
||||
"AccountingRegister" = @(
|
||||
@{ prefix = "AccountingRegisterRecord"; category = "Record" }
|
||||
@{ prefix = "AccountingRegisterRecord"; category = "Record" }
|
||||
@{ prefix = "AccountingRegisterExtDimensions"; category = "ExtDimensions" }
|
||||
@{ prefix = "AccountingRegisterManager"; category = "Manager" }
|
||||
@{ prefix = "AccountingRegisterSelection"; category = "Selection" }
|
||||
@{ prefix = "AccountingRegisterList"; category = "List" }
|
||||
@@ -223,6 +357,7 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "CalculationRegisterList"; category = "List" }
|
||||
@{ prefix = "CalculationRegisterRecordSet"; category = "RecordSet" }
|
||||
@{ prefix = "CalculationRegisterRecordKey"; category = "RecordKey" }
|
||||
@{ prefix = "RecalculationsManager"; category = "Recalcs" }
|
||||
)
|
||||
"ChartOfAccounts" = @(
|
||||
@{ prefix = "ChartOfAccountsObject"; category = "Object" }
|
||||
@@ -230,12 +365,15 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "ChartOfAccountsSelection"; category = "Selection" }
|
||||
@{ prefix = "ChartOfAccountsList"; category = "List" }
|
||||
@{ prefix = "ChartOfAccountsManager"; category = "Manager" }
|
||||
@{ prefix = "ChartOfAccountsExtDimensionTypes"; category = "ExtDimensionTypes" }
|
||||
@{ prefix = "ChartOfAccountsExtDimensionTypesRow"; category = "ExtDimensionTypesRow" }
|
||||
)
|
||||
"ChartOfCharacteristicTypes" = @(
|
||||
@{ prefix = "ChartOfCharacteristicTypesObject"; category = "Object" }
|
||||
@{ prefix = "ChartOfCharacteristicTypesRef"; category = "Ref" }
|
||||
@{ prefix = "ChartOfCharacteristicTypesSelection"; category = "Selection" }
|
||||
@{ prefix = "ChartOfCharacteristicTypesList"; category = "List" }
|
||||
@{ prefix = "Characteristic"; category = "Characteristic" }
|
||||
@{ prefix = "ChartOfCharacteristicTypesManager"; category = "Manager" }
|
||||
)
|
||||
"ChartOfCalculationTypes" = @(
|
||||
@@ -245,8 +383,11 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "ChartOfCalculationTypesList"; category = "List" }
|
||||
@{ prefix = "ChartOfCalculationTypesManager"; category = "Manager" }
|
||||
@{ prefix = "DisplacingCalculationTypes"; category = "DisplacingCalculationTypes" }
|
||||
@{ prefix = "DisplacingCalculationTypesRow"; category = "DisplacingCalculationTypesRow" }
|
||||
@{ prefix = "BaseCalculationTypes"; category = "BaseCalculationTypes" }
|
||||
@{ prefix = "BaseCalculationTypesRow"; category = "BaseCalculationTypesRow" }
|
||||
@{ prefix = "LeadingCalculationTypes"; category = "LeadingCalculationTypes" }
|
||||
@{ prefix = "LeadingCalculationTypesRow"; category = "LeadingCalculationTypesRow" }
|
||||
)
|
||||
"BusinessProcess" = @(
|
||||
@{ prefix = "BusinessProcessObject"; category = "Object" }
|
||||
@@ -254,6 +395,7 @@ $script:generatedTypes = @{
|
||||
@{ prefix = "BusinessProcessSelection"; category = "Selection" }
|
||||
@{ prefix = "BusinessProcessList"; category = "List" }
|
||||
@{ prefix = "BusinessProcessManager"; category = "Manager" }
|
||||
@{ prefix = "BusinessProcessRoutePointRef"; category = "RoutePointRef" }
|
||||
)
|
||||
"Task" = @(
|
||||
@{ prefix = "TaskObject"; category = "Object" }
|
||||
@@ -320,6 +462,16 @@ $typesWithChildObjects = @(
|
||||
# CommonModule properties to copy from source
|
||||
$commonModuleProps = @("Global","ClientManagedApplication","Server","ExternalConnection","ClientOrdinaryApplication","ServerCall")
|
||||
|
||||
# Свойства объекта, от которых зависит существование стандартного поля: без них платформа
|
||||
# отвергает загрузку — «Неверный путь к данным». Конфигуратор переносит ровно их (эталоны
|
||||
# Issue66Example7_1 и Issue66Example2). Проверено сплошным прогоном по типам: у регистра сведений
|
||||
# без InformationRegisterPeriodicity не разрешается «Запись.Period».
|
||||
$script:typeGateProps = @{
|
||||
"InformationRegister" = @("InformationRegisterPeriodicity","WriteMode")
|
||||
}
|
||||
# Владельцы справочника — список <xr:Item>, а не скаляр: переносится фрагментом, как __TypeXml
|
||||
$script:typesWithOwners = @("Catalog","ChartOfCharacteristicTypes")
|
||||
|
||||
# Standard system fields to skip when collecting DataPath references
|
||||
$script:standardFields = @("Code","Description","Ref","Parent","DeletionMark","Predefined","IsFolder","LineNumber","RowsCount","PredefinedDataName")
|
||||
|
||||
@@ -440,6 +592,37 @@ if ($BorrowMainAttribute) {
|
||||
}
|
||||
|
||||
# --- 10. Helper: read source object XML ---
|
||||
# Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
|
||||
# параметров выбора (см. Rewrite-ChoiceParameterLinks).
|
||||
function Get-SourceAttributeUuids {
|
||||
param([string]$typeName, [string]$objName)
|
||||
|
||||
$result = @{}
|
||||
$dirName = $childTypeDirMap[$typeName]
|
||||
if (-not $dirName) { return $result }
|
||||
$srcFile = Join-Path (Join-Path $cfgDir $dirName) "${objName}.xml"
|
||||
if (-not (Test-Path $srcFile)) { return $result }
|
||||
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
$doc.Load($srcFile)
|
||||
$objEl = $null
|
||||
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||
}
|
||||
if (-not $objEl) { return $result }
|
||||
$childObjects = $objEl.SelectSingleNode("*[local-name()='ChildObjects']")
|
||||
if (-not $childObjects) { return $result }
|
||||
foreach ($child in $childObjects.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
if ($child.LocalName -notin @('Attribute','TabularSection')) { continue }
|
||||
$uuid = $child.GetAttribute("uuid")
|
||||
$nameNode = $child.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
|
||||
if ($uuid -and $nameNode) { $result[$nameNode.InnerText.Trim()] = $uuid }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Read-SourceObject {
|
||||
param([string]$typeName, [string]$objName)
|
||||
|
||||
@@ -499,6 +682,19 @@ function Read-SourceObject {
|
||||
$srcProps["__TypeXml"] = [regex]::Replace($typeNode.OuterXml, '\s+xmlns(?::\w+)?="[^"]*"', '')
|
||||
}
|
||||
}
|
||||
# Владельцы: стандартное поле «Owner» появляется у справочника, только если задан Owners
|
||||
if ($script:typesWithOwners -ccontains $typeName) {
|
||||
$ownersNode = $propsNode.SelectSingleNode("md:Owners", $srcNs)
|
||||
if ($ownersNode -and $ownersNode.HasChildNodes) {
|
||||
$srcProps["__OwnersXml"] = [regex]::Replace($ownersNode.OuterXml, '\s+xmlns(?::\w+)?="[^"]*"', '')
|
||||
}
|
||||
}
|
||||
# Скалярные свойства, включающие стандартные поля своего типа
|
||||
foreach ($gp in @($script:typeGateProps[$typeName])) {
|
||||
if (-not $gp) { continue }
|
||||
$gpNode = $propsNode.SelectSingleNode("md:${gp}", $srcNs)
|
||||
if ($gpNode) { $srcProps[$gp] = $gpNode.InnerText.Trim() }
|
||||
}
|
||||
}
|
||||
|
||||
# Whether the platform emits <ChildObjects> for this type — the source object is the ground truth
|
||||
@@ -619,30 +815,56 @@ function Borrow-Form {
|
||||
# (e.g. a 2.13 form inside a 2.17 extension). The platform itself upgrades the form to the root version.
|
||||
$formVersion = $script:formatVersion
|
||||
|
||||
# Find direct children: form properties, AutoCommandBar, ChildItems
|
||||
# Find direct children: form properties, AutoCommandBar, ChildItems.
|
||||
# Секции формы отбираются по имени, а не по позиции: свойства лежат и до, и после <CommandSet>
|
||||
# (корпусная проверка: у всех 794 форм документов ERP с CommandSet он стоит раньше AutoCommandBar,
|
||||
# а AutoTime/UsePostingMode/RepostOnWrite — после него). Позиционная отсечка теряла весь хвост,
|
||||
# и платформа молча подставляла дефолты вместо потерянных свойств.
|
||||
$srcAutoCmd = $null
|
||||
$srcChildItems = $null
|
||||
$formProps = @()
|
||||
$reachedVisual = $false
|
||||
foreach ($fc in $srcFormEl.ChildNodes) {
|
||||
if ($fc.NodeType -ne 'Element') { continue }
|
||||
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) {
|
||||
$reachedVisual = $true; $srcAutoCmd = $fc; continue
|
||||
$srcAutoCmd = $fc; continue
|
||||
}
|
||||
if ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) {
|
||||
$reachedVisual = $true; $srcChildItems = $fc; continue
|
||||
}
|
||||
if ($fc.LocalName -eq 'Events' -or $fc.LocalName -eq 'Attributes' -or $fc.LocalName -eq 'Commands' -or $fc.LocalName -eq 'Parameters' -or $fc.LocalName -eq 'CommandSet') {
|
||||
$reachedVisual = $true; continue
|
||||
}
|
||||
if (-not $reachedVisual) {
|
||||
$formProps += $fc.OuterXml
|
||||
$srcChildItems = $fc; continue
|
||||
}
|
||||
# Структурные секции: в расширении их содержимое недействительно (обработчики, команды и
|
||||
# параметры базовой формы, ссылки командного интерфейса на команды базовой конфигурации).
|
||||
if ($script:formStructuralSections -ccontains $fc.LocalName) { continue }
|
||||
# Свойства, значение которых — имя реквизита формы. Реквизиты в заимствованную форму не
|
||||
# переносятся, поэтому Конфигуратор такие свойства выбрасывает (проверено на форме отчёта:
|
||||
# ReportResult и DetailsData выброшены, CustomSettingsFolder — имя элемента — сохранён).
|
||||
if ($script:formAttributeRefProps -ccontains $fc.LocalName) { continue }
|
||||
$formProps += $fc.OuterXml
|
||||
}
|
||||
|
||||
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
|
||||
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
|
||||
|
||||
# Основной реквизит исходной формы: его имя — корень путей к данным, которые нужно сохранить
|
||||
# («Объект.» у формы объекта, «Список.» у формы списка, «Запись.» у формы записи регистра)
|
||||
# Имя основного реквизита источника нужно в обоих режимах: по нему опознаётся корень путей
|
||||
# в ссылках параметров выбора. А $mainAttrName управляет вырезанием привязок и потому остаётся
|
||||
# пустым в скелетном режиме — там привязки снимаются все.
|
||||
$srcMainInfo = Get-MainAttributeInfo $srcFormEl $nsStripPattern
|
||||
$srcMainAttrName = if ($srcMainInfo) { $srcMainInfo.Name } else { "" }
|
||||
$formAttrIds = Get-FormAttributeIds $srcFormEl
|
||||
$mainAttrInfo = if ($BorrowMainAttr) { $srcMainInfo } else { $null }
|
||||
$mainAttrName = if ($mainAttrInfo) { $mainAttrInfo.Name } else { "" }
|
||||
if ($BorrowMainAttr -and -not $mainAttrInfo) {
|
||||
Warn " У формы нет основного реквизита — -BorrowMainAttribute проигнорирован"
|
||||
}
|
||||
|
||||
# uuid реквизитов объекта нужны ровно там, где основной реквизит НЕ попал в форму:
|
||||
# только тогда путь «<основной>.X» переводится в непрозрачный вид
|
||||
# Имена реквизитов объекта нужны в обоих режимах: без заимствования — чтобы построить
|
||||
# непрозрачный путь, с заимствованием — чтобы отличить реквизит (разрешается текстом) от
|
||||
# стандартного поля (не разрешается)
|
||||
$srcAttrUuids = Get-SourceAttributeUuids $typeName $objName
|
||||
|
||||
# AutoCommandBar: keep ChildItems (buttons with CommandName→0), Autofill→false
|
||||
$autoCmdXml = ""
|
||||
if ($srcAutoCmd) {
|
||||
@@ -650,10 +872,13 @@ function Borrow-Form {
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, $nsStripPattern, '')
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
||||
$autoCmdXml = $autoCmdXml -replace '<Autofill>true</Autofill>', '<Autofill>false</Autofill>'
|
||||
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
|
||||
# Вложенный CommandSet выбрасывается целиком, а не опустошается: Конфигуратор в заимствованной
|
||||
# форме оставляет только корневой (тот идёт свойством формы, здесь его нет).
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '(?s)\s*<CommandSet>.*?</CommandSet>', '')
|
||||
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<CommandSet/>', '')
|
||||
# Strip data-binding tags whose root attribute isn't borrowed
|
||||
$autoCmdXml = Strip-FormBindings $autoCmdXml ([bool]$BorrowMainAttr)
|
||||
$autoCmdXml = Strip-FormBindings $autoCmdXml $mainAttrName
|
||||
$autoCmdXml = Rewrite-ChoiceParameterLinks $autoCmdXml $srcAttrUuids $formAttrIds $srcMainAttrName ([bool]$mainAttrInfo)
|
||||
}
|
||||
|
||||
# ChildItems: copy full tree, clean up base-config references
|
||||
@@ -665,9 +890,11 @@ function Borrow-Form {
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
|
||||
# Strip data-binding tags whose root attribute isn't borrowed
|
||||
# (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*/RowPicture*)
|
||||
$childItemsXml = Strip-FormBindings $childItemsXml ([bool]$BorrowMainAttr)
|
||||
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '')
|
||||
$childItemsXml = Strip-FormBindings $childItemsXml $mainAttrName
|
||||
$childItemsXml = Rewrite-ChoiceParameterLinks $childItemsXml $srcAttrUuids $formAttrIds $srcMainAttrName ([bool]$mainAttrInfo)
|
||||
# Вложенные CommandSet (у таблиц, полей табличного документа и т.п.) — целиком, см. выше
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<CommandSet>.*?</CommandSet>', '')
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<CommandSet/>', '')
|
||||
# Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID)
|
||||
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<TypeLink>\s*<xr:DataPath>Items\.[^<]*</xr:DataPath>.*?</TypeLink>', '')
|
||||
# Strip element-level Events (base form handlers not in extension)
|
||||
@@ -884,17 +1111,9 @@ function Borrow-Form {
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
# Attributes: empty or with MainAttribute when BorrowMainAttr
|
||||
if ($BorrowMainAttr) {
|
||||
$objTypePrefix = ""
|
||||
$gtList = $script:generatedTypes[$typeName]
|
||||
if ($gtList) { foreach ($g in $gtList) { if ($g.category -eq "Object") { $objTypePrefix = $g.prefix; break } } }
|
||||
$mainAttrType = "cfg:${objTypePrefix}.${objName}"
|
||||
if ($BorrowMainAttr -and $mainAttrInfo) {
|
||||
$formXmlSb.Append("`t<Attributes>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t<Attribute name=`"Объект`" id=`"1000001`">`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t</Attribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t$($mainAttrInfo.Xml)`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t</Attributes>") | Out-Null
|
||||
} else {
|
||||
$formXmlSb.Append("`t<Attributes/>") | Out-Null
|
||||
@@ -928,13 +1147,15 @@ function Borrow-Form {
|
||||
}
|
||||
|
||||
# BaseForm Attributes: same as main section
|
||||
if ($BorrowMainAttr) {
|
||||
if ($BorrowMainAttr -and $mainAttrInfo) {
|
||||
$formXmlSb.Append("`t`t<Attributes>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t<Attribute name=`"Объект`" id=`"1000001`">`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<Type><v8:Type>${mainAttrType}</v8:Type></Type>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<MainAttribute>true</MainAttribute>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t`t<SavedData>true</SavedData>`r`n") | Out-Null
|
||||
$formXmlSb.Append("`t`t`t</Attribute>`r`n") | Out-Null
|
||||
# В BaseForm та же секция на уровень глубже — приём переиндентации тот же, что у ChildItems
|
||||
$maLines = $mainAttrInfo.Xml -split "`r?`n"
|
||||
for ($li = 0; $li -lt $maLines.Count; $li++) {
|
||||
if ($li -eq 0) { $formXmlSb.Append("`t`t`t$($maLines[$li])") | Out-Null }
|
||||
else { $formXmlSb.Append("`t$($maLines[$li])") | Out-Null }
|
||||
$formXmlSb.Append("`r`n") | Out-Null
|
||||
}
|
||||
$formXmlSb.Append("`t`t</Attributes>") | Out-Null
|
||||
} else {
|
||||
$formXmlSb.Append("`t`t<Attributes/>") | Out-Null
|
||||
@@ -960,6 +1181,11 @@ function Borrow-Form {
|
||||
$formXmlText = ($formXmlText -replace "`r`n", "`n") -replace "`n", "`r`n"
|
||||
[System.IO.File]::WriteAllText($formXmlFile, $formXmlText, $enc)
|
||||
Info " Created: $formXmlFile"
|
||||
if ($script:droppedLinks.Count -gt 0) {
|
||||
$uniq = @($script:droppedLinks | Sort-Object -Unique)
|
||||
Warn " Вырезано связей параметров выбора: $($uniq.Count) — путь не разрешается в расширении: $($uniq -join ', ')"
|
||||
$script:droppedLinks = @()
|
||||
}
|
||||
|
||||
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
||||
# not clobber user code added to the form module).
|
||||
@@ -1120,8 +1346,46 @@ function Build-InternalInfoXml {
|
||||
}
|
||||
|
||||
# --- 11b. Collect DataPath references from source Form.xml ---
|
||||
# --- 11b1. Основной реквизит исходной формы ---
|
||||
# Переносится ЦЕЛИКОМ, а не собирается из констант: имя, тип и состав детей зависят от вида формы.
|
||||
# У формы объекта это «Объект»/<Тип>Object + SavedData/UseAlways/Columns, у формы списка —
|
||||
# «Список»/DynamicList + Settings, у формы записи регистра — «Запись»/RecordManager + SavedData.
|
||||
# Синтез фиксированного набора давал для необъектных форм «Исключение XDTO» при загрузке.
|
||||
# Конфигуратор меняет у скопированного реквизита только id (эталоны Issue64UtB, Issue66Example2).
|
||||
# Имена реквизитов ИСХОДНОЙ формы → их id. Ссылки параметров выбора адресуют реквизит формы
|
||||
# именно по id базовой формы (см. Rewrite-ChoiceParameterLinks).
|
||||
function Get-FormAttributeIds {
|
||||
param($formEl)
|
||||
|
||||
$result = @{}
|
||||
$attrs = $formEl.SelectSingleNode("*[local-name()='Attributes']")
|
||||
if (-not $attrs) { return $result }
|
||||
foreach ($a in $attrs.ChildNodes) {
|
||||
if ($a.NodeType -ne 'Element' -or $a.LocalName -ne 'Attribute') { continue }
|
||||
$nm = $a.GetAttribute("name"); $id = $a.GetAttribute("id")
|
||||
if ($nm -and $id) { $result[$nm] = $id }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-MainAttributeInfo {
|
||||
param($formEl, [string]$nsStripPattern)
|
||||
|
||||
$mainAttr = $formEl.SelectSingleNode("*[local-name()='Attributes']/*[local-name()='Attribute'][*[local-name()='MainAttribute']='true']")
|
||||
if (-not $mainAttr) { return $null }
|
||||
$xml = [regex]::Replace($mainAttr.OuterXml, $nsStripPattern, '')
|
||||
# id заменяется только в открывающем теге самого реквизита — у вложенных элементов свои
|
||||
$xml = [regex]::Replace($xml, '^(<Attribute\s[^>]*?)id="[^"]*"', "`${1}id=`"$script:mainAttrId`"")
|
||||
return @{ Name = $mainAttr.GetAttribute("name"); Xml = $xml }
|
||||
}
|
||||
|
||||
function Collect-FormDataPaths {
|
||||
param([string]$formXmlPath)
|
||||
param([string]$formXmlPath, [string]$mainAttrName)
|
||||
|
||||
# Корень путей — имя основного реквизита формы: «Объект» у формы объекта, «Список» у формы
|
||||
# списка, «Запись» у формы записи регистра. Зашитый «Объект» не находил ничего у необъектных
|
||||
# форм, и в оболочку не заимствовалось ни одного дочернего объекта.
|
||||
$root = [regex]::Escape($mainAttrName)
|
||||
|
||||
$enc = New-Object System.Text.UTF8Encoding($true)
|
||||
$content = [System.IO.File]::ReadAllText($formXmlPath, $enc)
|
||||
@@ -1132,7 +1396,7 @@ function Collect-FormDataPaths {
|
||||
# Scan every data-binding tag (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*)
|
||||
# for Объект.* references — picture-path tags carry picture indices, not data attributes.
|
||||
foreach ($tag in $script:formBindingDataTags) {
|
||||
$bms = [regex]::Matches($content, "<$tag>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</$tag>")
|
||||
$bms = [regex]::Matches($content, "<$tag>[^<]*\b$root\.(\w+(?:\.\w+)*)</$tag>")
|
||||
foreach ($m in $bms) {
|
||||
$path = $m.Groups[1].Value
|
||||
$segments = $path.Split(".")
|
||||
@@ -1150,7 +1414,7 @@ function Collect-FormDataPaths {
|
||||
|
||||
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
|
||||
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
|
||||
$fieldMatches = [regex]::Matches($content, "<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>")
|
||||
$fieldMatches = [regex]::Matches($content, "<Field>[^<]*\b$root\.(\w+(?:\.\w+)*)</Field>")
|
||||
foreach ($m in $fieldMatches) {
|
||||
$path = $m.Groups[1].Value
|
||||
$segments = $path.Split(".")
|
||||
@@ -1164,6 +1428,30 @@ function Collect-FormDataPaths {
|
||||
}
|
||||
}
|
||||
|
||||
# Also scan <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в
|
||||
# самой форме (напр. Объект.Товары.Артикул). Такая ТЧ может больше нигде на форме не встречаться,
|
||||
# и без её заимствования платформа отвергает форму: «Неверный путь к данным».
|
||||
$acMatches = [regex]::Matches($content, "<AdditionalColumns table=`"$root\.(\w+)`"")
|
||||
foreach ($m in $acMatches) {
|
||||
$seg0 = $m.Groups[1].Value
|
||||
if ($script:standardFields -contains $seg0) { continue }
|
||||
$firstLevel[$seg0] = $true
|
||||
}
|
||||
|
||||
# Текст запроса динамического списка — такое же место ссылки на реквизиты объекта, как DataPath.
|
||||
# Конфигуратор заимствует всё, что упомянуто в запросе: на эталоне Issue66Example2 это 21 из 27
|
||||
# дочерних объектов, совпадение с ним точное в обе стороны. У списка без ручного запроса
|
||||
# (<QueryText> нет) заимствуется только видимое на форме — эталон Issue66Example3.
|
||||
# Разбирать язык запросов не нужно: имена-кандидаты отфильтрует Resolve-SourceAttributes по
|
||||
# реальному составу объекта, поэтому лишние слова из запроса безвредны.
|
||||
foreach ($qm in [regex]::Matches($content, '(?s)<QueryText>(.*?)</QueryText>')) {
|
||||
foreach ($w in [regex]::Matches($qm.Groups[1].Value, '[\w]+')) {
|
||||
$word = $w.Value
|
||||
if ($script:standardFields -contains $word) { continue }
|
||||
$firstLevel[$word] = $true
|
||||
}
|
||||
}
|
||||
|
||||
# Deduplicate deep paths
|
||||
$seen = @{}
|
||||
$uniqueDeep = @()
|
||||
@@ -1214,7 +1502,11 @@ function Resolve-SourceAttributes {
|
||||
foreach ($child in $childObjs.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
|
||||
if ($child.LocalName -eq 'Attribute') {
|
||||
# Реквизит объекта, измерение и ресурс регистра — один и тот же вид дочернего объекта с
|
||||
# точки зрения заимствования, различается только имя элемента. Конфигуратор переносит их
|
||||
# своим видом (эталон Issue66Example2: у регистра <Dimension> x3 и <Resource>), поэтому вид
|
||||
# запоминается и выпускается как есть — иначе измерение уехало бы в файл как <Attribute>.
|
||||
if ($script:childObjectKinds -ccontains $child.LocalName) {
|
||||
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $srcNs)
|
||||
if (-not $nameNode) { continue }
|
||||
$attrName = $nameNode.InnerText
|
||||
@@ -1226,7 +1518,7 @@ function Resolve-SourceAttributes {
|
||||
# Strip namespace declarations from Type
|
||||
$typeXml = [regex]::Replace($typeXml, '\s+xmlns(?::\w+)?="[^"]*"', '')
|
||||
|
||||
$attrs += @{ Name = $attrName; Uuid = $uuid; TypeXml = $typeXml }
|
||||
$attrs += @{ Name = $attrName; Uuid = $uuid; TypeXml = $typeXml; Kind = $child.LocalName }
|
||||
}
|
||||
elseif ($child.LocalName -eq 'TabularSection') {
|
||||
$nameNode = $child.SelectSingleNode("md:Properties/md:Name", $srcNs)
|
||||
@@ -1276,8 +1568,16 @@ function Resolve-SourceAttributes {
|
||||
$extraProps = [ordered]@{}
|
||||
$propsNode = $srcEl.SelectSingleNode("md:Properties", $srcNs)
|
||||
if ($propsNode) {
|
||||
$propsToExtract = @("Hierarchical","FoldersOnTop","CodeLength","DescriptionLength","CodeType","CodeAllowedLength",
|
||||
"NumberType","NumberLength","NumberAllowedLength","NumberPeriodicity")
|
||||
# NumberPeriodicity сюда НЕ входит: платформа считает его модификацией настроек нумерации и
|
||||
# тогда требует объявить ещё и <Numerator/>, иначе /UpdateDBCfg падает — «отключать
|
||||
# контролируемость свойства "Нумератор" недопустимо». Конфигуратор его не переносит
|
||||
# (эталон заимствования документа: NumberType/NumberLength/NumberAllowedLength и всё).
|
||||
# Загрузку это не ломает, ошибка вылезает только на обновлении конфигурации БД.
|
||||
# FoldersOnTop сюда НЕ входит: платформа его у заимствованной оболочки не хранит — при
|
||||
# загрузке молча выбрасывает (проверено раундтрипом: записали, выгрузили обратно, свойства
|
||||
# нет). Конфигуратор его тоже не переносит. Остальные из списка сохраняются.
|
||||
$propsToExtract = @("Hierarchical","CodeLength","DescriptionLength","CodeType","CodeAllowedLength",
|
||||
"NumberType","NumberLength","NumberAllowedLength")
|
||||
foreach ($pName in $propsToExtract) {
|
||||
$pNode = $propsNode.SelectSingleNode("md:${pName}", $srcNs)
|
||||
if ($pNode) { $extraProps[$pName] = $pNode.InnerText }
|
||||
@@ -1289,11 +1589,11 @@ function Resolve-SourceAttributes {
|
||||
|
||||
# --- 11d. Build adopted attribute XML ---
|
||||
function Build-AdoptedAttributeXml {
|
||||
param([string]$name, [string]$sourceUuid, [string]$typeXml, [string]$indent)
|
||||
param([string]$name, [string]$sourceUuid, [string]$typeXml, [string]$indent, [string]$kind = "Attribute")
|
||||
|
||||
$newUuid = [guid]::NewGuid().ToString()
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
$sb.AppendLine("${indent}<Attribute uuid=`"${newUuid}`">") | Out-Null
|
||||
$sb.AppendLine("${indent}<${kind} uuid=`"${newUuid}`">") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<InternalInfo/>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t<Properties>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t<ObjectBelonging>Adopted</ObjectBelonging>") | Out-Null
|
||||
@@ -1302,7 +1602,7 @@ function Build-AdoptedAttributeXml {
|
||||
$sb.AppendLine("${indent}`t`t<ExtendedConfigurationObject>${sourceUuid}</ExtendedConfigurationObject>") | Out-Null
|
||||
$sb.AppendLine("${indent}`t`t${typeXml}") | Out-Null
|
||||
$sb.AppendLine("${indent}`t</Properties>") | Out-Null
|
||||
$sb.Append("${indent}</Attribute>") | Out-Null
|
||||
$sb.Append("${indent}</${kind}>") | Out-Null
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
@@ -1425,14 +1725,6 @@ function Merge-AttributesIntoObject {
|
||||
$added = 0
|
||||
foreach ($attr in $attrsToAdd) {
|
||||
if ($existingNames.ContainsKey($attr.Name)) { continue }
|
||||
$attrXml = Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t"
|
||||
|
||||
# Expand self-closing ChildObjects if needed
|
||||
if (-not $childObjs.HasChildNodes -or $childObjs.IsEmpty) {
|
||||
$closeWs = $objDoc.CreateWhitespace("`r`n`t`t")
|
||||
$childObjs.AppendChild($closeWs) | Out-Null
|
||||
}
|
||||
|
||||
$added++
|
||||
}
|
||||
|
||||
@@ -1441,7 +1733,8 @@ function Merge-AttributesIntoObject {
|
||||
$allAttrXml = ""
|
||||
foreach ($attr in $attrsToAdd) {
|
||||
if ($existingNames.ContainsKey($attr.Name)) { continue }
|
||||
$allAttrXml += "`r`n" + (Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t")
|
||||
$kind = if ($attr.Kind) { $attr.Kind } else { "Attribute" }
|
||||
$allAttrXml += "`r`n" + (Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t" $kind)
|
||||
}
|
||||
|
||||
# Save via text manipulation to avoid namespace issues with InnerXml
|
||||
@@ -1459,8 +1752,9 @@ function Merge-AttributesIntoObject {
|
||||
if ($text3.Length -gt 0 -and $text3[0] -eq [char]0xFEFF) { $text3 = $text3.Substring(1) }
|
||||
$text3 = $text3.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||
|
||||
# Insert attributes before </ChildObjects>
|
||||
$text3 = $text3 -replace '</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>"
|
||||
# Самозакрытый элемент раскрывается текстом, а не пробельным узлом в DOM: тот давал
|
||||
# лишнюю строку с табуляцией перед первым <Attribute> (у Конфигуратора пустых строк нет).
|
||||
$text3 = Insert-IntoOwnChildObjects $text3 $allAttrXml
|
||||
|
||||
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||
@@ -1494,7 +1788,16 @@ function Borrow-MainAttribute {
|
||||
Write-Error "Source Form.xml not found: $srcFormXmlPath"
|
||||
exit 1
|
||||
}
|
||||
$dp = Collect-FormDataPaths $srcFormXmlPath
|
||||
# Имя основного реквизита исходной формы — корень путей, которые надо собрать
|
||||
$dpDoc = New-Object System.Xml.XmlDocument
|
||||
$dpDoc.PreserveWhitespace = $true
|
||||
$dpDoc.Load($srcFormXmlPath)
|
||||
$dpInfo = Get-MainAttributeInfo $dpDoc.DocumentElement '\s+xmlns(?::\w+)?="[^"]*"'
|
||||
if (-not $dpInfo) {
|
||||
Warn " У формы нет основного реквизита — заимствовать нечего"
|
||||
return
|
||||
}
|
||||
$dp = Collect-FormDataPaths $srcFormXmlPath $dpInfo.Name
|
||||
$firstLevelNames = $dp.FirstLevel
|
||||
$deepPaths = $dp.DeepPaths
|
||||
Info " Collected $($firstLevelNames.Count) first-level DataPath references, $($deepPaths.Count) deep paths"
|
||||
@@ -1520,19 +1823,15 @@ function Borrow-MainAttribute {
|
||||
$objContent = [System.IO.File]::ReadAllText($objFile, (New-Object System.Text.UTF8Encoding($true)))
|
||||
|
||||
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||
$existingChildNames = @{}
|
||||
if ($objContent -match '(?s)<ChildObjects>(.*?)</ChildObjects>') {
|
||||
foreach ($nm in [regex]::Matches($Matches[1], '<Name>(\w+)</Name>')) {
|
||||
$existingChildNames[$nm.Groups[1].Value] = $true
|
||||
}
|
||||
}
|
||||
$existingChildNames = Get-OwnChildObjectNames $objFile
|
||||
$insertAttrs = @($srcAttrs | Where-Object { -not $existingChildNames.ContainsKey($_.Name) })
|
||||
$insertTS = @($srcTS | Where-Object { -not $existingChildNames.ContainsKey($_.Name) })
|
||||
|
||||
# Generate full object XML with attributes and TS
|
||||
$contentSb = New-Object System.Text.StringBuilder
|
||||
foreach ($attr in $insertAttrs) {
|
||||
$attrXml = Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t"
|
||||
$attrKind = if ($attr.Kind) { $attr.Kind } else { "Attribute" }
|
||||
$attrXml = Build-AdoptedAttributeXml $attr.Name $attr.Uuid $attr.TypeXml "`t`t`t" $attrKind
|
||||
$contentSb.AppendLine($attrXml) | Out-Null
|
||||
}
|
||||
foreach ($ts in $insertTS) {
|
||||
@@ -1557,17 +1856,9 @@ function Borrow-MainAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
# Добавить заимствованное содержимое в ChildObjects объекта (там уже может лежать <Form>)
|
||||
if ($adoptedContent) {
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if ($objContent -match '<ChildObjects\s*/>') {
|
||||
$objContent = $objContent -replace '<ChildObjects\s*/>', "<ChildObjects>`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elseif ($objContent -match '(?s)<ChildObjects>(.*?)</ChildObjects>') {
|
||||
$existingInner = $Matches[1]
|
||||
$objContent = $objContent -replace '(?s)<ChildObjects>(.*?)</ChildObjects>', "<ChildObjects>${existingInner}`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
$objContent = Insert-IntoOwnChildObjects $objContent "`r`n${adoptedContent}"
|
||||
}
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
@@ -1580,6 +1871,21 @@ function Borrow-MainAttribute {
|
||||
foreach ($ts in $srcTS) {
|
||||
foreach ($tsa in $ts.Attributes) { $allTypeXmls += $tsa.TypeXml }
|
||||
}
|
||||
# Типы из <Columns> основного реквизита формы: колонку мы переносим (Borrow-Form), значит и её
|
||||
# тип должен быть заимствован — иначе колонка ссылается на DefinedType/справочник, которого в
|
||||
# расширении нет. Конфигуратор поступает так же (эталон: DefinedTypes/Артикул при заимствовании
|
||||
# формы заказа поставщику).
|
||||
$srcFormForCols = Join-Path (Join-Path (Join-Path (Join-Path (Join-Path $cfgDir $dirName) $objName) "Forms") $formName) "Ext/Form.xml"
|
||||
if (Test-Path $srcFormForCols) {
|
||||
$colsDoc = New-Object System.Xml.XmlDocument
|
||||
$colsDoc.PreserveWhitespace = $true
|
||||
$colsDoc.Load($srcFormForCols)
|
||||
$colsInfo = Get-MainAttributeInfo $colsDoc.DocumentElement '\s+xmlns(?::\w+)?="[^"]*"'
|
||||
if ($colsInfo) {
|
||||
foreach ($m in [regex]::Matches($colsInfo.Xml, '(?s)<Columns>.*?</Columns>')) { $allTypeXmls += $m.Value }
|
||||
}
|
||||
}
|
||||
|
||||
$refTypes = Collect-ReferenceTypes $allTypeXmls
|
||||
Info " Reference types to borrow: $($refTypes.Count)"
|
||||
|
||||
@@ -1753,6 +2059,16 @@ function Build-BorrowedObjectXml {
|
||||
$sb.AppendLine("`t`t`t$($sourceProps['__TypeXml'])") | Out-Null
|
||||
}
|
||||
|
||||
# Свойства, от которых зависят стандартные поля (см. $script:typeGateProps / $script:typesWithOwners)
|
||||
foreach ($gp in @($script:typeGateProps[$typeName])) {
|
||||
if ($gp -and $sourceProps.ContainsKey($gp)) {
|
||||
$sb.AppendLine("`t`t`t<${gp}>$($sourceProps[$gp])</${gp}>") | Out-Null
|
||||
}
|
||||
}
|
||||
if ($sourceProps.ContainsKey("__OwnersXml")) {
|
||||
$sb.AppendLine("`t`t`t$($sourceProps['__OwnersXml'])") | Out-Null
|
||||
}
|
||||
|
||||
$sb.AppendLine("`t`t</Properties>") | Out-Null
|
||||
|
||||
# ChildObjects (for types that need it)
|
||||
@@ -1920,6 +2236,47 @@ foreach ($item in $items) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- 14b. Владельцы заимствованных справочников ---
|
||||
# Ссылка в <Owners> должна вести на объект, который в расширении есть: иначе платформа падает при
|
||||
# загрузке (проверено — access violation, не сообщение об ошибке). Конфигуратор владельца
|
||||
# заимствует (эталон Issue66Example7_1: вместе со справочником перенесён и его ПВХ-владелец).
|
||||
# Проход общий и повторяется, пока находятся новые: у владельца может быть свой владелец.
|
||||
$ownerPass = 0
|
||||
while ($true) {
|
||||
$ownerPass++
|
||||
if ($ownerPass -gt 10) { break }
|
||||
$newOwners = @()
|
||||
foreach ($shell in (Get-ChildItem -Path $extDir -Filter "*.xml" -Recurse -File)) {
|
||||
$shellText = [System.IO.File]::ReadAllText($shell.FullName)
|
||||
if ($shellText -notmatch '<Owners>') { continue }
|
||||
foreach ($om in [regex]::Matches($shellText, '<xr:Item[^>]*>(\w+)\.(\w+)</xr:Item>')) {
|
||||
$oType = $om.Groups[1].Value; $oName = $om.Groups[2].Value
|
||||
if (-not $childTypeDirMap.ContainsKey($oType)) { continue }
|
||||
if (Test-ObjectBorrowed $oType $oName) { continue }
|
||||
if ($newOwners | Where-Object { $_.T -eq $oType -and $_.N -eq $oName }) { continue }
|
||||
$newOwners += @{ T = $oType; N = $oName }
|
||||
}
|
||||
}
|
||||
if ($newOwners.Count -eq 0) { break }
|
||||
foreach ($ow in $newOwners) {
|
||||
$owSrcFile = Join-Path (Join-Path $cfgDir $childTypeDirMap[$ow.T]) "$($ow.N).xml"
|
||||
if (-not (Test-Path $owSrcFile)) {
|
||||
Warn " Владелец $($ow.T).$($ow.N) не найден в источнике — ссылка останется висячей"
|
||||
continue
|
||||
}
|
||||
$owSrc = Read-SourceObject $ow.T $ow.N
|
||||
$owXml = Build-BorrowedObjectXml $ow.T $ow.N $owSrc.Uuid $owSrc.Properties
|
||||
$owDir = Join-Path $extDir $childTypeDirMap[$ow.T]
|
||||
if (-not (Test-Path $owDir)) { New-Item -ItemType Directory -Path $owDir -Force | Out-Null }
|
||||
$owFile = Join-Path $owDir "$($ow.N).xml"
|
||||
$owEnc = New-Object System.Text.UTF8Encoding($true)
|
||||
[System.IO.File]::WriteAllText($owFile, $owXml, $owEnc)
|
||||
Add-ToChildObjects $ow.T $ow.N
|
||||
$script:borrowedFiles += $owFile
|
||||
Info " Auto-borrowed owner: $($ow.T).$($ow.N)"
|
||||
}
|
||||
}
|
||||
|
||||
# --- 15. Save modified Configuration.xml ---
|
||||
$settings = New-Object System.Xml.XmlWriterSettings
|
||||
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.20 — Borrow objects from configuration into extension (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||
# cfe-borrow v1.31 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -39,18 +39,35 @@ V8_NS = "http://v8.1c.ru/8.1/data/core"
|
||||
# Form data-binding tags (value = attribute path). A binding survives only if its root
|
||||
# attribute is borrowed into the form's <Attributes>; otherwise it must be stripped or the
|
||||
# platform rejects the form with "Неверный путь к данным" on load.
|
||||
FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath"]
|
||||
# RowPictureDataPath тоже путь к данным («Объект.Товары.РасхождениеЗаказ», «Список.DefaultPicture»),
|
||||
# а не индекс картинки: эталон Конфигуратора сохраняет его с заимствованным основным реквизитом
|
||||
# и выбрасывает без него — то же правило, что у остальных путей.
|
||||
FORM_BINDING_DATA_TAGS = ["DataPath", "TitleDataPath", "FooterDataPath", "HeaderDataPath", "MultipleValueDataPath", "MultipleValuePresentDataPath", "RowPictureDataPath"]
|
||||
# Picture-path binding tags (value = picture index path, never a data attribute) — always stripped in the skeleton.
|
||||
FORM_BINDING_PICTURE_TAGS = ["RowPictureDataPath", "MultipleValuePictureDataPath"]
|
||||
FORM_BINDING_PICTURE_TAGS = ["MultipleValuePictureDataPath"]
|
||||
|
||||
# id основного реквизита в заимствованной форме — как у Конфигуратора
|
||||
MAIN_ATTR_ID = "1000001"
|
||||
|
||||
# Виды дочерних объектов, которые заимствуются в оболочку поимённо (табличные части — отдельно)
|
||||
CHILD_OBJECT_KINDS = ("Attribute", "Dimension", "Resource", "AddressingAttribute")
|
||||
|
||||
# Прямые дети <Form>, которые в заимствованную форму не переносятся.
|
||||
# Структурные секции: AutoCommandBar и ChildItems забираются отдельно, остальные выбрасываются целиком.
|
||||
FORM_STRUCTURAL_SECTIONS = ("Events", "Attributes", "Commands", "Parameters", "CommandInterface")
|
||||
# Свойства формы, значение которых — имя реквизита формы (реквизиты не заимствуются, ссылка повиснет).
|
||||
FORM_ATTRIBUTE_REF_PROPS = ("ReportResult", "DetailsData", "VariantAppearance", "GroupList")
|
||||
|
||||
|
||||
def strip_form_bindings(xml, keep_objekt):
|
||||
def strip_form_bindings(xml, main_attr_name):
|
||||
"""Strip data-binding tags whose root attribute isn't borrowed.
|
||||
keep_objekt=True (BorrowMainAttribute): keep Объект.* data bindings, strip the rest.
|
||||
keep_objekt=False (default skeleton): strip all bindings. Picture-path tags are always stripped."""
|
||||
main_attr_name задан (BorrowMainAttribute): оставить привязки от его имени, остальные снять.
|
||||
Пусто (скелет без основного реквизита): снять все. Картиночные пути снимаются всегда."""
|
||||
for tag in FORM_BINDING_DATA_TAGS:
|
||||
if keep_objekt:
|
||||
xml = re.sub(rf'\s*<{tag}>(?!Объект\.)[^<]*</{tag}>', '', xml)
|
||||
if main_attr_name:
|
||||
# Оставить и «Список.Поле», и путь ровно на сам реквизит («Список» у таблицы формы)
|
||||
root = re.escape(main_attr_name)
|
||||
xml = re.sub(rf'\s*<{tag}>(?!{root}(\.|<))[^<]*</{tag}>', '', xml)
|
||||
else:
|
||||
xml = re.sub(rf'\s*<{tag}>[^<]*</{tag}>', '', xml)
|
||||
for tag in FORM_BINDING_PICTURE_TAGS:
|
||||
@@ -58,6 +75,124 @@ def strip_form_bindings(xml, keep_objekt):
|
||||
return xml
|
||||
|
||||
|
||||
DROPPED_LINKS = []
|
||||
|
||||
|
||||
def rewrite_choice_parameter_links(xml, attr_uuids, form_attr_ids, main_attr_name, main_attr_borrowed):
|
||||
"""Ссылки параметров выбора (<ChoiceParameterLinks>/<xr:Link>) — привязка особого рода: путь лежит
|
||||
в <xr:DataPath> и обычным стриппингом не снимается. Текстовое имя в расширении разрешается только
|
||||
если его корень объявлен в <Attributes> самой заимствованной формы; иначе платформа отвергает
|
||||
загрузку — «Неверный путь к полю - X». Реквизиты формы не заимствуются никогда, поэтому ссылка на
|
||||
них разрешима только через id: Конфигуратор подставляет id реквизита ИСХОДНОЙ формы (эталоны
|
||||
Issue66Example4/5/6, JR2433, JR2976, JR49904 — совпадение на шести расширениях). Именно id
|
||||
исходной, а не заимствованной: при заимствовании реквизиты перенумеровываются в 1000000+, а
|
||||
ссылка продолжает указывать в нумерацию базовой формы.
|
||||
Путь на основной реквизит («Объект.X») при заимствованном основном реквизите разрешается текстом
|
||||
и остаётся читаемым; без заимствования переводится в «<id>/0:<uuid реквизита объекта>».
|
||||
Реквизит, которого в источнике нет, недоступен и по uuid: такую связь вырезаем целиком."""
|
||||
if '<ChoiceParameterLinks>' not in xml:
|
||||
return xml
|
||||
|
||||
main_pat = re.escape(main_attr_name) if main_attr_name else None
|
||||
main_id = form_attr_ids.get(main_attr_name, "1") if main_attr_name else "1"
|
||||
|
||||
def repl(m):
|
||||
link = m.group(0)
|
||||
dp = re.search(r'<xr:DataPath[^>]*>([^<]+)</xr:DataPath>', link)
|
||||
if not dp:
|
||||
return link
|
||||
path = dp.group(1)
|
||||
|
||||
# Путь на основной реквизит формы
|
||||
if main_pat:
|
||||
mm = re.match('^' + main_pat + r'\.(.+)$', path)
|
||||
if mm:
|
||||
attr_name = mm.group(1)
|
||||
if main_attr_borrowed:
|
||||
# Реквизит объекта разрешается текстом и остаётся читаемым. Стандартное поле
|
||||
# («Объект.Owner», «Объект.Date») — нет: платформа отвергает «Неверный путь к данным».
|
||||
# Конфигуратор в этом случае оставляет ссылку на сам реквизит (эталон Issue66Example7_1).
|
||||
if attr_name in attr_uuids:
|
||||
return link
|
||||
return re.sub(r'(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)',
|
||||
lambda x: f"{x.group(1)}{main_id}{x.group(2)}", link)
|
||||
if attr_name in attr_uuids:
|
||||
return re.sub(r'(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)',
|
||||
lambda x: f"{x.group(1)}{main_id}/0:{attr_uuids[attr_name]}{x.group(2)}", link)
|
||||
return ''
|
||||
|
||||
# Односегментный путь на реквизит формы — только по id исходной формы
|
||||
if '.' not in path and path in form_attr_ids:
|
||||
return re.sub(r'(<xr:DataPath[^>]*>)[^<]+(</xr:DataPath>)',
|
||||
lambda x: f"{x.group(1)}{form_attr_ids[path]}{x.group(2)}", link)
|
||||
|
||||
# Уже непрозрачный путь (форма-источник сама из расширения) — не трогаем
|
||||
if re.match(r'^\d', path):
|
||||
return link
|
||||
|
||||
# С заимствованным основным реквизитом текстовый путь разрешается: элементы формы на месте,
|
||||
# а их данные доступны через основной реквизит. Конфигуратор такие пути и оставляет текстом
|
||||
# (эталон Issue66Example7_1: «Items.Товары.CurrentData.Характеристика» перенесён как есть).
|
||||
if main_attr_borrowed:
|
||||
return link
|
||||
|
||||
# Прочее текстом не разрешается: платформа отвергает загрузку «Неверный путь к полю».
|
||||
# Сюда попадают «Items.<Элемент>.CurrentData.<Поле>» — их кодировка непрозрачна и по
|
||||
# имеющимся эталонам не воспроизводима. Связь параметров выбора — удобство подбора, а не
|
||||
# данные: без неё форма заимствуется и работает, с ней — не грузится вовсе.
|
||||
DROPPED_LINKS.append(path)
|
||||
return ''
|
||||
|
||||
xml = re.sub(r'\s*<xr:Link>.*?</xr:Link>', repl, xml, flags=re.DOTALL)
|
||||
# Опустевший контейнер платформе не нужен
|
||||
xml = re.sub(r'\s*<ChoiceParameterLinks>\s*</ChoiceParameterLinks>', '', xml, flags=re.DOTALL)
|
||||
return xml
|
||||
|
||||
|
||||
def get_own_child_object_names(obj_file):
|
||||
"""Имена ПРЯМЫХ детей собственного <ChildObjects> объекта — для дедупа при повторном
|
||||
заимствовании. Текстом это не снять: regex «первый <ChildObjects> до первого </ChildObjects>»
|
||||
у объекта с табличными частями обрывается на закрытии первой ТЧ, забирает имена её колонок и
|
||||
теряет то, что идёт после неё."""
|
||||
names = set()
|
||||
try:
|
||||
tree = etree.parse(obj_file)
|
||||
except Exception:
|
||||
return names
|
||||
root = tree.getroot()
|
||||
obj_el = next((c for c in root if isinstance(c.tag, str)), None)
|
||||
if obj_el is None:
|
||||
return names
|
||||
child_objs = next((c for c in obj_el if isinstance(c.tag, str) and localname(c) == "ChildObjects"), None)
|
||||
if child_objs is None:
|
||||
return names
|
||||
for child in child_objs:
|
||||
if not isinstance(child.tag, str):
|
||||
continue
|
||||
props = next((p for p in child if isinstance(p.tag, str) and localname(p) == "Properties"), None)
|
||||
if props is None:
|
||||
continue
|
||||
nm = next((n for n in props if isinstance(n.tag, str) and localname(n) == "Name"), None)
|
||||
if nm is not None and nm.text:
|
||||
names.add(nm.text.strip())
|
||||
return names
|
||||
|
||||
|
||||
def insert_into_own_child_objects(text, content):
|
||||
"""Вставка в СОБСТВЕННЫЙ <ChildObjects> объекта. Свой контейнер закрывается в файле последним:
|
||||
объект в файле один, а вложенные <ChildObjects> табличных частей закрываются раньше. Замена по
|
||||
всем вхождениям раскидывала реквизиты по каждой ТЧ — ps1 рвал XML, py прятал ТЧ внутрь ТЧ."""
|
||||
close_idx = text.rfind("</ChildObjects>")
|
||||
if close_idx >= 0:
|
||||
return text[:close_idx] + content + "\r\n\t\t" + text[close_idx:]
|
||||
# Своего закрывающего тега нет — значит контейнер самозакрытый (детей у него нет, вложенных тоже)
|
||||
self_matches = list(re.finditer(r'<ChildObjects\s*/>', text))
|
||||
if not self_matches:
|
||||
return text
|
||||
m = self_matches[-1]
|
||||
return text[:m.start()] + f"<ChildObjects>{content}\r\n\t\t</ChildObjects>" + text[m.end():]
|
||||
|
||||
|
||||
def decode_numeric_entities(s):
|
||||
"""lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed
|
||||
elements where the PowerShell port writes literal characters. Normalize numeric refs
|
||||
@@ -202,6 +337,7 @@ GENERATED_TYPES = {
|
||||
],
|
||||
"AccountingRegister": [
|
||||
{"prefix": "AccountingRegisterRecord", "category": "Record"},
|
||||
{"prefix": "AccountingRegisterExtDimensions", "category": "ExtDimensions"},
|
||||
{"prefix": "AccountingRegisterManager", "category": "Manager"},
|
||||
{"prefix": "AccountingRegisterSelection", "category": "Selection"},
|
||||
{"prefix": "AccountingRegisterList", "category": "List"},
|
||||
@@ -215,6 +351,7 @@ GENERATED_TYPES = {
|
||||
{"prefix": "CalculationRegisterList", "category": "List"},
|
||||
{"prefix": "CalculationRegisterRecordSet", "category": "RecordSet"},
|
||||
{"prefix": "CalculationRegisterRecordKey", "category": "RecordKey"},
|
||||
{"prefix": "RecalculationsManager", "category": "Recalcs"},
|
||||
],
|
||||
"ChartOfAccounts": [
|
||||
{"prefix": "ChartOfAccountsObject", "category": "Object"},
|
||||
@@ -222,12 +359,15 @@ GENERATED_TYPES = {
|
||||
{"prefix": "ChartOfAccountsSelection", "category": "Selection"},
|
||||
{"prefix": "ChartOfAccountsList", "category": "List"},
|
||||
{"prefix": "ChartOfAccountsManager", "category": "Manager"},
|
||||
{"prefix": "ChartOfAccountsExtDimensionTypes", "category": "ExtDimensionTypes"},
|
||||
{"prefix": "ChartOfAccountsExtDimensionTypesRow", "category": "ExtDimensionTypesRow"},
|
||||
],
|
||||
"ChartOfCharacteristicTypes": [
|
||||
{"prefix": "ChartOfCharacteristicTypesObject", "category": "Object"},
|
||||
{"prefix": "ChartOfCharacteristicTypesRef", "category": "Ref"},
|
||||
{"prefix": "ChartOfCharacteristicTypesSelection", "category": "Selection"},
|
||||
{"prefix": "ChartOfCharacteristicTypesList", "category": "List"},
|
||||
{"prefix": "Characteristic", "category": "Characteristic"},
|
||||
{"prefix": "ChartOfCharacteristicTypesManager", "category": "Manager"},
|
||||
],
|
||||
"ChartOfCalculationTypes": [
|
||||
@@ -237,8 +377,11 @@ GENERATED_TYPES = {
|
||||
{"prefix": "ChartOfCalculationTypesList", "category": "List"},
|
||||
{"prefix": "ChartOfCalculationTypesManager", "category": "Manager"},
|
||||
{"prefix": "DisplacingCalculationTypes", "category": "DisplacingCalculationTypes"},
|
||||
{"prefix": "DisplacingCalculationTypesRow", "category": "DisplacingCalculationTypesRow"},
|
||||
{"prefix": "BaseCalculationTypes", "category": "BaseCalculationTypes"},
|
||||
{"prefix": "BaseCalculationTypesRow", "category": "BaseCalculationTypesRow"},
|
||||
{"prefix": "LeadingCalculationTypes", "category": "LeadingCalculationTypes"},
|
||||
{"prefix": "LeadingCalculationTypesRow", "category": "LeadingCalculationTypesRow"},
|
||||
],
|
||||
"BusinessProcess": [
|
||||
{"prefix": "BusinessProcessObject", "category": "Object"},
|
||||
@@ -246,6 +389,7 @@ GENERATED_TYPES = {
|
||||
{"prefix": "BusinessProcessSelection", "category": "Selection"},
|
||||
{"prefix": "BusinessProcessList", "category": "List"},
|
||||
{"prefix": "BusinessProcessManager", "category": "Manager"},
|
||||
{"prefix": "BusinessProcessRoutePointRef", "category": "RoutePointRef"},
|
||||
],
|
||||
"Task": [
|
||||
{"prefix": "TaskObject", "category": "Object"},
|
||||
@@ -311,6 +455,16 @@ TYPES_WITH_CHILD_OBJECTS = [
|
||||
|
||||
COMMON_MODULE_PROPS = ["Global", "ClientManagedApplication", "Server", "ExternalConnection", "ClientOrdinaryApplication", "ServerCall"]
|
||||
|
||||
# Свойства объекта, от которых зависит существование стандартного поля: без них платформа
|
||||
# отвергает загрузку — «Неверный путь к данным». Конфигуратор переносит ровно их (эталоны
|
||||
# Issue66Example7_1 и Issue66Example2). Проверено сплошным прогоном по типам: у регистра
|
||||
# сведений без InformationRegisterPeriodicity не разрешается «Запись.Period».
|
||||
TYPE_GATE_PROPS = {
|
||||
"InformationRegister": ["InformationRegisterPeriodicity", "WriteMode"],
|
||||
}
|
||||
# Владельцы справочника — список <xr:Item>, а не скаляр: переносится фрагментом, как __TypeXml
|
||||
TYPES_WITH_OWNERS = ("Catalog", "ChartOfCharacteristicTypes")
|
||||
|
||||
# Standard system fields to skip when collecting DataPath references
|
||||
STANDARD_FIELDS = [
|
||||
"Code", "Description", "Ref", "Parent", "DeletionMark",
|
||||
@@ -582,6 +736,45 @@ def main():
|
||||
borrowed_files = []
|
||||
|
||||
# --- Helper functions ---
|
||||
def get_source_attribute_uuids(type_name, obj_name):
|
||||
"""Имена реквизитов исходного объекта → uuid. Нужны для непрозрачной формы пути в ссылках
|
||||
параметров выбора (см. rewrite_choice_parameter_links)."""
|
||||
result = {}
|
||||
dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
|
||||
if not dir_name:
|
||||
return result
|
||||
src_file = os.path.join(cfg_dir, dir_name, f"{obj_name}.xml")
|
||||
if not os.path.isfile(src_file):
|
||||
return result
|
||||
|
||||
tree = etree.parse(src_file, etree.XMLParser(remove_blank_text=True))
|
||||
obj_el = None
|
||||
for c in tree.getroot():
|
||||
if isinstance(c.tag, str):
|
||||
obj_el = c
|
||||
break
|
||||
if obj_el is None:
|
||||
return result
|
||||
for child in obj_el:
|
||||
if not isinstance(child.tag, str) or localname(child) != "ChildObjects":
|
||||
continue
|
||||
for sub in child:
|
||||
if not isinstance(sub.tag, str) or localname(sub) not in ("Attribute", "TabularSection"):
|
||||
continue
|
||||
uuid_val = sub.get("uuid")
|
||||
name_val = None
|
||||
for props in sub:
|
||||
if isinstance(props.tag, str) and localname(props) == "Properties":
|
||||
for prop in props:
|
||||
if isinstance(prop.tag, str) and localname(prop) == "Name":
|
||||
name_val = (prop.text or "").strip()
|
||||
break
|
||||
break
|
||||
if uuid_val and name_val:
|
||||
result[name_val] = uuid_val
|
||||
break
|
||||
return result
|
||||
|
||||
def read_source_object(type_name, obj_name):
|
||||
dir_name = CHILD_TYPE_DIR_MAP.get(type_name)
|
||||
if not dir_name:
|
||||
@@ -625,6 +818,17 @@ def main():
|
||||
if type_node is not None:
|
||||
type_xml = etree.tostring(type_node, encoding="unicode")
|
||||
src_props["__TypeXml"] = re.sub(r'\s+xmlns(?::\w+)?="[^"]*"', '', type_xml)
|
||||
# Владельцы: стандартное поле «Owner» появляется у справочника, только если задан Owners
|
||||
if type_name in TYPES_WITH_OWNERS:
|
||||
owners_node = props_node.find(f"{{{MD_NS}}}Owners")
|
||||
if owners_node is not None and len(owners_node):
|
||||
owners_xml = etree.tostring(owners_node, encoding="unicode")
|
||||
src_props["__OwnersXml"] = re.sub(r'\s+xmlns(?::\w+)?="[^"]*"', '', owners_xml)
|
||||
# Скалярные свойства, включающие стандартные поля своего типа
|
||||
for gp in TYPE_GATE_PROPS.get(type_name, []):
|
||||
gp_node = props_node.find(f"{{{MD_NS}}}{gp}")
|
||||
if gp_node is not None:
|
||||
src_props[gp] = (gp_node.text or "").strip()
|
||||
|
||||
# Whether the platform emits <ChildObjects> for this type — the source object is the ground truth
|
||||
src_props["__HasChildObjects"] = src_el.find(f"{{{MD_NS}}}ChildObjects") is not None
|
||||
@@ -703,6 +907,13 @@ def main():
|
||||
if type_name == "DefinedType" and "__TypeXml" in source_props:
|
||||
lines.append(f"\t\t\t{source_props['__TypeXml']}")
|
||||
|
||||
# Свойства, от которых зависят стандартные поля (см. TYPE_GATE_PROPS / TYPES_WITH_OWNERS)
|
||||
for gp in TYPE_GATE_PROPS.get(type_name, []):
|
||||
if gp in source_props:
|
||||
lines.append(f"\t\t\t<{gp}>{source_props[gp]}</{gp}>")
|
||||
if "__OwnersXml" in source_props:
|
||||
lines.append(f"\t\t\t{source_props['__OwnersXml']}")
|
||||
|
||||
lines.append("\t\t</Properties>")
|
||||
|
||||
if source_props.get("__HasChildObjects") or type_name in TYPES_WITH_CHILD_OBJECTS:
|
||||
@@ -806,8 +1017,59 @@ def main():
|
||||
save_xml_bom(obj_tree, obj_file)
|
||||
info(f" Registered form in: {obj_file}")
|
||||
|
||||
# --- 11b1. Основной реквизит исходной формы ---
|
||||
# Переносится ЦЕЛИКОМ, а не собирается из констант: имя, тип и состав детей зависят от вида формы.
|
||||
# У формы объекта это «Объект»/<Тип>Object + SavedData/UseAlways/Columns, у формы списка —
|
||||
# «Список»/DynamicList + Settings, у формы записи регистра — «Запись»/RecordManager + SavedData.
|
||||
# Синтез фиксированного набора давал для необъектных форм «Исключение XDTO» при загрузке.
|
||||
# Конфигуратор меняет у скопированного реквизита только id (эталоны Issue64UtB, Issue66Example2).
|
||||
def get_form_attribute_ids(form_el):
|
||||
"""Имена реквизитов ИСХОДНОЙ формы → их id. Ссылки параметров выбора адресуют реквизит формы
|
||||
именно по id базовой формы (см. rewrite_choice_parameter_links)."""
|
||||
result = {}
|
||||
for child in form_el:
|
||||
if not isinstance(child.tag, str) or localname(child) != "Attributes":
|
||||
continue
|
||||
for a in child:
|
||||
if not isinstance(a.tag, str) or localname(a) != "Attribute":
|
||||
continue
|
||||
nm, aid = a.get("name"), a.get("id")
|
||||
if nm and aid:
|
||||
result[nm] = aid
|
||||
break
|
||||
return result
|
||||
|
||||
def get_main_attribute_info(form_el, ns_strip_pattern):
|
||||
main_attr = None
|
||||
for child in form_el:
|
||||
if not isinstance(child.tag, str) or localname(child) != "Attributes":
|
||||
continue
|
||||
for attr in child:
|
||||
if not isinstance(attr.tag, str) or localname(attr) != "Attribute":
|
||||
continue
|
||||
for sub in attr:
|
||||
if isinstance(sub.tag, str) and localname(sub) == "MainAttribute" and (sub.text or "").strip() == "true":
|
||||
main_attr = attr
|
||||
break
|
||||
if main_attr is not None:
|
||||
break
|
||||
break
|
||||
if main_attr is None:
|
||||
return None
|
||||
# with_tail=False: хвостовой пробельный узел — часть родителя, а не секции; иначе в
|
||||
# вывод попадают пустые строки, которых нет у PS (OuterXml хвост не включает).
|
||||
xml = decode_numeric_entities(etree.tostring(main_attr, encoding="unicode", with_tail=False))
|
||||
xml = ns_strip_pattern.sub("", xml)
|
||||
# id заменяется только в открывающем теге самого реквизита — у вложенных элементов свои
|
||||
xml = re.sub(r'^(<Attribute\s[^>]*?)id="[^"]*"', lambda m: m.group(1) + f'id="{MAIN_ATTR_ID}"', xml)
|
||||
return {"Name": main_attr.get("name"), "Xml": xml}
|
||||
|
||||
# --- 11b. Collect DataPath references from source Form.xml ---
|
||||
def collect_form_data_paths(form_xml_path):
|
||||
def collect_form_data_paths(form_xml_path, main_attr_name):
|
||||
# Корень путей — имя основного реквизита формы: «Объект» у формы объекта, «Список» у формы
|
||||
# списка, «Запись» у формы записи регистра. Зашитый «Объект» не находил ничего у необъектных
|
||||
# форм, и в оболочку не заимствовалось ни одного дочернего объекта.
|
||||
root = re.escape(main_attr_name)
|
||||
with open(form_xml_path, "r", encoding="utf-8-sig") as fh:
|
||||
content = fh.read()
|
||||
|
||||
@@ -817,7 +1079,7 @@ def main():
|
||||
# Scan every data-binding tag (DataPath/TitleDataPath/FooterDataPath/HeaderDataPath/MultipleValue*)
|
||||
# for Объект.* references — picture-path tags carry picture indices, not data attributes.
|
||||
for tag in FORM_BINDING_DATA_TAGS:
|
||||
for m in re.finditer(r'<' + tag + r'>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</' + tag + r'>', content):
|
||||
for m in re.finditer(r'<' + tag + r'>[^<]*\b' + root + r'\.(\w+(?:\.\w+)*)</' + tag + r'>', content):
|
||||
path = m.group(1)
|
||||
segments = path.split(".")
|
||||
seg0 = segments[0]
|
||||
@@ -833,7 +1095,7 @@ def main():
|
||||
|
||||
# Also scan <Field>Объект.X</Field> — object attributes referenced by filter/conditional-appearance
|
||||
# fields (and dynamic lists), not via a *DataPath binding (e.g. УдалитьЮрФизЛицо). Designer borrows these too.
|
||||
for m in re.finditer(r'<Field>[^<]*\bОбъект\.(\w+(?:\.\w+)*)</Field>', content):
|
||||
for m in re.finditer(r'<Field>[^<]*\b' + root + r'\.(\w+(?:\.\w+)*)</Field>', content):
|
||||
path = m.group(1)
|
||||
segments = path.split(".")
|
||||
seg0 = segments[0]
|
||||
@@ -847,6 +1109,27 @@ def main():
|
||||
seg2 = segments[2] if len(segments) >= 3 else None
|
||||
deep_paths.append({"ObjectAttr": seg0, "SubAttr": seg1, "SubSubAttr": seg2})
|
||||
|
||||
# Also scan <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в
|
||||
# самой форме (напр. Объект.Товары.Артикул). Такая ТЧ может больше нигде на форме не встречаться,
|
||||
# и без её заимствования платформа отвергает форму: «Неверный путь к данным».
|
||||
for m in re.finditer(r'<AdditionalColumns table="' + root + r'\.(\w+)"', content):
|
||||
seg0 = m.group(1)
|
||||
if seg0 in STANDARD_FIELDS:
|
||||
continue
|
||||
first_level[seg0] = True
|
||||
|
||||
# Текст запроса динамического списка — такое же место ссылки на реквизиты объекта, как DataPath.
|
||||
# Конфигуратор заимствует всё, что упомянуто в запросе: на эталоне Issue66Example2 это 21 из 27
|
||||
# дочерних объектов, совпадение с ним точное в обе стороны. У списка без ручного запроса
|
||||
# (<QueryText> нет) заимствуется только видимое на форме — эталон Issue66Example3.
|
||||
# Разбирать язык запросов не нужно: имена-кандидаты отфильтрует resolve_source_attributes по
|
||||
# реальному составу объекта, поэтому лишние слова из запроса безвредны.
|
||||
for qm in re.finditer(r'(?s)<QueryText>(.*?)</QueryText>', content):
|
||||
for w in re.finditer(r'\w+', qm.group(1)):
|
||||
if w.group(0) in STANDARD_FIELDS:
|
||||
continue
|
||||
first_level[w.group(0)] = True
|
||||
|
||||
# Deduplicate deep paths
|
||||
seen = set()
|
||||
unique_deep = []
|
||||
@@ -894,7 +1177,11 @@ def main():
|
||||
continue
|
||||
ln = localname(child)
|
||||
|
||||
if ln == "Attribute":
|
||||
# Реквизит объекта, измерение и ресурс регистра — один и тот же вид дочернего объекта с
|
||||
# точки зрения заимствования, различается только имя элемента. Конфигуратор переносит их
|
||||
# своим видом (эталон Issue66Example2: у регистра <Dimension> x3 и <Resource>), поэтому вид
|
||||
# запоминается и выпускается как есть — иначе измерение уехало бы в файл как <Attribute>.
|
||||
if ln in CHILD_OBJECT_KINDS:
|
||||
name_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
if name_node is None:
|
||||
continue
|
||||
@@ -909,7 +1196,7 @@ def main():
|
||||
type_xml = etree.tostring(type_node, encoding="unicode")
|
||||
type_xml = ns_strip.sub("", type_xml)
|
||||
|
||||
attrs.append({"Name": attr_name, "Uuid": attr_uuid, "TypeXml": type_xml})
|
||||
attrs.append({"Name": attr_name, "Uuid": attr_uuid, "TypeXml": type_xml, "Kind": ln})
|
||||
|
||||
elif ln == "TabularSection":
|
||||
name_node = child.find(f"{{{MD_NS}}}Properties/{{{MD_NS}}}Name")
|
||||
@@ -969,10 +1256,18 @@ def main():
|
||||
extra_props = {}
|
||||
props_node = src_el.find(f"{{{MD_NS}}}Properties")
|
||||
if props_node is not None:
|
||||
# NumberPeriodicity сюда НЕ входит: платформа считает его модификацией настроек нумерации и
|
||||
# тогда требует объявить ещё и <Numerator/>, иначе /UpdateDBCfg падает — «отключать
|
||||
# контролируемость свойства "Нумератор" недопустимо». Конфигуратор его не переносит
|
||||
# (эталон заимствования документа: NumberType/NumberLength/NumberAllowedLength и всё).
|
||||
# Загрузку это не ломает, ошибка вылезает только на обновлении конфигурации БД.
|
||||
# FoldersOnTop сюда НЕ входит: платформа его у заимствованной оболочки не хранит — при
|
||||
# загрузке молча выбрасывает (проверено раундтрипом: записали, выгрузили обратно, свойства
|
||||
# нет). Конфигуратор его тоже не переносит. Остальные из списка сохраняются.
|
||||
props_to_extract = [
|
||||
"Hierarchical", "FoldersOnTop", "CodeLength", "DescriptionLength",
|
||||
"Hierarchical", "CodeLength", "DescriptionLength",
|
||||
"CodeType", "CodeAllowedLength", "NumberType", "NumberLength",
|
||||
"NumberAllowedLength", "NumberPeriodicity",
|
||||
"NumberAllowedLength",
|
||||
]
|
||||
for p_name in props_to_extract:
|
||||
p_node = props_node.find(f"{{{MD_NS}}}{p_name}")
|
||||
@@ -982,10 +1277,10 @@ def main():
|
||||
return {"Attributes": attrs, "TabularSections": tab_sections, "ExtraProps": extra_props}
|
||||
|
||||
# --- 11d. Build adopted attribute XML ---
|
||||
def build_adopted_attribute_xml(name, source_uuid, type_xml, indent):
|
||||
def build_adopted_attribute_xml(name, source_uuid, type_xml, indent, kind="Attribute"):
|
||||
new_uuid_val = new_guid()
|
||||
lines = [
|
||||
f'{indent}<Attribute uuid="{new_uuid_val}">',
|
||||
f'{indent}<{kind} uuid="{new_uuid_val}">',
|
||||
f'{indent}\t<InternalInfo/>',
|
||||
f'{indent}\t<Properties>',
|
||||
f'{indent}\t\t<ObjectBelonging>Adopted</ObjectBelonging>',
|
||||
@@ -994,7 +1289,7 @@ def main():
|
||||
f'{indent}\t\t<ExtendedConfigurationObject>{source_uuid}</ExtendedConfigurationObject>',
|
||||
f'{indent}\t\t{type_xml}',
|
||||
f'{indent}\t</Properties>',
|
||||
f'{indent}</Attribute>',
|
||||
f'{indent}</{kind}>',
|
||||
]
|
||||
return "\n".join(lines)
|
||||
|
||||
@@ -1069,25 +1364,19 @@ def main():
|
||||
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
|
||||
obj_content = fh.read()
|
||||
|
||||
# Collect existing attribute names for dedup (text-based)
|
||||
existing_names = set()
|
||||
for m in re.finditer(r'<Name>(\w+)</Name>', obj_content):
|
||||
existing_names.add(m.group(1))
|
||||
# Collect existing names for dedup — только прямые дети своего ChildObjects
|
||||
existing_names = get_own_child_object_names(obj_file)
|
||||
|
||||
all_attr_xml = ""
|
||||
added = 0
|
||||
for attr in attrs_to_add:
|
||||
if attr["Name"] in existing_names:
|
||||
continue
|
||||
all_attr_xml += "\r\n" + build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t")
|
||||
all_attr_xml += "\r\n" + build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t", attr.get("Kind", "Attribute"))
|
||||
added += 1
|
||||
|
||||
if added > 0:
|
||||
# Insert attributes — handle both <ChildObjects/> and <ChildObjects>...</ChildObjects>
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>{all_attr_xml}\r\n\t\t</ChildObjects>", obj_content)
|
||||
else:
|
||||
obj_content = obj_content.replace("</ChildObjects>", f"{all_attr_xml}\r\n\t\t</ChildObjects>")
|
||||
obj_content = insert_into_own_child_objects(obj_content, all_attr_xml)
|
||||
write_utf8_bom(obj_file, obj_content)
|
||||
info(f" Merged {added} attribute(s) into: {obj_file}")
|
||||
|
||||
@@ -1104,7 +1393,13 @@ def main():
|
||||
if not os.path.isfile(src_form_xml_path):
|
||||
print(f"Source Form.xml not found: {src_form_xml_path}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
dp = collect_form_data_paths(src_form_xml_path)
|
||||
# Имя основного реквизита исходной формы — корень путей, которые надо собрать
|
||||
dp_ns_strip = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||
dp_info = get_main_attribute_info(etree.parse(src_form_xml_path).getroot(), dp_ns_strip)
|
||||
if dp_info is None:
|
||||
warn(" У формы нет основного реквизита — заимствовать нечего")
|
||||
return
|
||||
dp = collect_form_data_paths(src_form_xml_path, dp_info["Name"])
|
||||
first_level_names = dp["FirstLevel"]
|
||||
deep_paths = dp["DeepPaths"]
|
||||
info(f" Collected {len(first_level_names)} first-level DataPath references, {len(deep_paths)} deep paths")
|
||||
@@ -1131,18 +1426,14 @@ def main():
|
||||
obj_content = fh.read()
|
||||
|
||||
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||
existing_child_names = set()
|
||||
m_co = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||
if m_co:
|
||||
for nm in re.findall(r'<Name>(\w+)</Name>', m_co.group(1)):
|
||||
existing_child_names.add(nm)
|
||||
existing_child_names = get_own_child_object_names(obj_file)
|
||||
insert_attrs = [a for a in src_attrs if a["Name"] not in existing_child_names]
|
||||
insert_ts = [t for t in src_ts if t["Name"] not in existing_child_names]
|
||||
|
||||
# Generate full object XML with attributes and TS
|
||||
content_parts = []
|
||||
for attr in insert_attrs:
|
||||
content_parts.append(build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t"))
|
||||
content_parts.append(build_adopted_attribute_xml(attr["Name"], attr["Uuid"], attr["TypeXml"], "\t\t\t", attr.get("Kind", "Attribute")))
|
||||
for ts in insert_ts:
|
||||
content_parts.append(build_adopted_tabular_section_xml(ts["Name"], ts["Uuid"], ts["GeneratedTypes"], ts["Attributes"], "\t\t\t"))
|
||||
adopted_content = "\n".join(content_parts).rstrip()
|
||||
@@ -1161,19 +1452,9 @@ def main():
|
||||
if props_xml:
|
||||
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}", 1)
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
# Добавить заимствованное содержимое в ChildObjects объекта (там уже может лежать <Form>)
|
||||
if adopted_content:
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>\r\n{adopted_content}\r\n\t\t</ChildObjects>", obj_content)
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elif re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content):
|
||||
m = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||
existing_inner = m.group(1)
|
||||
obj_content = obj_content.replace(
|
||||
f"<ChildObjects>{existing_inner}</ChildObjects>",
|
||||
f"<ChildObjects>{existing_inner}\r\n{adopted_content}\r\n\t\t</ChildObjects>"
|
||||
)
|
||||
obj_content = insert_into_own_child_objects(obj_content, f"\r\n{adopted_content}")
|
||||
|
||||
write_utf8_bom(obj_file, obj_content)
|
||||
info(f" Enriched object: {obj_file}")
|
||||
@@ -1185,6 +1466,18 @@ def main():
|
||||
for ts in src_ts:
|
||||
for tsa in ts["Attributes"]:
|
||||
all_type_xmls.append(tsa["TypeXml"])
|
||||
# Типы из <Columns> основного реквизита формы: колонку мы переносим (borrow_form), значит и её
|
||||
# тип должен быть заимствован — иначе колонка ссылается на DefinedType/справочник, которого в
|
||||
# расширении нет. Конфигуратор поступает так же (эталон: DefinedTypes/Артикул при заимствовании
|
||||
# формы заказа поставщику).
|
||||
src_form_for_cols = os.path.join(cfg_dir, dir_name, obj_name, "Forms", form_name, "Ext", "Form.xml")
|
||||
if os.path.isfile(src_form_for_cols):
|
||||
cols_tree = etree.parse(src_form_for_cols)
|
||||
cols_ns_strip = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||
cols_info = get_main_attribute_info(cols_tree.getroot(), cols_ns_strip)
|
||||
if cols_info:
|
||||
all_type_xmls.extend(re.findall(r'(?s)<Columns>.*?</Columns>', cols_info["Xml"]))
|
||||
|
||||
ref_types = collect_reference_types(all_type_xmls)
|
||||
info(f" Reference types to borrow: {len(ref_types)}")
|
||||
|
||||
@@ -1363,37 +1656,72 @@ def main():
|
||||
# (e.g. a 2.13 form inside a 2.17 extension). The platform upgrades the form to the root version.
|
||||
form_version = format_version
|
||||
|
||||
# Секции формы отбираются по имени, а не по позиции: свойства лежат и до, и после
|
||||
# <CommandSet> (корпусная проверка: у всех 794 форм документов ERP с CommandSet он стоит
|
||||
# раньше AutoCommandBar, а AutoTime/UsePostingMode/RepostOnWrite — после него). Позиционная
|
||||
# отсечка теряла весь хвост, и платформа молча подставляла дефолты вместо потерянных свойств.
|
||||
src_auto_cmd = None
|
||||
form_props = []
|
||||
reached_visual = False
|
||||
for fc in src_form_el:
|
||||
if not isinstance(fc.tag, str):
|
||||
continue
|
||||
ln = localname(fc)
|
||||
if ln == "AutoCommandBar" and src_auto_cmd is None:
|
||||
reached_visual = True
|
||||
src_auto_cmd = fc
|
||||
continue
|
||||
if ln in ("ChildItems", "Events", "Attributes", "Commands", "Parameters", "CommandSet"):
|
||||
reached_visual = True
|
||||
# ChildItems забирается отдельным поиском ниже
|
||||
if ln == "ChildItems":
|
||||
continue
|
||||
if not reached_visual:
|
||||
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
|
||||
form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode")))
|
||||
# Структурные секции: в расширении их содержимое недействительно (обработчики, команды и
|
||||
# параметры базовой формы, ссылки командного интерфейса на команды базовой конфигурации).
|
||||
if ln in FORM_STRUCTURAL_SECTIONS:
|
||||
continue
|
||||
# Свойства, значение которых — имя реквизита формы. Реквизиты в заимствованную форму не
|
||||
# переносятся, поэтому Конфигуратор такие свойства выбрасывает (проверено на форме отчёта:
|
||||
# ReportResult и DetailsData выброшены, CustomSettingsFolder — имя элемента — сохранён).
|
||||
if ln in FORM_ATTRIBUTE_REF_PROPS:
|
||||
continue
|
||||
# with_tail=False — хвостовой пробел принадлежит родителю; с ним в вывод попадали
|
||||
# пустые строки, которых нет у PS-порта (OuterXml хвост не включает).
|
||||
form_props.append(decode_numeric_entities(etree.tostring(fc, encoding="unicode", with_tail=False)))
|
||||
|
||||
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
|
||||
|
||||
# uuid реквизитов объекта — только для формы без заимствованного основного реквизита:
|
||||
# там ссылки параметров выбора переводятся на непрозрачную форму пути
|
||||
# Имя основного реквизита источника нужно в обоих режимах: по нему опознаётся корень путей
|
||||
# в ссылках параметров выбора. А main_attr_name управляет вырезанием привязок и потому
|
||||
# остаётся пустым в скелетном режиме — там привязки снимаются все.
|
||||
src_main_info = get_main_attribute_info(src_form_el, ns_strip_pattern)
|
||||
src_main_attr_name = src_main_info["Name"] if src_main_info else ""
|
||||
form_attr_ids = get_form_attribute_ids(src_form_el)
|
||||
|
||||
# Основной реквизит исходной формы: его имя — корень путей к данным, которые нужно сохранить
|
||||
# («Объект.» у формы объекта, «Список.» у формы списка, «Запись.» у формы записи регистра)
|
||||
main_attr_info = src_main_info if borrow_main_attr else None
|
||||
# Имена реквизитов объекта нужны в обоих режимах: без заимствования — чтобы построить
|
||||
# непрозрачный путь, с заимствованием — чтобы отличить реквизит (разрешается текстом) от
|
||||
# стандартного поля (не разрешается)
|
||||
src_attr_uuids = get_source_attribute_uuids(type_name, obj_name)
|
||||
main_attr_name = main_attr_info["Name"] if main_attr_info else ""
|
||||
if borrow_main_attr and main_attr_info is None:
|
||||
warn(" У формы нет основного реквизита — -BorrowMainAttribute проигнорирован")
|
||||
|
||||
# AutoCommandBar: keep ChildItems (buttons with CommandName->0), Autofill->false
|
||||
auto_cmd_xml = ""
|
||||
if src_auto_cmd is not None:
|
||||
auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode"))
|
||||
auto_cmd_xml = decode_numeric_entities(etree.tostring(src_auto_cmd, encoding="unicode", with_tail=False))
|
||||
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
|
||||
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
|
||||
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
|
||||
# Strip ExcludedCommand (references to standard commands invalid in extension)
|
||||
auto_cmd_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', auto_cmd_xml)
|
||||
# Вложенный CommandSet выбрасывается целиком, а не опустошается: Конфигуратор в заимствованной
|
||||
# форме оставляет только корневой (тот идёт свойством формы, здесь его нет).
|
||||
auto_cmd_xml = re.sub(r'(?s)\s*<CommandSet>.*?</CommandSet>', '', auto_cmd_xml)
|
||||
auto_cmd_xml = re.sub(r'\s*<CommandSet/>', '', auto_cmd_xml)
|
||||
# Strip data-binding tags whose root attribute isn't borrowed
|
||||
auto_cmd_xml = strip_form_bindings(auto_cmd_xml, borrow_main_attr)
|
||||
auto_cmd_xml = strip_form_bindings(auto_cmd_xml, main_attr_name)
|
||||
auto_cmd_xml = rewrite_choice_parameter_links(
|
||||
auto_cmd_xml, src_attr_uuids, form_attr_ids, src_main_attr_name, main_attr_info is not None)
|
||||
|
||||
# ChildItems: copy full tree, clean up base-config references
|
||||
child_items_xml = ""
|
||||
@@ -1404,14 +1732,17 @@ def main():
|
||||
break
|
||||
|
||||
if src_child_items is not None:
|
||||
child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode"))
|
||||
child_items_xml = decode_numeric_entities(etree.tostring(src_child_items, encoding="unicode", with_tail=False))
|
||||
child_items_xml = ns_strip_pattern.sub("", child_items_xml)
|
||||
# Replace all CommandName values with 0
|
||||
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
|
||||
# Strip data-binding tags whose root attribute isn't borrowed
|
||||
child_items_xml = strip_form_bindings(child_items_xml, borrow_main_attr)
|
||||
# Strip ExcludedCommand in nested AutoCommandBars (references to standard commands invalid in extension)
|
||||
child_items_xml = re.sub(r'\s*<ExcludedCommand>[^<]*</ExcludedCommand>', '', child_items_xml)
|
||||
child_items_xml = strip_form_bindings(child_items_xml, main_attr_name)
|
||||
child_items_xml = rewrite_choice_parameter_links(
|
||||
child_items_xml, src_attr_uuids, form_attr_ids, src_main_attr_name, main_attr_info is not None)
|
||||
# Вложенные CommandSet (у таблиц, полей табличного документа и т.п.) — целиком, см. выше
|
||||
child_items_xml = re.sub(r'(?s)\s*<CommandSet>.*?</CommandSet>', '', child_items_xml)
|
||||
child_items_xml = re.sub(r'\s*<CommandSet/>', '', child_items_xml)
|
||||
# Strip TypeLink blocks with human-readable DataPath (Items.XXX)
|
||||
child_items_xml = re.sub(r'\s*<TypeLink>\s*<xr:DataPath>Items\.[^<]*</xr:DataPath>.*?</TypeLink>', '', child_items_xml, flags=re.DOTALL)
|
||||
# Strip element-level Events
|
||||
@@ -1594,20 +1925,9 @@ def main():
|
||||
parts.append(f"\t{child_items_xml}\r\n")
|
||||
|
||||
# Attributes: empty or with MainAttribute when borrow_main_attr
|
||||
if borrow_main_attr:
|
||||
obj_type_prefix = ""
|
||||
gt_list = GENERATED_TYPES.get(type_name, [])
|
||||
for g in gt_list:
|
||||
if g["category"] == "Object":
|
||||
obj_type_prefix = g["prefix"]
|
||||
break
|
||||
main_attr_type = f"cfg:{obj_type_prefix}.{obj_name}"
|
||||
if borrow_main_attr and main_attr_info:
|
||||
parts.append("\t<Attributes>\r\n")
|
||||
parts.append('\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1000001">\r\n')
|
||||
parts.append(f"\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
|
||||
parts.append("\t\t\t<MainAttribute>true</MainAttribute>\r\n")
|
||||
parts.append("\t\t\t<SavedData>true</SavedData>\r\n")
|
||||
parts.append("\t\t</Attribute>\r\n")
|
||||
parts.append(f"\t\t{main_attr_info['Xml']}\r\n")
|
||||
parts.append("\t</Attributes>")
|
||||
else:
|
||||
parts.append("\t<Attributes/>")
|
||||
@@ -1637,13 +1957,12 @@ def main():
|
||||
parts.append("\r\n")
|
||||
|
||||
# BaseForm Attributes: same as main section
|
||||
if borrow_main_attr:
|
||||
if borrow_main_attr and main_attr_info:
|
||||
parts.append("\t\t<Attributes>\r\n")
|
||||
parts.append('\t\t\t<Attribute name="\u041e\u0431\u044a\u0435\u043a\u0442" id="1000001">\r\n')
|
||||
parts.append(f"\t\t\t\t<Type><v8:Type>{main_attr_type}</v8:Type></Type>\r\n")
|
||||
parts.append("\t\t\t\t<MainAttribute>true</MainAttribute>\r\n")
|
||||
parts.append("\t\t\t\t<SavedData>true</SavedData>\r\n")
|
||||
parts.append("\t\t\t</Attribute>\r\n")
|
||||
# В BaseForm та же секция на уровень глубже — приём переиндентации тот же, что у ChildItems
|
||||
for li, line in enumerate(main_attr_info['Xml'].split('\n')):
|
||||
parts.append(f"\t\t\t{line}" if li == 0 else f"\t{line}")
|
||||
parts.append("\r\n")
|
||||
parts.append("\t\t</Attributes>")
|
||||
else:
|
||||
parts.append("\t\t<Attributes/>")
|
||||
@@ -1656,6 +1975,10 @@ def main():
|
||||
form_xml_file = os.path.join(form_xml_dir, "Form.xml")
|
||||
write_xml_file(form_xml_file, "".join(parts))
|
||||
info(f" Created: {form_xml_file}")
|
||||
if DROPPED_LINKS:
|
||||
uniq = sorted(set(DROPPED_LINKS))
|
||||
warn(f" Вырезано связей параметров выбора: {len(uniq)} — путь не разрешается в расширении: {', '.join(uniq)}")
|
||||
DROPPED_LINKS.clear()
|
||||
|
||||
# 6. Create empty Module.bsl — but NEVER overwrite an existing one (re-borrow must
|
||||
# not clobber user code added to the form module).
|
||||
@@ -1773,6 +2096,47 @@ def main():
|
||||
borrowed_files.append(target_file)
|
||||
borrowed_count += 1
|
||||
|
||||
# --- Владельцы заимствованных справочников ---
|
||||
# Ссылка в <Owners> должна вести на объект, который в расширении есть: иначе платформа падает
|
||||
# при загрузке (проверено — access violation, не сообщение об ошибке). Конфигуратор владельца
|
||||
# заимствует (эталон Issue66Example7_1: вместе со справочником перенесён и его ПВХ-владелец).
|
||||
# Проход общий и повторяется, пока находятся новые: у владельца может быть свой владелец.
|
||||
for _owner_pass in range(10):
|
||||
new_owners = []
|
||||
for root_dir, _dirs, files in os.walk(ext_dir):
|
||||
for fn in files:
|
||||
if not fn.endswith(".xml"):
|
||||
continue
|
||||
with open(os.path.join(root_dir, fn), "r", encoding="utf-8-sig") as fh:
|
||||
shell_text = fh.read()
|
||||
if "<Owners>" not in shell_text:
|
||||
continue
|
||||
for om in re.finditer(r'<xr:Item[^>]*>(\w+)\.(\w+)</xr:Item>', shell_text):
|
||||
o_type, o_name = om.group(1), om.group(2)
|
||||
if o_type not in CHILD_TYPE_DIR_MAP:
|
||||
continue
|
||||
if test_object_borrowed(o_type, o_name):
|
||||
continue
|
||||
if (o_type, o_name) in new_owners:
|
||||
continue
|
||||
new_owners.append((o_type, o_name))
|
||||
if not new_owners:
|
||||
break
|
||||
for o_type, o_name in new_owners:
|
||||
ow_src_file = os.path.join(cfg_dir, CHILD_TYPE_DIR_MAP[o_type], f"{o_name}.xml")
|
||||
if not os.path.isfile(ow_src_file):
|
||||
warn(f" Владелец {o_type}.{o_name} не найден в источнике — ссылка останется висячей")
|
||||
continue
|
||||
ow_src = read_source_object(o_type, o_name)
|
||||
ow_xml = build_borrowed_object_xml(o_type, o_name, ow_src["Uuid"], ow_src["Properties"])
|
||||
ow_dir = os.path.join(ext_dir, CHILD_TYPE_DIR_MAP[o_type])
|
||||
os.makedirs(ow_dir, exist_ok=True)
|
||||
ow_file = os.path.join(ow_dir, f"{o_name}.xml")
|
||||
write_utf8_bom(ow_file, ow_xml)
|
||||
add_to_child_objects(o_type, o_name)
|
||||
borrowed_files.append(ow_file)
|
||||
info(f" Auto-borrowed owner: {o_type}.{o_name}")
|
||||
|
||||
# --- Save modified Configuration.xml ---
|
||||
save_xml_bom(tree, ext_resolved)
|
||||
info(f"Saved: {ext_resolved}")
|
||||
|
||||
@@ -23,7 +23,7 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
|
||||
```
|
||||
|
||||
## Mode A — обзор расширения
|
||||
@@ -50,8 +50,8 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-diff.ps1" -Exte
|
||||
|
||||
```powershell
|
||||
# Обзор — что изменено в расширении
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode A
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode A
|
||||
|
||||
# Проверка переноса — все ли #Вставка перенесены
|
||||
... -ExtensionPath src -ConfigPath C:\cfsrc\erp -Mode B
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Mode B
|
||||
```
|
||||
|
||||
@@ -33,39 +33,39 @@ allowed-tools:
|
||||
| `Name` | Имя расширения (обязат.) | — |
|
||||
| `Synonym` | Синоним | = Name |
|
||||
| `NamePrefix` | Префикс собственных объектов | = Name + "_" |
|
||||
| `OutputDir` | Каталог для создания | `src` |
|
||||
| `OutputDir` | Каталог для создания; клади расширение в свой подкаталог, названный по имени: `src\cfe\<Name>` | `src` |
|
||||
| `Purpose` | `Patch` (исправление) / `Customization` (доработка) / `AddOn` (дополнение) | `Customization` |
|
||||
| `Version` | Версия расширения | — |
|
||||
| `Vendor` | Поставщик | — |
|
||||
| `CompatibilityMode` | Режим совместимости | `Version8_3_24` |
|
||||
| `CompatibilityMode` | Режим совместимости; при заданном `ConfigPath` определяется по базовой конфигурации и этот параметр не нужен | `Version8_3_24` |
|
||||
| `ConfigPath` | Путь к выгрузке базовой конфигурации (авто-определяет CompatibilityMode и Language UUID) | — |
|
||||
| `NoRole` | Без основной роли | false |
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-init.ps1" -Name "МоёРасширение" -OutputDir "src\cfe\МоёРасширение" -ConfigPath "src\cf"
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Расширение для ERP с авто-определением совместимости из базовой конфигурации
|
||||
... -Name Расш1 -ConfigPath C:\WS\tasks\cfsrc\erp_8.3.24 -OutputDir src
|
||||
... -Name Расш1 -ConfigPath src\cf -OutputDir src\cfe\Расш1
|
||||
|
||||
# Расширение-исправление с явным режимом совместимости
|
||||
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src
|
||||
... -Name Расш1 -Purpose Patch -CompatibilityMode Version8_3_17 -OutputDir src\cfe\Расш1
|
||||
|
||||
# Расширение-доработка с версией
|
||||
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src
|
||||
... -Name МоёРасширение -Version "1.0.0.1" -Vendor "Компания" -OutputDir src\cfe\МоёРасширение
|
||||
|
||||
# Без роли, с явным префиксом
|
||||
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src
|
||||
... -Name ИсправлениеБага -NamePrefix "ИБ_" -Purpose Patch -NoRole -OutputDir src\cfe\ИсправлениеБага
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/cfe-validate <OutputDir>
|
||||
/cfe-validate <OutputDir> -ConfigPath <ConfigPath>
|
||||
```
|
||||
|
||||
|
||||
@@ -110,36 +110,36 @@ allowed-tools:
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-patch-method.ps1" -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
```
|
||||
|
||||
## Примеры
|
||||
|
||||
```powershell
|
||||
# Код перед записью
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Catalog.Контрагенты.ObjectModule" -MethodName "ПриЗаписи" -InterceptorType Before
|
||||
|
||||
# Перехват После на форме
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Document.Заказ.Form.ФормаДокумента" -MethodName "ПослеЗаписиНаСервере" -InterceptorType After
|
||||
|
||||
# Замена функции (ПродолжитьВызов)
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType Instead
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "CommonModule.ОбщийМодуль" -MethodName "ПолучитьДанные" -InterceptorType Instead
|
||||
|
||||
# ИзменениеИКонтроль — копия тела для правки маркерами
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -ModulePath "Document.РеализацияТоваров.ObjectModule" -MethodName "ОбработкаПроведения" -InterceptorType ModificationAndControl
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -ModulePath "Document.РеализацияТоваров.ObjectModule" -MethodName "ОбработкаПроведения" -InterceptorType ModificationAndControl
|
||||
|
||||
# ModulePath как путь к файлу модуля-источника (без -ConfigPath)
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ModulePath "src\cf\CommonModules\ОбщийМодуль\Ext\Module.bsl" -MethodName "ПолучитьДанные" -InterceptorType Instead
|
||||
... -ExtensionPath src\cfe\extname -ModulePath "src\cf\CommonModules\ОбщийМодуль\Ext\Module.bsl" -MethodName "ПолучитьДанные" -InterceptorType Instead
|
||||
|
||||
# Проверить все контролируемые методы расширения на дрейф
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Check
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Check
|
||||
|
||||
# Актуализировать дрейфнувшие контролируемые методы пачкой
|
||||
... -ExtensionPath src\cfe\ИмяРасширения -ConfigPath src\cf -Actualize
|
||||
... -ExtensionPath src\cfe\extname -ConfigPath src\cf -Actualize
|
||||
```
|
||||
|
||||
## Верификация
|
||||
|
||||
```
|
||||
/cfe-validate <ExtensionPath>
|
||||
/cfe-validate <ExtensionPath> -ConfigPath <ConfigPath>
|
||||
```
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
---
|
||||
name: cfe-validate
|
||||
description: Валидация расширения конфигурации 1С (CFE). Используй после создания или модификации расширения для проверки корректности
|
||||
argument-hint: <ExtensionPath> [-Detailed] [-MaxErrors 30]
|
||||
argument-hint: <ExtensionPath> [-ConfigPath <ConfigDir>] [-Detailed] [-MaxErrors 30]
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
@@ -17,13 +17,24 @@ allowed-tools:
|
||||
| Параметр | Обяз. | Умолч. | Описание |
|
||||
|---------------|:-----:|---------|-------------------------------------------------|
|
||||
| ExtensionPath | да | — | Путь к каталогу или Configuration.xml расширения |
|
||||
| ConfigPath | нет | — | Каталог конфигурации, из которой заимствованы объекты |
|
||||
| Detailed | нет | — | Подробный вывод (все проверки, включая успешные) |
|
||||
| MaxErrors | нет | 30 | Остановиться после N ошибок |
|
||||
| OutFile | нет | — | Записать результат в файл |
|
||||
|
||||
### ConfigPath
|
||||
|
||||
Указывай всегда, когда конфигурация-источник доступна: без неё часть ошибок заимствованных форм не ловится, и расширение может пройти валидацию, а потом быть отвергнутым платформой при загрузке.
|
||||
|
||||
Если пользователь не указал путь — определи сам:
|
||||
1. Прочитай `.v8-project.json` из корня проекта
|
||||
2. Разреши целевую базу (по имени, ветке или `default`)
|
||||
3. Возьми её поле `configSrc`
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src/Configuration.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname\Configuration.xml"
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/cfe-validate.ps1" -ExtensionPath "src\cfe\extname" -ConfigPath "src\cf"
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# cfe-validate v1.7 — Validate 1C configuration extension structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||
# cfe-validate v1.10 — Validate 1C configuration extension structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -9,7 +9,11 @@ param(
|
||||
|
||||
[int]$MaxErrors = 30,
|
||||
|
||||
[string]$OutFile
|
||||
[string]$OutFile,
|
||||
|
||||
# Конфигурация-источник. Без неё проверки, требующие сравнения с основной конфигурацией,
|
||||
# пропускаются (о чём сказано в отчёте), остальные работают как раньше.
|
||||
[string]$ConfigPath
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -89,6 +93,19 @@ $finalize = {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Reference tables ---
|
||||
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
||||
$identPattern = '^[A-Za-z\u0410-\u042F\u0401\u0430-\u044F\u0451_][A-Za-z0-9\u0410-\u042F\u0401\u0430-\u044F\u0451_]*$'
|
||||
@@ -144,6 +161,46 @@ $childTypeDirMap = @{
|
||||
"IntegrationService"="IntegrationServices"
|
||||
}
|
||||
|
||||
# Наборы GeneratedType по типу объекта (эталон — таблица §2.5 спецификации конфигурации).
|
||||
# Неполный набор в заимствованной оболочке платформа отвергает при загрузке: «отсутствует один
|
||||
# или более типов объекта <Тип>». Типы, у которых GeneratedType нет вовсе (общие модули,
|
||||
# подписки, регламентные задания и т.п.), в карте отсутствуют — для них проверка не выполняется.
|
||||
$generatedTypeCategories = @{
|
||||
"Catalog" = @("Object","Ref","Selection","List","Manager")
|
||||
"Document" = @("Object","Ref","Selection","List","Manager")
|
||||
"Enum" = @("Ref","Manager","List")
|
||||
"Constant" = @("Manager","ValueManager","ValueKey")
|
||||
"Report" = @("Object","Manager")
|
||||
"DataProcessor" = @("Object","Manager")
|
||||
"ExchangePlan" = @("Object","Ref","Selection","List","Manager")
|
||||
"Task" = @("Object","Ref","Selection","List","Manager")
|
||||
"BusinessProcess" = @("Object","Ref","Selection","List","Manager","RoutePointRef")
|
||||
"ChartOfCharacteristicTypes" = @("Object","Ref","Selection","List","Manager","Characteristic")
|
||||
"ChartOfAccounts" = @("Object","Ref","Selection","List","Manager","ExtDimensionTypes","ExtDimensionTypesRow")
|
||||
"ChartOfCalculationTypes" = @("Object","Ref","Selection","List","Manager","DisplacingCalculationTypes","DisplacingCalculationTypesRow","BaseCalculationTypes","BaseCalculationTypesRow","LeadingCalculationTypes","LeadingCalculationTypesRow")
|
||||
"InformationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","RecordManager")
|
||||
"AccumulationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey")
|
||||
"AccountingRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","ExtDimensions")
|
||||
"CalculationRegister" = @("Record","Manager","Selection","List","RecordSet","RecordKey","Recalcs")
|
||||
"DocumentJournal" = @("Selection","List","Manager")
|
||||
"Sequence" = @("Record","Manager","RecordSet")
|
||||
"FilterCriterion" = @("Manager","List")
|
||||
"SettingsStorage" = @("Manager")
|
||||
"IntegrationService" = @("Manager")
|
||||
"WSReference" = @("Manager")
|
||||
"DefinedType" = @("DefinedType")
|
||||
}
|
||||
|
||||
# Стандартные реквизиты объектов: в ChildObjects их нет, но пути Объект.<Стандартный> законны.
|
||||
# Имена зависят от варианта встроенного языка, поэтому держим оба написания.
|
||||
$script:standardObjectFields = @(
|
||||
"Code","Description","Ref","Parent","Owner","DeletionMark","Predefined","IsFolder","LineNumber",
|
||||
"Number","Date","Posted","PredefinedDataName","RegisterRecords","DataVersion","RowsCount",
|
||||
"Код","Наименование","Ссылка","Родитель","Владелец","ПометкаУдаления","Предопределенный",
|
||||
"ЭтоГруппа","НомерСтроки","Номер","Дата","Проведен","ИмяПредопределенныхДанных",
|
||||
"Движения","ВерсияДанных","КоличествоСтрок"
|
||||
)
|
||||
|
||||
# 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","Version8_5_1")
|
||||
@@ -195,11 +252,15 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
}
|
||||
|
||||
$version = $root.GetAttribute("version")
|
||||
$versionRank = Get-FormatRank $version
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17-2.20 or 2.21)"
|
||||
} elseif ($versionRank -eq 0) {
|
||||
Report-Error "1. Malformed version '$version' (expected N.N)"
|
||||
} elseif ($versionRank -lt (Get-FormatRank $formatVerifiedMin)) {
|
||||
Report-Warn "1. Format version '$version' is below the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} elseif ($versionRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
Report-Warn "1. Format version '$version' is above the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
}
|
||||
|
||||
# Must have Configuration child
|
||||
@@ -537,6 +598,7 @@ if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Check 9: Borrowed objects validation + Check 10: Sub-items ---
|
||||
$script:enumValuesIndex = @{}
|
||||
$script:borrowedTSIndex = @{}
|
||||
$script:formList = @()
|
||||
|
||||
# Helper: check if sub-item has explicit borrowed metadata
|
||||
@@ -640,6 +702,25 @@ if ($childObjNode) {
|
||||
} else {
|
||||
$borrowedOk++
|
||||
}
|
||||
|
||||
# Полнота набора GeneratedType: платформа отвергает оболочку с неполным набором
|
||||
# («отсутствует один или более типов объекта ChartOfCharacteristicTypes»)
|
||||
$expectedCats = $generatedTypeCategories[$typeName]
|
||||
if ($expectedCats) {
|
||||
$objInfo = $objEl.SelectSingleNode("md:InternalInfo", $objNs)
|
||||
$foundCats = @{}
|
||||
if ($objInfo) {
|
||||
foreach ($gt in $objInfo.SelectNodes("xr:GeneratedType", $objNs)) {
|
||||
$cat = $gt.GetAttribute("category")
|
||||
if ($cat) { $foundCats[$cat] = $true }
|
||||
}
|
||||
}
|
||||
$missingCats = @($expectedCats | Where-Object { -not $foundCats.ContainsKey($_) })
|
||||
if ($missingCats.Count -gt 0) {
|
||||
Report-Error "9. Borrowed ${typeName}.${childName}: missing GeneratedType categor$(if ($missingCats.Count -eq 1) { 'y' } else { 'ies' }) $($missingCats -join ', ')"
|
||||
$check9Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) ---
|
||||
@@ -667,6 +748,12 @@ if ($childObjNode) {
|
||||
$tsInfo = $subItem.SelectSingleNode("md:InternalInfo", $objNs)
|
||||
$tsName = $subItem.SelectSingleNode("md:Properties/md:Name", $objNs)
|
||||
$tsLabel = if ($tsName) { $tsName.InnerText } else { "?" }
|
||||
# Индекс заимствованных ТЧ — по нему Check 12 сверяет <AdditionalColumns table="Объект.X">
|
||||
if ($tsName) {
|
||||
$tsKey = "${typeName}.${childName}"
|
||||
if (-not $script:borrowedTSIndex.ContainsKey($tsKey)) { $script:borrowedTSIndex[$tsKey] = @{} }
|
||||
$script:borrowedTSIndex[$tsKey][$tsName.InnerText] = $true
|
||||
}
|
||||
if (-not $tsInfo) {
|
||||
Report-Error "10. ${ctx}: TabularSection.${tsLabel} missing InternalInfo"
|
||||
$check10Ok = $false
|
||||
@@ -896,6 +983,38 @@ foreach ($bf in $script:borrowedFormsWithTree) {
|
||||
}
|
||||
}
|
||||
|
||||
# Корень путей формы — имя её основного реквизита: «Объект» только у формы объекта, у формы
|
||||
# списка «Список», у формы записи регистра «Запись». С зашитым «Объект» обе проверки ниже на
|
||||
# таких формах молча не срабатывали. Ищем сначала в <Attributes> самой формы, потом в <BaseForm>.
|
||||
$rootName = ""
|
||||
$rootMatch = [regex]::Match($raw, '(?s)<Attribute name="([^"]+)"[^>]*>(?:(?!</Attribute>).)*?<MainAttribute>true</MainAttribute>')
|
||||
if ($rootMatch.Success) { $rootName = $rootMatch.Groups[1].Value }
|
||||
|
||||
# <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в самой форме.
|
||||
# Колонки есть, а самой ТЧ в расширении нет → платформа отвергает загрузку: «Неверный путь к
|
||||
# данным» плюс «Колонки не могут быть добавлены к реквизиту».
|
||||
$acTables = @{}
|
||||
if ($rootName) {
|
||||
$rootPat = [regex]::Escape($rootName)
|
||||
foreach ($m in [regex]::Matches($raw, "<AdditionalColumns table=`"${rootPat}\.(\w+)`"")) {
|
||||
$acTables[$m.Groups[1].Value] = $true
|
||||
}
|
||||
}
|
||||
# Соседние проверки этого блока эвристичны (имя стиля добывается регуляркой), поэтому там
|
||||
# предупреждение. Здесь сигнал точный — имя ТЧ берётся из атрибута, — а последствие жёсткое,
|
||||
# поэтому ошибка.
|
||||
if ($acTables.Count -gt 0) {
|
||||
$ownerKey = ($ctx -split '\.Form\.')[0]
|
||||
$ownerTS = $script:borrowedTSIndex[$ownerKey]
|
||||
foreach ($tblName in $acTables.Keys) {
|
||||
$depCheckCount++
|
||||
if (-not $ownerTS -or -not $ownerTS.ContainsKey($tblName)) {
|
||||
Report-Error "12. ${ctx}: <AdditionalColumns table=`"${rootName}.${tblName}`"> — TabularSection.${tblName} not borrowed in extension"
|
||||
$check12Ok = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($mi in $missingItems) {
|
||||
Report-Warn "12. ${ctx}: references ${mi} not borrowed in extension"
|
||||
$check12Ok = $false
|
||||
@@ -931,6 +1050,127 @@ if ($script:borrowedFormsWithTree.Count -eq 0) {
|
||||
Report-OK "13. TypeLink: clean"
|
||||
}
|
||||
|
||||
# --- Check 14: пути Объект.* заимствованных форм против конфигурации-источника ---
|
||||
# Требует -ConfigPath: отличить живой путь от висячего можно только по исходному объекту.
|
||||
# «Объект.Партнер» валиден и без заимствования реквизита (наследуется от базы), а «Объект.Товары.Артикул»
|
||||
# не разрешится нигде, если Артикул — не реквизит объекта и не колонка из <Columns> самой формы.
|
||||
# Такой путь платформа отвергает на загрузке: «Неверный путь к данным».
|
||||
if (-not $script:stopped -and $script:borrowedFormsWithTree.Count -gt 0) {
|
||||
if (-not $ConfigPath) {
|
||||
Out-Line "[INFO] 14. Пути Объект.* против конфигурации-источника не проверялись: не задан -ConfigPath"
|
||||
} else {
|
||||
$cfgRoot = $ConfigPath
|
||||
if (-not [System.IO.Path]::IsPathRooted($cfgRoot)) { $cfgRoot = Join-Path (Get-Location).Path $cfgRoot }
|
||||
if ((Test-Path $cfgRoot) -and -not (Test-Path $cfgRoot -PathType Container)) { $cfgRoot = Split-Path $cfgRoot -Parent }
|
||||
|
||||
if (-not (Test-Path (Join-Path $cfgRoot "Configuration.xml"))) {
|
||||
Report-Warn "14. -ConfigPath '$ConfigPath': Configuration.xml не найден — проверка путей пропущена"
|
||||
} else {
|
||||
$check14Ok = $true
|
||||
$pathCheckCount = 0
|
||||
|
||||
foreach ($bf in $script:borrowedFormsWithTree) {
|
||||
$raw = $bf.RawText
|
||||
$ctx = $bf.Context
|
||||
# Корень путей — имя основного реквизита формы (см. проверку 12). Нет его ни в
|
||||
# <Attributes> формы, ни в <BaseForm> — путей с корнем не бывает, проверять нечего.
|
||||
$rootMatch14 = [regex]::Match($raw, '(?s)<Attribute name="([^"]+)"[^>]*>(?:(?!</Attribute>).)*?<MainAttribute>true</MainAttribute>')
|
||||
if (-not $rootMatch14.Success) { continue }
|
||||
$rootName = $rootMatch14.Groups[1].Value
|
||||
# У динамического списка набор полей — результат его запроса, а не состав объекта:
|
||||
# туда входят и стандартные поля списка (Ref, Date, DefaultPicture), и псевдонимы
|
||||
# запроса. Сверять такие пути с ChildObjects объекта нельзя — будут ложные ошибки
|
||||
# (корпусная проверка: 3383 таких сегмента на 1094 формах списка УТ).
|
||||
if ($rootMatch14.Value -match '>cfg:DynamicList<') { continue }
|
||||
$ownerKey = ($ctx -split '\.Form\.')[0]
|
||||
$ownerParts = $ownerKey -split '\.', 2
|
||||
if ($ownerParts.Count -lt 2) { continue }
|
||||
$ownerType = $ownerParts[0]; $ownerName = $ownerParts[1]
|
||||
$ownerDir = $childTypeDirMap[$ownerType]
|
||||
if (-not $ownerDir) { continue }
|
||||
$srcObjFile = Join-Path (Join-Path $cfgRoot $ownerDir) "${ownerName}.xml"
|
||||
if (-not (Test-Path $srcObjFile)) {
|
||||
Report-Warn "14. ${ctx}: объект-источник не найден в конфигурации ($ownerDir/${ownerName}.xml)"
|
||||
continue
|
||||
}
|
||||
|
||||
# Имена, доступные первым сегментом пути: реквизиты и ТЧ объекта-источника.
|
||||
# Плюс для каждой ТЧ — её колонки: второй сегмент проверяем по ним (именно там
|
||||
# и жил дефект — Объект.Товары.Артикул при живой ТЧ Товары).
|
||||
$srcNames = @{}
|
||||
$srcTSColumns = @{}
|
||||
$srcDoc = New-Object System.Xml.XmlDocument
|
||||
$srcDoc.PreserveWhitespace = $false
|
||||
$srcDoc.Load($srcObjFile)
|
||||
$srcObjEl = $null
|
||||
foreach ($c in $srcDoc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $srcObjEl = $c; break }
|
||||
}
|
||||
$srcChildObjects = if ($srcObjEl) { $srcObjEl.SelectSingleNode("*[local-name()='ChildObjects']") } else { $null }
|
||||
if ($srcChildObjects) {
|
||||
foreach ($sub in $srcChildObjects.ChildNodes) {
|
||||
if ($sub.NodeType -ne 'Element') { continue }
|
||||
# У регистра дочерние объекты — Dimension/Resource, а не Attribute: без них замена
|
||||
# корня превратила бы тихий пропуск в ложные ошибки на форме записи.
|
||||
if ($sub.LocalName -notin @('Attribute','Dimension','Resource','TabularSection')) { continue }
|
||||
$nameNode = $sub.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
|
||||
if (-not $nameNode) { continue }
|
||||
$subName = $nameNode.InnerText.Trim()
|
||||
$srcNames[$subName] = $true
|
||||
if ($sub.LocalName -ne 'TabularSection') { continue }
|
||||
$cols = @{}
|
||||
foreach ($colName in $sub.SelectNodes("*[local-name()='ChildObjects']/*[local-name()='Attribute']/*[local-name()='Properties']/*[local-name()='Name']")) {
|
||||
$cols[$colName.InnerText.Trim()] = $true
|
||||
}
|
||||
$srcTSColumns[$subName] = $cols
|
||||
}
|
||||
}
|
||||
# Плюс колонки, объявленные в самой форме через <Columns>/<AdditionalColumns table="Объект.X">
|
||||
$rootPat14 = [regex]::Escape($rootName)
|
||||
foreach ($acm in [regex]::Matches($raw, "(?s)<AdditionalColumns table=`"${rootPat14}\.(\w+)`">(.*?)</AdditionalColumns>")) {
|
||||
$tbl = $acm.Groups[1].Value
|
||||
if (-not $srcTSColumns.ContainsKey($tbl)) { $srcTSColumns[$tbl] = @{} }
|
||||
foreach ($cm in [regex]::Matches($acm.Groups[2].Value, '<Column name="(\w+)"')) {
|
||||
$srcTSColumns[$tbl][$cm.Groups[1].Value] = $true
|
||||
}
|
||||
}
|
||||
|
||||
$badPaths = @{}
|
||||
foreach ($m in [regex]::Matches($raw, "<(?:\w+:)?\w*DataPath[^>]*>${rootPat14}\.([^<]+)</(?:\w+:)?\w*DataPath>")) {
|
||||
$segments = $m.Groups[1].Value -split '\.'
|
||||
$seg0 = $segments[0]
|
||||
$pathCheckCount++
|
||||
if ($script:standardObjectFields -contains $seg0) { continue }
|
||||
if (-not $srcNames.ContainsKey($seg0)) {
|
||||
$badPaths["${rootName}.${seg0}"] = "у ${ownerKey} нет такого реквизита или табличной части"
|
||||
continue
|
||||
}
|
||||
# Второй сегмент проверяем только для табличных частей: у ссылочного реквизита
|
||||
# он ведёт в чужой объект, и это уже другая проверка.
|
||||
if ($segments.Count -lt 2 -or -not $srcTSColumns.ContainsKey($seg0)) { continue }
|
||||
$seg1 = $segments[1]
|
||||
if ($script:standardObjectFields -contains $seg1) { continue }
|
||||
# Итог колонки — псевдополе платформы: Total<Колонка> при живой колонке законен
|
||||
if ($seg1 -like "Total*" -and $srcTSColumns[$seg0].ContainsKey($seg1.Substring(5))) { continue }
|
||||
if (-not $srcTSColumns[$seg0].ContainsKey($seg1)) {
|
||||
$badPaths["${rootName}.${seg0}.${seg1}"] = "у табличной части ${seg0} нет колонки ${seg1}, и <Columns> формы её не объявляет"
|
||||
}
|
||||
}
|
||||
foreach ($bad in ($badPaths.Keys | Sort-Object)) {
|
||||
Report-Error "14. ${ctx}: путь '${bad}' — $($badPaths[$bad])"
|
||||
$check14Ok = $false
|
||||
}
|
||||
}
|
||||
|
||||
if ($check14Ok) {
|
||||
Report-OK "14. Object paths vs source config: $pathCheckCount checked"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($script:stopped) { & $finalize; exit 1 }
|
||||
|
||||
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
||||
$extRootDir = Split-Path $resolvedPath -Parent
|
||||
$ctrlCount = 0
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-validate v1.7 — Validate 1C configuration extension XML structure (CFE) (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
||||
# cfe-validate v1.10 — Validate 1C configuration extension XML structure (CFE) (полнота GeneratedType, ТЧ из AdditionalColumns, сверка путей с -ConfigPath)
|
||||
# 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
|
||||
@@ -96,6 +96,50 @@ CHILD_TYPE_DIR_MAP = {
|
||||
'IntegrationService': 'IntegrationServices',
|
||||
}
|
||||
|
||||
# Наборы GeneratedType по типу объекта (эталон — таблица §2.5 спецификации конфигурации).
|
||||
# Неполный набор в заимствованной оболочке платформа отвергает при загрузке: «отсутствует один
|
||||
# или более типов объекта <Тип>». Типы, у которых GeneratedType нет вовсе (общие модули,
|
||||
# подписки, регламентные задания и т.п.), в карте отсутствуют — для них проверка не выполняется.
|
||||
GENERATED_TYPE_CATEGORIES = {
|
||||
'Catalog': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
|
||||
'Document': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
|
||||
'Enum': ['Ref', 'Manager', 'List'],
|
||||
'Constant': ['Manager', 'ValueManager', 'ValueKey'],
|
||||
'Report': ['Object', 'Manager'],
|
||||
'DataProcessor': ['Object', 'Manager'],
|
||||
'ExchangePlan': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
|
||||
'Task': ['Object', 'Ref', 'Selection', 'List', 'Manager'],
|
||||
'BusinessProcess': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'RoutePointRef'],
|
||||
'ChartOfCharacteristicTypes': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'Characteristic'],
|
||||
'ChartOfAccounts': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'ExtDimensionTypes', 'ExtDimensionTypesRow'],
|
||||
'ChartOfCalculationTypes': ['Object', 'Ref', 'Selection', 'List', 'Manager', 'DisplacingCalculationTypes', 'DisplacingCalculationTypesRow', 'BaseCalculationTypes', 'BaseCalculationTypesRow', 'LeadingCalculationTypes', 'LeadingCalculationTypesRow'],
|
||||
'InformationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'RecordManager'],
|
||||
'AccumulationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey'],
|
||||
'AccountingRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'ExtDimensions'],
|
||||
'CalculationRegister': ['Record', 'Manager', 'Selection', 'List', 'RecordSet', 'RecordKey', 'Recalcs'],
|
||||
'DocumentJournal': ['Selection', 'List', 'Manager'],
|
||||
'Sequence': ['Record', 'Manager', 'RecordSet'],
|
||||
'FilterCriterion': ['Manager', 'List'],
|
||||
'SettingsStorage': ['Manager'],
|
||||
'IntegrationService': ['Manager'],
|
||||
'WSReference': ['Manager'],
|
||||
'DefinedType': ['DefinedType'],
|
||||
}
|
||||
|
||||
# Стандартные реквизиты объектов: в ChildObjects их нет, но пути Объект.<Стандартный> законны.
|
||||
# Имена зависят от варианта встроенного языка, поэтому держим оба написания.
|
||||
# Основной реквизит формы: <Attribute name="X"> с <MainAttribute>true</MainAttribute> внутри
|
||||
MAIN_ATTR_RE = re.compile(
|
||||
r'<Attribute name=\"([^\"]+)\"[^>]*>(?:(?!</Attribute>).)*?<MainAttribute>true</MainAttribute>', re.DOTALL)
|
||||
|
||||
STANDARD_OBJECT_FIELDS = {
|
||||
'Code', 'Description', 'Ref', 'Parent', 'Owner', 'DeletionMark', 'Predefined', 'IsFolder', 'LineNumber',
|
||||
'Number', 'Date', 'Posted', 'PredefinedDataName', 'RegisterRecords', 'DataVersion', 'RowsCount',
|
||||
'Код', 'Наименование', 'Ссылка', 'Родитель', 'Владелец', 'ПометкаУдаления', 'Предопределенный',
|
||||
'ЭтоГруппа', 'НомерСтроки', 'Номер', 'Дата', 'Проведен', 'ИмяПредопределенныхДанных',
|
||||
'Движения', 'ВерсияДанных', 'КоличествоСтрок',
|
||||
}
|
||||
|
||||
# Valid enum values for extension properties
|
||||
VALID_ENUM_VALUES = {
|
||||
'ConfigurationExtensionCompatibilityMode': [
|
||||
@@ -117,6 +161,20 @@ VALID_ENUM_VALUES = {
|
||||
|
||||
EXPECTED_NS = 'http://v8.1c.ru/8.3/MDClasses'
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
class Reporter:
|
||||
def __init__(self, max_errors, detailed=False):
|
||||
@@ -177,11 +235,15 @@ def main():
|
||||
parser.add_argument('-Detailed', action='store_true')
|
||||
parser.add_argument('-MaxErrors', dest='MaxErrors', type=int, default=30)
|
||||
parser.add_argument('-OutFile', dest='OutFile', default='')
|
||||
# Конфигурация-источник. Без неё проверки, требующие сравнения с основной конфигурацией,
|
||||
# пропускаются (о чём сказано в отчёте), остальные работают как раньше.
|
||||
parser.add_argument('-ConfigPath', dest='ConfigPath', default='')
|
||||
args = ci_parse_args(parser)
|
||||
|
||||
extension_path = args.ExtensionPath
|
||||
max_errors = args.MaxErrors
|
||||
out_file = args.OutFile
|
||||
config_path_arg = args.ConfigPath
|
||||
|
||||
# --- Resolve path ---
|
||||
if not os.path.isabs(extension_path):
|
||||
@@ -237,11 +299,17 @@ def main():
|
||||
check1_ok = False
|
||||
|
||||
version = root.get('version', '')
|
||||
version_rank = format_rank(version)
|
||||
if not version:
|
||||
r.warn('1. Missing version attribute on MetaDataObject')
|
||||
elif version not in ('2.17', '2.18', '2.19', '2.20', '2.21'):
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
r.warn(f"1. Unusual version '{version}' (expected 2.17-2.20 or 2.21)")
|
||||
elif version_rank == 0:
|
||||
r.error(f"1. Malformed version '{version}' (expected N.N)")
|
||||
elif version_rank < format_rank(FORMAT_VERIFIED_MIN):
|
||||
r.warn(f"1. Format version '{version}' is below the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
elif version_rank > format_rank(FORMAT_VERIFIED_MAX):
|
||||
r.warn(f"1. Format version '{version}' is above the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
|
||||
# Must have Configuration child
|
||||
cfg_node = None
|
||||
@@ -560,6 +628,7 @@ def main():
|
||||
MD = NS['md']
|
||||
XR = NS['xr']
|
||||
enum_values_index = {}
|
||||
borrowed_ts_index = {}
|
||||
form_list = []
|
||||
|
||||
def is_borrowed_sub_item(sub_item):
|
||||
@@ -659,6 +728,23 @@ def main():
|
||||
else:
|
||||
borrowed_ok_count += 1
|
||||
|
||||
# Полнота набора GeneratedType: платформа отвергает оболочку с неполным набором
|
||||
# («отсутствует один или более типов объекта ChartOfCharacteristicTypes»)
|
||||
expected_cats = GENERATED_TYPE_CATEGORIES.get(type_name)
|
||||
if expected_cats:
|
||||
obj_info = obj_el.find(f'{{{MD}}}InternalInfo')
|
||||
found_cats = set()
|
||||
if obj_info is not None:
|
||||
for gt in obj_info.findall(f'{{{XR}}}GeneratedType'):
|
||||
cat = gt.get('category')
|
||||
if cat:
|
||||
found_cats.add(cat)
|
||||
missing_cats = [c for c in expected_cats if c not in found_cats]
|
||||
if missing_cats:
|
||||
word = 'category' if len(missing_cats) == 1 else 'categories'
|
||||
r.error(f"9. Borrowed {type_name}.{child_name}: missing GeneratedType {word} {', '.join(missing_cats)}")
|
||||
check9_ok = False
|
||||
|
||||
# --- Check 10: Sub-items (Attribute, TabularSection, EnumValue, Form) ---
|
||||
obj_child_objects = obj_el.find(f'{{{MD}}}ChildObjects')
|
||||
if obj_child_objects is not None:
|
||||
@@ -686,6 +772,9 @@ def main():
|
||||
ts_info = sub_item.find(f'{{{MD}}}InternalInfo')
|
||||
ts_name_el = sub_item.find(f'{{{MD}}}Properties/{{{MD}}}Name')
|
||||
ts_label = (ts_name_el.text or '?') if ts_name_el is not None else '?'
|
||||
# Индекс заимствованных ТЧ — по нему Check 12 сверяет <AdditionalColumns table="Объект.X">
|
||||
if ts_name_el is not None and ts_name_el.text:
|
||||
borrowed_ts_index.setdefault(f'{type_name}.{child_name}', {})[ts_name_el.text.strip()] = True
|
||||
if ts_info is None:
|
||||
r.error(f'10. {ctx}: TabularSection.{ts_label} missing InternalInfo')
|
||||
check10_ok = False
|
||||
@@ -878,6 +967,29 @@ def main():
|
||||
elif entry['Enum'] not in enum_values_index or entry['Value'] not in enum_values_index.get(entry['Enum'], {}):
|
||||
missing_items.append(f"Enum.{entry['Enum']}.EnumValue.{entry['Value']}")
|
||||
|
||||
# <AdditionalColumns table="Объект.X"> — доп. колонки табличной части, объявленные в самой форме.
|
||||
# Колонки есть, а самой ТЧ в расширении нет → платформа отвергает загрузку: «Неверный путь к
|
||||
# данным» плюс «Колонки не могут быть добавлены к реквизиту».
|
||||
# Соседние проверки этого блока эвристичны (имя стиля добывается регуляркой), поэтому там
|
||||
# предупреждение. Здесь сигнал точный — имя ТЧ берётся из атрибута, — а последствие жёсткое,
|
||||
# поэтому ошибка.
|
||||
# Корень путей формы — имя её основного реквизита: «Объект» только у формы объекта, у формы
|
||||
# списка «Список», у формы записи регистра «Запись». С зашитым «Объект» обе проверки на
|
||||
# таких формах молча не срабатывали. Ищем сначала в <Attributes> формы, потом в <BaseForm>.
|
||||
root_match = MAIN_ATTR_RE.search(raw)
|
||||
root_name = root_match.group(1) if root_match else ""
|
||||
ac_tables = set()
|
||||
if root_name:
|
||||
ac_tables = set(re.findall(r'<AdditionalColumns table="' + re.escape(root_name) + r'\.(\w+)"', raw))
|
||||
if ac_tables:
|
||||
owner_key = ctx.split('.Form.')[0]
|
||||
owner_ts = borrowed_ts_index.get(owner_key, {})
|
||||
for tbl_name in sorted(ac_tables):
|
||||
dep_check_count += 1
|
||||
if tbl_name not in owner_ts:
|
||||
r.error(f'12. {ctx}: <AdditionalColumns table="{root_name}.{tbl_name}"> — TabularSection.{tbl_name} not borrowed in extension')
|
||||
check12_ok = False
|
||||
|
||||
for mi in missing_items:
|
||||
r.warn(f'12. {ctx}: references {mi} not borrowed in extension')
|
||||
check12_ok = False
|
||||
@@ -909,6 +1021,130 @@ def main():
|
||||
elif check13_ok:
|
||||
r.ok('13. TypeLink: clean')
|
||||
|
||||
# --- Check 14: пути Объект.* заимствованных форм против конфигурации-источника ---
|
||||
# Требует -ConfigPath: отличить живой путь от висячего можно только по исходному объекту.
|
||||
# «Объект.Партнер» валиден и без заимствования реквизита (наследуется от базы), а «Объект.Товары.Артикул»
|
||||
# не разрешится нигде, если Артикул — не колонка ТЧ и не колонка из <Columns> самой формы.
|
||||
# Такой путь платформа отвергает на загрузке: «Неверный путь к данным».
|
||||
if not r.stopped and borrowed_forms_with_tree:
|
||||
if not config_path_arg:
|
||||
r.out('[INFO] 14. Пути Объект.* против конфигурации-источника не проверялись: не задан -ConfigPath')
|
||||
else:
|
||||
cfg_root = config_path_arg
|
||||
if not os.path.isabs(cfg_root):
|
||||
cfg_root = os.path.join(os.getcwd(), cfg_root)
|
||||
if os.path.exists(cfg_root) and not os.path.isdir(cfg_root):
|
||||
cfg_root = os.path.dirname(cfg_root)
|
||||
|
||||
if not os.path.isfile(os.path.join(cfg_root, 'Configuration.xml')):
|
||||
r.warn(f"14. -ConfigPath '{config_path_arg}': Configuration.xml не найден — проверка путей пропущена")
|
||||
else:
|
||||
check14_ok = True
|
||||
path_check_count = 0
|
||||
|
||||
for bf in borrowed_forms_with_tree:
|
||||
raw = bf['RawText']
|
||||
ctx = bf['Context']
|
||||
# Корень путей — имя основного реквизита формы (см. проверку 12). Нет его ни в
|
||||
# <Attributes> формы, ни в <BaseForm> — путей с корнем не бывает, проверять нечего.
|
||||
root_match14 = MAIN_ATTR_RE.search(raw)
|
||||
if root_match14 is None:
|
||||
continue
|
||||
root_name14 = root_match14.group(1)
|
||||
# У динамического списка набор полей — результат его запроса, а не состав объекта:
|
||||
# туда входят и стандартные поля списка (Ref, Date, DefaultPicture), и псевдонимы
|
||||
# запроса. Сверять такие пути с ChildObjects объекта нельзя — будут ложные ошибки
|
||||
# (корпусная проверка: 3383 таких сегмента на 1094 формах списка УТ).
|
||||
if '>cfg:DynamicList<' in root_match14.group(0):
|
||||
continue
|
||||
owner_key = ctx.split('.Form.')[0]
|
||||
owner_parts = owner_key.split('.', 1)
|
||||
if len(owner_parts) < 2:
|
||||
continue
|
||||
owner_type, owner_name = owner_parts
|
||||
owner_dir = CHILD_TYPE_DIR_MAP.get(owner_type)
|
||||
if not owner_dir:
|
||||
continue
|
||||
src_obj_file = os.path.join(cfg_root, owner_dir, f'{owner_name}.xml')
|
||||
if not os.path.isfile(src_obj_file):
|
||||
r.warn(f'14. {ctx}: объект-источник не найден в конфигурации ({owner_dir}/{owner_name}.xml)')
|
||||
continue
|
||||
|
||||
# Имена, доступные первым сегментом пути: реквизиты и ТЧ объекта-источника.
|
||||
# Плюс для каждой ТЧ — её колонки: второй сегмент проверяем по ним (именно там
|
||||
# и жил дефект — Объект.Товары.Артикул при живой ТЧ Товары).
|
||||
src_names = set()
|
||||
src_ts_columns = {}
|
||||
src_tree = etree.parse(src_obj_file, etree.XMLParser(remove_blank_text=True))
|
||||
src_obj_el = None
|
||||
for c in src_tree.getroot():
|
||||
if isinstance(c.tag, str):
|
||||
src_obj_el = c
|
||||
break
|
||||
src_child_objects = src_obj_el.find(f'{{{MD}}}ChildObjects') if src_obj_el is not None else None
|
||||
if src_child_objects is not None:
|
||||
for sub in src_child_objects:
|
||||
if not isinstance(sub.tag, str):
|
||||
continue
|
||||
sub_ln = etree.QName(sub.tag).localname
|
||||
# У регистра дочерние объекты — Dimension/Resource, а не Attribute: без них
|
||||
# замена корня превратила бы тихий пропуск в ложные ошибки на форме записи.
|
||||
if sub_ln not in ('Attribute', 'Dimension', 'Resource', 'TabularSection'):
|
||||
continue
|
||||
name_el = sub.find(f'{{{MD}}}Properties/{{{MD}}}Name')
|
||||
if name_el is None or not name_el.text:
|
||||
continue
|
||||
sub_name = name_el.text.strip()
|
||||
src_names.add(sub_name)
|
||||
if sub_ln != 'TabularSection':
|
||||
continue
|
||||
cols = set()
|
||||
for col_name in sub.findall(f'{{{MD}}}ChildObjects/{{{MD}}}Attribute/{{{MD}}}Properties/{{{MD}}}Name'):
|
||||
if col_name.text:
|
||||
cols.add(col_name.text.strip())
|
||||
src_ts_columns[sub_name] = cols
|
||||
# Плюс колонки, объявленные в самой форме через <Columns>/<AdditionalColumns table="Объект.X">
|
||||
root_pat14 = re.escape(root_name14)
|
||||
for acm in re.finditer(r'<AdditionalColumns table="' + root_pat14 + r'\.(\w+)">(.*?)</AdditionalColumns>', raw, re.DOTALL):
|
||||
tbl = acm.group(1)
|
||||
cols = src_ts_columns.setdefault(tbl, set())
|
||||
for cm in re.finditer(r'<Column name="(\w+)"', acm.group(2)):
|
||||
cols.add(cm.group(1))
|
||||
|
||||
bad_paths = {}
|
||||
for m in re.finditer(r'<(?:\w+:)?\w*DataPath[^>]*>' + root_pat14 + r'\.([^<]+)</(?:\w+:)?\w*DataPath>', raw):
|
||||
segments = m.group(1).split('.')
|
||||
seg0 = segments[0]
|
||||
path_check_count += 1
|
||||
if seg0 in STANDARD_OBJECT_FIELDS:
|
||||
continue
|
||||
if seg0 not in src_names:
|
||||
bad_paths[f'{root_name14}.{seg0}'] = f'у {owner_key} нет такого реквизита или табличной части'
|
||||
continue
|
||||
# Второй сегмент проверяем только для табличных частей: у ссылочного реквизита
|
||||
# он ведёт в чужой объект, и это уже другая проверка.
|
||||
if len(segments) < 2 or seg0 not in src_ts_columns:
|
||||
continue
|
||||
seg1 = segments[1]
|
||||
if seg1 in STANDARD_OBJECT_FIELDS:
|
||||
continue
|
||||
# Итог колонки — псевдополе платформы: Total<Колонка> при живой колонке законен
|
||||
if seg1.startswith('Total') and seg1[5:] in src_ts_columns[seg0]:
|
||||
continue
|
||||
if seg1 not in src_ts_columns[seg0]:
|
||||
bad_paths[f'{root_name14}.{seg0}.{seg1}'] = f'у табличной части {seg0} нет колонки {seg1}, и <Columns> формы её не объявляет'
|
||||
|
||||
for bad in sorted(bad_paths):
|
||||
r.error(f"14. {ctx}: путь '{bad}' — {bad_paths[bad]}")
|
||||
check14_ok = False
|
||||
|
||||
if check14_ok:
|
||||
r.ok(f'14. Object paths vs source config: {path_check_count} checked')
|
||||
|
||||
if r.stopped:
|
||||
r.finalize(out_file)
|
||||
sys.exit(1)
|
||||
|
||||
# --- Breadcrumb: controlled methods (&ИзменениеИКонтроль) drift is not checked here ---
|
||||
ctrl_count = 0
|
||||
for dp, _dn, files in os.walk(config_dir):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-load-git v1.19 — Load Git changes into 1C database
|
||||
# db-load-git v1.20 — Load Git changes into 1C database
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||
<#
|
||||
@@ -110,6 +110,12 @@ param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$UpdateDB,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
|
||||
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
|
||||
# но в логе есть отбраковка.
|
||||
[switch]$StrictLog,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string[]]$AdditionalV8Arguments = @(),
|
||||
|
||||
@@ -394,6 +400,41 @@ function Write-PlatformOutput {
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
|
||||
# Строки лога, о которых платформа сообщает, НЕ поднимая код возврата: метаданные отброшены или
|
||||
# конфигурация нерабочая, а операция при этом «успешна». Возвращает подошедшие строки.
|
||||
#
|
||||
# Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки автономны).
|
||||
# Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||
function Find-SilentRejections {
|
||||
param([string]$LogText)
|
||||
$patterns = @(
|
||||
'Неверное свойство объекта метаданных',
|
||||
'не входит в состав объекта метаданных',
|
||||
'Неизвестное имя типа',
|
||||
'Неизвестный объект метаданных',
|
||||
'Ни один из документов не является регистратором для регистра',
|
||||
'Неверное значение перечисления',
|
||||
'не может быть приведен к типу',
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в рантайме.
|
||||
# Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
'Для работы с конфигурацией необходима версия платформы не меньше'
|
||||
)
|
||||
$found = @()
|
||||
if ($LogText) {
|
||||
foreach ($line in ($LogText -split "`r?`n")) {
|
||||
foreach ($pat in $patterns) {
|
||||
if ($line -match [regex]::Escape($pat)) {
|
||||
$found += $line.Trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Возвращаем массив БЕЗ запятой-обёртки: вызывающий берёт результат в @(), а `return ,$found`
|
||||
# дал бы массив из одного пустого массива — фантомное срабатывание на чистом логе.
|
||||
return $found
|
||||
}
|
||||
|
||||
|
||||
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||
if ($engine -eq "ibcmd") {
|
||||
@@ -667,6 +708,7 @@ try {
|
||||
Write-Host "Error loading configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
$logContent = $null
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($logContent) {
|
||||
@@ -677,6 +719,16 @@ try {
|
||||
}
|
||||
Write-PlatformOutput $__v8.Output
|
||||
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: операция уже выполнена, повторять её ради того же текста незачем.
|
||||
$silentFailures = @(Find-SilentRejections $logContent)
|
||||
if ($silentFailures.Count -gt 0) {
|
||||
Write-Host "[warning] platform reported success, but the log contains $($silentFailures.Count) problem(s):" -ForegroundColor Yellow
|
||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-load-git v1.19 — Load Git changes into 1C database
|
||||
# db-load-git v1.20 — Load Git changes into 1C database
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -344,6 +344,38 @@ def print_platform_output(result):
|
||||
print("--- End ---")
|
||||
|
||||
|
||||
def find_silent_rejections(log_text):
|
||||
"""Строки лога, о которых платформа сообщает, НЕ поднимая код возврата.
|
||||
|
||||
Метаданные отброшены или конфигурация нерабочая, а операция при этом «успешна».
|
||||
Возвращает подошедшие строки.
|
||||
|
||||
Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки
|
||||
автономны). Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет
|
||||
весь смысл.
|
||||
"""
|
||||
patterns = [
|
||||
"Неверное свойство объекта метаданных",
|
||||
"не входит в состав объекта метаданных",
|
||||
"Неизвестное имя типа",
|
||||
"Неизвестный объект метаданных",
|
||||
"Ни один из документов не является регистратором для регистра",
|
||||
"Неверное значение перечисления",
|
||||
"не может быть приведен к типу",
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в
|
||||
# рантайме. Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
"Для работы с конфигурацией необходима версия платформы не меньше",
|
||||
]
|
||||
found = []
|
||||
if log_text:
|
||||
for line in log_text.splitlines():
|
||||
for pat in patterns:
|
||||
if pat in line:
|
||||
found.append(line.strip())
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
|
||||
"""Run an ibcmd command non-interactively.
|
||||
|
||||
@@ -446,6 +478,10 @@ def main():
|
||||
)
|
||||
parser.add_argument("-DryRun", action="store_true", help="Only show what would be loaded (no actual load)")
|
||||
parser.add_argument("-UpdateDB", action="store_true", help="Also update database configuration after load")
|
||||
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
|
||||
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
|
||||
# но в логе есть отбраковка.
|
||||
parser.add_argument("-StrictLog", action="store_true")
|
||||
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
|
||||
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
|
||||
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
|
||||
@@ -705,6 +741,7 @@ def main():
|
||||
else:
|
||||
print(f"Error loading configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
|
||||
|
||||
log_content = ""
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
@@ -717,6 +754,21 @@ def main():
|
||||
pass
|
||||
|
||||
print_platform_output(result)
|
||||
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: операция уже выполнена, повторять её ради того же текста незачем.
|
||||
silent_failures = find_silent_rejections(log_content)
|
||||
if silent_failures:
|
||||
print(
|
||||
f"[warning] platform reported success, but the log contains "
|
||||
f"{len(silent_failures)} problem(s):"
|
||||
)
|
||||
for line in silent_failures:
|
||||
print(f" {line}")
|
||||
if args.StrictLog and exit_code == 0:
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
finally:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-load-xml v1.20 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.21 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||
<#
|
||||
@@ -416,6 +416,41 @@ function Write-PlatformOutput {
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
|
||||
# Строки лога, о которых платформа сообщает, НЕ поднимая код возврата: метаданные отброшены или
|
||||
# конфигурация нерабочая, а операция при этом «успешна». Возвращает подошедшие строки.
|
||||
#
|
||||
# Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки автономны).
|
||||
# Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||
function Find-SilentRejections {
|
||||
param([string]$LogText)
|
||||
$patterns = @(
|
||||
'Неверное свойство объекта метаданных',
|
||||
'не входит в состав объекта метаданных',
|
||||
'Неизвестное имя типа',
|
||||
'Неизвестный объект метаданных',
|
||||
'Ни один из документов не является регистратором для регистра',
|
||||
'Неверное значение перечисления',
|
||||
'не может быть приведен к типу',
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в рантайме.
|
||||
# Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
'Для работы с конфигурацией необходима версия платформы не меньше'
|
||||
)
|
||||
$found = @()
|
||||
if ($LogText) {
|
||||
foreach ($line in ($LogText -split "`r?`n")) {
|
||||
foreach ($pat in $patterns) {
|
||||
if ($line -match [regex]::Escape($pat)) {
|
||||
$found += $line.Trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Возвращаем массив БЕЗ запятой-обёртки: вызывающий берёт результат в @(), а `return ,$found`
|
||||
# дал бы массив из одного пустого массива — фантомное срабатывание на чистом логе.
|
||||
return $found
|
||||
}
|
||||
|
||||
|
||||
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||
|
||||
@@ -607,28 +642,7 @@ try {
|
||||
}
|
||||
|
||||
# --- 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
$silentFailures = @(Find-SilentRejections $logContent)
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
@@ -647,10 +661,11 @@ try {
|
||||
}
|
||||
Write-PlatformOutput $__v8.Output
|
||||
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: загрузка уже выполнена, повторять её ради того же текста незачем.
|
||||
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
|
||||
Write-Host "[warning] platform reported success, but the log contains $($silentFailures.Count) problem(s):" -ForegroundColor Yellow
|
||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-load-xml v1.20 — Load 1C configuration from XML files
|
||||
# db-load-xml v1.21 — Load 1C configuration from XML files
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -344,6 +344,38 @@ def print_platform_output(result):
|
||||
print("--- End ---")
|
||||
|
||||
|
||||
def find_silent_rejections(log_text):
|
||||
"""Строки лога, о которых платформа сообщает, НЕ поднимая код возврата.
|
||||
|
||||
Метаданные отброшены или конфигурация нерабочая, а операция при этом «успешна».
|
||||
Возвращает подошедшие строки.
|
||||
|
||||
Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки
|
||||
автономны). Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет
|
||||
весь смысл.
|
||||
"""
|
||||
patterns = [
|
||||
"Неверное свойство объекта метаданных",
|
||||
"не входит в состав объекта метаданных",
|
||||
"Неизвестное имя типа",
|
||||
"Неизвестный объект метаданных",
|
||||
"Ни один из документов не является регистратором для регистра",
|
||||
"Неверное значение перечисления",
|
||||
"не может быть приведен к типу",
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в
|
||||
# рантайме. Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
"Для работы с конфигурацией необходима версия платформы не меньше",
|
||||
]
|
||||
found = []
|
||||
if log_text:
|
||||
for line in log_text.splitlines():
|
||||
for pat in patterns:
|
||||
if pat in line:
|
||||
found.append(line.strip())
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
|
||||
"""Run an ibcmd command non-interactively.
|
||||
|
||||
@@ -636,22 +668,7 @@ def main():
|
||||
# --- 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
|
||||
silent_failures = find_silent_rejections(log_content)
|
||||
|
||||
# --- Result ---
|
||||
# Default: mirror platform's verdict via exit code. Log content (including any
|
||||
@@ -668,15 +685,19 @@ def main():
|
||||
print("--- End ---")
|
||||
|
||||
print_platform_output(result)
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: загрузка уже выполнена, повторять её ради того же текста незачем.
|
||||
# Поток — stdout, как у PS1-порта: предупреждение относится к содержимому загрузки, а не к
|
||||
# отказу навыка, и при code 0 остаётся предупреждением. Раньше py писал его в stderr —
|
||||
# наблюдаемое поведение портов расходилось, и один кейс не мог проверить оба.
|
||||
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,
|
||||
f"[warning] platform reported success, but the log contains "
|
||||
f"{len(silent_failures)} problem(s):"
|
||||
)
|
||||
for f in silent_failures:
|
||||
print(f" {f}", file=sys.stderr)
|
||||
print(f" {f}")
|
||||
if args.StrictLog and exit_code == 0:
|
||||
exit_code = 1
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# db-update v1.14 — Update 1C database configuration
|
||||
# db-update v1.15 — Update 1C database configuration
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: *nix-раскладку платформы (/opt/1cv8/<ver>/1cv8, без .exe) знает только .py-порт — PS на *nix не исполняется.
|
||||
<#
|
||||
@@ -91,6 +91,12 @@ param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[switch]$WarningsAsErrors,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
|
||||
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
|
||||
# но в логе есть отбраковка.
|
||||
[switch]$StrictLog,
|
||||
|
||||
[Parameter(Mandatory=$false)]
|
||||
[string[]]$AdditionalV8Arguments = @(),
|
||||
|
||||
@@ -395,6 +401,41 @@ function Write-PlatformOutput {
|
||||
Write-Host "--- End ---"
|
||||
}
|
||||
|
||||
# Строки лога, о которых платформа сообщает, НЕ поднимая код возврата: метаданные отброшены или
|
||||
# конфигурация нерабочая, а операция при этом «успешна». Возвращает подошедшие строки.
|
||||
#
|
||||
# Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки автономны).
|
||||
# Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет весь смысл.
|
||||
function Find-SilentRejections {
|
||||
param([string]$LogText)
|
||||
$patterns = @(
|
||||
'Неверное свойство объекта метаданных',
|
||||
'не входит в состав объекта метаданных',
|
||||
'Неизвестное имя типа',
|
||||
'Неизвестный объект метаданных',
|
||||
'Ни один из документов не является регистратором для регистра',
|
||||
'Неверное значение перечисления',
|
||||
'не может быть приведен к типу',
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в рантайме.
|
||||
# Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
'Для работы с конфигурацией необходима версия платформы не меньше'
|
||||
)
|
||||
$found = @()
|
||||
if ($LogText) {
|
||||
foreach ($line in ($LogText -split "`r?`n")) {
|
||||
foreach ($pat in $patterns) {
|
||||
if ($line -match [regex]::Escape($pat)) {
|
||||
$found += $line.Trim()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# Возвращаем массив БЕЗ запятой-обёртки: вызывающий берёт результат в @(), а `return ,$found`
|
||||
# дал бы массив из одного пустого массива — фантомное срабатывание на чистом логе.
|
||||
return $found
|
||||
}
|
||||
|
||||
|
||||
$engine = if ((Split-Path $V8Path -Leaf) -match '^ibcmd') { "ibcmd" } else { "1cv8" }
|
||||
|
||||
@@ -496,6 +537,7 @@ try {
|
||||
Write-Host "Error updating database configuration (code: $exitCode)$(Get-ExitAnnotation $exitCode)" -ForegroundColor Red
|
||||
}
|
||||
|
||||
$logContent = $null
|
||||
if (Test-Path $outFile) {
|
||||
$logContent = Get-Content $outFile -Raw -ErrorAction SilentlyContinue
|
||||
if ($logContent) {
|
||||
@@ -506,6 +548,16 @@ try {
|
||||
}
|
||||
Write-PlatformOutput $__v8.Output
|
||||
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: операция уже выполнена, повторять её ради того же текста незачем.
|
||||
$silentFailures = @(Find-SilentRejections $logContent)
|
||||
if ($silentFailures.Count -gt 0) {
|
||||
Write-Host "[warning] platform reported success, but the log contains $($silentFailures.Count) problem(s):" -ForegroundColor Yellow
|
||||
foreach ($f in $silentFailures) { Write-Host " $f" -ForegroundColor Yellow }
|
||||
if ($StrictLog -and $exitCode -eq 0) { $exitCode = 1 }
|
||||
}
|
||||
|
||||
exit $exitCode
|
||||
|
||||
} finally {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# db-update v1.14 — Update 1C database configuration
|
||||
# db-update v1.15 — Update 1C database configuration
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -344,6 +344,38 @@ def print_platform_output(result):
|
||||
print("--- End ---")
|
||||
|
||||
|
||||
def find_silent_rejections(log_text):
|
||||
"""Строки лога, о которых платформа сообщает, НЕ поднимая код возврата.
|
||||
|
||||
Метаданные отброшены или конфигурация нерабочая, а операция при этом «успешна».
|
||||
Возвращает подошедшие строки.
|
||||
|
||||
Копия этой функции есть в каждом навыке, который читает /Out-лог загрузки (навыки
|
||||
автономны). Держать копии одинаковыми — сознательно: разошедшиеся копии сводят на нет
|
||||
весь смысл.
|
||||
"""
|
||||
patterns = [
|
||||
"Неверное свойство объекта метаданных",
|
||||
"не входит в состав объекта метаданных",
|
||||
"Неизвестное имя типа",
|
||||
"Неизвестный объект метаданных",
|
||||
"Ни один из документов не является регистратором для регистра",
|
||||
"Неверное значение перечисления",
|
||||
"не может быть приведен к типу",
|
||||
# Режим совместимости выше платформы: объекты в базу не попадают, отказ приходит в
|
||||
# рантайме. Обрезано до инвариантной части — конкретная версия в сообщении меняется.
|
||||
"Для работы с конфигурацией необходима версия платформы не меньше",
|
||||
]
|
||||
found = []
|
||||
if log_text:
|
||||
for line in log_text.splitlines():
|
||||
for pat in patterns:
|
||||
if pat in line:
|
||||
found.append(line.strip())
|
||||
break
|
||||
return found
|
||||
|
||||
|
||||
def run_ibcmd(cmd, has_username=False, warn_no_user=True):
|
||||
"""Run an ibcmd command non-interactively.
|
||||
|
||||
@@ -411,6 +443,10 @@ def main():
|
||||
parser.add_argument("-Dynamic", default="", choices=["", "+", "-"])
|
||||
parser.add_argument("-Server", action="store_true")
|
||||
parser.add_argument("-WarningsAsErrors", action="store_true")
|
||||
# Ключ для регрессов и верификации снапшотов, не для повседневного вызова: в SKILL.md
|
||||
# намеренно не выносится. Поднимает код возврата, если платформа отчиталась об успехе,
|
||||
# но в логе есть отбраковка.
|
||||
parser.add_argument("-StrictLog", action="store_true")
|
||||
parser.add_argument("-AdditionalV8Arguments", nargs="*", default=[],
|
||||
help="Extra 1cv8 arguments, e.g. /UseHwLicenses+")
|
||||
parser.add_argument("-AdditionalIbcmdArguments", nargs="*", default=[],
|
||||
@@ -527,6 +563,7 @@ def main():
|
||||
else:
|
||||
print(f"Error updating database configuration (code: {exit_code}){describe_exit(exit_code)}", file=sys.stderr)
|
||||
|
||||
log_content = ""
|
||||
if os.path.isfile(out_file):
|
||||
try:
|
||||
with open(out_file, "r", encoding="utf-8-sig") as f:
|
||||
@@ -539,6 +576,21 @@ def main():
|
||||
pass
|
||||
|
||||
print_platform_output(result)
|
||||
|
||||
# Причину не называем: строки лога печатаются следом и говорят за себя, а класс проблемы
|
||||
# разный — от отброшенного свойства до нерабочей на этой платформе конфигурации. Подсказку
|
||||
# про -StrictLog не даём: операция уже выполнена, повторять её ради того же текста незачем.
|
||||
silent_failures = find_silent_rejections(log_content)
|
||||
if silent_failures:
|
||||
print(
|
||||
f"[warning] platform reported success, but the log contains "
|
||||
f"{len(silent_failures)} problem(s):"
|
||||
)
|
||||
for line in silent_failures:
|
||||
print(f" {line}")
|
||||
if args.StrictLog and exit_code == 0:
|
||||
exit_code = 1
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
finally:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# stub-db-create v1.7 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# stub-db-create v1.8 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -163,14 +163,35 @@ function Format-ArgsForDisplay {
|
||||
}
|
||||
|
||||
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- 1. Scan XML files for reference types ---
|
||||
|
||||
$typeMap = @{} # MetadataType -> @(Name1, Name2, ...)
|
||||
|
||||
# Версия формата заглушечной конфигурации. Платформа грузит формат не новее себя, поэтому зашитая
|
||||
# версия ломала бы сборку исходников более старого формата на соответствующей ей платформе. Берём
|
||||
# версию из корня собираемого объекта (ExternalDataProcessor/ExternalReport); вложенные файлы —
|
||||
# запасной вариант, если корень почему-то не попался.
|
||||
$srcRootVersion = ""
|
||||
$srcAnyVersion = ""
|
||||
|
||||
$xmlFiles = Get-ChildItem -Path $SourceDir -Filter "*.xml" -Recurse -File
|
||||
foreach ($f in $xmlFiles) {
|
||||
$content = [System.IO.File]::ReadAllText($f.FullName, [System.Text.Encoding]::UTF8)
|
||||
|
||||
if ($content -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') {
|
||||
$ver = $Matches[1]
|
||||
if (-not $srcAnyVersion) { $srcAnyVersion = $ver }
|
||||
if (-not $srcRootVersion -and $content -match '<(ExternalDataProcessor|ExternalReport)[ >]') {
|
||||
$srcRootVersion = $ver
|
||||
}
|
||||
}
|
||||
|
||||
# Ref types: cfg:CatalogRef.XXX or d5p1:CatalogRef.XXX (and similar depth prefixes d4p1, d3p1, etc.)
|
||||
$refPattern = '(?:cfg:|d\dp1:)(CatalogRef|DocumentRef|EnumRef|ChartOfAccountsRef|ChartOfCharacteristicTypesRef|ChartOfCalculationTypesRef|ExchangePlanRef|BusinessProcessRef|TaskRef)\.([A-Za-z\u0400-\u04FF\d_]+)'
|
||||
foreach ($m in [regex]::Matches($content, $refPattern)) {
|
||||
@@ -337,7 +358,24 @@ if ($hasRefTypes) {
|
||||
$cfgDir = Join-Path $TempBasePath "cfg"
|
||||
New-Item -ItemType Directory -Path $cfgDir -Force | Out-Null
|
||||
|
||||
$ns = '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"'
|
||||
# Заглушке нужна САМАЯ НИЗКАЯ работающая версия, а не версия исходников: ограничение платформы
|
||||
# одностороннее — она читает формат не новее себя. Отсюда min(версия исходников, 2.17): на 2.17+
|
||||
# заглушка остаётся 2.17 (как было), а под исходники 2.13-2.16 опускается до их версии, иначе
|
||||
# конфигурация не загрузится платформой, которая эти исходники и выгрузила («Неизвестная версия
|
||||
# формата 2.17 загружаемого файла», замерено на 8.3.20).
|
||||
#
|
||||
$srcVersion = if ($srcRootVersion) { $srcRootVersion } elseif ($srcAnyVersion) { $srcAnyVersion } else { "2.17" }
|
||||
$srcRank = Get-FormatRank $srcVersion
|
||||
$stubFormatVersion = if ($srcRank -gt 0 -and $srcRank -lt (Get-FormatRank "2.17")) { $srcVersion } else { "2.17" }
|
||||
# Режим совместимости заглушки — по той же логике. Платформа отказывается работать с
|
||||
# конфигурацией, чей режим выше её самой («Для работы с конфигурацией необходима версия
|
||||
# платформы не меньше, чем 8.3.24»), и тогда объекты заглушки в базу не попадают: загрузка
|
||||
# рапортует успех, а сборка падает на «Неизвестное имя типа». Ступени — лестница версий
|
||||
# формата из docs/1c-configuration-spec.md.
|
||||
$compatByFormat = @{ "2.13" = "Version8_3_20"; "2.14" = "Version8_3_21"; "2.15" = "Version8_3_22"; "2.16" = "Version8_3_23" }
|
||||
$stubCompatMode = if ($compatByFormat.ContainsKey($stubFormatVersion)) { $compatByFormat[$stubFormatVersion] } else { "Version8_3_24" }
|
||||
|
||||
$ns = '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="' + $stubFormatVersion + '"'
|
||||
|
||||
# GeneratedType definitions per metadata type
|
||||
$gtDefs = @{
|
||||
@@ -521,7 +559,7 @@ if ($hasRefTypes) {
|
||||
<Synonym/>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<ConfigurationExtensionCompatibilityMode>$stubCompatMode</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -572,7 +610,7 @@ if ($hasRefTypes) {
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<CompatibilityMode>$stubCompatMode</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>$childXml
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# stub-db-create v1.7 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# stub-db-create v1.8 — Create temp 1C infobase with metadata stubs for EPF/ERF build
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -339,6 +339,66 @@ def scan_ref_types(source_dir):
|
||||
return type_map
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
def detect_stub_format_version(source_dir):
|
||||
"""Версия формата заглушечной конфигурации.
|
||||
|
||||
Заглушке нужна САМАЯ НИЗКАЯ работающая версия, а не версия исходников: ограничение платформы
|
||||
одностороннее — она читает формат не новее себя. Отсюда min(версия исходников, 2.17): на 2.17+
|
||||
заглушка остаётся 2.17 (как было), а под исходники 2.13-2.16 опускается до их версии, иначе
|
||||
конфигурация не загрузится платформой, которая эти исходники и выгрузила («Неизвестная версия
|
||||
формата 2.17 загружаемого файла», замерено на 8.3.20).
|
||||
|
||||
Версию исходников берём из корня собираемого объекта (ExternalDataProcessor/ExternalReport);
|
||||
вложенные файлы — запасной вариант, если корень почему-то не попался.
|
||||
"""
|
||||
root_version = ""
|
||||
any_version = ""
|
||||
ver_pattern = re.compile(r'<MetaDataObject[^>]+version="(\d+\.\d+)"')
|
||||
root_pattern = re.compile(r'<(ExternalDataProcessor|ExternalReport)[ >]')
|
||||
for dirpath, _, filenames in os.walk(source_dir):
|
||||
for fn in filenames:
|
||||
if not fn.endswith('.xml'):
|
||||
continue
|
||||
try:
|
||||
with open(os.path.join(dirpath, fn), 'r', encoding='utf-8-sig') as f:
|
||||
content = f.read()
|
||||
except Exception:
|
||||
continue
|
||||
m = ver_pattern.search(content)
|
||||
if not m:
|
||||
continue
|
||||
if not any_version:
|
||||
any_version = m.group(1)
|
||||
if not root_version and root_pattern.search(content):
|
||||
root_version = m.group(1)
|
||||
src_version = root_version or any_version or "2.17"
|
||||
src_rank = format_rank(src_version)
|
||||
return src_version if 0 < src_rank < format_rank("2.17") else "2.17"
|
||||
|
||||
|
||||
# Режим совместимости заглушки — по той же логике, что и версия формата. Платформа отказывается
|
||||
# работать с конфигурацией, чей режим выше её самой («Для работы с конфигурацией необходима версия
|
||||
# платформы не меньше, чем 8.3.24»), и тогда объекты заглушки в базу не попадают: загрузка
|
||||
# рапортует успех, а сборка падает на «Неизвестное имя типа». Ступени — лестница версий формата
|
||||
# из docs/1c-configuration-spec.md.
|
||||
COMPAT_BY_FORMAT = {
|
||||
"2.13": "Version8_3_20",
|
||||
"2.14": "Version8_3_21",
|
||||
"2.15": "Version8_3_22",
|
||||
"2.16": "Version8_3_23",
|
||||
}
|
||||
|
||||
|
||||
def stub_compatibility_mode(format_version):
|
||||
return COMPAT_BY_FORMAT.get(format_version, "Version8_3_24")
|
||||
|
||||
|
||||
def scan_register_columns(source_dir):
|
||||
"""Scan Form.xml for register record set columns referenced via DataPath.
|
||||
Returns {"RegisterType.RegisterName": {"col1": True, "col2": True}}."""
|
||||
@@ -417,7 +477,7 @@ NS = (
|
||||
'xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" '
|
||||
'xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" '
|
||||
'xmlns:xs="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17"'
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
)
|
||||
|
||||
CLASS_IDS = [
|
||||
@@ -1046,6 +1106,9 @@ def main():
|
||||
type_map = scan_ref_types(args.SourceDir)
|
||||
register_columns = scan_register_columns(args.SourceDir)
|
||||
has_ref_types = len(type_map) > 0
|
||||
stub_format_version = detect_stub_format_version(args.SourceDir)
|
||||
stub_compat = stub_compatibility_mode(stub_format_version)
|
||||
ns_decl = f'{NS} version="{stub_format_version}"'
|
||||
|
||||
temp_base = args.TempBasePath or os.path.join(tempfile.gettempdir(), f'epf_stub_db_{random.randint(0,999999)}')
|
||||
|
||||
@@ -1077,7 +1140,7 @@ def main():
|
||||
child_xml += f'\n\t\t\t<{tag}>{name}</{tag}>'
|
||||
|
||||
cfg_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject {NS}>
|
||||
<MetaDataObject {ns_decl}>
|
||||
\t<Configuration uuid="{uuid_cfg}">
|
||||
\t\t<InternalInfo>{co_xml}
|
||||
\t\t</InternalInfo>
|
||||
@@ -1086,7 +1149,7 @@ def main():
|
||||
\t\t\t<Synonym/>
|
||||
\t\t\t<Comment/>
|
||||
\t\t\t<NamePrefix/>
|
||||
\t\t\t<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
\t\t\t<ConfigurationExtensionCompatibilityMode>{stub_compat}</ConfigurationExtensionCompatibilityMode>
|
||||
\t\t\t<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
\t\t\t<UsePurposes>
|
||||
\t\t\t\t<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -1137,7 +1200,7 @@ def main():
|
||||
\t\t\t<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
\t\t\t<InterfaceCompatibilityMode>Taxi</InterfaceCompatibilityMode>
|
||||
\t\t\t<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
\t\t\t<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
\t\t\t<CompatibilityMode>{stub_compat}</CompatibilityMode>
|
||||
\t\t\t<DefaultConstantsForm/>
|
||||
\t\t</Properties>
|
||||
\t\t<ChildObjects>{child_xml}
|
||||
@@ -1151,7 +1214,7 @@ def main():
|
||||
lang_dir = os.path.join(cfg_dir, 'Languages')
|
||||
os.makedirs(lang_dir, exist_ok=True)
|
||||
lang_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject {NS}>
|
||||
<MetaDataObject {ns_decl}>
|
||||
\t<Language uuid="{uuid_lang}">
|
||||
\t\t<Properties>
|
||||
\t\t\t<Name>\u0420\u0443\u0441\u0441\u043a\u0438\u0439</Name>
|
||||
@@ -1280,7 +1343,7 @@ def main():
|
||||
child_obj_xml = '\n\t\t<ChildObjects/>'
|
||||
|
||||
obj_xml = f"""<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject {NS}>
|
||||
<MetaDataObject {ns_decl}>
|
||||
\t<{tag} uuid="{obj_uuid}">{internal_xml}
|
||||
\t\t<Properties>
|
||||
{props_xml}
|
||||
|
||||
@@ -26,12 +26,18 @@ allowed-tools:
|
||||
| Name | да | — | Имя обработки (латиница/кириллица) |
|
||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||
| FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
|
||||
| FormatVersion | нет | `2.17` | Версия формата выгрузки — см. ниже |
|
||||
|
||||
`FormatVersion` — **не выше** версии формата платформы, на которой объект будут собирать и открывать:
|
||||
8.3.24 — `2.17`, 8.3.25 — `2.18`, 8.3.26 — `2.19`, 8.3.27 — `2.20`, 8.5 — `2.21`. Ниже брать можно:
|
||||
платформа читает свой формат и любой более старый, поэтому дефолт `2.17` подходит для всей линейки
|
||||
8.3.24 и выше. Для более старых платформ счёт идёт так же, по одной версии на релиз (8.3.23 — `2.16`),
|
||||
но на них навыки не проверялись — такое значение принимается с предупреждением.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"]
|
||||
```
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# epf-init v1.7 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# epf-init v1.8 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -11,12 +11,25 @@ param(
|
||||
# Версия формата выгрузки. Своей конфигурации у автономной обработки нет, наследовать
|
||||
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри обработки
|
||||
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
|
||||
[string]$FormatVersion = "2.17"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница — docs/1c-configuration-spec.md,
|
||||
# «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и старше) реальны, поэтому запретом их не
|
||||
# закрываем: за пределами диапазона — ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только
|
||||
# на нечисловое значение: это опечатка, а не версия.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
$formatRank = Get-FormatRank $FormatVersion
|
||||
|
||||
function Esc-XmlText {
|
||||
param([string]$s)
|
||||
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||
@@ -25,6 +38,17 @@ function Esc-XmlText {
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# Проверка версии — ПОСЛЕ настройки кодировки консоли: иначе em-dash в сообщении уедет в вопросы.
|
||||
# Пишем прямо в stderr, а не Write-Warning: в PS 5.1 предупреждение уходит в stdout, получает
|
||||
# локализованный префикс и переносится по 80 символов — подстрока в тесте перестаёт находиться.
|
||||
if ($formatRank -eq 0) {
|
||||
[Console]::Error.WriteLine("Malformed -FormatVersion '$FormatVersion' (expected N.N, e.g. 2.17)")
|
||||
exit 1
|
||||
}
|
||||
if ($formatRank -lt (Get-FormatRank $formatVerifiedMin) -or $formatRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
[Console]::Error.WriteLine("WARNING: Format version '$FormatVersion' is outside the tested range $formatVerifiedMin-$formatVerifiedMax — the scaffold is emitted as requested but was not verified on that platform")
|
||||
}
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
$uuid3 = [guid]::NewGuid().ToString()
|
||||
@@ -34,7 +58,7 @@ $uuid4 = [guid]::NewGuid().ToString()
|
||||
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
|
||||
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
|
||||
if ($formatRank -ge 221) {
|
||||
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-init v1.7 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# epf-init v1.8 — Init 1C external data processor scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external data processor."""
|
||||
import sys, os, re, argparse, uuid
|
||||
@@ -56,6 +56,10 @@ def format_rank(ver):
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -66,10 +70,22 @@ def main():
|
||||
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
|
||||
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
|
||||
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17')
|
||||
args = ci_parse_args(parser)
|
||||
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и
|
||||
# старше) реальны, поэтому запретом их не закрываем: за пределами диапазона —
|
||||
# ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только на нечисловое значение.
|
||||
format_rank_value = format_rank(args.FormatVersion)
|
||||
if format_rank_value == 0:
|
||||
print(f"Malformed -FormatVersion '{args.FormatVersion}' (expected N.N, e.g. 2.17)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not (format_rank(FORMAT_VERIFIED_MIN) <= format_rank_value <= format_rank(FORMAT_VERIFIED_MAX)):
|
||||
print(f"WARNING: Format version '{args.FormatVersion}' is outside the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — the scaffold is emitted as requested "
|
||||
f"but was not verified on that platform", file=sys.stderr)
|
||||
|
||||
name = args.Name
|
||||
synonym = args.Synonym if args.Synonym else name
|
||||
src_dir = args.SrcDir
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# epf-validate v1.4 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.5 — 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(
|
||||
@@ -111,6 +111,19 @@ $finalize = {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Reference tables ---
|
||||
|
||||
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
||||
@@ -183,11 +196,15 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
}
|
||||
|
||||
$version = $root.GetAttribute("version")
|
||||
$versionRank = Get-FormatRank $version
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17-2.20 or 2.21)"
|
||||
} elseif ($versionRank -eq 0) {
|
||||
Report-Error "1. Malformed version '$version' (expected N.N)"
|
||||
} elseif ($versionRank -lt (Get-FormatRank $formatVerifiedMin)) {
|
||||
Report-Warn "1. Format version '$version' is below the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} elseif ($versionRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
Report-Warn "1. Format version '$version' is above the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
}
|
||||
|
||||
# Detect type: ExternalDataProcessor or ExternalReport
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# epf-validate v1.4 — Validate 1C external data processor / report structure
|
||||
# epf-validate v1.5 — 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
|
||||
|
||||
@@ -60,6 +60,21 @@ CHILD_TYPE_ORDER = {
|
||||
}
|
||||
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
def localname(el):
|
||||
return etree.QName(el.tag).localname
|
||||
|
||||
@@ -185,11 +200,17 @@ def main():
|
||||
check1_ok = False
|
||||
|
||||
version = root.get("version", "")
|
||||
version_rank = format_rank(version)
|
||||
if not version:
|
||||
report_warn("1. Missing version attribute on MetaDataObject")
|
||||
elif version not in ("2.17", "2.18", "2.19", "2.20", "2.21"):
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.20 or 2.21)")
|
||||
elif version_rank == 0:
|
||||
report_error(f"1. Malformed version '{version}' (expected N.N)")
|
||||
elif version_rank < format_rank(FORMAT_VERIFIED_MIN):
|
||||
report_warn(f"1. Format version '{version}' is below the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
elif version_rank > format_rank(FORMAT_VERIFIED_MAX):
|
||||
report_warn(f"1. Format version '{version}' is above the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
|
||||
# Detect type
|
||||
child_elements = []
|
||||
|
||||
@@ -26,13 +26,19 @@ allowed-tools:
|
||||
| Name | да | — | Имя отчёта (латиница/кириллица) |
|
||||
| Synonym | нет | = Name | Синоним (отображаемое имя) |
|
||||
| SrcDir | нет | `src` | Каталог исходников относительно CWD |
|
||||
| FormatVersion | нет | `2.17` | Версия формата: 2.20 — платформа 8.3.27, 2.21 — 8.5. Дефолт открывается любой платформой |
|
||||
| FormatVersion | нет | `2.17` | Версия формата выгрузки — см. ниже |
|
||||
| --WithSKD | нет | — | Создать пустую СКД и привязать к MainDataCompositionSchema |
|
||||
|
||||
`FormatVersion` — **не выше** версии формата платформы, на которой объект будут собирать и открывать:
|
||||
8.3.24 — `2.17`, 8.3.25 — `2.18`, 8.3.26 — `2.19`, 8.3.27 — `2.20`, 8.5 — `2.21`. Ниже брать можно:
|
||||
платформа читает свой формат и любой более старый, поэтому дефолт `2.17` подходит для всей линейки
|
||||
8.3.24 и выше. Для более старых платформ счёт идёт так же, по одной версии на релиз (8.3.23 — `2.16`),
|
||||
но на них навыки не проверялись — такое значение принимается с предупреждением.
|
||||
|
||||
## Команда
|
||||
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<2.17|2.18|2.19|2.20|2.21>"] [-WithSKD]
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/init.ps1" -Name "<Name>" [-Synonym "<Synonym>"] [-SrcDir "<SrcDir>"] [-FormatVersion "<версия>"] [-WithSKD]
|
||||
```
|
||||
|
||||
## Дальнейшие шаги
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# erf-init v1.7 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# erf-init v1.8 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -13,12 +13,25 @@ param(
|
||||
# Версия формата выгрузки. Своей конфигурации у автономного отчёта нет, наследовать
|
||||
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри отчёта
|
||||
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||
[ValidateSet("2.17", "2.18", "2.19", "2.20", "2.21")]
|
||||
[string]$FormatVersion = "2.17"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница — docs/1c-configuration-spec.md,
|
||||
# «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и старше) реальны, поэтому запретом их не
|
||||
# закрываем: за пределами диапазона — ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только
|
||||
# на нечисловое значение: это опечатка, а не версия.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
$formatRank = Get-FormatRank $FormatVersion
|
||||
|
||||
function Esc-XmlText {
|
||||
param([string]$s)
|
||||
# Эскейп ТЕКСТА элемента: только & < > — кавычку и апостроф платформа держит сырыми.
|
||||
@@ -27,6 +40,17 @@ function Esc-XmlText {
|
||||
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
|
||||
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
|
||||
|
||||
# Проверка версии — ПОСЛЕ настройки кодировки консоли: иначе em-dash в сообщении уедет в вопросы.
|
||||
# Пишем прямо в stderr, а не Write-Warning: в PS 5.1 предупреждение уходит в stdout, получает
|
||||
# локализованный префикс и переносится по 80 символов — подстрока в тесте перестаёт находиться.
|
||||
if ($formatRank -eq 0) {
|
||||
[Console]::Error.WriteLine("Malformed -FormatVersion '$FormatVersion' (expected N.N, e.g. 2.17)")
|
||||
exit 1
|
||||
}
|
||||
if ($formatRank -lt (Get-FormatRank $formatVerifiedMin) -or $formatRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
[Console]::Error.WriteLine("WARNING: Format version '$FormatVersion' is outside the tested range $formatVerifiedMin-$formatVerifiedMax — the scaffold is emitted as requested but was not verified on that platform")
|
||||
}
|
||||
|
||||
$uuid1 = [guid]::NewGuid().ToString()
|
||||
$uuid2 = [guid]::NewGuid().ToString()
|
||||
$uuid3 = [guid]::NewGuid().ToString()
|
||||
@@ -36,7 +60,7 @@ $uuid4 = [guid]::NewGuid().ToString()
|
||||
$xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
|
||||
# 2.21 (8.5) добавила в шапку пространство палитры. Вставляем НА МЕСТО (после lf, перед style):
|
||||
# платформа держит объявления по алфавиту, дописать в конец нельзя.
|
||||
if (($FormatVersion -match '^(\d+)\.(\d+)$') -and ([int]$Matches[1] * 100 + [int]$Matches[2]) -ge 221) {
|
||||
if ($formatRank -ge 221) {
|
||||
$xmlnsDecl = $xmlnsDecl -replace ' xmlns:style=', ' xmlns:pal="http://v8.1c.ru/8.1/data/ui/colors/palette" xmlns:style='
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# erf-init v1.7 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# erf-init v1.8 — Init 1C external report scaffold (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Generates minimal XML source files for a 1C external report."""
|
||||
import sys, os, re, argparse, uuid
|
||||
@@ -56,6 +56,10 @@ def format_rank(ver):
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def main():
|
||||
sys.stdout.reconfigure(encoding="utf-8")
|
||||
sys.stderr.reconfigure(encoding="utf-8")
|
||||
@@ -66,11 +70,23 @@ def main():
|
||||
# Версия формата выгрузки. Своей конфигурации у автономного объекта нет, наследовать
|
||||
# версию неоткуда — поэтому её задают явно. Формы, макеты и справку внутри объекта
|
||||
# навыки берут уже отсюда: их детектор читает version из корня этого файла.
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17',
|
||||
choices=['2.17', '2.18', '2.19', '2.20', '2.21'])
|
||||
parser.add_argument('-FormatVersion', dest='FormatVersion', default='2.17')
|
||||
parser.add_argument('-WithSKD', dest='WithSKD', action='store_true')
|
||||
args = ci_parse_args(parser)
|
||||
|
||||
# Проверенный диапазон: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версии ниже 2.17 (платформы 8.3.23 и
|
||||
# старше) реальны, поэтому запретом их не закрываем: за пределами диапазона —
|
||||
# ПРЕДУПРЕЖДЕНИЕ, скаффолд всё равно выпускается. Ошибка — только на нечисловое значение.
|
||||
format_rank_value = format_rank(args.FormatVersion)
|
||||
if format_rank_value == 0:
|
||||
print(f"Malformed -FormatVersion '{args.FormatVersion}' (expected N.N, e.g. 2.17)", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
if not (format_rank(FORMAT_VERIFIED_MIN) <= format_rank_value <= format_rank(FORMAT_VERIFIED_MAX)):
|
||||
print(f"WARNING: Format version '{args.FormatVersion}' is outside the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — the scaffold is emitted as requested "
|
||||
f"but was not verified on that platform", file=sys.stderr)
|
||||
|
||||
name = args.Name
|
||||
synonym = args.Synonym if args.Synonym else name
|
||||
src_dir = args.SrcDir
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-compile v1.191 — Compile 1C managed form from JSON or object metadata (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$JsonPath,
|
||||
@@ -5926,9 +5926,17 @@ function Emit-Attributes {
|
||||
}
|
||||
if ($hasAddCols) {
|
||||
foreach ($ac in @($attr.additionalColumns)) {
|
||||
# Пустой список колонок задаётся ЯВНО (`"columns": []`) — это законная форма,
|
||||
# платформа так пишет таблицу, у которой доп. колонок нет. А вот отсутствие ключа
|
||||
# — недосказанность автора: «доп. колонки есть», а какие, не указано. Раньше на
|
||||
# этом PS падал с «Не удается индексировать в массив NULL» (@($null).Count = 1).
|
||||
if ($null -eq $ac.PSObject.Properties['columns'] -or $null -eq $ac.columns) {
|
||||
Write-Error "additionalColumns group for table '$($ac.table)': key 'columns' is missing — list the columns, or pass an empty array for a table without extra columns"
|
||||
exit 1
|
||||
}
|
||||
$acCols = @($ac.columns)
|
||||
if ($acCols.Count -eq 0) {
|
||||
# Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа)
|
||||
# Явно пустая группа → self-closing (как платформа)
|
||||
X "$inner`t<AdditionalColumns table=`"$($ac.table)`"/>"
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-compile v1.191 — Compile 1C managed form from JSON or object metadata (+write_xml_file/write_utf8_bom: общий эталон записи)
|
||||
# form-compile v1.192 — Compile 1C managed form from JSON or object metadata (гвард на группу additionalColumns без ключа columns)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import copy
|
||||
@@ -5773,9 +5773,18 @@ def emit_attributes(lines, attrs, indent, conditional_appearance=None):
|
||||
emit_attr_column(lines, col, f'{inner}\t')
|
||||
if has_add_cols:
|
||||
for ac in attr['additionalColumns']:
|
||||
ac_cols = ac.get('columns') or []
|
||||
# Пустой список колонок задаётся ЯВНО (`"columns": []`) — это законная форма,
|
||||
# платформа так пишет таблицу, у которой доп. колонок нет. А вот отсутствие ключа
|
||||
# — недосказанность автора: «доп. колонки есть», а какие, не указано. PS-порт на
|
||||
# этом падал с «Не удается индексировать в массив NULL» (@($null).Count = 1).
|
||||
if ac.get('columns') is None:
|
||||
print(f"additionalColumns group for table '{ac['table']}': key 'columns' is missing "
|
||||
"— list the columns, or pass an empty array for a table without extra columns",
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
ac_cols = ac['columns']
|
||||
if not ac_cols:
|
||||
# Пустая группа доп.колонок (table-ref без колонок) → self-closing (как платформа)
|
||||
# Явно пустая группа → self-closing (как платформа)
|
||||
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}"/>')
|
||||
continue
|
||||
lines.append(f'{inner}\t<AdditionalColumns table="{ac["table"]}">')
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# form-decompile v0.149 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||
param(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-decompile v0.148 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# form-decompile v0.149 — Decompile 1C managed Form.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# ВНИМАНИЕ: раундтрип не гарантируется. Навык исключён из авто-использования моделью.
|
||||
#
|
||||
@@ -103,29 +103,29 @@ def _attr(node, name, ns_uri=None):
|
||||
def convert_string_to_json_literal(s):
|
||||
if s is None:
|
||||
return 'null'
|
||||
sb = ['"']
|
||||
out = ['"']
|
||||
for ch in s:
|
||||
code = ord(ch)
|
||||
if code == 0x22:
|
||||
sb.append('\\"')
|
||||
out.append('\\"')
|
||||
elif code == 0x5C:
|
||||
sb.append('\\\\')
|
||||
out.append('\\\\')
|
||||
elif code == 0x08:
|
||||
sb.append('\\b')
|
||||
out.append('\\b')
|
||||
elif code == 0x09:
|
||||
sb.append('\\t')
|
||||
out.append('\\t')
|
||||
elif code == 0x0A:
|
||||
sb.append('\\n')
|
||||
out.append('\\n')
|
||||
elif code == 0x0C:
|
||||
sb.append('\\f')
|
||||
out.append('\\f')
|
||||
elif code == 0x0D:
|
||||
sb.append('\\r')
|
||||
out.append('\\r')
|
||||
elif code < 0x20:
|
||||
sb.append('\\u%04x' % code)
|
||||
out.append('\\u%04x' % code)
|
||||
else:
|
||||
sb.append(ch)
|
||||
sb.append('"')
|
||||
return ''.join(sb)
|
||||
out.append(ch)
|
||||
out.append('"')
|
||||
return ''.join(out)
|
||||
|
||||
|
||||
def _num_to_str(obj):
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# form-validate v1.10 — Validate 1C managed form
|
||||
# form-validate v1.17 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -56,9 +56,23 @@ try {
|
||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($xmlDoc.NameTable)
|
||||
$nsMgr.AddNamespace("f", "http://v8.1c.ru/8.3/xcf/logform")
|
||||
$nsMgr.AddNamespace("v8", "http://v8.1c.ru/8.1/data/core")
|
||||
$nsMgr.AddNamespace("xsi", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
|
||||
$root = $xmlDoc.DocumentElement
|
||||
|
||||
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья
|
||||
# support-guard: is_external_root, авторитет — cf-edit).
|
||||
function Test-ExternalObjectRoot([string]$xmlPath) {
|
||||
if (-not (Test-Path $xmlPath)) { return $false }
|
||||
try {
|
||||
[xml]$mx = Get-Content -Path $xmlPath -Encoding UTF8
|
||||
$el = $mx.DocumentElement.FirstChild
|
||||
while ($el -and $el.NodeType -ne 'Element') { $el = $el.NextSibling }
|
||||
if ($el) { return @('ExternalDataProcessor','ExternalReport') -contains $el.LocalName }
|
||||
} catch {}
|
||||
return $false
|
||||
}
|
||||
|
||||
# --- 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)
|
||||
@@ -66,13 +80,56 @@ $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 }
|
||||
# Порядок проверок тот же, что у Detect-FormatVersion: сначала корень автономной обработки,
|
||||
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
|
||||
# версию конфигурации.
|
||||
$extRoot = "$walkDir.xml"
|
||||
if (-not $script:versionAnchor) {
|
||||
if (Test-ExternalObjectRoot $extRoot) {
|
||||
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
|
||||
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
$script:versionAnchor = $extRoot
|
||||
break
|
||||
}
|
||||
}
|
||||
if (Test-Path (Join-Path $walkDir "Configuration.xml")) {
|
||||
$script:isConfigContext = $true
|
||||
$script:configXmlPath = Join-Path $walkDir "Configuration.xml"
|
||||
if (-not $script:versionAnchor) { $script:versionAnchor = $script:configXmlPath }
|
||||
break
|
||||
}
|
||||
$walkDir = Split-Path $walkDir
|
||||
}
|
||||
|
||||
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
|
||||
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
$d = $dir
|
||||
while ($d) {
|
||||
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||
$extPath = "$d.xml"
|
||||
if (Test-Path $extPath) {
|
||||
$extText = [System.IO.File]::ReadAllText($extPath, [System.Text.Encoding]::UTF8)
|
||||
$extHead = $extText.Substring(0, [Math]::Min(2000, $extText.Length))
|
||||
if ($extHead -match '<(ExternalDataProcessor|ExternalReport)[ >]' -and $extHead -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$cfgPath = Join-Path $d "Configuration.xml"
|
||||
if (Test-Path $cfgPath) {
|
||||
$cfgText = [System.IO.File]::ReadAllText($cfgPath, [System.Text.Encoding]::UTF8)
|
||||
# Длину среза берём по СТРОКЕ, а не по размеру файла: размер в БАЙТАХ, Substring считает
|
||||
# СИМВОЛЫ, и на кириллице байт больше — короткий Configuration.xml ронял навык исключением.
|
||||
$head = $cfgText.Substring(0, [Math]::Min(2000, $cfgText.Length))
|
||||
if ($head -match '<MetaDataObject[^>]+version="(\d+\.\d+)"') { return $Matches[1] }
|
||||
}
|
||||
$parent = Split-Path $d -Parent
|
||||
if ($parent -eq $d) { break }
|
||||
$d = $parent
|
||||
}
|
||||
return "2.17"
|
||||
}
|
||||
|
||||
# --- Counters ---
|
||||
|
||||
$errors = 0
|
||||
@@ -101,6 +158,19 @@ function Report-Warn {
|
||||
Write-Host "[WARN] $msg"
|
||||
}
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Form name from path ---
|
||||
|
||||
$formName = [System.IO.Path]::GetFileNameWithoutExtension($FormPath)
|
||||
@@ -127,13 +197,17 @@ if ($root.LocalName -ne "Form") {
|
||||
Report-Error "Root element is '$($root.LocalName)', expected 'Form'"
|
||||
} else {
|
||||
$version = $root.GetAttribute("version")
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
if ($version -in @("2.17", "2.18", "2.19", "2.20")) {
|
||||
Report-OK "Root element: Form version=$version"
|
||||
} elseif ($version) {
|
||||
Report-Warn "Form version='$version' (expected 2.17-2.20)"
|
||||
} else {
|
||||
$versionRank = Get-FormatRank $version
|
||||
if (-not $version) {
|
||||
Report-Warn "Form version attribute missing"
|
||||
} elseif ($versionRank -eq 0) {
|
||||
Report-Error "Malformed version '$version' (expected N.N)"
|
||||
} elseif ($versionRank -lt (Get-FormatRank $formatVerifiedMin)) {
|
||||
Report-Warn "Format version '$version' is below the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} elseif ($versionRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
Report-Warn "Format version '$version' is above the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} else {
|
||||
Report-OK "Root element: Form version=$version"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,10 +218,15 @@ if (-not $stopped) {
|
||||
if ($acb) {
|
||||
$acbName = $acb.GetAttribute("name")
|
||||
$acbId = $acb.GetAttribute("id")
|
||||
# id=-1 — соглашение, а не требование: в корпусе УТ/БП/ERP так у 21 094 форм из 21 097,
|
||||
# но три формы платформа выгружает с обычным id и грузит их без нареканий. Поэтому
|
||||
# предупреждение; ошибка — только если id вовсе не число.
|
||||
if ($acbId -eq "-1") {
|
||||
Report-OK "AutoCommandBar: name='$acbName', id=$acbId"
|
||||
} elseif ($acbId -match '^-?\d+$') {
|
||||
Report-Warn "AutoCommandBar id='$acbId', usually '-1'"
|
||||
} else {
|
||||
Report-Error "AutoCommandBar id='$acbId', expected '-1'"
|
||||
Report-Error "AutoCommandBar id='$acbId' is not a number"
|
||||
}
|
||||
} else {
|
||||
Report-Error "AutoCommandBar element missing"
|
||||
@@ -427,11 +506,19 @@ if (-not $stopped) {
|
||||
$segments = $cleanPath -split '\.'
|
||||
$rootAttr = $segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if ($rootAttr -eq 'Items') {
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute.
|
||||
# Разрешаем ЦЕПОЧКОЙ: таблица во вложенной таблице сама привязана через Items.*, и один
|
||||
# шаг оставлял корнем литерал «Items» — форма платформы объявлялась битой (типовые
|
||||
# НастройкаПравилОбработкиЗаявокСотрудников в БП и ERP).
|
||||
$itemsHops = 0
|
||||
$itemsBroken = $false
|
||||
while ($rootAttr -eq 'Items') {
|
||||
$itemsHops++
|
||||
if ($itemsHops -gt 10) { $itemsBroken = $true; break } # страховка от кольца ссылок
|
||||
if ($segments.Count -lt 3 -or $segments[2] -ne 'CurrentData') {
|
||||
Report-Warn "[$tag] '$elName': $bTag='$dataPath' — unknown Items.* shape, expected Items.<Table>.CurrentData.*"
|
||||
continue
|
||||
$itemsBroken = $true
|
||||
break
|
||||
}
|
||||
$tableName = $segments[1]
|
||||
$tableEl = $null
|
||||
@@ -444,17 +531,21 @@ if (-not $stopped) {
|
||||
if (-not $tableEl) {
|
||||
Report-Error "[$tag] '$elName': $bTag='$dataPath' — table element '$tableName' not found"
|
||||
$pathErrors++
|
||||
continue
|
||||
$itemsBroken = $true
|
||||
break
|
||||
}
|
||||
$tableDpNode = $tableEl.Node.SelectSingleNode("f:DataPath", $nsMgr)
|
||||
if (-not $tableDpNode -or -not $tableDpNode.InnerText.Trim()) {
|
||||
# Table without DataPath — can't resolve further, accept silently
|
||||
continue
|
||||
$itemsBroken = $true
|
||||
break
|
||||
}
|
||||
$tableDp = $tableDpNode.InnerText.Trim() -replace '\[\d+\]', ''
|
||||
if ($tableDp.StartsWith('~')) { $tableDp = $tableDp.Substring(1) }
|
||||
$rootAttr = ($tableDp -split '\.')[0]
|
||||
$segments = $tableDp -split '\.'
|
||||
$rootAttr = $segments[0]
|
||||
}
|
||||
if ($itemsBroken) { continue }
|
||||
|
||||
if (-not $attrMap.ContainsKey($rootAttr)) {
|
||||
Report-Error "[$tag] '$elName': $bTag='$dataPath' — attribute '$rootAttr' not found"
|
||||
@@ -569,13 +660,18 @@ if (-not $stopped) {
|
||||
$actionErrors = 0
|
||||
$actionChecked = 0
|
||||
|
||||
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
|
||||
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
|
||||
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
|
||||
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
|
||||
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
|
||||
foreach ($cmd in $cmdNodes) {
|
||||
if ($stopped) { break }
|
||||
$cmdName = $cmd.GetAttribute("name")
|
||||
$actionNode = $cmd.SelectSingleNode("f:Action", $nsMgr)
|
||||
$actionChecked++
|
||||
if (-not $actionNode -or -not $actionNode.InnerText.Trim()) {
|
||||
Report-Error "Command '$cmdName': missing or empty Action"
|
||||
Report-Warn "Command '$cmdName': no Action — handler must be assigned at runtime, otherwise the command does nothing"
|
||||
$actionErrors++
|
||||
}
|
||||
}
|
||||
@@ -742,6 +838,41 @@ if (-not $stopped -and $isExtension) {
|
||||
Report-OK "Extension ID ranges: $extAttrCount attr(s), $extCmdCount cmd(s) — all >= 1000000"
|
||||
}
|
||||
}
|
||||
|
||||
# 11d. Пути на основной реквизит, которого форма не объявляет.
|
||||
# Check 5 такое пропускает: у заимствованной формы он не проверяет базовые элементы (id < 1000000),
|
||||
# а привязки в <xr:Link> вообще вне его списка тегов. Между тем это ровно тот случай, на котором
|
||||
# платформа отвергает загрузку: «Неверный путь к полю - Объект.X». Правило: если основной реквизит
|
||||
# не объявлен в <Attributes> формы, любой путь с его корнем не разрешится.
|
||||
# Корень берётся из основного реквизита BaseForm: «Объект» он только у формы объекта, у формы
|
||||
# списка это «Список», у формы записи регистра «Запись». С зашитым «Объект» проверка на таких
|
||||
# формах молча не срабатывала — валидатор рапортовал «чисто» на форме, которую платформа не примет.
|
||||
$mainAttrDeclared = $false
|
||||
foreach ($attr in $attrNodes) {
|
||||
$maNode = $attr.SelectSingleNode("f:MainAttribute", $nsMgr)
|
||||
if ($maNode -and $maNode.InnerText.Trim() -eq "true") { $mainAttrDeclared = $true; break }
|
||||
}
|
||||
|
||||
if (-not $mainAttrDeclared) {
|
||||
# Значения привязок ищем текстом: интересуют и обычные теги, и <xr:DataPath> внутри
|
||||
# <ChoiceParameterLinks>, а те живут в чужом пространстве имён.
|
||||
$rawForm = [System.IO.File]::ReadAllText($FormPath, [System.Text.Encoding]::UTF8)
|
||||
$mainBase = $baseFormNode.SelectSingleNode("f:Attributes/f:Attribute[f:MainAttribute='true']", $bfNs)
|
||||
$rootName = if ($mainBase -and $mainBase.GetAttribute("name")) { $mainBase.GetAttribute("name") } else { "Объект" }
|
||||
$rootPat = [regex]::Escape($rootName)
|
||||
$danglingPaths = @{}
|
||||
foreach ($m in [regex]::Matches($rawForm, "<(?:\w+:)?\w*DataPath[^>]*>(${rootPat}\.[^<]+)</(?:\w+:)?\w*DataPath>")) {
|
||||
$danglingPaths[$m.Groups[1].Value] = $true
|
||||
}
|
||||
if ($danglingPaths.Count -gt 0) {
|
||||
$shown = @($danglingPaths.Keys | Sort-Object)
|
||||
$sample = ($shown | Select-Object -First 3) -join ", "
|
||||
$suffix = if ($shown.Count -gt 3) { " (и ещё $($shown.Count - 3))" } else { "" }
|
||||
Report-Error "Path(s) rooted at '${rootName}' but the form declares no MainAttribute: $sample$suffix"
|
||||
} elseif ($mainBase) {
|
||||
Report-OK "Object paths: none dangling (MainAttribute not declared)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Check callType without BaseForm (structural warning)
|
||||
@@ -844,6 +975,71 @@ if (-not $stopped) {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 13: префиксы в значениях объявлены в самом файле ---
|
||||
# `cfg:DataProcessorObject.X` в <v8:Type> при незадекларированном xmlns:cfg — валидный XML, который
|
||||
# платформа не читает вовсе: «Исключение XDTO произошло при чтении файла». Ошибка типична для
|
||||
# рукописного XML: префикс скопирован из чужой формы, а объявление в корне забыто. Область видимости
|
||||
# считаем по узлу (GetNamespaceOfPrefix), а не по корню: локальная xmlns на элементе законна.
|
||||
|
||||
if (-not $stopped) {
|
||||
$prefixErrors = 0
|
||||
$prefixChecked = 0
|
||||
|
||||
$prefixPattern = '^([A-Za-z_][A-Za-z0-9_.-]*):.+$'
|
||||
# Значения, где префикс обязан резолвиться: тип реквизита/колонки и xsi:type
|
||||
# Только листовые узлы: под local-name()='Type' подходит и обёртка <Type>, и вложенный <v8:Type>,
|
||||
# а InnerText обёртки — то же значение, иначе одна ошибка сообщалась бы дважды.
|
||||
foreach ($node in $xmlDoc.SelectNodes("//*[local-name()='Type' or local-name()='TypeSet']", $nsMgr)) {
|
||||
if ($node.SelectSingleNode("*")) { continue }
|
||||
$val = $node.InnerText.Trim()
|
||||
if (-not $val) { continue }
|
||||
$m = [regex]::Match($val, $prefixPattern)
|
||||
if (-not $m.Success) { continue }
|
||||
$prefixChecked++
|
||||
$pfx = $m.Groups[1].Value
|
||||
if (-not $node.GetNamespaceOfPrefix($pfx)) {
|
||||
Report-Error "13. Type '$val': namespace prefix '${pfx}:' is not declared — the platform cannot read the file (XDTO)"
|
||||
$prefixErrors++
|
||||
}
|
||||
}
|
||||
foreach ($node in $xmlDoc.SelectNodes("//*[@xsi:type]", $nsMgr)) {
|
||||
$val = $node.GetAttribute("type", "http://www.w3.org/2001/XMLSchema-instance")
|
||||
$m = [regex]::Match($val, $prefixPattern)
|
||||
if (-not $m.Success) { continue }
|
||||
$prefixChecked++
|
||||
$pfx = $m.Groups[1].Value
|
||||
if (-not $node.GetNamespaceOfPrefix($pfx)) {
|
||||
Report-Error "13. xsi:type='$val': namespace prefix '${pfx}:' is not declared — the platform cannot read the file (XDTO)"
|
||||
$prefixErrors++
|
||||
}
|
||||
}
|
||||
|
||||
if ($prefixChecked -eq 0) {
|
||||
Report-OK "13. Namespace prefixes: nothing to check"
|
||||
} elseif ($prefixErrors -eq 0) {
|
||||
Report-OK "13. Namespace prefixes: $prefixChecked values, all declared"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
|
||||
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
|
||||
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
|
||||
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
|
||||
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
|
||||
|
||||
if (-not $stopped -and $script:versionAnchor) {
|
||||
$formVer = $root.GetAttribute("version")
|
||||
$dumpVer = Detect-FormatVersion (Split-Path (Resolve-Path $FormPath) -Parent)
|
||||
|
||||
if (-not $formVer) {
|
||||
Report-OK "14. Format version: not comparable"
|
||||
} elseif ($formVer -ne $dumpVer) {
|
||||
Report-Error "14. Format version $formVer differs from the dump ($dumpVer) — a dump carries one version, the platform refuses a file it cannot read"
|
||||
} else {
|
||||
Report-OK "14. Format version: $formVer, matches the dump"
|
||||
}
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
|
||||
$checks = $script:okCount + $errors + $warnings
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# form-validate v1.10 — Validate 1C managed form
|
||||
# form-validate v1.17 — Validate 1C managed form
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -71,6 +71,64 @@ VALID_CFG_PREFIXES = {
|
||||
}
|
||||
|
||||
|
||||
# Корень автономной внешней обработки/отчёта. Копия общего эталона (семья
|
||||
# support-guard: is_external_root, авторитет — cf-edit).
|
||||
def _sg_is_external_root(xml_path):
|
||||
if not os.path.isfile(xml_path):
|
||||
return False
|
||||
try:
|
||||
mx = etree.parse(xml_path).getroot()
|
||||
for child in mx:
|
||||
if isinstance(child.tag, str):
|
||||
return child.tag.split("}")[-1] in ("ExternalDataProcessor", "ExternalReport")
|
||||
except Exception:
|
||||
return False
|
||||
return False
|
||||
|
||||
|
||||
# Версия формата выгрузки. Копия общего эталона (семья detect_format_version, авторитет —
|
||||
# form-compile): та же ветка для автономной EPF/ERF, где версию несёт корень обработки.
|
||||
def detect_format_version(d):
|
||||
while d:
|
||||
# Автономная внешняя обработка/отчёт: своего Configuration.xml у неё нет, версию несёт
|
||||
# корень самой обработки. Без этого форма и макет внутри обработки 2.21 писались бы 2.17.
|
||||
ext_path = d + ".xml"
|
||||
if os.path.isfile(ext_path):
|
||||
with open(ext_path, "r", encoding="utf-8-sig") as f:
|
||||
ext_head = f.read(2000)
|
||||
if re.search(r'<(ExternalDataProcessor|ExternalReport)[ >]', ext_head):
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', ext_head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
cfg_path = os.path.join(d, "Configuration.xml")
|
||||
if os.path.isfile(cfg_path):
|
||||
with open(cfg_path, "r", encoding="utf-8-sig") as f:
|
||||
head = f.read(2000)
|
||||
m = re.search(r'<MetaDataObject[^>]+version="(\d+\.\d+)"', head)
|
||||
if m:
|
||||
return m.group(1)
|
||||
parent = os.path.dirname(d)
|
||||
if parent == d:
|
||||
break
|
||||
d = parent
|
||||
return "2.17"
|
||||
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
def localname(el):
|
||||
return etree.QName(el.tag).localname
|
||||
|
||||
@@ -128,13 +186,29 @@ def main():
|
||||
|
||||
# Detect context: config vs EPF/ERF
|
||||
is_config_context = False
|
||||
config_xml_path = ''
|
||||
version_anchor = ''
|
||||
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
|
||||
# Порядок проверок тот же, что у detect_format_version: сначала корень автономной обработки,
|
||||
# потом Configuration.xml — иначе форма внутри EPF, лежащей в дереве конфигурации, взяла бы
|
||||
# версию конфигурации.
|
||||
ext_root = walk_dir + '.xml'
|
||||
if not version_anchor:
|
||||
if _sg_is_external_root(ext_root):
|
||||
# Ближайший якорь побеждает: автономная обработка остаётся автономной, даже если её
|
||||
# исходники лежат внутри дерева с Configuration.xml (типовая раскладка проекта:
|
||||
# src/cf рядом с src/epf). Иначе её собственные External*-типы считались бы ошибкой.
|
||||
version_anchor = ext_root
|
||||
break
|
||||
if os.path.isfile(os.path.join(walk_dir, 'Configuration.xml')):
|
||||
is_config_context = True
|
||||
config_xml_path = os.path.join(walk_dir, 'Configuration.xml')
|
||||
if not version_anchor:
|
||||
version_anchor = config_xml_path
|
||||
break
|
||||
walk_dir = parent
|
||||
|
||||
@@ -183,13 +257,19 @@ def main():
|
||||
report_error(f"Root element is '{localname(root)}', expected 'Form'")
|
||||
else:
|
||||
version = root.get("version", "")
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27).
|
||||
if version in ("2.17", "2.18", "2.19", "2.20"):
|
||||
report_ok(f"Root element: Form version={version}")
|
||||
elif version:
|
||||
report_warn(f"Form version='{version}' (expected 2.17-2.20)")
|
||||
else:
|
||||
version_rank = format_rank(version)
|
||||
if not version:
|
||||
report_warn("Form version attribute missing")
|
||||
elif version_rank == 0:
|
||||
report_error(f"Malformed version '{version}' (expected N.N)")
|
||||
elif version_rank < format_rank(FORMAT_VERIFIED_MIN):
|
||||
report_warn(f"Format version '{version}' is below the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
elif version_rank > format_rank(FORMAT_VERIFIED_MAX):
|
||||
report_warn(f"Format version '{version}' is above the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
else:
|
||||
report_ok(f"Root element: Form version={version}")
|
||||
|
||||
# --- Check 2: AutoCommandBar ---
|
||||
if not stopped:
|
||||
@@ -197,10 +277,15 @@ def main():
|
||||
if acb is not None:
|
||||
acb_name = acb.get("name", "")
|
||||
acb_id = acb.get("id", "")
|
||||
# id=-1 — соглашение, а не требование: в корпусе УТ/БП/ERP так у 21 094 форм из 21 097,
|
||||
# но три формы платформа выгружает с обычным id и грузит их без нареканий. Поэтому
|
||||
# предупреждение; ошибка — только если id вовсе не число.
|
||||
if acb_id == "-1":
|
||||
report_ok(f"AutoCommandBar: name='{acb_name}', id={acb_id}")
|
||||
elif re.match(r'^-?\d+$', acb_id):
|
||||
report_warn(f"AutoCommandBar id='{acb_id}', usually '-1'")
|
||||
else:
|
||||
report_error(f"AutoCommandBar id='{acb_id}', expected '-1'")
|
||||
report_error(f"AutoCommandBar id='{acb_id}' is not a number")
|
||||
else:
|
||||
report_error("AutoCommandBar element missing")
|
||||
|
||||
@@ -452,11 +537,21 @@ def main():
|
||||
segments = clean_path.split(".")
|
||||
root_attr = segments[0]
|
||||
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute
|
||||
if root_attr == 'Items':
|
||||
# Resolve Items.<TableName>.CurrentData.<Field>... — table element, not attribute.
|
||||
# Разрешаем ЦЕПОЧКОЙ: таблица во вложенной таблице сама привязана через Items.*, и один
|
||||
# шаг оставлял корнем литерал «Items» — форма платформы объявлялась битой (типовые
|
||||
# НастройкаПравилОбработкиЗаявокСотрудников в БП и ERP).
|
||||
items_hops = 0
|
||||
items_broken = False
|
||||
while root_attr == 'Items':
|
||||
items_hops += 1
|
||||
if items_hops > 10: # страховка от кольца ссылок
|
||||
items_broken = True
|
||||
break
|
||||
if len(segments) < 3 or segments[2] != 'CurrentData':
|
||||
report_warn(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — unknown Items.* shape, expected Items.<Table>.CurrentData.*")
|
||||
continue
|
||||
items_broken = True
|
||||
break
|
||||
table_name = segments[1]
|
||||
table_el = None
|
||||
for candidate in all_elements:
|
||||
@@ -466,14 +561,19 @@ def main():
|
||||
if table_el is None:
|
||||
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — table element '{table_name}' not found")
|
||||
path_errors += 1
|
||||
continue
|
||||
items_broken = True
|
||||
break
|
||||
table_dp_node = table_el["Node"].find(f"{{{F_NS}}}DataPath")
|
||||
if table_dp_node is None or not (table_dp_node.text or "").strip():
|
||||
continue
|
||||
items_broken = True
|
||||
break
|
||||
table_dp = re.sub(r'\[\d+\]', '', (table_dp_node.text or "").strip())
|
||||
if table_dp.startswith('~'):
|
||||
table_dp = table_dp[1:]
|
||||
root_attr = table_dp.split(".")[0]
|
||||
segments = table_dp.split(".")
|
||||
root_attr = segments[0]
|
||||
if items_broken:
|
||||
continue
|
||||
|
||||
if root_attr not in attr_map:
|
||||
report_error(f"[{tag}] '{el_name}': {b_tag}='{data_path}' — attribute '{root_attr}' not found")
|
||||
@@ -487,6 +587,8 @@ def main():
|
||||
path_msg = f"{path_msg}, {skip_note}" if path_msg else skip_note
|
||||
if path_errors == 0 and path_msg:
|
||||
report_ok(f"Data bindings: {path_msg}")
|
||||
elif path_errors == 0:
|
||||
report_ok("Data bindings: none")
|
||||
|
||||
# --- Check 6: Button command references ---
|
||||
if not stopped:
|
||||
@@ -521,6 +623,8 @@ def main():
|
||||
|
||||
if cmd_errors == 0 and cmd_checked > 0:
|
||||
report_ok(f"Command references: {cmd_checked} buttons checked")
|
||||
elif cmd_checked == 0:
|
||||
report_ok("Command references: none")
|
||||
|
||||
# --- Check 7: Events have handler names ---
|
||||
if not stopped:
|
||||
@@ -560,12 +664,19 @@ def main():
|
||||
|
||||
if event_errors == 0 and event_checked > 0:
|
||||
report_ok(f"Event handlers: {event_checked} events checked")
|
||||
elif event_checked == 0:
|
||||
report_ok("Event handlers: none")
|
||||
|
||||
# --- Check 8: Command actions ---
|
||||
if not stopped:
|
||||
action_errors = 0
|
||||
action_checked = 0
|
||||
|
||||
# Предупреждение, а не ошибка: <Action> может назначаться в рантайме
|
||||
# (`Команда.Действие = "Подключаемый_…"` в ПриСозданииНаСервере) — приём типовых конфигураций
|
||||
# там, где обработчик существует не во всякой сборке. Назначать может и чужой модуль
|
||||
# (переопределяемый слой, подключаемые команды), так что по одному Form.xml не решить.
|
||||
# Корпус УТ/БП/ERP: 406 таких команд на 275 формах, произведённых платформой.
|
||||
for cmd in cmd_nodes:
|
||||
if stopped:
|
||||
break
|
||||
@@ -573,11 +684,13 @@ def main():
|
||||
action_node = cmd.find(f"{{{F_NS}}}Action")
|
||||
action_checked += 1
|
||||
if action_node is None or not (action_node.text or "").strip():
|
||||
report_error(f"Command '{cmd_name}': missing or empty Action")
|
||||
report_warn(f"Command '{cmd_name}': no Action — handler must be assigned at runtime, otherwise the command does nothing")
|
||||
action_errors += 1
|
||||
|
||||
if action_errors == 0 and action_checked > 0:
|
||||
report_ok(f"Command actions: {action_checked} commands checked")
|
||||
elif action_checked == 0:
|
||||
report_ok("Command actions: none")
|
||||
|
||||
# --- Check 9: MainAttribute count ---
|
||||
if not stopped:
|
||||
@@ -708,6 +821,39 @@ def main():
|
||||
if (ext_attr_count + ext_cmd_count) > 0:
|
||||
report_ok(f"Extension ID ranges: {ext_attr_count} attr(s), {ext_cmd_count} cmd(s) \u2014 all >= 1000000")
|
||||
|
||||
# 11d. \u041f\u0443\u0442\u0438 \u043d\u0430 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442, \u043a\u043e\u0442\u043e\u0440\u043e\u0433\u043e \u0444\u043e\u0440\u043c\u0430 \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u044f\u0435\u0442.
|
||||
# Check 5 \u0442\u0430\u043a\u043e\u0435 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u0435\u0442: \u0443 \u0437\u0430\u0438\u043c\u0441\u0442\u0432\u043e\u0432\u0430\u043d\u043d\u043e\u0439 \u0444\u043e\u0440\u043c\u044b \u043e\u043d \u043d\u0435 \u043f\u0440\u043e\u0432\u0435\u0440\u044f\u0435\u0442 \u0431\u0430\u0437\u043e\u0432\u044b\u0435 \u044d\u043b\u0435\u043c\u0435\u043d\u0442\u044b (id < 1000000),
|
||||
# \u0430 \u043f\u0440\u0438\u0432\u044f\u0437\u043a\u0438 \u0432 <xr:Link> \u0432\u043e\u043e\u0431\u0449\u0435 \u0432\u043d\u0435 \u0435\u0433\u043e \u0441\u043f\u0438\u0441\u043a\u0430 \u0442\u0435\u0433\u043e\u0432. \u041c\u0435\u0436\u0434\u0443 \u0442\u0435\u043c \u044d\u0442\u043e \u0440\u043e\u0432\u043d\u043e \u0442\u043e\u0442 \u0441\u043b\u0443\u0447\u0430\u0439, \u043d\u0430 \u043a\u043e\u0442\u043e\u0440\u043e\u043c
|
||||
# \u043f\u043b\u0430\u0442\u0444\u043e\u0440\u043c\u0430 \u043e\u0442\u0432\u0435\u0440\u0433\u0430\u0435\u0442 \u0437\u0430\u0433\u0440\u0443\u0437\u043a\u0443: \u00ab\u041d\u0435\u0432\u0435\u0440\u043d\u044b\u0439 \u043f\u0443\u0442\u044c \u043a \u043f\u043e\u043b\u044e - \u041e\u0431\u044a\u0435\u043a\u0442.X\u00bb. \u041f\u0440\u0430\u0432\u0438\u043b\u043e: \u0435\u0441\u043b\u0438 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0439 \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442
|
||||
# \u043d\u0435 \u043e\u0431\u044a\u044f\u0432\u043b\u0435\u043d \u0432 <Attributes> \u0444\u043e\u0440\u043c\u044b, \u043b\u044e\u0431\u043e\u0439 \u043f\u0443\u0442\u044c \u0441 \u0435\u0433\u043e \u043a\u043e\u0440\u043d\u0435\u043c \u043d\u0435 \u0440\u0430\u0437\u0440\u0435\u0448\u0438\u0442\u0441\u044f.
|
||||
# \u041a\u043e\u0440\u0435\u043d\u044c \u0431\u0435\u0440\u0451\u0442\u0441\u044f \u0438\u0437 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u0433\u043e \u0440\u0435\u043a\u0432\u0438\u0437\u0438\u0442\u0430 BaseForm: \u00ab\u041e\u0431\u044a\u0435\u043a\u0442\u00bb \u043e\u043d \u0442\u043e\u043b\u044c\u043a\u043e \u0443 \u0444\u043e\u0440\u043c\u044b \u043e\u0431\u044a\u0435\u043a\u0442\u0430, \u0443 \u0444\u043e\u0440\u043c\u044b
|
||||
# \u0441\u043f\u0438\u0441\u043a\u0430 \u044d\u0442\u043e \u00ab\u0421\u043f\u0438\u0441\u043e\u043a\u00bb, \u0443 \u0444\u043e\u0440\u043c\u044b \u0437\u0430\u043f\u0438\u0441\u0438 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430 \u00ab\u0417\u0430\u043f\u0438\u0441\u044c\u00bb. \u0421 \u0437\u0430\u0448\u0438\u0442\u044b\u043c \u00ab\u041e\u0431\u044a\u0435\u043a\u0442\u00bb \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u043d\u0430 \u0442\u0430\u043a\u0438\u0445
|
||||
# \u0444\u043e\u0440\u043c\u0430\u0445 \u043c\u043e\u043b\u0447\u0430 \u043d\u0435 \u0441\u0440\u0430\u0431\u0430\u0442\u044b\u0432\u0430\u043b\u0430.
|
||||
main_attr_declared = False
|
||||
for attr in attr_nodes:
|
||||
ma_node = attr.find(f"{{{F_NS}}}MainAttribute")
|
||||
if ma_node is not None and (ma_node.text or "").strip() == "true":
|
||||
main_attr_declared = True
|
||||
break
|
||||
|
||||
if not main_attr_declared:
|
||||
# \u0417\u043d\u0430\u0447\u0435\u043d\u0438\u044f \u043f\u0440\u0438\u0432\u044f\u0437\u043e\u043a \u0438\u0449\u0435\u043c \u0442\u0435\u043a\u0441\u0442\u043e\u043c: \u0438\u043d\u0442\u0435\u0440\u0435\u0441\u0443\u044e\u0442 \u0438 \u043e\u0431\u044b\u0447\u043d\u044b\u0435 \u0442\u0435\u0433\u0438, \u0438 <xr:DataPath> \u0432\u043d\u0443\u0442\u0440\u0438
|
||||
# <ChoiceParameterLinks>, \u0430 \u0442\u0435 \u0436\u0438\u0432\u0443\u0442 \u0432 \u0447\u0443\u0436\u043e\u043c \u043f\u0440\u043e\u0441\u0442\u0440\u0430\u043d\u0441\u0442\u0432\u0435 \u0438\u043c\u0451\u043d.
|
||||
with open(form_path, "r", encoding="utf-8-sig") as fh:
|
||||
raw_form = fh.read()
|
||||
main_base = base_form_node.find(f"{{{F_NS}}}Attributes/{{{F_NS}}}Attribute[{{{F_NS}}}MainAttribute='true']")
|
||||
root_name = main_base.get("name") if main_base is not None and main_base.get("name") else "\u041e\u0431\u044a\u0435\u043a\u0442"
|
||||
root_pat = re.escape(root_name)
|
||||
dangling_paths = set(re.findall(
|
||||
r'<(?:\w+:)?\w*DataPath[^>]*>(' + root_pat + r'\.[^<]+)</(?:\w+:)?\w*DataPath>', raw_form))
|
||||
if dangling_paths:
|
||||
shown = sorted(dangling_paths)
|
||||
sample = ", ".join(shown[:3])
|
||||
suffix = f" (\u0438 \u0435\u0449\u0451 {len(shown) - 3})" if len(shown) > 3 else ""
|
||||
report_error(f"Path(s) rooted at '{root_name}' but the form declares no MainAttribute: {sample}{suffix}")
|
||||
elif main_base is not None:
|
||||
report_ok("Object paths: none dangling (MainAttribute not declared)")
|
||||
|
||||
# Check callType without BaseForm
|
||||
if not stopped and not is_extension:
|
||||
call_type_without_base = False
|
||||
@@ -770,6 +916,62 @@ def main():
|
||||
else:
|
||||
report_ok('12. Types: no type values to check')
|
||||
|
||||
# --- Check 13: префиксы в значениях объявлены в самом файле ---
|
||||
# `cfg:DataProcessorObject.X` в <v8:Type> при незадекларированном xmlns:cfg — валидный XML, который
|
||||
# платформа не читает вовсе: «Исключение XDTO произошло при чтении файла». Ошибка типична для
|
||||
# рукописного XML: префикс скопирован из чужой формы, а объявление в корне забыто. Область видимости
|
||||
# считаем по узлу (nsmap элемента), а не по корню: локальная xmlns на элементе законна.
|
||||
if not stopped:
|
||||
prefix_errors = 0
|
||||
prefix_checked = 0
|
||||
prefix_re = re.compile(r'^([A-Za-z_][A-Za-z0-9_.-]*):.+$')
|
||||
|
||||
for node in root.iter():
|
||||
if not isinstance(node.tag, str):
|
||||
continue
|
||||
ln = localname(node)
|
||||
values = []
|
||||
if ln in ('Type', 'TypeSet'):
|
||||
values.append((node.text or '').strip())
|
||||
xsi_type = node.get(f'{{{"http://www.w3.org/2001/XMLSchema-instance"}}}type')
|
||||
if xsi_type:
|
||||
values.append(xsi_type.strip())
|
||||
for val in values:
|
||||
if not val:
|
||||
continue
|
||||
m = prefix_re.match(val)
|
||||
if not m:
|
||||
continue
|
||||
prefix_checked += 1
|
||||
pfx = m.group(1)
|
||||
if pfx not in node.nsmap:
|
||||
kind = "xsi:type" if val == xsi_type else "Type"
|
||||
report_error(f"13. {kind} '{val}': namespace prefix '{pfx}:' is not declared "
|
||||
"— the platform cannot read the file (XDTO)")
|
||||
prefix_errors += 1
|
||||
|
||||
if prefix_checked == 0:
|
||||
report_ok('13. Namespace prefixes: nothing to check')
|
||||
elif prefix_errors == 0:
|
||||
report_ok(f'13. Namespace prefixes: {prefix_checked} values, all declared')
|
||||
|
||||
# --- Check 14: версия формата формы совпадает с версией выгрузки ---
|
||||
# Версию задаёт платформа, которой выгружали, и в пределах одной выгрузки она едина. Форма из
|
||||
# другой версии — «Неизвестная версия формата N загружаемого файла»: платформа не читает файл,
|
||||
# который новее её самой. Источник версии ищем общим helper-ом: он же покрывает автономную
|
||||
# внешнюю обработку/отчёт, где Configuration.xml нет и версию несёт корень самой обработки.
|
||||
if not stopped and version_anchor:
|
||||
form_ver = root.get('version', '')
|
||||
dump_ver = detect_format_version(os.path.dirname(os.path.abspath(form_path)))
|
||||
|
||||
if not form_ver:
|
||||
report_ok('14. Format version: not comparable')
|
||||
elif form_ver != dump_ver:
|
||||
report_error(f'14. Format version {form_ver} differs from the dump ({dump_ver}) '
|
||||
'— a dump carries one version, the platform refuses a file it cannot read')
|
||||
else:
|
||||
report_ok(f'14. Format version: {form_ver}, matches the dump')
|
||||
|
||||
# --- Finalize ---
|
||||
checks = ok_count + errors + warnings
|
||||
if errors == 0 and warnings == 0 and not detailed:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.18 — Validate 1C metadata object structure
|
||||
# meta-validate v1.19 — Validate 1C metadata object structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -153,6 +153,19 @@ $finalize = {
|
||||
}
|
||||
}
|
||||
|
||||
# --- Format version ---
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
$formatVerifiedMin = "2.17"
|
||||
$formatVerifiedMax = "2.21"
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
|
||||
# --- Reference tables ---
|
||||
|
||||
$guidPattern = '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$'
|
||||
@@ -350,12 +363,15 @@ if ($root.NamespaceURI -ne $expectedNs) {
|
||||
|
||||
# Version attribute
|
||||
$version = $root.GetAttribute("version")
|
||||
$versionRank = Get-FormatRank $version
|
||||
if (-not $version) {
|
||||
Report-Warn "1. Missing version attribute on MetaDataObject"
|
||||
} elseif ($version -notin @("2.17", "2.18", "2.19", "2.20", "2.21")) {
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
|
||||
# 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
|
||||
Report-Warn "1. Unusual version '$version' (expected 2.17-2.21)"
|
||||
} elseif ($versionRank -eq 0) {
|
||||
Report-Error "1. Malformed version '$version' (expected N.N)"
|
||||
} elseif ($versionRank -lt (Get-FormatRank $formatVerifiedMin)) {
|
||||
Report-Warn "1. Format version '$version' is below the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
} elseif ($versionRank -gt (Get-FormatRank $formatVerifiedMax)) {
|
||||
Report-Warn "1. Format version '$version' is above the tested range $formatVerifiedMin-$formatVerifiedMax — skills were not verified on it"
|
||||
}
|
||||
|
||||
# Detect type element — exactly one child element in md namespace
|
||||
@@ -1515,12 +1531,7 @@ $versionedProps = @{
|
||||
"AuxiliaryVariantForm" = "2.21" # вспомогательная форма варианта отчёта
|
||||
"UseInInterfaceCompatibilityMode" = "2.21" # использование общей формы в режиме совместимости интерфейса
|
||||
}
|
||||
# Версия формата как число: "2.20" → 220. Строковое сравнение неверно ("2.9" > "2.17").
|
||||
function Get-FormatRank([string]$ver) {
|
||||
if ($ver -match '^(\d+)\.(\d+)$') { return [int]$Matches[1] * 100 + [int]$Matches[2] }
|
||||
return 0
|
||||
}
|
||||
$fileRank = Get-FormatRank $version
|
||||
$fileRank = $versionRank
|
||||
if ($fileRank -gt 0) {
|
||||
foreach ($vp in ($versionedProps.Keys | Sort-Object)) {
|
||||
$nodes = $xmlDoc.SelectNodes("//md:$vp | //xr:$vp", $ns)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# meta-validate v1.18 — Validate 1C metadata object structure (Python port)
|
||||
# meta-validate v1.19 — Validate 1C metadata object structure (Python port)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import os
|
||||
@@ -175,6 +175,21 @@ def finalize():
|
||||
print(f"Written to: {out_file}")
|
||||
|
||||
|
||||
# ── Format version ───────────────────────────────────────────
|
||||
# Проверенный диапазон версий формата выгрузки: 2.17 (8.3.24) … 2.21 (8.5). Полная лестница —
|
||||
# docs/1c-configuration-spec.md, «Лестница версий». Версию задаёт платформа ВЫГРУЗКИ, а не режим
|
||||
# совместимости конфигурации. Версии ниже 2.17 (платформы 8.3.23 и старше) существуют, но навыки
|
||||
# на них не проверялись — это предупреждение о непокрытии, а не о некорректности файла.
|
||||
FORMAT_VERIFIED_MIN = "2.17"
|
||||
FORMAT_VERIFIED_MAX = "2.21"
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
# ── Reference tables ─────────────────────────────────────────
|
||||
|
||||
guid_pattern = re.compile(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$')
|
||||
@@ -399,12 +414,17 @@ if root_ns != expected_ns:
|
||||
|
||||
# Version attribute
|
||||
version = root.get("version", "")
|
||||
version_rank = format_rank(version)
|
||||
if not version:
|
||||
report_warn("1. Missing version attribute on MetaDataObject")
|
||||
elif version not in ("2.17", "2.18", "2.19", "2.20", "2.21"):
|
||||
# Лестница версий формата: 2.17 (8.3.20-8.3.24), 2.18 (8.3.25), 2.19 (8.3.26),
|
||||
# 2.20 (8.3.27), 2.21 (8.5). Версию задаёт платформа ВЫГРУЗКИ, а не режим совместимости.
|
||||
report_warn(f"1. Unusual version '{version}' (expected 2.17-2.21)")
|
||||
elif version_rank == 0:
|
||||
report_error(f"1. Malformed version '{version}' (expected N.N)")
|
||||
elif version_rank < format_rank(FORMAT_VERIFIED_MIN):
|
||||
report_warn(f"1. Format version '{version}' is below the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
elif version_rank > format_rank(FORMAT_VERIFIED_MAX):
|
||||
report_warn(f"1. Format version '{version}' is above the tested range "
|
||||
f"{FORMAT_VERIFIED_MIN}-{FORMAT_VERIFIED_MAX} — skills were not verified on it")
|
||||
|
||||
# Detect type element -- exactly one child element in md namespace
|
||||
type_node = None
|
||||
@@ -1437,14 +1457,7 @@ versioned_props = {
|
||||
"UseInInterfaceCompatibilityMode": "2.21", # использование общей формы в режиме совместимости интерфейса
|
||||
}
|
||||
|
||||
|
||||
def format_rank(ver):
|
||||
""""2.20" → 220, "2.9" → 209. Строковое сравнение неверно ("2.9" > "2.17")."""
|
||||
m = re.match(r'^(\d+)\.(\d+)$', ver or '')
|
||||
return int(m.group(1)) * 100 + int(m.group(2)) if m else 0
|
||||
|
||||
|
||||
file_rank = format_rank(version)
|
||||
file_rank = version_rank
|
||||
if file_rank > 0:
|
||||
for vp in sorted(versioned_props):
|
||||
nodes = find_all(root, f"//md:{vp} | //xr:{vp}")
|
||||
|
||||
@@ -41,26 +41,189 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-compile.ps1" -J
|
||||
|
||||
**Если макет создаётся по изображению** (скриншот, скан печатной формы) — сначала вызвать `/img-grid` для наложения сетки, по ней определить границы колонок и пропорции, затем использовать `"Nx"` ширины + `"page"` для автоматического расчёта размеров.
|
||||
|
||||
## JSON-схема DSL
|
||||
## Что читать под задачу
|
||||
|
||||
Ниже — компактная структура и ключевые правила, достаточные для типового макета. Полные таблицы полей (все свойства шрифтов, стилей, ячеек), развёрнутый пример и ограничения формата — в **`reference/dsl-spec.md`**; нужны не всегда, читать по необходимости.
|
||||
Ниже — всё, что нужно для типового макета. Остальное лежит по файлу на задачу, читать нужно
|
||||
только свой:
|
||||
|
||||
Краткая структура:
|
||||
| Задача | Ключи | Файл |
|
||||
|---|---|---|
|
||||
| Свойство стиля вне частых: отступ, защита, узор, маска, поведение при выводе | — | `reference/style-properties.md` |
|
||||
| Область, не описываемая диапазоном строк; свои ширины колонок у части документа | `namedAreas`, `columnSets`, `columnSet` | `reference/layout.md` |
|
||||
| Колонтитулы, ориентация, поля, масштаб | `header`, `footer`, `printSettings` | `reference/print.md` |
|
||||
| Картинка, фигура или надпись поверх сетки; картинка в ячейке | `pictures`, `drawings`, `pictureParameter` | `reference/drawings.md` |
|
||||
| Ячейки для ввода данных пользователем | `valueType`, `controlType`, `value` | `reference/input-cells.md` |
|
||||
| Сворачиваемые группы строк или колонок | `rowGroups`, `columnGroups` | `reference/groups.md` |
|
||||
| Всплывающая подсказка у ячейки | `note` | `reference/notes.md` |
|
||||
|
||||
```
|
||||
{ columns, page, defaultWidth, columnWidths,
|
||||
fonts: { name: { face, size, bold, italic, underline, strikeout } },
|
||||
styles: { name: { font, align, valign, border, borderWidth, wrap, format } },
|
||||
areas: [{ name, rows: [{ height, rowStyle, cells: [
|
||||
{ col, span, rowspan, style, param, detail, text, template }
|
||||
]}]}]
|
||||
## Пример
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 5,
|
||||
"columnWidths": { "1": 5, "2": 40, "3-5": 12 },
|
||||
"fonts": { "жирный": { "face": "Arial", "size": 10, "bold": true } },
|
||||
"styles": {
|
||||
"шапка": { "font": "жирный", "horizontalAlignment": "Center", "textPlacement": "Wrap", "border": "Solid" },
|
||||
"клетка": { "border": "Solid" },
|
||||
"число": { "border": "Solid", "horizontalAlignment": "Right", "format": "ЧЦ=15; ЧДЦ=2" },
|
||||
"итог": { "font": "жирный", "horizontalAlignment": "Right", "topBorder": "Solid" }
|
||||
},
|
||||
"areas": [
|
||||
{ "name": "Заголовок", "rows": [
|
||||
{ "height": 20, "cells": [{ "col": 1, "span": 5, "style": "шапка", "param": "ЗаголовокОтчёта" }] },
|
||||
{}
|
||||
]},
|
||||
{ "name": "ШапкаТаблицы", "rows": [
|
||||
{ "rowStyle": "шапка", "cells": ["№", "Номенклатура", "Количество", "Цена", "Сумма"] }
|
||||
]},
|
||||
{ "name": "Строка", "rows": [
|
||||
{ "rowStyle": "клетка", "cells": [
|
||||
{ "col": 1, "param": "НомерСтроки" },
|
||||
{ "col": 2, "param": "Товар", "detail": "Номенклатура" },
|
||||
{ "col": 3, "style": "число", "param": "Количество" },
|
||||
{ "col": 4, "style": "число", "param": "Цена" },
|
||||
{ "col": 5, "style": "число", "param": "Сумма" }
|
||||
]}
|
||||
]},
|
||||
{ "name": "Итого", "rows": [
|
||||
{ "rowStyle": "итог", "cells": [null, null, null, "Итого:", "{Всего}"] }
|
||||
]}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Ключевые правила:
|
||||
- `page` — формат страницы (`"A4-landscape"`, `"A4-portrait"` или число). Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"`
|
||||
- `col` — 1-based позиция колонки
|
||||
- `rowStyle` — автозаполнение пустот стилем (рамки по всей ширине)
|
||||
- Тип заполнения определяется автоматически: `param` → Parameter, `text` → Text, `template` → Template
|
||||
- `rowspan` — объединение строк вниз (rowStyle учитывает занятые ячейки)
|
||||
- `empty` в строке — шорткат для N подряд пустых строк (`{ "empty": 3 }` = три `{}`)
|
||||
Шапка таблицы и итог записаны позиционно — позиция берётся из порядка, `col` не нужен;
|
||||
строка таблицы записана ячейками с `col`, потому что у них разные стили и расшифровка.
|
||||
|
||||
## Структура DSL
|
||||
|
||||
```
|
||||
{ columns, page, defaultWidth, columnWidths, columnStyles, textLanguages,
|
||||
fonts: { имя: { face, size, bold, italic, underline, strikeout } | { ref } },
|
||||
styles: { имя: { font, horizontalAlignment, verticalAlignment, textPlacement,
|
||||
border, leftBorder, topBorder, rightBorder, bottomBorder,
|
||||
borderColor, backColor, textColor, format } },
|
||||
areas: [{ name, columnSet, rows: [
|
||||
{ height, hidden, rowStyle, empty, cells: [
|
||||
{ col, span, rowspan, style, param, detail, text, template, note,
|
||||
valueType, controlType, value, pictureParameter } ] } ] }],
|
||||
namedAreas, columnSets, rowGroups, columnGroups, header, footer, printSettings,
|
||||
pictures, drawings
|
||||
}
|
||||
```
|
||||
|
||||
Верхний уровень: `columns` обязателен, остальное по необходимости.
|
||||
|
||||
| Ключ | Описание |
|
||||
|---|---|
|
||||
| `columns` | Количество колонок раскладки по умолчанию |
|
||||
| `page` | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Сам вычисляет `defaultWidth` из суммы пропорций `"Nx"` |
|
||||
| `defaultWidth` | Ширина колонок по умолчанию (10) |
|
||||
| `columnWidths` | Ширины: ключи 1-based (`"1"`, `"3-14"`, `"5,7,9"`), значение — число или `"2x"` (доля от `defaultWidth`) |
|
||||
| `columnStyles` | Стиль на колонку целиком: те же ключи диапазонов, значение — имя стиля |
|
||||
| `textLanguages` | Языки, на которые разворачивается текст, заданный строкой (по умолчанию `["ru"]`) |
|
||||
| `areas` | Области — диапазоны подряд идущих строк, в порядке документа |
|
||||
|
||||
## Области, строки, ячейки
|
||||
|
||||
**Область** (`areas[]`) — диапазон подряд идущих строк: `name` (необязательно; с именем область
|
||||
доступна как `Макет.ПолучитьОбласть("Имя")`) и `rows`. Область без имени — просто кусок сетки.
|
||||
|
||||
**Строка** (`rows[]`): `height`, `hidden`, `rowStyle`, `cells`. Пустая строка — `{}`,
|
||||
а `{ "empty": 3 }` заменяет три подряд. `height` и `hidden` — свойства самой строки, у ячейки
|
||||
таких нет.
|
||||
|
||||
**Ячейка** (`cells[]`):
|
||||
|
||||
| Ключ | Описание |
|
||||
|---|---|
|
||||
| `col` | Позиция колонки, 1-based |
|
||||
| `span` / `rowspan` | Объединение вправо / вниз |
|
||||
| `style` | Имя стиля; перекрывает `rowStyle` для этой ячейки |
|
||||
| `param` | Параметр заполнения — `Область.Параметры.Имя = …` |
|
||||
| `text` | Статический текст: строка или объект «язык → текст» |
|
||||
| `template` | Текст со вставками `[Параметр]` |
|
||||
| `detail` | Параметр расшифровки; ставится и без `param` |
|
||||
|
||||
Содержимое задаётся ровно одним ключом из `param` / `text` / `template`; ячейка без них —
|
||||
пустая (нужна, например, ради рамки). Пустая строка `"text": ""` — это тоже текст, а не
|
||||
отсутствие текста.
|
||||
|
||||
### Короткая форма: список ячеек позиционно
|
||||
|
||||
Позиция берётся из порядка, `col` не нужен:
|
||||
|
||||
| Элемент | Значение |
|
||||
|---|---|
|
||||
| `"текст"` | Статический текст |
|
||||
| `{ "ru": "…", "en": "…" }` | Тот же текст на нескольких языках |
|
||||
| `"{Имя}"` | Параметр заполнения |
|
||||
| `">"` | Продолжение ячейки слева — увеличивает её `span` |
|
||||
| `"|"` | Продолжение ячейки сверху — увеличивает её `rowspan` |
|
||||
| `null` | Пропуск колонки |
|
||||
| `{ … }` | Обычная ячейка без `col` — когда нужны `style`, `detail`, `template` |
|
||||
|
||||
```json
|
||||
"rows": [
|
||||
["Вид", "Остаток", ">", "Итог"],
|
||||
["|", "начало", "конец", "|"],
|
||||
["{Вид}", "{Нач}", "{Кон}", "{Итог}"]
|
||||
]
|
||||
```
|
||||
|
||||
Здесь «Вид» и «Итог» объединены по вертикали, «Остаток» — по горизонтали на две колонки.
|
||||
|
||||
Позиционный список — это форма записи ЯЧЕЕК, а не строки. Если у строки нет своих свойств, он
|
||||
и есть строка (как в примере выше). Если нужны `height`, `hidden` или `rowStyle` — тот же
|
||||
список кладётся в `cells`:
|
||||
|
||||
```json
|
||||
{ "rowStyle": "итог", "cells": [null, null, null, "Итого:", "{Всего}"] }
|
||||
```
|
||||
|
||||
### `rowStyle` — оформление строки
|
||||
|
||||
Стиль ложится на ВСЮ ширину строки: колонки без явных ячеек получают его тоже — так выходят
|
||||
сплошные рамки в табличной части. Он же становится оформлением самой строки. Ячейки с `rowspan`
|
||||
из предыдущих строк при этом пропускаются.
|
||||
|
||||
## Оформление
|
||||
|
||||
Ячейка, строка и колонка ссылаются на один и тот же именованный стиль: ячейка — ключом `style`,
|
||||
строка — `rowStyle`, колонка — через `columnStyles`.
|
||||
|
||||
**Шрифт** (`fonts.<имя>`): `face` (Arial), `size` (10), `bold`, `italic`, `underline`,
|
||||
`strikeout`. Либо ссылка вместо описания: `{ "ref": "style:TextFont" }`,
|
||||
`{ "ref": "sys:DefaultGUIFont" }`. Шрифт `"default"` берётся, когда стиль не указал свой.
|
||||
|
||||
**Частые ключи стиля** — имя ключа совпадает с именем свойства в выгрузке, значения перечислений
|
||||
регистр не различают:
|
||||
|
||||
| Ключ | Значение |
|
||||
|---|---|
|
||||
| `font` | Имя из `fonts` |
|
||||
| `horizontalAlignment` | `Left`, `Center`, `Right`, `Justify`, `Auto` |
|
||||
| `verticalAlignment` | `Top`, `Center`, `Bottom` |
|
||||
| `textPlacement` | Длинный текст: `Wrap` (перенос), `Cut` (обрезать), `Block`, `Auto` |
|
||||
| `border` | Рамка со всех сторон |
|
||||
| `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | Отдельная сторона |
|
||||
| `borderColor` | Цвет рамки |
|
||||
| `backColor`, `textColor` | Цвет фона и текста |
|
||||
| `format` | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` |
|
||||
|
||||
**Рамка**: `"Solid"` (ширина 1) либо `{ "style": "Solid", "width": 2 }`. Стили линии: `None`,
|
||||
`Solid`, `Dotted`, `Dashed`, `DashDotted`, `DashDottedDotted`, `ThinDashed`, `LargeDashed`,
|
||||
`ThickDashed`, `Double`. Стороны можно задавать по отдельности всегда: совпавшие четыре
|
||||
компилятор свернёт сам.
|
||||
|
||||
**Цвет** — нотация платформы: `#RRGGBB`, `style:ИмяСтиля` (элемент стиля конфигурации),
|
||||
`web:Имя`, `win:Имя`. Несуществующее имя платформа отвергнет при загрузке.
|
||||
|
||||
```json
|
||||
"styles": {
|
||||
"шапка": { "font": "жирный", "horizontalAlignment": "Center", "backColor": "#EBEBEB" },
|
||||
"рамка": { "border": { "style": "Solid", "width": 2 } },
|
||||
"снизу": { "bottomBorder": "Dotted" }
|
||||
}
|
||||
```
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
# Рисунки и картинки
|
||||
|
||||
Рисунок — объект поверх сетки ячеек: картинка, фигура или надпись. Его положение задают два
|
||||
якоря: ячейка плюс смещение в точках от её левого верхнего угла.
|
||||
|
||||
```json
|
||||
"pictures": {
|
||||
"знак": { "ref": "v8ui:Стоп48" },
|
||||
"логотип": { "data": "iVBORw0KGgo...", "transparent": { "x": 24, "y": 29 } }
|
||||
},
|
||||
"drawings": [
|
||||
{ "type": "Picture", "picture": "логотип", "name": "Логотип",
|
||||
"begin": { "row": 1, "col": 1 },
|
||||
"end": { "row": 3, "col": 2, "dy": 34, "dx": 115 },
|
||||
"pictureSize": "Proportionally", "detail": "ПараметрРасшифровки" },
|
||||
{ "type": "Rectangle", "style": "заливка",
|
||||
"line": { "style": "Dashed", "width": 2 },
|
||||
"sides": { "left": true, "top": true, "right": false, "bottom": false },
|
||||
"begin": { "row": 5, "col": 1 }, "end": { "row": 6, "col": 3 } },
|
||||
{ "type": "Text", "text": { "ru": "Подпись", "en": "Signature" },
|
||||
"begin": { "row": 8, "col": 1 }, "end": { "row": 8, "col": 3, "dy": 12 } }
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `type` | нет | `Picture` | `Picture`, `Rectangle`, `Ellipse`, `Line`, `Text`, `Chart`, `GanttChart` |
|
||||
| `begin` / `end` | да | — | Якоря: `row`, `col` (1-based) и смещения `dy`, `dx` в точках |
|
||||
| `picture` | нет | — | Имя записи из `pictures` |
|
||||
| `pictureSize` | нет | `Stretch` | Как картинка заполняет прямоугольник: `Stretch`, `AutoSize`, `Proportionally`, `RealSize` |
|
||||
| `text` | нет | — | Надпись: строка или объект «язык → текст» |
|
||||
| `name` | нет | — | Имя рисунка, по которому к нему обращаются из кода |
|
||||
| `detail` | нет | — | Параметр расшифровки |
|
||||
| `style` | нет | — | Именованный стиль: заливка, шрифт, выравнивание надписи |
|
||||
| `line` | нет | — | Линия рисунка (она же его рамка) — как `border` у ячейки |
|
||||
| `sides` | нет | — | Какие стороны рамки видны: `left`, `top`, `right`, `bottom` |
|
||||
| `id` | нет | номер по порядку | Идентификатор рисунка |
|
||||
| `zOrder` | нет | номер по порядку | Порядок перекрытия: чем больше, тем выше |
|
||||
|
||||
Порядок в списке `drawings` — порядок в документе; кто кого перекрывает, задаёт `zOrder`.
|
||||
|
||||
Запись в `pictures` — либо ссылка (`ref`), либо сами данные в base64 (`data`). Ссылкой
|
||||
задаются и предопределённая картинка платформы, и общая картинка конфигурации — пишутся они
|
||||
одинаково, префиксом `v8ui:`. Пустая запись `{}` — картинка не задана, такое в макетах встречается.
|
||||
Прозрачность задаётся ключом `transparent` в одной из двух форм: `false` — прозрачного фона
|
||||
нет; `{ "x": …, "y": … }` — прозрачным считается цвет пикселя с этими координатами внутри
|
||||
картинки (по флажку «прозрачный фон» Конфигуратор берёт её правый нижний пиксель).
|
||||
|
||||
Одну запись `pictures` могут использовать несколько рисунков — данные в макете не дублируются.
|
||||
|
||||
## Картинка в ячейке
|
||||
|
||||
Картинка бывает не только поверх сетки, но и внутри ячейки — тогда её задаёт стиль, а сама
|
||||
картинка берётся из той же палитры `pictures`:
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 2,
|
||||
"pictures": { "стоп": { "ref": "v8ui:Стоп48" } },
|
||||
"styles": { "значок": { "picIndex": 1, "picHorizontalAlignment": "Center",
|
||||
"picVerticalAlignment": "Center", "textPosition": "Bottom" } },
|
||||
"areas": [{ "rows": [
|
||||
[{ "style": "значок", "text": "удалить", "pictureParameter": "Удалить" }]
|
||||
]}]
|
||||
}
|
||||
```
|
||||
|
||||
- `picIndex` — номер записи в `pictures`, считая с единицы в порядке объявления;
|
||||
- `picHorizontalAlignment`, `picVerticalAlignment`, `pictureSizeMode`, `textPosition` — как
|
||||
картинка стоит в ячейке и где относительно неё текст (значения — в `style-properties.md`);
|
||||
- `pictureParameter` — ключ самой ЯЧЕЙКИ, а не стиля: имя параметра, которым картинку
|
||||
подставляют из кода, как `param` подставляет текст. С текстом ячейки уживается.
|
||||
@@ -1,160 +0,0 @@
|
||||
# Спецификация MXL DSL — JSON-формат описания табличного документа
|
||||
|
||||
Компактный JSON-формат для описания макетов табличных документов 1С (SpreadsheetDocument). Используется навыком `/mxl-compile` (JSON → XML).
|
||||
|
||||
## Пример
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 10,
|
||||
"defaultWidth": 30,
|
||||
"columnWidths": { "1": 15, "2-8": 40, "9-10": 50 },
|
||||
|
||||
"fonts": {
|
||||
"default": { "face": "Arial", "size": 10 },
|
||||
"bold": { "face": "Arial", "size": 10, "bold": true },
|
||||
"header": { "face": "Arial", "size": 14, "bold": true }
|
||||
},
|
||||
|
||||
"styles": {
|
||||
"default": {},
|
||||
"header": { "font": "header", "align": "center" },
|
||||
"label": { "font": "bold" },
|
||||
"bordered": { "border": "all" },
|
||||
"bordered-right": { "border": "all", "align": "right" },
|
||||
"total-right": { "font": "bold", "border": "top", "align": "right" }
|
||||
},
|
||||
|
||||
"areas": [
|
||||
{
|
||||
"name": "Заголовок",
|
||||
"rows": [
|
||||
{ "height": 20, "cells": [
|
||||
{ "col": 1, "span": 10, "style": "header", "param": "ТекстЗаголовка" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ШапкаТаблицы",
|
||||
"rows": [
|
||||
{ "rowStyle": "bordered", "cells": [
|
||||
{ "col": 1, "text": "№" },
|
||||
{ "col": 2, "span": 6, "text": "Наименование" },
|
||||
{ "col": 9, "text": "Кол-во" },
|
||||
{ "col": 10, "text": "Сумма" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Строка",
|
||||
"rows": [
|
||||
{ "rowStyle": "bordered", "cells": [
|
||||
{ "col": 1, "param": "НомерСтроки" },
|
||||
{ "col": 2, "span": 6, "param": "Товар", "detail": "Номенклатура" },
|
||||
{ "col": 9, "style": "bordered-right", "param": "Количество" },
|
||||
{ "col": 10, "style": "bordered-right", "param": "Сумма" }
|
||||
]}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Итого",
|
||||
"rows": [
|
||||
{ "cells": [
|
||||
{ "col": 8, "span": 2, "style": "total-right", "text": "Итого:" },
|
||||
{ "col": 10, "style": "total-right", "param": "Всего" }
|
||||
]}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## Верхний уровень
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `columns` | да | — | Количество колонок |
|
||||
| `page` | нет | — | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` |
|
||||
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию. Игнорируется если задан `page` и все колонки используют `"Nx"` |
|
||||
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) |
|
||||
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
||||
| `styles` | нет | `{}` | Именованные стили |
|
||||
| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) |
|
||||
|
||||
## Шрифты (`fonts.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `face` | `"Arial"` | Имя шрифта |
|
||||
| `size` | `10` | Размер |
|
||||
| `bold` | `false` | Жирный |
|
||||
| `italic` | `false` | Курсив |
|
||||
| `underline` | `false` | Подчёркнутый |
|
||||
| `strikeout` | `false` | Зачёркнутый |
|
||||
|
||||
Шрифт `"default"` используется когда стиль не указывает шрифт явно. Если не определён, создаётся автоматически (Arial 10).
|
||||
|
||||
## Стили (`styles.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `font` | `"default"` | Ссылка на имя шрифта |
|
||||
| `align` | — | `left`, `center`, `right` |
|
||||
| `valign` | — | `top`, `center` |
|
||||
| `border` | — | Стороны рамки: `all`, `top`, `bottom`, `left`, `right`, `none`. Через запятую: `"top,bottom"` |
|
||||
| `borderWidth` | `"thin"` | Толщина рамки: `thin` (1px) или `thick` (2px) |
|
||||
| `wrap` | `false` | Перенос текста |
|
||||
| `format` | — | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` и т.д. |
|
||||
|
||||
## Области (`areas[]`)
|
||||
|
||||
| Поле | Обяз. | Описание |
|
||||
|------|:-----:|----------|
|
||||
| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` |
|
||||
| `rows` | да | Массив строк |
|
||||
|
||||
## Строки (`rows[]`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `height` | — | Высота строки (если не задана, используется авто) |
|
||||
| `rowStyle` | — | Стиль для ВСЕХ колонок (заполняет пустоты рамками) |
|
||||
| `cells` | `[]` | Массив ячеек |
|
||||
| `empty` | — | Количество подряд идущих пустых строк (заменяет N отдельных `{}`) |
|
||||
|
||||
Строка без `cells` и `rowStyle` → пустая строка. `{ "empty": 3 }` эквивалентно трём `{}`.
|
||||
|
||||
## Ячейки (`cells[]`)
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `col` | да | — | Позиция колонки (1-based) |
|
||||
| `span` | нет | `1` | Объединение по горизонтали (количество колонок) |
|
||||
| `rowspan` | нет | `1` | Объединение по вертикали (количество строк) |
|
||||
| `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) |
|
||||
| `param` | нет | — | Параметр заполнения |
|
||||
| `detail` | нет | — | Параметр расшифровки (только с `param`) |
|
||||
| `text` | нет | — | Статический текст |
|
||||
| `template` | нет | — | Шаблонный текст с `[Параметр]` |
|
||||
|
||||
### Тип заполнения
|
||||
|
||||
Определяется автоматически по содержимому ячейки:
|
||||
- `param` → fillType=Parameter
|
||||
- `template` → fillType=Template
|
||||
- `text` → fillType=Text
|
||||
- ничего → без fillType (пустая ячейка или рамка)
|
||||
|
||||
## `rowStyle` — автозаполнение
|
||||
|
||||
Когда задан `rowStyle`, компилятор создаёт ячейки для ВСЕХ колонок строки. Позиции без явных ячеек заполняются пустыми ячейками с указанным стилем. Это обеспечивает сплошные рамки в табличных строках.
|
||||
|
||||
Если в предыдущих строках той же области есть ячейки с `rowspan`, их колонки при автозаполнении пропускаются.
|
||||
|
||||
## Ограничения
|
||||
|
||||
Текущая версия не поддерживает:
|
||||
- Множественные наборы колонок (`columnsID`)
|
||||
- Области типа Columns / Rectangle
|
||||
- Рисунки (штрихкоды, картинки)
|
||||
- Фон ячеек
|
||||
@@ -0,0 +1,26 @@
|
||||
# Группы строк и колонок
|
||||
|
||||
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
|
||||
вложенность выражена вхождением одного диапазона в другой:
|
||||
|
||||
```json
|
||||
"rowGroups": [
|
||||
{ "rows": "2-4" },
|
||||
{ "rows": 3 },
|
||||
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
|
||||
],
|
||||
"columnGroups": [
|
||||
{ "cols": "2-3", "name": { "ru": "Показатели", "en": "Values" } },
|
||||
{ "cols": 3 }
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `rows` / `cols` | да | — | Диапазон 1-based: число или `"N-M"` |
|
||||
| `name` | нет | — | Имя группы: строка или объект «язык → текст» |
|
||||
| `collapsed` | нет | `false` | Свёрнута ли группа при открытии |
|
||||
| `titleLocation` | нет | `auto` | Расположение заголовка: `begin`, `end`, `auto` |
|
||||
|
||||
Диапазоны либо вложены, либо не пересекаются: частичное пересечение — ошибка. Порядок в списке
|
||||
не важен, число уровней вложенности считается само.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Ячейки-поля ввода
|
||||
|
||||
Ячейка может не показывать текст, а принимать значение от пользователя — так делают макеты для
|
||||
ввода данных. Достаточно задать тип значения:
|
||||
|
||||
```json
|
||||
{ "col": 1, "valueType": "Number(15,3,nonneg)" }
|
||||
{ "col": 2, "valueType": "String(10)" }
|
||||
{ "col": 3, "valueType": "Boolean", "controlType": "checkbox" }
|
||||
{ "col": 4, "valueType": "CatalogRef.Валюты" }
|
||||
{ "col": 5, "valueType": "Boolean + String + Date + CatalogRef.Валюты" }
|
||||
{ "col": 6, "valueType": "AnyRef" }
|
||||
```
|
||||
|
||||
Грамматика типа:
|
||||
|
||||
| Запись | Значение |
|
||||
|---|---|
|
||||
| `Boolean` | булево |
|
||||
| `String`, `String(10)`, `String(10,fixed)` | строка; без длины — неограниченная, `fixed` — фиксированной длины |
|
||||
| `Number`, `Number(15,3)`, `Number(15,3,nonneg)` | число; без параметров — без ограничения разрядности, `nonneg` — неотрицательное |
|
||||
| `Date`, `DateTime`, `Time` | дата, дата со временем, время |
|
||||
| `CatalogRef.Валюты`, `DocumentRef.Реализация`, `EnumRef.Статусы`, `DefinedType.Сумма` | ссылочный тип |
|
||||
| `CatalogRef`, `DocumentRef`, `AnyRef` | категория целиком: любая ссылка этого вида |
|
||||
| `A + B` | составной тип; повтор одного примитива недопустим |
|
||||
|
||||
Русские имена принимаются наравне с английскими: `Число(15,3)`, `Строка(10)`,
|
||||
`СправочникСсылка.Валюты`.
|
||||
|
||||
`text` и `template` в такой ячейке недопустимы — платформа не показывает текст там, где вводится
|
||||
значение. `param` и `detail` допустимы: они описывают заполнение и расшифровку, а не содержимое.
|
||||
|
||||
Пустой тип `"valueType": ""` — «ячейка содержит значение», но тип не ограничен.
|
||||
|
||||
Значение поля ввода задаётся ключом `value`. Его тип — тип литерала JSON: строка, число,
|
||||
`true`/`false`; к объявленному типу ячейки значение не приводится. Пустая строка означает
|
||||
пустое значение объявленного типа. Дата пишется строкой `ГГГГ-ММ-ДДTчч:мм:сс` и читается
|
||||
как дата у ячейки, объявленной датой; у ссылочного и составного типа значение — строка.
|
||||
|
||||
```json
|
||||
{ "col": 1, "valueType": "Number(15,3)", "value": 12.5 }
|
||||
{ "col": 2, "valueType": "String(10)", "value": "5" }
|
||||
{ "col": 3, "valueType": "Boolean", "value": true }
|
||||
{ "col": 4, "valueType": "DateTime", "value": "" }
|
||||
```
|
||||
|
||||
`controlType` нужен редко: умолчание платформы — поле ввода, и оно подходит всем типам, включая
|
||||
`Boolean`. Флажок задаётся явно.
|
||||
@@ -0,0 +1,74 @@
|
||||
# Разметка: области координатами и колоночные раскладки
|
||||
|
||||
Читать, когда область не описывается диапазоном подряд идущих строк или когда части документа нужны разные ширины колонок.
|
||||
|
||||
## Именованные области координатами (`namedAreas[]`)
|
||||
|
||||
Для областей, которые диапазоном подряд идущих строк не описываются: полоса колонок, прямоугольник, ячейка, а также пересекающиеся с другими.
|
||||
|
||||
| Поле | Обяз. | Описание |
|
||||
|------|:-----:|----------|
|
||||
| `name` | да | Имя области |
|
||||
| `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based |
|
||||
| `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based |
|
||||
| `columnSet` | нет | Колоночная раскладка области. Без ключа выводится из накрытых строк, `""` — привязки нет |
|
||||
|
||||
\* Обязательна хотя бы одна из осей.
|
||||
|
||||
**Тип области не указывается** — он следует из того, какие оси заданы, как в `ТабличныйДокумент.Область()`: только строки → полоса строк, только колонки → полоса колонок, обе оси → прямоугольник, одиночные значения по обеим осям → одна ячейка.
|
||||
|
||||
```json
|
||||
"namedAreas": [
|
||||
{ "name": "ОбластьПечатиПоВысоте", "rows": "1-48" },
|
||||
{ "name": "ОбластьПечатиПоШирине", "cols": "1-35" },
|
||||
{ "name": "HZY", "rows": 9, "cols": "16-17" }
|
||||
]
|
||||
```
|
||||
|
||||
Диапазон — та же грамматика, что у `columnWidths`, но **только** число или `"N-M"`: список через запятую запрещён, область непрерывна. Имя обязательно, и хотя бы одна ось должна быть задана; нарушение любого из этих правил → ненулевой код выхода и сообщение в stderr.
|
||||
|
||||
## Колоночные раскладки (`columnSets`)
|
||||
|
||||
Группа строк может иметь собственные ширины колонок — в 1С это «индивидуальная ширина колонок».
|
||||
Документные `columns` и `columnWidths` описывают раскладку по умолчанию; дополнительные
|
||||
объявляются в `columnSets`, а область ссылается на нужную ключом `columnSet` — так же, как
|
||||
ячейка ссылается на `styles` через `style`.
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 52,
|
||||
"columnWidths": { "1": 8 },
|
||||
"columnSets": {
|
||||
"таблица": { "columns": 52, "columnWidths": { "1": 7, "2-52": 24 } }
|
||||
},
|
||||
"areas": [
|
||||
{ "name": "Шапка", "rows": [["Отчёт о продажах"]] },
|
||||
{ "name": "ТабличнаяЧасть", "columnSet": "таблица",
|
||||
"rows": [["№", "Номенклатура"]] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Раскладка описывается той же парой полей, что и документная: `columns` — количество колонок
|
||||
(у раскладок оно обычно разное), `columnWidths` — ширины. Ключ словаря — имя раскладки;
|
||||
в макетах, полученных декомпиляцией, это идентификатор из исходного файла, при описании
|
||||
с нуля — любая строка.
|
||||
|
||||
Все строки области получают раскладку области, поэтому одна область не может смешивать
|
||||
раскладки. Позиции колонок (`col`, `span`) проверяются по ширине раскладки СВОЕЙ области,
|
||||
а не документной.
|
||||
|
||||
Ссылка на необъявленную раскладку → ненулевой код выхода и сообщение в stderr.
|
||||
|
||||
## Стиль колонки (`columnStyles`)
|
||||
|
||||
Колонка несёт то же оформление, что ячейка и строка. Ключи — та же грамматика диапазонов,
|
||||
что у `columnWidths`; значение — имя стиля.
|
||||
|
||||
```json
|
||||
"columnWidths": { "1": 30, "2-3": 15 },
|
||||
"columnStyles": { "1": "по-центру", "4": "скрытая" }
|
||||
```
|
||||
|
||||
Внутри `columnSets` работает тот же ключ. Ширина и стиль независимы: колонка может иметь
|
||||
только ширину, только стиль или и то, и другое.
|
||||
@@ -0,0 +1,26 @@
|
||||
# Примечания к ячейкам
|
||||
|
||||
Всплывающая подсказка, которую платформа показывает при наведении. Задаётся ключом `note` —
|
||||
строкой, объектом «язык → текст» или полной формой:
|
||||
|
||||
```json
|
||||
{ "col": 1, "text": "Итого", "note": "Сумма без НДС" }
|
||||
{ "col": 2, "note": { "ru": "на дату документа", "en": "as of the document date" } }
|
||||
{ "col": 3, "note": { "text": "не более 20%", "style": "жёлтая-подсказка" } }
|
||||
{ "col": 4, "note": { "text": "…", "autoSize": false,
|
||||
"box": { "top": 58, "left": -175, "bottom": 362, "right": 478 } } }
|
||||
```
|
||||
|
||||
Объект трактуется по ключам — так же, как текст ячейки в короткой форме строки: есть ключ
|
||||
примечания (`text`, `style`, `box`, `autoSize`) → это описание примечания, иначе ключи
|
||||
считаются идентификаторами языков.
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `text` | — | Текст подсказки: строка или объект «язык → текст» |
|
||||
| `style` | стиль подсказки | Имя стиля из `styles`; без него — оформление, которое даёт Конфигуратор |
|
||||
| `autoSize` | `true` | Подгонять ли размер окошка под текст |
|
||||
| `box` | — | Смещения окошка: `top`, `left` — положение, `bottom`, `right` — размер. Без ключа окошко встаёт стандартно |
|
||||
|
||||
Координаты ячейки в примечании не задаются. `autoSize` и `box` независимы: при автоподгоне
|
||||
размера положение окошка всё равно хранится.
|
||||
@@ -0,0 +1,59 @@
|
||||
# Печать: колонтитулы и параметры страницы
|
||||
|
||||
Читать, когда макет готовят к печати: повторяющиеся надписи сверху и снизу, ориентация, поля, масштаб.
|
||||
|
||||
## Колонтитулы
|
||||
|
||||
У верхнего и нижнего колонтитула по три слота — `left`, `center`, `right` — и общие настройки:
|
||||
|
||||
```json
|
||||
"header": {
|
||||
"font": "мелкий",
|
||||
"verticalAlignment": "Bottom",
|
||||
"startPage": 2,
|
||||
"left": "Слева\nвторая строка",
|
||||
"center": "Стр. [&НомерСтраницы] из [&СтраницВсего]",
|
||||
"right": { "ru": "Справа", "en": "Right" }
|
||||
},
|
||||
"footer": {
|
||||
"show": false,
|
||||
"center": { "formatted": "<b>Итог</> <colorstyle -16>красным</>" }
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `left`, `center`, `right` | — | Текст слота: строка, объект «язык → текст» или `{ "formatted": … }` |
|
||||
| `font` | — | Имя шрифта из `fonts` — на весь колонтитул |
|
||||
| `verticalAlignment` | — | Положение текста по вертикали: `Top`, `Center`, `Bottom` |
|
||||
| `show` | `true` | Выводить ли колонтитул |
|
||||
| `startPage` | `1` | Страница, с которой колонтитул печатается |
|
||||
|
||||
Текст может быть многострочным (`\n`) и содержать поля `[&НомерСтраницы]`, `[&СтраницВсего]`,
|
||||
`[&Дата]`, `[&Время]` — платформа подставляет их при печати. `{ "formatted": … }` — форматированная
|
||||
строка: разметка живёт прямо в тексте (`<b>жирный</>`, `<fontsize 12>`, `<colorstyle -16>`).
|
||||
|
||||
## Параметры печати
|
||||
|
||||
Плоский объект; имя ключа совпадает с именем свойства в выгрузке:
|
||||
|
||||
```json
|
||||
"printSettings": {
|
||||
"pageOrientation": "Landscape",
|
||||
"topMargin": 500, "leftMargin": 500, "bottomMargin": 500, "rightMargin": 500,
|
||||
"headerSize": 1000, "footerSize": 1000,
|
||||
"fitToPage": true, "firstPageNumber": 7
|
||||
}
|
||||
```
|
||||
|
||||
Допустимые ключи: `pageOrientation`, `scale`, `collate`, `copies`, `perPage`, `topMargin`,
|
||||
`leftMargin`, `bottomMargin`, `rightMargin`, `headerSize`, `footerSize`, `fitToPage`,
|
||||
`blackAndWhite`, `printerName`, `paper`, `paperSource`, `pageWidth`, `pageHeight`, `duplexType`,
|
||||
`pagePlacementAlternation`, `firstPageNumber`. Незнакомый ключ — ошибка. Порядок в объекте не
|
||||
важен: компилятор пишет свойства в том порядке, что и платформа.
|
||||
|
||||
## Чего в DSL нет
|
||||
|
||||
Область печати и повторение области при печати (шапка на каждой странице) не описываются:
|
||||
эти настройки задаются в Конфигураторе и при пересборке чужого макета теряются. Если задача
|
||||
требует именно их — правьте макет в Конфигураторе, а не через DSL.
|
||||
@@ -0,0 +1,81 @@
|
||||
# Свойства стиля: полный перечень
|
||||
|
||||
Читать, когда нужного свойства нет среди частых, описанных в SKILL.md: отступы, защита, узор, маска, формат редактирования, поведение при выводе.
|
||||
|
||||
Имя ключа совпадает с именем свойства в выгрузке — исключений нет.
|
||||
|
||||
Тип значения:
|
||||
|
||||
- **число** — целое;
|
||||
- **да/нет** — `true` / `false`;
|
||||
- **перечисление** — одно из указанных, регистр не важен;
|
||||
- **цвет** — `#RRGGBB`, `style:Имя`, `web:Имя`, `win:Имя`;
|
||||
- **линия** — `"Solid"` либо `{ style, width }`;
|
||||
- **текст** — строка (разворачивается на языки макета).
|
||||
|
||||
## Текст и выравнивание
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `font` | имя | Ссылка на имя из `fonts` |
|
||||
| `horizontalAlignment` | перечисление | `Left`, `Center`, `Right`, `Justify`, `Auto` |
|
||||
| `verticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
|
||||
| `textPlacement` | перечисление | `Wrap`, `Cut`, `Block`, `Auto` |
|
||||
| `textOrientation` | число | Поворот в десятых долях градуса: `900` = 90° |
|
||||
| `textColor` | цвет | Цвет текста |
|
||||
| `indent` | число | Отступ текста |
|
||||
| `autoIndent` | число | Автоматический отступ |
|
||||
| `format` | текст | Формат данных: `"ЧЦ=15; ЧДЦ=2"` |
|
||||
| `editFormat` | текст | Формат редактирования |
|
||||
| `mask` | текст | Маска ввода |
|
||||
| `markNegatives` | да/нет | Выделять отрицательные |
|
||||
|
||||
## Фон и рамка
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `backColor` | цвет | Цвет фона |
|
||||
| `pattern` | перечисление | `WithoutPattern`, `Solid`, `Pattern1`…`Pattern17` (в Конфигураторе «Узор 1» … «Узор 17») |
|
||||
| `patternColor` | цвет | Цвет узора |
|
||||
| `border` | линия | Все четыре стороны |
|
||||
| `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | линия | Отдельная сторона |
|
||||
| `borderColor` | цвет | Цвет рамки |
|
||||
|
||||
## Поведение
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `hidden` | да/нет | Скрыть |
|
||||
| `protection` | да/нет | Защита от редактирования |
|
||||
| `print` | да/нет | Выводить на печать |
|
||||
| `hyperLink` | да/нет | Гиперссылка |
|
||||
| `detailsUse` | перечисление | Использование расшифровки: `Cell`, `Row`, `WithoutProcessing` |
|
||||
| `autoMarkIncomplete` | да/нет | Автоотметка незаполненного |
|
||||
| `bySelectedColumns` | да/нет | По выделенным колонкам |
|
||||
| `columnSizeChange` | перечисление | `Normal`, `QuickChange` |
|
||||
| `autoWidthCalculation` | да/нет | Автоматический расчёт ширины |
|
||||
| `widthWeightFactor` | число | Весовой коэффициент ширины |
|
||||
|
||||
## Картинка в ячейке
|
||||
|
||||
Ключи ниже задают картинку внутри ячейки; сама палитра картинок и рисунки поверх сетки —
|
||||
в `reference/drawings.md`.
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `picIndex` | число | Номер картинки |
|
||||
| `pictureSizeMode` | перечисление | `AutoSize`, `Proportionally`, `RealSize` |
|
||||
| `picHorizontalAlignment` | перечисление | `Auto`, `Center`, `Left`, `Right` |
|
||||
| `picVerticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
|
||||
| `textPosition` | перечисление | Положение текста относительно картинки: `Auto`, `Top`, `Right`, `Bottom` |
|
||||
|
||||
## Чего в стиле нет
|
||||
|
||||
- `width` — свойство колонки, задаётся через `columnWidths`;
|
||||
- `height` — свойство строки, задаётся ключом `height` у строки;
|
||||
- `fillType` — выводится из того, каким ключом задано содержимое ячейки
|
||||
(`text` / `param` / `template`);
|
||||
- `containsValue`, `valueType`, `controlType` — свойства конкретной ячейки, а не общего
|
||||
оформления: задаются ключами ячейки `valueType` и `controlType`;
|
||||
- линия рисунка и её стороны — свойства рисунка: задаются его ключами `line` и `sides`
|
||||
(`reference/drawings.md`); у ячейки такого свойства нет вовсе.
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -41,4 +41,32 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/mxl-decompile.ps1"
|
||||
3. Вызвать `/mxl-compile` для генерации нового Template.xml
|
||||
4. Вызвать `/mxl-validate` для проверки
|
||||
|
||||
Формат JSON на выходе — тот же DSL, что принимает `/mxl-compile`; его полное описание живёт в навыке `/mxl-compile`.
|
||||
Формат JSON на выходе — тот же DSL, что принимает `/mxl-compile`; его описание живёт в навыке `/mxl-compile`.
|
||||
|
||||
## Ключи, которых нет в описании DSL
|
||||
|
||||
В разобранном макете попадаются ключи, которых нет в инструкции `/mxl-compile`: например,
|
||||
у `rowStyle` — модификатор `apply`, у ячейки-поля ввода — `controlType: "none"`. Они несут
|
||||
состояние исходного файла, которое иначе не выразить. Писать их самому не нужно; удалять
|
||||
из разобранного JSON — тоже: компилятор принимает их обратно как есть, а без них макет
|
||||
изменится.
|
||||
|
||||
## Что нужно знать о пересборке
|
||||
|
||||
Правка через DSL — это полная перегенерация макета, а не точечное изменение XML. Поэтому
|
||||
diff после цикла «декомпиляция → компиляция» обычно шире фактической доработки, даже когда
|
||||
содержание сохранено целиком.
|
||||
|
||||
Побайтового совпадения с исходником ждать не всегда стоит. В макетах, которые долго правили
|
||||
в Конфигураторе, остаются следы прежних состояний: например, формат ячейки несёт ширину
|
||||
колонки, которая с тех пор изменилась. Из итогового документа такие значения не выводятся,
|
||||
и в пересобранный файл они не возвращаются.
|
||||
|
||||
Не переживают цикл вовсе (в JSON не попадают, в XML не возвращаются):
|
||||
|
||||
- объединения, не привязанные к ячейке — по всей высоте или ширине документа;
|
||||
- настройки диаграмм (`Chart`, `GanttChart`) — сам рисунок сохраняется, его содержимое нет;
|
||||
- область печати.
|
||||
|
||||
Если задача — сохранить чужой макет как есть и поправить в нём малое, сверяйте результат
|
||||
`/mxl-info` или diff-ом: перечисленное выше нужно вернуть руками.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -10,7 +10,7 @@ allowed-tools:
|
||||
|
||||
# /mxl-validate — валидация макета табличного документа (MXL)
|
||||
|
||||
Проверяет Template.xml на структурные ошибки: индексы, ссылки на палитры, диапазоны именованных областей и объединений.
|
||||
Проверяет Template.xml на структурные ошибки: индексы, ссылки на палитры, диапазоны именованных областей и объединений, согласованность ячеек-полей ввода.
|
||||
|
||||
## Параметры
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# mxl-validate v1.2 — Validate 1C spreadsheet
|
||||
# mxl-validate v1.7 — Validate 1C spreadsheet
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Alias('Path')]
|
||||
@@ -91,7 +91,9 @@ function Report-Warn {
|
||||
|
||||
$templateName = [System.IO.Path]::GetFileName([System.IO.Path]::GetDirectoryName([System.IO.Path]::GetDirectoryName($TemplatePath)))
|
||||
if ($Detailed) {
|
||||
Write-Host "=== Validation: $templateName ==="
|
||||
# Форма имени та же, что в итоговой строке и у py-порта: раньше шапка -Detailed
|
||||
# печаталась без префикса «Template.», и вывод портов расходился одной строкой.
|
||||
Write-Host "=== Validation: Template.$templateName ==="
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
@@ -284,6 +286,14 @@ foreach ($ri in $rowNodes) {
|
||||
Report-Error "Row ${rowIndex}: cell format index $val > format palette size ($formatCount)"
|
||||
}
|
||||
}
|
||||
# Примечание — четвёртый владелец формата, и его ссылка тоже бывает битой.
|
||||
$noteNode = $cell.SelectSingleNode("d:note", $nsMgr)
|
||||
if ($noteNode) {
|
||||
$nf = $noteNode.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||
if ($nf -and [int]$nf.InnerText -gt $formatCount) {
|
||||
Report-Error "Row ${rowIndex}: note format index $($nf.InnerText) > format palette size ($formatCount)"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -398,6 +408,145 @@ foreach ($drawing in $root.SelectNodes("d:drawing", $nsMgr)) {
|
||||
Report-Error "Drawing id=${drawId}: pictureIndex=$picIdx > picture count ($pictureCount)"
|
||||
}
|
||||
}
|
||||
# Оформление рисунка — такая же запись палитры форматов, как у ячейки, и висячая
|
||||
# ссылка на неё так же валит загрузку макета.
|
||||
$fmtIdxNode = $drawing.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||
if ($fmtIdxNode) {
|
||||
$fmtIdx = [int]$fmtIdxNode.InnerText
|
||||
if ($fmtIdx -gt $formatCount) {
|
||||
$drawId = $drawing.SelectSingleNode("d:id", $nsMgr).InnerText
|
||||
Report-Error "Drawing id=${drawId}: formatIndex=$fmtIdx > format palette size ($formatCount)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# --- Check 13: value cells (input fields) ---
|
||||
# Свойства containsValue/valueType/controlType принадлежат ЯЧЕЙКЕ: на корпусе ERP
|
||||
# (370 197 ссылок) на такие записи палитры ссылаются только <f>, ни строка, ни колонка,
|
||||
# ни defaultFormatIndex. Ячейка со значением текста не несёт — ни одна из 370 197.
|
||||
|
||||
$valueFormatIdx = @{}
|
||||
$controlGuids = @('381ed624-9217-4e63-85db-c4c3cb87daae', '35af3d93-d7c7-4a2e-a8eb-bac87a1a3f26')
|
||||
for ($i = 0; $i -lt $formatNodes.Count; $i++) {
|
||||
$fmt = $formatNodes[$i]
|
||||
$contains = $fmt.SelectSingleNode("d:containsValue", $nsMgr)
|
||||
$vt = $fmt.SelectSingleNode("d:valueType", $nsMgr)
|
||||
if (-not $contains -and -not $vt) { continue }
|
||||
$num = $i + 1
|
||||
$valueFormatIdx[$num] = $true
|
||||
if (-not $contains -or $contains.InnerText.Trim() -cne 'true') {
|
||||
Report-Error "Format ${num}: <valueType> without <containsValue>true</containsValue>"
|
||||
} elseif (-not $vt) {
|
||||
Report-Error "Format ${num}: <containsValue> without <valueType>"
|
||||
}
|
||||
if ($vt) {
|
||||
foreach ($child in $vt.ChildNodes) {
|
||||
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
$tag = $child.get_LocalName()
|
||||
if ($tag -cne 'Type' -and $tag -cne 'TypeSet' -and $tag -notlike '*Qualifiers') {
|
||||
Report-Error "Format ${num}: unexpected <$tag> inside <valueType>"
|
||||
}
|
||||
}
|
||||
}
|
||||
$ctl = $fmt.SelectSingleNode("d:controlType", $nsMgr)
|
||||
if ($ctl -and ($controlGuids -notcontains $ctl.InnerText.Trim().ToLowerInvariant())) {
|
||||
Report-Warn "Format ${num}: unknown controlType $($ctl.InnerText.Trim())"
|
||||
}
|
||||
# Флажок Конфигуратор предлагает только для булева и числа. Движок принимает его на любом
|
||||
# типе (проверено сборкой EPF и обратной выгрузкой), поэтому это предупреждение, а не ошибка:
|
||||
# собрать такую ячейку в Конфигураторе нельзя, и почти наверняка это описка.
|
||||
if ($ctl -and $ctl.InnerText.Trim().ToLowerInvariant() -ceq $controlGuids[1] -and $vt) {
|
||||
$kinds = @()
|
||||
foreach ($child in $vt.ChildNodes) {
|
||||
if ($child.NodeType -ne [System.Xml.XmlNodeType]::Element) { continue }
|
||||
if ($child.get_LocalName() -ceq 'Type') { $kinds += $child.InnerText.Trim() }
|
||||
}
|
||||
if (-not (($kinds.Count -eq 1) -and ($kinds[0] -ceq 'xs:boolean' -or $kinds[0] -ceq 'xs:decimal'))) {
|
||||
Report-Warn "Format ${num}: checkbox control on a type other than Boolean or Number"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($ri in $root.SelectNodes("d:rowsItem", $nsMgr)) {
|
||||
if ($stopped) { break }
|
||||
$row = $ri.SelectSingleNode("d:row", $nsMgr)
|
||||
if (-not $row) { continue }
|
||||
$idxNode = $ri.SelectSingleNode("d:index", $nsMgr)
|
||||
$rn = if ($idxNode) { $idxNode.InnerText } else { '?' }
|
||||
$rowFmt = $row.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||
if ($rowFmt -and $valueFormatIdx.ContainsKey([int]$rowFmt.InnerText)) {
|
||||
Report-Warn "Row ${rn}: formatIndex points to a value format (cell-only property)"
|
||||
}
|
||||
foreach ($cGroup in $row.SelectNodes("d:c", $nsMgr)) {
|
||||
$cell = $cGroup.SelectSingleNode("d:c", $nsMgr)
|
||||
if (-not $cell) { continue }
|
||||
$fNode = $cell.SelectSingleNode("d:f", $nsMgr)
|
||||
if (-not $fNode -or -not $valueFormatIdx.ContainsKey([int]$fNode.InnerText)) {
|
||||
# Значение и настройки элемента управления бывают только у ячейки-поля ввода:
|
||||
# на корпусе ни одного <v> и ни одного <control> в обычной ячейке.
|
||||
if ($cell.SelectSingleNode("d:v", $nsMgr)) {
|
||||
Report-Error "Row ${rn}: cell carries value but its format has no containsValue"
|
||||
}
|
||||
if ($cell.SelectSingleNode("d:control", $nsMgr)) {
|
||||
Report-Error "Row ${rn}: cell carries control settings but its format has no containsValue"
|
||||
}
|
||||
continue
|
||||
}
|
||||
if ($cell.SelectSingleNode("d:tl", $nsMgr)) {
|
||||
Report-Error "Row ${rn}: cell contains a value and text at the same time"
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($cols in $root.SelectNodes("d:columns", $nsMgr)) {
|
||||
foreach ($ci in $cols.SelectNodes("d:columnsItem", $nsMgr)) {
|
||||
$col = $ci.SelectSingleNode("d:column", $nsMgr)
|
||||
if (-not $col) { continue }
|
||||
$fmtNode = $col.SelectSingleNode("d:formatIndex", $nsMgr)
|
||||
if ($fmtNode -and $valueFormatIdx.ContainsKey([int]$fmtNode.InnerText)) {
|
||||
$colIdxNode = $ci.SelectSingleNode("d:index", $nsMgr)
|
||||
$colIdxText = if ($colIdxNode) { $colIdxNode.InnerText } else { '?' }
|
||||
Report-Warn "Column ${colIdxText}: formatIndex points to a value format (cell-only property)"
|
||||
}
|
||||
}
|
||||
}
|
||||
$dfi = $root.SelectSingleNode("d:defaultFormatIndex", $nsMgr)
|
||||
if ($dfi -and $valueFormatIdx.ContainsKey([int]$dfi.InnerText)) {
|
||||
Report-Warn "defaultFormatIndex points to a value format (cell-only property)"
|
||||
}
|
||||
if ($valueFormatIdx.Count -gt 0) {
|
||||
Report-OK "Value cells: $($valueFormatIdx.Count) value formats"
|
||||
}
|
||||
|
||||
# --- Check 14: группировки строк и колонок ---
|
||||
# Диапазоны группировок либо вложены, либо не пересекаются: на корпусе 40 620 886 пар
|
||||
# непересекающихся и 599 958 вложенных, частичных пересечений нет ни одного.
|
||||
|
||||
foreach ($tag in @('vg', 'hg')) {
|
||||
$ranges = @()
|
||||
foreach ($g in $root.SelectNodes("d:$tag", $nsMgr)) {
|
||||
$bNode = $g.SelectSingleNode("d:b", $nsMgr)
|
||||
if (-not $bNode) {
|
||||
Report-Error "Group <$tag>: <b> is missing"
|
||||
continue
|
||||
}
|
||||
$b = [int]$bNode.InnerText
|
||||
$eNode = $g.SelectSingleNode("d:e", $nsMgr)
|
||||
$e = if ($eNode) { [int]$eNode.InnerText } else { $b }
|
||||
if ($e -lt $b) { Report-Error "Group <$tag> $b..${e}: range is reversed" }
|
||||
if ($tag -ceq 'vg' -and $docHeight -gt 0 -and $b -ge $docHeight) {
|
||||
Report-Warn "Group <vg> starts at row $b, beyond document height ($docHeight)"
|
||||
}
|
||||
$ranges += ,@($b, $e)
|
||||
}
|
||||
for ($i = 0; $i -lt $ranges.Count; $i++) {
|
||||
for ($j = $i + 1; $j -lt $ranges.Count; $j++) {
|
||||
$a = $ranges[$i]; $c = $ranges[$j]
|
||||
if ($a[1] -lt $c[0] -or $c[1] -lt $a[0]) { continue }
|
||||
if (($a[0] -le $c[0] -and $c[1] -le $a[1]) -or ($c[0] -le $a[0] -and $a[1] -le $c[1])) { continue }
|
||||
Report-Error "Groups <$tag> $($a[0])..$($a[1]) and $($c[0])..$($c[1]) overlap partially"
|
||||
}
|
||||
}
|
||||
if ($ranges.Count -gt 0) { Report-OK "Groups <$tag>: $($ranges.Count)" }
|
||||
}
|
||||
|
||||
# --- Summary ---
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# mxl-validate v1.2 — Validate 1C spreadsheet document Template.xml
|
||||
#!/usr/bin/env python3
|
||||
# mxl-validate v1.7 — Validate 1C spreadsheet document Template.xml
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Validates spreadsheet Template.xml: height, palette refs, column/row indices, areas, merges."""
|
||||
import sys, os, argparse
|
||||
@@ -223,7 +223,11 @@ def main():
|
||||
r.error(f'Font index {max_font_ref} exceeds palette size ({font_count})')
|
||||
elif max_font_ref > 0:
|
||||
r.error(f'Font index {max_font_ref} referenced but no fonts defined')
|
||||
# No font references — no check needed
|
||||
else:
|
||||
# Макет без единого шрифта — норма (ячейки-поля ввода, макеты без оформления).
|
||||
# Строку печатаем как ps1-порт: раньше py молчал, и на таком макете число проверок
|
||||
# у портов расходилось.
|
||||
r.ok('No font references')
|
||||
|
||||
# --- Check 11: line/border indices in formats ---
|
||||
if line_count > 0:
|
||||
@@ -233,7 +237,8 @@ def main():
|
||||
r.error(f'Line index {max_line_ref} exceeds palette size ({line_count})')
|
||||
elif max_line_ref > 0:
|
||||
r.error(f'Line index {max_line_ref} referenced but no lines defined')
|
||||
# No line/border references — no check needed
|
||||
else:
|
||||
r.ok('No line/border references')
|
||||
|
||||
# --- Check 3, 4, 5, 6: row/cell checks ---
|
||||
max_cell_format_ref = 0
|
||||
@@ -298,6 +303,13 @@ def main():
|
||||
max_cell_format_ref = val
|
||||
if val > format_count:
|
||||
r.error(f'Row {row_index}: cell format index {val} > format palette size ({format_count})')
|
||||
# Примечание — четвёртый владелец формата, и его ссылка тоже бывает битой.
|
||||
note = cell.find(f'{{{NS_D}}}note')
|
||||
if note is not None:
|
||||
nf = note.find(f'{{{NS_D}}}formatIndex')
|
||||
if nf is not None and nf.text and int(nf.text) > format_count:
|
||||
r.error(f'Row {row_index}: note format index {nf.text}'
|
||||
f' > format palette size ({format_count})')
|
||||
|
||||
row_index += 1
|
||||
|
||||
@@ -393,6 +405,134 @@ def main():
|
||||
draw_id = draw_id_node.text if draw_id_node is not None else '?'
|
||||
r.error(f'Drawing id={draw_id}: pictureIndex={pic_idx} > picture count ({picture_count})')
|
||||
|
||||
# Оформление рисунка — такая же запись палитры форматов, как у ячейки, и висячая
|
||||
# ссылка на неё так же валит загрузку макета.
|
||||
fmt_idx_node = drawing.find(f'{{{NS_D}}}formatIndex')
|
||||
if fmt_idx_node is not None and fmt_idx_node.text:
|
||||
fmt_idx = int(fmt_idx_node.text)
|
||||
if fmt_idx > format_count:
|
||||
draw_id_node = drawing.find(f'{{{NS_D}}}id')
|
||||
draw_id = draw_id_node.text if draw_id_node is not None else '?'
|
||||
r.error(f'Drawing id={draw_id}: formatIndex={fmt_idx} > format palette size ({format_count})')
|
||||
|
||||
# --- Check 13: value cells (input fields) ---
|
||||
# Свойства containsValue/valueType/controlType принадлежат ЯЧЕЙКЕ: на корпусе ERP
|
||||
# (370 197 ссылок) на такие записи палитры ссылаются только <f>, ни строка, ни колонка,
|
||||
# ни defaultFormatIndex. Ячейка со значением текста не несёт — ни одна из 370 197.
|
||||
value_format_idx = set()
|
||||
control_guids = ('381ed624-9217-4e63-85db-c4c3cb87daae', '35af3d93-d7c7-4a2e-a8eb-bac87a1a3f26')
|
||||
for i, fmt in enumerate(format_nodes, start=1):
|
||||
contains = fmt.find(f'{{{NS_D}}}containsValue')
|
||||
vt = fmt.find(f'{{{NS_D}}}valueType')
|
||||
if contains is None and vt is None:
|
||||
continue
|
||||
value_format_idx.add(i)
|
||||
if contains is None or (contains.text or '').strip() != 'true':
|
||||
r.error(f'Format {i}: <valueType> without <containsValue>true</containsValue>')
|
||||
elif vt is None:
|
||||
r.error(f'Format {i}: <containsValue> without <valueType>')
|
||||
if vt is not None:
|
||||
for child in vt:
|
||||
tag = child.tag.split('}')[-1]
|
||||
if tag not in ('Type', 'TypeSet') and not tag.endswith('Qualifiers'):
|
||||
r.error(f'Format {i}: unexpected <{tag}> inside <valueType>')
|
||||
ctl = fmt.find(f'{{{NS_D}}}controlType')
|
||||
if ctl is not None and (ctl.text or '').strip().lower() not in control_guids:
|
||||
r.warn(f'Format {i}: unknown controlType {(ctl.text or "").strip()}')
|
||||
# Флажок Конфигуратор предлагает только для булева и числа. Движок принимает его на
|
||||
# любом типе (проверено сборкой EPF и обратной выгрузкой), поэтому это предупреждение,
|
||||
# а не ошибка: собрать такую ячейку в Конфигураторе нельзя, и почти наверняка это описка.
|
||||
if ctl is not None and (ctl.text or '').strip().lower() == control_guids[1] and vt is not None:
|
||||
kinds = [(t.text or '').strip() for t in vt if t.tag.endswith('}Type')]
|
||||
if kinds != ['xs:boolean'] and kinds != ['xs:decimal']:
|
||||
r.warn(f'Format {i}: checkbox control on a type other than Boolean or Number')
|
||||
|
||||
for ri in root.findall(f'{{{NS_D}}}rowsItem'):
|
||||
if r.stopped:
|
||||
break
|
||||
row = ri.find(f'{{{NS_D}}}row')
|
||||
if row is None:
|
||||
continue
|
||||
idx_node = ri.find(f'{{{NS_D}}}index')
|
||||
rn = idx_node.text if idx_node is not None else '?'
|
||||
row_fmt = row.find(f'{{{NS_D}}}formatIndex')
|
||||
if row_fmt is not None and row_fmt.text and int(row_fmt.text) in value_format_idx:
|
||||
r.warn(f'Row {rn}: formatIndex points to a value format (cell-only property)')
|
||||
for c_group in row.findall(f'{{{NS_D}}}c'):
|
||||
cell = c_group.find(f'{{{NS_D}}}c')
|
||||
if cell is None:
|
||||
continue
|
||||
f_node = cell.find(f'{{{NS_D}}}f')
|
||||
if f_node is None or not f_node.text or int(f_node.text) not in value_format_idx:
|
||||
continue
|
||||
if cell.find(f'{{{NS_D}}}tl') is not None:
|
||||
r.error(f'Row {rn}: cell contains a value and text at the same time')
|
||||
# Значение и настройки элемента управления бывают только у ячейки-поля ввода: на корпусе
|
||||
# ни одного <v> и ни одного <control> в обычной ячейке.
|
||||
for ri in root.findall(f'{{{NS_D}}}rowsItem'):
|
||||
if r.stopped:
|
||||
break
|
||||
row = ri.find(f'{{{NS_D}}}row')
|
||||
if row is None:
|
||||
continue
|
||||
idx_node = ri.find(f'{{{NS_D}}}index')
|
||||
rn = idx_node.text if idx_node is not None else '?'
|
||||
for c_group in row.findall(f'{{{NS_D}}}c'):
|
||||
cell = c_group.find(f'{{{NS_D}}}c')
|
||||
if cell is None:
|
||||
continue
|
||||
f_node = cell.find(f'{{{NS_D}}}f')
|
||||
is_value = f_node is not None and f_node.text and int(f_node.text) in value_format_idx
|
||||
if is_value:
|
||||
continue
|
||||
for tag, what in (('v', 'value'), ('control', 'control settings')):
|
||||
if cell.find(f'{{{NS_D}}}{tag}') is not None:
|
||||
r.error(f'Row {rn}: cell carries {what} but its format has no containsValue')
|
||||
for cols in root.findall(f'{{{NS_D}}}columns'):
|
||||
for ci in cols.findall(f'{{{NS_D}}}columnsItem'):
|
||||
col = ci.find(f'{{{NS_D}}}column')
|
||||
if col is None:
|
||||
continue
|
||||
fmt_node = col.find(f'{{{NS_D}}}formatIndex')
|
||||
if fmt_node is not None and fmt_node.text and int(fmt_node.text) in value_format_idx:
|
||||
col_idx_node = ci.find(f'{{{NS_D}}}index')
|
||||
col_idx_text = col_idx_node.text if col_idx_node is not None else '?'
|
||||
r.warn(f'Column {col_idx_text}: formatIndex points to a value format (cell-only property)')
|
||||
dfi = root.find(f'{{{NS_D}}}defaultFormatIndex')
|
||||
if dfi is not None and dfi.text and int(dfi.text) in value_format_idx:
|
||||
r.warn('defaultFormatIndex points to a value format (cell-only property)')
|
||||
if value_format_idx:
|
||||
r.ok(f'Value cells: {len(value_format_idx)} value formats')
|
||||
|
||||
# --- Check 14: группировки строк и колонок ---
|
||||
# Диапазоны группировок либо вложены, либо не пересекаются: на корпусе 40 620 886 пар
|
||||
# непересекающихся и 599 958 вложенных, частичных пересечений нет ни одного.
|
||||
for tag in ('vg', 'hg'):
|
||||
ranges = []
|
||||
for g in root.findall(f'{{{NS_D}}}{tag}'):
|
||||
b_node = g.find(f'{{{NS_D}}}b')
|
||||
if b_node is None or not b_node.text:
|
||||
r.error(f'Group <{tag}>: <b> is missing')
|
||||
continue
|
||||
b = int(b_node.text)
|
||||
e_node = g.find(f'{{{NS_D}}}e')
|
||||
e = int(e_node.text) if e_node is not None and e_node.text else b
|
||||
if e < b:
|
||||
r.error(f'Group <{tag}> {b}..{e}: range is reversed')
|
||||
if tag == 'vg' and doc_height > 0 and b >= doc_height:
|
||||
r.warn(f'Group <vg> starts at row {b}, beyond document height ({doc_height})')
|
||||
ranges.append((b, e))
|
||||
for i in range(len(ranges)):
|
||||
for j in range(i + 1, len(ranges)):
|
||||
a, c = ranges[i], ranges[j]
|
||||
if a[1] < c[0] or c[1] < a[0]:
|
||||
continue
|
||||
if (a[0] <= c[0] and c[1] <= a[1]) or (c[0] <= a[0] and a[1] <= c[1]):
|
||||
continue
|
||||
r.error(f'Groups <{tag}> {a[0]}..{a[1]} and {c[0]}..{c[1]} overlap partially')
|
||||
if ranges:
|
||||
r.ok(f'Groups <{tag}>: {len(ranges)}')
|
||||
|
||||
# --- Finalize ---
|
||||
checks = r.ok_count + r.errors + r.warnings
|
||||
if r.errors == 0 and r.warnings == 0 and not detailed:
|
||||
|
||||
@@ -64,7 +64,7 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
|
||||
|
||||
### Русские синонимы
|
||||
|
||||
Поддерживаются русские типы (`Справочник`→Catalog, `Документ`→Document) и права (`Чтение`→Read, `Просмотр`→View). Смешивание допустимо: `"Справочник.Контрагенты: Чтение, View"`.
|
||||
Поддерживаются русские типы (`Справочник`→Catalog, `Документ`→Document) и права (`Чтение`→Read, `Просмотр`→View). Каноничная форма — английская.
|
||||
|
||||
### Шаблоны RLS
|
||||
|
||||
@@ -99,7 +99,13 @@ powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-compile.ps1" -
|
||||
}
|
||||
```
|
||||
|
||||
Подробные таблицы пресетов, русских синонимов и дополнительные примеры — в `dsl-reference.md`.
|
||||
## Что можно писать в `objects`
|
||||
|
||||
Права имеют 27 типов объектов; тип или имя права вне списка — ошибка: роль не создаётся, файлы не пишутся, `Configuration.xml` не меняется. Права нельзя назначить на `Enum`, `CommonModule`, `DefinedType`, `CommonPicture`, `CommonTemplate`, `Language`, `FunctionalOption`, `EventSubscription`, `ScheduledJob`, `StyleItem`, `SettingsStorage` и подобные — в дереве редактора ролей их нет.
|
||||
|
||||
Права на части объекта задаются точечным путём: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`, `WebService.Обмен.Operation.Загрузить: Use`.
|
||||
|
||||
Полные таблицы «тип → права», виды вложенности (включая внешние источники данных), список типов без прав, таблицы пресетов и дополнительные примеры — в `dsl-reference.md`.
|
||||
|
||||
## Верификация
|
||||
|
||||
|
||||
@@ -2,49 +2,86 @@
|
||||
|
||||
Подробная справка по JSON DSL для `/role-compile`. Компактное описание — в [SKILL.md](SKILL.md).
|
||||
|
||||
## Структура верхнего уровня
|
||||
## Какие объекты можно писать в `objects`
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ИмяРоли",
|
||||
"synonym": "Отображаемое имя роли",
|
||||
"comment": "",
|
||||
"setForNewObjects": false,
|
||||
"setForAttributesByDefault": true,
|
||||
"independentRightsOfChildObjects": false,
|
||||
"objects": [ ... ],
|
||||
"templates": [ ... ]
|
||||
}
|
||||
```
|
||||
Права в роли имеют ровно 27 типов. Тип вне этого списка — ошибка компиляции: роль не
|
||||
создаётся, файлы не пишутся.
|
||||
|
||||
- `name` — программное имя роли (обязательно)
|
||||
- `synonym` — отображаемое имя (по умолчанию = name)
|
||||
- `comment` — комментарий (по умолчанию пусто)
|
||||
- Глобальные флаги — по умолчанию `false`, `true`, `false`
|
||||
| Тип | Права |
|
||||
|-----|-------|
|
||||
| `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`, `ChartOfCharacteristicTypes` | Read, Insert, Update, Delete, View, Edit, InputByString, Interactive* (Insert, SetDeletionMark, ClearDeletionMark, Delete, DeleteMarked), *PredefinedData, *DataHistory* |
|
||||
| `Document` | то же + Posting, UndoPosting, InteractivePosting, InteractivePostingRegular, InteractiveUndoPosting, InteractiveChangeOfPosted |
|
||||
| `ChartOfAccounts`, `ChartOfCalculationTypes`, `ExchangePlan` | Read, Insert, Update, Delete, View, Edit, InputByString, Interactive*, *DataHistory* (без InteractiveDeleteMarked у ChartOfAccounts) |
|
||||
| `BusinessProcess` | Read, Insert, Update, Delete, View, Edit, InputByString, Start, InteractiveInsert, InteractiveSetDeletionMark, InteractiveClearDeletionMark, InteractiveDelete, InteractiveActivate, InteractiveStart |
|
||||
| `Task` | то же, но вместо Start/InteractiveStart — Execute, InteractiveExecute |
|
||||
| `InformationRegister` | Read, Update, View, Edit, TotalsControl, *DataHistory* |
|
||||
| `AccumulationRegister`, `AccountingRegister` | Read, Update, View, Edit, TotalsControl |
|
||||
| `CalculationRegister` | Read, View |
|
||||
| `Constant` | Read, Update, View, Edit, *DataHistory* |
|
||||
| `DocumentJournal` | Read, View |
|
||||
| `Sequence` | Read, Update |
|
||||
| `DataProcessor`, `Report` | Use, View |
|
||||
| `WebService`, `HTTPService`, `IntegrationService` | Use |
|
||||
| `CommonForm`, `CommonCommand`, `Subsystem`, `FilterCriterion` | View |
|
||||
| `CommonAttribute` | View, Edit |
|
||||
| `SessionParameter` | Get, Set |
|
||||
| `ExternalDataSource` | Use, Administration, StandardAuthenticationChange, SessionStandardAuthenticationChange, SessionOSAuthenticationChange |
|
||||
|
||||
## Объекты: два формата
|
||||
Полные списки прав по каждому типу — в коде навыка (`scripts/role-compile.ps1`,
|
||||
таблица `$knownRights`); навык проверяет каждое имя права и отвергает несуществующее.
|
||||
|
||||
### Вложенные объекты
|
||||
|
||||
Путь длиннее двух сегментов задаёт права на часть объекта. Вид вложенности —
|
||||
предпоследний сегмент, он определяет набор прав.
|
||||
|
||||
| Вид | Права |
|
||||
|-----|-------|
|
||||
| `Attribute`, `StandardAttribute`, `TabularSection`, `StandardTabularSection`, `Dimension`, `Resource`, `AccountingFlag`, `ExtDimensionAccountingFlag`, `AddressingAttribute` | View, Edit |
|
||||
| `Command`, вложенная `Subsystem` | View |
|
||||
| `Operation` (веб-сервис), `Method` (HTTP-сервис, под `URLTemplate`), `IntegrationServiceChannel` | Use |
|
||||
| `Recalculation` (регистр расчёта) | Read, Update |
|
||||
|
||||
Виды `Operation`, `Method`, `IntegrationServiceChannel`, `Recalculation` и виды внешнего
|
||||
источника допустимы только у своего типа-родителя. Графы журнала документов
|
||||
(`DocumentJournal.Ж.Column.Г`) собственных прав не имеют.
|
||||
|
||||
Примеры: `Catalog.Контрагенты.Attribute.ИНН: View, Edit`,
|
||||
`Document.Реализация.TabularSection.Товары.Attribute.Цена: View`,
|
||||
`WebService.Обмен.Operation.Загрузить: Use`,
|
||||
`HTTPService.API.URLTemplate.Файлы.Method.get: Use`.
|
||||
|
||||
### Вложенные объекты внешнего источника данных
|
||||
|
||||
Виды `Table`, `Cube`, `Function`, `Field`, `DimensionTable` бывают **только** под
|
||||
`ExternalDataSource`.
|
||||
|
||||
| Путь | Права |
|
||||
|------|-------|
|
||||
| `ExternalDataSource.И.Table.Т` | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveDelete |
|
||||
| `ExternalDataSource.И.Table.Т.Field.П` | View, Edit |
|
||||
| `ExternalDataSource.И.Cube.К` | Read, View |
|
||||
| `ExternalDataSource.И.Cube.К.Dimension.И`, `…Resource.Р` | View |
|
||||
| `ExternalDataSource.И.Cube.К.DimensionTable.Т` | Read, View |
|
||||
| `ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П` | View, Edit |
|
||||
| `ExternalDataSource.И.Function.Ф` | Use, View |
|
||||
| `…Table.Т.Command.К`, `…Cube.К.Command.К`, `…DimensionTable.Т.Command.К` | View |
|
||||
|
||||
### Типы объектов без прав в ролях
|
||||
|
||||
Права нельзя назначить на `Enum`, `CommonModule`, `DefinedType`, `CommonPicture`,
|
||||
`CommonTemplate`, `Language`, `FunctionalOption`, `FunctionalOptionsParameter`,
|
||||
`EventSubscription`, `ScheduledJob`, `StyleItem`, `Style`, `SettingsStorage`,
|
||||
`XDTOPackage`, `WSReference`, `DocumentNumerator` — в дереве редактора ролей этих типов нет.
|
||||
|
||||
Попытка — ошибка компиляции. Права на перечисления особенно опасны: конфигурация с таким
|
||||
блоком не загружается в информационную базу, конфигуратор зависает без сообщений.
|
||||
|
||||
## Формат записи объектов
|
||||
|
||||
Массив `objects` принимает строки (shorthand) и объекты (полная форма).
|
||||
|
||||
### Строковый shorthand
|
||||
|
||||
```
|
||||
"ОбъектМетаданных: @пресет"
|
||||
"ОбъектМетаданных: Право1, Право2"
|
||||
```
|
||||
|
||||
Примеры:
|
||||
```json
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
"Document.Реализация: @edit",
|
||||
"InformationRegister.Цены: Read, Update",
|
||||
"DataProcessor.Загрузка: @view"
|
||||
]
|
||||
```
|
||||
|
||||
### Объектная форма (для RLS и переопределений)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "Document.Реализация",
|
||||
@@ -58,6 +95,9 @@
|
||||
- `rights` — переопределения: dict `{"Right": true/false}` или массив `["Right1", "Right2"]`
|
||||
- `rls` — RLS-ограничения: `{"ИмяПрава": "текст условия"}`
|
||||
|
||||
Русские имена типов и прав тоже принимаются (`Справочник.Контрагенты: Чтение`), каноничная
|
||||
форма — английская.
|
||||
|
||||
## Пресеты — подробные таблицы
|
||||
|
||||
Пресеты обозначаются `@` в строковом формате. В объектной форме ключ `preset` без `@`.
|
||||
@@ -88,127 +128,8 @@
|
||||
| SessionParameter | Get, Set |
|
||||
| CommonAttribute | View, Edit |
|
||||
|
||||
Для сервисов (WebService, HTTPService, IntegrationService) пресеты не определены — используй явные права: `"WebService.Имя: Use"`.
|
||||
|
||||
Если пресет не определён для типа объекта — предупреждение с подсказкой доступных.
|
||||
|
||||
## Русские синонимы
|
||||
|
||||
Скрипт автоматически транслирует русские имена в английские. Можно смешивать: `"Справочник.Контрагенты: Чтение, View"` — работает.
|
||||
|
||||
### Типы объектов
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Справочник` | 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 |
|
||||
|
||||
### Вложенные типы
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Реквизит` | Attribute |
|
||||
| `СтандартныйРеквизит` | StandardAttribute |
|
||||
| `ТабличнаяЧасть` | TabularSection |
|
||||
| `Измерение` | Dimension |
|
||||
| `Ресурс` | Resource |
|
||||
| `Команда` | Command |
|
||||
| `РеквизитАдресации` | AddressingAttribute |
|
||||
|
||||
### Права (основные)
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Чтение` | Read |
|
||||
| `Добавление` | Insert |
|
||||
| `Изменение` | Update |
|
||||
| `Удаление` | Delete |
|
||||
| `Просмотр` | View |
|
||||
| `Редактирование` | Edit |
|
||||
| `ВводПоСтроке` | InputByString |
|
||||
| `Проведение` | Posting |
|
||||
| `ОтменаПроведения` | UndoPosting |
|
||||
| `Использование` | Use |
|
||||
| `Получение` | Get |
|
||||
| `Установка` | Set |
|
||||
| `Старт` | Start |
|
||||
| `Выполнение` | Execute |
|
||||
| `УправлениеИтогами` | TotalsControl |
|
||||
|
||||
### Права (интерактивные)
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `ИнтерактивноеДобавление` | InteractiveInsert |
|
||||
| `ИнтерактивнаяПометкаУдаления` | InteractiveSetDeletionMark |
|
||||
| `ИнтерактивноеСнятиеПометкиУдаления` | InteractiveClearDeletionMark |
|
||||
| `ИнтерактивноеУдаление` | InteractiveDelete |
|
||||
| `ИнтерактивноеУдалениеПомеченных` | InteractiveDeleteMarked |
|
||||
| `ИнтерактивноеПроведение` | InteractivePosting |
|
||||
| `ИнтерактивноеПроведениеНеоперативное` | InteractivePostingRegular |
|
||||
| `ИнтерактивнаяОтменаПроведения` | InteractiveUndoPosting |
|
||||
| `ИнтерактивноеИзменениеПроведенных` | InteractiveChangeOfPosted |
|
||||
| `ИнтерактивныйСтарт` | InteractiveStart |
|
||||
| `ИнтерактивнаяАктивация` | InteractiveActivate |
|
||||
| `ИнтерактивноеВыполнение` | InteractiveExecute |
|
||||
|
||||
### Права (конфигурация)
|
||||
|
||||
| Русский | English |
|
||||
|---------|---------|
|
||||
| `Администрирование` | Administration |
|
||||
| `АдминистрированиеДанных` | DataAdministration |
|
||||
| `ТонкийКлиент` | ThinClient |
|
||||
| `ТолстыйКлиент` | ThickClient |
|
||||
| `ВебКлиент` | WebClient |
|
||||
| `МобильныйКлиент` | MobileClient |
|
||||
| `ВнешнееСоединение` | ExternalConnection |
|
||||
| `Вывод` | Output |
|
||||
| `СохранениеДанныхПользователя` | SaveUserData |
|
||||
|
||||
## Типы объектов без прав в ролях
|
||||
|
||||
Следующие типы 1С **не могут** иметь права в ролях (не добавляются в `objects`):
|
||||
|
||||
| Тип | Причина |
|
||||
|-----|---------|
|
||||
| Enum (Перечисление) | Права наследуются от конфигурации, явное назначение невозможно |
|
||||
| CommonModule (ОбщийМодуль) | Не имеет собственных прав в роли |
|
||||
| DefinedType (ОпределяемыйТип) | Тип данных, не объект прав |
|
||||
| CommonPicture (ОбщаяКартинка) | Ресурс, не объект прав |
|
||||
| CommonTemplate (ОбщийМакет) | Ресурс, не объект прав |
|
||||
| Language (Язык) | Конфигурационный элемент |
|
||||
| FunctionalOption (ФункциональнаяОпция) | Не объект прав |
|
||||
| FunctionalOptionsParameter | Не объект прав |
|
||||
| EventSubscription (ПодпискаНаСобытие) | Не объект прав |
|
||||
| ScheduledJob (РегламентноеЗадание) | Не объект прав |
|
||||
| StyleItem (ЭлементСтиля) | Ресурс оформления |
|
||||
Для сервисов (WebService, HTTPService, IntegrationService) и внешних источников данных
|
||||
пресеты не определены — задавай права явно: `"WebService.Имя: Use"`.
|
||||
|
||||
## Шаблоны ограничений (RLS templates)
|
||||
|
||||
@@ -227,36 +148,7 @@
|
||||
|
||||
## Примеры
|
||||
|
||||
### 1. Простая роль (только пресеты)
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ЧтениеНоменклатуры",
|
||||
"synonym": "Чтение номенклатуры",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
"Catalog.Контрагенты: @view",
|
||||
"DataProcessor.Загрузка: @view"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Роль для регламентного задания
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ОбновлениеЦен",
|
||||
"synonym": "Обновление цен номенклатуры",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: Read",
|
||||
"Catalog.Валюты: Read",
|
||||
"InformationRegister.ЦеныНоменклатуры: Read, Update",
|
||||
"Constant.ОсновнаяВалюта: Read"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Роль с RLS
|
||||
### Роль с RLS
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -281,22 +173,7 @@
|
||||
}
|
||||
```
|
||||
|
||||
### 4. Роль с русскими синонимами
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "ПросмотрДанных",
|
||||
"synonym": "Просмотр данных",
|
||||
"objects": [
|
||||
"Справочник.Контрагенты: @view",
|
||||
"Документ.Реализация: Чтение, Просмотр",
|
||||
"РегистрСведений.Цены: @edit",
|
||||
"Обработка.ЗагрузкаДанных: @view"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### 5. Роль с переопределением прав из пресета
|
||||
### Роль с переопределением прав из пресета
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -311,3 +188,16 @@
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Роль с правами на части объекта
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "МенеджерБезЦен",
|
||||
"synonym": "Менеджер без доступа к ценам",
|
||||
"objects": [
|
||||
"Catalog.Номенклатура: @view",
|
||||
{ "name": "Catalog.Номенклатура.Attribute.Цена", "rights": { "View": false, "Edit": false } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# role-compile v1.24 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -217,7 +217,24 @@ $script:typeAliases = @{
|
||||
"ПараметрСеанса" = "SessionParameter"
|
||||
"ОбщийРеквизит" = "CommonAttribute"
|
||||
"Конфигурация" = "Configuration"
|
||||
"ВнешнийИсточникДанных" = "ExternalDataSource"
|
||||
# Типы без прав в ролях: алиасы нужны не ради генерации, а ради отказа по делу —
|
||||
# иначе на русскую запись навык ответит «неизвестный тип 'ОбщийМодуль'».
|
||||
"Перечисление" = "Enum"
|
||||
"ОбщийМодуль" = "CommonModule"
|
||||
"ОпределяемыйТип" = "DefinedType"
|
||||
"ОбщаяКартинка" = "CommonPicture"
|
||||
"ОбщийМакет" = "CommonTemplate"
|
||||
"Язык" = "Language"
|
||||
"ФункциональнаяОпция" = "FunctionalOption"
|
||||
"ПараметрФункциональныхОпций" = "FunctionalOptionsParameter"
|
||||
"ПодпискаНаСобытие" = "EventSubscription"
|
||||
"РегламентноеЗадание" = "ScheduledJob"
|
||||
"ЭлементСтиля" = "StyleItem"
|
||||
"ХранилищеНастроек" = "SettingsStorage"
|
||||
"ПакетXDTO" = "XDTOPackage"
|
||||
"WSСсылка" = "WSReference"
|
||||
"Нумератор" = "DocumentNumerator"
|
||||
# Nested
|
||||
"Реквизит" = "Attribute"
|
||||
"СтандартныйРеквизит" = "StandardAttribute"
|
||||
@@ -403,11 +420,72 @@ $script:knownRights = @{
|
||||
"IntegrationService" = @("Use")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
"ExternalDataSource" = @(
|
||||
"Use","Administration","StandardAuthenticationChange",
|
||||
"SessionStandardAuthenticationChange","SessionOSAuthenticationChange"
|
||||
)
|
||||
}
|
||||
|
||||
# Nested objects: Attribute, StandardAttribute, TabularSection, Dimension, Resource, AddressingAttribute
|
||||
$script:nestedRights = @("View","Edit")
|
||||
$script:commandRights = @("View")
|
||||
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
|
||||
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
|
||||
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
|
||||
$script:nestedKindRights = @{
|
||||
"Attribute" = @("View","Edit")
|
||||
"StandardAttribute" = @("View","Edit")
|
||||
"TabularSection" = @("View","Edit")
|
||||
"StandardTabularSection" = @("View","Edit")
|
||||
"Dimension" = @("View","Edit")
|
||||
"Resource" = @("View","Edit")
|
||||
"AccountingFlag" = @("View","Edit")
|
||||
"ExtDimensionAccountingFlag" = @("View","Edit")
|
||||
"AddressingAttribute" = @("View","Edit")
|
||||
"Field" = @("View","Edit")
|
||||
"Command" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"Operation" = @("Use")
|
||||
"Method" = @("Use")
|
||||
"IntegrationServiceChannel" = @("Use")
|
||||
"Recalculation" = @("Read","Update")
|
||||
"Cube" = @("Read","View")
|
||||
"DimensionTable" = @("Read","View")
|
||||
"Function" = @("Use","View")
|
||||
"Table" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveDelete"
|
||||
)
|
||||
}
|
||||
|
||||
# Виды, существующие только у одного типа-родителя: без этой привязки
|
||||
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
|
||||
$script:kindOwners = @{
|
||||
"Table" = "ExternalDataSource"
|
||||
"Cube" = "ExternalDataSource"
|
||||
"Function" = "ExternalDataSource"
|
||||
"Field" = "ExternalDataSource"
|
||||
"DimensionTable" = "ExternalDataSource"
|
||||
"Recalculation" = "CalculationRegister"
|
||||
"Operation" = "WebService"
|
||||
"Method" = "HTTPService"
|
||||
"IntegrationServiceChannel" = "IntegrationService"
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
$script:nestedKindRightsByType = @{
|
||||
"ExternalDataSource" = @{
|
||||
"Dimension" = @("View")
|
||||
"Resource" = @("View")
|
||||
}
|
||||
}
|
||||
|
||||
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
|
||||
# отказ даёт отсутствие типа в $knownRights; здесь только причина для сообщения.
|
||||
$script:noRightsTypes = @(
|
||||
"Enum","CommonModule","DefinedType","CommonPicture","CommonTemplate","Language",
|
||||
"FunctionalOption","FunctionalOptionsParameter","EventSubscription","ScheduledJob",
|
||||
"StyleItem","Style","SettingsStorage","XDTOPackage","WSReference","DocumentNumerator"
|
||||
)
|
||||
|
||||
# --- 4. Presets (@view, @edit) ---
|
||||
|
||||
@@ -472,6 +550,66 @@ function Is-NestedObject {
|
||||
return ($objectName.Split(".").Count -ge 3)
|
||||
}
|
||||
|
||||
# Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
|
||||
# (ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца.
|
||||
function Get-NestedKind {
|
||||
param([string]$objectName)
|
||||
$parts = $objectName.Split(".")
|
||||
if ($parts.Count -lt 3) { return $null }
|
||||
return $parts[$parts.Count - 2]
|
||||
}
|
||||
|
||||
function Get-NestedRights {
|
||||
param([string]$objectType, [string]$kind)
|
||||
if ($script:nestedKindRightsByType.ContainsKey($objectType) -and
|
||||
$script:nestedKindRightsByType[$objectType].ContainsKey($kind)) {
|
||||
return @($script:nestedKindRightsByType[$objectType][$kind])
|
||||
}
|
||||
if ($script:nestedKindRights.ContainsKey($kind)) { return @($script:nestedKindRights[$kind]) }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Отказ копится, а не печатается сразу: роль пишется целиком, поэтому единственный
|
||||
# безопасный момент отказа — до первой записи, и показать надо все причины сразу.
|
||||
$script:validationErrors = @()
|
||||
|
||||
function Add-ValidationError {
|
||||
param([string]$message)
|
||||
$script:validationErrors += $message
|
||||
}
|
||||
|
||||
# Проверка имени объекта: тип по белому списку (всегда, включая вложенные пути) и вид
|
||||
# вложенности. Запрещённый и незнакомый тип — разные диагнозы.
|
||||
function Validate-ObjectName {
|
||||
param([string]$objectName)
|
||||
|
||||
$objectType = Get-ObjectType $objectName
|
||||
if (-not $script:knownRights.ContainsKey($objectType)) {
|
||||
if ($script:noRightsTypes -contains $objectType) {
|
||||
Add-ValidationError "${objectName}: тип '$objectType' не имеет прав в роли — уберите объект из списка"
|
||||
} else {
|
||||
$similar = @($script:knownRights.Keys | Where-Object { $_ -like "*$objectType*" -or $objectType -like "*$_*" })
|
||||
$sug = if ($similar.Count -gt 0) { " Возможно: $(($similar | Select-Object -First 3) -join ', ')?" } else { "" }
|
||||
Add-ValidationError "${objectName}: неизвестный тип объекта '$objectType'.$sug"
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
if (Is-NestedObject $objectName) {
|
||||
$kind = Get-NestedKind $objectName
|
||||
if ($script:kindOwners.ContainsKey($kind) -and $objectType -ne $script:kindOwners[$kind]) {
|
||||
Add-ValidationError "${objectName}: вид '$kind' бывает только у $($script:kindOwners[$kind])"
|
||||
return $false
|
||||
}
|
||||
if ($null -eq (Get-NestedRights $objectType $kind)) {
|
||||
Add-ValidationError "${objectName}: неизвестный вид вложенности '$kind'"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
function Resolve-Preset {
|
||||
param([string]$objectType, [string]$presetName)
|
||||
|
||||
@@ -503,23 +641,18 @@ function Validate-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
|
||||
}
|
||||
# Тип уже проверен Validate-ObjectName — здесь только права, иначе про один
|
||||
# запрещённый тип напечатается столько строк, сколько у него перечислено прав.
|
||||
if (-not $script:knownRights.ContainsKey($objectType)) { return $false }
|
||||
|
||||
if (-not $script:knownRights.ContainsKey($objectType)) {
|
||||
Write-Warning "${objectName}: unknown object type '$objectType'"
|
||||
if (Is-NestedObject $objectName) {
|
||||
$kind = Get-NestedKind $objectName
|
||||
$validNested = Get-NestedRights $objectType $kind
|
||||
if ($null -eq $validNested) { return $false }
|
||||
if ($rightName -notin $validNested) {
|
||||
Add-ValidationError "${objectName}: право '$rightName' недопустимо для вида '$kind' (допустимо: $($validNested -join ', '))"
|
||||
return $false
|
||||
}
|
||||
return $true
|
||||
}
|
||||
|
||||
@@ -528,8 +661,8 @@ function Validate-RightName {
|
||||
$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"
|
||||
$sugStr = if ($suggestions.Count -gt 0) { " Возможно: $(($suggestions | Select-Object -First 3) -join ', ')?" } else { "" }
|
||||
Add-ValidationError "${objectName}: право '$rightName' не существует у типа '$objectType'.$sugStr"
|
||||
return $false
|
||||
}
|
||||
|
||||
@@ -550,6 +683,9 @@ function Parse-ObjectEntry {
|
||||
}
|
||||
$objName = Translate-ObjectName ($entry.Substring(0, $colonIdx).Trim())
|
||||
$rightsStr = $entry.Substring($colonIdx + 1).Trim()
|
||||
# Объект с непроходным именем дальше не разбираем: пресет для несуществующего типа
|
||||
# добавил бы к отказу ещё и бессмысленное предупреждение.
|
||||
if (-not (Validate-ObjectName $objName)) { return $null }
|
||||
$objectType = Get-ObjectType $objName
|
||||
|
||||
if ($rightsStr.StartsWith('@')) {
|
||||
@@ -575,6 +711,8 @@ function Parse-ObjectEntry {
|
||||
return $null
|
||||
}
|
||||
|
||||
if (-not (Validate-ObjectName $objName)) { return $null }
|
||||
|
||||
$objectType = Get-ObjectType $objName
|
||||
$rightsMap = [ordered]@{}
|
||||
|
||||
@@ -648,6 +786,16 @@ if ($def.objects) {
|
||||
}
|
||||
}
|
||||
|
||||
# Отказ ДО записи: роль пишется тремя файлами (метаданные, права, регистрация в
|
||||
# Configuration.xml), и частично записанная роль хуже отсутствующей. Печатаем все причины
|
||||
# разом — иначе пользователь чинит их по одной. Console.Error, а не Write-Error: под
|
||||
# ErrorActionPreference=Stop последний бросает исключение (см. комментарий в support-guard).
|
||||
if ($script:validationErrors.Count -gt 0) {
|
||||
[Console]::Error.WriteLine("[role-compile] Роль не создана: $($script:validationErrors.Count) ошибок в описании прав.")
|
||||
foreach ($e in $script:validationErrors) { [Console]::Error.WriteLine(" ERROR: $e") }
|
||||
exit 1
|
||||
}
|
||||
|
||||
# --- Detect format version ---
|
||||
|
||||
function Detect-FormatVersion([string]$dir) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# role-compile v1.24 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# role-compile v1.25 — Compile 1C role from JSON (+русские алиасы типов: формы с ё и без)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -354,7 +354,24 @@ TYPE_ALIASES = {
|
||||
"ПараметрСеанса": "SessionParameter",
|
||||
"ОбщийРеквизит": "CommonAttribute",
|
||||
"Конфигурация": "Configuration",
|
||||
"ВнешнийИсточникДанных": "ExternalDataSource",
|
||||
# Типы без прав в ролях: алиасы нужны не ради генерации, а ради отказа по делу —
|
||||
# иначе на русскую запись навык ответит «неизвестный тип 'ОбщийМодуль'».
|
||||
"Перечисление": "Enum",
|
||||
"ОбщийМодуль": "CommonModule",
|
||||
"ОпределяемыйТип": "DefinedType",
|
||||
"ОбщаяКартинка": "CommonPicture",
|
||||
"ОбщийМакет": "CommonTemplate",
|
||||
"Язык": "Language",
|
||||
"ФункциональнаяОпция": "FunctionalOption",
|
||||
"ПараметрФункциональныхОпций": "FunctionalOptionsParameter",
|
||||
"ПодпискаНаСобытие": "EventSubscription",
|
||||
"РегламентноеЗадание": "ScheduledJob",
|
||||
"ЭлементСтиля": "StyleItem",
|
||||
"ХранилищеНастроек": "SettingsStorage",
|
||||
"ПакетXDTO": "XDTOPackage",
|
||||
"WSСсылка": "WSReference",
|
||||
"Нумератор": "DocumentNumerator",
|
||||
# Nested
|
||||
"Реквизит": "Attribute",
|
||||
"СтандартныйРеквизит": "StandardAttribute",
|
||||
@@ -516,10 +533,72 @@ KNOWN_RIGHTS = {
|
||||
"IntegrationService": ["Use"],
|
||||
"SessionParameter": ["Get", "Set"],
|
||||
"CommonAttribute": ["View", "Edit"],
|
||||
"ExternalDataSource": [
|
||||
"Use", "Administration", "StandardAuthenticationChange",
|
||||
"SessionStandardAuthenticationChange", "SessionOSAuthenticationChange",
|
||||
],
|
||||
}
|
||||
|
||||
NESTED_RIGHTS = ["View", "Edit"]
|
||||
COMMAND_RIGHTS = ["View"]
|
||||
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
|
||||
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
|
||||
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
|
||||
NESTED_KIND_RIGHTS = {
|
||||
"Attribute": ["View", "Edit"],
|
||||
"StandardAttribute": ["View", "Edit"],
|
||||
"TabularSection": ["View", "Edit"],
|
||||
"StandardTabularSection": ["View", "Edit"],
|
||||
"Dimension": ["View", "Edit"],
|
||||
"Resource": ["View", "Edit"],
|
||||
"AccountingFlag": ["View", "Edit"],
|
||||
"ExtDimensionAccountingFlag": ["View", "Edit"],
|
||||
"AddressingAttribute": ["View", "Edit"],
|
||||
"Field": ["View", "Edit"],
|
||||
"Command": ["View"],
|
||||
"Subsystem": ["View"],
|
||||
"Operation": ["Use"],
|
||||
"Method": ["Use"],
|
||||
"IntegrationServiceChannel": ["Use"],
|
||||
"Recalculation": ["Read", "Update"],
|
||||
"Cube": ["Read", "View"],
|
||||
"DimensionTable": ["Read", "View"],
|
||||
"Function": ["Use", "View"],
|
||||
"Table": [
|
||||
"Read", "Insert", "Update", "Delete", "View", "Edit", "InputByString",
|
||||
"InteractiveInsert", "InteractiveDelete",
|
||||
],
|
||||
}
|
||||
|
||||
# Виды, существующие только у одного типа-родителя: без этой привязки
|
||||
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
|
||||
KIND_OWNERS = {
|
||||
'Table': 'ExternalDataSource',
|
||||
'Cube': 'ExternalDataSource',
|
||||
'Function': 'ExternalDataSource',
|
||||
'Field': 'ExternalDataSource',
|
||||
'DimensionTable': 'ExternalDataSource',
|
||||
'Recalculation': 'CalculationRegister',
|
||||
'Operation': 'WebService',
|
||||
'Method': 'HTTPService',
|
||||
'IntegrationServiceChannel': 'IntegrationService',
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
NESTED_KIND_RIGHTS_BY_TYPE = {
|
||||
"ExternalDataSource": {
|
||||
"Dimension": ["View"],
|
||||
"Resource": ["View"],
|
||||
},
|
||||
}
|
||||
|
||||
# Типы без прав в ролях (в дереве редактора ролей их нет). Список НЕ управляет поведением —
|
||||
# отказ даёт отсутствие типа в KNOWN_RIGHTS; здесь только выбор формулировки.
|
||||
NO_RIGHTS_TYPES = [
|
||||
"Enum", "CommonModule", "DefinedType", "CommonPicture", "CommonTemplate", "Language",
|
||||
"FunctionalOption", "FunctionalOptionsParameter", "EventSubscription", "ScheduledJob",
|
||||
"StyleItem", "Style", "SettingsStorage", "XDTOPackage", "WSReference", "DocumentNumerator",
|
||||
]
|
||||
|
||||
# --- Presets ---
|
||||
|
||||
@@ -594,6 +673,56 @@ def is_nested_object(object_name):
|
||||
return len(object_name.split('.')) >= 3
|
||||
|
||||
|
||||
def get_nested_kind(object_name):
|
||||
"""Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
|
||||
(ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца."""
|
||||
parts = object_name.split('.')
|
||||
if len(parts) < 3:
|
||||
return None
|
||||
return parts[-2]
|
||||
|
||||
|
||||
def get_nested_rights(object_type, kind):
|
||||
by_type = NESTED_KIND_RIGHTS_BY_TYPE.get(object_type)
|
||||
if by_type and kind in by_type:
|
||||
return by_type[kind]
|
||||
return NESTED_KIND_RIGHTS.get(kind)
|
||||
|
||||
|
||||
# Отказ копится, а не печатается сразу: роль пишется целиком, поэтому единственный
|
||||
# безопасный момент отказа — до первой записи, и показать надо все причины сразу.
|
||||
VALIDATION_ERRORS = []
|
||||
|
||||
|
||||
def add_validation_error(message):
|
||||
VALIDATION_ERRORS.append(message)
|
||||
|
||||
|
||||
def validate_object_name(object_name):
|
||||
"""Тип по белому списку (всегда, включая вложенные пути) и вид вложенности.
|
||||
Запрещённый и незнакомый тип — разные диагнозы."""
|
||||
object_type = get_object_type(object_name)
|
||||
if object_type not in KNOWN_RIGHTS:
|
||||
if object_type in NO_RIGHTS_TYPES:
|
||||
add_validation_error(f"{object_name}: тип '{object_type}' не имеет прав в роли — уберите объект из списка")
|
||||
else:
|
||||
similar = [t for t in KNOWN_RIGHTS if object_type in t or t in object_type][:3]
|
||||
sug = f" Возможно: {', '.join(similar)}?" if similar else ''
|
||||
add_validation_error(f"{object_name}: неизвестный тип объекта '{object_type}'.{sug}")
|
||||
return False
|
||||
|
||||
if is_nested_object(object_name):
|
||||
kind = get_nested_kind(object_name)
|
||||
if kind in KIND_OWNERS and object_type != KIND_OWNERS[kind]:
|
||||
add_validation_error(f"{object_name}: вид '{kind}' бывает только у {KIND_OWNERS[kind]}")
|
||||
return False
|
||||
if get_nested_rights(object_type, kind) is None:
|
||||
add_validation_error(f"{object_name}: неизвестный вид вложенности '{kind}'")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def resolve_preset(object_type, preset_name):
|
||||
preset = preset_name.lstrip('@')
|
||||
if preset not in PRESETS:
|
||||
@@ -614,26 +743,26 @@ def resolve_preset(object_type, preset_name):
|
||||
def validate_right_name(object_name, right_name):
|
||||
object_type = get_object_type(object_name)
|
||||
|
||||
if is_nested_object(object_name):
|
||||
if '.Command.' in object_name:
|
||||
if right_name not in COMMAND_RIGHTS:
|
||||
print(f"WARNING: {object_name}: '{right_name}' not valid for commands (only: View)", file=sys.stderr)
|
||||
return False
|
||||
else:
|
||||
if right_name not in NESTED_RIGHTS:
|
||||
print(f"WARNING: {object_name}: '{right_name}' not valid for nested objects (only: View, Edit)", file=sys.stderr)
|
||||
return False
|
||||
return True
|
||||
|
||||
# Тип уже проверен validate_object_name — здесь только права, иначе про один
|
||||
# запрещённый тип напечатается столько строк, сколько у него перечислено прав.
|
||||
if object_type not in KNOWN_RIGHTS:
|
||||
print(f"WARNING: {object_name}: unknown object type '{object_type}'", file=sys.stderr)
|
||||
return False
|
||||
|
||||
if is_nested_object(object_name):
|
||||
kind = get_nested_kind(object_name)
|
||||
valid_nested = get_nested_rights(object_type, kind)
|
||||
if valid_nested is None:
|
||||
return False
|
||||
if right_name not in valid_nested:
|
||||
add_validation_error(f"{object_name}: право '{right_name}' недопустимо для вида '{kind}' (допустимо: {', '.join(valid_nested)})")
|
||||
return False
|
||||
return True
|
||||
|
||||
valid_rights = KNOWN_RIGHTS[object_type]
|
||||
if right_name not in valid_rights:
|
||||
suggestions = [r for r in valid_rights if right_name in r or r in right_name]
|
||||
sug_str = f" Did you mean: {', '.join(suggestions)}?" if suggestions else ""
|
||||
print(f"WARNING: {object_name}: unknown right '{right_name}'.{sug_str}", file=sys.stderr)
|
||||
suggestions = [r for r in valid_rights if right_name in r or r in right_name][:3]
|
||||
sug_str = f" Возможно: {', '.join(suggestions)}?" if suggestions else ""
|
||||
add_validation_error(f"{object_name}: право '{right_name}' не существует у типа '{object_type}'.{sug_str}")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -648,6 +777,10 @@ def parse_object_entry(entry):
|
||||
return None
|
||||
obj_name = translate_object_name(entry[:colon_idx].strip())
|
||||
rights_str = entry[colon_idx + 1:].strip()
|
||||
# Объект с непроходным именем дальше не разбираем: пресет для несуществующего типа
|
||||
# добавил бы к отказу ещё и бессмысленное предупреждение.
|
||||
if not validate_object_name(obj_name):
|
||||
return None
|
||||
object_type = get_object_type(obj_name)
|
||||
|
||||
if rights_str.startswith('@'):
|
||||
@@ -668,6 +801,9 @@ def parse_object_entry(entry):
|
||||
print("WARNING: Object entry missing 'name' field", file=sys.stderr)
|
||||
return None
|
||||
|
||||
if not validate_object_name(obj_name):
|
||||
return None
|
||||
|
||||
object_type = get_object_type(obj_name)
|
||||
# Use a list of tuples to preserve insertion order
|
||||
rights_map = {} # name -> {Value, Condition}
|
||||
@@ -761,6 +897,15 @@ def main():
|
||||
if parsed:
|
||||
parsed_objects.append(parsed)
|
||||
|
||||
# Отказ ДО записи: роль пишется тремя файлами (метаданные, права, регистрация в
|
||||
# Configuration.xml), и частично записанная роль хуже отсутствующей. Печатаем все
|
||||
# причины разом — иначе пользователь чинит их по одной.
|
||||
if VALIDATION_ERRORS:
|
||||
print(f"[role-compile] Роль не создана: {len(VALIDATION_ERRORS)} ошибок в описании прав.", file=sys.stderr)
|
||||
for e in VALIDATION_ERRORS:
|
||||
print(f" ERROR: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- 3. Generate UUID ---
|
||||
uid = new_uuid()
|
||||
|
||||
|
||||
@@ -25,3 +25,14 @@ allowed-tools:
|
||||
```powershell
|
||||
powershell.exe -NoProfile -File "${CLAUDE_SKILL_DIR}/scripts/role-validate.ps1" -RightsPath "Roles/МояРоль"
|
||||
```
|
||||
|
||||
## Что считается ошибкой
|
||||
|
||||
Ошибка (код возврата 1), а не предупреждение:
|
||||
|
||||
- тип объекта, не имеющий прав в ролях (`Enum`, `CommonModule`, `CommonPicture`, `ScheduledJob` и т.п.) — конфигурация с блоком прав на перечисление не загружается в базу, конфигуратор зависает без сообщений;
|
||||
- незнакомый тип объекта (опечатка);
|
||||
- имя права, которого у этого типа нет;
|
||||
- вид вложенности, невозможный у данного типа, и недопустимое право у вложенного объекта.
|
||||
|
||||
Полные списки «тип → права» и виды вложенности — в `role-compile/dsl-reference.md`.
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# role-validate v1.2 — Validate 1C role structure
|
||||
# role-validate v1.3 — Validate 1C role structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -127,11 +127,72 @@ $script:knownRights = @{
|
||||
"IntegrationService" = @("Use")
|
||||
"SessionParameter" = @("Get","Set")
|
||||
"CommonAttribute" = @("View","Edit")
|
||||
"ExternalDataSource" = @(
|
||||
"Use","Administration","StandardAuthenticationChange",
|
||||
"SessionStandardAuthenticationChange","SessionOSAuthenticationChange"
|
||||
)
|
||||
}
|
||||
|
||||
$script:nestedRights = @("View","Edit")
|
||||
$script:channelRights = @("Use")
|
||||
$script:commandRights = @("View")
|
||||
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
|
||||
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
|
||||
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
|
||||
$script:nestedKindRights = @{
|
||||
"Attribute" = @("View","Edit")
|
||||
"StandardAttribute" = @("View","Edit")
|
||||
"TabularSection" = @("View","Edit")
|
||||
"StandardTabularSection" = @("View","Edit")
|
||||
"Dimension" = @("View","Edit")
|
||||
"Resource" = @("View","Edit")
|
||||
"AccountingFlag" = @("View","Edit")
|
||||
"ExtDimensionAccountingFlag" = @("View","Edit")
|
||||
"AddressingAttribute" = @("View","Edit")
|
||||
"Field" = @("View","Edit")
|
||||
"Command" = @("View")
|
||||
"Subsystem" = @("View")
|
||||
"Operation" = @("Use")
|
||||
"Method" = @("Use")
|
||||
"IntegrationServiceChannel" = @("Use")
|
||||
"Recalculation" = @("Read","Update")
|
||||
"Cube" = @("Read","View")
|
||||
"DimensionTable" = @("Read","View")
|
||||
"Function" = @("Use","View")
|
||||
"Table" = @(
|
||||
"Read","Insert","Update","Delete","View","Edit","InputByString",
|
||||
"InteractiveInsert","InteractiveDelete"
|
||||
)
|
||||
}
|
||||
|
||||
# Виды, существующие только у одного типа-родителя: без этой привязки
|
||||
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
|
||||
$script:kindOwners = @{
|
||||
"Table" = "ExternalDataSource"
|
||||
"Cube" = "ExternalDataSource"
|
||||
"Function" = "ExternalDataSource"
|
||||
"Field" = "ExternalDataSource"
|
||||
"DimensionTable" = "ExternalDataSource"
|
||||
"Recalculation" = "CalculationRegister"
|
||||
"Operation" = "WebService"
|
||||
"Method" = "HTTPService"
|
||||
"IntegrationServiceChannel" = "IntegrationService"
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
$script:nestedKindRightsByType = @{
|
||||
"ExternalDataSource" = @{
|
||||
"Dimension" = @("View")
|
||||
"Resource" = @("View")
|
||||
}
|
||||
}
|
||||
|
||||
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
|
||||
# отказ даёт отсутствие типа в $knownRights; здесь только причина для сообщения.
|
||||
$script:noRightsTypes = @(
|
||||
"Enum","CommonModule","DefinedType","CommonPicture","CommonTemplate","Language",
|
||||
"FunctionalOption","FunctionalOptionsParameter","EventSubscription","ScheduledJob",
|
||||
"StyleItem","Style","SettingsStorage","XDTOPackage","WSReference","DocumentNumerator"
|
||||
)
|
||||
|
||||
# --- 2. Output helpers ---
|
||||
|
||||
@@ -179,6 +240,37 @@ function Is-NestedObject {
|
||||
return ($name.Split(".").Count -ge 3)
|
||||
}
|
||||
|
||||
# Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
|
||||
# (ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца.
|
||||
function Get-NestedKind {
|
||||
param([string]$name)
|
||||
$parts = $name.Split(".")
|
||||
if ($parts.Count -lt 3) { return $null }
|
||||
return $parts[$parts.Count - 2]
|
||||
}
|
||||
|
||||
function Get-NestedRights {
|
||||
param([string]$objectType, [string]$kind)
|
||||
if ($script:nestedKindRightsByType.ContainsKey($objectType) -and
|
||||
$script:nestedKindRightsByType[$objectType].ContainsKey($kind)) {
|
||||
return @($script:nestedKindRightsByType[$objectType][$kind])
|
||||
}
|
||||
if ($script:nestedKindRights.ContainsKey($kind)) { return @($script:nestedKindRights[$kind]) }
|
||||
return $null
|
||||
}
|
||||
|
||||
# Запрещённый тип и незнакомый тип — разные диагнозы: первый документирован, второй
|
||||
# скорее опечатка или пробел в таблице.
|
||||
function Format-TypeError {
|
||||
param([string]$objName, [string]$objectType)
|
||||
if ($script:noRightsTypes -contains $objectType) {
|
||||
return "${objName}: тип '$objectType' не имеет прав в роли — блок нужно удалить"
|
||||
}
|
||||
$similar = Find-Similar -needle $objectType -haystack @($script:knownRights.Keys)
|
||||
$sug = if ($similar.Count -gt 0) { " Возможно: $($similar -join ', ')?" } else { "" }
|
||||
return "${objName}: неизвестный тип объекта '$objectType'.$sug"
|
||||
}
|
||||
|
||||
function Find-Similar {
|
||||
param([string]$needle, [string[]]$haystack)
|
||||
$result = @($haystack | Where-Object {
|
||||
@@ -303,9 +395,16 @@ foreach ($obj in $objects) {
|
||||
$objectType = Get-ObjectType $objName
|
||||
$isNested = Is-NestedObject $objName
|
||||
|
||||
# Check object type is known
|
||||
if (-not $isNested -and -not $script:knownRights.ContainsKey($objectType)) {
|
||||
Report-Warn "${objName}: unknown object type '$objectType'"
|
||||
# Тип проверяется ВСЕГДА, включая вложенные пути: раньше `Enum.Х.Attribute.Y` не проверялся
|
||||
# вообще, потому что ветка «unknown» стояла под `-not $isNested`.
|
||||
$nestedKind = if ($isNested) { Get-NestedKind $objName } else { $null }
|
||||
$typeKnown = $script:knownRights.ContainsKey($objectType)
|
||||
if (-not $typeKnown) {
|
||||
Report-Error (Format-TypeError $objName $objectType)
|
||||
} elseif ($isNested -and $script:kindOwners.ContainsKey($nestedKind) -and $objectType -ne $script:kindOwners[$nestedKind]) {
|
||||
Report-Error "${objName}: вид '$nestedKind' бывает только у $($script:kindOwners[$nestedKind])"
|
||||
} elseif ($isNested -and $null -eq (Get-NestedRights $objectType $nestedKind)) {
|
||||
Report-Error "${objName}: неизвестный вид вложенности '$nestedKind'"
|
||||
}
|
||||
|
||||
# Check rights
|
||||
@@ -345,27 +444,20 @@ foreach ($obj in $objects) {
|
||||
|
||||
$rightCount++
|
||||
|
||||
# Validate right name
|
||||
if ($isNested) {
|
||||
if ($objName -match '\.Command\.') {
|
||||
if ($rName -notin $script:commandRights) {
|
||||
Report-Warn "${objName}: '$rName' not valid for commands (only: View)"
|
||||
}
|
||||
} elseif ($objName -match '\.IntegrationServiceChannel\.') {
|
||||
if ($rName -notin $script:channelRights) {
|
||||
Report-Warn "${objName}: '$rName' not valid for channels (only: Use)"
|
||||
}
|
||||
} else {
|
||||
if ($rName -notin $script:nestedRights) {
|
||||
Report-Warn "${objName}: '$rName' not valid for nested objects (only: View, Edit)"
|
||||
}
|
||||
# Validate right name. Тип уже отвергнут выше — второй раз про него не пишем.
|
||||
if (-not $typeKnown) {
|
||||
# уже сообщено
|
||||
} elseif ($isNested) {
|
||||
$validNested = Get-NestedRights $objectType $nestedKind
|
||||
if ($null -ne $validNested -and $rName -notin $validNested) {
|
||||
Report-Error "${objName}: право '$rName' недопустимо для вида '$nestedKind' (допустимо: $($validNested -join ', '))"
|
||||
}
|
||||
} elseif ($script:knownRights.ContainsKey($objectType)) {
|
||||
} else {
|
||||
$validRights = $script:knownRights[$objectType]
|
||||
if ($rName -notin $validRights) {
|
||||
$similar = Find-Similar -needle $rName -haystack $validRights
|
||||
$sugStr = if ($similar.Count -gt 0) { " Did you mean: $($similar -join ', ')?" } else { "" }
|
||||
Report-Warn "${objName}: unknown right '$rName'.$sugStr"
|
||||
$sugStr = if ($similar.Count -gt 0) { " Возможно: $($similar -join ', ')?" } else { "" }
|
||||
Report-Error "${objName}: право '$rName' не существует у типа '$objectType'.$sugStr"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# role-validate v1.2 — Validate 1C role Rights.xml structure
|
||||
# role-validate v1.3 — Validate 1C role Rights.xml structure
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
"""Validates role Rights.xml: root element, global flags, objects, rights, RLS, templates."""
|
||||
import sys, os, argparse, re
|
||||
@@ -145,11 +145,72 @@ KNOWN_RIGHTS = {
|
||||
'IntegrationService': ['Use'],
|
||||
'SessionParameter': ['Get', 'Set'],
|
||||
'CommonAttribute': ['View', 'Edit'],
|
||||
'ExternalDataSource': [
|
||||
'Use', 'Administration', 'StandardAuthenticationChange',
|
||||
'SessionStandardAuthenticationChange', 'SessionOSAuthenticationChange',
|
||||
],
|
||||
}
|
||||
|
||||
NESTED_RIGHTS = ['View', 'Edit']
|
||||
CHANNEL_RIGHTS = ['Use']
|
||||
COMMAND_RIGHTS = ['View']
|
||||
# Виды вложенности (предпоследний сегмент пути) → допустимые права. Списки сняты с корпуса
|
||||
# типовых конфигураций и с выгрузки роли, где права проставлены по всему дереву редактора:
|
||||
# догадкам тут не место — закрытый список превращает промах в ложный отказ.
|
||||
NESTED_KIND_RIGHTS = {
|
||||
'Attribute': ['View', 'Edit'],
|
||||
'StandardAttribute': ['View', 'Edit'],
|
||||
'TabularSection': ['View', 'Edit'],
|
||||
'StandardTabularSection': ['View', 'Edit'],
|
||||
'Dimension': ['View', 'Edit'],
|
||||
'Resource': ['View', 'Edit'],
|
||||
'AccountingFlag': ['View', 'Edit'],
|
||||
'ExtDimensionAccountingFlag': ['View', 'Edit'],
|
||||
'AddressingAttribute': ['View', 'Edit'],
|
||||
'Field': ['View', 'Edit'],
|
||||
'Command': ['View'],
|
||||
'Subsystem': ['View'],
|
||||
'Operation': ['Use'],
|
||||
'Method': ['Use'],
|
||||
'IntegrationServiceChannel': ['Use'],
|
||||
'Recalculation': ['Read', 'Update'],
|
||||
'Cube': ['Read', 'View'],
|
||||
'DimensionTable': ['Read', 'View'],
|
||||
'Function': ['Use', 'View'],
|
||||
'Table': [
|
||||
'Read', 'Insert', 'Update', 'Delete', 'View', 'Edit', 'InputByString',
|
||||
'InteractiveInsert', 'InteractiveDelete',
|
||||
],
|
||||
}
|
||||
|
||||
# Виды, существующие только у одного типа-родителя: без этой привязки
|
||||
# `Catalog.Товары.Field.Цена` прошёл бы как валидный вложенный объект.
|
||||
KIND_OWNERS = {
|
||||
'Table': 'ExternalDataSource',
|
||||
'Cube': 'ExternalDataSource',
|
||||
'Function': 'ExternalDataSource',
|
||||
'Field': 'ExternalDataSource',
|
||||
'DimensionTable': 'ExternalDataSource',
|
||||
'Recalculation': 'CalculationRegister',
|
||||
'Operation': 'WebService',
|
||||
'Method': 'HTTPService',
|
||||
'IntegrationServiceChannel': 'IntegrationService',
|
||||
}
|
||||
|
||||
# Один и тот же вид под разными родителями имеет разный набор: измерение регистра —
|
||||
# View + Edit, измерение куба внешнего источника — только View. Объединять нельзя,
|
||||
# объединение молча разрешило бы Edit там, где платформа его не даёт.
|
||||
NESTED_KIND_RIGHTS_BY_TYPE = {
|
||||
'ExternalDataSource': {
|
||||
'Dimension': ['View'],
|
||||
'Resource': ['View'],
|
||||
},
|
||||
}
|
||||
|
||||
# Типы без прав в ролях (в дереве редактора ролей их нет). Таблица НЕ управляет поведением —
|
||||
# отказ даёт отсутствие типа в KNOWN_RIGHTS; здесь только причина для сообщения.
|
||||
NO_RIGHTS_TYPES = [
|
||||
'Enum', 'CommonModule', 'DefinedType', 'CommonPicture', 'CommonTemplate', 'Language',
|
||||
'FunctionalOption', 'FunctionalOptionsParameter', 'EventSubscription', 'ScheduledJob',
|
||||
'StyleItem', 'Style', 'SettingsStorage', 'XDTOPackage', 'WSReference', 'DocumentNumerator',
|
||||
]
|
||||
|
||||
|
||||
def get_object_type(name):
|
||||
@@ -163,6 +224,22 @@ def is_nested_object(name):
|
||||
return name.count('.') >= 2
|
||||
|
||||
|
||||
def get_nested_kind(name):
|
||||
"""Вид вложенности — предпоследний сегмент: путь бывает и восьмисегментным
|
||||
(ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П), считать от конца."""
|
||||
parts = name.split('.')
|
||||
if len(parts) < 3:
|
||||
return None
|
||||
return parts[-2]
|
||||
|
||||
|
||||
def get_nested_rights(object_type, kind):
|
||||
by_type = NESTED_KIND_RIGHTS_BY_TYPE.get(object_type)
|
||||
if by_type and kind in by_type:
|
||||
return by_type[kind]
|
||||
return NESTED_KIND_RIGHTS.get(kind)
|
||||
|
||||
|
||||
def find_similar(needle, haystack):
|
||||
result = []
|
||||
needle_lower = needle.lower()
|
||||
@@ -175,6 +252,16 @@ def find_similar(needle, haystack):
|
||||
return result
|
||||
|
||||
|
||||
def format_type_error(obj_name, object_type):
|
||||
"""Запрещённый тип и незнакомый тип — разные диагнозы: первый документирован,
|
||||
второй скорее опечатка или пробел в таблице."""
|
||||
if object_type in NO_RIGHTS_TYPES:
|
||||
return f"{obj_name}: тип '{object_type}' не имеет прав в роли — блок нужно удалить"
|
||||
similar = find_similar(object_type, list(KNOWN_RIGHTS.keys()))
|
||||
sug = f" Возможно: {', '.join(similar)}?" if similar else ''
|
||||
return f"{obj_name}: неизвестный тип объекта '{object_type}'.{sug}"
|
||||
|
||||
|
||||
def get_child_text(parent, local_name, ns):
|
||||
"""Get text of first child element with given local name in namespace."""
|
||||
for child in parent:
|
||||
@@ -339,9 +426,16 @@ def main():
|
||||
object_type = get_object_type(obj_name)
|
||||
is_nested = is_nested_object(obj_name)
|
||||
|
||||
# Check object type is known
|
||||
if not is_nested and object_type not in KNOWN_RIGHTS:
|
||||
report_warn(f"{obj_name}: unknown object type '{object_type}'")
|
||||
# Тип проверяется ВСЕГДА, включая вложенные пути: раньше `Enum.Х.Attribute.Y`
|
||||
# не проверялся вообще — ветка «unknown» стояла под `not is_nested`.
|
||||
nested_kind = get_nested_kind(obj_name) if is_nested else None
|
||||
type_known = object_type in KNOWN_RIGHTS
|
||||
if not type_known:
|
||||
report_error(format_type_error(obj_name, object_type))
|
||||
elif is_nested and nested_kind in KIND_OWNERS and object_type != KIND_OWNERS[nested_kind]:
|
||||
report_error(f"{obj_name}: вид '{nested_kind}' бывает только у {KIND_OWNERS[nested_kind]}")
|
||||
elif is_nested and get_nested_rights(object_type, nested_kind) is None:
|
||||
report_error(f"{obj_name}: неизвестный вид вложенности '{nested_kind}'")
|
||||
|
||||
# Check rights
|
||||
for child in obj:
|
||||
@@ -384,23 +478,19 @@ def main():
|
||||
|
||||
right_count += 1
|
||||
|
||||
# Validate right name
|
||||
if is_nested:
|
||||
if '.Command.' in obj_name:
|
||||
if r_name not in COMMAND_RIGHTS:
|
||||
report_warn(f"{obj_name}: '{r_name}' not valid for commands (only: View)")
|
||||
elif '.IntegrationServiceChannel.' in obj_name:
|
||||
if r_name not in CHANNEL_RIGHTS:
|
||||
report_warn(f"{obj_name}: '{r_name}' not valid for channels (only: Use)")
|
||||
else:
|
||||
if r_name not in NESTED_RIGHTS:
|
||||
report_warn(f"{obj_name}: '{r_name}' not valid for nested objects (only: View, Edit)")
|
||||
elif object_type in KNOWN_RIGHTS:
|
||||
# Validate right name. Тип уже отвергнут выше — второй раз про него не пишем.
|
||||
if not type_known:
|
||||
pass
|
||||
elif is_nested:
|
||||
valid_nested = get_nested_rights(object_type, nested_kind)
|
||||
if valid_nested is not None and r_name not in valid_nested:
|
||||
report_error(f"{obj_name}: право '{r_name}' недопустимо для вида '{nested_kind}' (допустимо: {', '.join(valid_nested)})")
|
||||
else:
|
||||
valid_rights = KNOWN_RIGHTS[object_type]
|
||||
if r_name not in valid_rights:
|
||||
similar = find_similar(r_name, valid_rights)
|
||||
sug_str = f' Did you mean: {", ".join(similar)}?' if similar else ''
|
||||
report_warn(f"{obj_name}: unknown right '{r_name}'.{sug_str}")
|
||||
sug_str = f" Возможно: {', '.join(similar)}?" if similar else ''
|
||||
report_error(f"{obj_name}: право '{r_name}' не существует у типа '{object_type}'.{sug_str}")
|
||||
|
||||
report_ok(f'{obj_count} objects, {right_count} rights')
|
||||
if rls_count > 0:
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.36 — Atomic 1C DCS editor (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||
# skd-edit v1.37 — Atomic 1C DCS editor (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
# NB: парный .py собирает выражения автодат вне f-string ради совместимости с python 3.9 (PEP 701).
|
||||
param(
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-edit v1.36 — Atomic 1C DCS editor (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||
# skd-edit v1.37 — Atomic 1C DCS editor (Python port) (+esc_xml/esc_xml_text: разное экранирование атрибута и текста)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -2437,7 +2437,7 @@ elif operation == "rename-parameter":
|
||||
new_name = m_rn.group(2).strip()
|
||||
|
||||
if old_name == new_name:
|
||||
print('[WARN] rename-parameter: old and new names are equal -- skipped')
|
||||
print('[WARN] rename-parameter: old and new names are equal — skipped')
|
||||
continue
|
||||
|
||||
# 1. Rename <parameter><name>OldName</name>
|
||||
@@ -2494,7 +2494,7 @@ elif operation == "reorder-parameters":
|
||||
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')
|
||||
print('[WARN] reorder-parameters: empty list — skipped')
|
||||
continue
|
||||
|
||||
all_params = []
|
||||
@@ -2647,7 +2647,7 @@ elif operation == "add-selection":
|
||||
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')
|
||||
print(f'[WARN] SelectedItemAuto already exists in {target} — skipped')
|
||||
continue
|
||||
|
||||
sel_indent = get_container_child_indent(selection)
|
||||
|
||||
@@ -160,7 +160,7 @@ python scripts/switch.py --runtime powershell # вернуть на PowerShell
|
||||
- [Встроенная справка](docs/1c-help-spec.md) — Help.xml, HTML-страницы, кнопка справки на форме
|
||||
- [Пакетный режим конфигуратора 1С](docs/build-spec.md) — команды `1cv8.exe`, DESIGNER, ENTERPRISE, CREATEINFOBASE
|
||||
- [Табличный документ (MXL)](docs/1c-spreadsheet-spec.md) — XML-формат SpreadsheetDocument, совместимость версий
|
||||
- [MXL DSL](docs/mxl-dsl-spec.md) — JSON-формат описания макета для `/mxl-compile` и `/mxl-decompile`
|
||||
- [MXL DSL](docs/mxl-dsl-spec.md) — JSON-формат описания макета для `/mxl-compile` и `/mxl-decompile`: структура, оформление, полный перечень свойств стиля
|
||||
- [Form DSL](docs/form-dsl-spec.md) — JSON-формат описания формы для `/form-compile`
|
||||
- [Роли (Rights.xml)](docs/1c-role-spec.md) — XML-формат прав роли, типы объектов, RLS
|
||||
- [Role DSL](docs/role-dsl-spec.md) — JSON-формат описания ролей для `/role-compile`
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Спецификация формата XML объектов метаданных конфигурации 1С
|
||||
|
||||
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
|
||||
Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.18` (8.3.25), `2.19` (8.3.26), `2.20` (8.3.27+).
|
||||
Проверенный диапазон версий формата: `2.17` (платформа 8.3.24) … `2.21` (8.5); полная лестница — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
Источники: выгрузки ERP 2, Бухгалтерия предприятия (платформы 8.3.20, 8.3.24, 8.3.27).
|
||||
Источники: выгрузки ERP 2, Бухгалтерия предприятия (платформы 8.3.24–8.3.27).
|
||||
|
||||
> **Связанные спецификации:**
|
||||
> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md)
|
||||
@@ -1698,9 +1698,9 @@ XML-элемент: `<WebService>`. Трёхуровневая вложенно
|
||||
|
||||
## 26. Различия версий платформы
|
||||
|
||||
### 26.1. Лестница версий 2.17 → 2.18 → 2.19 → 2.20
|
||||
### 26.1. Лестница версий
|
||||
|
||||
Атрибут `version` корневого элемента `<MetaDataObject>` задаёт платформа, которой выгружают: 8.3.20–8.3.24 → `2.17`, 8.3.25 → `2.18`, 8.3.26 → `2.19`, 8.3.27 → `2.20`. От режима совместимости конфигурации он не зависит.
|
||||
Атрибут `version` корневого элемента `<MetaDataObject>` задаёт платформа, которой выгружают, — по одной версии формата на релиз платформы: 8.3.24 → `2.17`, 8.3.25 → `2.18`, 8.3.26 → `2.19`, 8.3.27 → `2.20`, 8.5 → `2.21`. От режима совместимости конфигурации он не зависит. Полная лестница, включая версии ниже проверенного диапазона, — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
**Изменения в версии 2.18 (платформа 8.3.25):**
|
||||
|
||||
@@ -1742,7 +1742,7 @@ XML-элемент: `<WebService>`. Трёхуровневая вложенно
|
||||
|
||||
### 26.2. Стабильные элементы
|
||||
|
||||
Между версиями 8.3.20 → 8.3.24 → 8.3.25 → 8.3.26 → 8.3.27:
|
||||
Между версиями 8.3.24 → 8.3.25 → 8.3.26 → 8.3.27:
|
||||
- Структура каталогов **без изменений**
|
||||
- Пространства имён **без изменений**
|
||||
- UUID объектов **сохраняются**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Спецификация корневой структуры конфигурации 1С
|
||||
|
||||
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
|
||||
Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.18` (8.3.25), `2.19` (8.3.26), `2.20` (8.3.27+).
|
||||
Проверенный диапазон версий формата: `2.17` (платформа 8.3.24) … `2.21` (8.5). Полная лестница — [§7.1](#71-лестница-версий).
|
||||
|
||||
Источники: выгрузки Бухгалтерия предприятия (платформы 8.3.20, 8.3.24, 8.3.27), ERP 2 (8.3.24).
|
||||
Источники: выгрузки Бухгалтерия предприятия (платформы 8.3.24–8.3.27), ERP 2 (8.3.24), УНФ (8.5.1).
|
||||
|
||||
> **Связанные спецификации:**
|
||||
> - Объекты метаданных — [1c-config-objects-spec.md](1c-config-objects-spec.md)
|
||||
@@ -283,6 +283,44 @@ ClassId — фиксированные идентификаторы классо
|
||||
|
||||
Внутри одного типа объекты отсортированы по имени (алфавитный порядок). Типы, для которых нет объектов, в ChildObjects не записываются.
|
||||
|
||||
### 2.5. InternalInfo объектов — наборы GeneratedType
|
||||
|
||||
У каждого объекта метаданных в `<InternalInfo>` перечислены порождаемые платформой типы. Набор фиксирован для типа объекта: имя элемента — `<префикс>.<ИмяОбъекта>`, атрибут `category` — категория из таблицы. **Неполный набор платформа отвергает при загрузке**: «отсутствует один или более типов объекта <Тип>» (для заимствованных оболочек в расширениях — тоже).
|
||||
|
||||
Запись `префикс`/Категория; `…` в префиксе = имя типа объекта (`…Ref` у `ChartOfAccounts` читается `ChartOfAccountsRef`).
|
||||
|
||||
| Тип объекта | Префикс + категория (в порядке типовой выгрузки) |
|
||||
|-------------|--------------------------------------------------|
|
||||
| `Catalog` | `CatalogObject`/Object, `CatalogRef`/Ref, `CatalogSelection`/Selection, `CatalogList`/List, `CatalogManager`/Manager |
|
||||
| `Document` | `DocumentObject`/Object, `DocumentRef`/Ref, `DocumentSelection`/Selection, `DocumentList`/List, `DocumentManager`/Manager |
|
||||
| `Enum` | `EnumRef`/Ref, `EnumManager`/Manager, `EnumList`/List |
|
||||
| `Constant` | `ConstantManager`/Manager, `ConstantValueManager`/ValueManager, `ConstantValueKey`/ValueKey |
|
||||
| `Report` | `ReportObject`/Object, `ReportManager`/Manager |
|
||||
| `DataProcessor` | `DataProcessorObject`/Object, `DataProcessorManager`/Manager |
|
||||
| `ExchangePlan` | `ExchangePlanObject`/Object, `ExchangePlanRef`/Ref, `ExchangePlanSelection`/Selection, `ExchangePlanList`/List, `ExchangePlanManager`/Manager |
|
||||
| `Task` | `TaskObject`/Object, `TaskRef`/Ref, `TaskSelection`/Selection, `TaskList`/List, `TaskManager`/Manager |
|
||||
| `BusinessProcess` | `BusinessProcessObject`/Object, `BusinessProcessRef`/Ref, `BusinessProcessSelection`/Selection, `BusinessProcessList`/List, `BusinessProcessManager`/Manager, `BusinessProcessRoutePointRef`/RoutePointRef |
|
||||
| `ChartOfCharacteristicTypes` | `ChartOfCharacteristicTypesObject`/Object, `…Ref`/Ref, `…Selection`/Selection, `…List`/List, `Characteristic`/Characteristic, `…Manager`/Manager |
|
||||
| `ChartOfAccounts` | `ChartOfAccountsObject`/Object, `…Ref`/Ref, `…Selection`/Selection, `…List`/List, `…Manager`/Manager, `…ExtDimensionTypes`/ExtDimensionTypes, `…ExtDimensionTypesRow`/ExtDimensionTypesRow |
|
||||
| `ChartOfCalculationTypes` | `ChartOfCalculationTypesObject`/Object, `…Ref`/Ref, `…Selection`/Selection, `…List`/List, `…Manager`/Manager, `DisplacingCalculationTypes`/DisplacingCalculationTypes, `DisplacingCalculationTypesRow`/DisplacingCalculationTypesRow, `BaseCalculationTypes`/BaseCalculationTypes, `BaseCalculationTypesRow`/BaseCalculationTypesRow, `LeadingCalculationTypes`/LeadingCalculationTypes, `LeadingCalculationTypesRow`/LeadingCalculationTypesRow |
|
||||
| `InformationRegister` | `InformationRegisterRecord`/Record, `…Manager`/Manager, `…Selection`/Selection, `…List`/List, `…RecordSet`/RecordSet, `…RecordKey`/RecordKey, `…RecordManager`/RecordManager |
|
||||
| `AccumulationRegister` | `AccumulationRegisterRecord`/Record, `…Manager`/Manager, `…Selection`/Selection, `…List`/List, `…RecordSet`/RecordSet, `…RecordKey`/RecordKey |
|
||||
| `AccountingRegister` | `AccountingRegisterRecord`/Record, `AccountingRegisterExtDimensions`/ExtDimensions, `…Manager`/Manager, `…Selection`/Selection, `…List`/List, `…RecordSet`/RecordSet, `…RecordKey`/RecordKey |
|
||||
| `CalculationRegister` | `CalculationRegisterRecord`/Record, `…Manager`/Manager, `…Selection`/Selection, `…List`/List, `…RecordSet`/RecordSet, `…RecordKey`/RecordKey, `RecalculationsManager`/Recalcs |
|
||||
| `DocumentJournal` | `DocumentJournalSelection`/Selection, `DocumentJournalList`/List, `DocumentJournalManager`/Manager |
|
||||
| `Sequence` | `SequenceRecord`/Record, `SequenceManager`/Manager, `SequenceRecordSet`/RecordSet |
|
||||
| `FilterCriterion` | `FilterCriterionManager`/Manager, `FilterCriterionList`/List |
|
||||
| `SettingsStorage` | `SettingsStorageManager`/Manager |
|
||||
| `IntegrationService` | `IntegrationServiceManager`/Manager |
|
||||
| `WSReference` | `WSReferenceManager`/Manager |
|
||||
| `DefinedType` | `DefinedType`/DefinedType |
|
||||
|
||||
Табличные части объектов несут собственную пару в своём `<InternalInfo>`: `<Тип>TabularSection.<Объект>.<ТЧ>`/TabularSection и `<Тип>TabularSectionRow.<Объект>.<ТЧ>`/TabularSectionRow.
|
||||
|
||||
Без `<InternalInfo>`/`GeneratedType` идут `CommonModule`, `ScheduledJob`, `EventSubscription` и прочие объекты без порождаемых типов.
|
||||
|
||||
Порядок в таблице — как в типовых выгрузках; на загрузку он не влияет (проверено вставкой `Characteristic` в середину набора заимствованного плана видов характеристик), значим именно состав.
|
||||
|
||||
---
|
||||
|
||||
## 3. ConfigDumpInfo.xml — служебный файл выгрузки
|
||||
@@ -962,22 +1000,31 @@ ChildObjects содержат `IntegrationServiceChannel` (каналы) — inl
|
||||
|
||||
---
|
||||
|
||||
## 7. Различия версий формата 2.17 → 2.18 → 2.19 → 2.20
|
||||
## 7. Различия версий формата 2.17 → 2.18 → 2.19 → 2.20 → 2.21
|
||||
|
||||
### 7.1. Лестница версий
|
||||
|
||||
Версию формата задаёт **платформа, которой выгружают**; от режима совместимости конфигурации она не зависит. Промежуточных версий не пропускается:
|
||||
Версию формата задаёт **платформа, которой выгружают**; от режима совместимости конфигурации она не зависит. Версия меняется **каждым релизом платформы**, промежуточных не пропускается:
|
||||
|
||||
| Платформа | Версия формата |
|
||||
|-----------|----------------|
|
||||
| 8.3.20–8.3.24 | `2.17` |
|
||||
| 8.3.25 | `2.18` |
|
||||
| 8.3.26 | `2.19` |
|
||||
| 8.3.27 | `2.20` |
|
||||
| Платформа | Версия формата | Замерено |
|
||||
|-----------|----------------|----------|
|
||||
| 8.3.20 | `2.13` | да |
|
||||
| 8.3.21 | `2.14` | нет |
|
||||
| 8.3.22 | `2.15` | нет |
|
||||
| 8.3.23 | `2.16` | да |
|
||||
| 8.3.24 | `2.17` | да |
|
||||
| 8.3.25 | `2.18` | да |
|
||||
| 8.3.26 | `2.19` | да |
|
||||
| 8.3.27 | `2.20` | да |
|
||||
| 8.5.1 | `2.21` | да |
|
||||
|
||||
«Замерено» = создана пустая ИБ этой платформой и выгружена ею же; результат прочитан из `ConfigDumpInfo.xml` и корня `Configuration.xml`. Строки «нет» — интерполяция по шагу лестницы: платформ 8.3.21 и 8.3.22 в наличии не было. Замеренные крайние точки жёстко ограничивают этот пробел: 2.13 на 8.3.20 и 2.16 на 8.3.23 — ровно +3 версии на 3 релиза.
|
||||
|
||||
**Проверенный диапазон навыков — `2.17`–`2.21`.** Ниже 2.17 навыки работать не запрещают, но и не обещают: `*-init` выпустят скаффолд с предупреждением, валидаторы отметят версию как непроверенную. Дельты форматов 2.13–2.16 не изучались.
|
||||
|
||||
```xml
|
||||
<MetaDataObject ... version="2.17">
|
||||
<MetaDataObject ... version="2.20">
|
||||
<MetaDataObject ... version="2.21">
|
||||
```
|
||||
|
||||
### 7.2. Что появилось в каждой версии
|
||||
@@ -987,9 +1034,21 @@ ChildObjects содержат `IntegrationServiceChannel` (каналы) — inl
|
||||
| `2.18` | `TypeReductionMode` у стандартных реквизитов и измерений регистров сведений; `TextToSpeech` в `UsedMobileApplicationFunctionalities`; три редких свойства форм (`ViewModeApplicationOnSetReportResult`, `ReportResultViewMode`, `UseForFoldersAndItems`) |
|
||||
| `2.19` | Новых тегов нет. Роли перестали писать право, совпадающее с `setForNewObjects` |
|
||||
| `2.20` | `LineNumberLength` у табличных частей |
|
||||
| `2.21` | Пространство имён `xmlns:pal` в шапке; `Color` у значений перечислений; `AuxiliaryVariantForm`; `UseInInterfaceCompatibilityMode`; блок свойств интерфейса 8.5 в `Configuration.xml` (`MainClientApplicationWindowInterfaceVariant`, `ClientApplicationTheme`, `ClientApplicationWindowsOpenVariant`, `Version85InterfaceMigrationMode`, `Caption`/`ShortCaption`, восемь `Auxiliary*Form`) |
|
||||
|
||||
Ниже `2.17` дельта снята по пустой конфигурации в двух точках, 2.13 (8.3.20) и 2.16 (8.3.23):
|
||||
|
||||
| Переход | Что появилось в `Configuration.xml` |
|
||||
|---|---|
|
||||
| `2.13` → `2.16` | `DatabaseTablespacesUseMode`, `DefaultReportAppearanceTemplate` — на какой из ступеней 2.14–2.16, не установлено |
|
||||
| `2.16` → `2.17` | `AllowedIncomingShareRequestTypes` |
|
||||
|
||||
Это свойства корня конфигурации, а не объектов метаданных, поэтому в реестр «тег → минимальная версия» (`meta-validate`, проверка 18) они не идут: он про объекты. Внутри проверенного диапазона `AllowedIncomingShareRequestTypes` существует всегда, так что гейт по версии для него не нужен.
|
||||
|
||||
Три свойства форм — не новые возможности, а изменение **дефолта эмиссии**: платформа 8.3.24 их принимает и сохраняет при роундтрипе, просто не пишет сама. `TypeReductionMode`, наоборот, свойство новое — 8.3.24 молча отбраковывает его при загрузке («Свойство не входит в состав объекта метаданных», при этом exit 0).
|
||||
|
||||
`TextToSpeech` — третья, самая жёсткая категория: на 8.3.24 его присутствие роняет загрузку («Ошибка XDTO в файле - Configuration.xml, при чтении свойства»), а не отбраковывается молча. Это последняя, 38-я запись `UsedMobileApplicationFunctionalities` (в `2.17` их 37); значение значимое — `true` переживает роундтрип. Пропуск тега на `2.18`+ ошибкой не является, но платформа допишет его со значением `false` и роундтрип разойдётся. Замерено выгрузками пустой ИБ шести платформ и роликами загрузки на 8.3.24/8.3.25.
|
||||
|
||||
### 7.3. Configuration.xml — Properties
|
||||
|
||||
Набор свойств Properties **идентичен** во всех версиях. `ConfigurationExtensionCompatibilityMode` платформа при выгрузке подставляет **свой собственный** (8.3.25 пишет `Version8_3_25`, 8.3.27 — `Version8_3_27`) даже если конфигурация не менялась, — при диффе двух дампов это выглядит как правка конфигурации, но ею не является.
|
||||
|
||||
+36
-7
@@ -182,7 +182,7 @@ CommonForms регистрируются в `Configuration.xml`:
|
||||
</Form>
|
||||
```
|
||||
|
||||
Все 17 namespace-деклараций **идентичны** во всех формах конфигурации. Атрибут `version` одинаков во всей выгрузке и определяется платформой (`2.17` для 8.3.20–8.3.24, далее `2.18` / `2.19` / `2.20`); примеры ниже приведены для `2.17`.
|
||||
Все 17 namespace-деклараций **идентичны** во всех формах конфигурации. Атрибут `version` одинаков во всей выгрузке и определяется платформой выгрузки — по одной версии на релиз (см. [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий)); примеры ниже приведены для `2.17`.
|
||||
|
||||
### Назначение namespace-префиксов
|
||||
|
||||
@@ -212,22 +212,51 @@ CommonForms регистрируются в `Configuration.xml`:
|
||||
|
||||
```
|
||||
<Form>
|
||||
┌─ Свойства формы (необязательные, в произвольном порядке)
|
||||
┌─ Свойства формы (необязательные) — часть до <CommandSet>, часть после
|
||||
├─ <CommandSet> — исключённые стандартные команды
|
||||
├─ …продолжение свойств формы (AutoTime, UsePostingMode, RepostOnWrite, ReportResult…)
|
||||
├─ <AutoCommandBar> — главная командная панель (обязательный, id="-1")
|
||||
├─ <Events> — обработчики событий формы
|
||||
├─ <ChildItems> — дерево UI-элементов
|
||||
├─ <Attributes> — реквизиты формы
|
||||
├─ <Commands> — пользовательские команды
|
||||
├─ <Parameters> — параметры открытия формы
|
||||
└─ <Commands> — пользовательские команды
|
||||
└─ <CommandInterface> — командный интерфейс формы
|
||||
</Form>
|
||||
```
|
||||
|
||||
Порядок **строгий**, а не произвольный: на 30223 формах корпуса (ERP, УТ, УНФ 8.5, БП, общие
|
||||
формы) не встретилось ни одной пары элементов, идущей в разном порядке в разных формах.
|
||||
|
||||
Важно: `<CommandSet>` стоит **в середине блока свойств**, а не перед ним — свойства есть и до, и
|
||||
после него. У всех 794 форм документов ERP с `CommandSet` он предшествует `AutoCommandBar`, а
|
||||
`AutoTime`/`UsePostingMode`/`RepostOnWrite` идут **после** `CommandSet`.
|
||||
|
||||
Надёжная граница между свойствами и структурными секциями — **`<AutoCommandBar>`**: он присутствует
|
||||
в 100% форм корпуса, и ни одно свойство никогда не стоит после него. Код, отделяющий свойства от
|
||||
структуры, должен опираться на него, а не на «первую встреченную секцию».
|
||||
|
||||
Полный наблюдаемый порядок:
|
||||
|
||||
```
|
||||
Title · Width · Height · WindowOpeningMode · EnterKeyBehavior · AutoSaveDataInSettings ·
|
||||
SaveDataInSettings · SaveWindowSettings · SettingsStorage · AutoTitle · AutoURL · Group ·
|
||||
ChildItemsWidth · ChildrenAlign · HorizontalSpacing · Scale · VerticalSpacing · HorizontalAlign ·
|
||||
VerticalAlign · AutoFillCheck · Customizable · Enabled · CommandBarLocation · VerticalScroll ·
|
||||
ScalingMode · ConversationsRepresentation · MobileDeviceCommandBarContent · ScaleVariant ·
|
||||
CommandSet · GroupList · ShowTitle · CreateButtonsGroupPicture · CreateButtonsGroupTitle ·
|
||||
ShowCloseButton · CollapseItemsByImportanceVariant · WindowViewMode · ShowCommandBar · AutoTime ·
|
||||
ReportResult · DetailsData · ReportFormType · UseForFoldersAndItems · UsePostingMode ·
|
||||
RepostOnWrite · VariantAppearance · AutoShowState · CustomSettingsFolder · ReportResultViewMode ·
|
||||
ViewModeApplicationOnSetReportResult · AutoCommandBar · Events · ChildItems · Attributes ·
|
||||
Commands · Parameters · CommandInterface
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Свойства формы
|
||||
|
||||
Прямые дочерние элементы `<Form>` (все необязательные, указываются до `<CommandSet>`/`<AutoCommandBar>`):
|
||||
Прямые дочерние элементы `<Form>` — все необязательные, порядок между ними фиксирован (см. §2):
|
||||
|
||||
### Общие свойства (все типы форм)
|
||||
|
||||
@@ -258,8 +287,8 @@ CommonForms регистрируются в `Configuration.xml`:
|
||||
|
||||
| Элемент | Значения | Описание |
|
||||
|---------|----------|----------|
|
||||
| `<AutoTime>` | `CurrentOrLast`, `Current`, `Last` | Управление временем документа |
|
||||
| `<UsePostingMode>` | `Auto`, `Postings`, `Movements` | Режим проведения |
|
||||
| `<AutoTime>` | `CurrentOrLast`, `DontUse`, `Current`, `Last` | Управление временем документа. В корпусе встречаются только `CurrentOrLast` и `DontUse` |
|
||||
| `<UsePostingMode>` | `Auto`, `Regular` | Режим проведения |
|
||||
| `<RepostOnWrite>` | `true`/`false` | Перепроведение при записи |
|
||||
|
||||
### Свойства справочников (Catalogs, ChartsOfAccounts)
|
||||
@@ -274,7 +303,7 @@ CommonForms регистрируются в `Configuration.xml`:
|
||||
|---------|----------|----------|
|
||||
| `<ReportResult>` | string | Имя реквизита результата (`Результат`) |
|
||||
| `<DetailsData>` | string | Имя реквизита расшифровки (`ДанныеРасшифровки`) |
|
||||
| `<ReportFormType>` | `Main`, `Settings`, `Choice` | Тип формы отчёта |
|
||||
| `<ReportFormType>` | `Main`, `Settings`, `Variant` | Тип формы отчёта |
|
||||
| `<AutoShowState>` | `Auto`, `Show`, `Hide` | Автоотображение состояния |
|
||||
| `<ReportResultViewMode>` | `Auto`, `Table`, `Spreadsheet` | Режим отображения результата |
|
||||
| `<ViewModeApplicationOnSetReportResult>` | `Auto`, `Always`, `Never` | Применение режима |
|
||||
|
||||
+51
-10
@@ -1,6 +1,6 @@
|
||||
# Спецификация формата ролей 1С:Предприятия 8.3
|
||||
|
||||
Полное описание XML-формата ролей в выгрузке конфигурации. Версии формата: 2.17 (платформа 8.3.20–8.3.24), 2.18 (8.3.25), 2.19 (8.3.26), 2.20 (8.3.27+). Структура идентична; отличается атрибут `version`, а с 2.19 — ещё и набор выгружаемых прав (см. «Версии формата»).
|
||||
Полное описание XML-формата ролей в выгрузке конфигурации. Проверенный диапазон версий формата: 2.17 (платформа 8.3.24) … 2.21 (8.5); полная лестница — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий). Структура идентична; отличается атрибут `version`, а с 2.19 — ещё и набор выгружаемых прав (см. «Версии формата»).
|
||||
|
||||
## Файловая структура
|
||||
|
||||
@@ -542,18 +542,44 @@ Subsystem.Администрирование.Subsystem.Пользователи
|
||||
| `SessionParameter` | Get, Set |
|
||||
| `CommonAttribute` | View, Edit |
|
||||
|
||||
#### ExternalDataSource
|
||||
|
||||
Источник ошибки в прежних редакциях спецификации: внешние источники данных права **имеют** —
|
||||
узел «Внешние источники данных» есть в дереве редактора ролей. Значения ниже сняты с выгрузки
|
||||
роли со всеми проставленными правами (8.3.25).
|
||||
|
||||
| Объект | Права |
|
||||
|--------|-------|
|
||||
| `ExternalDataSource.И` | Use, Administration, StandardAuthenticationChange, SessionStandardAuthenticationChange, SessionOSAuthenticationChange |
|
||||
| `ExternalDataSource.И.Table.Т` | Read, Insert, Update, Delete, View, Edit, InputByString, InteractiveInsert, InteractiveDelete |
|
||||
| `ExternalDataSource.И.Table.Т.Field.П` | View, Edit |
|
||||
| `ExternalDataSource.И.Cube.К` | Read, View |
|
||||
| `ExternalDataSource.И.Cube.К.Dimension.И` | View |
|
||||
| `ExternalDataSource.И.Cube.К.Resource.Р` | View |
|
||||
| `ExternalDataSource.И.Cube.К.DimensionTable.Т` | Read, View |
|
||||
| `ExternalDataSource.И.Cube.К.DimensionTable.Т.Field.П` | View, Edit |
|
||||
| `ExternalDataSource.И.Function.Ф` | Use, View |
|
||||
| `…Table.Т.Command.К`, `…Cube.К.Command.К`, `…DimensionTable.Т.Command.К` | View |
|
||||
|
||||
#### Типы объектов БЕЗ прав в ролях
|
||||
|
||||
Следующие типы не фигурируют в Rights.xml (права не применимы или управляются иначе):
|
||||
Следующие типы не фигурируют в Rights.xml — в дереве редактора ролей их нет:
|
||||
|
||||
- `Enum` (перечисления)
|
||||
- `FunctionalOption`
|
||||
- `DefinedType`
|
||||
- `Enum` (перечисления) — блок прав на перечисление приводит к зависанию загрузки
|
||||
конфигурации в информационную базу: конфигуратор не завершается и не выдаёт сообщений
|
||||
- `CommonModule`
|
||||
- `DefinedType`
|
||||
- `CommonPicture`
|
||||
- `CommonTemplate`
|
||||
- `Language`
|
||||
- `FunctionalOption`, `FunctionalOptionsParameter`
|
||||
- `EventSubscription`
|
||||
- `ScheduledJob`
|
||||
- `StyleItem`, `Style`
|
||||
- `SettingsStorage`
|
||||
- `ExternalDataSource`
|
||||
- `XDTOPackage`
|
||||
- `WSReference`
|
||||
- `DocumentNumerator`
|
||||
|
||||
---
|
||||
|
||||
@@ -646,8 +672,21 @@ Subsystem.Администрирование.Subsystem.Пользователи
|
||||
| `TabularSection.*.Attribute` | (все с TabularSection) | View, Edit |
|
||||
| `Dimension` | InformationRegister, AccumulationRegister, AccountingRegister | View, Edit |
|
||||
| `Resource` | InformationRegister, AccumulationRegister, AccountingRegister | View, Edit |
|
||||
| `Command` | Catalog, Document, DataProcessor, Report, *Register, DocumentJournal, ExchangePlan, BusinessProcess, Task | View |
|
||||
| `AddressingAttribute` | Task | View, Edit |
|
||||
| `Command` | Catalog, Document, DataProcessor, Report, *Register, DocumentJournal, ExchangePlan, BusinessProcess, Task, ExternalDataSource (Table/Cube/DimensionTable) | View |
|
||||
| `AddressingAttribute` | Task, BusinessProcess | View, Edit |
|
||||
| `StandardTabularSection` | Catalog, Document, ChartOf* | View, Edit |
|
||||
| `AccountingFlag`, `ExtDimensionAccountingFlag` | ChartOfAccounts | View, Edit |
|
||||
| `Subsystem` | Subsystem (вложенная) | View |
|
||||
| `Operation` | WebService | Use |
|
||||
| `URLTemplate.*.Method` | HTTPService | Use |
|
||||
| `IntegrationServiceChannel` | IntegrationService | Use |
|
||||
| `Recalculation` | CalculationRegister | Read, Update |
|
||||
| `Column` (графа журнала) | — | прав не имеет |
|
||||
| `Table`, `Cube`, `Function`, `Field`, `DimensionTable` | только ExternalDataSource | см. раздел ExternalDataSource |
|
||||
|
||||
Виды, встречающиеся в выгрузках типовых конфигураций, сверены с корпусом
|
||||
(`acc`, `erp`, `ut`, `unf` — ~2750 ролей); внешние источники данных — с выгрузкой роли,
|
||||
где права проставлены по всему дереву.
|
||||
|
||||
---
|
||||
|
||||
@@ -838,11 +877,13 @@ RLS применяется к правам `Read`, `Update`, `Insert`, `Delete`
|
||||
|
||||
| Платформа | version (метаданные) | version (Rights.xml) | Изменения |
|
||||
|-----------|:--------------------:|:--------------------:|-----------|
|
||||
| 8.3.20 | 2.17 | 2.17 | Базовая |
|
||||
| 8.3.24 | 2.17 | 2.17 | Без изменений |
|
||||
| 8.3.24 | 2.17 | 2.17 | Базовая (нижняя граница проверенного диапазона) |
|
||||
| 8.3.25 | 2.18 | 2.18 | Только номер версии |
|
||||
| 8.3.26 | 2.19 | 2.19 | Право, совпадающее с `setForNewObjects`, больше не пишется (см. ниже) |
|
||||
| 8.3.27 | 2.20 | 2.20 | Только номер версии |
|
||||
| 8.5.1 | 2.21 | 2.21 | Только номер версии |
|
||||
|
||||
Полная лестница платформа → версия формата, включая ступени ниже проверенного диапазона, — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
Начиная с `2.19` платформа **опускает право, значение которого совпадает с `<setForNewObjects>`** в шапке `Rights.xml`: роль с `setForNewObjects=true` теряет из выгрузки права со значением `true`, роль с `false` — со значением `false`. Возможности не удалялись: право отсутствует в выгрузке, но действует по умолчанию роли. Структура файла при этом не менялась.
|
||||
|
||||
|
||||
@@ -169,5 +169,5 @@
|
||||
| Meta DSL | JSON-формат для создания/редактирования объектов | [meta-dsl-spec.md](meta-dsl-spec.md) |
|
||||
| Form DSL | JSON-формат для компиляции форм | [form-dsl-spec.md](form-dsl-spec.md) |
|
||||
| SKD DSL | JSON-формат для компиляции СКД | [skd-dsl-spec.md](skd-dsl-spec.md) |
|
||||
| MXL DSL | JSON-формат для компиляции табличных документов | [mxl-dsl-spec.md](mxl-dsl-spec.md) |
|
||||
| MXL DSL | JSON-формат для компиляции табличных документов, включая оформление и полный перечень свойств стиля | [mxl-dsl-spec.md](mxl-dsl-spec.md) |
|
||||
| Role DSL | JSON-формат для компиляции ролей | [role-dsl-spec.md](role-dsl-spec.md) |
|
||||
|
||||
+434
-21
@@ -63,6 +63,18 @@
|
||||
</languageSettings>
|
||||
```
|
||||
|
||||
Языков бывает несколько, и объявленный набор НЕ выводится из того, на каких языках лежит
|
||||
текст ячеек: на корпусе ERP объявлен один `ru` у 10 753 макетов, при том что текст в них
|
||||
лежит и под `ru`, и под `en`. Наборы: `[ru]` — 10 753, `[ru,en]` — 148, `[en,ru]` — 20.
|
||||
|
||||
`id` и `code` всегда со значением; `description` бывает пустым и тогда пишется
|
||||
самозакрывающимся тегом `<description/>` (77 записей из 11 094). Из `id` эти поля не
|
||||
выводятся: у `en` описание бывает «Английский», пустым и «English».
|
||||
|
||||
`currentLanguage` иногда отсутствует вовсе (3 макета), иногда указывает на необъявленный
|
||||
язык (8 макетов с `en` при объявленном одном `ru`). `defaultLanguage` во всех четырёх
|
||||
русских типовых равен `ru`, но это свойство конфигурации, а не формата.
|
||||
|
||||
## Колонки
|
||||
|
||||
### Основной набор
|
||||
@@ -80,7 +92,16 @@
|
||||
</columns>
|
||||
```
|
||||
|
||||
Перечисляются только колонки с нестандартной шириной. Формат колонки определяет ширину через свойство `<width>` в палитре форматов.
|
||||
Перечисляются колонки, у которых есть собственный формат. Обычно это ширина (`<width>` в
|
||||
палитре форматов), но формат колонки несёт и оформление: шрифт (3 006 форматов на корпусе),
|
||||
выравнивания, рамки, `hidden`.
|
||||
|
||||
Два случая, которые легко упустить:
|
||||
|
||||
- `<formatIndex>0</formatIndex>` — колонка перечислена, а формата у неё **нет** (ноль здесь не
|
||||
индекс записи). Встречается у 4% макетов;
|
||||
- индекс колонки бывает **больше или равен `<size>`** — платформа описывает колонки за
|
||||
пределами объявленной ширины сетки (25% макетов).
|
||||
|
||||
### Дополнительные наборы колонок
|
||||
|
||||
@@ -106,6 +127,11 @@
|
||||
|
||||
Типичное применение: сложные печатные формы (УПД, УКД), где шапка/подвал/табличная часть имеют разную разбивку на колонки.
|
||||
|
||||
Именованная область тоже ссылается на набор — тегом `<columnsID>` внутри `<area>`, последним.
|
||||
На корпусе привязку несут 913 483 прямоугольных области, 20 063 полосы строк и 743 полосы колонок;
|
||||
висячих ссылок нет ни одной. Из накрытых строк она выводится не всегда: у областей типа `Rows`
|
||||
13 623 повторяют раскладку строк, а 8 179 её не несут при тех же строках.
|
||||
|
||||
## Строки и ячейки
|
||||
|
||||
### Строка
|
||||
@@ -123,8 +149,12 @@
|
||||
</rowsItem>
|
||||
```
|
||||
|
||||
- Строки с одинаковым содержимым объединяются через `<indexTo>`
|
||||
- `<indexTo>` объединяет подряд идущие **пустые** строки. Одинаковые НЕпустые строки платформа
|
||||
не схлопывает никогда: на корпусе ERP (10 924 макета) схлопнутых непустых нет ни одной,
|
||||
несхлопнутых одинаковых непустых — 98 153; несхлопнутых одинаковых пустых — тоже ни одной
|
||||
- `<columnsID>` привязывает строку к дополнительному набору колонок. Без него — используется основной набор
|
||||
- `<formatIndex>` строки несёт не только высоту: в корпусе там `hidden` (17 423 вхождения),
|
||||
`font` (9 216), `backColor`, выравнивания, `protection`, рамки
|
||||
|
||||
### Ячейка
|
||||
|
||||
@@ -152,6 +182,24 @@
|
||||
- Если `<i>` не указан — колонка = предыдущая + 1
|
||||
- Первая ячейка без `<i>` идёт в колонку 0
|
||||
|
||||
Платформа пишет `<i>` **только при разрыве последовательности**: у подряд идущей ячейки его нет.
|
||||
На корпусе ERP из 1 212 023 записанных номеров ни один не избыточен (счётчик начинается с −1,
|
||||
поэтому у ячейки в колонке 0 номера тоже нет).
|
||||
|
||||
У текста ячейки ТРИ состояния, а не два:
|
||||
|
||||
| запись | смысл |
|
||||
|---|---|
|
||||
| `<tl>` отсутствует | текста нет |
|
||||
| `<tl>` с элементами | текст по элементу на язык (содержимое бывает пустым) |
|
||||
| `<tl/>` | тег есть, языков в нём нет |
|
||||
|
||||
Третье — не редкость: 38 075 ячеек на 1200 макетов, встречается в 57% макетов корпуса.
|
||||
|
||||
`<f>0</f>` означает, что формата у ячейки **нет вовсе** — это не индекс записи. Так записана
|
||||
ячейка без собственного оформления: 170 710 таких ячеек против 50 635, ссылающихся на формат
|
||||
по умолчанию; `<f>0</f>` встречается в 71% макетов.
|
||||
|
||||
### Типы заполнения ячеек
|
||||
|
||||
Тип заполнения определяется свойством `fillType` в формате ячейки:
|
||||
@@ -249,6 +297,8 @@
|
||||
</namedItem>
|
||||
```
|
||||
|
||||
**Порядок элементов `namedItem` — по имени.** Платформа хранит их отсортированными, регистронезависимо: на выборке 541 макета ERP 8.3.24 с несколькими именованными элементами иного порядка не встретилось ни разу. Сортировка ординальная (латиница раньше кириллицы); случай с «ё» в выборке не встретился и не проверен.
|
||||
|
||||
### Тип Columns — вертикальная область
|
||||
|
||||
```xml
|
||||
@@ -325,12 +375,13 @@
|
||||
</line>
|
||||
```
|
||||
|
||||
| xsi:type | Значения |
|
||||
|-----------------------------------------|-------------|
|
||||
| `v8ui:SpreadsheetDocumentCellLineType` | Solid, None |
|
||||
| `v8ui:SpreadsheetDocumentDrawingLineType` | Solid, None |
|
||||
| xsi:type | Значения |
|
||||
|-------------------------------------------|----------|
|
||||
| `v8ui:SpreadsheetDocumentCellLineType` | `Solid`, `None`, `Dotted`, `ThinDashed`, `LargeDashed`, `ThickDashed`, `Double` |
|
||||
| `v8ui:SpreadsheetDocumentDrawingLineType` | те же |
|
||||
|
||||
Атрибут `width` — толщина линии (1 = тонкая, 2 = толстая).
|
||||
Атрибут `width` — толщина линии; в корпусе встречаются 0, 1, 2, 3. Атрибут `gap` на корпусе
|
||||
всегда `false`, но пишется всегда.
|
||||
|
||||
## Шрифты
|
||||
|
||||
@@ -341,10 +392,38 @@
|
||||
<font faceName="Arial" height="14" bold="true" italic="false"
|
||||
underline="false" strikeout="false" kind="Absolute" scale="100"/>
|
||||
|
||||
<!-- Ссылка на стиль -->
|
||||
<!-- Ссылка на элемент стиля конфигурации -->
|
||||
<font ref="style:TextFont" kind="StyleItem"/>
|
||||
|
||||
<!-- Ссылка на системный шрифт: префикс sys в корне НЕ объявлен, поэтому объявление
|
||||
дописывается прямо на узел — тот же приём, что у цветов из web-палитры -->
|
||||
<font xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system"
|
||||
ref="sys:ANSIVariableFont" kind="WindowsFont"/>
|
||||
```
|
||||
|
||||
`kind` выводится из префикса ссылки: `style:` → `StyleItem`, `sys:` → `WindowsFont`.
|
||||
На корпусе ERP таких шрифтов 272 в 213 макетах из 10 924 (StyleItem 209, WindowsFont 63).
|
||||
|
||||
Шрифт, на который не ссылается ни один формат, в палитру не попадает: у макета без
|
||||
оформления элемента `<font>` нет вовсе.
|
||||
|
||||
## Устройство палитр
|
||||
|
||||
Три правила, общие для всех палитр. Все проверены на корпусе ERP (10 924 макета) и на
|
||||
макетах, собранных вручную в Конфигураторе.
|
||||
|
||||
**Порядок — документный.** Записи идут в том порядке, в каком встречаются при обходе
|
||||
документа сверху вниз: сначала форматы колонок (в порядке колонок), затем форматы строк и
|
||||
ячеек. Порядок НЕ зависит от того, в какой последовательности автор оформлял макет:
|
||||
контрольный опыт с оформлением снизу вверх дал палитру в порядке строк.
|
||||
|
||||
**Формат по умолчанию — последняя запись.** На корпусе он последний в 8285 макетах из 10 863,
|
||||
первым — в 25.
|
||||
|
||||
**Палитра форматов дедуплицирована по содержимому:** двух одинаковых записей в ней нет
|
||||
(10 804 макета из 10 924). Остальные 120 — накопленный мусор редактирования, там доходит до
|
||||
583 записей при 130 уникальных.
|
||||
|
||||
## Форматы
|
||||
|
||||
Палитра форматов — центральный элемент. **Индексация 1-based** (индекс 0 = формат не задан).
|
||||
@@ -374,25 +453,357 @@
|
||||
|
||||
Все свойства опциональны. Формат может содержать только `<width>` (для колонки) или только `<height>` (для строки).
|
||||
|
||||
### Порядок тегов
|
||||
|
||||
Внутри `<format>` теги идут в строгой последовательности — от неё зависит побайтовое совпадение
|
||||
с выгрузкой. Порядок снят с корпуса ERP: 766 960 форматов, ни один его не нарушает.
|
||||
|
||||
```
|
||||
print · drawingBorder · drawingHaveLeftBorder · drawingHaveTopBorder ·
|
||||
drawingHaveRightBorder · drawingHaveBottomBorder · font · leftBorder · topBorder ·
|
||||
rightBorder · bottomBorder · border · height · borderColor · width ·
|
||||
autoWidthCalculation · widthWeightFactor · horizontalAlignment · verticalAlignment ·
|
||||
textColor · backColor · patternColor · pattern · textPlacement · fillType · protection ·
|
||||
hidden · textOrientation · detailsUse · bySelectedColumns · markNegatives ·
|
||||
containsValue · valueType · format · controlType · hyperLink · autoMarkIncomplete ·
|
||||
indent · autoIndent · editFormat · columnSizeChange · mask · picIndex ·
|
||||
pictureSizeMode · picHorizontalAlignment · picVerticalAlignment · textPosition
|
||||
```
|
||||
|
||||
Обрати внимание: `height` идёт РАНЬШЕ `width`, а `format` — после `valueType`.
|
||||
|
||||
### Ширина и высота
|
||||
|
||||
Единица — **1/8 символа**: `72` = стандартные 9 символов, `240` = 30, `80` = 10.
|
||||
|
||||
### Рамка
|
||||
|
||||
Четыре стороны и свёрнутый `<border>` взаимоисключающи. Если все четыре стороны одинаковы,
|
||||
платформа пишет один `<border>`; иначе — по сторонам. На корпусе: 70 265 свёрнутых форматов
|
||||
против 36 783 записанных по сторонам, и среди вторых нет ни одного с четырьмя совпадающими
|
||||
значениями; смешения `border` с посторонними тегами нет ни разу.
|
||||
|
||||
### Цвет
|
||||
|
||||
Значение цвета (`backColor`, `textColor`, `borderColor`, `patternColor`) — строка с **префиксом
|
||||
пространства имён**, а не свободный текст:
|
||||
|
||||
| Запись в XML | Смысл |
|
||||
|---|---|
|
||||
| `#RRGGBB` | RGB-hex |
|
||||
| `style:ИмяСтиля` | элемент стиля; префикс `style` объявлен в корне документа |
|
||||
| `d3p1:Имя` + `xmlns:d3p1=".../ui/colors/web"` на самом узле | цвет web-палитры |
|
||||
| `d3p1:Имя` + `xmlns:d3p1=".../ui/colors/windows"` на самом узле | системный цвет Windows |
|
||||
|
||||
Корень `<document>` объявляет только `style`, поэтому для web- и windows-палитр платформа
|
||||
дописывает объявление прямо на узел. Префикс всегда `d3p1` (1264 вхождения на корпусе без
|
||||
отклонений). В Form.xml те же цвета выглядят как `web:Имя` и `win:Имя` — там эти префиксы
|
||||
объявлены в корне.
|
||||
|
||||
### Связь формата с контекстом
|
||||
|
||||
| Контекст | Ссылка | Значимые свойства формата |
|
||||
|------------------|--------------------|--------------------------|
|
||||
| Колонка | `<formatIndex>` | `width` |
|
||||
| Строка | `<formatIndex>` | `height` |
|
||||
| Ячейка | `<f>` | Все остальные |
|
||||
| Рисунок | `<formatIndex>` | `drawingBorder` |
|
||||
| По умолчанию | `<defaultFormatIndex>` | `width` |
|
||||
Один формат обслуживает всех: колонка, строка и ячейка ссылаются в одну палитру, и набор
|
||||
свойств у них общий (`hidden` встречается у ячеек 30 175 раз, у строк 17 423, у колонок 57;
|
||||
`protection` — 84 442 / 1 492 / 1 017).
|
||||
|
||||
## Ресурсы картинок
|
||||
| Контекст | Ссылка | Свойства, специфичные для контекста |
|
||||
|------------------|------------------------|-------------------------------------|
|
||||
| Колонка | `<formatIndex>` | `width` |
|
||||
| Строка | `<formatIndex>` | `height`, `hidden` |
|
||||
| Ячейка | `<f>` | `fillType`, `containsValue` |
|
||||
| Рисунок | `<formatIndex>` | `drawingBorder`, `drawing*` |
|
||||
| По умолчанию | `<defaultFormatIndex>` | `width` |
|
||||
|
||||
Оформление, применённое к строке целиком, платформа записывает И в формат строки, И в формат
|
||||
каждой ячейки (`backColor`: 13 899 ячеек повторяют против 96). Исключение — `hidden`: оно
|
||||
остаётся только у строки (24 026 против 62 856).
|
||||
|
||||
### Ячейка-поле ввода: `containsValue` / `valueType` / `controlType`
|
||||
|
||||
Ячейка может содержать не текст, а редактируемое значение. Три тега формата описывают это
|
||||
целиком:
|
||||
|
||||
```xml
|
||||
<format>
|
||||
<containsValue>true</containsValue>
|
||||
<valueType>
|
||||
<v8:Type>xs:decimal</v8:Type>
|
||||
<v8:NumberQualifiers>
|
||||
<v8:Digits>15</v8:Digits>
|
||||
<v8:FractionDigits>3</v8:FractionDigits>
|
||||
<v8:AllowedSign>Nonnegative</v8:AllowedSign>
|
||||
</v8:NumberQualifiers>
|
||||
</valueType>
|
||||
<controlType>381ed624-9217-4e63-85db-c4c3cb87daae</controlType>
|
||||
</format>
|
||||
```
|
||||
|
||||
Свойство принадлежит ЯЧЕЙКЕ, хотя и живёт в общей палитре: из 370 197 ссылок на такие записи
|
||||
(корпус ERP, 5 809 макетов из 10 924) все до одной идут из `<f>` — ни строка, ни колонка, ни
|
||||
`<defaultFormatIndex>` на них не ссылаются ни разу.
|
||||
|
||||
| Наблюдение | Данные корпуса |
|
||||
|---|---|
|
||||
| `containsValue` всегда `true` | 63 726 форматов, других значений нет |
|
||||
| `valueType` присутствует всегда | 0 форматов без него; 12 — с ПУСТЫМ `<valueType/>` (тип не ограничен) |
|
||||
| текста у такой ячейки не бывает | 0 из 370 197 несут `<tl>` |
|
||||
| `<parameter>` и `<detailParameter>` — бывают | 3 219 и 2 278 |
|
||||
| `fillType` у формата со значением недостоверен | 94 062 записи несут `Text` при полном отсутствии текста — след прежнего состояния, как `width` |
|
||||
|
||||
**Элемент управления** задаётся GUID; имён в XML нет вовсе. В Конфигураторе это «ЭлементУправления»
|
||||
со значениями «Поле ввода» и «Поле флажка»:
|
||||
|
||||
| GUID | Элемент | Вхождений |
|
||||
|---|---|---:|
|
||||
| `381ed624-9217-4e63-85db-c4c3cb87daae` | Поле ввода | 63 629 |
|
||||
| `35af3d93-d7c7-4a2e-a8eb-bac87a1a3f26` | Поле флажка | 7 |
|
||||
|
||||
Умолчание — поле ввода, и оно же стоит у Булево: из 664 булевых форматов флажок выбран в семи.
|
||||
У 90 форматов (22 макета регламентированной отчётности, явно машинно-сгенерированных) тега
|
||||
`<controlType>` нет вовсе.
|
||||
|
||||
Флажок Конфигуратор предлагает **только для Булево и Числа**. Ограничение при этом чисто
|
||||
интерфейсное: макет с флажком у строки и у даты платформа принимает и возвращает GUID дословно
|
||||
(проверено сборкой EPF и обратной выгрузкой через базу — ни отказа, ни нормализации).
|
||||
|
||||
**Структура `<valueType>`** — то же описание типов, что в метаданных: сначала все `<v8:Type>` и
|
||||
`<v8:TypeSet>` в порядке источника, затем блоки квалификаторов в порядке Number → String → Date.
|
||||
Голая категория без имени объекта (`CatalogRef`, `AnyRef`) — это `<v8:TypeSet>`, а не `<v8:Type>`.
|
||||
|
||||
Ссылочные типы несут **локальное объявление пространства имён на каждом узле**:
|
||||
|
||||
```xml
|
||||
<v8:Type xmlns:d4p1="http://v8.1c.ru/8.1/data/enterprise/current-config">d4p1:CatalogRef.Валюты</v8:Type>
|
||||
```
|
||||
|
||||
Префикс всегда `d4p1` (15 вхождений на корпусе, отклонений нет) — независимо от того, есть ли
|
||||
в макете цвета web-палитры, которым платформа даёт `d3p1`. Корень `<document>` пространство
|
||||
current-config не объявляет, поэтому вынести объявление наверх нельзя.
|
||||
|
||||
**Продолжение конструкции — на уровне САМОЙ ячейки**, а не палитры:
|
||||
|
||||
```xml
|
||||
<c>
|
||||
<f>34</f>
|
||||
<v xsi:type="xs:decimal">0</v>
|
||||
<control xsi:type="xs:base64Binary">77u/MiwxLDM4MWVkNjI0…</control>
|
||||
</c>
|
||||
```
|
||||
|
||||
| Тег ячейки | Смысл | Вхождений | Макетов |
|
||||
|---|---|---:|---:|
|
||||
| `<v>` | сохранённое значение ячейки; `xsi:type` соответствует объявленному типу (decimal 153 566, string 61 203, dateTime 3 333, boolean 1 012) | 219 114 | 4 578 |
|
||||
| `<control>` | сериализованные настройки элемента управления (base64; внутри — тот же GUID и структура вида маски/списка выбора) | 26 370 | 1 246 |
|
||||
|
||||
Оба тега встречаются ИСКЛЮЧИТЕЛЬНО в ячейках, чей формат несёт `containsValue` — ни одного
|
||||
вхождения в обычной ячейке. То есть это вторая половина той же конструкции, а не отдельное
|
||||
свойство. Обрати внимание на совпадение имён: `<control>` у ячейки и `<controlType>` в формате —
|
||||
разные вещи.
|
||||
|
||||
**Ловушка: `width` в формате ЯЧЕЙКИ не описывает ячейку.** Ссылка ячейки на запись палитры не
|
||||
обновляется при изменении ширины колонки, поэтому там остаётся ширина от прежнего состояния
|
||||
документа. Проверено на контролируемом стенде: в макете, где ширины колонок поменяли на
|
||||
обратные, ячейки продолжают ссылаться на записи со старыми ширинами. Из итогового XML это
|
||||
значение не выводится — воспроизводить его не нужно.
|
||||
|
||||
## Колонтитулы и параметры печати
|
||||
|
||||
```xml
|
||||
<leftHeader>
|
||||
<f>2</f> <!-- формат: шрифт, выравнивание, признак вывода, стартовая страница -->
|
||||
<tl><v8:item><v8:lang>ru</v8:lang><v8:content>Слева</v8:content></v8:item></tl>
|
||||
</leftHeader>
|
||||
…
|
||||
<printSettings>
|
||||
<topMargin>1000</topMargin> … <firstPageNumber>15</firstPageNumber>
|
||||
</printSettings>
|
||||
```
|
||||
|
||||
Шесть элементов идут после строк и перед `<templateMode>`: `leftHeader`, `centerHeader`,
|
||||
`rightHeader`, `leftFooter`, `centerFooter`, `rightFooter`. Пишутся не комплектом: `leftHeader`
|
||||
есть у 1 013 макетов, остальные — у 677–790.
|
||||
|
||||
| Наблюдение | Данные корпуса |
|
||||
|---|---|
|
||||
| текст слота | `<tl>` — обычная строка, `<tfl>` — форматированная (разметка внутри содержимого: `<b>…</>`, `<fontsize 12>`, `<colorstyle -16>`) |
|
||||
| формат слота | `<height>` = признак вывода (`1` / `-1`), `<width>` = страница, с которой печатать, плюс шрифт и `verticalAlignment` |
|
||||
| формат общий на колонтитул | у 674 макетов из 677; разный у трёх |
|
||||
| формат делится с другими владельцами | 637 против 153 «только колонтитулы» — палитра дедуплицирована, поэтому `<width>` в чужой записи означает ширину колонки, а не страницу |
|
||||
|
||||
**Ловушка:** перенос строки внутри текста колонтитула платформа хранит голым LF, тогда как весь
|
||||
остальной файл идёт CRLF. Это единственное место документа с другим переводом строки.
|
||||
|
||||
`printSettings` (2 350 макетов) — плоский набор в фиксированном порядке: `pageOrientation` ·
|
||||
`scale` · `collate` · `copies` · `perPage` · `topMargin` · `leftMargin` · `bottomMargin` ·
|
||||
`rightMargin` · `headerSize` · `footerSize` · `fitToPage` · `blackAndWhite` · `printerName` ·
|
||||
`paper` · `paperSource` · `pageWidth` · `pageHeight` · `duplexType` · `pagePlacementAlternation` ·
|
||||
`firstPageNumber`. Номер первой страницы принадлежит именно параметрам печати, а не колонтитулу.
|
||||
|
||||
## Группы строк и колонок
|
||||
|
||||
```xml
|
||||
<vgLevels>2</vgLevels>
|
||||
<vgRows>8</vgRows>
|
||||
<vg>
|
||||
<b>1</b> <!-- начало диапазона, 0-based -->
|
||||
<e>2</e> <!-- конец; нет — группировка из одной строки -->
|
||||
<t><v8:item><v8:lang>ru</v8:lang><v8:content>Итоговая</v8:content></v8:item></t>
|
||||
<o>false</o> <!-- свёрнута; развёрнутая тег не пишет -->
|
||||
<g>Begin</g> <!-- расположение заголовка: Begin | End; нет — Авто -->
|
||||
</vg>
|
||||
<hg>…</hg> <!-- то же для колонок -->
|
||||
```
|
||||
|
||||
Строки группируются часто (1 797 макетов, 170 967 групп), колонки — почти никогда
|
||||
(1 макет, 1 группировка). Порядок документных тегов: `height` → `vgLevels` → `vgRows` → `vg` →
|
||||
`hg`; `vgLevels` пишется только при наличии группировок.
|
||||
|
||||
| Наблюдение | Данные корпуса |
|
||||
|---|---|
|
||||
| диапазоны либо вложены, либо не пересекаются | 40 620 886 пар непересекающихся, 599 958 вложенных, **0 частичных** |
|
||||
| порядок записи — родитель раньше вложенных, по возрастанию начала | совпало у 1 798 макетов из 1 798 |
|
||||
| `<vgLevels>` = глубина вложенности | совпало у 1 797 из 1 797 |
|
||||
| `<o>` | только `false` (6 130) — развёрнутая группировка тег не пишет |
|
||||
| `<g>` | в корпусе только `Begin` (6); значение `End` даёт Конфигуратор при «Расположение заголовка: Конец» |
|
||||
|
||||
Имя (`<t>`) — мультиязычная строка, и в 143 455 случаях это автоимя `R<номер строки + 1>`.
|
||||
Вывести его нельзя: 16 666 имён «протухли» (`R91` у строки 103 — строки вставляли после
|
||||
создания группировки), а 10 287 группировок имени не имеют вовсе. Языковые наборы —
|
||||
`ru`+`en` (93 250) либо псевдоязык `#` (67 431), никогда вместе.
|
||||
|
||||
## Примечание к ячейке
|
||||
|
||||
```xml
|
||||
<note>
|
||||
<drawingType>Comment</drawingType>
|
||||
<id>0</id>
|
||||
<formatIndex>1</formatIndex>
|
||||
<text><v8:item><v8:lang>ru</v8:lang><v8:content>тест</v8:content></v8:item></text>
|
||||
<beginRow>1</beginRow> <beginRowOffset>-21</beginRowOffset>
|
||||
<endRow>0</endRow> <endRowOffset>51</endRowOffset>
|
||||
<beginColumn>1</beginColumn> <beginColumnOffset>21</beginColumnOffset>
|
||||
<endColumn>0</endColumn> <endColumnOffset>408</endColumnOffset>
|
||||
<autoSize>true</autoSize>
|
||||
<pictureSize>Stretch</pictureSize>
|
||||
</note>
|
||||
```
|
||||
|
||||
Конструкция редкая (188 макетов, 1 087 примечаний), но структура жёсткая: все четырнадцать тегов
|
||||
присутствуют у всех 1 087, порядок один и тот же, опциональных нет. Информации при этом меньше,
|
||||
чем тегов:
|
||||
|
||||
| Тег | Наблюдение |
|
||||
|---|---|
|
||||
| `drawingType`, `pictureSize`, `id` | константы: `Comment`, `Stretch`, `0` |
|
||||
| `beginRow`, `beginColumn` | всегда `1`/`1` — 1085 и 1086 из 1087; три исключения в одном макете |
|
||||
| `endRow`, `endColumn` | **координаты самой ячейки** — 1087 из 1087 |
|
||||
| четыре смещения | авторские: сдвиг окошка меняет `begin*Offset`, растяжение — `end*Offset` |
|
||||
| `autoSize` | `true` у 1036 из 1087 |
|
||||
|
||||
`autoSize` описывает не наличие геометрии, а пересчёт размера: при `true` окошко всё равно несёт
|
||||
координаты, и они осмысленны — 306 различных пар `(endRowOffset, endColumnOffset)` против
|
||||
12 различных пар положения.
|
||||
|
||||
Формат примечания — обычная запись палитры, на корпусе их всего 7 различных: 926 — стиль
|
||||
подсказки (`verticalAlignment: Top` + `style:ToolTipTextColor` + `style:ToolTipBackColor`),
|
||||
105 — он же с заливкой `#FFFAD9`.
|
||||
|
||||
## Палитра картинок
|
||||
|
||||
Идёт последним блоком документа. Внутренний `<index>` 0-based, а ссылки на него — 1-based:
|
||||
`<pictureIndex>` у рисунка, `picIndex` в записи формата у картинки в ячейке.
|
||||
|
||||
```xml
|
||||
<picture>
|
||||
<index>0</index>
|
||||
<picture ref="v8ui:Штрихкод"/> <!-- ссылка на предопределённую картинку -->
|
||||
<picture ref="v8ui:Штрихкод"/> <!-- ссылка на библиотеку картинок платформы -->
|
||||
</picture>
|
||||
<picture>
|
||||
<index>1</index>
|
||||
<picture t="false">iVBORw0KGgoAAAANSUhEUgAAAG4AAAA2CAIAAACp5Ds2AAAAAXNSR0IArs4c6QAA
|
||||
AARnQUlBAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAA</picture>
|
||||
</picture>
|
||||
```
|
||||
|
||||
Данные base64 платформа переносит по строкам тем же переводом, что и весь файл (CRLF), —
|
||||
в отличие от блоба настроек элемента управления, где перенос голый LF.
|
||||
|
||||
Атрибуты внутреннего `<picture>` по корпусу ERP (10 130 записей):
|
||||
|
||||
| Вид | Сколько | Смысл |
|
||||
|-----|--------:|-------|
|
||||
| данные без атрибутов | 9 896 | картинка хранится в макете |
|
||||
| `<picture/>` | 151 | картинка не задана; ровно по одной такой записи на макет |
|
||||
| `ref="v8ui:Имя"` | 83 | ссылка на библиотеку картинок платформы |
|
||||
| `tx`, `ty` при данных | 9 | координата пикселя, чей цвет считается прозрачным |
|
||||
|
||||
Прозрачность записана двумя способами, и они исключают друг друга. Снятый флажок «прозрачный
|
||||
фон» даёт `t="false"`, поставленный — координату пикселя в `tx`/`ty` без атрибута `t`. Проверено
|
||||
на стенде: одна и та же картинка 25×30 вставлена дважды, без флажка `t="false"`, с флажком
|
||||
`tx="24" ty="29"` — правый нижний пиксель. Координата именно пиксельная, а не размер картинки:
|
||||
в корпусе встречается `tx="97" ty="83"` при картинке 368×117, то есть Конфигуратор углом
|
||||
не ограничен.
|
||||
|
||||
Прозрачность не привязана к тому, где лежит картинка: и `t`, и `tx`/`ty` пишутся у ссылочной
|
||||
записи тоже, причём ПЕРЕД `ref` — `<picture t="false" ref="v8ui:Имя"/>`,
|
||||
`<picture tx="24" ty="29" ref="v8ui:CalculationType"/>`. В «Бухгалтерии предприятия» на 8.3.27
|
||||
ссылочных записей с `t` 67, а `t="false"` стоит у 11 135 картинок; значения `true` у этого
|
||||
атрибута не встретилось ни в одной проверенной конфигурации — включённую прозрачность
|
||||
выражают `tx`/`ty`.
|
||||
|
||||
Сама ссылка не различает, откуда картинка: и предопределённая картинка платформы, и общая
|
||||
картинка конфигурации записываются одинаково, префиксом `v8ui:`. Проверить, что за ней стоит,
|
||||
по одному макету нельзя — нужна конфигурация. При загрузке макета платформа ссылку не проверяет:
|
||||
макет со ссылкой на общую картинку грузится в базу, где такой картинки нет (в отличие
|
||||
от ссылочных типов у ячейки-поля ввода, которым нужна матч-конфигурация).
|
||||
|
||||
## Рисунки
|
||||
|
||||
Рисунок — объект поверх сетки: картинка, фигура, надпись, диаграмма. Лежит в `<drawing>`
|
||||
перед палитрами; геометрия задана двумя якорями «ячейка + смещение в точках».
|
||||
|
||||
```xml
|
||||
<drawing>
|
||||
<drawingType>Picture</drawingType>
|
||||
<id>3</id>
|
||||
<zOrder>3</zOrder>
|
||||
<beginRow>1</beginRow>
|
||||
<beginColumn>1</beginColumn>
|
||||
<beginRowOffset>0</beginRowOffset>
|
||||
<beginColumnOffset>0</beginColumnOffset>
|
||||
<endRow>3</endRow>
|
||||
<endColumn>2</endColumn>
|
||||
<endRowOffset>34</endRowOffset>
|
||||
<endColumnOffset>115</endColumnOffset>
|
||||
<formatIndex>4</formatIndex>
|
||||
<pictureSize>Proportionally</pictureSize>
|
||||
<pictureIndex>2</pictureIndex>
|
||||
<detailParameter>Расшифровка</detailParameter>
|
||||
</drawing>
|
||||
```
|
||||
|
||||
Координаты строк и колонок здесь 0-based, в отличие от `<i>` ячейки. `<id>` и `<zOrder>`
|
||||
самостоятельны: на корпусе они расходятся у 8 253 рисунков из 11 268, так что выводить их
|
||||
из порядка следования нельзя. `<pictureSize>Stretch</pictureSize>` — умолчание, пишется всегда.
|
||||
|
||||
Имя рисунка хранится отдельно, среди именованных элементов:
|
||||
|
||||
```xml
|
||||
<namedItem xsi:type="NamedItemDrawing">
|
||||
<name>Логотип</name>
|
||||
<drawingID>3</drawingID>
|
||||
</namedItem>
|
||||
```
|
||||
|
||||
Оформление рисунка — обычная запись палитры форматов, но с собственными тегами: `drawingBorder`
|
||||
(ссылка в палитру линий, вид `SpreadsheetDocumentDrawingLineType`) и
|
||||
`drawingHaveLeftBorder` / `drawingHaveTopBorder` / `drawingHaveRightBorder` /
|
||||
`drawingHaveBottomBorder`. У ячейки таких свойств не бывает: все 2 135 записей корпуса с ними
|
||||
принадлежат рисункам. Остальное в записи — общие свойства (заливка, шрифт, выравнивание).
|
||||
|
||||
Тело диаграмм (`Chart`, `GanttChart`) лежит в том же `<drawing>` в отдельном пространстве имён
|
||||
и здесь не разбирается.
|
||||
|
||||
## Типичная структура макета печатной формы
|
||||
|
||||
Печатная форма обычно состоит из именованных горизонтальных областей:
|
||||
@@ -420,11 +831,11 @@
|
||||
|
||||
## Совместимость версий платформы
|
||||
|
||||
Проведено сравнение выгрузок конфигурации «Бухгалтерия предприятия 3.0» на трёх версиях платформы: 8.3.20, 8.3.24, 8.3.27.
|
||||
Проведено сравнение выгрузок конфигурации «Бухгалтерия предприятия 3.0» на версиях платформы 8.3.24, 8.3.25, 8.3.26, 8.3.27.
|
||||
|
||||
### Template.xml (табличный документ)
|
||||
|
||||
Содержимое `Template.xml` **побайтно идентично** на всех трёх версиях. Формат табличного документа стабилен — пространства имён, набор тегов и структура не менялись между 8.3.20 и 8.3.27.
|
||||
Содержимое `Template.xml` **побайтно идентично** на всех четырёх выгрузках. Формат табличного документа стабилен — пространства имён, набор тегов и структура не менялись между 8.3.24 и 8.3.27.
|
||||
|
||||
### Метаданные (version в MetaDataObject)
|
||||
|
||||
@@ -432,15 +843,17 @@
|
||||
|
||||
| Платформа | version |
|
||||
|-----------|---------|
|
||||
| 8.3.20 | 2.17 |
|
||||
| 8.3.24 | 2.17 |
|
||||
| 8.3.25 | 2.18 |
|
||||
| 8.3.26 | 2.19 |
|
||||
| 8.3.27 | 2.20 |
|
||||
| 8.5.1 | 2.21 |
|
||||
|
||||
Полная лестница, включая ступени ниже проверенного диапазона, — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
### Form.xml (управляемая форма)
|
||||
|
||||
Содержимое `Form.xml` идентично между 8.3.20 и 8.3.24. Дальше различается **только** атрибут `version` в корневом элементе `<Form>` (`2.17` → `2.18` → `2.19` → `2.20` по платформам). Пространства имён и структура не изменились.
|
||||
В пределах проверенного диапазона содержимое `Form.xml` различается **только** атрибутом `version` в корневом элементе `<Form>` (`2.17` → `2.18` → `2.19` → `2.20` по платформам). Структура не менялась; в `2.21` добавилось пространство имён `xmlns:pal`.
|
||||
|
||||
### BSL-модули
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
# Спецификация формата XML подсистем и командного интерфейса 1С
|
||||
|
||||
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
|
||||
Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.18` (8.3.25), `2.19` (8.3.26), `2.20` (8.3.27+).
|
||||
Проверенный диапазон версий формата: `2.17` (платформа 8.3.24) … `2.21` (8.5); полная лестница — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
Источники: выгрузки Бухгалтерия предприятия (платформы 8.3.20, 8.3.24, 8.3.27), ERP 2 (8.3.24).
|
||||
Источники: выгрузки Бухгалтерия предприятия (платформы 8.3.24–8.3.27), ERP 2 (8.3.24).
|
||||
|
||||
> **Связанные спецификации:**
|
||||
> - Корневая структура конфигурации — [1c-configuration-spec.md](1c-configuration-spec.md)
|
||||
@@ -883,9 +883,9 @@ CommandGroup.<ИмяГруппы>
|
||||
|
||||
## 10. Различия версий формата
|
||||
|
||||
| Аспект | `2.17` (8.3.20–8.3.24) | `2.18`–`2.20` (8.3.25–8.3.27) |
|
||||
| Аспект | `2.17` (8.3.24) | `2.18`–`2.21` (8.3.25–8.5) |
|
||||
|---|---|---|
|
||||
| Атрибут `version` | `2.17` | `2.18` / `2.19` / `2.20` по платформе |
|
||||
| Атрибут `version` | `2.17` | `2.18` / `2.19` / `2.20` / `2.21` по платформе |
|
||||
| BOM (UTF-8 BOM) | Нет | Да (EF BB BF) |
|
||||
| Набор элементов | Идентичный | Идентичный |
|
||||
| Набор свойств подсистемы | Идентичный | Идентичный |
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
# Спецификация формата XML пакетов XDTO 1С
|
||||
|
||||
Формат: XML-выгрузка конфигурации 1С:Предприятие 8.3 (Конфигуратор → Конфигурация → Выгрузить конфигурацию в файлы).
|
||||
Версии формата: `2.17` (платформа 8.3.20–8.3.24), `2.18` (8.3.25), `2.19` (8.3.26), `2.20` (8.3.27+).
|
||||
Проверенный диапазон версий формата: `2.17` (платформа 8.3.24) … `2.21` (8.5); полная лестница — [1c-configuration-spec.md §7.1](1c-configuration-spec.md#71-лестница-версий).
|
||||
|
||||
Источники: выгрузки Бухгалтерия предприятия (8.3.24), ERP 2 (8.3.24) — 760 пакетов.
|
||||
|
||||
|
||||
@@ -55,8 +55,8 @@
|
||||
| `commandBarLocation` | `<CommandBarLocation>` | `Top`, `Bottom`, `None` |
|
||||
| `saveDataInSettings` | `<SaveDataInSettings>` | `UseList`, `Use`, `DontUse` |
|
||||
| `autoSaveDataInSettings` | `<AutoSaveDataInSettings>` | `Use`, `DontUse` |
|
||||
| `autoTime` | `<AutoTime>` | `CurrentOrLast`, `Current`, `Last` |
|
||||
| `usePostingMode` | `<UsePostingMode>` | `Auto`, `Postings`, `Movements` |
|
||||
| `autoTime` | `<AutoTime>` | `CurrentOrLast`, `DontUse`, `Current`, `Last` |
|
||||
| `usePostingMode` | `<UsePostingMode>` | `Auto`, `Regular` |
|
||||
| `repostOnWrite` | `<RepostOnWrite>` | `true` / `false` |
|
||||
| `autoURL` | `<AutoURL>` | `true` / `false` |
|
||||
| `enabled` | `<Enabled>` | `true` / `false` — доступность всей формы (редкое; форма-уровень) |
|
||||
|
||||
+606
-51
@@ -18,11 +18,11 @@
|
||||
|
||||
"styles": {
|
||||
"default": {},
|
||||
"header": { "font": "header", "align": "center" },
|
||||
"header": { "font": "header", "horizontalAlignment": "Center" },
|
||||
"label": { "font": "bold" },
|
||||
"bordered": { "border": "all" },
|
||||
"bordered-right": { "border": "all", "align": "right" },
|
||||
"total-right": { "font": "bold", "border": "top", "align": "right" }
|
||||
"bordered": { "border": "Solid" },
|
||||
"bordered-right": { "border": "Solid", "horizontalAlignment": "Right" },
|
||||
"total-right": { "font": "bold", "topBorder": "Solid", "horizontalAlignment": "Right" }
|
||||
},
|
||||
|
||||
"areas": [
|
||||
@@ -73,88 +73,643 @@
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `columns` | да | — | Количество колонок |
|
||||
| `columns` | да | — | Количество колонок в раскладке по умолчанию. `0` допустимо: значит, все строки живут в раскладках из `columnSets` |
|
||||
| `page` | нет | — | Формат страницы: `"A4-landscape"` (780), `"A4-portrait"` (540) или число. Автоматически вычисляет `defaultWidth` из суммы пропорций `"Nx"` |
|
||||
| `defaultWidth` | нет | 10 | Ширина колонок по умолчанию. Игнорируется если задан `page` и все колонки используют `"Nx"` |
|
||||
| `columnWidths` | нет | `{}` | Ширины колонок. Ключи 1-based: `"1"`, `"3-14"`, `"5,7,9"`. Значения: число (абсолют) или `"Nx"` (множитель от defaultWidth, напр. `"2x"`, `"0.5x"`) |
|
||||
| `columnStyles` | нет | — | Оформление колонок: те же ключи, значение — имя стиля (см. «Оформление») |
|
||||
| `textLanguages` | нет | `["ru"]` | Языки, на которых пишется текст, заданный строкой (см. ниже) |
|
||||
| `fonts` | нет | — | Именованные шрифты (если не задано, создаётся Arial 10) |
|
||||
| `styles` | нет | `{}` | Именованные стили |
|
||||
| `areas` | да | — | Массив именованных областей (порядок = порядок в документе) |
|
||||
|
||||
## Шрифты (`fonts.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `face` | `"Arial"` | Имя шрифта |
|
||||
| `size` | `10` | Размер |
|
||||
| `bold` | `false` | Жирный |
|
||||
| `italic` | `false` | Курсив |
|
||||
| `underline` | `false` | Подчёркнутый |
|
||||
| `strikeout` | `false` | Зачёркнутый |
|
||||
|
||||
Шрифт `"default"` используется когда стиль не указывает шрифт явно. Если не определён, создаётся автоматически (Arial 10).
|
||||
|
||||
## Стили (`styles.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `font` | `"default"` | Ссылка на имя шрифта |
|
||||
| `align` | — | `left`, `center`, `right` |
|
||||
| `valign` | — | `top`, `center` |
|
||||
| `border` | — | Стороны рамки: `all`, `top`, `bottom`, `left`, `right`, `none`. Через запятую: `"top,bottom"` |
|
||||
| `borderWidth` | `"thin"` | Толщина рамки: `thin` (1px) или `thick` (2px) |
|
||||
| `wrap` | `false` | Перенос текста |
|
||||
| `format` | — | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` и т.д. |
|
||||
| `styles` | нет | `{}` | Именованные стили (см. «Оформление») |
|
||||
| `areas` | да | — | Массив областей — диапазонов подряд идущих строк (порядок = порядок в документе); имя необязательно |
|
||||
| `namedAreas` | нет | — | Именованные области, заданные координатами (см. ниже) |
|
||||
| `columnSets` | нет | — | Дополнительные колоночные раскладки (см. «Колоночные раскладки») |
|
||||
| `rowGroups` | нет | — | Группы строк (см. ниже) |
|
||||
| `columnGroups` | нет | — | Группы колонок (см. ниже) |
|
||||
| `header` / `footer` | нет | — | Верхний и нижний колонтитулы (см. ниже) |
|
||||
| `printSettings` | нет | — | Параметры печати (см. ниже) |
|
||||
| `pictures` | нет | — | Палитра картинок: ссылки на библиотеку платформы или данные (см. ниже) |
|
||||
| `drawings` | нет | — | Рисунки: картинки, фигуры, надписи (см. ниже) |
|
||||
|
||||
## Области (`areas[]`)
|
||||
|
||||
| Поле | Обяз. | Описание |
|
||||
|------|:-----:|----------|
|
||||
| `name` | да | Имя области для `Макет.ПолучитьОбласть("Имя")` |
|
||||
| `name` | нет | Имя области для `Макет.ПолучитьОбласть("Имя")` |
|
||||
| `columnSet` | нет | Ссылка на раскладку из `columnSets` |
|
||||
| `rows` | да | Массив строк |
|
||||
|
||||
Макет собирается из областей — диапазонов подряд идущих строк. Имя делает область именованной: она доступна в коде как `Макет.ПолучитьОбласть("Имя")` и занимает строки своего диапазона. **Область без имени** — просто кусок сетки: так описываются строки, не принадлежащие ни одной именованной области.
|
||||
|
||||
## Именованные области координатами (`namedAreas[]`)
|
||||
|
||||
Для областей, которые диапазоном подряд идущих строк не описываются: полоса колонок, прямоугольник, ячейка, а также пересекающиеся с другими.
|
||||
|
||||
| Поле | Обяз. | Описание |
|
||||
|------|:-----:|----------|
|
||||
| `name` | да | Имя области |
|
||||
| `rows` | \* | Строки: число или диапазон `"N-M"`, 1-based |
|
||||
| `cols` | \* | Колонки: число или диапазон `"N-M"`, 1-based |
|
||||
| `columnSet` | нет | Колоночная раскладка области. Без ключа выводится из накрытых строк, `""` — привязки нет |
|
||||
|
||||
\* Обязательна хотя бы одна из осей.
|
||||
|
||||
**Тип области не указывается** — он следует из того, какие оси заданы, как в `ТабличныйДокумент.Область()`: только строки → полоса строк, только колонки → полоса колонок, обе оси → прямоугольник, одиночные значения по обеим осям → одна ячейка.
|
||||
|
||||
```json
|
||||
"namedAreas": [
|
||||
{ "name": "ОбластьПечатиПоВысоте", "rows": "1-48" },
|
||||
{ "name": "ОбластьПечатиПоШирине", "cols": "1-35" },
|
||||
{ "name": "HZY", "rows": 9, "cols": "16-17" }
|
||||
]
|
||||
```
|
||||
|
||||
Диапазон — та же грамматика, что у `columnWidths`, но **только** число или `"N-M"`: список через запятую запрещён, область непрерывна. Имя обязательно, и хотя бы одна ось должна быть задана; нарушение любого из этих правил → ненулевой код выхода и сообщение в stderr.
|
||||
|
||||
## Строки (`rows[]`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `height` | — | Высота строки (если не задана, используется авто) |
|
||||
| `rowStyle` | — | Стиль для ВСЕХ колонок (заполняет пустоты рамками) |
|
||||
| `hidden` | `false` | Скрыть строку |
|
||||
| `rowStyle` | — | Стиль строки: ложится и на саму строку, и на ВСЕ её колонки (заполняет пустоты рамками) |
|
||||
| `cells` | `[]` | Массив ячеек |
|
||||
| `empty` | — | Количество подряд идущих пустых строк (заменяет N отдельных `{}`) |
|
||||
|
||||
Строка без `cells` и `rowStyle` → пустая строка. `{ "empty": 3 }` эквивалентно трём `{}`.
|
||||
|
||||
`height` и `hidden` — собственные свойства строки: у ячейки таких нет, и в её оформление они
|
||||
не попадают. Всё остальное оформление строки задаётся через `rowStyle`.
|
||||
|
||||
### Короткая форма: список ячеек позиционно
|
||||
|
||||
Список ячеек можно записывать позиционно — позиция определяется порядком, `col` не указывается.
|
||||
Если у строки нет своих свойств, такой список и есть строка; если нужны `height`, `hidden`
|
||||
или `rowStyle` — тот же список кладётся в `cells`.
|
||||
|
||||
| Элемент | Значение |
|
||||
|---------|----------|
|
||||
| `"текст"` | Статический текст (`text`) |
|
||||
| `{ "ru": "…", "en": "…" }` | Тот же текст на нескольких языках |
|
||||
| `"{Имя}"` | Параметр (`param`) |
|
||||
| `">"` | Продолжение ячейки слева — увеличивает её `span` |
|
||||
| `"|"` | Продолжение ячейки сверху — увеличивает её `rowspan` |
|
||||
| `null` | Пустая колонка: позиция занята, ячейка не создаётся |
|
||||
| `{ ... }` | Обычная ячейка **без** `col`; нужна для `style`, `detail`, `template` |
|
||||
|
||||
Объект-элемент трактуется по его ключам: если среди них есть ключ ячейки (`span`, `rowspan`,
|
||||
`style`, `param`, `detail`, `text`, `template`, `valueType`, `controlType`, `value`,
|
||||
`control`) — объект
|
||||
описывает
|
||||
свойства ячейки. Иначе он целиком считается её текстом, а его ключи — идентификаторами языков.
|
||||
|
||||
```json
|
||||
"rows": [
|
||||
["Вид", "Остаток", ">", "Итог"],
|
||||
["|", "начало", "конец", "|"],
|
||||
["{Вид}", "{Нач}", "{Кон}", "{Итог}"]
|
||||
]
|
||||
```
|
||||
Здесь «Вид» и «Итог» объединены по вертикали на две строки, «Остаток» — по горизонтали на две колонки.
|
||||
|
||||
```json
|
||||
{ "rowStyle": "итог", "cells": [null, null, null, "Итого:", "{Всего}"] }
|
||||
```
|
||||
|
||||
Ограничение у формы одно: ею не выразить текст, совпадающий с `">"`, `"|"` или с шаблоном
|
||||
`"{...}"` — такие ячейки записываются объектом с ключом `text`.
|
||||
|
||||
Маркеру нужно, что продолжать: `">"` требует ячейку слева в той же строке, `"|"` — ячейку сверху. Объектный элемент не должен нести `col`: позиция уже задана порядком. Число элементов не может превышать `columns`. Нарушение любого из этих правил → ненулевой код выхода и сообщение в stderr.
|
||||
|
||||
## Ячейки (`cells[]`)
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `col` | да | — | Позиция колонки (1-based) |
|
||||
| `col` | да | — | Позиция колонки (1-based). В короткой форме строки не указывается — позиция берётся из порядка |
|
||||
| `span` | нет | `1` | Объединение по горизонтали (количество колонок) |
|
||||
| `rowspan` | нет | `1` | Объединение по вертикали (количество строк) |
|
||||
| `style` | нет | rowStyle | Стиль ячейки (переопределяет rowStyle) |
|
||||
| `param` | нет | — | Параметр заполнения |
|
||||
| `detail` | нет | — | Параметр расшифровки (только с `param`) |
|
||||
| `text` | нет | — | Статический текст |
|
||||
| `template` | нет | — | Шаблонный текст с `[Параметр]` |
|
||||
| `detail` | нет | — | Параметр расшифровки. Самостоятельный ключ: ставится и на ячейку без `param` — с текстом, пустую или поле ввода |
|
||||
| `text` | нет | — | Статический текст. Строка или объект `{ ru, en }` — см. ниже |
|
||||
| `template` | нет | — | Шаблонный текст с `[Параметр]`. Строка или объект, как `text` |
|
||||
| `valueType` | нет | — | Тип значения: ячейка становится полем ввода (см. ниже) |
|
||||
| `controlType` | нет | `input` | Элемент управления поля ввода: `input` или `checkbox`. Только вместе с `valueType` |
|
||||
| `value` | нет | — | Значение в поле ввода. Только вместе с `valueType` |
|
||||
| `control` | нет | — | Настройки элемента управления в записи платформы (base64). Раундтрип, не для ручного авторинга |
|
||||
| `note` | нет | — | Примечание к ячейке (см. ниже) |
|
||||
|
||||
### Тип заполнения
|
||||
### Содержимое ячейки
|
||||
|
||||
Определяется автоматически по содержимому ячейки:
|
||||
- `param` → fillType=Parameter
|
||||
- `template` → fillType=Template
|
||||
- `text` → fillType=Text
|
||||
- ничего → без fillType (пустая ячейка или рамка)
|
||||
Задаётся ровно одним из ключей, объявлять способ заполнения отдельно не нужно:
|
||||
- `param` — параметр заполнения;
|
||||
- `template` — текст со вставками `[Параметр]`;
|
||||
- `text` — статический текст;
|
||||
- ничего — пустая ячейка (нужна, например, ради рамки).
|
||||
|
||||
## `rowStyle` — автозаполнение
|
||||
### Текст на нескольких языках
|
||||
|
||||
Когда задан `rowStyle`, компилятор создаёт ячейки для ВСЕХ колонок строки. Позиции без явных ячеек заполняются пустыми ячейками с указанным стилем. Это обеспечивает сплошные рамки в табличных строках.
|
||||
`text` и `template` принимают строку или объект «язык → текст». Объект даёт по надписи на каждый язык, в порядке ключей. Строка означает один и тот же текст на всех языках макета — по умолчанию только русский.
|
||||
|
||||
```json
|
||||
{ "col": 1, "text": "Наименование" }
|
||||
{ "col": 2, "text": { "ru": "Поставщик", "en": "Supplier" } }
|
||||
```
|
||||
|
||||
Набор языков задаётся документным ключом `textLanguages`:
|
||||
|
||||
```json
|
||||
{ "columns": 3, "textLanguages": ["ru", "en"], "areas": [] }
|
||||
```
|
||||
|
||||
С таким объявлением `"Наименование"` из примера выше даст надпись и под `ru`, и под `en`.
|
||||
|
||||
Ключ ни на что в конфигурации не смотрит — это просто список языков, на которые разворачивается строка.
|
||||
|
||||
Пустая строка — это текст: ячейка с `"text": ""` даёт пустую надпись, а не ячейку без текста.
|
||||
|
||||
## Ячейка-поле ввода
|
||||
|
||||
Ячейка может не показывать текст, а принимать значение от пользователя — так делают макеты для
|
||||
ввода данных. Достаточно задать тип значения:
|
||||
|
||||
```json
|
||||
{ "col": 1, "valueType": "Number(15,3,nonneg)" }
|
||||
{ "col": 2, "valueType": "String(10)" }
|
||||
{ "col": 3, "valueType": "Boolean", "controlType": "checkbox" }
|
||||
{ "col": 4, "valueType": "CatalogRef.Валюты" }
|
||||
{ "col": 5, "valueType": "Boolean + String + Date + CatalogRef.Валюты" }
|
||||
{ "col": 6, "valueType": "AnyRef" }
|
||||
```
|
||||
|
||||
Грамматика типа:
|
||||
|
||||
| Запись | Значение |
|
||||
|---|---|
|
||||
| `Boolean` | булево |
|
||||
| `String`, `String(10)`, `String(10,fixed)` | строка; без длины — неограниченная, `fixed` — фиксированной длины |
|
||||
| `Number`, `Number(15,3)`, `Number(15,3,nonneg)` | число; без параметров — без ограничения разрядности, `nonneg` — неотрицательное |
|
||||
| `Date`, `DateTime`, `Time` | дата, дата со временем, время |
|
||||
| `CatalogRef.Валюты`, `DocumentRef.Реализация`, `EnumRef.Статусы`, `DefinedType.Сумма` | ссылочный тип |
|
||||
| `CatalogRef`, `DocumentRef`, `AnyRef` | категория целиком: любая ссылка этого вида |
|
||||
| `A + B` | составной тип; повтор одного примитива недопустим |
|
||||
|
||||
Русские имена принимаются наравне с английскими: `Число(15,3)`, `Строка(10)`,
|
||||
`СправочникСсылка.Валюты`.
|
||||
|
||||
`text` и `template` в такой ячейке недопустимы — платформа не показывает текст там, где вводится
|
||||
значение. `param` и `detail` допустимы: они описывают заполнение и расшифровку, а не содержимое.
|
||||
|
||||
Пустой тип `"valueType": ""` — «ячейка содержит значение», но тип не ограничен.
|
||||
|
||||
Значение поля ввода задаётся ключом `value`, и его тип выражается литералом JSON: строка —
|
||||
строкой, число — числом, булево — `true`/`false`. Приведения к объявленному типу **не
|
||||
происходит**: платформа хранит значение с его собственным типом и совпадать с объявленным он
|
||||
не обязан (у ссылочного и составного типа значение всегда строковое). Пустая строка означает
|
||||
пустое значение объявленного типа. Дата записывается строкой в формате `ГГГГ-ММ-ДДTчч:мм:сс`
|
||||
и читается как дата только у ячейки, объявленной датой.
|
||||
|
||||
```json
|
||||
{ "col": 1, "valueType": "Number(15,3)", "value": 12.5 }
|
||||
{ "col": 2, "valueType": "String(10)", "value": "5" }
|
||||
{ "col": 3, "valueType": "Boolean", "value": true }
|
||||
{ "col": 4, "valueType": "DateTime", "value": "" }
|
||||
```
|
||||
|
||||
`controlType` нужен редко: умолчание платформы — поле ввода, и оно применяется ко всем типам,
|
||||
включая `Boolean`. Флажок задаётся явно. Значение `"none"` (тега элемента управления нет вовсе) —
|
||||
форма раундтрипа, для ручного авторинга не нужна.
|
||||
|
||||
## Примечание к ячейке
|
||||
|
||||
Всплывающая подсказка, которую платформа показывает при наведении. Задаётся ключом `note` —
|
||||
строкой, объектом «язык → текст» или полной формой:
|
||||
|
||||
```json
|
||||
{ "col": 1, "text": "Итого", "note": "Сумма без НДС" }
|
||||
{ "col": 2, "note": { "ru": "на дату документа", "en": "as of the document date" } }
|
||||
{ "col": 3, "note": { "text": "не более 20%", "style": "жёлтая-подсказка" } }
|
||||
{ "col": 4, "note": { "text": "…", "autoSize": false,
|
||||
"box": { "top": 58, "left": -175, "bottom": 362, "right": 478 } } }
|
||||
```
|
||||
|
||||
Объект трактуется по ключам — так же, как текст ячейки в короткой форме строки: есть ключ
|
||||
примечания (`text`, `style`, `box`, `autoSize`, `anchor`) → это описание примечания, иначе ключи
|
||||
считаются идентификаторами языков.
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `text` | — | Текст подсказки: строка или объект «язык → текст» |
|
||||
| `style` | стиль подсказки | Имя стиля из `styles`; без него — оформление, которое даёт Конфигуратор |
|
||||
| `autoSize` | `true` | Подгонять ли размер окошка под текст |
|
||||
| `box` | канонический | Смещения окошка: `top`, `left` — положение, `bottom`, `right` — размер |
|
||||
| `anchor` | `{ row: 1, col: 1 }` | Якорь начала окошка. Раундтрип, не для ручного авторинга |
|
||||
|
||||
Координаты ячейки в примечании не задаются — платформа привязывает конец окошка к самой ячейке,
|
||||
и компилятор проставляет это сам. `autoSize` и `box` независимы: при автоподгоне размера
|
||||
положение окошка всё равно хранится.
|
||||
|
||||
## Колонтитулы
|
||||
|
||||
У верхнего и нижнего колонтитула по три слота — `left`, `center`, `right` — и общие настройки:
|
||||
|
||||
```json
|
||||
"header": {
|
||||
"font": "мелкий",
|
||||
"verticalAlignment": "Bottom",
|
||||
"startPage": 2,
|
||||
"left": "Слева\nвторая строка",
|
||||
"center": "Стр. [&НомерСтраницы] из [&СтраницВсего]",
|
||||
"right": { "ru": "Справа", "en": "Right" }
|
||||
},
|
||||
"footer": {
|
||||
"show": false,
|
||||
"center": { "formatted": "<b>Итог</> <colorstyle -16>красным</>" }
|
||||
}
|
||||
```
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `left`, `center`, `right` | — | Текст слота: строка, объект «язык → текст» или `{ "formatted": … }` |
|
||||
| `font` | — | Имя шрифта из `fonts` — на весь колонтитул |
|
||||
| `verticalAlignment` | — | Положение текста по вертикали: `Top`, `Center`, `Bottom` |
|
||||
| `show` | `true` | Выводить ли колонтитул |
|
||||
| `startPage` | `1` | Страница, с которой колонтитул печатается |
|
||||
|
||||
Текст может быть многострочным (`\n`) и содержать поля `[&НомерСтраницы]`, `[&СтраницВсего]`,
|
||||
`[&Дата]`, `[&Время]` — платформа подставляет их при печати. `{ "formatted": … }` — форматированная
|
||||
строка: разметка живёт прямо в тексте (`<b>жирный</>`, `<fontsize 12>`, `<colorstyle -16>`).
|
||||
|
||||
## Параметры печати
|
||||
|
||||
Плоский объект; имя ключа совпадает с именем свойства в выгрузке:
|
||||
|
||||
```json
|
||||
"printSettings": {
|
||||
"pageOrientation": "Landscape",
|
||||
"topMargin": 500, "leftMargin": 500, "bottomMargin": 500, "rightMargin": 500,
|
||||
"headerSize": 1000, "footerSize": 1000,
|
||||
"fitToPage": true, "firstPageNumber": 7
|
||||
}
|
||||
```
|
||||
|
||||
Допустимые ключи: `pageOrientation`, `scale`, `collate`, `copies`, `perPage`, `topMargin`,
|
||||
`leftMargin`, `bottomMargin`, `rightMargin`, `headerSize`, `footerSize`, `fitToPage`,
|
||||
`blackAndWhite`, `printerName`, `paper`, `paperSource`, `pageWidth`, `pageHeight`, `duplexType`,
|
||||
`pagePlacementAlternation`, `firstPageNumber`. Незнакомый ключ — ошибка. Порядок в объекте не
|
||||
важен: компилятор пишет свойства в том порядке, что и платформа.
|
||||
|
||||
## Группы строк и колонок
|
||||
|
||||
Диапазон строк или колонок, который сворачивается кнопкой на полях. Задаются плоским списком;
|
||||
вложенность выражена вхождением одного диапазона в другой:
|
||||
|
||||
```json
|
||||
"rowGroups": [
|
||||
{ "rows": "2-4" },
|
||||
{ "rows": 3 },
|
||||
{ "rows": 5, "name": "Итоговая", "collapsed": true, "titleLocation": "begin" }
|
||||
],
|
||||
"columnGroups": [
|
||||
{ "cols": "2-3", "name": { "ru": "Показатели", "en": "Values" } },
|
||||
{ "cols": 3 }
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `rows` / `cols` | да | — | Диапазон 1-based: число или `"N-M"` |
|
||||
| `name` | нет | — | Имя группы: строка или объект «язык → текст» |
|
||||
| `collapsed` | нет | `false` | Свёрнута ли группа при открытии |
|
||||
| `titleLocation` | нет | `auto` | Расположение заголовка: `begin`, `end`, `auto` |
|
||||
|
||||
Диапазоны либо вложены, либо не пересекаются — частичное пересечение платформа не хранит, и
|
||||
компилятор отвергает его с ненулевым кодом выхода. Число уровней вложенности считается само.
|
||||
Порядок в списке не важен: компилятор пишет группы так же, как платформа, — родитель
|
||||
раньше вложенных, по возрастанию начала.
|
||||
|
||||
## Рисунки и картинки
|
||||
|
||||
Рисунок — объект поверх сетки ячеек: картинка, фигура или надпись. Его положение задают два
|
||||
якоря: ячейка плюс смещение в точках от её левого верхнего угла.
|
||||
|
||||
```json
|
||||
"pictures": {
|
||||
"знак": { "ref": "v8ui:Стоп48" },
|
||||
"логотип": { "data": "iVBORw0KGgo...", "transparent": { "x": 24, "y": 29 } }
|
||||
},
|
||||
"drawings": [
|
||||
{ "type": "Picture", "picture": "логотип", "name": "Логотип",
|
||||
"begin": { "row": 1, "col": 1 },
|
||||
"end": { "row": 3, "col": 2, "dy": 34, "dx": 115 },
|
||||
"pictureSize": "Proportionally", "detail": "ПараметрРасшифровки" },
|
||||
{ "type": "Rectangle", "style": "заливка",
|
||||
"line": { "style": "Dashed", "width": 2 },
|
||||
"sides": { "left": true, "top": true, "right": false, "bottom": false },
|
||||
"begin": { "row": 5, "col": 1 }, "end": { "row": 6, "col": 3 } },
|
||||
{ "type": "Text", "text": { "ru": "Подпись", "en": "Signature" },
|
||||
"begin": { "row": 8, "col": 1 }, "end": { "row": 8, "col": 3, "dy": 12 } }
|
||||
]
|
||||
```
|
||||
|
||||
| Поле | Обяз. | По умолч. | Описание |
|
||||
|------|:-----:|-----------|----------|
|
||||
| `type` | нет | `Picture` | `Picture`, `Rectangle`, `Ellipse`, `Line`, `Text`, `Chart`, `GanttChart` |
|
||||
| `begin` / `end` | да | — | Якоря: `row`, `col` (1-based) и смещения `dy`, `dx` в точках |
|
||||
| `picture` | нет | — | Имя записи из `pictures` |
|
||||
| `pictureSize` | нет | `Stretch` | Как картинка заполняет прямоугольник: `Stretch`, `AutoSize`, `Proportionally`, `RealSize` |
|
||||
| `text` | нет | — | Надпись: строка или объект «язык → текст» |
|
||||
| `name` | нет | — | Имя рисунка, по которому к нему обращаются из кода |
|
||||
| `detail` | нет | — | Параметр расшифровки |
|
||||
| `style` | нет | — | Именованный стиль: заливка, шрифт, выравнивание надписи |
|
||||
| `line` | нет | — | Линия рисунка (она же его рамка) — как `border` у ячейки |
|
||||
| `sides` | нет | — | Какие стороны рамки видны: `left`, `top`, `right`, `bottom` |
|
||||
| `id` | нет | номер по порядку | Идентификатор рисунка |
|
||||
| `zOrder` | нет | номер по порядку | Порядок перекрытия: чем больше, тем выше |
|
||||
|
||||
Порядок в списке `drawings` — порядок в документе; кто кого перекрывает, задаёт `zOrder`.
|
||||
|
||||
Запись в `pictures` — либо ссылка (`ref`), либо сами данные в base64 (`data`). Ссылкой
|
||||
задаются и предопределённая картинка платформы, и общая картинка конфигурации — пишутся они
|
||||
одинаково, префиксом `v8ui:`. Пустая запись `{}` — картинка не задана, такое в макетах встречается.
|
||||
Прозрачность задаётся ключом `transparent` в одной из двух форм: `false` — прозрачного фона
|
||||
нет; `{ "x": …, "y": … }` — прозрачным считается цвет пикселя с этими координатами внутри
|
||||
картинки (по флажку «прозрачный фон» Конфигуратор берёт её правый нижний пиксель).
|
||||
|
||||
Одну запись `pictures` могут использовать несколько рисунков — данные в макете не дублируются.
|
||||
|
||||
## Картинка в ячейке
|
||||
|
||||
Картинка бывает не только поверх сетки, но и внутри ячейки — тогда её задаёт стиль, а сама
|
||||
картинка берётся из той же палитры `pictures`:
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 2,
|
||||
"pictures": { "стоп": { "ref": "v8ui:Стоп48" } },
|
||||
"styles": { "значок": { "picIndex": 1, "picHorizontalAlignment": "Center",
|
||||
"picVerticalAlignment": "Center", "textPosition": "Bottom" } },
|
||||
"areas": [{ "rows": [
|
||||
[{ "style": "значок", "text": "удалить", "pictureParameter": "Удалить" }]
|
||||
]}]
|
||||
}
|
||||
```
|
||||
|
||||
- `picIndex` — номер записи в `pictures`, считая с единицы в порядке объявления;
|
||||
- `picHorizontalAlignment`, `picVerticalAlignment`, `pictureSizeMode`, `textPosition` — как
|
||||
картинка стоит в ячейке и где относительно неё текст (значения — в разделе «Полный список свойств стиля»);
|
||||
- `pictureParameter` — ключ самой ЯЧЕЙКИ, а не стиля: имя параметра, которым картинку
|
||||
подставляют из кода, как `param` подставляет текст. С текстом ячейки уживается.
|
||||
|
||||
## `rowStyle` — оформление строки
|
||||
|
||||
Стиль применяется ко ВСЕЙ ширине строки: позиции без явных ячеек получают тот же стиль. Так в табличных строках получаются сплошные рамки. Он же становится оформлением самой строки — именно так платформа хранит строку, оформленную целиком.
|
||||
|
||||
Стиль конкретной ячейки (`style`) перекрывает `rowStyle` для этой ячейки.
|
||||
|
||||
Если в предыдущих строках той же области есть ячейки с `rowspan`, их колонки при автозаполнении пропускаются.
|
||||
|
||||
## Оформление: шрифты, стили, цвета, рамки
|
||||
|
||||
Оформление в табличном документе — одна сущность на всех: ячейка, строка и колонка ссылаются
|
||||
на один и тот же именованный стиль. Ячейка — ключом `style`, строка — `rowStyle`, колонка —
|
||||
через `columnStyles`.
|
||||
|
||||
### Шрифты (`fonts.<name>`)
|
||||
|
||||
| Поле | По умолч. | Описание |
|
||||
|------|-----------|----------|
|
||||
| `face` | `"Arial"` | Имя шрифта |
|
||||
| `size` | `10` | Размер (бывает дробным: `8.3`) |
|
||||
| `bold` | `false` | Жирный |
|
||||
| `italic` | `false` | Курсив |
|
||||
| `underline` | `false` | Подчёркнутый |
|
||||
| `strikeout` | `false` | Зачёркнутый |
|
||||
|
||||
Шрифт `"default"` используется, когда стиль не указывает шрифт явно. Если не определён,
|
||||
создаётся автоматически (Arial 10).
|
||||
|
||||
Вместо собственного описания шрифт может быть **ссылкой** — на элемент стиля конфигурации
|
||||
или на системный шрифт. Тогда у него единственное поле:
|
||||
|
||||
```json
|
||||
"fonts": {
|
||||
"основной": { "ref": "style:TextFont" },
|
||||
"системный": { "ref": "sys:DefaultGUIFont" }
|
||||
}
|
||||
```
|
||||
|
||||
Это та же запись, что у шрифта в описании формы.
|
||||
|
||||
### Стили (`styles.<name>`)
|
||||
|
||||
Ключ стиля — имя свойства так, как оно называется в выгрузке. Ниже частые; полный
|
||||
перечень — в разделе «Полный список свойств стиля».
|
||||
|
||||
| Поле | Описание |
|
||||
|------|----------|
|
||||
| `font` | Ссылка на имя из `fonts` |
|
||||
| `horizontalAlignment` | `Left`, `Center`, `Right`, `Justify`, `Auto` |
|
||||
| `verticalAlignment` | `Top`, `Center`, `Bottom` |
|
||||
| `textPlacement` | Что делать с длинным текстом: `Wrap` (перенос), `Cut` (обрезать), `Block`, `Auto` |
|
||||
| `backColor` | Цвет фона (см. «Цвет») |
|
||||
| `textColor` | Цвет текста |
|
||||
| `border`, `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | Рамка (см. «Рамка») |
|
||||
| `borderColor` | Цвет рамки |
|
||||
| `format` | Формат данных 1С: `"ЧЦ=15; ЧДЦ=2"`, `"ДФ=dd.MM.yyyy"` |
|
||||
| `hidden` | Скрыть |
|
||||
| `protection` | Защита от редактирования |
|
||||
| `indent` | Отступ |
|
||||
| `textOrientation` | Поворот текста, в десятых долях градуса (`900` = 90°) |
|
||||
|
||||
Значения перечислений регистр не различают: `"center"` и `"Center"` равнозначны.
|
||||
|
||||
```json
|
||||
"styles": {
|
||||
"шапка": {
|
||||
"font": "жирный",
|
||||
"horizontalAlignment": "Center",
|
||||
"verticalAlignment": "Center",
|
||||
"textPlacement": "Wrap",
|
||||
"backColor": "#EBEBEB"
|
||||
},
|
||||
"итог": { "font": "жирный", "topBorder": "Solid", "horizontalAlignment": "Right" }
|
||||
}
|
||||
```
|
||||
|
||||
### Цвет
|
||||
|
||||
Строка в одной из четырёх форм — это нотация самой платформы:
|
||||
|
||||
| Форма | Значение |
|
||||
|-------|----------|
|
||||
| `#RRGGBB` | RGB-hex, напр. `#FFFFC0` |
|
||||
| `style:ИмяСтиля` | Элемент стиля конфигурации или платформы, напр. `style:FormBackColor` |
|
||||
| `web:Имя` | Цвет из web-палитры, напр. `web:Gainsboro`, `web:FireBrick` |
|
||||
| `win:Имя` | Системный цвет Windows, напр. `win:ButtonText` |
|
||||
|
||||
Имя должно существовать в своей палитре — несуществующее платформа отвергнет при загрузке.
|
||||
|
||||
### Рамка
|
||||
|
||||
Пять ключей: `border` — все четыре стороны сразу, `leftBorder` / `topBorder` / `rightBorder` /
|
||||
`bottomBorder` — по отдельности. Значение одинаковое у всех:
|
||||
|
||||
| Запись | Значение |
|
||||
|--------|----------|
|
||||
| `"Solid"` | Стиль линии, ширина 1 |
|
||||
| `{ "style": "Solid", "width": 2 }` | Стиль и ширина |
|
||||
|
||||
Стили линии: `None`, `Solid`, `Dotted`, `Dashed`, `DashDotted`, `DashDottedDotted`,
|
||||
`ThinDashed`, `LargeDashed`, `ThickDashed`, `Double`. Конфигуратор предлагает разные наборы
|
||||
в разных местах — у рамки ячейки одни, у линии рисунка другие, — но палитра одна на документ.
|
||||
|
||||
Задавать стороны по отдельности можно всегда: если все четыре совпали, компилятор сам свернёт
|
||||
их в один `border` — так это хранит платформа.
|
||||
|
||||
```json
|
||||
"рамка-снизу": { "bottomBorder": "Dotted" },
|
||||
"рамка-вокруг": { "border": { "style": "Solid", "width": 2 } }
|
||||
```
|
||||
|
||||
### Стиль колонки (`columnStyles`)
|
||||
|
||||
Колонка несёт то же оформление, что ячейка и строка. Ключи — та же грамматика диапазонов,
|
||||
что у `columnWidths`; значение — имя стиля.
|
||||
|
||||
```json
|
||||
"columnWidths": { "1": 30, "2-3": 15 },
|
||||
"columnStyles": { "1": "по-центру", "4": "скрытая" }
|
||||
```
|
||||
|
||||
Внутри `columnSets` работает тот же ключ. Ширина и стиль независимы: колонка может иметь
|
||||
только ширину, только стиль или и то, и другое.
|
||||
|
||||
## Колоночные раскладки (`columnSets`)
|
||||
|
||||
Группа строк может иметь собственные ширины колонок — в 1С это «индивидуальная ширина колонок».
|
||||
Документные `columns` и `columnWidths` описывают раскладку по умолчанию; дополнительные
|
||||
объявляются в `columnSets`, а область ссылается на нужную ключом `columnSet` — так же, как
|
||||
ячейка ссылается на `styles` через `style`.
|
||||
|
||||
```json
|
||||
{
|
||||
"columns": 52,
|
||||
"columnWidths": { "1": 8 },
|
||||
"columnSets": {
|
||||
"таблица": { "columns": 52, "columnWidths": { "1": 7, "2-52": 24 } }
|
||||
},
|
||||
"areas": [
|
||||
{ "name": "Шапка", "rows": [["Отчёт о продажах"]] },
|
||||
{ "name": "ТабличнаяЧасть", "columnSet": "таблица",
|
||||
"rows": [["№", "Номенклатура"]] }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Раскладка описывается той же парой полей, что и документная: `columns` — количество колонок
|
||||
(у раскладок оно обычно разное), `columnWidths` — ширины. Ключ словаря — имя раскладки;
|
||||
в макетах, полученных декомпиляцией, это идентификатор из исходного файла, при описании
|
||||
с нуля — любая строка.
|
||||
|
||||
Все строки области получают раскладку области, поэтому одна область не может смешивать
|
||||
раскладки. Позиции колонок (`col`, `span`) проверяются по ширине раскладки СВОЕЙ области,
|
||||
а не документной.
|
||||
|
||||
Ссылка на необъявленную раскладку → ненулевой код выхода и сообщение в stderr.
|
||||
|
||||
## Полный список свойств стиля
|
||||
|
||||
Имя ключа совпадает с именем свойства в выгрузке — исключений нет. Частые свойства
|
||||
с примерами — в разделе «Оформление», здесь полный перечень.
|
||||
|
||||
Тип значения:
|
||||
|
||||
- **число** — целое;
|
||||
- **да/нет** — `true` / `false`;
|
||||
- **перечисление** — одно из указанных, регистр не важен;
|
||||
- **цвет** — `#RRGGBB`, `style:Имя`, `web:Имя`, `win:Имя`;
|
||||
- **линия** — `"Solid"` либо `{ style, width }`;
|
||||
- **текст** — строка (разворачивается на языки макета).
|
||||
|
||||
### Текст и выравнивание
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `font` | имя | Ссылка на имя из `fonts` |
|
||||
| `horizontalAlignment` | перечисление | `Left`, `Center`, `Right`, `Justify`, `Auto` |
|
||||
| `verticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
|
||||
| `textPlacement` | перечисление | `Wrap`, `Cut`, `Block`, `Auto` |
|
||||
| `textOrientation` | число | Поворот в десятых долях градуса: `900` = 90° |
|
||||
| `textColor` | цвет | Цвет текста |
|
||||
| `indent` | число | Отступ текста |
|
||||
| `autoIndent` | число | Автоматический отступ |
|
||||
| `format` | текст | Формат данных: `"ЧЦ=15; ЧДЦ=2"` |
|
||||
| `editFormat` | текст | Формат редактирования |
|
||||
| `mask` | текст | Маска ввода |
|
||||
| `markNegatives` | да/нет | Выделять отрицательные |
|
||||
|
||||
### Фон и рамка
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `backColor` | цвет | Цвет фона |
|
||||
| `pattern` | перечисление | `WithoutPattern`, `Solid`, `Pattern1`…`Pattern17` (в Конфигураторе «Узор 1» … «Узор 17») |
|
||||
| `patternColor` | цвет | Цвет узора |
|
||||
| `border` | линия | Все четыре стороны |
|
||||
| `leftBorder`, `topBorder`, `rightBorder`, `bottomBorder` | линия | Отдельная сторона |
|
||||
| `borderColor` | цвет | Цвет рамки |
|
||||
|
||||
### Поведение
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `hidden` | да/нет | Скрыть |
|
||||
| `protection` | да/нет | Защита от редактирования |
|
||||
| `print` | да/нет | Выводить на печать |
|
||||
| `hyperLink` | да/нет | Гиперссылка |
|
||||
| `detailsUse` | перечисление | Использование расшифровки: `Cell`, `Row`, `WithoutProcessing` |
|
||||
| `autoMarkIncomplete` | да/нет | Автоотметка незаполненного |
|
||||
| `bySelectedColumns` | да/нет | По выделенным колонкам |
|
||||
| `columnSizeChange` | перечисление | `Normal`, `QuickChange` |
|
||||
| `autoWidthCalculation` | да/нет | Автоматический расчёт ширины |
|
||||
| `widthWeightFactor` | число | Весовой коэффициент ширины |
|
||||
|
||||
### Картинка в ячейке
|
||||
|
||||
| Ключ | Тип | Значение |
|
||||
|------|-----|----------|
|
||||
| `picIndex` | число | Номер картинки |
|
||||
| `pictureSizeMode` | перечисление | `AutoSize`, `Proportionally`, `RealSize` |
|
||||
| `picHorizontalAlignment` | перечисление | `Auto`, `Center`, `Left`, `Right` |
|
||||
| `picVerticalAlignment` | перечисление | `Top`, `Center`, `Bottom` |
|
||||
| `textPosition` | перечисление | Положение текста относительно картинки: `Auto`, `Top`, `Right`, `Bottom` |
|
||||
|
||||
### Чего в стиле нет
|
||||
|
||||
- `width` — свойство колонки, задаётся через `columnWidths`;
|
||||
- `height` — свойство строки, задаётся ключом `height` у строки;
|
||||
- `fillType` — выводится из того, каким ключом задано содержимое ячейки
|
||||
(`text` / `param` / `template`);
|
||||
- `containsValue`, `valueType`, `controlType` — свойства конкретной ячейки, а не общего
|
||||
оформления: задаются ключами ячейки `valueType` и `controlType`;
|
||||
- линия рисунка и её стороны — свойства рисунка: задаются его ключами `line` и `sides`
|
||||
(у ячейки такого свойства нет вовсе).
|
||||
|
||||
## Ограничения
|
||||
|
||||
Текущая версия не поддерживает:
|
||||
- Множественные наборы колонок (`columnsID`)
|
||||
- Области типа Columns / Rectangle
|
||||
- Рисунки (штрихкоды, картинки)
|
||||
- Фон ячеек
|
||||
DSL описывает не все конструкции табличного документа. Перечисленное ниже **теряется при
|
||||
round-trip** (`/mxl-decompile` → `/mxl-compile`): в JSON оно не попадает, в сгенерированный
|
||||
XML не возвращается.
|
||||
|
||||
- объединения, не привязанные к ячейке (по всей высоте или ширине документа);
|
||||
- настройки диаграмм (`Chart`, `GanttChart`) — сам рисунок сохраняется, его содержимое нет;
|
||||
- область печати.
|
||||
|
||||
Пересборка макета из DSL — это полная перегенерация, а не точечная правка XML, поэтому
|
||||
diff после round-trip обычно шире фактической доработки.
|
||||
|
||||
Отдельно про побайтовое совпадение. В макетах, которые долго правили в Конфигураторе,
|
||||
встречаются следы прежних состояний: формат ячейки может нести ширину колонки, которая с тех
|
||||
пор изменилась. Такие значения не описывают итоговый документ и из него не выводятся, поэтому
|
||||
собранный XML совпадёт с исходным не всегда — при полностью сохранённом содержании.
|
||||
|
||||
+52
-2
@@ -21,6 +21,21 @@ node tests/skills/runner.mjs --help # полный
|
||||
|
||||
Exit code: 0 = все прошли, 1 = есть падения.
|
||||
|
||||
### Сколько кейсов должно быть пропущено
|
||||
|
||||
`○ skipped` — норма, а не падение: кейс может гейтиться по ОС (`osOnly`), по порту
|
||||
(`runtimeOnly`) или требовать внешней выгрузки (`setup: external:`), которой на машине нет.
|
||||
Но само число ни о чём не говорит, пока не с чем сверить, а запоминать его нельзя — оно растёт
|
||||
с набором кейсов.
|
||||
|
||||
```bash
|
||||
node tests/skills/expected-skips.mjs --runtime python # ожидаемое число для этой ОС и порта
|
||||
node tests/skills/expected-skips.mjs --list # + поимённо, с причиной
|
||||
```
|
||||
|
||||
Совпало со `Skipped: N` из прогона — норма. Разошлось — разбираться: появился новый источник
|
||||
скипа либо кейс скипается не по той причине, что заявлена.
|
||||
|
||||
### Платформенная верификация снапшотов
|
||||
|
||||
```bash
|
||||
@@ -49,6 +64,7 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
||||
| `check-uuid-invariant.mjs` | uuid объекта и сущностей не перегенерируются при правке |
|
||||
| `check-inline-drift.mjs` | копии общих inline-утилит совпадают с эталонами своих вариантов |
|
||||
| `check-type-maps.mjs` | карты типов метаданных согласованы с таблицей `ChildObjects` из `docs/1c-configuration-spec.md` |
|
||||
| `check-format-versions.mjs` | проверенный диапазон версий формата: границы одинаковы во всех навыках и сходятся с лестницей из `docs/1c-configuration-spec.md` |
|
||||
|
||||
`check-inline-drift.mjs` держит реестр семей внутри себя: у каждой семьи перечислены варианты, у
|
||||
варианта — навык-эталон и список копий. Отклоняющийся вариант обязан иметь обоснование (`why`),
|
||||
@@ -57,6 +73,13 @@ node tests/skills/check-inline-drift.mjs --list # реестр: семья →
|
||||
|
||||
Правишь общую утилиту — правишь эталон, копируешь во всех потребителей варианта, прогоняешь гард.
|
||||
|
||||
`check-format-versions.mjs` держит границы проверенного диапазона версий формата выгрузки. Раньше
|
||||
допустимый список версий был независимым литералом в каждом валидаторе, и сверять его было не с
|
||||
чем: волна 2.21 прошла по четырём валидаторам и молча обошла пятый — `form-validate` остался на
|
||||
2.17–2.20 и ругался на форму, которую сам же создавал (issue #63). Эталон верхней границы — последняя
|
||||
**замеренная** ступень таблицы «Лестница версий» из спецификации, поэтому расхождение спеки и кода
|
||||
падает здесь, а не на чужой выгрузке.
|
||||
|
||||
`check-type-maps.mjs` сверяет словари типов метаданных с таблицей «Порядок типов в ChildObjects»
|
||||
из спецификации: имя типа, каталог и порядок обязаны совпадать, а тип, которого в карте нет
|
||||
намеренно, объявляется исключением **с причиной**. Так проверка отличает намеренное ограничение
|
||||
@@ -260,24 +283,41 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
|
||||
| `args_extra` | нет | Массив дополнительных CLI-аргументов |
|
||||
| `preRun` | нет | Массив шагов подготовки (см. ниже) |
|
||||
| `inputFrom` | нет | Имя файла в `workDir` — вход берётся из него, а не из `input`. Нужно, когда вход производит `preRun` (напр. декомпилятор): `input` пишется ПОСЛЕ preRun и затёр бы его |
|
||||
| `expect` | нет | Дополнительные проверки (см. ниже) |
|
||||
| `expectError` | нет | `true` или строка — ожидается ошибка |
|
||||
| `noSnapshot` | нет | Непустая строка с причиной — кейс объявляет, что эталон не нужен (см. «Эталоны») |
|
||||
| `idempotent` | нет | `true` — повторный прогон с теми же аргументами должен дать байт-в-байт тот же `workDir` |
|
||||
| `runtimeOnly` | нет | `"powershell"` / `"python"` — кейс имеет смысл только на одном порте, на другом `○ skipped` |
|
||||
| `osOnly` | нет | `"win32"` / `"darwin"` / `"linux"` — кейс работает только на этой ОС (напр. фейк платформы написан как `.cmd`), на других `○ skipped` |
|
||||
| `osOnly` | нет | `"win32"` / `"darwin"` / `"linux"` — кейс работает только на этой ОС (напр. фейк платформы написан как `.cmd`), на других `○ skipped`. Принимает и массив: `["darwin","linux"]` |
|
||||
| `cwd` | нет | `"workDir"` — запустить навык из рабочего каталога (нужно, если кейс кладёт туда `.v8-project.json`) |
|
||||
| `skipValidation` | нет | `true` — не запускать `postValidate` из `_skill.json` (только при `--with-validation`) |
|
||||
| `skipPlatformVerify` | нет | Непустая строка с причиной — `verify-snapshots.mjs` пропускает кейс. Нужен, когда фикстура не является конфигурацией, которую можно загрузить (напр. фрагмент расширения) |
|
||||
|
||||
### Ключи `expect`
|
||||
|
||||
| Ключ | Описание |
|
||||
|---|---|
|
||||
| `files` | Массив путей относительно `workDir` — каждый должен существовать после прогона |
|
||||
| `filesAbsent` | Массив путей относительно `workDir` — ни одного не должно существовать. Работает и у негативного кейса: «отказ произошёл ДО записи» иначе проверить нечем |
|
||||
| `stdoutContains` | Строка или массив строк — все должны присутствовать в stdout |
|
||||
| `stdoutNotContains` | Строка или массив строк — ни одной не должно быть в stdout |
|
||||
| `stderrContains` | Строка или массив строк — все должны присутствовать в stderr. Для предупреждений: навык печатает их в stderr и продолжает, поэтому `expectError` тут не годится |
|
||||
| `fileContains` | Объект (или массив) `{ file, text }` — `file` относительно `workDir`, `text` строка или массив строк; все должны присутствовать |
|
||||
| `fileNotContains` | То же, но ни одной подстроки быть не должно |
|
||||
| `filesEqual` | Объект (или массив) `{ actual, expected }` — два пути относительно `workDir`, содержимое должно совпадать построчно |
|
||||
| `preserves` | Объект (или массив объектов) — байтовые свойства файла, которые навык обязан сохранить |
|
||||
|
||||
`fileContains` / `fileNotContains` читают файл **сырыми байтами**, минуя нормализацию снэпшота.
|
||||
Это единственный способ проверить то, что нормализация стирает: объявления `xmlns` (py-прогон их
|
||||
режет — снэпшот совпадёт при любом исходе), BOM, конкретный регистр. Для обычной проверки
|
||||
содержимого достаточно эталона.
|
||||
|
||||
Подстрока проверяется **буквально**, вместе с пробелами и переводами строк. Ожидание вида
|
||||
`"<CommandSet></CommandSet>"` не сработает на `<CommandSet>\r\n\t</CommandSet>` — оно всегда
|
||||
«проходит» и потому не проверяет ничего. Пустой элемент, вложенность, количество вхождений —
|
||||
это к эталону, а не к `fileNotContains`.
|
||||
|
||||
`preserves` проверяет то, что снэпшот-сравнение нормализует и потому увидеть не может:
|
||||
|
||||
| Ключ | Описание |
|
||||
@@ -307,7 +347,17 @@ ibcmd-проход автоматически `○ skipped`, если рядом
|
||||
| Форма шага | Описание |
|
||||
|---|---|
|
||||
| `{ "script": "<навык>/scripts/<файл>", "input": {...}, "args": { "-Flag": "{inputFile}" } }` | Прогон другого навыка для подготовки фикстуры. Плейсхолдеры: `{inputFile}`, `{workDir}` |
|
||||
| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON) |
|
||||
| `{ "writeFile": { "path": "<путь>", "content": "<строка или объект>" } }` | Записать произвольный файл в `workDir` (объект сериализуется в JSON). Ключ `"executable": true` ставит бит исполнения — нужен фейку платформы на *nix, где навык запускает её через `exec`; на Windows no-op |
|
||||
|
||||
Дополнительно у шага-скрипта: `"cwd": "{workDir}"` — запустить шаг из рабочего каталога. Нужно,
|
||||
когда относительный путь в `args` (напр. `-OutputPath: "Template.xml"`) должен лечь в фикстуру, а
|
||||
не в текущий каталог процесса. Записи различаются: у шага — `{workDir}` в скобках, у кейса —
|
||||
`"cwd": "workDir"` без них.
|
||||
|
||||
Оба раннера (`runner.mjs` и `verify-snapshots.mjs`) читают этот DSL **каждый своей реализацией**.
|
||||
Добавляя ключ, добавляй его в оба и в эту таблицу: недокументированный ключ, который понимает
|
||||
только один раннер, даёт тихую дыру — кейс зелёный в функциональном прогоне и не доезжает до
|
||||
платформы в верификации.
|
||||
|
||||
## Эталоны (snapshots)
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -241,7 +241,7 @@
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -241,7 +241,7 @@
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -241,7 +241,7 @@
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
@@ -241,7 +241,7 @@
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "CompatibilityMode DontUse: предупреждение, но конфигурация создаётся",
|
||||
"params": {
|
||||
"name": "КонфаDU"
|
||||
},
|
||||
"args_extra": [
|
||||
"-CompatibilityMode",
|
||||
"DontUse"
|
||||
],
|
||||
"expect": {
|
||||
"files": [
|
||||
"Configuration.xml"
|
||||
],
|
||||
"fileContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": "<CompatibilityMode>DontUse</CompatibilityMode>"
|
||||
},
|
||||
"stderrContains": "CompatibilityMode 'DontUse' is not"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"name": "Обычный режим совместимости: предупреждения про DontUse нет",
|
||||
"params": {
|
||||
"name": "КонфаОК"
|
||||
},
|
||||
"args_extra": [
|
||||
"-CompatibilityMode",
|
||||
"Version8_3_27",
|
||||
"-FormatVersion",
|
||||
"2.20"
|
||||
],
|
||||
"expect": {
|
||||
"fileContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": "<CompatibilityMode>Version8_3_27</CompatibilityMode>"
|
||||
},
|
||||
"stdoutNotContains": "DontUse"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "Формат 2.17: TextToSpeech не эмитится — на 8.3.24 тег ломает загрузку XDTO-ошибкой",
|
||||
"params": {
|
||||
"name": "Конфа217"
|
||||
},
|
||||
"args_extra": [
|
||||
"-FormatVersion",
|
||||
"2.17"
|
||||
],
|
||||
"expect": {
|
||||
"fileContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": "version=\"2.17\""
|
||||
},
|
||||
"fileNotContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": "TextToSpeech"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "Формат 2.18: TextToSpeech в мобильной функциональности (появился в 8.3.25, не в 8.5)",
|
||||
"params": {
|
||||
"name": "Конфа218"
|
||||
},
|
||||
"args_extra": [
|
||||
"-FormatVersion",
|
||||
"2.18",
|
||||
"-CompatibilityMode",
|
||||
"Version8_3_25"
|
||||
],
|
||||
"expect": {
|
||||
"fileContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": [
|
||||
"<app:functionality>TextToSpeech</app:functionality>",
|
||||
"version=\"2.18\""
|
||||
]
|
||||
},
|
||||
"fileNotContains": {
|
||||
"file": "Configuration.xml",
|
||||
"text": "xmlns:pal="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КонфаDU</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>КонфаDU</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>DontUse</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles/>
|
||||
<Vendor/>
|
||||
<Version/>
|
||||
<UpdateCatalogAddress/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries/>
|
||||
<CommonSettingsStorage/>
|
||||
<ReportsUserSettingsStorage/>
|
||||
<ReportsVariantsStorage/>
|
||||
<FormDataSettingsStorage/>
|
||||
<DynamicListsUserSettingsStorage/>
|
||||
<URLExternalDataStorage/>
|
||||
<Content/>
|
||||
<DefaultReportForm/>
|
||||
<DefaultReportVariantForm/>
|
||||
<DefaultReportSettingsForm/>
|
||||
<DefaultReportAppearanceTemplate/>
|
||||
<DefaultDynamicListSettingsForm/>
|
||||
<DefaultSearchForm/>
|
||||
<DefaultDataHistoryChangeHistoryForm/>
|
||||
<DefaultDataHistoryVersionDataForm/>
|
||||
<DefaultDataHistoryVersionDifferencesForm/>
|
||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||
<RequiredMobileApplicationPermissions/>
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles/>
|
||||
<MobileApplicationURLs/>
|
||||
<AllowedIncomingShareRequestTypes/>
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface/>
|
||||
<DefaultStyle/>
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation/>
|
||||
<DetailedInformation/>
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>DontUse</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,255 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>КонфаОК</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>КонфаОК</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_27</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles/>
|
||||
<Vendor/>
|
||||
<Version/>
|
||||
<UpdateCatalogAddress/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries/>
|
||||
<CommonSettingsStorage/>
|
||||
<ReportsUserSettingsStorage/>
|
||||
<ReportsVariantsStorage/>
|
||||
<FormDataSettingsStorage/>
|
||||
<DynamicListsUserSettingsStorage/>
|
||||
<URLExternalDataStorage/>
|
||||
<Content/>
|
||||
<DefaultReportForm/>
|
||||
<DefaultReportVariantForm/>
|
||||
<DefaultReportSettingsForm/>
|
||||
<DefaultReportAppearanceTemplate/>
|
||||
<DefaultDynamicListSettingsForm/>
|
||||
<DefaultSearchForm/>
|
||||
<DefaultDataHistoryChangeHistoryForm/>
|
||||
<DefaultDataHistoryVersionDataForm/>
|
||||
<DefaultDataHistoryVersionDifferencesForm/>
|
||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||
<RequiredMobileApplicationPermissions/>
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>TextToSpeech</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles/>
|
||||
<MobileApplicationURLs/>
|
||||
<AllowedIncomingShareRequestTypes/>
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface/>
|
||||
<DefaultStyle/>
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation/>
|
||||
<DetailedInformation/>
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_27</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.20">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,251 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Конфа217</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Конфа217</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles/>
|
||||
<Vendor/>
|
||||
<Version/>
|
||||
<UpdateCatalogAddress/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries/>
|
||||
<CommonSettingsStorage/>
|
||||
<ReportsUserSettingsStorage/>
|
||||
<ReportsVariantsStorage/>
|
||||
<FormDataSettingsStorage/>
|
||||
<DynamicListsUserSettingsStorage/>
|
||||
<URLExternalDataStorage/>
|
||||
<Content/>
|
||||
<DefaultReportForm/>
|
||||
<DefaultReportVariantForm/>
|
||||
<DefaultReportSettingsForm/>
|
||||
<DefaultReportAppearanceTemplate/>
|
||||
<DefaultDynamicListSettingsForm/>
|
||||
<DefaultSearchForm/>
|
||||
<DefaultDataHistoryChangeHistoryForm/>
|
||||
<DefaultDataHistoryVersionDataForm/>
|
||||
<DefaultDataHistoryVersionDifferencesForm/>
|
||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||
<RequiredMobileApplicationPermissions/>
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles/>
|
||||
<MobileApplicationURLs/>
|
||||
<AllowedIncomingShareRequestTypes/>
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface/>
|
||||
<DefaultStyle/>
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation/>
|
||||
<DetailedInformation/>
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,255 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.18">
|
||||
<Configuration uuid="UUID-001">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-002</xr:ClassId>
|
||||
<xr:ObjectId>UUID-003</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-004</xr:ClassId>
|
||||
<xr:ObjectId>UUID-005</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-006</xr:ClassId>
|
||||
<xr:ObjectId>UUID-007</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-008</xr:ClassId>
|
||||
<xr:ObjectId>UUID-009</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-010</xr:ClassId>
|
||||
<xr:ObjectId>UUID-011</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-012</xr:ClassId>
|
||||
<xr:ObjectId>UUID-013</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>UUID-014</xr:ClassId>
|
||||
<xr:ObjectId>UUID-015</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Конфа218</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Конфа218</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_25</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles/>
|
||||
<Vendor/>
|
||||
<Version/>
|
||||
<UpdateCatalogAddress/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries/>
|
||||
<CommonSettingsStorage/>
|
||||
<ReportsUserSettingsStorage/>
|
||||
<ReportsVariantsStorage/>
|
||||
<FormDataSettingsStorage/>
|
||||
<DynamicListsUserSettingsStorage/>
|
||||
<URLExternalDataStorage/>
|
||||
<Content/>
|
||||
<DefaultReportForm/>
|
||||
<DefaultReportVariantForm/>
|
||||
<DefaultReportSettingsForm/>
|
||||
<DefaultReportAppearanceTemplate/>
|
||||
<DefaultDynamicListSettingsForm/>
|
||||
<DefaultSearchForm/>
|
||||
<DefaultDataHistoryChangeHistoryForm/>
|
||||
<DefaultDataHistoryVersionDataForm/>
|
||||
<DefaultDataHistoryVersionDifferencesForm/>
|
||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||
<RequiredMobileApplicationPermissions/>
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>TextToSpeech</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles/>
|
||||
<MobileApplicationURLs/>
|
||||
<AllowedIncomingShareRequestTypes/>
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface/>
|
||||
<DefaultStyle/>
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation/>
|
||||
<DetailedInformation/>
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_25</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
|
||||
<top>
|
||||
<panel id="UUID-001">
|
||||
<uuid>UUID-002</uuid>
|
||||
</panel>
|
||||
</top>
|
||||
<left>
|
||||
<panel id="UUID-003">
|
||||
<uuid>UUID-004</uuid>
|
||||
</panel>
|
||||
</left>
|
||||
<panelDef id="UUID-004"/>
|
||||
<panelDef id="UUID-005"/>
|
||||
<panelDef id="UUID-006"/>
|
||||
<panelDef id="UUID-002"/>
|
||||
<panelDef id="UUID-007"/>
|
||||
</ClientApplicationInterface>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.18">
|
||||
<Language uuid="UUID-001">
|
||||
<Properties>
|
||||
<Name>Русский</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Русский</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<LanguageCode>ru</LanguageCode>
|
||||
</Properties>
|
||||
</Language>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"name": "Заимствование справочника с владельцем: Owners переносится, сам владелец заимствуется",
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Catalog", "name": "Договоры" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Catalog", "name": "Терминалы", "owners": ["Catalog.Договоры"] },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
},
|
||||
{
|
||||
"script": "cfe-init/scripts/cfe-init",
|
||||
"args": { "-Name": "Тест", "-OutputDir": "{workDir}/ext", "-ConfigPath": "{workDir}" }
|
||||
}
|
||||
],
|
||||
"params": { "extensionPath": "ext", "object": "Catalog.Терминалы" },
|
||||
"expect": {
|
||||
"fileContains": {
|
||||
"file": "ext/Catalogs/Терминалы.xml",
|
||||
"text": "<Owners><xr:Item xsi:type=\"xr:MDObjectRef\">Catalog.Договоры</xr:Item></Owners>"
|
||||
},
|
||||
"files": ["ext/Catalogs/Договоры.xml"]
|
||||
}
|
||||
}
|
||||
@@ -3,42 +3,106 @@
|
||||
"preRun": [
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Report", "name": "ОстаткиТоваров" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "Document",
|
||||
"name": "Накладная"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "DocumentJournal", "name": "Складские" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "Report",
|
||||
"name": "ОстаткиТоваров"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "Sequence", "name": "ПартионныйУчет" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "DocumentJournal",
|
||||
"name": "Складские",
|
||||
"registeredDocuments": [
|
||||
"Document.Накладная"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "HTTPService", "name": "ОбменДанными" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "Sequence",
|
||||
"name": "ПартионныйУчет",
|
||||
"documents": [
|
||||
"Document.Накладная"
|
||||
]
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "WebService", "name": "СервисОбмена" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "HTTPService",
|
||||
"name": "ОбменДанными"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "FilterCriterion", "name": "ПоКонтрагенту" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "WebService",
|
||||
"name": "СервисОбмена",
|
||||
"namespace": "http://example.org/СервисОбмена"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": { "type": "SettingsStorage", "name": "ХранилищеОтчетов" },
|
||||
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
|
||||
"input": {
|
||||
"type": "FilterCriterion",
|
||||
"name": "ПоКонтрагенту"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "meta-compile/scripts/meta-compile",
|
||||
"input": {
|
||||
"type": "SettingsStorage",
|
||||
"name": "ХранилищеОтчетов"
|
||||
},
|
||||
"args": {
|
||||
"-JsonPath": "{inputFile}",
|
||||
"-OutputDir": "{workDir}"
|
||||
}
|
||||
},
|
||||
{
|
||||
"script": "cfe-init/scripts/cfe-init",
|
||||
"args": { "-Name": "Тест", "-OutputDir": "{workDir}/ext", "-ConfigPath": "{workDir}" }
|
||||
"args": {
|
||||
"-Name": "Тест",
|
||||
"-OutputDir": "{workDir}/ext",
|
||||
"-ConfigPath": "{workDir}"
|
||||
}
|
||||
}
|
||||
],
|
||||
"params": {
|
||||
|
||||
@@ -0,0 +1,252 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Configuration uuid="f8dc6d3a-a3d4-4616-a2a4-c5e7d11378e6">
|
||||
<InternalInfo>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>9cd510cd-abfc-11d4-9434-004095e12fc7</xr:ClassId>
|
||||
<xr:ObjectId>bbe393d6-2b09-4eb4-8108-ffa213bd7b0c</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>9fcd25a0-4822-11d4-9414-008048da11f9</xr:ClassId>
|
||||
<xr:ObjectId>cbe8384b-2457-4cd1-9b91-d997d742f70c</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>e3687481-0a87-462c-a166-9f34594f9bba</xr:ClassId>
|
||||
<xr:ObjectId>2867dedd-bd83-4c4e-a874-60b4efdb9a49</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>9de14907-ec23-4a07-96f0-85521cb6b53b</xr:ClassId>
|
||||
<xr:ObjectId>b01c6621-d6ca-4f50-9b14-c75aeb1b8d64</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>51f2d5d8-ea4d-4064-8892-82951750031e</xr:ClassId>
|
||||
<xr:ObjectId>9dd3fa49-c088-4d5e-b45f-0c5d86c97960</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>e68182ea-4237-4383-967f-90c1e3370bc7</xr:ClassId>
|
||||
<xr:ObjectId>d15341ec-27b4-4847-aff3-7d8aef4e5076</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
<xr:ContainedObject>
|
||||
<xr:ClassId>fb282519-d103-4dd3-bc12-cb271d631dfc</xr:ClassId>
|
||||
<xr:ObjectId>af01c748-9b50-4545-8f07-44f1ae28dc04</xr:ObjectId>
|
||||
</xr:ContainedObject>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>TestConfig</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>TestConfig</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<NamePrefix/>
|
||||
<ConfigurationExtensionCompatibilityMode>Version8_3_24</ConfigurationExtensionCompatibilityMode>
|
||||
<DefaultRunMode>ManagedApplication</DefaultRunMode>
|
||||
<UsePurposes>
|
||||
<v8:Value xsi:type="app:ApplicationUsePurpose">PlatformApplication</v8:Value>
|
||||
</UsePurposes>
|
||||
<ScriptVariant>Russian</ScriptVariant>
|
||||
<DefaultRoles/>
|
||||
<Vendor/>
|
||||
<Version/>
|
||||
<UpdateCatalogAddress/>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<UseManagedFormInOrdinaryApplication>false</UseManagedFormInOrdinaryApplication>
|
||||
<UseOrdinaryFormInManagedApplication>false</UseOrdinaryFormInManagedApplication>
|
||||
<AdditionalFullTextSearchDictionaries/>
|
||||
<CommonSettingsStorage/>
|
||||
<ReportsUserSettingsStorage/>
|
||||
<ReportsVariantsStorage/>
|
||||
<FormDataSettingsStorage/>
|
||||
<DynamicListsUserSettingsStorage/>
|
||||
<URLExternalDataStorage/>
|
||||
<Content/>
|
||||
<DefaultReportForm/>
|
||||
<DefaultReportVariantForm/>
|
||||
<DefaultReportSettingsForm/>
|
||||
<DefaultReportAppearanceTemplate/>
|
||||
<DefaultDynamicListSettingsForm/>
|
||||
<DefaultSearchForm/>
|
||||
<DefaultDataHistoryChangeHistoryForm/>
|
||||
<DefaultDataHistoryVersionDataForm/>
|
||||
<DefaultDataHistoryVersionDifferencesForm/>
|
||||
<DefaultCollaborationSystemUsersChoiceForm/>
|
||||
<RequiredMobileApplicationPermissions/>
|
||||
<UsedMobileApplicationFunctionalities>
|
||||
<app:functionality>
|
||||
<app:functionality>Biometrics</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Location</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundLocation</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BluetoothPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>WiFiPrinters</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Contacts</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Calendars</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PushNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>LocalNotifications</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InAppPurchases</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PersonalComputerFileExchange</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Ads</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NumberDialing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>CallLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AutoSendSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ReceiveSMS</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SMSLog</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Camera</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Microphone</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>MusicLibrary</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>PictureAndVideoLibraries</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioPlaybackAndVibration</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>InstallPackages</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>OSBackup</app:functionality>
|
||||
<app:use>true</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>ApplicationUsageStatistics</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BarcodeScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>BackgroundAudioRecording</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllFilesAccess</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Videoconferences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>NFC</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>DocumentScanning</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>SpeechToText</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>Geofences</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>IncomingShareRequests</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
<app:functionality>
|
||||
<app:functionality>AllIncomingShareRequestsTypesProcessing</app:functionality>
|
||||
<app:use>false</app:use>
|
||||
</app:functionality>
|
||||
</UsedMobileApplicationFunctionalities>
|
||||
<StandaloneConfigurationRestrictionRoles/>
|
||||
<MobileApplicationURLs/>
|
||||
<AllowedIncomingShareRequestTypes/>
|
||||
<MainClientApplicationWindowMode>Normal</MainClientApplicationWindowMode>
|
||||
<DefaultInterface/>
|
||||
<DefaultStyle/>
|
||||
<DefaultLanguage>Language.Русский</DefaultLanguage>
|
||||
<BriefInformation/>
|
||||
<DetailedInformation/>
|
||||
<Copyright/>
|
||||
<VendorInformationAddress/>
|
||||
<ConfigurationInformationAddress/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<ObjectAutonumerationMode>NotAutoFree</ObjectAutonumerationMode>
|
||||
<ModalityUseMode>DontUse</ModalityUseMode>
|
||||
<SynchronousPlatformExtensionAndAddInCallUseMode>DontUse</SynchronousPlatformExtensionAndAddInCallUseMode>
|
||||
<InterfaceCompatibilityMode>TaxiEnableVersion8_2</InterfaceCompatibilityMode>
|
||||
<DatabaseTablespacesUseMode>DontUse</DatabaseTablespacesUseMode>
|
||||
<CompatibilityMode>Version8_3_24</CompatibilityMode>
|
||||
<DefaultConstantsForm/>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Language>Русский</Language>
|
||||
<Document>Расход</Document>
|
||||
</ChildObjects>
|
||||
</Configuration>
|
||||
</MetaDataObject>
|
||||
@@ -0,0 +1,265 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<MetaDataObject xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="2.17">
|
||||
<Document uuid="c7b141e8-5c6e-49de-b21f-6200a1df1b1f">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DocumentObject.Расход" category="Object">
|
||||
<xr:TypeId>eb27c20a-bab8-4763-88c7-28d0e4b84510</xr:TypeId>
|
||||
<xr:ValueId>bfcb76b5-226f-4f45-9f28-a7943c562569</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentRef.Расход" category="Ref">
|
||||
<xr:TypeId>13d09c16-3a64-4676-b614-174c17e07f87</xr:TypeId>
|
||||
<xr:ValueId>ce525519-655a-41b8-a643-91079f53adbe</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentSelection.Расход" category="Selection">
|
||||
<xr:TypeId>d3e4f4a8-bfcc-44c2-a638-aa9cbe7aebd9</xr:TypeId>
|
||||
<xr:ValueId>b8ed9db3-7f2e-4591-acb5-c24d14870680</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentList.Расход" category="List">
|
||||
<xr:TypeId>b44fa690-e696-481e-9e62-4b8c9d4fbdcd</xr:TypeId>
|
||||
<xr:ValueId>beb612e8-6065-43ef-a372-56e7a6764915</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentManager.Расход" category="Manager">
|
||||
<xr:TypeId>ea527740-0b46-4d9c-8ce7-8f2f8e2c1a80</xr:TypeId>
|
||||
<xr:ValueId>9113f260-017e-4cc4-83a0-f3f3b5636d7f</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Расход</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Расход</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<UseStandardCommands>true</UseStandardCommands>
|
||||
<Numerator/>
|
||||
<NumberType>String</NumberType>
|
||||
<NumberLength>11</NumberLength>
|
||||
<NumberAllowedLength>Variable</NumberAllowedLength>
|
||||
<NumberPeriodicity>Year</NumberPeriodicity>
|
||||
<CheckUnique>true</CheckUnique>
|
||||
<Autonumbering>true</Autonumbering>
|
||||
<Characteristics/>
|
||||
<BasedOn/>
|
||||
<InputByString>
|
||||
<xr:Field>Document.Расход.StandardAttribute.Number</xr:Field>
|
||||
</InputByString>
|
||||
<CreateOnInput>Use</CreateOnInput>
|
||||
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
|
||||
<FullTextSearchOnInputByString>DontUse</FullTextSearchOnInputByString>
|
||||
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
|
||||
<DefaultObjectForm>Document.Расход.Form.ФормаДокумента</DefaultObjectForm>
|
||||
<DefaultListForm/>
|
||||
<DefaultChoiceForm/>
|
||||
<AuxiliaryObjectForm/>
|
||||
<AuxiliaryListForm/>
|
||||
<AuxiliaryChoiceForm/>
|
||||
<Posting>Allow</Posting>
|
||||
<RealTimePosting>Deny</RealTimePosting>
|
||||
<RegisterRecordsDeletion>AutoDelete</RegisterRecordsDeletion>
|
||||
<RegisterRecordsWritingOnPost>WriteSelected</RegisterRecordsWritingOnPost>
|
||||
<SequenceFilling>AutoFill</SequenceFilling>
|
||||
<RegisterRecords/>
|
||||
<PostInPrivilegedMode>true</PostInPrivilegedMode>
|
||||
<UnpostInPrivilegedMode>true</UnpostInPrivilegedMode>
|
||||
<IncludeHelpInContents>false</IncludeHelpInContents>
|
||||
<DataLockFields/>
|
||||
<DataLockControlMode>Managed</DataLockControlMode>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<ObjectPresentation/>
|
||||
<ExtendedObjectPresentation/>
|
||||
<ListPresentation/>
|
||||
<ExtendedListPresentation/>
|
||||
<Explanation/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<DataHistory>DontUse</DataHistory>
|
||||
<UpdateDataHistoryImmediatelyAfterWrite>false</UpdateDataHistoryImmediatelyAfterWrite>
|
||||
<ExecuteAfterWriteDataHistoryVersionProcessing>false</ExecuteAfterWriteDataHistoryVersionProcessing>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="3d95bb61-b416-48e8-ad6b-7704aad7314a">
|
||||
<Properties>
|
||||
<Name>Комментарий</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Комментарий</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:string</v8:Type>
|
||||
<v8:StringQualifiers>
|
||||
<v8:Length>100</v8:Length>
|
||||
<v8:AllowedLength>Variable</v8:AllowedLength>
|
||||
</v8:StringQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillFromFillingValue>false</FillFromFillingValue>
|
||||
<FillValue xsi:type="xs:string"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
<Form>ФормаДокумента</Form>
|
||||
<TabularSection uuid="198783b6-78db-44de-96ee-3464dd2c974c">
|
||||
<InternalInfo>
|
||||
<xr:GeneratedType name="DocumentTabularSection.Расход.Товары" category="TabularSection">
|
||||
<xr:TypeId>383903b4-680c-4371-8b74-7b8a385f87a9</xr:TypeId>
|
||||
<xr:ValueId>2aaf40f3-841b-403e-8f92-187171f7155f</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
<xr:GeneratedType name="DocumentTabularSectionRow.Расход.Товары" category="TabularSectionRow">
|
||||
<xr:TypeId>8093107b-4b90-4aa6-be95-98a41606b985</xr:TypeId>
|
||||
<xr:ValueId>6bf19a8e-a966-4f89-9315-30a54b2b7c4f</xr:ValueId>
|
||||
</xr:GeneratedType>
|
||||
</InternalInfo>
|
||||
<Properties>
|
||||
<Name>Товары</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Товары</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<ToolTip/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<StandardAttributes>
|
||||
<xr:StandardAttribute name="LineNumber">
|
||||
<xr:LinkByType/>
|
||||
<xr:FillChecking>DontCheck</xr:FillChecking>
|
||||
<xr:MultiLine>false</xr:MultiLine>
|
||||
<xr:FillFromFillingValue>false</xr:FillFromFillingValue>
|
||||
<xr:CreateOnInput>Auto</xr:CreateOnInput>
|
||||
<xr:MaxValue xsi:nil="true"/>
|
||||
<xr:ToolTip/>
|
||||
<xr:ExtendedEdit>false</xr:ExtendedEdit>
|
||||
<xr:Format/>
|
||||
<xr:ChoiceForm/>
|
||||
<xr:QuickChoice>Auto</xr:QuickChoice>
|
||||
<xr:ChoiceHistoryOnInput>Auto</xr:ChoiceHistoryOnInput>
|
||||
<xr:EditFormat/>
|
||||
<xr:PasswordMode>false</xr:PasswordMode>
|
||||
<xr:DataHistory>Use</xr:DataHistory>
|
||||
<xr:MarkNegatives>false</xr:MarkNegatives>
|
||||
<xr:MinValue xsi:nil="true"/>
|
||||
<xr:Synonym/>
|
||||
<xr:Comment/>
|
||||
<xr:FullTextSearch>Use</xr:FullTextSearch>
|
||||
<xr:ChoiceParameterLinks/>
|
||||
<xr:FillValue xsi:nil="true"/>
|
||||
<xr:Mask/>
|
||||
<xr:ChoiceParameters/>
|
||||
</xr:StandardAttribute>
|
||||
</StandardAttributes>
|
||||
</Properties>
|
||||
<ChildObjects>
|
||||
<Attribute uuid="50e685c9-bb7e-4f1e-a8b6-26a8fc14da27">
|
||||
<Properties>
|
||||
<Name>Количество</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Количество</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:decimal</v8:Type>
|
||||
<v8:NumberQualifiers>
|
||||
<v8:Digits>10</v8:Digits>
|
||||
<v8:FractionDigits>3</v8:FractionDigits>
|
||||
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||
</v8:NumberQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
<Attribute uuid="b6da5fed-5f07-466f-b83e-4f0551e73b5a">
|
||||
<Properties>
|
||||
<Name>ИндексКартинки</Name>
|
||||
<Synonym>
|
||||
<v8:item>
|
||||
<v8:lang>ru</v8:lang>
|
||||
<v8:content>Индекс картинки</v8:content>
|
||||
</v8:item>
|
||||
</Synonym>
|
||||
<Comment/>
|
||||
<Type>
|
||||
<v8:Type>xs:decimal</v8:Type>
|
||||
<v8:NumberQualifiers>
|
||||
<v8:Digits>2</v8:Digits>
|
||||
<v8:FractionDigits>0</v8:FractionDigits>
|
||||
<v8:AllowedSign>Any</v8:AllowedSign>
|
||||
</v8:NumberQualifiers>
|
||||
</Type>
|
||||
<PasswordMode>false</PasswordMode>
|
||||
<Format/>
|
||||
<EditFormat/>
|
||||
<ToolTip/>
|
||||
<MarkNegatives>false</MarkNegatives>
|
||||
<Mask/>
|
||||
<MultiLine>false</MultiLine>
|
||||
<ExtendedEdit>false</ExtendedEdit>
|
||||
<MinValue xsi:nil="true"/>
|
||||
<MaxValue xsi:nil="true"/>
|
||||
<FillChecking>DontCheck</FillChecking>
|
||||
<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>
|
||||
<ChoiceParameterLinks/>
|
||||
<ChoiceParameters/>
|
||||
<QuickChoice>Auto</QuickChoice>
|
||||
<CreateOnInput>Auto</CreateOnInput>
|
||||
<ChoiceForm/>
|
||||
<LinkByType/>
|
||||
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
|
||||
<Indexing>DontIndex</Indexing>
|
||||
<FullTextSearch>Use</FullTextSearch>
|
||||
<DataHistory>Use</DataHistory>
|
||||
</Properties>
|
||||
</Attribute>
|
||||
</ChildObjects>
|
||||
</TabularSection>
|
||||
</ChildObjects>
|
||||
</Document>
|
||||
</MetaDataObject>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user