From ff2d8513c422b70c97549893de50be5605d4d297 Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Wed, 20 May 2026 12:52:26 +0300 Subject: [PATCH] feat(skd-compile): time type, string(N,fix), and composite type parameters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Calibrated against live Designer output in upload/erf/ПроверкаЭкранирования. - New type 'time' (synonym 'время'): xs:dateTime with DateFractions=Time for time-of-day values. Designer uses the same xs:dateTime XSD type as date/dateTime — only DateFractions differs. Empty value: typed-zero 0001-01-01T00:00:00 (same as dateTime). - Extended string regex to accept (N,fix) → AllowedLength=Fixed (was Variable-only). Non-empty fixed-string values are emitted as-given without space-padding to Length — the platform handles padding on save. - Composite types in parameters (array of types in object form, e.g. ["string(10,fix)", "CatalogRef.X"]) now work end-to-end: valueType emits each type with its qualifiers, and empty composite values serialize as matching Designer. Test case empty-param-values extended with 5 new params covering all three additions. Snapshot validated by skd-validate. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../skd-compile/scripts/skd-compile.ps1 | 39 ++++++++++---- .../skills/skd-compile/scripts/skd-compile.py | 39 ++++++++++---- .../cases/skd-compile/empty-param-values.json | 7 ++- .../snapshots/empty-param-values/Template.xml | 54 +++++++++++++++++++ 4 files changed, 119 insertions(+), 20 deletions(-) diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index 4fa26c2a..00c37ad9 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.25 — Compile 1C DCS from JSON +# skd-compile v1.26 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -143,6 +143,7 @@ $script:typeSynonyms["строка"] = "string" $script:typeSynonyms["булево"] = "boolean" $script:typeSynonyms["дата"] = "date" $script:typeSynonyms["датавремя"] = "dateTime" +$script:typeSynonyms["время"] = "time" $script:typeSynonyms["стандартныйпериод"] = "StandardPeriod" # English canonical (lowercase for lookup) $script:typeSynonyms["bool"] = "boolean" @@ -219,13 +220,14 @@ function Emit-SingleValueType { return } - # string or string(N) - if ($typeStr -match '^string(\((\d+)\))?$') { + # string, string(N), string(N,fix) — fix → AllowedLength=Fixed + if ($typeStr -match '^string(\((\d+)(,(fix|fixed))?\))?$') { $len = if ($Matches[2]) { $Matches[2] } else { "0" } + $al = if ($Matches[4]) { "Fixed" } else { "Variable" } X "$indentxs:string" X "$indent" X "$indent`t$len" - X "$indent`tVariable" + X "$indent`t$al" X "$indent" return } @@ -253,11 +255,12 @@ function Emit-SingleValueType { return } - # date / dateTime - if ($typeStr -match '^(date|dateTime)$') { + # date / dateTime / time — all use xs:dateTime, differ only in DateFractions + if ($typeStr -match '^(date|dateTime|time)$') { $fractions = switch ($typeStr) { "date" { "Date" } "dateTime" { "DateTime" } + "time" { "Time" } } X "$indentxs:dateTime" X "$indent" @@ -996,7 +999,15 @@ function Emit-SingleParam { # 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 + if ($parsed.type -is [array] -or $parsed.type -is [System.Collections.IList]) { + # Composite type — Designer writes xsi:nil for any empty composite; + # non-empty composite values are uncommon and would need per-type tagging. + if (Test-EmptyValue $parsed.value) { + if (-not $vla) { X "`t`t" } + } + } else { + Emit-ParamValue -type $parsed.type -val $parsed.value -indent "`t`t" -valueListAllowed $vla + } # Hidden implies useRestriction=true + availableAsField=false if ($parsed.hidden -eq $true) { @@ -1073,9 +1084,19 @@ function Emit-Parameters { if ($p -is [string]) { $parsed = Parse-ParamShorthand $p } else { + # Composite type: ["string(10,fix)", "CatalogRef.X"] → array of resolved + # strings; emit-valueType handles arrays, empty value falls through to nil. + $resolvedType = "" + if ($p.type) { + if ($p.type -is [array] -or $p.type -is [System.Collections.IList]) { + $resolvedType = @($p.type | ForEach-Object { Resolve-TypeStr "$_" }) + } else { + $resolvedType = Resolve-TypeStr "$($p.type)" + } + } $parsed = @{ name = "$($p.name)" - type = if ($p.type) { Resolve-TypeStr "$($p.type)" } else { "" } + type = $resolvedType value = $p.value autoDates = $false } @@ -1148,7 +1169,7 @@ function Emit-EmptyValue { X "$indent" } elseif ($t -match '^string') { X "$indent<${pf}value xsi:type=`"xs:string`"/>" - } elseif ($t -match '^date') { + } elseif ($t -match '^(date|time)') { 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" diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index bf2e5e6a..63c33b5b 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.25 — Compile 1C DCS from JSON +# skd-compile v1.26 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -74,6 +74,7 @@ TYPE_SYNONYMS = { "\u0431\u0443\u043b\u0435\u0432\u043e": "boolean", "\u0434\u0430\u0442\u0430": "date", "\u0434\u0430\u0442\u0430\u0432\u0440\u0435\u043c\u044f": "dateTime", + "\u0432\u0440\u0435\u043c\u044f": "time", "\u0441\u0442\u0430\u043d\u0434\u0430\u0440\u0442\u043d\u044b\u0439\u043f\u0435\u0440\u0438\u043e\u0434": "StandardPeriod", # English canonical (lowercase) "bool": "boolean", @@ -148,14 +149,15 @@ def emit_single_value_type(lines, type_str, indent): lines.append(f'{indent}xs:boolean') return - # string or string(N) - m = re.match(r'^string(\((\d+)\))?$', type_str) + # string, string(N), string(N,fix) — fix → AllowedLength=Fixed + m = re.match(r'^string(\((\d+)(,(fix|fixed))?\))?$', type_str) if m: length = m.group(2) if m.group(2) else '0' + al = 'Fixed' if m.group(4) else 'Variable' lines.append(f'{indent}xs:string') lines.append(f'{indent}') lines.append(f'{indent}\t{length}') - lines.append(f'{indent}\tVariable') + lines.append(f'{indent}\t{al}') lines.append(f'{indent}') return @@ -181,10 +183,10 @@ def emit_single_value_type(lines, type_str, indent): lines.append(f'{indent}') return - # date / dateTime - m = re.match(r'^(date|dateTime)$', type_str) + # date / dateTime / time — all use xs:dateTime, differ only in DateFractions + m = re.match(r'^(date|dateTime|time)$', type_str) if m: - fractions_map = {'date': 'Date', 'dateTime': 'DateTime'} + fractions_map = {'date': 'Date', 'dateTime': 'DateTime', 'time': 'Time'} fractions = fractions_map[type_str] lines.append(f'{indent}xs:dateTime') lines.append(f'{indent}') @@ -827,7 +829,7 @@ def emit_empty_value(lines, type_str, indent, tag_prefix='', value_list_allowed= 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): + elif re.match(r'^(date|time)', 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') @@ -898,7 +900,15 @@ def emit_single_param(lines, p, parsed): # 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) + p_type = parsed.get('type', '') + if isinstance(p_type, (list, tuple)): + # Composite type — Designer writes xsi:nil for any empty composite; + # non-empty composite values are uncommon and would need per-type tagging. + if is_empty_value(parsed.get('value')): + if not vla: + lines.append('\t\t') + else: + emit_param_value(lines, p_type, parsed.get('value'), '\t\t', vla) # Hidden implies useRestriction=true + availableAsField=false if parsed.get('hidden') is True: @@ -971,9 +981,18 @@ def emit_parameters(lines, defn): if isinstance(p, str): parsed = parse_param_shorthand(p) else: + # Composite type: ["string(10,fix)", "CatalogRef.X"] → list of resolved + # strings; emit_value_type handles lists, empty value falls through to nil. + raw_type = p.get('type') + if isinstance(raw_type, (list, tuple)): + resolved_type = [resolve_type_str(str(t)) for t in raw_type] + elif raw_type: + resolved_type = resolve_type_str(str(raw_type)) + else: + resolved_type = '' parsed = { 'name': str(p.get('name', '')), - 'type': resolve_type_str(str(p['type'])) if p.get('type') else '', + 'type': resolved_type, 'value': p.get('value'), 'autoDates': False, } diff --git a/tests/skills/cases/skd-compile/empty-param-values.json b/tests/skills/cases/skd-compile/empty-param-values.json index 2131d040..e0091f9f 100644 --- a/tests/skills/cases/skd-compile/empty-param-values.json +++ b/tests/skills/cases/skd-compile/empty-param-values.json @@ -17,7 +17,12 @@ "ПараметрБулево: boolean = ", "ПараметрСтандартныйПериод: StandardPeriod = _", { "name": "ПараметрТипНеЗадан", "value": null }, - "ПараметрСписокСтрок: string @valueList" + "ПараметрСписокСтрок: string @valueList", + "ПараметрВремяСЗначением: time = 0001-01-01T12:30:00", + "ПараметрВремяПусто: time", + "ПараметрСтрокаФиксСЗначением: string(10,fix) = АБВ", + "ПараметрСтрокаФиксПусто: string(10,fix)", + { "name": "СоставнойТип", "type": ["string(10,fix)", "CatalogRef.ПлоскийПростой"], "value": null } ] }, "validatePath": "Template.xml", 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 index 40d32cb4..75000f69 100644 --- a/tests/skills/cases/skd-compile/snapshots/empty-param-values/Template.xml +++ b/tests/skills/cases/skd-compile/snapshots/empty-param-values/Template.xml @@ -112,6 +112,60 @@ true + + ПараметрВремяСЗначением + + xs:dateTime + + Time + + + 0001-01-01T12:30:00 + + + ПараметрВремяПусто + + xs:dateTime + + Time + + + 0001-01-01T00:00:00 + + + ПараметрСтрокаФиксСЗначением + + xs:string + + 10 + Fixed + + + АБВ + + + ПараметрСтрокаФиксПусто + + xs:string + + 10 + Fixed + + + + + + СоставнойТип + + xs:string + + 10 + Fixed + + d5p1:CatalogRef.ПлоскийПростой + + + Основной