mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-04 18:50:23 +03:00
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:
co-authored by
Claude Opus 5
parent
01c45581ef
commit
87f97e986a
@@ -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',
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user