feat(meta-edit): таблица и функция добавляются в существующий внешний источник

Дыра в сценарии: добавить таблицу или функцию к уже существующему источнику было нечем.
Совет «пересоберите источник целиком через meta-compile» оказался вредным — повторная
компиляция заменяет файл источника, выдаёт объекту НОВЫЙ uuid и оставляет файлы не
упомянутых таблиц сиротами: в ChildObjects их уже нет, а на диске они остались.

meta-edit на файле источника принимает add.tables и add.functions. Таблица — единственная
операция навыка, создающая файл: <Источник>/Tables/<Имя>.xml плюс имя в ChildObjects.
Синтаксис тот же, что у meta-compile.

Формат файла таблицы обязан быть один и тот же, кем бы файл ни был создан, поэтому эмиттеры
скопированы из meta-compile механически и зарегистрированы в check-inline-drift.mjs — гард
теперь сверяет семь функций между двумя навыками. Чтобы копии не тянули за собой пол-navыка,
две из них развязаны от эмиттеров-специфик: Build-EdsTableXml принимает готовый XML полей,
Emit-EdsFunction — готовый XML типа. Каждый навык рендерит их своим эмиттером, вывод не изменился.

meta-compile теперь предупреждает о перезаписи существующего объекта — для всех видов, не только
внешних источников: молчаливая замена uuid ломает ссылки, а узнать об этом было неоткуда.

