diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md index 1886d419..8a8853bc 100644 --- a/.claude/skills/skd-compile/SKILL.md +++ b/.claude/skills/skd-compile/SKILL.md @@ -147,6 +147,8 @@ Shorthand: `"Имя [Заголовок]: тип = значение @флаги" Объектная форма: `title`, `hidden: true`, `valueListAllowed: true`, `availableAsField: false`, `denyIncompleteValues: true`, `use: "Always"`. +Если значения по умолчанию нет — пропусти `=` в shorthand или укажи `"value": null` в объектной форме. + Список допустимых значений (availableValues): ```json diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index 671c5486..df537bbe 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.ps1 +++ b/.claude/skills/skd-compile/scripts/skd-compile.ps1 @@ -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" } - # Value - Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t" + # Value — for valueListAllowed params Designer omits 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" - X "`t`t`t$(Esc-Xml $avVal)" + 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$(Esc-Xml $avVal)" + } # `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`tCustom" + X "$indent`t0001-01-01T00:00:00" + X "$indent`t0001-01-01T00:00:00" + X "$indent" + } 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" + } elseif ($t -match '^decimal') { + X "$indent<${pf}value xsi:type=`"xs:decimal`">0" + } elseif ($t -eq "boolean") { + X "$indent<${pf}value xsi:type=`"xs:boolean`">false" + } 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" + } 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 diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index 5f8384c8..38efd58f 100644 --- a/.claude/skills/skd-compile/scripts/skd-compile.py +++ b/.claude/skills/skd-compile/scripts/skd-compile.py @@ -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}\tCustom') + lines.append(f'{indent}\t0001-01-01T00:00:00') + lines.append(f'{indent}\t0001-01-01T00:00:00') + lines.append(f'{indent}') + 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') + elif re.match(r'^decimal', t): + lines.append(f'{indent}<{pf}value xsi:type="xs:decimal">0') + elif t == 'boolean': + lines.append(f'{indent}<{pf}value xsi:type="xs:boolean">false') + 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') - # Value - emit_param_value(lines, parsed.get('type', ''), parsed.get('value'), '\t\t') + # Value — for valueListAllowed params Designer omits 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') - lines.append(f'\t\t\t{esc_xml(av_val)}') + 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{esc_xml(av_val)}') # `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') + 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 diff --git a/tests/skills/cases/skd-compile/empty-param-values.json b/tests/skills/cases/skd-compile/empty-param-values.json new file mode 100644 index 00000000..2131d040 --- /dev/null +++ b/tests/skills/cases/skd-compile/empty-param-values.json @@ -0,0 +1,27 @@ +{ + "name": "Параметры с пустыми значениями (все типы, разные sentinel-формы)", + "params": { "outputPath": "Template.xml" }, + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ 1 КАК Поле1", + "fields": ["Поле1: число(1,0)"] + }], + "parameters": [ + "Параметр1", + "Параметр2: string =", + "ПараметрСписок: EnumRef.СтатусТеста @valueList = _", + "ПараметрСсылка: CatalogRef.ПлоскийПростой", + "ПараметрДата: date = null", + { "name": "ПараметрЧисло", "type": "decimal", "value": null }, + "ПараметрБулево: boolean = ", + "ПараметрСтандартныйПериод: StandardPeriod = _", + { "name": "ПараметрТипНеЗадан", "value": null }, + "ПараметрСписокСтрок: string @valueList" + ] + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"] + } +} diff --git a/tests/skills/cases/skd-compile/snapshots/auto-data-parameters/Template.xml b/tests/skills/cases/skd-compile/snapshots/auto-data-parameters/Template.xml index c1f7b2ca..d2270b2f 100644 --- a/tests/skills/cases/skd-compile/snapshots/auto-data-parameters/Template.xml +++ b/tests/skills/cases/skd-compile/snapshots/auto-data-parameters/Template.xml @@ -77,6 +77,11 @@ v8:StandardPeriod + + Custom + 0001-01-01T00:00:00 + 0001-01-01T00:00:00 + Флаг @@ -129,6 +134,7 @@ Variable + Валюта diff --git a/tests/skills/cases/skd-compile/snapshots/empty-param-values/Template.xml b/tests/skills/cases/skd-compile/snapshots/empty-param-values/Template.xml new file mode 100644 index 00000000..624a0bb3 --- /dev/null +++ b/tests/skills/cases/skd-compile/snapshots/empty-param-values/Template.xml @@ -0,0 +1,131 @@ + + + + ИсточникДанных1 + Local + + + Основной + + Поле1 + Поле1 + + xs:decimal + + 1 + 0 + Any + + + + ИсточникДанных1 + ВЫБРАТЬ 1 КАК Поле1 + + + Параметр1 + + + + Параметр2 + + xs:string + + 0 + Variable + + + + + + ПараметрСписок + + d5p1:EnumRef.СтатусТеста + + true + + + ПараметрСсылка + + d5p1:CatalogRef.ПлоскийПростой + + + + + ПараметрДата + + xs:dateTime + + Date + + + 0001-01-01T00:00:00 + + + ПараметрЧисло + + decimal + + 0 + + + ПараметрБулево + + xs:boolean + + false + + + ПараметрСтандартныйПериод + + v8:StandardPeriod + + + Custom + 0001-01-01T00:00:00 + 0001-01-01T00:00:00 + + + + ПараметрТипНеЗадан + + + + ПараметрСписокСтрок + + xs:string + + 0 + Variable + + + true + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/skd-compile/snapshots/multi-lang-title/Template.xml b/tests/skills/cases/skd-compile/snapshots/multi-lang-title/Template.xml index ba89d296..0e810997 100644 --- a/tests/skills/cases/skd-compile/snapshots/multi-lang-title/Template.xml +++ b/tests/skills/cases/skd-compile/snapshots/multi-lang-title/Template.xml @@ -71,6 +71,11 @@ v8:StandardPeriod + + Custom + 0001-01-01T00:00:00 + 0001-01-01T00:00:00 + Основной diff --git a/tests/skills/cases/skd-compile/snapshots/with-parameters/Template.xml b/tests/skills/cases/skd-compile/snapshots/with-parameters/Template.xml index 496f1606..4f7e6693 100644 --- a/tests/skills/cases/skd-compile/snapshots/with-parameters/Template.xml +++ b/tests/skills/cases/skd-compile/snapshots/with-parameters/Template.xml @@ -104,6 +104,7 @@ d5p1:CatalogRef.Организации + Основной