feat(meta-decompile,meta-compile): поддержка WebService и полнота его формата

meta-decompile не знал тип WebService — 18 сервисов УТ не проходили раундтрип.
Добавлен разбор (оба порта): namespace, состав XDTO-пакетов, дескриптор,
операции с параметрами.

Компилятор поддерживал тип поверхностно; раундтрип на реальных сервисах
показал, чего не хватало:

- XDTOPackages эмитился скаляром, а это СПИСОК элементов: ссылка на пакет
  конфигурации (xr:MDObjectRef) либо URI внешнего пространства имён
  (xs:string). Presentation пуст, CheckState 0 — 19/19 по корпусу;
- DescriptorFileName не эмитился вовсе, хотя есть у всех 18 сервисов и НЕ
  выводится из имени (DMILService -> dmil.1cws) — нужен явный ключ;
- DataLockControlMode не эмитился (Managed у всех 192 операций);
- Comment не эмитился ни у сервиса, ни у операций и параметров;
- типы из собственного пространства имён (81 случай) писались без локального
  xmlns. В DSL задаются нотацией Кларка "{uri}ИмяТипа", компилятор объявляет
  xmlns сам — как это делает платформа.

Nillable захватывается явно у операций и параметров: по корпусу значения
смешанные (операции 103/89, параметры 128/395), дефолт угадать нельзя.

Порядок операций и параметров приведён к порядку DSL в обоих портах — PS шёл
в порядке хеш-таблицы, py сортировал.

