mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-07-20 01:20:59 +03:00
fix(skd-compile): Designer-compatible empty parameter values
Centralized empty-value handling: shorthand `=`, `= _`, `= null` and object-form `value: null` / `""` now serialize per type, matching what 1C Designer writes: - ref / no-type → <value xsi:nil="true"/> - string → <value xsi:type="xs:string"/> - date/decimal/boolean → typed zero (0001-01-01 / 0 / false) - StandardPeriod → Custom variant with zero dates - @valueList → omit <value> entirely Closes BUG-1 (StandardPeriod @autoDates) and BUG-2 (CatalogRef.X = _ producing invalid <value>_</value>) reported by titan team. New helpers Test-EmptyValue / Emit-EmptyValue (ps1) and is_empty_value / emit_empty_value (py) shared by Emit-ParamValue, availableValues loop, and explicit dataParameters emit. Shorthand regex .+ → .* so trailing `=` parses as empty. Reference: upload/erf/ПроверкаЭкранирования (live Designer dump). New test case empty-param-values covers all 10 type×sentinel combos; 3 existing snapshots regenerated to include the now-correct <value> tags. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
3eaa7ffa3b
commit
449f814d16
@@ -147,6 +147,8 @@ Shorthand: `"Имя [Заголовок]: тип = значение @флаги"
|
||||
|
||||
Объектная форма: `title`, `hidden: true`, `valueListAllowed: true`, `availableAsField: false`, `denyIncompleteValues: true`, `use: "Always"`.
|
||||
|
||||
Если значения по умолчанию нет — пропусти `=` в shorthand или укажи `"value": null` в объектной форме.
|
||||
|
||||
Список допустимых значений (availableValues):
|
||||
|
||||
```json
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# skd-compile v1.23 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.24 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[string]$DefinitionFile,
|
||||
@@ -375,8 +375,8 @@ function Parse-ParamShorthand {
|
||||
$s = ($s -replace '\s*\[[^\]]*\]\s*', ' ').Trim()
|
||||
}
|
||||
|
||||
# Split "Name: Type = Value"
|
||||
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.+))?$') {
|
||||
# Split "Name: Type = Value" — RHS may be empty (`= ` / `=`) → treated as empty value
|
||||
if ($s -match '^([^:]+):\s*(\S+)(\s*=\s*(.*))?$') {
|
||||
$result.name = $Matches[1].Trim()
|
||||
$result.type = Resolve-TypeStr ($Matches[2].Trim())
|
||||
if ($Matches[4]) {
|
||||
@@ -985,8 +985,9 @@ function Emit-SingleParam {
|
||||
X "`t`t</valueType>"
|
||||
}
|
||||
|
||||
# Value
|
||||
Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t"
|
||||
# Value — for valueListAllowed params Designer omits <value> when empty
|
||||
$vla = [bool]$parsed.valueListAllowed
|
||||
Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t" -valueListAllowed $vla
|
||||
|
||||
# Hidden implies useRestriction=true + availableAsField=false
|
||||
if ($parsed.hidden -eq $true) {
|
||||
@@ -1017,13 +1018,17 @@ function Emit-SingleParam {
|
||||
# AvailableValues
|
||||
if ($p -isnot [string] -and $p.availableValues) {
|
||||
foreach ($av in $p.availableValues) {
|
||||
$avVal = "$($av.value)"
|
||||
$avType = "xs:string"
|
||||
if ($avVal -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$avType = "dcscor:DesignTimeValue"
|
||||
}
|
||||
X "`t`t<availableValue>"
|
||||
X "`t`t`t<value xsi:type=`"$avType`">$(Esc-Xml $avVal)</value>"
|
||||
if (Test-EmptyValue $av.value) {
|
||||
Emit-EmptyValue -type $parsed.type -indent "`t`t`t" -tagPrefix "" -valueListAllowed $false
|
||||
} else {
|
||||
$avVal = "$($av.value)"
|
||||
$avType = "xs:string"
|
||||
if ($avVal -match '^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.') {
|
||||
$avType = "dcscor:DesignTimeValue"
|
||||
}
|
||||
X "`t`t`t<value xsi:type=`"$avType`">$(Esc-Xml $avVal)</value>"
|
||||
}
|
||||
# `title` accepted as synonym of `presentation` — both map to the same UI label.
|
||||
$avPres = if ($av.presentation) { $av.presentation } elseif ($av.title) { $av.title } else { "" }
|
||||
if ($avPres) {
|
||||
@@ -1107,10 +1112,52 @@ function Emit-Parameters {
|
||||
}
|
||||
}
|
||||
|
||||
function Emit-ParamValue {
|
||||
param([string]$type, $val, [string]$indent)
|
||||
function Test-EmptyValue {
|
||||
param($v)
|
||||
if ($null -eq $v) { return $true }
|
||||
$s = "$v".Trim()
|
||||
if ($s -eq "") { return $true }
|
||||
if ($s -eq "_") { return $true }
|
||||
if ($s.ToLowerInvariant() -eq "null") { return $true }
|
||||
return $false
|
||||
}
|
||||
|
||||
if ($null -eq $val) { return }
|
||||
function Emit-EmptyValue {
|
||||
param([string]$type, [string]$indent, [string]$tagPrefix = "", [bool]$valueListAllowed = $false)
|
||||
|
||||
if ($valueListAllowed) { return }
|
||||
$t = if ($null -eq $type) { "" } else { "$type" }
|
||||
$pf = $tagPrefix
|
||||
|
||||
if ($t -eq "") {
|
||||
X "$indent<${pf}value xsi:nil=`"true`"/>"
|
||||
} elseif ($t -eq "StandardPeriod") {
|
||||
X "$indent<${pf}value xsi:type=`"v8:StandardPeriod`">"
|
||||
X "$indent`t<v8:variant xsi:type=`"v8:StandardPeriodVariant`">Custom</v8:variant>"
|
||||
X "$indent`t<v8:startDate>0001-01-01T00:00:00</v8:startDate>"
|
||||
X "$indent`t<v8:endDate>0001-01-01T00:00:00</v8:endDate>"
|
||||
X "$indent</${pf}value>"
|
||||
} elseif ($t -match '^string') {
|
||||
X "$indent<${pf}value xsi:type=`"xs:string`"/>"
|
||||
} elseif ($t -match '^date') {
|
||||
X "$indent<${pf}value xsi:type=`"xs:dateTime`">0001-01-01T00:00:00</${pf}value>"
|
||||
} elseif ($t -match '^decimal') {
|
||||
X "$indent<${pf}value xsi:type=`"xs:decimal`">0</${pf}value>"
|
||||
} elseif ($t -eq "boolean") {
|
||||
X "$indent<${pf}value xsi:type=`"xs:boolean`">false</${pf}value>"
|
||||
} else {
|
||||
# Ref types or unknown — safe nil
|
||||
X "$indent<${pf}value xsi:nil=`"true`"/>"
|
||||
}
|
||||
}
|
||||
|
||||
function Emit-ParamValue {
|
||||
param([string]$type, $val, [string]$indent, [bool]$valueListAllowed = $false)
|
||||
|
||||
if (Test-EmptyValue $val) {
|
||||
Emit-EmptyValue -type $type -indent $indent -tagPrefix "" -valueListAllowed $valueListAllowed
|
||||
return
|
||||
}
|
||||
|
||||
$valStr = "$val"
|
||||
|
||||
@@ -1868,6 +1915,8 @@ function Emit-DataParameters {
|
||||
# Value
|
||||
if ($dp.nilValue -eq $true) {
|
||||
X "$indent`t`t<dcscor:value xsi:nil=`"true`"/>"
|
||||
} elseif (Test-EmptyValue $dp.value) {
|
||||
Emit-EmptyValue -type "$($dp.valueType)" -indent "$indent`t`t" -tagPrefix "dcscor:" -valueListAllowed $false
|
||||
} elseif ($null -ne $dp.value) {
|
||||
$vtype = "$($dp.valueType)"
|
||||
if ($dp.value -is [PSCustomObject] -and $dp.value.variant) {
|
||||
@@ -2206,7 +2255,7 @@ function Emit-SettingsVariants {
|
||||
}
|
||||
$dpItem | Add-Member -NotePropertyName "value" -NotePropertyValue @{ variant = $variant }
|
||||
if ($variant -ne 'Custom') { $hasMeaningfulValue = $true }
|
||||
} elseif ($null -ne $ap.value -and "$($ap.value)" -ne '') {
|
||||
} elseif (-not (Test-EmptyValue $ap.value)) {
|
||||
$dpItem | Add-Member -NotePropertyName "value" -NotePropertyValue $ap.value
|
||||
$dpItem | Add-Member -NotePropertyName "valueType" -NotePropertyValue "$($ap.type)"
|
||||
$hasMeaningfulValue = $true
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# skd-compile v1.23 — Compile 1C DCS from JSON
|
||||
# skd-compile v1.24 — Compile 1C DCS from JSON
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
import argparse
|
||||
import json
|
||||
@@ -282,8 +282,8 @@ def parse_param_shorthand(s):
|
||||
result['title'] = m.group(1).strip()
|
||||
s = re.sub(r'\s*\[[^\]]*\]\s*', ' ', s).strip()
|
||||
|
||||
# Split "Name: Type = Value"
|
||||
m = re.match(r'^([^:]+):\s*(\S+)(\s*=\s*(.+))?$', s)
|
||||
# Split "Name: Type = Value" — RHS may be empty (`= ` / `=`) → treated as empty value
|
||||
m = re.match(r'^([^:]+):\s*(\S+)(\s*=\s*(.*))?$', s)
|
||||
if m:
|
||||
result['name'] = m.group(1).strip()
|
||||
result['type'] = resolve_type_str(m.group(2).strip())
|
||||
@@ -790,8 +790,49 @@ def emit_total_fields(lines, defn):
|
||||
|
||||
# === Parameters ===
|
||||
|
||||
def emit_param_value(lines, type_str, val, indent):
|
||||
if val is None:
|
||||
def is_empty_value(v):
|
||||
if v is None:
|
||||
return True
|
||||
s = str(v).strip()
|
||||
if s == '':
|
||||
return True
|
||||
if s == '_':
|
||||
return True
|
||||
if s.lower() == 'null':
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def emit_empty_value(lines, type_str, indent, tag_prefix='', value_list_allowed=False):
|
||||
if value_list_allowed:
|
||||
return
|
||||
t = type_str or ''
|
||||
pf = tag_prefix
|
||||
|
||||
if t == '':
|
||||
lines.append(f'{indent}<{pf}value xsi:nil="true"/>')
|
||||
elif t == 'StandardPeriod':
|
||||
lines.append(f'{indent}<{pf}value xsi:type="v8:StandardPeriod">')
|
||||
lines.append(f'{indent}\t<v8:variant xsi:type="v8:StandardPeriodVariant">Custom</v8:variant>')
|
||||
lines.append(f'{indent}\t<v8:startDate>0001-01-01T00:00:00</v8:startDate>')
|
||||
lines.append(f'{indent}\t<v8:endDate>0001-01-01T00:00:00</v8:endDate>')
|
||||
lines.append(f'{indent}</{pf}value>')
|
||||
elif re.match(r'^string', t):
|
||||
lines.append(f'{indent}<{pf}value xsi:type="xs:string"/>')
|
||||
elif re.match(r'^date', t):
|
||||
lines.append(f'{indent}<{pf}value xsi:type="xs:dateTime">0001-01-01T00:00:00</{pf}value>')
|
||||
elif re.match(r'^decimal', t):
|
||||
lines.append(f'{indent}<{pf}value xsi:type="xs:decimal">0</{pf}value>')
|
||||
elif t == 'boolean':
|
||||
lines.append(f'{indent}<{pf}value xsi:type="xs:boolean">false</{pf}value>')
|
||||
else:
|
||||
# Ref types or unknown — safe nil
|
||||
lines.append(f'{indent}<{pf}value xsi:nil="true"/>')
|
||||
|
||||
|
||||
def emit_param_value(lines, type_str, val, indent, value_list_allowed=False):
|
||||
if is_empty_value(val):
|
||||
emit_empty_value(lines, type_str, indent, '', value_list_allowed)
|
||||
return
|
||||
|
||||
val_str = str(val)
|
||||
@@ -847,8 +888,9 @@ def emit_single_param(lines, p, parsed):
|
||||
emit_value_type(lines, parsed['type'], '\t\t\t')
|
||||
lines.append('\t\t</valueType>')
|
||||
|
||||
# Value
|
||||
emit_param_value(lines, parsed.get('type', ''), parsed.get('value'), '\t\t')
|
||||
# Value — for valueListAllowed params Designer omits <value> when empty
|
||||
vla = bool(parsed.get('valueListAllowed'))
|
||||
emit_param_value(lines, parsed.get('type', ''), parsed.get('value'), '\t\t', vla)
|
||||
|
||||
# Hidden implies useRestriction=true + availableAsField=false
|
||||
if parsed.get('hidden') is True:
|
||||
@@ -876,12 +918,15 @@ def emit_single_param(lines, p, parsed):
|
||||
# AvailableValues
|
||||
if p is not None and not isinstance(p, str) and p.get('availableValues'):
|
||||
for av in p['availableValues']:
|
||||
av_val = str(av.get('value', ''))
|
||||
av_type = 'xs:string'
|
||||
if re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', av_val):
|
||||
av_type = 'dcscor:DesignTimeValue'
|
||||
lines.append('\t\t<availableValue>')
|
||||
lines.append(f'\t\t\t<value xsi:type="{av_type}">{esc_xml(av_val)}</value>')
|
||||
if is_empty_value(av.get('value')):
|
||||
emit_empty_value(lines, parsed.get('type', ''), '\t\t\t', '', False)
|
||||
else:
|
||||
av_val = str(av.get('value', ''))
|
||||
av_type = 'xs:string'
|
||||
if re.match(r'^(Перечисление|Справочник|ПланСчетов|Документ|ПланВидовХарактеристик|ПланВидовРасчета)\.', av_val):
|
||||
av_type = 'dcscor:DesignTimeValue'
|
||||
lines.append(f'\t\t\t<value xsi:type="{av_type}">{esc_xml(av_val)}</value>')
|
||||
# `title` accepted as synonym of `presentation` — both map to the same UI label.
|
||||
av_pres = av.get('presentation') or av.get('title') or ''
|
||||
if av_pres:
|
||||
@@ -1585,6 +1630,8 @@ def emit_data_parameters(lines, items, indent):
|
||||
# Value
|
||||
if dp.get('nilValue') is True:
|
||||
lines.append(f'{indent}\t\t<dcscor:value xsi:nil="true"/>')
|
||||
elif is_empty_value(dp.get('value')):
|
||||
emit_empty_value(lines, str(dp.get('valueType') or ''), f'{indent}\t\t', 'dcscor:', False)
|
||||
elif dp.get('value') is not None:
|
||||
val = dp['value']
|
||||
vtype = str(dp.get('valueType') or '')
|
||||
@@ -1853,7 +1900,7 @@ def emit_settings_variants(lines, defn):
|
||||
item['value'] = {'variant': variant}
|
||||
if variant != 'Custom':
|
||||
has_meaningful_value = True
|
||||
elif ap.get('value') is not None and str(ap.get('value')) != '':
|
||||
elif not is_empty_value(ap.get('value')):
|
||||
item['value'] = ap['value']
|
||||
item['valueType'] = str(ap.get('type') or '')
|
||||
has_meaningful_value = True
|
||||
|
||||
Reference in New Issue
Block a user