diff --git a/.claude/skills/meta-compile/reference/web.md b/.claude/skills/meta-compile/reference/web.md
index 4a129fad..64084137 100644
--- a/.claude/skills/meta-compile/reference/web.md
+++ b/.claude/skills/meta-compile/reference/web.md
@@ -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",
diff --git a/.claude/skills/meta-compile/scripts/meta-compile.ps1 b/.claude/skills/meta-compile/scripts/meta-compile.ps1
index a75eed03..d5313ec5 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.ps1
+++ b/.claude/skills/meta-compile/scripts/meta-compile.ps1
@@ -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"
}
+# XDTO-тип значения: "{uri}Local" (нотация Кларка) → тег с ЛОКАЛЬНЫМ объявлением xmlns, как пишет
+# платформа (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$(Esc-Xml $objName)"
Emit-MLText $i "Synonym" $synonym
- X "$i"
+ if ($def.comment) { X "$i$(Esc-Xml "$($def.comment)")" } else { X "$i" }
$namespace = if ($def.namespace) { "$($def.namespace)" } else { "" }
X "$i$(Esc-Xml $namespace)"
- $xdtoPackages = if ($def.xdtoPackages) { "$($def.xdtoPackages)" } else { "" }
- if ($xdtoPackages) { X "$i$xdtoPackages" } else { X "$i" }
+ # 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"
+ foreach ($p in $pkgs) {
+ $pv = "$p"
+ $xt = if ($pv -like 'XDTOPackage.*') { 'xr:MDObjectRef' } else { 'xs:string' }
+ X "$i`t"
+ X "$i`t`t"
+ X "$i`t`t0"
+ X "$i`t`t$(Esc-Xml $pv)"
+ X "$i`t"
+ }
+ X "$i"
+ } else { X "$i" }
+
+ # Имя файла дескриптора не выводится из имени сервиса (DMILService → dmil.1cws) — только дефолт.
+ $descriptor = if ($def.descriptorFileName) { "$($def.descriptorFileName)" } else { "$objName.1cws" }
+ X "$i$(Esc-Xml $descriptor)"
$reuseSessions = Get-EnumProp "ReuseSessions" "reuseSessions" "DontUse"
X "$i$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"
X "$indent`t`t$(Esc-Xml $opName)"
Emit-MLText "$indent`t`t" "Synonym" $opSynonym
- X "$indent`t`t"
- X "$indent`t`t$returnType"
+ if ($opComment) { X "$indent`t`t$(Esc-Xml $opComment)" } else { X "$indent`t`t" }
+ Emit-XDTOType "$indent`t`t" "XDTOReturningValueType" $returnType
X "$indent`t`t$nillable"
X "$indent`t`t$transactioned"
X "$indent`t`t$(Esc-Xml $handler)"
+ X "$indent`t`t$dataLock"
X "$indent`t"
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"
X "$indent`t`t`t"
X "$indent`t`t`t`t$(Esc-Xml $paramName)"
Emit-MLText "$indent`t`t`t`t" "Synonym" $paramSynonym
- X "$indent`t`t`t`t$paramType"
+ if ($paramComment) { X "$indent`t`t`t`t$(Esc-Xml $paramComment)" } else { X "$indent`t`t`t`t" }
+ Emit-XDTOType "$indent`t`t`t`t" "XDTOValueType" $paramType
X "$indent`t`t`t`t$paramNillable"
X "$indent`t`t`t`t$paramDir"
X "$indent`t`t`t"
@@ -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
diff --git a/.claude/skills/meta-compile/scripts/meta-compile.py b/.claude/skills/meta-compile/scripts/meta-compile.py
index 470cdd00..3025379b 100644
--- a/.claude/skills/meta-compile/scripts/meta-compile.py
+++ b/.claude/skills/meta-compile/scripts/meta-compile.py
@@ -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}{session_max_age}')
+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}{esc_xml(obj_name)}')
emit_mltext(i, 'Synonym', synonym)
- X(f'{i}')
+ X(f'{i}{esc_xml(str(defn["comment"]))}' if defn.get('comment') else f'{i}')
namespace = str(defn['namespace']) if defn.get('namespace') else ''
X(f'{i}{esc_xml(namespace)}')
- xdto_packages = str(defn['xdtoPackages']) if defn.get('xdtoPackages') else ''
- if xdto_packages:
- X(f'{i}{xdto_packages}')
+ # 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}')
+ for p in pkgs:
+ pv = str(p)
+ xt = 'xr:MDObjectRef' if pv.startswith('XDTOPackage.') else 'xs:string'
+ X(f'{i}\t')
+ X(f'{i}\t\t')
+ X(f'{i}\t\t0')
+ X(f'{i}\t\t{esc_xml(pv)}')
+ X(f'{i}\t')
+ X(f'{i}')
else:
X(f'{i}')
+ # Имя файла дескриптора не выводится из имени сервиса (DMILService -> dmil.1cws) — только дефолт.
+ descriptor = str(defn['descriptorFileName']) if defn.get('descriptorFileName') else f'{obj_name}.1cws'
+ X(f'{i}{esc_xml(descriptor)}')
reuse_sessions = get_enum_prop('ReuseSessions', 'reuseSessions', 'DontUse')
X(f'{i}{reuse_sessions}')
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')
X(f'{indent}\t\t{esc_xml(op_name)}')
emit_mltext(f'{indent}\t\t', 'Synonym', op_synonym)
- X(f'{indent}\t\t')
- X(f'{indent}\t\t{return_type}')
+ X(f'{indent}\t\t{esc_xml(op_comment)}' if op_comment else f'{indent}\t\t')
+ emit_xdto_type(f'{indent}\t\t', 'XDTOReturningValueType', return_type)
X(f'{indent}\t\t{nillable}')
X(f'{indent}\t\t{transactioned}')
X(f'{indent}\t\t{esc_xml(handler)}')
+ X(f'{indent}\t\t{data_lock}')
X(f'{indent}\t')
if params:
X(f'{indent}\t')
- 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')
X(f'{indent}\t\t\t')
X(f'{indent}\t\t\t\t{esc_xml(param_name)}')
emit_mltext(f'{indent}\t\t\t\t', 'Synonym', param_synonym)
- X(f'{indent}\t\t\t\t{param_type}')
+ X(f'{indent}\t\t\t\t{esc_xml(param_comment)}' if param_comment else f'{indent}\t\t\t\t')
+ emit_xdto_type(f'{indent}\t\t\t\t', 'XDTOValueType', param_type)
X(f'{indent}\t\t\t\t{param_nillable}')
X(f'{indent}\t\t\t\t{param_dir}')
X(f'{indent}\t\t\t')
@@ -4297,7 +4341,8 @@ if obj_type == 'WebService':
if operations:
has_children = True
X('\t\t')
- 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')
else:
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1 b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
index 714e4e5c..3df1ea41 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.ps1
@@ -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))
diff --git a/.claude/skills/meta-decompile/scripts/meta-decompile.py b/.claude/skills/meta-decompile/scripts/meta-decompile.py
index e9b8dc83..15393e79 100644
--- a/.claude/skills/meta-decompile/scripts/meta-decompile.py
+++ b/.claude/skills/meta-decompile/scripts/meta-decompile.py
@@ -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):
# Скалярное значение параметра выбора () → 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-элемент массива () -> 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',
)
diff --git a/tests/skills/cases/meta-compile/snapshots/web-service/WebServices/СервисОбмена.xml b/tests/skills/cases/meta-compile/snapshots/web-service/WebServices/СервисОбмена.xml
index aa634550..ca48a239 100644
--- a/tests/skills/cases/meta-compile/snapshots/web-service/WebServices/СервисОбмена.xml
+++ b/tests/skills/cases/meta-compile/snapshots/web-service/WebServices/СервисОбмена.xml
@@ -11,7 +11,19 @@
http://example.com/exchange
-
+
+
+
+ 0
+ http://v8.1c.ru/8.3/data/ext
+
+
+
+ 0
+ http://example.com/exchange
+
+
+ exchange.1cws
AutoUse
20
@@ -30,23 +42,10 @@
true
false
ПолучитьДанные
+ Managed
-
- ДатаНачала
-
-
- ru
- Дата начала
-
-
- xs:dateTime
- true
- In
-
-
-
ИдентификаторЗапроса
@@ -55,11 +54,76 @@
Идентификатор запроса
+
xs:string
true
In
+
+
+ ДатаНачала
+
+
+ ru
+ Дата начала
+
+
+
+ xs:dateTime
+ true
+ In
+
+
+
+
+
+
+ ОбменСообщениями
+
+
+ ru
+ Обмен сообщениями
+
+
+ Основная операция
+ d1p1:MessageExchange
+ false
+ false
+ ВыполнитьОбмен
+ Managed
+
+
+
+
+ Сообщение
+
+
+ ru
+ Сообщение
+
+
+
+ d1p1:MessageExchange
+ false
+ In
+
+
+
+
+ Ошибка
+
+
+ ru
+ Ошибка
+
+
+
+ xs:string
+ false
+ Out
+
+
diff --git a/tests/skills/cases/meta-compile/web-service.json b/tests/skills/cases/meta-compile/web-service.json
index 8bde3617..a78e22de 100644
--- a/tests/skills/cases/meta-compile/web-service.json
+++ b/tests/skills/cases/meta-compile/web-service.json
@@ -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" }
+ }
}
}
},