Проверка: 25 сервисов УТ (18 WebService + 7 HTTPService) 25/25 без
расхождений; JSON декомпилятора побайтово совпадает у PS и py на всех 25;
сюита 76/76 на обоих портах; 1С-сертификация обоих кейсов пройдена.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-08-02 11:40:10 +03:00
co-authored by Claude Opus 5
parent 01c45581ef
commit 87f97e986a
7 changed files with 442 additions and 45 deletions
+20 -7
View File
@@ -7,16 +7,21 @@
| Ключ | Умолчание | Значения |
|------|-----------|----------|
| `rootURL` | `= name` (в нижнем регистре) | корневой URL |
| `reuseSessions` | `DontUse` | `DontUse` / `AutoUse` |
| `reuseSessions` | `DontUse` | `DontUse` / `Use` / `AutoUse` |
| `sessionMaxAge` | `20` | время жизни сессии, сек |
| `urlTemplates` | `{}` | шаблоны URL (см. ниже) |
`urlTemplates` — объект `{ "ИмяШаблона": def }`, где `def`:
- строка — URL-путь без методов: `"/health"`;
- объект: `template` (путь с параметрами `{id}`, по умолчанию `/имяшаблона`), `methods``{ "ИмяМетода": "HTTPMethod" }`.
- объект: `template` (путь с параметрами `{id}`, по умолчанию `/имяшаблона`), `synonym`, `comment`,
`methods``{ "ИмяМетода": def }`.
`methods` — значение либо строка (только HTTP-метод), либо объект: `httpMethod`, `handler`,
`synonym`, `comment`.
HTTP-методы: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `CONNECT`, `TRACE`, `MERGE`.
Обработчик метода в модуле именуется `{ИмяШаблона}{ИмяМетода}`.
Обработчик по умолчанию именуется `{ИмяШаблона}{ИмяМетода}`; в типовых конфигурациях он часто
произвольный — тогда задавайте `handler` явно.
```json
{ "type": "HTTPService", "name": "API", "rootURL": "api",
@@ -31,21 +36,29 @@ HTTP-методы: `GET`, `POST`, `PUT`, `DELETE`, `PATCH`, `HEAD`, `OPTIONS`, `
| Ключ | Умолчание | Значения |
|------|-----------|----------|
| `namespace` | пусто | URI пространства имён WSDL |
| `xdtoPackages` | пусто | XDTO-пакеты |
| `reuseSessions` | `DontUse` | `DontUse` / `AutoUse` |
| `xdtoPackages` | пусто | список пакетов (см. ниже) |
| `descriptorFileName` | `= name` + `.1cws` | имя файла дескриптора |
| `reuseSessions` | `DontUse` | `DontUse` / `Use` / `AutoUse` |
| `sessionMaxAge` | `20` | время жизни сессии, сек |
| `operations` | `{}` | операции (см. ниже) |
`xdtoPackages`**массив** значений: `"XDTOPackage.Имя"` — пакет конфигурации, любое другое
значение — URI внешнего пространства имён (например `"http://v8.1c.ru/8.3/data/ext"`).
`operations` — объект `{ "ИмяОперации": def }`, где `def`:
- строка — XDTO-тип возврата без параметров: `"xs:string"`;
- объект: `returnType` (по умолчанию `xs:string`), `nillable` (bool), `transactioned` (bool),
`handler` (имя процедуры, по умолчанию = имя операции), `parameters`.
`procedureName` (имя процедуры, по умолчанию = имя операции; синоним ключа — `handler`),
`dataLockControlMode` (по умолчанию `Managed`), `synonym`, `comment`, `parameters`.
`parameters` — объект `{ "ИмяПараметра": def }`, где `def`:
- строка — XDTO-тип (`direction` = `In`);
- объект: `type` (по умолчанию `xs:string`), `nillable` (bool, по умолчанию `true`), `direction` (`In` / `Out` / `InOut`).
- объект: `type` (по умолчанию `xs:string`), `nillable` (bool, по умолчанию `true`),
`direction` (`In` / `Out` / `InOut`), `synonym`, `comment`.
XDTO-типы: `xs:string`, `xs:boolean`, `xs:int`, `xs:long`, `xs:decimal`, `xs:dateTime`, `xs:base64Binary`.
Тип из собственного пространства имён задаётся в нотации Кларка — `"{http://ваш.uri}ИмяТипа"`;
компилятор сам объявит локальный `xmlns` в теге, как это делает платформа.
```json
{ "type": "WebService", "name": "DataExchange", "namespace": "http://www.1c.ru/DataExchange",
@@ -1,4 +1,4 @@
# meta-compile v1.71 — Compile 1C metadata object from JSON
# meta-compile v1.72 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)]
@@ -3812,19 +3812,51 @@ function Emit-HTTPServiceProperties {
X "$i<SessionMaxAge>$sessionMaxAge</SessionMaxAge>"
}
# XDTO-тип значения: "{uri}Local" (нотация Кларка) → тег с ЛОКАЛЬНЫМ объявлением xmlns, как пишет
# платформа (<XDTOValueType xmlns:d1p1="uri">d1p1:Local</...>). Простой "xs:string" — как есть.
function Emit-XDTOType {
param([string]$indent, [string]$tag, [string]$value)
if ($value -match '^\{([^}]+)\}(.+)$') {
$uri = $Matches[1]; $local = $Matches[2]
X "$indent<$tag xmlns:d1p1=`"$(Esc-Xml $uri)`">d1p1:$(Esc-Xml $local)</$tag>"
} else {
X "$indent<$tag>$(Esc-Xml $value)</$tag>"
}
}
function Emit-WebServiceProperties {
param([string]$indent)
$i = $indent
X "$i<Name>$(Esc-Xml $objName)</Name>"
Emit-MLText $i "Synonym" $synonym
X "$i<Comment/>"
if ($def.comment) { X "$i<Comment>$(Esc-Xml "$($def.comment)")</Comment>" } else { X "$i<Comment/>" }
$namespace = if ($def.namespace) { "$($def.namespace)" } else { "" }
X "$i<Namespace>$(Esc-Xml $namespace)</Namespace>"
$xdtoPackages = if ($def.xdtoPackages) { "$($def.xdtoPackages)" } else { "" }
if ($xdtoPackages) { X "$i<XDTOPackages>$xdtoPackages</XDTOPackages>" } else { X "$i<XDTOPackages/>" }
# XDTOPackages — СПИСОК элементов, а не скаляр: значение либо ссылка на пакет конфигурации
# (xr:MDObjectRef "XDTOPackage.Имя"), либо URI внешнего пространства имён (xs:string).
# Presentation всегда пуст, CheckState всегда 0 (проверено на корпусе: 19/19).
$pkgs = @()
if ($def.xdtoPackages) { $pkgs = @($def.xdtoPackages) }
if ($pkgs.Count -gt 0) {
X "$i<XDTOPackages>"
foreach ($p in $pkgs) {
$pv = "$p"
$xt = if ($pv -like 'XDTOPackage.*') { 'xr:MDObjectRef' } else { 'xs:string' }
X "$i`t<xr:Item>"
X "$i`t`t<xr:Presentation/>"
X "$i`t`t<xr:CheckState>0</xr:CheckState>"
X "$i`t`t<xr:Value xsi:type=`"$xt`">$(Esc-Xml $pv)</xr:Value>"
X "$i`t</xr:Item>"
}
X "$i</XDTOPackages>"
} else { X "$i<XDTOPackages/>" }
# Имя файла дескриптора не выводится из имени сервиса (DMILService → dmil.1cws) — только дефолт.
$descriptor = if ($def.descriptorFileName) { "$($def.descriptorFileName)" } else { "$objName.1cws" }
X "$i<DescriptorFileName>$(Esc-Xml $descriptor)</DescriptorFileName>"
$reuseSessions = Get-EnumProp "ReuseSessions" "reuseSessions" "DontUse"
X "$i<ReuseSessions>$reuseSessions</ReuseSessions>"
@@ -3952,7 +3984,10 @@ function Emit-Operation {
$nillable = "false"
$transactioned = "false"
$handler = $opName
$params = @{}
$opComment = ""
$dataLock = "Managed" # у всех 192 операций корпуса
# [ordered]: порядок параметров — как в DSL (он же порядок исходного XML).
$params = [ordered]@{}
if ($opDef -is [string]) {
$returnType = "$opDef"
@@ -3961,6 +3996,10 @@ function Emit-Operation {
if ($opDef.nillable -eq $true) { $nillable = "true" }
if ($opDef.transactioned -eq $true) { $transactioned = "true" }
if ($opDef.handler) { $handler = "$($opDef.handler)" }
if ($opDef.procedureName) { $handler = "$($opDef.procedureName)" }
if ($opDef.synonym) { $opSynonym = "$($opDef.synonym)" }
if ($opDef.comment) { $opComment = "$($opDef.comment)" }
if ($opDef.dataLockControlMode) { $dataLock = "$($opDef.dataLockControlMode)" }
if ($opDef.parameters) {
$opDef.parameters.PSObject.Properties | ForEach-Object {
$params[$_.Name] = $_.Value
@@ -3972,11 +4011,12 @@ function Emit-Operation {
X "$indent`t<Properties>"
X "$indent`t`t<Name>$(Esc-Xml $opName)</Name>"
Emit-MLText "$indent`t`t" "Synonym" $opSynonym
X "$indent`t`t<Comment/>"
X "$indent`t`t<XDTOReturningValueType>$returnType</XDTOReturningValueType>"
if ($opComment) { X "$indent`t`t<Comment>$(Esc-Xml $opComment)</Comment>" } else { X "$indent`t`t<Comment/>" }
Emit-XDTOType "$indent`t`t" "XDTOReturningValueType" $returnType
X "$indent`t`t<Nillable>$nillable</Nillable>"
X "$indent`t`t<Transactioned>$transactioned</Transactioned>"
X "$indent`t`t<ProcedureName>$(Esc-Xml $handler)</ProcedureName>"
X "$indent`t`t<DataLockControlMode>$dataLock</DataLockControlMode>"
X "$indent`t</Properties>"
if ($params.Count -gt 0) {
@@ -3988,6 +4028,7 @@ function Emit-Operation {
$paramType = "xs:string"
$paramNillable = "true"
$paramDir = "In"
$paramComment = ""
if ($paramDef -is [string]) {
$paramType = "$paramDef"
@@ -3995,13 +4036,16 @@ function Emit-Operation {
if ($paramDef.type) { $paramType = "$($paramDef.type)" }
if ($paramDef.nillable -eq $false) { $paramNillable = "false" }
if ($paramDef.direction) { $paramDir = "$($paramDef.direction)" }
if ($paramDef.synonym) { $paramSynonym = "$($paramDef.synonym)" }
if ($paramDef.comment) { $paramComment = "$($paramDef.comment)" }
}
X "$indent`t`t<Parameter uuid=`"$paramUuid`">"
X "$indent`t`t`t<Properties>"
X "$indent`t`t`t`t<Name>$(Esc-Xml $paramName)</Name>"
Emit-MLText "$indent`t`t`t`t" "Synonym" $paramSynonym
X "$indent`t`t`t`t<XDTOValueType>$paramType</XDTOValueType>"
if ($paramComment) { X "$indent`t`t`t`t<Comment>$(Esc-Xml $paramComment)</Comment>" } else { X "$indent`t`t`t`t<Comment/>" }
Emit-XDTOType "$indent`t`t`t`t" "XDTOValueType" $paramType
X "$indent`t`t`t`t<Nillable>$paramNillable</Nillable>"
X "$indent`t`t`t`t<TransferDirection>$paramDir</TransferDirection>"
X "$indent`t`t`t</Properties>"
@@ -4408,7 +4452,8 @@ if ($objType -eq "HTTPService") {
# --- WebService: Operations ---
if ($objType -eq "WebService") {
$operations = @{}
# [ordered]: порядок операций — как в DSL (он же порядок исходного XML).
$operations = [ordered]@{}
if ($def.operations) {
$def.operations.PSObject.Properties | ForEach-Object {
$operations[$_.Name] = $_.Value
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-compile v1.71 — Compile 1C metadata object from JSON
# meta-compile v1.72 — Compile 1C metadata object from JSON
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -3737,18 +3737,44 @@ def emit_http_service_properties(indent):
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
X(f'{i}<SessionMaxAge>{session_max_age}</SessionMaxAge>')
def emit_xdto_type(indent, tag, value):
"""XDTO-тип значения: "{uri}Local" (нотация Кларка) -> тег с ЛОКАЛЬНЫМ объявлением xmlns,
как пишет платформа. Простой "xs:string" как есть."""
m = re.match(r'^\{([^}]+)\}(.+)$', value or '')
if m:
X(f'{indent}<{tag} xmlns:d1p1="{esc_xml(m.group(1))}">d1p1:{esc_xml(m.group(2))}</{tag}>')
else:
X(f'{indent}<{tag}>{esc_xml(value)}</{tag}>')
def emit_web_service_properties(indent):
i = indent
X(f'{i}<Name>{esc_xml(obj_name)}</Name>')
emit_mltext(i, 'Synonym', synonym)
X(f'{i}<Comment/>')
X(f'{i}<Comment>{esc_xml(str(defn["comment"]))}</Comment>' if defn.get('comment') else f'{i}<Comment/>')
namespace = str(defn['namespace']) if defn.get('namespace') else ''
X(f'{i}<Namespace>{esc_xml(namespace)}</Namespace>')
xdto_packages = str(defn['xdtoPackages']) if defn.get('xdtoPackages') else ''
if xdto_packages:
X(f'{i}<XDTOPackages>{xdto_packages}</XDTOPackages>')
# XDTOPackages — СПИСОК элементов: ссылка на пакет конфигурации (xr:MDObjectRef) либо URI
# внешнего пространства имён (xs:string). Presentation пуст, CheckState 0 (корпус: 19/19).
pkgs = defn.get('xdtoPackages') or []
if not isinstance(pkgs, list):
pkgs = [pkgs]
if pkgs:
X(f'{i}<XDTOPackages>')
for p in pkgs:
pv = str(p)
xt = 'xr:MDObjectRef' if pv.startswith('XDTOPackage.') else 'xs:string'
X(f'{i}\t<xr:Item>')
X(f'{i}\t\t<xr:Presentation/>')
X(f'{i}\t\t<xr:CheckState>0</xr:CheckState>')
X(f'{i}\t\t<xr:Value xsi:type="{xt}">{esc_xml(pv)}</xr:Value>')
X(f'{i}\t</xr:Item>')
X(f'{i}</XDTOPackages>')
else:
X(f'{i}<XDTOPackages/>')
# Имя файла дескриптора не выводится из имени сервиса (DMILService -> dmil.1cws) — только дефолт.
descriptor = str(defn['descriptorFileName']) if defn.get('descriptorFileName') else f'{obj_name}.1cws'
X(f'{i}<DescriptorFileName>{esc_xml(descriptor)}</DescriptorFileName>')
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
X(f'{i}<ReuseSessions>{reuse_sessions}</ReuseSessions>')
session_max_age = str(defn['sessionMaxAge']) if defn.get('sessionMaxAge') is not None else '20'
@@ -3858,6 +3884,8 @@ def emit_operation(indent, op_name, op_def):
nillable = 'false'
transactioned = 'false'
handler = op_name
op_comment = ''
data_lock = 'Managed' # у всех 192 операций корпуса
params = {}
if isinstance(op_def, str):
return_type = op_def
@@ -3870,6 +3898,14 @@ def emit_operation(indent, op_name, op_def):
transactioned = 'true'
if op_def.get('handler'):
handler = str(op_def['handler'])
if op_def.get('procedureName'):
handler = str(op_def['procedureName'])
if op_def.get('synonym'):
op_synonym = str(op_def['synonym'])
if op_def.get('comment'):
op_comment = str(op_def['comment'])
if op_def.get('dataLockControlMode'):
data_lock = str(op_def['dataLockControlMode'])
if op_def.get('parameters'):
for k, v in op_def['parameters'].items():
params[k] = v
@@ -3877,20 +3913,23 @@ def emit_operation(indent, op_name, op_def):
X(f'{indent}\t<Properties>')
X(f'{indent}\t\t<Name>{esc_xml(op_name)}</Name>')
emit_mltext(f'{indent}\t\t', 'Synonym', op_synonym)
X(f'{indent}\t\t<Comment/>')
X(f'{indent}\t\t<XDTOReturningValueType>{return_type}</XDTOReturningValueType>')
X(f'{indent}\t\t<Comment>{esc_xml(op_comment)}</Comment>' if op_comment else f'{indent}\t\t<Comment/>')
emit_xdto_type(f'{indent}\t\t', 'XDTOReturningValueType', return_type)
X(f'{indent}\t\t<Nillable>{nillable}</Nillable>')
X(f'{indent}\t\t<Transactioned>{transactioned}</Transactioned>')
X(f'{indent}\t\t<ProcedureName>{esc_xml(handler)}</ProcedureName>')
X(f'{indent}\t\t<DataLockControlMode>{data_lock}</DataLockControlMode>')
X(f'{indent}\t</Properties>')
if params:
X(f'{indent}\t<ChildObjects>')
for param_name, param_def in sorted(params.items()):
# Порядок — как в DSL (он же порядок исходного XML), а не отсортированный.
for param_name, param_def in params.items():
param_uuid = new_uuid()
param_synonym = split_camel_case(param_name)
param_type = 'xs:string'
param_nillable = 'true'
param_dir = 'In'
param_comment = ''
if isinstance(param_def, str):
param_type = param_def
else:
@@ -3900,11 +3939,16 @@ def emit_operation(indent, op_name, op_def):
param_nillable = 'false'
if param_def.get('direction'):
param_dir = str(param_def['direction'])
if param_def.get('synonym'):
param_synonym = str(param_def['synonym'])
if param_def.get('comment'):
param_comment = str(param_def['comment'])
X(f'{indent}\t\t<Parameter uuid="{param_uuid}">')
X(f'{indent}\t\t\t<Properties>')
X(f'{indent}\t\t\t\t<Name>{esc_xml(param_name)}</Name>')
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', param_synonym)
X(f'{indent}\t\t\t\t<XDTOValueType>{param_type}</XDTOValueType>')
X(f'{indent}\t\t\t\t<Comment>{esc_xml(param_comment)}</Comment>' if param_comment else f'{indent}\t\t\t\t<Comment/>')
emit_xdto_type(f'{indent}\t\t\t\t', 'XDTOValueType', param_type)
X(f'{indent}\t\t\t\t<Nillable>{param_nillable}</Nillable>')
X(f'{indent}\t\t\t\t<TransferDirection>{param_dir}</TransferDirection>')
X(f'{indent}\t\t\t</Properties>')
@@ -4297,7 +4341,8 @@ if obj_type == 'WebService':
if operations:
has_children = True
X('\t\t<ChildObjects>')
for op_name in sorted(op_order):
# Порядок — как в DSL (он же порядок исходного XML), а не отсортированный.
for op_name in op_order:
emit_operation('\t\t\t', op_name, operations[op_name])
X('\t\t</ChildObjects>')
else:
@@ -1,4 +1,4 @@
# meta-decompile v0.57 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.58 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Поддержаны: Catalog, ExchangePlan, ChartOfCharacteristicTypes, ChartOfAccounts, ChartOfCalculationTypes, Document,
@@ -92,7 +92,7 @@ foreach ($c in $rootEl.ChildNodes) { if ($c.NodeType -eq 'Element') { $objNode =
if (-not $objNode) { [Console]::Error.WriteLine("meta-decompile: пустой MetaDataObject"); exit 3 }
$objType = $objNode.LocalName
if ($objType -notin @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate', 'HTTPService')) {
if ($objType -notin @('Catalog', 'ExchangePlan', 'ChartOfCharacteristicTypes', 'ChartOfAccounts', 'ChartOfCalculationTypes', 'Document', 'InformationRegister', 'AccumulationRegister', 'AccountingRegister', 'CalculationRegister', 'BusinessProcess', 'Task', 'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence', 'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob', 'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter', 'WSReference', 'CommonPicture', 'CommonTemplate', 'HTTPService', 'WebService')) {
[Console]::Error.WriteLine("meta-decompile: тип '$objType' пока не поддержан (…, CommonPicture, CommonTemplate)"); exit 3
}
@@ -781,6 +781,34 @@ if ($objType -eq 'CommonCommand') {
Add-BoolProp 'modifiesData' 'ModifiesData' $false
Add-EnumProp 'onMainServerUnavalableBehavior' 'OnMainServerUnavalableBehavior' 'Auto'
}
# XDTO-тип из элемента: если значение с префиксом (d6p1:Local) — разворачиваем префикс в URI и
# отдаём в нотации Кларка "{uri}Local"; префиксы платформы (dNpM) произвольны и переносу не подлежат.
function Get-XDTOTypeValue {
param($node)
if (-not $node) { return $null }
$txt = $node.InnerText
if ($txt -match '^([\w.-]+):(.+)$') {
$prefix = $Matches[1]; $local = $Matches[2]
$uri = $node.GetNamespaceOfPrefix($prefix)
# xs: и прочие стандартные оставляем как есть — компилятор их пишет дословно.
if ($uri -and $prefix -notin @('xs','xsi','v8','xr')) { return "{$uri}$local" }
}
return $txt
}
# WebService — пространство имён, состав XDTO-пакетов, дескриптор, операции с параметрами.
if ($objType -eq 'WebService') {
$ns = P 'Namespace'; if ($ns) { $dsl['namespace'] = $ns }
$pkgNodes = @($props.SelectNodes('md:XDTOPackages/xr:Item/xr:Value', $nsm))
if ($pkgNodes.Count -gt 0) {
$pkgs = [System.Collections.ArrayList]@()
foreach ($pn in $pkgNodes) { [void]$pkgs.Add($pn.InnerText) }
$dsl['xdtoPackages'] = $pkgs
}
$dfn = P 'DescriptorFileName'
if ($dfn -and $dfn -cne "$objName.1cws") { $dsl['descriptorFileName'] = $dfn }
Add-EnumProp 'reuseSessions' 'ReuseSessions' 'DontUse'
Add-IntProp 'sessionMaxAge' 'SessionMaxAge' 20
}
# HTTPService — корневой URL, повторное использование сеансов, время жизни сеанса.
# Шаблоны URL с методами разбираются в блоке ChildObjects.
if ($objType -eq 'HTTPService') {
@@ -1129,6 +1157,56 @@ if ($saNode) {
# --- ChildObjects: Attributes + TabularSections ---
$childObjs = $objNode.SelectSingleNode('md:ChildObjects', $nsm)
if ($childObjs) {
# WebService: операции с параметрами. Строчное сокращение — только тип возврата (когда всё
# остальное дефолтно); иначе объект с nillable/transactioned/procedureName/параметрами.
$opNodes = @($childObjs.SelectNodes('md:Operation', $nsm))
if ($opNodes.Count -gt 0) {
$ops = [ordered]@{}
foreach ($op in $opNodes) {
$op_p = $op.SelectSingleNode('md:Properties', $nsm)
$opName = ($op_p.SelectSingleNode('md:Name', $nsm)).InnerText
$o = [ordered]@{}
$rt = Get-XDTOTypeValue ($op_p.SelectSingleNode('md:XDTOReturningValueType', $nsm))
if ($rt -and $rt -cne 'xs:string') { $o['returnType'] = $rt }
$nil = $op_p.SelectSingleNode('md:Nillable', $nsm)
if ($nil -and $nil.InnerText -eq 'true') { $o['nillable'] = $true }
$tr = $op_p.SelectSingleNode('md:Transactioned', $nsm)
if ($tr -and $tr.InnerText -eq 'true') { $o['transactioned'] = $true }
$pn = $op_p.SelectSingleNode('md:ProcedureName', $nsm)
if ($pn -and $pn.InnerText -cne $opName) { $o['procedureName'] = $pn.InnerText }
$dl = $op_p.SelectSingleNode('md:DataLockControlMode', $nsm)
if ($dl -and $dl.InnerText -cne 'Managed') { $o['dataLockControlMode'] = $dl.InnerText }
$osyn = Get-MLValue ($op_p.SelectSingleNode('md:Synonym', $nsm))
if ($null -ne $osyn -and "$osyn" -cne (Split-CamelWords $opName)) { $o['synonym'] = $osyn }
$ocmt = $op_p.SelectSingleNode('md:Comment', $nsm)
if ($ocmt -and $ocmt.InnerText) { $o['comment'] = $ocmt.InnerText }
$parNodes = @($op.SelectNodes('md:ChildObjects/md:Parameter', $nsm))
if ($parNodes.Count -gt 0) {
$pars = [ordered]@{}
foreach ($par in $parNodes) {
$pp = $par.SelectSingleNode('md:Properties', $nsm)
$parName = ($pp.SelectSingleNode('md:Name', $nsm)).InnerText
$po = [ordered]@{}
$pt = Get-XDTOTypeValue ($pp.SelectSingleNode('md:XDTOValueType', $nsm))
if ($pt) { $po['type'] = $pt }
$pnil = $pp.SelectSingleNode('md:Nillable', $nsm)
if ($pnil -and $pnil.InnerText -eq 'false') { $po['nillable'] = $false }
$pdir = $pp.SelectSingleNode('md:TransferDirection', $nsm)
if ($pdir -and $pdir.InnerText -cne 'In') { $po['direction'] = $pdir.InnerText }
$psyn = Get-MLValue ($pp.SelectSingleNode('md:Synonym', $nsm))
if ($null -ne $psyn -and "$psyn" -cne (Split-CamelWords $parName)) { $po['synonym'] = $psyn }
$pcmt = $pp.SelectSingleNode('md:Comment', $nsm)
if ($pcmt -and $pcmt.InnerText) { $po['comment'] = $pcmt.InnerText }
# Только тип и дефолтное остальное → строчное сокращение.
if ($po.Count -eq 1 -and $po.Contains('type')) { $pars[$parName] = $po['type'] } else { $pars[$parName] = $po }
}
$o['parameters'] = $pars
}
if ($o.Count -eq 1 -and $o.Contains('returnType')) { $ops[$opName] = $o['returnType'] } else { $ops[$opName] = $o }
}
$dsl['operations'] = $ops
}
# HTTPService: шаблоны URL и их методы. Шаблон — {template, methods{}}, метод — строка (только
# HTTP-метод, когда обработчик совпадает с авто-выводом ИмяШаблона+ИмяМетода) либо объект.
$tmplNodes = @($childObjs.SelectNodes('md:URLTemplate', $nsm))
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# meta-decompile v0.57 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# meta-decompile v0.58 — XML объекта метаданных 1С → JSON-черновик формата meta-compile
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
#
# Зеркало meta-decompile.ps1 (КАНОН). Структура 1:1 — те же имена функций, порядок, комментарии.
@@ -58,6 +58,14 @@ def _text(node):
return ''.join(node.itertext())
def _ns_of_prefix(node, prefix):
"""URI по префиксу — зеркало GetNamespaceOfPrefix из PS. lxml отдаёт nsmap с учётом
унаследованных объявлений, поэтому локальный xmlns:dNpM на самом теге тоже виден."""
if node is None:
return None
return node.nsmap.get(prefix)
def _attr(node, name, ns=None):
"""GetAttribute(name[, ns]) — .NET возвращает '' для отсутствующего атрибута, lxml → None."""
if node is None:
@@ -290,6 +298,25 @@ def get_type_shorthand(type_node):
# Скалярное значение параметра выбора (<Value xsi:type=...>) → JSON-значение (bool/число/строка).
def get_xdto_type_value(node):
"""XDTO-тип: префиксное значение (d6p1:Local) -> нотация Кларка "{uri}Local".
Префиксы платформы (dNpM) произвольны и переносу не подлежат; стандартные (xs:, v8:, xr:)
оставляем дословно — компилятор их так и пишет.
"""
if node is None:
return None
txt = _text(node)
m = re.match(r'^([\w.\-]+):(.+)$', txt or '')
if m:
prefix, local = m.group(1), m.group(2)
if prefix not in ('xs', 'xsi', 'v8', 'xr'):
uri = _ns_of_prefix(node, prefix)
if uri:
return '{%s}%s' % (uri, local)
return txt
def convert_ch_scalar_node(vN):
# nil-элемент массива (<v8:Value xsi:nil="true"/>) -> JSON null. Без этого он приезжал пустой
# строкой и компилятор эмитил xs:string вместо nil.
@@ -1191,6 +1218,26 @@ def build_dsl():
get_picture_to_dsl(props, dsl)
add_enum_prop('category', 'Category', 'NavigationPanel')
# CommonCommand.
# WebService — пространство имён, состав XDTO-пакетов, дескриптор.
if obj_type == 'WebService':
ns_ = P('Namespace')
if ns_:
dsl['namespace'] = ns_
pkg_nodes = _nodes(props, 'md:XDTOPackages/xr:Item/xr:Value')
if len(pkg_nodes) > 0:
dsl['xdtoPackages'] = [_text(pn) for pn in pkg_nodes]
dfn = P('DescriptorFileName')
if dfn and dfn != f'{obj_name}.1cws':
dsl['descriptorFileName'] = dfn
add_enum_prop('reuseSessions', 'ReuseSessions', 'DontUse')
add_int_prop('sessionMaxAge', 'SessionMaxAge', 20)
# HTTPService — корневой URL, повторное использование сеансов, время жизни сеанса.
if obj_type == 'HTTPService':
ru = P('RootURL')
if ru and ru != obj_name.lower():
dsl['rootURL'] = ru
add_enum_prop('reuseSessions', 'ReuseSessions', 'DontUse')
add_int_prop('sessionMaxAge', 'SessionMaxAge', 20)
if obj_type == 'CommonCommand':
grp = P('Group')
if grp:
@@ -1618,6 +1665,100 @@ def build_dsl():
# --- ChildObjects: Attributes + TabularSections ---
child_objs = _single(obj_node, 'md:ChildObjects')
if child_objs is not None:
# WebService: операции с параметрами. Строчное сокращение — только тип возврата.
op_nodes = _nodes(child_objs, 'md:Operation')
if len(op_nodes) > 0:
ops = {}
for op in op_nodes:
op_p = _single(op, 'md:Properties')
op_name = _text(_single(op_p, 'md:Name'))
o = {}
rt = get_xdto_type_value(_single(op_p, 'md:XDTOReturningValueType'))
if rt and rt != 'xs:string':
o['returnType'] = rt
if _text(_single(op_p, 'md:Nillable')) == 'true':
o['nillable'] = True
if _text(_single(op_p, 'md:Transactioned')) == 'true':
o['transactioned'] = True
pn = _text(_single(op_p, 'md:ProcedureName'))
if pn and pn != op_name:
o['procedureName'] = pn
dl = _text(_single(op_p, 'md:DataLockControlMode'))
if dl and dl != 'Managed':
o['dataLockControlMode'] = dl
osyn = get_ml_value(_single(op_p, 'md:Synonym'))
if osyn is not None and str(osyn) != split_camel_words(op_name):
o['synonym'] = osyn
ocmt = _text(_single(op_p, 'md:Comment'))
if ocmt:
o['comment'] = ocmt
par_nodes = _nodes(op, 'md:ChildObjects/md:Parameter')
if len(par_nodes) > 0:
pars = {}
for par in par_nodes:
pp = _single(par, 'md:Properties')
par_name = _text(_single(pp, 'md:Name'))
po = {}
pt = get_xdto_type_value(_single(pp, 'md:XDTOValueType'))
if pt:
po['type'] = pt
if _text(_single(pp, 'md:Nillable')) == 'false':
po['nillable'] = False
pdir = _text(_single(pp, 'md:TransferDirection'))
if pdir and pdir != 'In':
po['direction'] = pdir
psyn = get_ml_value(_single(pp, 'md:Synonym'))
if psyn is not None and str(psyn) != split_camel_words(par_name):
po['synonym'] = psyn
pcmt = _text(_single(pp, 'md:Comment'))
if pcmt:
po['comment'] = pcmt
pars[par_name] = po['type'] if list(po.keys()) == ['type'] else po
o['parameters'] = pars
ops[op_name] = o['returnType'] if list(o.keys()) == ['returnType'] else o
dsl['operations'] = ops
# HTTPService: шаблоны URL и их методы.
tmpl_nodes = _nodes(child_objs, 'md:URLTemplate')
if len(tmpl_nodes) > 0:
tmpls = {}
for t in tmpl_nodes:
tp = _single(t, 'md:Properties')
t_name = _text(_single(tp, 'md:Name'))
t_obj = {}
t_template = _text(_single(tp, 'md:Template'))
if t_template:
t_obj['template'] = t_template
t_syn = get_ml_value(_single(tp, 'md:Synonym'))
if t_syn is not None and str(t_syn) != split_camel_words(t_name):
t_obj['synonym'] = t_syn
t_cmt = _text(_single(tp, 'md:Comment'))
if t_cmt:
t_obj['comment'] = t_cmt
m_nodes = _nodes(t, 'md:ChildObjects/md:Method')
if len(m_nodes) > 0:
methods = {}
for m in m_nodes:
mp = _single(m, 'md:Properties')
m_name = _text(_single(mp, 'md:Name'))
http_val = _text(_single(mp, 'md:HTTPMethod')) or 'GET'
handler_val = _text(_single(mp, 'md:Handler')) or ''
m_syn = get_ml_value(_single(mp, 'md:Synonym'))
m_cmt = _text(_single(mp, 'md:Comment'))
syn_default = m_syn is None or str(m_syn) == split_camel_words(m_name)
if handler_val == f'{t_name}{m_name}' and syn_default and not m_cmt:
methods[m_name] = http_val
else:
mo = {'httpMethod': http_val}
if handler_val:
mo['handler'] = handler_val
if not syn_default:
mo['synonym'] = m_syn
if m_cmt:
mo['comment'] = m_cmt
methods[m_name] = mo
t_obj['methods'] = methods
tmpls[t_name] = t_obj
dsl['urlTemplates'] = tmpls
attrs = _nodes(child_objs, 'md:Attribute')
if len(attrs) > 0:
arr = []
@@ -1938,7 +2079,7 @@ SUPPORTED_TYPES = (
'Enum', 'Report', 'DataProcessor', 'Constant', 'DefinedType', 'FunctionalOption', 'DocumentJournal', 'Sequence',
'FilterCriterion', 'DocumentNumerator', 'SettingsStorage', 'CommonModule', 'EventSubscription', 'ScheduledJob',
'CommonForm', 'SessionParameter', 'CommonCommand', 'CommandGroup', 'CommonAttribute', 'FunctionalOptionsParameter',
'WSReference', 'CommonPicture', 'CommonTemplate',
'WSReference', 'CommonPicture', 'CommonTemplate', 'HTTPService', 'WebService',
)
@@ -11,7 +11,19 @@
</Synonym>
<Comment/>
<Namespace>http://example.com/exchange</Namespace>
<XDTOPackages/>
<XDTOPackages>
<xr:Item>
<xr:Presentation/>
<xr:CheckState>0</xr:CheckState>
<xr:Value xsi:type="xs:string">http://v8.1c.ru/8.3/data/ext</xr:Value>
</xr:Item>
<xr:Item>
<xr:Presentation/>
<xr:CheckState>0</xr:CheckState>
<xr:Value xsi:type="xs:string">http://example.com/exchange</xr:Value>
</xr:Item>
</XDTOPackages>
<DescriptorFileName>exchange.1cws</DescriptorFileName>
<ReuseSessions>AutoUse</ReuseSessions>
<SessionMaxAge>20</SessionMaxAge>
</Properties>
@@ -30,23 +42,10 @@
<Nillable>true</Nillable>
<Transactioned>false</Transactioned>
<ProcedureName>ПолучитьДанные</ProcedureName>
<DataLockControlMode>Managed</DataLockControlMode>
</Properties>
<ChildObjects>
<Parameter uuid="UUID-003">
<Properties>
<Name>ДатаНачала</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Дата начала</v8:content>
</v8:item>
</Synonym>
<XDTOValueType>xs:dateTime</XDTOValueType>
<Nillable>true</Nillable>
<TransferDirection>In</TransferDirection>
</Properties>
</Parameter>
<Parameter uuid="UUID-004">
<Properties>
<Name>ИдентификаторЗапроса</Name>
<Synonym>
@@ -55,11 +54,76 @@
<v8:content>Идентификатор запроса</v8:content>
</v8:item>
</Synonym>
<Comment/>
<XDTOValueType>xs:string</XDTOValueType>
<Nillable>true</Nillable>
<TransferDirection>In</TransferDirection>
</Properties>
</Parameter>
<Parameter uuid="UUID-004">
<Properties>
<Name>ДатаНачала</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Дата начала</v8:content>
</v8:item>
</Synonym>
<Comment/>
<XDTOValueType>xs:dateTime</XDTOValueType>
<Nillable>true</Nillable>
<TransferDirection>In</TransferDirection>
</Properties>
</Parameter>
</ChildObjects>
</Operation>
<Operation uuid="UUID-005">
<Properties>
<Name>ОбменСообщениями</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Обмен сообщениями</v8:content>
</v8:item>
</Synonym>
<Comment>Основная операция</Comment>
<XDTOReturningValueType xmlns:d1p1="http://example.com/exchange">d1p1:MessageExchange</XDTOReturningValueType>
<Nillable>false</Nillable>
<Transactioned>false</Transactioned>
<ProcedureName>ВыполнитьОбмен</ProcedureName>
<DataLockControlMode>Managed</DataLockControlMode>
</Properties>
<ChildObjects>
<Parameter uuid="UUID-006">
<Properties>
<Name>Сообщение</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Сообщение</v8:content>
</v8:item>
</Synonym>
<Comment/>
<XDTOValueType xmlns:d1p1="http://example.com/exchange">d1p1:MessageExchange</XDTOValueType>
<Nillable>false</Nillable>
<TransferDirection>In</TransferDirection>
</Properties>
</Parameter>
<Parameter uuid="UUID-007">
<Properties>
<Name>Ошибка</Name>
<Synonym>
<v8:item>
<v8:lang>ru</v8:lang>
<v8:content>Ошибка</v8:content>
</v8:item>
</Synonym>
<Comment/>
<XDTOValueType>xs:string</XDTOValueType>
<Nillable>false</Nillable>
<TransferDirection>Out</TransferDirection>
</Properties>
</Parameter>
</ChildObjects>
</Operation>
</ChildObjects>
@@ -5,6 +5,8 @@
"name": "СервисОбмена",
"namespace": "http://example.com/exchange",
"reuseSessions": "AutoUse",
"descriptorFileName": "exchange.1cws",
"xdtoPackages": ["http://v8.1c.ru/8.3/data/ext", "http://example.com/exchange"],
"operations": {
"ПолучитьДанные": {
"returnType": "xs:string",
@@ -14,6 +16,15 @@
"ИдентификаторЗапроса": "xs:string",
"ДатаНачала": "xs:dateTime"
}
},
"ОбменСообщениями": {
"returnType": "{http://example.com/exchange}MessageExchange",
"procedureName": "ВыполнитьОбмен",
"comment": "Основная операция",
"parameters": {
"Сообщение": { "type": "{http://example.com/exchange}MessageExchange", "nillable": false },
"Ошибка": { "type": "xs:string", "nillable": false, "direction": "Out" }
}
}
}
},