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)" + } else { + X "$indent<$tag>$(Esc-Xml $value)" + } +} + 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))}') + else: + X(f'{indent}<{tag}>{esc_xml(value)}') + + 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" } + } } } },