mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-23 13:11:05 +03:00
feat(skd): inputParameters — ChoiceParameters/Links + typed values (round-trip)
DSL: object-form ключ inputParameters — массив элементов, каждый типизирован
по форме value:
- choiceParameters: [{name, values: [...]}] — параметры выбора (DesignTimeValue)
- choiceParameterLinks: [{name, value, mode}] — связи параметров выбора
- value (+ optional use=false) — простое типизированное значение (bool/string/number)
Compile: Emit-InputParameters / emit_input_parameters → <r:inputParameters>...
Decompile: Read-InputParameters читает любой xsi:type, без SilentDrop warnings.
Build-Parameter — убран вызов несуществующего Check-InputParameters.
В SKILL.md не добавляем (форма сложная — модель не пишет с нуля, но при
декомпиляции из реального отчёта получает корректно и compile примет назад).
Новый тест field-input-parameters (3 типа элементов, bit-perfect round-trip).
Versions: compile v1.29→v1.30, decompile v0.11→v0.12.
На сэмпле 30 ERP-отчётов: SilentDrop:ChoiceParameters/Links 51 → 0,
clean reports 8 → 21, total sentinel'ы 109 → 58.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# skd-compile v1.29 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.30 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -722,6 +722,79 @@ function Emit-DataSources {
|
||||
}
|
||||
|
||||
# === Fields ===
|
||||
function Has-JsonProp {
|
||||
param($obj, [string]$name)
|
||||
if ($null -eq $obj) { return $false }
|
||||
if ($obj.PSObject -and $obj.PSObject.Properties) {
|
||||
return $null -ne $obj.PSObject.Properties[$name]
|
||||
}
|
||||
if ($obj -is [System.Collections.IDictionary]) { return $obj.Contains($name) }
|
||||
return $false
|
||||
}
|
||||
|
||||
function Emit-InputParameters {
|
||||
param($ip, [string]$indent)
|
||||
if ($null -eq $ip) { return }
|
||||
$items = @($ip)
|
||||
if ($items.Count -eq 0) { return }
|
||||
X "$indent<inputParameters>"
|
||||
foreach ($item in $items) {
|
||||
X "$indent`t<dcscor:item>"
|
||||
if ((Has-JsonProp $item 'use') -and $null -ne $item.use -and -not $item.use) {
|
||||
X "$indent`t`t<dcscor:use>false</dcscor:use>"
|
||||
}
|
||||
X "$indent`t`t<dcscor:parameter>$(Esc-Xml "$($item.parameter)")</dcscor:parameter>"
|
||||
if (Has-JsonProp $item 'choiceParameters') {
|
||||
$cp = $item.choiceParameters
|
||||
$cpItems = if ($null -ne $cp) { @($cp) } else { @() }
|
||||
if ($cpItems.Count -eq 0) {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`"/>"
|
||||
} else {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameters`">"
|
||||
foreach ($cpItem in $cpItems) {
|
||||
X "$indent`t`t`t<dcscor:item>"
|
||||
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-Xml "$($cpItem.name)")</dcscor:choiceParameter>"
|
||||
foreach ($v in @($cpItem.values)) {
|
||||
X "$indent`t`t`t`t<dcscor:value xsi:type=`"dcscor:DesignTimeValue`">$(Esc-Xml "$v")</dcscor:value>"
|
||||
}
|
||||
X "$indent`t`t`t</dcscor:item>"
|
||||
}
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
}
|
||||
} elseif (Has-JsonProp $item 'choiceParameterLinks') {
|
||||
$cpl = $item.choiceParameterLinks
|
||||
$cplItems = if ($null -ne $cpl) { @($cpl) } else { @() }
|
||||
if ($cplItems.Count -eq 0) {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameterLinks`"/>"
|
||||
} else {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"dcscor:ChoiceParameterLinks`">"
|
||||
foreach ($cplItem in $cplItems) {
|
||||
X "$indent`t`t`t<dcscor:item>"
|
||||
X "$indent`t`t`t`t<dcscor:choiceParameter>$(Esc-Xml "$($cplItem.name)")</dcscor:choiceParameter>"
|
||||
X "$indent`t`t`t`t<dcscor:value>$(Esc-Xml "$($cplItem.value)")</dcscor:value>"
|
||||
$mode = if ($cplItem.mode) { "$($cplItem.mode)" } else { 'Auto' }
|
||||
X "$indent`t`t`t`t<dcscor:mode xmlns:d8p1=`"http://v8.1c.ru/8.1/data/enterprise`" xsi:type=`"d8p1:LinkedValueChangeMode`">$mode</dcscor:mode>"
|
||||
X "$indent`t`t`t</dcscor:item>"
|
||||
}
|
||||
X "$indent`t`t</dcscor:value>"
|
||||
}
|
||||
} elseif (Has-JsonProp $item 'value') {
|
||||
# Simple typed value — определяем xsi:type из JSON-типа
|
||||
$val = $item.value
|
||||
if ($val -is [bool]) {
|
||||
$vStr = if ($val) { 'true' } else { 'false' }
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:boolean`">$vStr</dcscor:value>"
|
||||
} elseif ($val -is [int] -or $val -is [long] -or $val -is [double] -or $val -is [decimal]) {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:decimal`">$val</dcscor:value>"
|
||||
} else {
|
||||
X "$indent`t`t<dcscor:value xsi:type=`"xs:string`">$(Esc-Xml "$val")</dcscor:value>"
|
||||
}
|
||||
}
|
||||
X "$indent`t</dcscor:item>"
|
||||
}
|
||||
X "$indent</inputParameters>"
|
||||
}
|
||||
|
||||
function Emit-Field {
|
||||
param($fieldDef, [string]$indent)
|
||||
|
||||
@@ -771,6 +844,10 @@ function Emit-Field {
|
||||
if ($fieldDef.orderExpression) {
|
||||
$f["orderExpression"] = $fieldDef.orderExpression
|
||||
}
|
||||
# inputParameters — массив элементов, типизированных по форме value
|
||||
if ($null -ne $fieldDef.inputParameters) {
|
||||
$f["inputParameters"] = $fieldDef.inputParameters
|
||||
}
|
||||
}
|
||||
|
||||
X "$indent<field xsi:type=`"DataSetFieldField`">"
|
||||
@@ -875,6 +952,11 @@ function Emit-Field {
|
||||
X "$indent`t<presentationExpression>$(Esc-Xml $f["presentationExpression"])</presentationExpression>"
|
||||
}
|
||||
|
||||
# InputParameters — в конце field
|
||||
if ($f["inputParameters"]) {
|
||||
Emit-InputParameters -ip $f["inputParameters"] -indent "$indent`t"
|
||||
}
|
||||
|
||||
X "$indent</field>"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-compile v1.29 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.30 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -549,6 +549,58 @@ def emit_data_sources(lines, data_sources):
|
||||
|
||||
# === Fields ===
|
||||
|
||||
def emit_input_parameters(lines, ip, indent):
|
||||
if not ip:
|
||||
return
|
||||
items = list(ip)
|
||||
if len(items) == 0:
|
||||
return
|
||||
lines.append(f'{indent}<inputParameters>')
|
||||
for item in items:
|
||||
lines.append(f'{indent}\t<dcscor:item>')
|
||||
if 'use' in item and item['use'] is False:
|
||||
lines.append(f'{indent}\t\t<dcscor:use>false</dcscor:use>')
|
||||
lines.append(f'{indent}\t\t<dcscor:parameter>{esc_xml(str(item.get("parameter", "")))}</dcscor:parameter>')
|
||||
if 'choiceParameters' in item:
|
||||
cp_items = list(item['choiceParameters']) if item['choiceParameters'] else []
|
||||
if len(cp_items) == 0:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="dcscor:ChoiceParameters"/>')
|
||||
else:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="dcscor:ChoiceParameters">')
|
||||
for cp in cp_items:
|
||||
lines.append(f'{indent}\t\t\t<dcscor:item>')
|
||||
lines.append(f'{indent}\t\t\t\t<dcscor:choiceParameter>{esc_xml(str(cp.get("name", "")))}</dcscor:choiceParameter>')
|
||||
for v in cp.get('values', []) or []:
|
||||
lines.append(f'{indent}\t\t\t\t<dcscor:value xsi:type="dcscor:DesignTimeValue">{esc_xml(str(v))}</dcscor:value>')
|
||||
lines.append(f'{indent}\t\t\t</dcscor:item>')
|
||||
lines.append(f'{indent}\t\t</dcscor:value>')
|
||||
elif 'choiceParameterLinks' in item:
|
||||
cpl_items = list(item['choiceParameterLinks']) if item['choiceParameterLinks'] else []
|
||||
if len(cpl_items) == 0:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="dcscor:ChoiceParameterLinks"/>')
|
||||
else:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="dcscor:ChoiceParameterLinks">')
|
||||
for cpl in cpl_items:
|
||||
lines.append(f'{indent}\t\t\t<dcscor:item>')
|
||||
lines.append(f'{indent}\t\t\t\t<dcscor:choiceParameter>{esc_xml(str(cpl.get("name", "")))}</dcscor:choiceParameter>')
|
||||
lines.append(f'{indent}\t\t\t\t<dcscor:value>{esc_xml(str(cpl.get("value", "")))}</dcscor:value>')
|
||||
mode = cpl.get('mode') or 'Auto'
|
||||
lines.append(f'{indent}\t\t\t\t<dcscor:mode xmlns:d8p1="http://v8.1c.ru/8.1/data/enterprise" xsi:type="d8p1:LinkedValueChangeMode">{mode}</dcscor:mode>')
|
||||
lines.append(f'{indent}\t\t\t</dcscor:item>')
|
||||
lines.append(f'{indent}\t\t</dcscor:value>')
|
||||
elif 'value' in item:
|
||||
val = item['value']
|
||||
if isinstance(val, bool):
|
||||
vstr = 'true' if val else 'false'
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:boolean">{vstr}</dcscor:value>')
|
||||
elif isinstance(val, (int, float)):
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:decimal">{val}</dcscor:value>')
|
||||
else:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:type="xs:string">{esc_xml(str(val))}</dcscor:value>')
|
||||
lines.append(f'{indent}\t</dcscor:item>')
|
||||
lines.append(f'{indent}</inputParameters>')
|
||||
|
||||
|
||||
def emit_field(lines, field_def, indent):
|
||||
if isinstance(field_def, str):
|
||||
f = parse_field_shorthand(field_def)
|
||||
@@ -587,6 +639,9 @@ def emit_field(lines, field_def, indent):
|
||||
# orderExpression — {expression, orderType, autoOrder}
|
||||
if field_def.get('orderExpression'):
|
||||
f['orderExpression'] = field_def['orderExpression']
|
||||
# inputParameters — массив элементов, типизированных по форме value
|
||||
if field_def.get('inputParameters') is not None:
|
||||
f['inputParameters'] = field_def['inputParameters']
|
||||
|
||||
lines.append(f'{indent}<field xsi:type="DataSetFieldField">')
|
||||
lines.append(f'{indent}\t<dataPath>{esc_xml(f["dataPath"])}</dataPath>')
|
||||
@@ -672,6 +727,10 @@ def emit_field(lines, field_def, indent):
|
||||
if f.get('presentationExpression'):
|
||||
lines.append(f'{indent}\t<presentationExpression>{esc_xml(f["presentationExpression"])}</presentationExpression>')
|
||||
|
||||
# InputParameters — в конце field
|
||||
if f.get('inputParameters'):
|
||||
emit_input_parameters(lines, f['inputParameters'], f'{indent}\t')
|
||||
|
||||
lines.append(f'{indent}</field>')
|
||||
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-decompile v0.11 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# skd-decompile v0.12 — Decompile 1C DCS Template.xml to JSON DSL (draft)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)]
|
||||
@@ -320,31 +320,67 @@ function Get-AppearanceDict {
|
||||
return $dict
|
||||
}
|
||||
|
||||
# Check field/parameter inputParameters block for non-empty ChoiceParameters/ChoiceParameterLinks
|
||||
# and add warnings (silent-drop visibility — данные действительно теряются, но видно в warnings.md).
|
||||
function Check-InputParameters {
|
||||
param($parentNode, [string]$loc)
|
||||
# Read <r:inputParameters> → JSON array. Returns $null если отсутствует или пустой.
|
||||
function Read-InputParameters {
|
||||
param($parentNode)
|
||||
$ip = $parentNode.SelectSingleNode("r:inputParameters", $ns)
|
||||
if (-not $ip) { return }
|
||||
if (-not $ip) { return $null }
|
||||
$result = @()
|
||||
foreach ($it in $ip.SelectNodes("dcscor:item", $ns)) {
|
||||
$entry = [ordered]@{}
|
||||
$useText = Get-Text $it "dcscor:use"
|
||||
$pName = Get-Text $it "dcscor:parameter"
|
||||
$entry['parameter'] = $pName
|
||||
if ($useText -eq 'false') { $entry['use'] = $false }
|
||||
$val = $it.SelectSingleNode("dcscor:value", $ns)
|
||||
if (-not $val) { continue }
|
||||
$vType = Get-LocalXsiType $val
|
||||
if ($vType -eq 'ChoiceParameters' -or $vType -eq 'ChoiceParameterLinks') {
|
||||
# Empty (no inner items) — silently skip; it's a no-op default.
|
||||
$inner = $val.SelectNodes("dcscor:item", $ns)
|
||||
if ($inner.Count -eq 0) { continue }
|
||||
$null = Add-Warning -kind "SilentDrop:$vType" -loc "$loc/inputParameters/$pName" -detail "Параметр выбора '$pName' ($vType с $($inner.Count) элементами) не воспроизводится в DSL"
|
||||
if ($val) {
|
||||
$vType = Get-LocalXsiType $val
|
||||
if ($vType -eq 'ChoiceParameters') {
|
||||
$cp = @()
|
||||
foreach ($cpItem in $val.SelectNodes("dcscor:item", $ns)) {
|
||||
$cpEntry = [ordered]@{ name = Get-Text $cpItem "dcscor:choiceParameter" }
|
||||
$values = @()
|
||||
foreach ($v in $cpItem.SelectNodes("dcscor:value", $ns)) { $values += $v.InnerText }
|
||||
$cpEntry['values'] = $values
|
||||
$cp += $cpEntry
|
||||
}
|
||||
$entry['choiceParameters'] = $cp
|
||||
} elseif ($vType -eq 'ChoiceParameterLinks') {
|
||||
$cpl = @()
|
||||
foreach ($cplItem in $val.SelectNodes("dcscor:item", $ns)) {
|
||||
$cplEntry = [ordered]@{
|
||||
name = Get-Text $cplItem "dcscor:choiceParameter"
|
||||
value = Get-Text $cplItem "dcscor:value"
|
||||
}
|
||||
$mode = Get-Text $cplItem "dcscor:mode"
|
||||
if ($mode) { $cplEntry['mode'] = $mode }
|
||||
$cpl += $cplEntry
|
||||
}
|
||||
$entry['choiceParameterLinks'] = $cpl
|
||||
} else {
|
||||
# Simple typed value
|
||||
$txt = $val.InnerText
|
||||
if ($vType -eq 'boolean') {
|
||||
$entry['value'] = ($txt -eq 'true')
|
||||
} elseif ($vType -eq 'decimal') {
|
||||
if ($txt -match '^-?\d+$') { $entry['value'] = [int]$txt }
|
||||
else { $entry['value'] = [double]$txt }
|
||||
} else {
|
||||
$entry['value'] = $txt
|
||||
}
|
||||
}
|
||||
}
|
||||
$result += $entry
|
||||
}
|
||||
if ($result.Count -eq 0) { return $null }
|
||||
return ,$result
|
||||
}
|
||||
|
||||
# Build a field JSON entry (shorthand if possible, object form otherwise)
|
||||
function Build-Field {
|
||||
param($fieldNode, [string]$loc)
|
||||
# Silent-drop detection (non-blocking warnings only)
|
||||
Check-InputParameters -parentNode $fieldNode -loc $loc
|
||||
# inputParameters теперь поддерживается в DSL — читается ниже в needsObject
|
||||
$inputParameters = Read-InputParameters -parentNode $fieldNode
|
||||
# orderExpression теперь поддерживается в DSL — читается ниже в needsObject
|
||||
$orderExprNode = $fieldNode.SelectSingleNode("r:orderExpression", $ns)
|
||||
$orderExpression = $null
|
||||
@@ -376,7 +412,7 @@ function Build-Field {
|
||||
|
||||
# Можно ли роль положить в shorthand-строку?
|
||||
$roleInString = $roleRendered -and $roleRendered.isString
|
||||
$needsObject = $title -or $appearance -or $presExpr -or ($typeShort -is [array]) -or ($roleRendered -and -not $roleInString) -or $orderExpression
|
||||
$needsObject = $title -or $appearance -or $presExpr -or ($typeShort -is [array]) -or ($roleRendered -and -not $roleInString) -or $orderExpression -or $inputParameters
|
||||
|
||||
if (-not $needsObject) {
|
||||
# shorthand: "Name: type @role K=V #restrict"
|
||||
@@ -405,6 +441,7 @@ function Build-Field {
|
||||
if ($typeShort) { $obj['type'] = $typeShort }
|
||||
if ($roleRendered) { $obj['role'] = $roleRendered.value }
|
||||
if ($orderExpression) { $obj['orderExpression'] = $orderExpression }
|
||||
if ($inputParameters) { $obj['inputParameters'] = $inputParameters }
|
||||
if ($restrictTokens) { $obj['restrict'] = ($restrictTokens | ForEach-Object { $_ -replace '^#','' }) }
|
||||
if ($presExpr) { $obj['presentationExpression'] = $presExpr }
|
||||
if ($appearance) { $obj['appearance'] = $appearance }
|
||||
@@ -485,8 +522,6 @@ function Get-StandardPeriodVariant {
|
||||
# Build parameter → shorthand or object form
|
||||
function Build-Parameter {
|
||||
param($pNode, [string]$loc)
|
||||
# Silent-drop detection
|
||||
Check-InputParameters -parentNode $pNode -loc $loc
|
||||
$name = Get-Text $pNode "r:name"
|
||||
$titleNode = $pNode.SelectSingleNode("r:title", $ns)
|
||||
$title = Get-MLText $titleNode
|
||||
|
||||
Reference in New Issue
Block a user