Раундтрип на платформе 8.3.24.1691: таблица, добавленная через meta-edit, возвращается из
выгрузки байт в байт.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
Nick Shirokov
2026-09-06 13:55:01 +03:00
co-authored by Claude Opus 5
parent ed766154fe
commit 123d6e326b
14 changed files with 1603 additions and 46 deletions
@@ -122,6 +122,21 @@
}
```
## Добавить в существующий источник
`meta-compile` описывает источник **целиком**: повторный запуск заменяет его файл и выдаёт новый
uuid, а таблицы, которых нет в описании, останутся на диске сиротами. Чтобы дописать таблицу или
функцию в уже существующий источник, есть `meta-edit`:
```json
{ "add": {
"tables": { "sales": { "keyFields": ["id"], "fields": ["id: Number(10,0)", "summa: Number(15,2)"] } },
"functions": { "nextKey": "NEXT VALUE FOR public.seq_key" }
} }
```
Удалить таблицу — `meta-remove ExternalDataSource.<Источник>.Table.<Таблица>`.
## Не поддерживается
- **Кубы OLAP** (`Cube`, `DimensionTable`, `Dimension`, `Resource`).
@@ -1,4 +1,4 @@
# meta-compile v1.105 — Compile 1C metadata object from JSON
# meta-compile v1.106 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -4411,7 +4411,9 @@ function Emit-ExternalDataSourceProperties {
# Функция внешнего источника. Параметров как объектов метаданных нет: они записаны прямо
# в выражении как &1, &2 (см. reference/external-data-source.md).
function Emit-EdsFunction {
param([string]$indent, [string]$fnName, $val)
# $typeXml — уже собранный узел <Type> возвращаемого значения: его рендерит вызывающий навык
# своим эмиттером типов. Так тело функции не зависит от того, какой это навык.
param([string]$indent, [string]$fnName, $val, [string]$typeXml)
$expr = ""
$returns = ""
$returnValue = $true
@@ -4437,8 +4439,8 @@ function Emit-EdsFunction {
Emit-MLText "$indent`t`t" "Synonym" $fnSynonym
if ($fnComment) { X "$indent`t`t<Comment>$(Esc-XmlText $fnComment)</Comment>" } else { X "$indent`t`t<Comment/>" }
X "$indent`t`t<ReturnValue>$(if ($returnValue) { 'true' } else { 'false' })</ReturnValue>"
if ($returnValue) {
Emit-ValueType "$indent`t`t" $(if ($returns) { $returns } else { "String" })
if ($returnValue -and $typeXml) {
X $typeXml.TrimEnd("`r", "`n")
} else {
X "$indent`t`t<Type/>"
}
@@ -4474,10 +4476,7 @@ function Emit-EdsTableProperties {
# ВАЖНО: платформа при загрузке XML сбрасывает заданное значение в пустую строку — проверено
# на её собственной выгрузке. Задать его можно только интерактивно, поэтому дефолт у таблицы
# с полем родителя — пустая строка (как после загрузки), а без него — nil.
$upv = if ($t) { $t.unfilledParentValue } else { $null }
$hasParent = ($t -and $t.parentField)
if ($null -ne $upv) { Emit-MinMaxValue $i "UnfilledParentValue" $upv }
elseif ($hasParent) { X "$i<UnfilledParentValue xsi:type=`"xs:string`"/>" }
if ($t -and $t.parentField) { X "$i<UnfilledParentValue xsi:type=`"xs:string`"/>" }
else { X "$i<UnfilledParentValue xsi:nil=`"true`"/>" }
Emit-Characteristics $i $(if ($t) { $t.characteristics } else { $null })
@@ -4516,7 +4515,9 @@ function Emit-EdsTableProperties {
# поэтому «перехват» — запомнить длину, отдать эмиттерам, вырезать добавленное
# (тот же приём, что у составного типа).
function Build-EdsTableXml {
param([string]$srcName, [string]$tableName, $entry)
# $fieldsXml — уже собранные узлы <Field>: их рендерит вызывающий навык своим эмиттером
# реквизита. Так тело функции не зависит от того, какой это навык.
param([string]$srcName, [string]$tableName, $entry, [string]$fieldsXml)
$before = $script:xml.Length
$tableUuid = New-Guid-String
@@ -4546,13 +4547,9 @@ function Build-EdsTableXml {
Emit-EdsTableProperties "`t`t`t" $srcName $tableName $entry.props
X "`t`t</Properties>"
$fields = @($entry.fields)
if ($fields.Count -gt 0) {
if ($fieldsXml) {
X "`t`t<ChildObjects>"
foreach ($f in $fields) {
$parsed = Parse-AttributeShorthand $f
Emit-Attribute "`t`t`t" $parsed "eds-field" "Field"
}
X $fieldsXml.TrimEnd("`r", "`n")
X "`t`t</ChildObjects>"
} else {
X "`t`t<ChildObjects/>"
@@ -5021,7 +5018,19 @@ if ($objType -eq "ExternalDataSource") {
X "`t`t`t<Table>$(Esc-XmlText $tblName)</Table>"
}
foreach ($fnName in $functions.Keys) {
Emit-EdsFunction "`t`t`t" $fnName $functions[$fnName]
$fnVal = $functions[$fnName]
$fnReturns = if ($fnVal -is [string]) { "String" }
elseif ($fnVal.returns) { "$($fnVal.returns)" }
elseif ($fnVal.returnType) { "$($fnVal.returnType)" } else { "String" }
$fnNoValue = (-not ($fnVal -is [string])) -and ($null -ne $fnVal.returnValue) -and ($fnVal.returnValue -ne $true)
$fnTypeXml = ""
if (-not $fnNoValue) {
$typeBefore = $script:xml.Length
Emit-ValueType "`t`t`t`t`t" $fnReturns
$fnTypeXml = $script:xml.ToString($typeBefore, $script:xml.Length - $typeBefore)
[void]$script:xml.Remove($typeBefore, $script:xml.Length - $typeBefore)
}
Emit-EdsFunction "`t`t`t" $fnName $fnVal $fnTypeXml
}
X "`t`t</ChildObjects>"
} else {
@@ -5310,6 +5319,13 @@ function Write-XmlFileKeepEol([string]$path, [string]$text, $encoding) {
[System.IO.File]::WriteAllText($path, $text.TrimEnd("`r", "`n"), $encoding)
}
# Объект с таким именем уже есть: компиляция заменит его файл ЦЕЛИКОМ и выдаст новый uuid —
# ссылки на прежний объект (из кода, состава подсистем, типов реквизитов) станут висячими.
# Для доработки существующего объекта есть meta-edit; молчать об этом нельзя.
if (Test-Path $mainXmlPath) {
Write-Warning "$objType '$objName' уже существует ($typePlural/$objName.xml) — файл будет перезаписан, объект получит НОВЫЙ uuid, ссылки на прежний сломаются. Для правки существующего объекта используйте meta-edit."
}
Write-XmlFileKeepEol $mainXmlPath $metadataXml $enc
# Таблицы внешнего источника — отдельными файлами в <Источник>/Tables/.
@@ -5319,7 +5335,14 @@ if ($objType -eq "ExternalDataSource" -and $script:edsTables.Count -gt 0) {
$tablesDir = Join-Path $objSubDir "Tables"
if (-not (Test-Path $tablesDir)) { New-Item -ItemType Directory -Path $tablesDir -Force | Out-Null }
foreach ($tblName in $script:edsTables.Keys) {
$tableXml = Build-EdsTableXml $objName $tblName $script:edsTables[$tblName]
$entry = $script:edsTables[$tblName]
$fieldsBefore = $script:xml.Length
foreach ($f in @($entry.fields)) {
Emit-Attribute "`t`t`t" (Parse-AttributeShorthand $f) "eds-field" "Field"
}
$fieldsXml = $script:xml.ToString($fieldsBefore, $script:xml.Length - $fieldsBefore)
[void]$script:xml.Remove($fieldsBefore, $script:xml.Length - $fieldsBefore)
$tableXml = Build-EdsTableXml $objName $tblName $entry $fieldsXml
$tablePath = Join-Path $tablesDir "$tblName.xml"
Write-XmlFileKeepEol $tablePath $tableXml $enc
$edsTablesCreated += $tablePath
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.105 — Compile 1C metadata object from JSON
# meta-compile v1.106 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -4403,9 +4403,12 @@ def emit_external_data_source_properties(indent):
X(f'{i}<DataLockControlMode>{dlcm}</DataLockControlMode>')
def emit_eds_function(indent, fn_name, val):
def emit_eds_function(indent, fn_name, val, type_xml):
"""Функция внешнего источника. Параметров как объектов метаданных нет: они записаны прямо
в выражении как &1, &2 (см. reference/external-data-source.md)."""
в выражении как &1, &2 (см. reference/external-data-source.md).
type_xml уже собранный узел <Type> возвращаемого значения: его рендерит вызывающий навык
своим эмиттером типов. Так тело функции не зависит от того, какой это навык."""
fn_synonym = None
fn_comment = ''
returns = ''
@@ -4432,8 +4435,8 @@ def emit_eds_function(indent, fn_name, val):
else:
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<ReturnValue>{"true" if return_value else "false"}</ReturnValue>')
if return_value:
emit_value_type(f'{indent}\t\t', returns or 'String')
if return_value and type_xml:
X(type_xml.rstrip('\r\n'))
else:
X(f'{indent}\t\t<Type/>')
X(f'{indent}\t\t<ExpressionInDataSource>{esc_xml_text(expr)}</ExpressionInDataSource>')
@@ -4475,10 +4478,7 @@ def emit_eds_table_properties(indent, src_name, table_name, t):
# ВАЖНО: платформа при загрузке XML сбрасывает заданное значение в пустую строку — проверено
# на её собственной выгрузке. Задать его можно только интерактивно, поэтому дефолт у таблицы
# с полем родителя — пустая строка (как после загрузки), а без него — nil.
upv = t.get('unfilledParentValue')
if upv is not None:
emit_min_max_value(i, 'UnfilledParentValue', upv)
elif t.get('parentField'):
if t.get('parentField'):
X(f'{i}<UnfilledParentValue xsi:type="xs:string"/>')
else:
X(f'{i}<UnfilledParentValue xsi:nil="true"/>')
@@ -4529,8 +4529,10 @@ EDS_TABLE_GENERATED_TYPES = (
)
def build_eds_table_xml(src_name, table_name, entry):
"""Отдельный XML-документ таблицы. Возвращает строку: X пишет в общий список строк,
def build_eds_table_xml(src_name, table_name, entry, fields_xml):
"""Отдельный XML-документ таблицы. fields_xml — уже собранные узлы <Field>: их рендерит
вызывающий навык своим эмиттером реквизита, поэтому тело не зависит от того, какой это навык.
Возвращает строку: X пишет в общий список строк,
поэтому «перехват» запомнить длину, отдать эмиттерам, срезать добавленное
(в ps1-порте тот же приём выражен через StringBuilder различие рантаймов, не логики)."""
before = len(lines)
@@ -4552,11 +4554,9 @@ def build_eds_table_xml(src_name, table_name, entry):
emit_eds_table_properties('\t\t\t', src_name, table_name, entry['props'])
X('\t\t</Properties>')
fields = entry['fields']
if fields:
if fields_xml:
X('\t\t<ChildObjects>')
for f in fields:
emit_attribute('\t\t\t', parse_attribute_shorthand(f), 'eds-field', 'Field')
X(fields_xml.rstrip('\r\n'))
X('\t\t</ChildObjects>')
else:
X('\t\t<ChildObjects/>')
@@ -4996,7 +4996,18 @@ if obj_type == 'ExternalDataSource':
for tbl_name in eds_tables:
X(f'\t\t\t<Table>{esc_xml_text(tbl_name)}</Table>')
for fn_name, fn_val in functions.items():
emit_eds_function('\t\t\t', fn_name, fn_val)
if isinstance(fn_val, str):
fn_returns, fn_no_value = 'String', False
else:
fn_returns = str(fn_val.get('returns') or fn_val.get('returnType') or 'String')
fn_no_value = fn_val.get('returnValue') is not None and fn_val.get('returnValue') is not True
fn_type_xml = ''
if not fn_no_value:
type_before = len(lines)
emit_value_type('\t\t\t\t\t', fn_returns)
fn_type_xml = '\r\n'.join(lines[type_before:])
del lines[type_before:]
emit_eds_function('\t\t\t', fn_name, fn_val, fn_type_xml)
X('\t\t</ChildObjects>')
else:
X('\t\t<ChildObjects/>')
@@ -5069,6 +5080,12 @@ os.makedirs(type_dir, exist_ok=True)
if obj_type not in types_no_sub_dir:
os.makedirs(obj_sub_dir, exist_ok=True)
# Объект с таким именем уже есть: компиляция заменит его файл ЦЕЛИКОМ и выдаст новый uuid —
# ссылки на прежний объект (из кода, состава подсистем, типов реквизитов) станут висячими.
# Для доработки существующего объекта есть meta-edit; молчать об этом нельзя.
if os.path.exists(main_xml_path):
print(f"WARNING: {obj_type} '{obj_name}' уже существует ({type_plural}/{obj_name}.xml) — файл будет перезаписан, объект получит НОВЫЙ uuid, ссылки на прежний сломаются. Для правки существующего объекта используйте meta-edit.", file=sys.stderr)
write_xml_file_keep_eol(main_xml_path, metadata_xml)
# Таблицы внешнего источника — отдельными файлами в <Источник>/Tables/.
@@ -5078,7 +5095,13 @@ if obj_type == 'ExternalDataSource' and eds_tables:
tables_dir = os.path.join(obj_sub_dir, 'Tables')
os.makedirs(tables_dir, exist_ok=True)
for tbl_name in eds_tables:
table_xml = build_eds_table_xml(obj_name, tbl_name, eds_tables[tbl_name])
entry = eds_tables[tbl_name]
fields_before = len(lines)
for f in entry['fields']:
emit_attribute('\t\t\t', parse_attribute_shorthand(f), 'eds-field', 'Field')
fields_xml = '\r\n'.join(lines[fields_before:])
del lines[fields_before:]
table_xml = build_eds_table_xml(obj_name, tbl_name, entry, fields_xml)
table_path = os.path.join(tables_dir, f'{tbl_name}.xml')
write_xml_file_keep_eol(table_path, table_xml)
eds_tables_created.append(table_path)
+15 -3
View File
@@ -55,9 +55,21 @@
] } }
```
Сам внешний источник точечно не правится: и таблица (отдельный файл), и функция (узел с полным
набором свойств) собираются `meta-compile` по описанию источника целиком. Удалить таблицу —
`meta-remove ExternalDataSource.<Источник>.Table.<Таблица>`.
## add-tables / add-functions (внешний источник данных)
На файле источника (`ExternalDataSources/<Имя>.xml`) добавляются таблицы и функции. Таблица
единственная операция навыка, создающая **файл**: `<Источник>/Tables/<Имя>.xml` плюс имя в
`ChildObjects` источника. Синтаксис таблицы тот же, что в `meta-compile`
(см. `reference/external-data-source.md` там же).
```json
{ "add": {
"tables": { "sales": { "keyFields": ["id"], "fields": ["id: Number(10,0)", "summa: Number(15,2)"] } },
"functions": { "nextKey": "NEXT VALUE FOR public.seq_key" }
} }
```
Удалить таблицу — `meta-remove ExternalDataSource.<Источник>.Table.<Таблица>`.
## add-ts
+372 -4
View File
@@ -1,4 +1,4 @@
# meta-edit v1.44 — Edit existing 1C metadata object XML
# meta-edit v1.45 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
[CmdletBinding(PositionalBinding=$false)]
param(
@@ -466,6 +466,8 @@ $script:childTypeSynonyms = @{
"commands" = "commands"; "команды" = "commands"
"properties" = "properties"; "свойства" = "properties"
"fields" = "fields"; "поля" = "fields"
"tables" = "tables"; "таблицы" = "tables"
"functions" = "functions"; "функции" = "functions"
}
# Type synonyms (from meta-compile)
@@ -1561,9 +1563,7 @@ $script:validChildTypes = @{
"CalculationRegister" = @("dimensions","resources","attributes","forms","templates","commands")
"DocumentJournal" = @("columns","forms","templates","commands")
"Constant" = @("forms")
# Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл),
# и функция (узел с полным набором свойств) требуют эмиттера, который живёт там.
"ExternalDataSource" = @()
"ExternalDataSource" = @("tables","functions")
"Table" = @("fields","forms","templates","commands")
}
@@ -1587,6 +1587,309 @@ $script:childTypeToXmlTag = @{
"templates" = "Template"
"commands" = "Command"
"fields" = "Field"
"tables" = "Table"
"functions" = "Function"
}
# ============================================================
# Section 8b: Внешние источники данных — копии из meta-compile
# ============================================================
# Тела ниже скопированы из meta-compile и обязаны совпадать с ним байт в байт:
# таблица внешнего источника собирается в ОТДЕЛЬНЫЙ файл, и формат этого файла
# должен быть один и тот же, кем бы он ни был создан. Держит check-inline-drift.mjs.
function Emit-FormRef {
param([string]$i, [string]$tag, $val)
if ($val) { X "$i<$tag>$(Esc-XmlText (Normalize-FormRef "$val"))</$tag>" } else { X "$i<$tag/>" }
}
# Шапка пространств имён файла таблицы внешнего источника — копия из meta-compile.
$script:xmlnsDecl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
$script:xml = New-Object System.Text.StringBuilder 32768
function X {
param([string]$text)
$script:xml.AppendLine($text) | Out-Null
}
function Emit-MLItems {
param([string]$indent, $val)
if ($val -is [System.Collections.IDictionary]) {
foreach ($k in $val.Keys) {
X "$indent<v8:item>"; X "$indent`t<v8:lang>$k</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$($val[$k])")</v8:content>"; X "$indent</v8:item>"
}
} elseif ($val -is [System.Management.Automation.PSCustomObject]) {
foreach ($p in $val.PSObject.Properties) {
X "$indent<v8:item>"; X "$indent`t<v8:lang>$($p.Name)</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$($p.Value)")</v8:content>"; X "$indent</v8:item>"
}
} else {
X "$indent<v8:item>"; X "$indent`t<v8:lang>ru</v8:lang>"; X "$indent`t<v8:content>$(Esc-XmlText "$val")</v8:content>"; X "$indent</v8:item>"
}
}
function Emit-MLText {
param([string]$indent, [string]$tag, $text)
# Пусто (null / пустая строка) → самозакрывающийся тег.
if (($null -eq $text) -or (($text -is [string]) -and ($text -eq ''))) {
X "$indent<$tag/>"
return
}
X "$indent<$tag>"
Emit-MLItems "$indent`t" $text
X "$indent</$tag>"
}
function Emit-Characteristics {
param([string]$indent, $chars)
if (-not $chars -or @($chars).Count -eq 0) { X "$indent<Characteristics/>"; return }
X "$indent<Characteristics>"
foreach ($ch in @($chars)) {
$types = Get-ChElProp $ch @('types','characteristicTypes','типы')
$values = Get-ChElProp $ch @('values','characteristicValues','значения')
$tFrom = Normalize-CharFrom "$(Get-ChElProp $types @('from','source','источник'))"
$vFrom = Normalize-CharFrom "$(Get-ChElProp $values @('from','source','источник'))"
$key = Expand-CharField "$(Get-ChElProp $types @('key','keyField'))" $tFrom
$tff = Expand-CharField "$(Get-ChElProp $types @('filterField','typesFilterField'))" $tFrom
$obj = Expand-CharField "$(Get-ChElProp $values @('object','objectField'))" $vFrom
$typ = Expand-CharField "$(Get-ChElProp $values @('type','typeField'))" $vFrom
$val = Expand-CharField "$(Get-ChElProp $values @('value','valueField'))" $vFrom
# числовые поля-флаги (обычно -1; иногда 0)
$dpf = Get-CharIntField $types @('dataPathField')
$mvu = Get-CharIntField $types @('multipleValuesUseField')
$mvk = Get-CharIntField $values @('multipleValuesKeyField')
$mvo = Get-CharIntField $values @('multipleValuesOrderField')
X "$indent`t<xr:Characteristic>"
X "$indent`t`t<xr:CharacteristicTypes from=`"$(Esc-Xml $tFrom)`">"
X "$indent`t`t`t<xr:KeyField>$(Esc-XmlText $key)</xr:KeyField>"
X "$indent`t`t`t<xr:TypesFilterField>$(Esc-XmlText $tff)</xr:TypesFilterField>"
# filterValue: $null→nil; голое→xs:string, полный путь→DTR, bool→xs:boolean.
$tfvRaw = Get-ChElProp $types @('filterValue','typesFilterValue')
if ($null -eq $tfvRaw) { X "$indent`t`t`t<xr:TypesFilterValue xsi:nil=`"true`"/>" }
else {
$tfvN = Normalize-ChoiceValue $tfvRaw
if ([string]::IsNullOrEmpty($tfvN.Text)) { X "$indent`t`t`t<xr:TypesFilterValue xsi:type=`"$($tfvN.XsiType)`"/>" }
else { X "$indent`t`t`t<xr:TypesFilterValue xsi:type=`"$($tfvN.XsiType)`">$(Esc-XmlText $tfvN.Text)</xr:TypesFilterValue>" }
}
# Числовое значение (обычно -1 или 0) — как есть; разворачивать через Expand-CharField нельзя,
# оно примет "0" за короткое имя поля и выдаст "<from>.Attribute.0".
$dpfOut = if ("$dpf" -match '^-?\d+$') { "$dpf" } else { Esc-XmlText (Expand-CharField "$dpf" $tFrom) }
X "$indent`t`t`t<xr:DataPathField>$dpfOut</xr:DataPathField>"
X "$indent`t`t`t<xr:MultipleValuesUseField>$mvu</xr:MultipleValuesUseField>"
X "$indent`t`t</xr:CharacteristicTypes>"
X "$indent`t`t<xr:CharacteristicValues from=`"$(Esc-Xml $vFrom)`">"
X "$indent`t`t`t<xr:ObjectField>$(Esc-XmlText $obj)</xr:ObjectField>"
X "$indent`t`t`t<xr:TypeField>$(Esc-XmlText $typ)</xr:TypeField>"
X "$indent`t`t`t<xr:ValueField>$(Esc-XmlText $val)</xr:ValueField>"
X "$indent`t`t`t<xr:MultipleValuesKeyField>$mvk</xr:MultipleValuesKeyField>"
X "$indent`t`t`t<xr:MultipleValuesOrderField>$mvo</xr:MultipleValuesOrderField>"
X "$indent`t`t</xr:CharacteristicValues>"
X "$indent`t</xr:Characteristic>"
}
X "$indent</Characteristics>"
}
function Emit-MDRefList {
param([string]$indent, [string]$tag, $items)
$arr = @(); if ($items) { $arr = @($items) }
if ($arr.Count -gt 0) {
X "$indent<$tag>"
foreach ($it in $arr) { X "$indent`t<xr:Item xsi:type=`"xr:MDObjectRef`">$(Esc-XmlText (Normalize-MDObjectRef "$it"))</xr:Item>" }
X "$indent</$tag>"
} else {
X "$indent<$tag/>"
}
}
function Get-EdsTables {
param($val)
$tables = [ordered]@{}
if (-not $val) { return $tables }
function New-EdsTableEntry { param($v)
if ($v -is [array] -or $v.GetType().Name -eq 'Object[]') {
return @{ props = $null; fields = @($v) }
}
$f = if ($null -ne $v.fields) { @($v.fields) } elseif ($null -ne $v.columns) { @($v.columns) } else { @() }
return @{ props = $v; fields = $f }
}
if ($val -is [array] -or $val.GetType().Name -eq 'Object[]') {
foreach ($t in $val) { $tables["$($t.name)"] = New-EdsTableEntry $t }
} else {
$val.PSObject.Properties | ForEach-Object { $tables[$_.Name] = New-EdsTableEntry $_.Value }
}
return $tables
}
function Get-EdsFieldRef {
param([string]$srcName, [string]$tableName, [string]$fieldName)
if (-not $fieldName) { return "" }
if ($fieldName -like "ExternalDataSource.*") { return $fieldName }
return "ExternalDataSource.$srcName.Table.$tableName.Field.$fieldName"
}
function Emit-EdsFieldRefList {
param([string]$indent, [string]$tag, $names, [string]$srcName, [string]$tableName)
$list = @($names | Where-Object { $_ })
if ($list.Count -eq 0) { X "$indent<$tag/>"; return }
X "$indent<$tag>"
foreach ($n in $list) {
X "$indent`t<xr:Field>$(Esc-XmlText (Get-EdsFieldRef $srcName $tableName "$n"))</xr:Field>"
}
X "$indent</$tag>"
}
function Emit-EdsFieldRefScalar {
param([string]$indent, [string]$tag, $name, [string]$srcName, [string]$tableName)
if (-not $name) { X "$indent<$tag/>"; return }
X "$indent<$tag>$(Esc-XmlText (Get-EdsFieldRef $srcName $tableName "$name"))</$tag>"
}
function Emit-EdsFunction {
# $typeXml — уже собранный узел <Type> возвращаемого значения: его рендерит вызывающий навык
# своим эмиттером типов. Так тело функции не зависит от того, какой это навык.
param([string]$indent, [string]$fnName, $val, [string]$typeXml)
$expr = ""
$returns = ""
$returnValue = $true
$fnSynonym = $null
$fnComment = ""
if ($val -is [string]) {
$expr = "$val"
} else {
$expr = if ($val.expression) { "$($val.expression)" } elseif ($val.expressionInDataSource) { "$($val.expressionInDataSource)" } else { "" }
$returns = if ($val.returns) { "$($val.returns)" } elseif ($val.returnType) { "$($val.returnType)" } else { "" }
if ($null -ne $val.returnValue) { $returnValue = ($val.returnValue -eq $true) }
$fnSynonym = $val.synonym
$fnComment = if ($val.comment) { "$($val.comment)" } else { "" }
}
if (-not $expr) {
Write-Error "Функция '$fnName' внешнего источника данных: не задано выражение (ключ expression)."
exit 1
}
$uuid = New-Guid-String
X "$indent<Function uuid=`"$uuid`">"
X "$indent`t<Properties>"
X "$indent`t`t<Name>$(Esc-XmlText $fnName)</Name>"
Emit-MLText "$indent`t`t" "Synonym" $fnSynonym
if ($fnComment) { X "$indent`t`t<Comment>$(Esc-XmlText $fnComment)</Comment>" } else { X "$indent`t`t<Comment/>" }
X "$indent`t`t<ReturnValue>$(if ($returnValue) { 'true' } else { 'false' })</ReturnValue>"
if ($returnValue -and $typeXml) {
X $typeXml.TrimEnd("`r", "`n")
} else {
X "$indent`t`t<Type/>"
}
X "$indent`t`t<ExpressionInDataSource>$(Esc-XmlText $expr)</ExpressionInDataSource>"
X "$indent`t</Properties>"
X "$indent</Function>"
}
function Emit-EdsTableProperties {
param([string]$indent, [string]$srcName, [string]$tableName, $t)
$i = $indent
$tblSynonym = if ($t -and $null -ne $t.synonym) { $t.synonym } else { Split-CamelCase $tableName }
X "$i<Name>$(Esc-XmlText $tableName)</Name>"
Emit-MLText $i "Synonym" $tblSynonym
if ($t -and $t.comment) { X "$i<Comment>$(Esc-XmlText "$($t.comment)")</Comment>" } else { X "$i<Comment/>" }
$tableType = if ($t -and $t.tableType) { "$($t.tableType)" } else { "Table" }
X "$i<TableType>$tableType</TableType>"
# Имя в источнике по умолчанию равно имени объекта — так поступает и платформа.
$nids = if ($t -and $t.nameInDataSource) { "$($t.nameInDataSource)" } elseif ($tableType -eq "Expression") { "" } else { $tableName }
if ($nids) { X "$i<NameInDataSource>$(Esc-XmlText $nids)</NameInDataSource>" } else { X "$i<NameInDataSource/>" }
$expr = if ($t -and $t.expressionInDataSource) { "$($t.expressionInDataSource)" } elseif ($t -and $t.expression) { "$($t.expression)" } else { "" }
if ($expr) { X "$i<ExpressionInDataSource>$(Esc-XmlText $expr)</ExpressionInDataSource>" } else { X "$i<ExpressionInDataSource/>" }
$dataType = if ($t -and $t.tableDataType) { "$($t.tableDataType)" } else { "NonobjectData" }
X "$i<TableDataType>$dataType</TableDataType>"
Emit-EdsFieldRefList $i "KeyFields" $(if ($t) { $t.keyFields } else { $null }) $srcName $tableName
Emit-EdsFieldRefScalar $i "PresentationField" $(if ($t) { $t.presentationField } else { $null }) $srcName $tableName
Emit-EdsFieldRefScalar $i "ParentField" $(if ($t) { $t.parentField } else { $null }) $srcName $tableName
# Признака незаполненного родителя отдельным узлом нет: NULL против «Заданного значения»
# различаются формой самого значения (xsi:nil против типизированного).
# ВАЖНО: платформа при загрузке XML сбрасывает заданное значение в пустую строку — проверено
# на её собственной выгрузке. Задать его можно только интерактивно, поэтому дефолт у таблицы
# с полем родителя — пустая строка (как после загрузки), а без него — nil.
if ($t -and $t.parentField) { X "$i<UnfilledParentValue xsi:type=`"xs:string`"/>" }
else { X "$i<UnfilledParentValue xsi:nil=`"true`"/>" }
Emit-Characteristics $i $(if ($t) { $t.characteristics } else { $null })
X "$i<UseStandardCommands>$(if ($t -and $t.useStandardCommands -eq $false) { 'false' } else { 'true' })</UseStandardCommands>"
X "$i<QuickChoice>$(if ($t -and $t.quickChoice -eq $true) { 'true' } else { 'false' })</QuickChoice>"
# Ввод по строке: ключа нет → выводим из поля представления (так делает платформа при загрузке).
# Явный список, в том числе пустой, уважаем как есть — отсюда presence-aware проверка.
$ibsGiven = ($t -and $t.PSObject -and $t.PSObject.Properties -and ($t.PSObject.Properties.Name -contains 'inputByString'))
$ibs = if ($ibsGiven) { $t.inputByString } elseif ($t -and $t.presentationField) { @($t.presentationField) } else { $null }
Emit-EdsFieldRefList $i "InputByString" $ibs $srcName $tableName
X "$i<CreateOnInput>$(if ($t -and $t.createOnInput) { "$($t.createOnInput)" } else { 'Auto' })</CreateOnInput>"
X "$i<SearchStringModeOnInputByString>$(if ($t -and $t.searchStringModeOnInputByString) { "$($t.searchStringModeOnInputByString)" } else { 'Begin' })</SearchStringModeOnInputByString>"
X "$i<ChoiceDataGetModeOnInputByString>$(if ($t -and $t.choiceDataGetModeOnInputByString) { "$($t.choiceDataGetModeOnInputByString)" } else { 'Directly' })</ChoiceDataGetModeOnInputByString>"
X "$i<ChoiceHistoryOnInput>$(if ($t -and $t.choiceHistoryOnInput) { "$($t.choiceHistoryOnInput)" } else { 'Auto' })</ChoiceHistoryOnInput>"
foreach ($formTag in @("DefaultObjectForm","DefaultRecordForm","DefaultListForm","DefaultChoiceForm")) {
$key = $formTag.Substring(0,1).ToLower() + $formTag.Substring(1)
Emit-FormRef $i $formTag $(if ($t) { $t.$key } else { $null })
}
foreach ($presTag in @("ObjectPresentation","ExtendedObjectPresentation","RecordPresentation",
"ExtendedRecordPresentation","ListPresentation","ExtendedListPresentation","Explanation")) {
$key = $presTag.Substring(0,1).ToLower() + $presTag.Substring(1)
Emit-MLText $i $presTag $(if ($t) { $t.$key } else { $null })
}
X "$i<IncludeHelpInContents>$(if ($t -and $t.includeHelpInContents -eq $true) { 'true' } else { 'false' })</IncludeHelpInContents>"
X "$i<ReadOnly>$(if ($t -and $t.readOnly -eq $true) { 'true' } else { 'false' })</ReadOnly>"
X "$i<TransactionsIsolationLevel>$(if ($t -and $t.transactionsIsolationLevel) { "$($t.transactionsIsolationLevel)" } else { 'Auto' })</TransactionsIsolationLevel>"
Emit-EdsFieldRefScalar $i "DataVersionField" $(if ($t) { $t.dataVersionField } else { $null }) $srcName $tableName
X "$i<EditType>$(if ($t -and $t.editType) { "$($t.editType)" } else { 'InDialog' })</EditType>"
Emit-MDRefList $i "BasedOn" $(if ($t) { $t.basedOn } else { $null })
Emit-EdsFieldRefList $i "DataLockFields" $(if ($t) { $t.dataLockFields } else { $null }) $srcName $tableName
X "$i<DataLockControlMode>$(if ($t -and $t.dataLockControlMode) { "$($t.dataLockControlMode)" } else { 'Automatic' })</DataLockControlMode>"
}
function Build-EdsTableXml {
# $fieldsXml — уже собранные узлы <Field>: их рендерит вызывающий навык своим эмиттером
# реквизита. Так тело функции не зависит от того, какой это навык.
param([string]$srcName, [string]$tableName, $entry, [string]$fieldsXml)
$before = $script:xml.Length
$tableUuid = New-Guid-String
X '<?xml version="1.0" encoding="UTF-8"?>'
X "<MetaDataObject $($script:xmlnsDecl) version=`"$($script:formatVersion)`">"
X "`t<Table uuid=`"$tableUuid`">"
# InternalInfo у таблицы эмитится здесь, а не через $script:generatedTypes: имя элемента
# трёхчастное (Префикс.Источник.Таблица), общая карта такой формы не знает.
X "`t`t<InternalInfo>"
foreach ($pair in @(
@("ExternalDataSourceTableManager", "Manager"),
@("ExternalDataSourceTableObject", "Object"),
@("ExternalDataSourceTableRef", "Ref"),
@("ExternalDataSourceTableList", "List"),
@("ExternalDataSourceTableRecord", "Record"),
@("ExternalDataSourceTableRecordSet", "RecordSet"),
@("ExternalDataSourceTableRecordKey", "RecordKey"),
@("ExternalDataSourceTableRecordManager", "RecordManager"))) {
X "`t`t`t<xr:GeneratedType name=`"$($pair[0]).$srcName.$tableName`" category=`"$($pair[1])`">"
X "`t`t`t`t<xr:TypeId>$(New-Guid-String)</xr:TypeId>"
X "`t`t`t`t<xr:ValueId>$(New-Guid-String)</xr:ValueId>"
X "`t`t`t</xr:GeneratedType>"
}
X "`t`t</InternalInfo>"
X "`t`t<Properties>"
Emit-EdsTableProperties "`t`t`t" $srcName $tableName $entry.props
X "`t`t</Properties>"
if ($fieldsXml) {
X "`t`t<ChildObjects>"
X $fieldsXml.TrimEnd("`r", "`n")
X "`t`t</ChildObjects>"
} else {
X "`t`t<ChildObjects/>"
}
X "`t</Table>"
X "</MetaDataObject>"
$chunk = $script:xml.ToString($before, $script:xml.Length - $before)
[void]$script:xml.Remove($before, $script:xml.Length - $before)
return $chunk
}
# ============================================================
@@ -1973,6 +2276,71 @@ function Process-Add($addDef) {
$existingNames[$parsed.name] = "Attribute"
}
}
"tables" {
# Таблица внешнего источника — ОТДЕЛЬНЫЙ файл рядом с файлом источника плюс имя
# в его ChildObjects. Единственная операция навыка, создающая файл: без неё
# добавить таблицу в существующий источник было нечем (пересборка источника
# целиком меняет его uuid и оставляет файлы выброшенных таблиц сиротами).
$srcDir = Join-Path (Split-Path -Parent $resolvedPath) $script:objName
$tablesDir = Join-Path $srcDir "Tables"
foreach ($entry in (Get-EdsTables $items).GetEnumerator()) {
$tblName = $entry.Key
if ($existingNames.ContainsKey($tblName)) {
Warn "Table '$tblName' already exists, skipping"
continue
}
$tablePath = Join-Path $tablesDir "$tblName.xml"
if (Test-Path $tablePath) {
Warn "Файл таблицы уже существует: $tablePath — пропускаю"
continue
}
$fieldParts = @()
foreach ($f in @($entry.Value.fields)) {
$fieldParts += Build-AttributeFragment (Parse-AttributeShorthand $f) "eds-field" "`t`t`t" "Field"
}
$fieldsXml = $fieldParts -join "`r`n"
$tableXml = Build-EdsTableXml $script:objName $tblName $entry.Value $fieldsXml
if (-not (Test-Path $tablesDir)) { New-Item -ItemType Directory -Path $tablesDir -Force | Out-Null }
[System.IO.File]::WriteAllText($tablePath, $tableXml.TrimEnd("`r", "`n"), (New-Object System.Text.UTF8Encoding($true)))
$fragmentXml = "$indent<Table>$(Esc-XmlText $tblName)</Table>"
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Table" @{ name = $tblName }
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added table: $tblName ($tablePath)"
$script:addCount++
$existingNames[$tblName] = "Table"
}
}
"functions" {
# Функция живёт узлом внутри файла источника — отдельного файла у неё нет.
foreach ($prop in $items.PSObject.Properties) {
$fnName = $prop.Name
if ($existingNames.ContainsKey($fnName)) {
Warn "Function '$fnName' already exists, skipping"
continue
}
$before = $script:xml.Length
$fnVal = $prop.Value
$fnReturns = if ($fnVal -is [string]) { "String" }
elseif ($fnVal.returns) { "$($fnVal.returns)" }
elseif ($fnVal.returnType) { "$($fnVal.returnType)" } else { "String" }
$fnNoValue = (-not ($fnVal -is [string])) -and ($null -ne $fnVal.returnValue) -and ($fnVal.returnValue -ne $true)
$fnTypeXml = if ($fnNoValue) { "" } else { Build-ValueTypeXml "$indent`t`t" $fnReturns }
Emit-EdsFunction $indent $fnName $fnVal $fnTypeXml
$fragmentXml = $script:xml.ToString($before, $script:xml.Length - $before)
[void]$script:xml.Remove($before, $script:xml.Length - $before)
$nodes = Import-Fragment $fragmentXml
$refNode = Find-InsertionPoint "Function" @{ name = $fnName }
foreach ($node in $nodes) {
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
}
Info "Added function: $fnName"
$script:addCount++
$existingNames[$fnName] = "Function"
}
}
"fields" {
# Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст.
foreach ($item in $items) {
+375 -5
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-edit v1.44 — Edit existing 1C metadata object XML
# meta-edit v1.45 — Edit existing 1C metadata object XML
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -322,6 +322,8 @@ CFG_NS = "http://v8.1c.ru/8.1/data/enterprise/current-config"
# Версия формата правимого файла — из его же корня. Нужна эмиттерам: часть свойств
# появилась в поздних версиях (напр. <Color> у значения перечисления — в 2.21).
is_format_221 = False
# Версия формата файла объекта: ею же помечается файл таблицы внешнего источника.
format_version = "2.17"
# Префикс current-config, объявленный в КОРНЕ правимого файла (у платформы — cfg).
# None = корень его не объявляет → эмиттер ссылочных типов остаётся на локальной форме.
cfg_prefix = None
@@ -504,6 +506,8 @@ child_type_synonyms = {
"commands": "commands", "команды": "commands",
"properties": "properties", "свойства": "properties",
"fields": "fields", "поля": "fields",
"tables": "tables", "таблицы": "tables",
"functions": "functions", "функции": "functions",
}
type_synonyms = {
@@ -1551,9 +1555,7 @@ valid_child_types = {
"CalculationRegister": ["dimensions", "resources", "attributes", "forms", "templates", "commands"],
"DocumentJournal": ["columns", "forms", "templates", "commands"],
"Constant": ["forms"],
# Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл),
# и функция (узел с полным набором свойств) требуют эмиттера, который живёт там.
"ExternalDataSource": [],
"ExternalDataSource": ["tables", "functions"],
"Table": ["fields", "forms", "templates", "commands"],
}
@@ -1577,8 +1579,320 @@ child_type_to_xml_tag = {
"templates": "Template",
"commands": "Command",
"fields": "Field",
"tables": "Table",
"functions": "Function",
}
# ============================================================
# Section 8b: Внешние источники данных — копии из meta-compile
# ============================================================
# Тела ниже скопированы из meta-compile и обязаны совпадать с ним байт в байт:
# таблица внешнего источника собирается в ОТДЕЛЬНЫЙ файл, и формат этого файла
# должен быть один и тот же, кем бы он ни был создан. Держит check-inline-drift.mjs.
xmlns_decl = 'xmlns="http://v8.1c.ru/8.3/MDClasses" xmlns:app="http://v8.1c.ru/8.2/managed-application/core" xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config" xmlns:cmi="http://v8.1c.ru/8.2/managed-application/cmi" xmlns:ent="http://v8.1c.ru/8.1/data/enterprise" xmlns:lf="http://v8.1c.ru/8.2/managed-application/logform" xmlns:style="http://v8.1c.ru/8.1/data/ui/style" xmlns:sys="http://v8.1c.ru/8.1/data/ui/fonts/system" xmlns:v8="http://v8.1c.ru/8.1/data/core" xmlns:v8ui="http://v8.1c.ru/8.1/data/ui" xmlns:web="http://v8.1c.ru/8.1/data/ui/colors/web" xmlns:win="http://v8.1c.ru/8.1/data/ui/colors/windows" xmlns:xen="http://v8.1c.ru/8.3/xcf/enums" xmlns:xpr="http://v8.1c.ru/8.3/xcf/predef" xmlns:xr="http://v8.1c.ru/8.3/xcf/readable" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"'
lines = []
def emit_form_ref(i, tag, val):
"""Ссылка на форму по умолчанию: непустая → <Tag>значение</Tag>, иначе <Tag/>."""
if val:
X(f'{i}<{tag}>{esc_xml_text(normalize_form_ref(str(val)))}</{tag}>')
else:
X(f'{i}<{tag}/>')
def X(text):
lines.append(text)
# ML-значение: строка → один <v8:item> ru; dict {lang: content} → item на язык (в порядке ключей).
def emit_ml_items(indent, val):
if isinstance(val, dict):
for k, v in val.items():
X(f'{indent}<v8:item>')
X(f'{indent}\t<v8:lang>{k}</v8:lang>')
X(f'{indent}\t<v8:content>{esc_xml_text(str(v))}</v8:content>')
X(f'{indent}</v8:item>')
else:
X(f'{indent}<v8:item>')
X(f'{indent}\t<v8:lang>ru</v8:lang>')
X(f'{indent}\t<v8:content>{esc_xml_text(str(val))}</v8:content>')
X(f'{indent}</v8:item>')
def emit_mltext(indent, tag, text):
# Пусто (None / '') → самозакрывающийся тег.
if text is None or (isinstance(text, str) and text == ''):
X(f'{indent}<{tag}/>')
return
X(f'{indent}<{tag}>')
emit_ml_items(f'{indent}\t', text)
X(f'{indent}</{tag}>')
def emit_characteristics(indent, chars):
if not chars:
X(f'{indent}<Characteristics/>')
return
X(f'{indent}<Characteristics>')
for ch in chars:
types = ch_el_prop(ch, ['types', 'characteristicTypes', 'типы'])
values = ch_el_prop(ch, ['values', 'characteristicValues', 'значения'])
t_from = normalize_char_from(ch_el_prop(types, ['from', 'source', 'источник']) or '')
v_from = normalize_char_from(ch_el_prop(values, ['from', 'source', 'источник']) or '')
key = expand_char_field(ch_el_prop(types, ['key', 'keyField']), t_from)
tff = expand_char_field(ch_el_prop(types, ['filterField', 'typesFilterField']), t_from)
obj = expand_char_field(ch_el_prop(values, ['object', 'objectField']), v_from)
typ = expand_char_field(ch_el_prop(values, ['type', 'typeField']), v_from)
val = expand_char_field(ch_el_prop(values, ['value', 'valueField']), v_from)
dpf = char_int_field(types, ['dataPathField'])
mvu = char_int_field(types, ['multipleValuesUseField'])
mvk = char_int_field(values, ['multipleValuesKeyField'])
mvo = char_int_field(values, ['multipleValuesOrderField'])
X(f'{indent}\t<xr:Characteristic>')
X(f'{indent}\t\t<xr:CharacteristicTypes from="{esc_xml(t_from)}">')
X(f'{indent}\t\t\t<xr:KeyField>{esc_xml_text(key)}</xr:KeyField>')
X(f'{indent}\t\t\t<xr:TypesFilterField>{esc_xml_text(tff)}</xr:TypesFilterField>')
# filterValue: None→nil; голое→xs:string, полный путь→DTR, bool→xs:boolean.
tfv_raw = ch_el_prop(types, ['filterValue', 'typesFilterValue'])
if tfv_raw is None:
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:nil="true"/>')
else:
tfv_xt, tfv_tx = normalize_choice_value(tfv_raw)
if tfv_tx == '' or tfv_tx is None:
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:type="{tfv_xt}"/>')
else:
X(f'{indent}\t\t\t<xr:TypesFilterValue xsi:type="{tfv_xt}">{esc_xml_text(tfv_tx)}</xr:TypesFilterValue>')
# Числовое значение (обычно -1 или 0) — как есть; expand_char_field примет "0" за короткое
# имя поля и выдаст "<from>.Attribute.0".
dpf_out = str(dpf) if re.fullmatch(r'-?\d+', str(dpf)) else esc_xml_text(expand_char_field(str(dpf), t_from))
X(f'{indent}\t\t\t<xr:DataPathField>{dpf_out}</xr:DataPathField>')
X(f'{indent}\t\t\t<xr:MultipleValuesUseField>{mvu}</xr:MultipleValuesUseField>')
X(f'{indent}\t\t</xr:CharacteristicTypes>')
X(f'{indent}\t\t<xr:CharacteristicValues from="{esc_xml(v_from)}">')
X(f'{indent}\t\t\t<xr:ObjectField>{esc_xml_text(obj)}</xr:ObjectField>')
X(f'{indent}\t\t\t<xr:TypeField>{esc_xml_text(typ)}</xr:TypeField>')
X(f'{indent}\t\t\t<xr:ValueField>{esc_xml_text(val)}</xr:ValueField>')
X(f'{indent}\t\t\t<xr:MultipleValuesKeyField>{mvk}</xr:MultipleValuesKeyField>')
X(f'{indent}\t\t\t<xr:MultipleValuesOrderField>{mvo}</xr:MultipleValuesOrderField>')
X(f'{indent}\t\t</xr:CharacteristicValues>')
X(f'{indent}\t</xr:Characteristic>')
X(f'{indent}</Characteristics>')
def emit_md_ref_list(indent, tag, items):
"""Список MDObjectRef (Documents/RegisterRecords/DocumentMap/…) с <xr:Item>. omit-on-empty."""
arr = list(items) if items else []
if arr:
X(f'{indent}<{tag}>')
for it in arr:
X(f'{indent}\t<xr:Item xsi:type="xr:MDObjectRef">{esc_xml_text(normalize_md_object_ref(str(it)))}</xr:Item>')
X(f'{indent}</{tag}>')
else:
X(f'{indent}<{tag}/>')
def get_eds_tables(val):
"""Таблицы: dict имя → массив полей ЛИБО объект со свойствами и ключом fields/columns."""
tables = {}
if not val:
return tables
def entry(v):
if isinstance(v, list):
return {'props': None, 'fields': list(v)}
f = v.get('fields') if v.get('fields') is not None else v.get('columns')
return {'props': v, 'fields': list(f) if f else []}
if isinstance(val, list):
for t in val:
tables[str(t.get('name'))] = entry(t)
else:
for k, v in val.items():
tables[k] = entry(v)
return tables
def get_eds_field_ref(src_name, table_name, field_name):
"""Ссылка на поле таблицы: в DSL короткое имя, в XML — полный путь."""
if not field_name:
return ''
if str(field_name).startswith('ExternalDataSource.'):
return str(field_name)
return f'ExternalDataSource.{src_name}.Table.{table_name}.Field.{field_name}'
def emit_eds_field_ref_list(indent, tag, names, src_name, table_name):
items = [n for n in (names or []) if n]
if not items:
X(f'{indent}<{tag}/>')
return
X(f'{indent}<{tag}>')
for n in items:
X(f'{indent}\t<xr:Field>{esc_xml_text(get_eds_field_ref(src_name, table_name, n))}</xr:Field>')
X(f'{indent}</{tag}>')
def emit_eds_field_ref_scalar(indent, tag, name, src_name, table_name):
if not name:
X(f'{indent}<{tag}/>')
return
X(f'{indent}<{tag}>{esc_xml_text(get_eds_field_ref(src_name, table_name, name))}</{tag}>')
def emit_eds_function(indent, fn_name, val, type_xml):
"""Функция внешнего источника. Параметров как объектов метаданных нет: они записаны прямо
в выражении как &1, &2 (см. reference/external-data-source.md).
type_xml — уже собранный узел <Type> возвращаемого значения: его рендерит вызывающий навык
своим эмиттером типов. Так тело функции не зависит от того, какой это навык."""
fn_synonym = None
fn_comment = ''
returns = ''
return_value = True
if isinstance(val, str):
expr = val
else:
expr = str(val.get('expression') or val.get('expressionInDataSource') or '')
returns = str(val.get('returns') or val.get('returnType') or '')
if val.get('returnValue') is not None:
return_value = val.get('returnValue') is True
fn_synonym = val.get('synonym')
fn_comment = str(val['comment']) if val.get('comment') else ''
if not expr:
print(f"ERROR: Функция '{fn_name}' внешнего источника данных: не задано выражение (ключ expression).",
file=sys.stderr)
sys.exit(1)
X(f'{indent}<Function uuid="{new_uuid()}">')
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml_text(fn_name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', fn_synonym)
if fn_comment:
X(f'{indent}\t\t<Comment>{esc_xml_text(fn_comment)}</Comment>')
else:
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<ReturnValue>{"true" if return_value else "false"}</ReturnValue>')
if return_value and type_xml:
X(type_xml.rstrip('\r\n'))
else:
X(f'{indent}\t\t<Type/>')
X(f'{indent}\t\t<ExpressionInDataSource>{esc_xml_text(expr)}</ExpressionInDataSource>')
X(f'{indent}\t</Properties>')
X(f'{indent}</Function>')
def emit_eds_table_properties(indent, src_name, table_name, t):
"""Свойства таблицы: 38 узлов в порядке выгрузки платформы."""
i = indent
t = t or {}
tbl_synonym = t['synonym'] if t.get('synonym') is not None else split_camel_case(table_name)
X(f'{i}<Name>{esc_xml_text(table_name)}</Name>')
emit_mltext(i, 'Synonym', tbl_synonym)
if t.get('comment'):
X(f'{i}<Comment>{esc_xml_text(str(t["comment"]))}</Comment>')
else:
X(f'{i}<Comment/>')
table_type = str(t.get('tableType') or 'Table')
X(f'{i}<TableType>{table_type}</TableType>')
# Имя в источнике по умолчанию равно имени объекта — так поступает и платформа.
if t.get('nameInDataSource'):
nids = str(t['nameInDataSource'])
elif table_type == 'Expression':
nids = ''
else:
nids = table_name
X(f'{i}<NameInDataSource>{esc_xml_text(nids)}</NameInDataSource>' if nids else f'{i}<NameInDataSource/>')
expr = str(t.get('expressionInDataSource') or t.get('expression') or '')
X(f'{i}<ExpressionInDataSource>{esc_xml_text(expr)}</ExpressionInDataSource>' if expr else f'{i}<ExpressionInDataSource/>')
X(f'{i}<TableDataType>{t.get("tableDataType") or "NonobjectData"}</TableDataType>')
emit_eds_field_ref_list(i, 'KeyFields', t.get('keyFields'), src_name, table_name)
emit_eds_field_ref_scalar(i, 'PresentationField', t.get('presentationField'), src_name, table_name)
emit_eds_field_ref_scalar(i, 'ParentField', t.get('parentField'), src_name, table_name)
# Признака незаполненного родителя отдельным узлом нет: NULL против «Заданного значения»
# различаются формой самого значения (xsi:nil против типизированного).
# ВАЖНО: платформа при загрузке XML сбрасывает заданное значение в пустую строку — проверено
# на её собственной выгрузке. Задать его можно только интерактивно, поэтому дефолт у таблицы
# с полем родителя — пустая строка (как после загрузки), а без него — nil.
if t.get('parentField'):
X(f'{i}<UnfilledParentValue xsi:type="xs:string"/>')
else:
X(f'{i}<UnfilledParentValue xsi:nil="true"/>')
emit_characteristics(i, t.get('characteristics'))
X(f'{i}<UseStandardCommands>{"false" if t.get("useStandardCommands") is False else "true"}</UseStandardCommands>')
X(f'{i}<QuickChoice>{"true" if t.get("quickChoice") is True else "false"}</QuickChoice>')
# Ввод по строке: ключа нет → выводим из поля представления (так делает платформа при загрузке).
# Явный список, в том числе пустой, уважаем как есть — отсюда presence-aware проверка.
if 'inputByString' in t:
ibs = t.get('inputByString')
elif t.get('presentationField'):
ibs = [t['presentationField']]
else:
ibs = None
emit_eds_field_ref_list(i, 'InputByString', ibs, src_name, table_name)
X(f'{i}<CreateOnInput>{t.get("createOnInput") or "Auto"}</CreateOnInput>')
X(f'{i}<SearchStringModeOnInputByString>{t.get("searchStringModeOnInputByString") or "Begin"}</SearchStringModeOnInputByString>')
X(f'{i}<ChoiceDataGetModeOnInputByString>{t.get("choiceDataGetModeOnInputByString") or "Directly"}</ChoiceDataGetModeOnInputByString>')
X(f'{i}<ChoiceHistoryOnInput>{t.get("choiceHistoryOnInput") or "Auto"}</ChoiceHistoryOnInput>')
for form_tag in ('DefaultObjectForm', 'DefaultRecordForm', 'DefaultListForm', 'DefaultChoiceForm'):
key = form_tag[0].lower() + form_tag[1:]
emit_form_ref(i, form_tag, t.get(key))
for pres_tag in ('ObjectPresentation', 'ExtendedObjectPresentation', 'RecordPresentation',
'ExtendedRecordPresentation', 'ListPresentation', 'ExtendedListPresentation', 'Explanation'):
key = pres_tag[0].lower() + pres_tag[1:]
emit_mltext(i, pres_tag, t.get(key))
X(f'{i}<IncludeHelpInContents>{"true" if t.get("includeHelpInContents") is True else "false"}</IncludeHelpInContents>')
X(f'{i}<ReadOnly>{"true" if t.get("readOnly") is True else "false"}</ReadOnly>')
X(f'{i}<TransactionsIsolationLevel>{t.get("transactionsIsolationLevel") or "Auto"}</TransactionsIsolationLevel>')
emit_eds_field_ref_scalar(i, 'DataVersionField', t.get('dataVersionField'), src_name, table_name)
X(f'{i}<EditType>{t.get("editType") or "InDialog"}</EditType>')
emit_md_ref_list(i, 'BasedOn', t.get('basedOn'))
emit_eds_field_ref_list(i, 'DataLockFields', t.get('dataLockFields'), src_name, table_name)
X(f'{i}<DataLockControlMode>{t.get("dataLockControlMode") or "Automatic"}</DataLockControlMode>')
EDS_TABLE_GENERATED_TYPES = (
('ExternalDataSourceTableManager', 'Manager'),
('ExternalDataSourceTableObject', 'Object'),
('ExternalDataSourceTableRef', 'Ref'),
('ExternalDataSourceTableList', 'List'),
('ExternalDataSourceTableRecord', 'Record'),
('ExternalDataSourceTableRecordSet', 'RecordSet'),
('ExternalDataSourceTableRecordKey', 'RecordKey'),
('ExternalDataSourceTableRecordManager', 'RecordManager'),
)
def build_eds_table_xml(src_name, table_name, entry, fields_xml):
"""Отдельный XML-документ таблицы. fields_xml — уже собранные узлы <Field>: их рендерит
вызывающий навык своим эмиттером реквизита, поэтому тело не зависит от того, какой это навык.
Возвращает строку: X пишет в общий список строк,
поэтому «перехват» — запомнить длину, отдать эмиттерам, срезать добавленное
(в ps1-порте тот же приём выражен через StringBuilder — различие рантаймов, не логики)."""
before = len(lines)
X('<?xml version="1.0" encoding="UTF-8"?>')
X(f'<MetaDataObject {xmlns_decl} version="{format_version}">')
X(f'\t<Table uuid="{new_uuid()}">')
# InternalInfo у таблицы эмитится здесь, а не через generated_types: имя элемента
# трёхчастное (Префикс.Источник.Таблица), общая карта такой формы не знает.
X('\t\t<InternalInfo>')
for prefix, category in EDS_TABLE_GENERATED_TYPES:
X(f'\t\t\t<xr:GeneratedType name="{prefix}.{src_name}.{table_name}" category="{category}">')
X(f'\t\t\t\t<xr:TypeId>{new_uuid()}</xr:TypeId>')
X(f'\t\t\t\t<xr:ValueId>{new_uuid()}</xr:ValueId>')
X('\t\t\t</xr:GeneratedType>')
X('\t\t</InternalInfo>')
X('\t\t<Properties>')
emit_eds_table_properties('\t\t\t', src_name, table_name, entry['props'])
X('\t\t</Properties>')
if fields_xml:
X('\t\t<ChildObjects>')
X(fields_xml.rstrip('\r\n'))
X('\t\t</ChildObjects>')
else:
X('\t\t<ChildObjects/>')
X('\t</Table>')
X('</MetaDataObject>')
chunk = '\r\n'.join(lines[before:])
del lines[before:]
return chunk
# ============================================================
# DSL key normalization
# ============================================================
@@ -1914,6 +2228,61 @@ def process_add(add_def):
add_count += 1
existing_names[parsed["name"]] = "Attribute"
elif child_type == "tables":
# Таблица внешнего источника — ОТДЕЛЬНЫЙ файл рядом с файлом источника плюс имя
# в его ChildObjects. Единственная операция навыка, создающая файл: без неё
# добавить таблицу в существующий источник было нечем (пересборка источника
# целиком меняет его uuid и оставляет файлы выброшенных таблиц сиротами).
src_dir = os.path.join(os.path.dirname(resolved_path), obj_name)
tables_dir = os.path.join(src_dir, "Tables")
for tbl_name, entry in get_eds_tables(items).items():
if tbl_name in existing_names:
warn(f"Table '{tbl_name}' already exists, skipping")
continue
table_path = os.path.join(tables_dir, f"{tbl_name}.xml")
if os.path.exists(table_path):
warn(f"\u0424\u0430\u0439\u043b \u0442\u0430\u0431\u043b\u0438\u0446\u044b \u0443\u0436\u0435 \u0441\u0443\u0449\u0435\u0441\u0442\u0432\u0443\u0435\u0442: {table_path} \u2014 \u043f\u0440\u043e\u043f\u0443\u0441\u043a\u0430\u044e")
continue
field_parts = []
for f in entry["fields"]:
field_parts.append(build_attribute_fragment(parse_attribute_shorthand(f), "eds-field", "\t\t\t", "Field"))
fields_xml = "\r\n".join(field_parts)
table_xml = build_eds_table_xml(obj_name, tbl_name, entry, fields_xml)
os.makedirs(tables_dir, exist_ok=True)
with open(table_path, "w", encoding="utf-8-sig", newline="") as fh:
fh.write(table_xml.rstrip("\r\n"))
nodes = import_fragment(f"{indent}<Table>{esc_xml_text(tbl_name)}</Table>")
ref_node = find_insertion_point("Table", {"name": tbl_name})
for node in nodes:
insert_before_element(child_objects_el, node, ref_node, indent)
info(f"Added table: {tbl_name} ({table_path})")
add_count += 1
existing_names[tbl_name] = "Table"
elif child_type == "functions":
# Функция живёт узлом внутри файла источника — отдельного файла у неё нет.
for fn_name, fn_val in items.items():
if fn_name in existing_names:
warn(f"Function '{fn_name}' already exists, skipping")
continue
if isinstance(fn_val, str):
fn_returns, fn_no_value = "String", False
else:
fn_returns = str(fn_val.get("returns") or fn_val.get("returnType") or "String")
fn_no_value = fn_val.get("returnValue") is not None and fn_val.get("returnValue") is not True
fn_type_xml = "" if fn_no_value else build_value_type_xml(f"{indent}\t\t", fn_returns)
before = len(lines)
emit_eds_function(indent, fn_name, fn_val, fn_type_xml)
fragment_xml = "\r\n".join(lines[before:])
del lines[before:]
nodes = import_fragment(fragment_xml)
ref_node = find_insertion_point("Function", {"name": fn_name})
for node in nodes:
insert_before_element(child_objects_el, node, ref_node, indent)
info(f"Added function: {fn_name}")
add_count += 1
existing_names[fn_name] = "Function"
elif child_type == "fields":
# Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст.
for item in items:
@@ -3399,11 +3768,12 @@ def main():
xml_root = xml_tree.getroot()
# Префикс current-config берём из объявлений корня — им и пишем ссылочные типы.
global cfg_prefix, is_format_221
global cfg_prefix, is_format_221, format_version
cfg_prefix = next((p for p, u in (xml_root.nsmap or {}).items() if u == CFG_NS and p), None)
_fv = xml_root.get("version") or "2.17"
_m = re.match(r'^(\d+)\.(\d+)$', _fv)
is_format_221 = bool(_m) and int(_m.group(1)) * 100 + int(_m.group(2)) >= 221
format_version = _fv
# --- Detect object type ---
if localname(xml_root) != "MetaDataObject":
@@ -0,0 +1,48 @@
{
"name": "Таблица и функция в существующий внешний источник",
"setup": "empty-config",
"preRun": [
{
"script": "meta-compile/scripts/meta-compile",
"input": {
"type": "ExternalDataSource",
"name": "PG",
"tables": { "products": { "keyFields": ["id"], "fields": ["id: Number(10,0)"] } }
},
"args": { "-JsonPath": "{inputFile}", "-OutputDir": "{workDir}" }
}
],
"params": { "objectPath": "ExternalDataSources/PG.xml" },
"input": {
"add": {
"tables": {
"sales": {
"nameInDataSource": "eds.public.sales",
"tableDataType": "ObjectData",
"keyFields": ["id"],
"fields": ["id: Number(10,0)", "summa: Number(15,2)", "comment: String(0) | nullable"]
}
},
"functions": { "nextKey": "NEXT VALUE FOR public.seq_key" }
}
},
"expect": {
"stdoutContains": ["Added table: sales", "Added function: nextKey"],
"files": ["ExternalDataSources/PG/Tables/sales.xml", "ExternalDataSources/PG/Tables/products.xml"],
"fileContains": [
{
"file": "ExternalDataSources/PG.xml",
"text": ["<Table>products</Table>", "<Table>sales</Table>", "NEXT VALUE FOR public.seq_key"]
},
{
"file": "ExternalDataSources/PG/Tables/sales.xml",
"text": [
"<NameInDataSource>eds.public.sales</NameInDataSource>",
"<TableDataType>ObjectData</TableDataType>",
"<xr:Field>ExternalDataSource.PG.Table.sales.Field.id</xr:Field>",
"<xr:GeneratedType name=\"ExternalDataSourceTableRef.PG.sales\" category=\"Ref\">"
]
}
]
}
}
@@ -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="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>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>
<ExternalDataSource>PG</ExternalDataSource>
</ChildObjects>
</Configuration>
</MetaDataObject>
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<ClientApplicationInterface xmlns="http://v8.1c.ru/8.2/managed-application/core" xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="InterfaceLayouter">
<top>
<panel id="UUID-001">
<uuid>UUID-002</uuid>
</panel>
</top>
<left>
<panel id="UUID-003">
<uuid>UUID-004</uuid>
</panel>
</left>
<panelDef id="UUID-004"/>
<panelDef id="UUID-005"/>
<panelDef id="UUID-006"/>
<panelDef id="UUID-002"/>
<panelDef id="UUID-007"/>
</ClientApplicationInterface>
@@ -0,0 +1,50 @@
<?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">
<ExternalDataSource uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="ExternalDataSourceManager.PG" category="Manager">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTablesManager.PG" category="TablesManager">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceCubesManager.PG" category="CubesManager">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>PG</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>PG</v8:content>
</v8:item>
</Synonym>
<Comment/>
<DataLockControlMode>Automatic</DataLockControlMode>
</Properties>
<ChildObjects>
<Table>products</Table>
<Table>sales</Table>
<Function uuid="UUID-008">
<Properties>
<Name>nextKey</Name>
<Synonym/>
<Comment/>
<ReturnValue>true</ReturnValue>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>10</v8:Length>
<v8:AllowedLength>Variable</v8:AllowedLength>
</v8:StringQualifiers>
</Type>
<ExpressionInDataSource>NEXT VALUE FOR public.seq_key</ExpressionInDataSource>
</Properties>
</Function>
</ChildObjects>
</ExternalDataSource>
</MetaDataObject>
@@ -0,0 +1,130 @@
<?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">
<Table uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="ExternalDataSourceTableManager.PG.products" category="Manager">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableObject.PG.products" category="Object">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRef.PG.products" category="Ref">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableList.PG.products" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecord.PG.products" category="Record">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordSet.PG.products" category="RecordSet">
<xr:TypeId>UUID-012</xr:TypeId>
<xr:ValueId>UUID-013</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordKey.PG.products" category="RecordKey">
<xr:TypeId>UUID-014</xr:TypeId>
<xr:ValueId>UUID-015</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordManager.PG.products" category="RecordManager">
<xr:TypeId>UUID-016</xr:TypeId>
<xr:ValueId>UUID-017</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>products</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>products</v8:content>
</v8:item>
</Synonym>
<Comment/>
<TableType>Table</TableType>
<NameInDataSource>products</NameInDataSource>
<ExpressionInDataSource/>
<TableDataType>NonobjectData</TableDataType>
<KeyFields>
<xr:Field>ExternalDataSource.PG.Table.products.Field.id</xr:Field>
</KeyFields>
<PresentationField/>
<ParentField/>
<UnfilledParentValue xsi:nil="true"/>
<Characteristics/>
<UseStandardCommands>true</UseStandardCommands>
<QuickChoice>false</QuickChoice>
<InputByString/>
<CreateOnInput>Auto</CreateOnInput>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DefaultObjectForm/>
<DefaultRecordForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<RecordPresentation/>
<ExtendedRecordPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<ReadOnly>false</ReadOnly>
<TransactionsIsolationLevel>Auto</TransactionsIsolationLevel>
<DataVersionField/>
<EditType>InDialog</EditType>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Automatic</DataLockControlMode>
</Properties>
<ChildObjects>
<Field uuid="UUID-018">
<Properties>
<Name>id</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>id</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</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"/>
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:decimal">0</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<ChoiceForm/>
<NameInDataSource>id</NameInDataSource>
<ReadOnly>false</ReadOnly>
<AllowNull>false</AllowNull>
</Properties>
</Field>
</ChildObjects>
</Table>
</MetaDataObject>
@@ -0,0 +1,213 @@
<?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">
<Table uuid="UUID-001">
<InternalInfo>
<xr:GeneratedType name="ExternalDataSourceTableManager.PG.sales" category="Manager">
<xr:TypeId>UUID-002</xr:TypeId>
<xr:ValueId>UUID-003</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableObject.PG.sales" category="Object">
<xr:TypeId>UUID-004</xr:TypeId>
<xr:ValueId>UUID-005</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRef.PG.sales" category="Ref">
<xr:TypeId>UUID-006</xr:TypeId>
<xr:ValueId>UUID-007</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableList.PG.sales" category="List">
<xr:TypeId>UUID-008</xr:TypeId>
<xr:ValueId>UUID-009</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecord.PG.sales" category="Record">
<xr:TypeId>UUID-010</xr:TypeId>
<xr:ValueId>UUID-011</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordSet.PG.sales" category="RecordSet">
<xr:TypeId>UUID-012</xr:TypeId>
<xr:ValueId>UUID-013</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordKey.PG.sales" category="RecordKey">
<xr:TypeId>UUID-014</xr:TypeId>
<xr:ValueId>UUID-015</xr:ValueId>
</xr:GeneratedType>
<xr:GeneratedType name="ExternalDataSourceTableRecordManager.PG.sales" category="RecordManager">
<xr:TypeId>UUID-016</xr:TypeId>
<xr:ValueId>UUID-017</xr:ValueId>
</xr:GeneratedType>
</InternalInfo>
<Properties>
<Name>sales</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>sales</v8:content>
</v8:item>
</Synonym>
<Comment/>
<TableType>Table</TableType>
<NameInDataSource>eds.public.sales</NameInDataSource>
<ExpressionInDataSource/>
<TableDataType>ObjectData</TableDataType>
<KeyFields>
<xr:Field>ExternalDataSource.PG.Table.sales.Field.id</xr:Field>
</KeyFields>
<PresentationField/>
<ParentField/>
<UnfilledParentValue xsi:nil="true"/>
<Characteristics/>
<UseStandardCommands>true</UseStandardCommands>
<QuickChoice>false</QuickChoice>
<InputByString/>
<CreateOnInput>Auto</CreateOnInput>
<SearchStringModeOnInputByString>Begin</SearchStringModeOnInputByString>
<ChoiceDataGetModeOnInputByString>Directly</ChoiceDataGetModeOnInputByString>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<DefaultObjectForm/>
<DefaultRecordForm/>
<DefaultListForm/>
<DefaultChoiceForm/>
<ObjectPresentation/>
<ExtendedObjectPresentation/>
<RecordPresentation/>
<ExtendedRecordPresentation/>
<ListPresentation/>
<ExtendedListPresentation/>
<Explanation/>
<IncludeHelpInContents>false</IncludeHelpInContents>
<ReadOnly>false</ReadOnly>
<TransactionsIsolationLevel>Auto</TransactionsIsolationLevel>
<DataVersionField/>
<EditType>InDialog</EditType>
<BasedOn/>
<DataLockFields/>
<DataLockControlMode>Automatic</DataLockControlMode>
</Properties>
<ChildObjects>
<Field uuid="UUID-018">
<Properties>
<Name>id</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>id</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>10</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"/>
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:decimal">0</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<ChoiceForm/>
<NameInDataSource>id</NameInDataSource>
<ReadOnly>false</ReadOnly>
<AllowNull>false</AllowNull>
</Properties>
</Field>
<Field uuid="UUID-019">
<Properties>
<Name>summa</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>summa</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:decimal</v8:Type>
<v8:NumberQualifiers>
<v8:Digits>15</v8:Digits>
<v8:FractionDigits>2</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"/>
<FillFromFillingValue>false</FillFromFillingValue>
<FillValue xsi:type="xs:decimal">0</FillValue>
<FillChecking>DontCheck</FillChecking>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<ChoiceForm/>
<NameInDataSource>summa</NameInDataSource>
<ReadOnly>false</ReadOnly>
<AllowNull>false</AllowNull>
</Properties>
</Field>
<Field uuid="UUID-020">
<Properties>
<Name>comment</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>comment</v8:content>
</v8:item>
</Synonym>
<Comment/>
<Type>
<v8:Type>xs:string</v8:Type>
<v8:StringQualifiers>
<v8:Length>0</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>
<ChoiceParameterLinks/>
<ChoiceParameters/>
<QuickChoice>Auto</QuickChoice>
<CreateOnInput>Auto</CreateOnInput>
<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>
<ChoiceForm/>
<NameInDataSource>comment</NameInDataSource>
<ReadOnly>false</ReadOnly>
<AllowNull>true</AllowNull>
</Properties>
</Field>
</ChildObjects>
</Table>
</MetaDataObject>
@@ -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>
+19
View File
@@ -31,6 +31,25 @@ const SKILLS = join(ROOT, '.claude', 'skills');
// Заготовку по новой семье печатает: node debug/inline-utils/scan-dupes.mjs --stub <py>:<ps1>
const FAMILIES = [
// ─── внешние источники данных: формат файла таблицы один на всех ─────────
// Таблица внешнего источника — отдельный файл, и создать его может как meta-compile
// (источник целиком), так и meta-edit (добавить таблицу в существующий). Формат обязан
// быть один и тот же, поэтому эмиттеры скопированы, а не написаны заново.
...[
// Общие утилиты (Emit-MLText, Emit-FormRef, X и т.п.) сюда не входят: они живут в доброй
// половине навыков и сводить их — отдельная работа, не относящаяся к внешним источникам.
['Get-EdsTables', 'get_eds_tables'],
['Get-EdsFieldRef', 'get_eds_field_ref'],
['Emit-EdsFieldRefList', 'emit_eds_field_ref_list'],
['Emit-EdsFieldRefScalar', 'emit_eds_field_ref_scalar'],
['Emit-EdsFunction', 'emit_eds_function'],
['Emit-EdsTableProperties', 'emit_eds_table_properties'],
['Build-EdsTableXml', 'build_eds_table_xml'],
].map(([ps1, py]) => ({
name: `внешние источники: ${py}`, py, ps1,
variants: [{ id: 'full', authority: 'meta-compile', consumers: ['meta-edit'] }],
})),
// ─── support-guard: запрет правки объекта на поддержке ───────────────────
{
name: 'support-guard: assert_edit_allowed', py: 'assert_edit_allowed', ps1: 'Assert-EditAllowed',