diff --git a/.claude/skills/skd-compile/SKILL.md b/.claude/skills/skd-compile/SKILL.md index 9c5791a8..aefdc940 100644 --- a/.claude/skills/skd-compile/SKILL.md +++ b/.claude/skills/skd-compile/SKILL.md @@ -152,11 +152,19 @@ Shorthand: `"Имя [Заголовок]: тип = значение @флаги" Флаги shorthand: - `@autoDates` — добавляет к параметру StandardPeriod пару дат `НачалоПериода`/`КонецПериода`, вычисляемых из него. Используй их в тексте запроса как `&НачалоПериода`/`&КонецПериода`; пользователь выбирает только сам период. По умолчанию сам параметр получает `use=Always` и `denyIncompleteValues=true` (чтобы производные даты всегда были заполнены); в объектной форме можно явно переопределить. -- `@valueList` — `true` — разрешает передавать список значений +- `@valueList` — `true` — разрешает передавать список значений (при значении-списке ниже подразумевается автоматически) - `@hidden` — скрытый параметр: `availableAsField=false` + исключается из `"dataParameters": "auto"` Объектная форма: `title`, `hidden: true`, `valueListAllowed: true`, `availableAsField: false`, `denyIncompleteValues: true`, `use: "Always"`. +Значение-список: несколько значений по умолчанию через запятую в `значение` (для запятой внутри значения — кавычки `'...'`). В объектной форме — массив в `value`. + +```json +"parameters": [ + "Виды: ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные = ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры" +] +``` + Если значения по умолчанию нет — пропусти `=` в shorthand или укажи `"value": null` в объектной форме. Список допустимых значений (availableValues): diff --git a/.claude/skills/skd-compile/scripts/skd-compile.ps1 b/.claude/skills/skd-compile/scripts/skd-compile.ps1 index 1680ecde..3e33c149 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.104 — Compile 1C DCS from JSON +# skd-compile v1.105 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -475,6 +475,31 @@ function Parse-TotalShorthand { # --- 7. Parameter shorthand parser --- +function Split-ValueListCsv { + # Split on top-level commas (respecting 'single'/"double" quotes), strip quotes, + # drop empties. No ':' handling — values may contain colons (dateTime). + param([string]$s) + $result = @() + if ($null -eq $s) { return ,$result } + $items = @() + $buf = New-Object System.Text.StringBuilder + $inQuote = $null + for ($i = 0; $i -lt $s.Length; $i++) { + $ch = $s[$i] + if ($inQuote) { [void]$buf.Append($ch); if ($ch -eq $inQuote) { $inQuote = $null } } + elseif ($ch -eq "'" -or $ch -eq '"') { $inQuote = $ch; [void]$buf.Append($ch) } + elseif ($ch -eq ',') { $items += $buf.ToString(); [void]$buf.Clear() } + else { [void]$buf.Append($ch) } + } + if ($buf.Length -gt 0) { $items += $buf.ToString() } + foreach ($raw in $items) { + $t = $raw.Trim() + if ($t.Length -ge 2 -and (($t[0] -eq "'" -and $t[-1] -eq "'") -or ($t[0] -eq '"' -and $t[-1] -eq '"'))) { $t = $t.Substring(1, $t.Length - 2) } + if ($t -ne "") { $result += $t } + } + return ,$result +} + function Parse-ParamShorthand { param([string]$s) @@ -509,7 +534,17 @@ function Parse-ParamShorthand { $result.name = $Matches[1].Trim() $result.type = Resolve-TypeStr ($Matches[2].Trim()) if ($Matches[4]) { - $result.value = $Matches[4].Trim() + $rhs = $Matches[4].Trim() + $items = Split-ValueListCsv $rhs + if ($items.Count -ge 2) { + # Multi-value default → list; valueListAllowed implied + $result.value = $items + $result.valueListAllowed = $true + } elseif ($items.Count -eq 1) { + $result.value = $items[0] + } else { + $result.value = $rhs + } } } else { $result.name = $s.Trim() diff --git a/.claude/skills/skd-compile/scripts/skd-compile.py b/.claude/skills/skd-compile/scripts/skd-compile.py index a3131746..09da5e66 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.104 — Compile 1C DCS from JSON +# skd-compile v1.105 — Compile 1C DCS from JSON # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import json @@ -325,6 +325,39 @@ def parse_total_shorthand(s): # --- Parameter shorthand parser --- +def split_value_list_csv(s): + """Split on top-level commas (respecting single/double quotes), strip quotes, + drop empties. No ':' handling — values may contain colons (dateTime).""" + result = [] + if s is None: + return result + items = [] + buf = [] + in_quote = None + for ch in s: + if in_quote: + buf.append(ch) + if ch == in_quote: + in_quote = None + elif ch in ("'", '"'): + in_quote = ch + buf.append(ch) + elif ch == ',': + items.append("".join(buf)) + buf = [] + else: + buf.append(ch) + if buf: + items.append("".join(buf)) + for raw in items: + t = raw.strip() + if len(t) >= 2 and ((t[0] == "'" and t[-1] == "'") or (t[0] == '"' and t[-1] == '"')): + t = t[1:-1] + if t != "": + result.append(t) + return result + + def parse_param_shorthand(s): result = {'name': '', 'type': '', 'value': None, 'autoDates': False, 'title': None} @@ -355,7 +388,16 @@ def parse_param_shorthand(s): result['name'] = m.group(1).strip() result['type'] = resolve_type_str(m.group(2).strip()) if m.group(4): - result['value'] = m.group(4).strip() + rhs = m.group(4).strip() + items = split_value_list_csv(rhs) + if len(items) >= 2: + # Multi-value default → list; valueListAllowed implied + result['value'] = items + result['valueListAllowed'] = True + elif len(items) == 1: + result['value'] = items[0] + else: + result['value'] = rhs else: result['name'] = s.strip() diff --git a/.claude/skills/skd-edit/SKILL.md b/.claude/skills/skd-edit/SKILL.md index 3d44815d..fc8c4bf9 100644 --- a/.claude/skills/skd-edit/SKILL.md +++ b/.claude/skills/skd-edit/SKILL.md @@ -91,6 +91,13 @@ Shorthand: `"Имя [Заголовок]: тип = значение [availableVa - `@autoDates` — генерирует пару скрытых параметров `ДатаНачала`/`ДатаОкончания` для StandardPeriod-параметра. - `@hidden` — скрывает параметр от пользовательских настроек (для параметров-констант, используемых в запросе). - `@always` — параметр всегда подставляется в запрос. Часто вместе с `@hidden`, но используется и отдельно (для видимых обязательных параметров типа отчётного периода). +- `@valueList` — разрешает передавать в параметр список значений (при значении-списке ниже подразумевается автоматически, отдельно указывать не обязательно). + +Значение-список: несколько значений по умолчанию задаются через запятую в `значение`. Для запятой внутри одного значения — кавычки `'...'`. + +``` +"Виды [Виды субконто]: ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные = ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры" +``` ``` "ПС: CatalogRef.Контрагенты = Справочник.Контрагенты.ПустаяСсылка @hidden" @@ -115,6 +122,7 @@ Shorthand: `"ИмяПараметра [Заголовок] [ключ=значе "ПериодОтчета [Отчетный период]" # только title "ПорядокОкругления availableValue=Перечисление.Округления.Окр1: руб., Перечисление.Округления.Окр1000: тыс." "СчетПС value=ПланСчетов.Хозрасчетный.КассаПредприятия" +"Виды value=ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры" "Контрагент @hidden @always" ``` @@ -122,7 +130,7 @@ Shorthand: `"ИмяПараметра [Заголовок] [ключ=значе `availableValue=` **заменяет весь список** допустимых значений (старые удаляются). Формат и кавычки — те же, что в `add-parameter`. -`value=` заменяет значение параметра (тип значения подбирается автоматически по объявленному типу параметра). +`value=` заменяет значение параметра. Несколько значений через запятую → **список значений** (заменяет все прежние); для запятой внутри значения — кавычки `'...'`. Флаги `@hidden` / `@always` — те же, что и в `add-parameter`. Идемпотентны. diff --git a/.claude/skills/skd-edit/scripts/skd-edit.ps1 b/.claude/skills/skd-edit/scripts/skd-edit.ps1 index 5beac7f9..ff4d9086 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.ps1 +++ b/.claude/skills/skd-edit/scripts/skd-edit.ps1 @@ -1,4 +1,4 @@ -# skd-edit v1.24 — Atomic 1C DCS editor +# skd-edit v1.25 — Atomic 1C DCS editor # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [Parameter(Mandatory)] @@ -378,7 +378,19 @@ function Parse-ParamShorthand { $hasEq = $null -ne $Matches[3] $rhs = $Matches[4] if ($hasEq) { - $result.value = if ($rhs) { $rhs.Trim() } else { "" } + if ($rhs -and $rhs.Trim()) { + $items = Parse-ValueList $rhs.Trim() + if ($items.Count -ge 2) { + # Multi-value default → list; valueListAllowed implied + $result.value = $items + $result.valueListAllowed = $true + } else { + # Scalar (single item, quotes stripped) or empty sentinel + $result.value = if ($items.Count -eq 1) { $items[0] } else { "" } + } + } else { + $result.value = "" + } } } else { $result.name = $s.Trim() @@ -679,16 +691,13 @@ function Parse-OutputParamShorthand { return @{ key = $s.Trim(); value = "" } } -function Parse-AvailableValueList { - # Returns array of @{ value=...; presentation=... } from comma-separated list. - # Items can use 'single' or "double" quotes (stripped). Quoted spans preserve commas/colons. +function Split-QuotedCsv { + # Splits on top-level commas, respecting 'single' and "double" quoted spans. + # Returns raw (un-stripped, un-trimmed) item spans. Used by both availableValue + # (value:presentation) and value-list (values only) parsing. param([string]$s) - - $result = @() - if (-not $s) { return ,$result } - - # Tokenize by ',' respecting quoted spans $items = @() + if ($null -eq $s) { return ,$items } $buf = New-Object System.Text.StringBuilder $inQuote = $null for ($i = 0; $i -lt $s.Length; $i++) { @@ -707,16 +716,42 @@ function Parse-AvailableValueList { } } if ($buf.Length -gt 0) { $items += $buf.ToString() } + return ,$items +} - # For each item: split into value[:presentation], strip quotes - $stripQuotes = { - param($t) - $t = $t.Trim() - if ($t.Length -ge 2 -and (($t[0] -eq "'" -and $t[-1] -eq "'") -or ($t[0] -eq '"' -and $t[-1] -eq '"'))) { - return $t.Substring(1, $t.Length - 2) - } - return $t +function Strip-Quotes { + # Strips a single surrounding pair of matching quotes; trims first. + param([string]$t) + $t = $t.Trim() + if ($t.Length -ge 2 -and (($t[0] -eq "'" -and $t[-1] -eq "'") -or ($t[0] -eq '"' -and $t[-1] -eq '"'))) { + return $t.Substring(1, $t.Length - 2) } + return $t +} + +function Parse-ValueList { + # Returns array of value strings (quotes stripped) split by top-level commas. + # No ':' handling — values may contain colons (e.g. dateTime 2024-01-01T12:30:00). + param([string]$s) + $result = @() + if ($null -eq $s) { return ,$result } + foreach ($raw in (Split-QuotedCsv $s)) { + $v = Strip-Quotes $raw + if ($v -ne "") { $result += $v } + } + return ,$result +} + +function Parse-AvailableValueList { + # Returns array of @{ value=...; presentation=... } from comma-separated list. + # Items can use 'single' or "double" quotes (stripped). Quoted spans preserve commas/colons. + param([string]$s) + + $result = @() + if (-not $s) { return ,$result } + + $items = Split-QuotedCsv $s + $stripQuotes = { param($t) Strip-Quotes $t } foreach ($raw in $items) { $item = $raw.Trim() @@ -1135,7 +1170,14 @@ function Build-ParamFragment { } $vla = [bool]$parsed.valueListAllowed - if ($null -ne $parsed.value) { + $valIsArray = ($parsed.value -is [array]) -or ($parsed.value -is [System.Collections.IList] -and $parsed.value -isnot [string]) + if ($valIsArray) { + # Multi-value default (value-list): one per item + foreach ($v in $parsed.value) { + $valueLines = Build-ParamValueXml -type $parsed.type -value $v -indent "$i`t" + foreach ($vl in $valueLines) { $lines += $vl } + } + } elseif ($null -ne $parsed.value) { if (Test-EmptyValue $parsed.value) { $emptyXml = Build-EmptyValueXml -type $parsed.type -indent "$i`t" -tagPrefix "" -tagName "value" -valueListAllowed $vla if ($emptyXml) { $lines += $emptyXml } @@ -2292,6 +2334,20 @@ switch ($Operation) { $avPart = $rest.Substring($avIdx) } + # Separate a multi-value value=... (list) — kv-regex below grabs only a single + # \S+ token, so a comma-separated list (with spaces) wouldn't be captured. + # availableValue already peeled, so 'value=' here is the real value key. + $valueListItems = $null + $vlIdx = $simpleRest.IndexOf('value=') + if ($vlIdx -ge 0) { + $vlRhs = $simpleRest.Substring($vlIdx + 'value='.Length) + $cand = Parse-ValueList $vlRhs + if ($cand.Count -ge 2) { + $valueListItems = $cand + $simpleRest = $simpleRest.Substring(0, $vlIdx).Trim() + } + } + # Process simple key=value pairs (use, denyIncompleteValues, value, etc.) if ($simpleRest) { $kvPairs = [regex]::Matches($simpleRest, '(\w+)=(\S+)') @@ -2337,14 +2393,20 @@ switch ($Operation) { $fragXml = $valueLines -join "`n" } - $wasExisting = ($null -ne $existing) - if ($existing) { - # Capture position by next-element sibling, then remove existing - $refNode = $existing.NextSibling + # Collect ALL existing (a param may carry a value-list) — scalar + # value= collapses them to one, so remove every , not just the first. + $allValueEls = @() + foreach ($ch in $paramEl.ChildNodes) { + if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'value' -and $ch.NamespaceURI -eq $schNs) { $allValueEls += $ch } + } + $wasExisting = ($allValueEls.Count -gt 0) + if ($wasExisting) { + # Capture position after the last existing value, then remove all + $refNode = $allValueEls[$allValueEls.Count - 1].NextSibling while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) { $refNode = $refNode.NextSibling } - Remove-NodeWithWhitespace $existing + foreach ($ve in $allValueEls) { Remove-NodeWithWhitespace $ve } } else { # Insert before useRestriction/availableValue/denyIncompleteValues/use $refNode = $null @@ -2385,6 +2447,60 @@ switch ($Operation) { } } + # Process multi-value list (value=v1, v2, ...) — replace ALL , ensure valueListAllowed=true + if ($valueListItems) { + # Declared type from + $declaredType = "" + $vtEl = $null + foreach ($ch in $paramEl.ChildNodes) { + if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'valueType' -and $ch.NamespaceURI -eq $schNs) { $vtEl = $ch; break } + } + if ($vtEl) { + foreach ($tnode in $vtEl.ChildNodes) { + if ($tnode.NodeType -eq 'Element' -and $tnode.LocalName -eq 'Type') { + $declaredType = $tnode.InnerText.Trim() -replace '^d\d+p\d+:', '' + break + } + } + } + # Remove ALL existing ; capture insertion ref after the last one + $valueEls = @() + foreach ($child in $paramEl.ChildNodes) { + if ($child.NodeType -eq 'Element' -and $child.LocalName -eq 'value' -and $child.NamespaceURI -eq $schNs) { $valueEls += $child } + } + $refNode = $null + if ($valueEls.Count -gt 0) { + $refNode = $valueEls[$valueEls.Count - 1].NextSibling + while ($refNode -and ($refNode.NodeType -eq 'Whitespace' -or $refNode.NodeType -eq 'SignificantWhitespace')) { $refNode = $refNode.NextSibling } + foreach ($ve in $valueEls) { Remove-NodeWithWhitespace $ve } + } else { + foreach ($child in $paramEl.ChildNodes) { + if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('useRestriction','availableValue','denyIncompleteValues','use')) { $refNode = $child; break } + } + } + foreach ($v in $valueListItems) { + $fragXml = (Build-ParamValueXml -type $declaredType -value $v -indent $childIndent) -join "`n" + $nodes = Import-Fragment $xmlDoc $fragXml + foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refNode $childIndent } + } + # Ensure true (schema order: after useRestriction, before availableValue/use) + $vlaEl = $null + foreach ($ch in $paramEl.ChildNodes) { + if ($ch.NodeType -eq 'Element' -and $ch.LocalName -eq 'valueListAllowed' -and $ch.NamespaceURI -eq $schNs) { $vlaEl = $ch; break } + } + if ($vlaEl) { + if ($vlaEl.InnerText.Trim() -ne 'true') { $vlaEl.InnerText = 'true' } + } else { + $refVla = $null + foreach ($child in $paramEl.ChildNodes) { + if ($child.NodeType -eq 'Element' -and $child.LocalName -in @('availableValue','denyIncompleteValues','use')) { $refVla = $child; break } + } + $nodes = Import-Fragment $xmlDoc "$childIndenttrue" + foreach ($node in $nodes) { Insert-BeforeElement $paramEl $node $refVla $childIndent } + } + $script:Dirty = $true; Write-Host "[OK] Parameter `"$paramName`": value set to list of $($valueListItems.Count) item(s)" + } + # Process availableValue — replace whole list with new items if ($avPart) { $avRest = ($avPart -replace '^availableValue=', '').Trim() diff --git a/.claude/skills/skd-edit/scripts/skd-edit.py b/.claude/skills/skd-edit/scripts/skd-edit.py index e1c34763..6f574b4c 100644 --- a/.claude/skills/skd-edit/scripts/skd-edit.py +++ b/.claude/skills/skd-edit/scripts/skd-edit.py @@ -1,4 +1,4 @@ -# skd-edit v1.24 — Atomic 1C DCS editor (Python port) +# skd-edit v1.25 — Atomic 1C DCS editor (Python port) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse import os @@ -371,7 +371,18 @@ def parse_param_shorthand(s): result["name"] = m.group(1).strip() result["type"] = resolve_type_str(m.group(2).strip()) if m.group(3) is not None: - result["value"] = m.group(4).strip() if m.group(4) else "" + rhs = m.group(4) + if rhs and rhs.strip(): + items = parse_value_list(rhs.strip()) + if len(items) >= 2: + # Multi-value default → list; valueListAllowed implied + result["value"] = items + result["valueListAllowed"] = True + else: + # Scalar (single item, quotes stripped) or empty sentinel + result["value"] = items[0] if len(items) == 1 else "" + else: + result["value"] = "" else: result["name"] = s.strip() @@ -631,14 +642,12 @@ def parse_output_param_shorthand(s): return {"key": s.strip(), "value": ""} -def parse_available_value_list(s): - """Returns list of {value, presentation} from comma-separated list. - Items can use single/double quotes (stripped). Quoted spans preserve commas/colons.""" - if not s: - return [] - - # Tokenize by ',' respecting quoted spans +def split_quoted_csv(s): + """Split on top-level commas, respecting single/double quoted spans. + Returns raw (un-stripped) item spans. Shared by availableValue and value-list parsing.""" items = [] + if s is None: + return items buf = [] in_quote = None for ch in s: @@ -656,12 +665,37 @@ def parse_available_value_list(s): buf.append(ch) if buf: items.append("".join(buf)) + return items - def strip_quotes(t): - t = t.strip() - if len(t) >= 2 and ((t[0] == "'" and t[-1] == "'") or (t[0] == '"' and t[-1] == '"')): - return t[1:-1] - return t + +def strip_quotes(t): + """Strip a single surrounding pair of matching quotes; trims first.""" + t = t.strip() + if len(t) >= 2 and ((t[0] == "'" and t[-1] == "'") or (t[0] == '"' and t[-1] == '"')): + return t[1:-1] + return t + + +def parse_value_list(s): + """Return list of value strings (quotes stripped) split by top-level commas. + No ':' handling — values may contain colons (e.g. dateTime 2024-01-01T12:30:00).""" + if s is None: + return [] + result = [] + for raw in split_quoted_csv(s): + v = strip_quotes(raw) + if v != "": + result.append(v) + return result + + +def parse_available_value_list(s): + """Returns list of {value, presentation} from comma-separated list. + Items can use single/double quotes (stripped). Quoted spans preserve commas/colons.""" + if not s: + return [] + + items = split_quoted_csv(s) result = [] for raw in items: @@ -1012,7 +1046,12 @@ def build_param_fragment(parsed, indent): lines.append(f"{i}\t") vla = bool(parsed.get("valueListAllowed")) - if parsed["value"] is not None: + if isinstance(parsed["value"], list): + # Multi-value default (value-list): one per item + for v in parsed["value"]: + for vl in build_param_value_xml(parsed.get("type", ""), v, f"{i}\t"): + lines.append(vl) + elif parsed["value"] is not None: if is_empty_value(parsed["value"]): empty_xml = build_empty_value_xml(parsed.get("type", ""), f"{i}\t", "", "value", vla) if empty_xml: @@ -1987,6 +2026,17 @@ elif operation == "modify-parameter": simple_rest = rest[:av_idx].strip() av_part = rest[av_idx:] + # Separate a multi-value value=... (list) — kv-regex below grabs only a single + # \S+ token, so a comma-separated list (with spaces) wouldn't be captured. + value_list_items = None + vl_idx = simple_rest.find("value=") + if vl_idx >= 0: + vl_rhs = simple_rest[vl_idx + len("value="):] + cand = parse_value_list(vl_rhs) + if len(cand) >= 2: + value_list_items = cand + simple_rest = simple_rest[:vl_idx].strip() + # Process simple key=value pairs (use, denyIncompleteValues, etc.) if simple_rest: for m in re.finditer(r'(\w+)=(\S+)', simple_rest): @@ -2012,12 +2062,15 @@ elif operation == "modify-parameter": else: value_lines = build_param_value_xml(declared_type, value, child_indent) frag_xml = "\n".join(value_lines) - was_existing = existing is not None - if existing is not None: - # Find next-element sibling as ref before removing - idx = list(param_el).index(existing) - ref_node = param_el[idx + 1] if idx + 1 < len(param_el) else None - remove_node_with_whitespace(existing) + # Collect ALL existing (a param may carry a value-list) — scalar + # value= collapses them to one, so remove every , not just the first. + all_value_els = [ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "value" and etree.QName(ch.tag).namespace == SCH_NS] + was_existing = len(all_value_els) > 0 + if was_existing: + last_idx = list(param_el).index(all_value_els[-1]) + ref_node = param_el[last_idx + 1] if last_idx + 1 < len(param_el) else None + for ve in all_value_els: + remove_node_with_whitespace(ve) else: ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("useRestriction", "availableValue", "denyIncompleteValues", "use")), None) if frag_xml: @@ -2040,6 +2093,39 @@ elif operation == "modify-parameter": insert_before_element(param_el, node, ref_node, child_indent) dirty = True; print(f'[OK] Parameter "{param_name}": {key}={value} added') + # Process multi-value list (value=v1, v2, ...) — replace ALL , ensure valueListAllowed=true + if value_list_items: + declared_type = "" + vt_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "valueType" and etree.QName(ch.tag).namespace == SCH_NS), None) + if vt_el is not None: + for tnode in vt_el: + if isinstance(tnode.tag, str) and local_name(tnode) == "Type": + declared_type = re.sub(r'^d\d+p\d+:', '', (tnode.text or "").strip()) + break + # Remove ALL existing ; capture insertion ref after the last one + value_els = [ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "value" and etree.QName(ch.tag).namespace == SCH_NS] + if value_els: + last_idx = list(param_el).index(value_els[-1]) + ref_node = param_el[last_idx + 1] if last_idx + 1 < len(param_el) else None + for ve in value_els: + remove_node_with_whitespace(ve) + else: + ref_node = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("useRestriction", "availableValue", "denyIncompleteValues", "use")), None) + for v in value_list_items: + frag_xml = "\n".join(build_param_value_xml(declared_type, v, child_indent)) + for node in import_fragment(xml_doc, frag_xml): + insert_before_element(param_el, node, ref_node, child_indent) + # Ensure true (schema order: after useRestriction, before availableValue/use) + vla_el = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) == "valueListAllowed" and etree.QName(ch.tag).namespace == SCH_NS), None) + if vla_el is not None: + if (vla_el.text or "").strip() != "true": + vla_el.text = "true" + else: + ref_vla = next((ch for ch in param_el if isinstance(ch.tag, str) and local_name(ch) in ("availableValue", "denyIncompleteValues", "use")), None) + for node in import_fragment(xml_doc, f"{child_indent}true"): + insert_before_element(param_el, node, ref_vla, child_indent) + dirty = True; print(f'[OK] Parameter "{param_name}": value set to list of {len(value_list_items)} item(s)') + # Process availableValue if av_part: av_rest = av_part[len("availableValue="):].strip() diff --git a/docs/1c-dcs-spec.md b/docs/1c-dcs-spec.md index 1dbe89a2..ca8badc3 100644 --- a/docs/1c-dcs-spec.md +++ b/docs/1c-dcs-spec.md @@ -530,6 +530,26 @@ DataCompositionSchema Стандартные варианты периодов (`v8:StandardPeriodVariant`): `Custom`, `Today`, `ThisWeek`, `ThisMonth`, `ThisQuarter`, `ThisYear`, `LastMonth`, `LastQuarter`, `LastYear` и др. +#### Значение-список (несколько значений по умолчанию) + +Значением параметра может быть список — несколько элементов `` подряд внутри +``, при `true`: + +```xml + + ВидыСубконто + + d5p1:ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные + + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры + true + true + +``` + +Порядок элементов: `name, title, valueType, value*, useRestriction, …, valueListAllowed`. + --- ## 9. Макеты областей (template) diff --git a/docs/skd-dsl-spec.md b/docs/skd-dsl-spec.md index 087f7262..8dce8de9 100644 --- a/docs/skd-dsl-spec.md +++ b/docs/skd-dsl-spec.md @@ -339,6 +339,12 @@ XML-маппинг — по `` на каждый элемент: **Парсинг:** `"A: T = V"` → `name=A`, `type=T`, `value=V`. Значение `LastMonth` и другие варианты периодов → `v8:StandardPeriod` с `v8:variant`. +`` может быть **списком** — несколько значений через запятую (с `'...'` для запятой внутри значения). В этом случае эмитятся несколько ``, а `valueListAllowed=true` выводится автоматически (явный `@valueList` не нужен). Эквивалент объектной формы `"value": [ ... ]`. + +```json +"parameters": ["Виды: ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные = ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры"] +``` + ### @autoDates Флаг `@autoDates` в shorthand параметра автоматически генерирует два дополнительных параметра: diff --git a/tests/skills/cases/skd-compile/parameter-value-list.json b/tests/skills/cases/skd-compile/parameter-value-list.json new file mode 100644 index 00000000..bee7918b --- /dev/null +++ b/tests/skills/cases/skd-compile/parameter-value-list.json @@ -0,0 +1,18 @@ +{ + "name": "СКД: параметр со списком значений в шортхенде", + "params": { "outputPath": "Template.xml" }, + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т", + "fields": ["Поле: string"] + }], + "parameters": [ + "Виды: ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные = ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры" + ] + }, + "validatePath": "Template.xml", + "expect": { + "files": ["Template.xml"] + } +} diff --git a/tests/skills/cases/skd-compile/snapshots/parameter-value-list/Template.xml b/tests/skills/cases/skd-compile/snapshots/parameter-value-list/Template.xml new file mode 100644 index 00000000..3b2e3635 --- /dev/null +++ b/tests/skills/cases/skd-compile/snapshots/parameter-value-list/Template.xml @@ -0,0 +1,55 @@ + + + + ИсточникДанных1 + Local + + + Основной + + Поле + Поле + + xs:string + + 0 + Variable + + + + ИсточникДанных1 + ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т + + + Виды + + d5p1:ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные + + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры + false + true + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/skd-edit/add-parameter-value-list-dates.json b/tests/skills/cases/skd-edit/add-parameter-value-list-dates.json new file mode 100644 index 00000000..bbb92bd6 --- /dev/null +++ b/tests/skills/cases/skd-edit/add-parameter-value-list-dates.json @@ -0,0 +1,24 @@ +{ + "name": "add-parameter список значений-дат — двоеточия в значениях не режутся", + "preRun": [ + { + "script": "skd-compile/scripts/skd-compile", + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т", + "fields": ["Поле: string"] + }] + }, + "args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" } + } + ], + "params": { + "templatePath": "Template.xml", + "operation": "add-parameter", + "value": "Даты: dateTime = 2024-01-01T00:00:00, 2024-06-15T12:30:45" + }, + "expect": { + "files": ["Template.xml"] + } +} diff --git a/tests/skills/cases/skd-edit/add-parameter-value-list.json b/tests/skills/cases/skd-edit/add-parameter-value-list.json new file mode 100644 index 00000000..45e528e7 --- /dev/null +++ b/tests/skills/cases/skd-edit/add-parameter-value-list.json @@ -0,0 +1,21 @@ +{ + "name": "add-parameter со списком значений (несколько + valueListAllowed)", + "preRun": [ + { + "script": "skd-compile/scripts/skd-compile", + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т", + "fields": ["Поле: string"] + }] + }, + "args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" } + } + ], + "params": { + "templatePath": "Template.xml", + "operation": "add-parameter", + "value": "Виды: ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные = ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты, ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры" + } +} diff --git a/tests/skills/cases/skd-edit/modify-parameter-value-list.json b/tests/skills/cases/skd-edit/modify-parameter-value-list.json new file mode 100644 index 00000000..50646249 --- /dev/null +++ b/tests/skills/cases/skd-edit/modify-parameter-value-list.json @@ -0,0 +1,25 @@ +{ + "name": "modify-parameter value=список — скаляр заменяется списком + valueListAllowed добавлен", + "preRun": [ + { + "script": "skd-compile/scripts/skd-compile", + "input": { + "dataSets": [{ + "name": "Основной", + "query": "ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т", + "fields": ["Поле: string"] + }] + }, + "args": { "-DefinitionFile": "{inputFile}", "-OutputPath": "{workDir}/Template.xml" } + }, + { + "script": "skd-edit/scripts/skd-edit", + "args": { "-TemplatePath": "{workDir}/Template.xml", "-Operation": "add-parameter", "-Value": "Орг: CatalogRef.Организации = Справочник.Организации.X" } + } + ], + "params": { + "templatePath": "Template.xml", + "operation": "modify-parameter", + "value": "Орг value=Справочник.Организации.X, Справочник.Организации.Y" + } +} diff --git a/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list-dates/Template.xml b/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list-dates/Template.xml new file mode 100644 index 00000000..7aec802f --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list-dates/Template.xml @@ -0,0 +1,57 @@ + + + + ИсточникДанных1 + Local + + + Основной + + Поле + Поле + + xs:string + + 0 + Variable + + + + ИсточникДанных1 + ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т + + + Даты + + xs:dateTime + + DateTime + + + 2024-01-01T00:00:00 + 2024-06-15T12:30:45 + true + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list/Template.xml b/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list/Template.xml new file mode 100644 index 00000000..91d3c91a --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/add-parameter-value-list/Template.xml @@ -0,0 +1,54 @@ + + + + ИсточникДанных1 + Local + + + Основной + + Поле + Поле + + xs:string + + 0 + Variable + + + + ИсточникДанных1 + ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т + + + Виды + + d5p1:ChartOfCharacteristicTypesRef.ВидыСубконтоХозрасчетные + + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Контрагенты + ПланВидовХарактеристик.ВидыСубконтоХозрасчетные.Договоры + true + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + + + diff --git a/tests/skills/cases/skd-edit/snapshots/modify-parameter-value-list/Template.xml b/tests/skills/cases/skd-edit/snapshots/modify-parameter-value-list/Template.xml new file mode 100644 index 00000000..01a66334 --- /dev/null +++ b/tests/skills/cases/skd-edit/snapshots/modify-parameter-value-list/Template.xml @@ -0,0 +1,54 @@ + + + + ИсточникДанных1 + Local + + + Основной + + Поле + Поле + + xs:string + + 0 + Variable + + + + ИсточникДанных1 + ВЫБРАТЬ Т.Поле ИЗ Регистр КАК Т + + + Орг + + d5p1:CatalogRef.Организации + + Справочник.Организации.X + Справочник.Организации.Y + true + + + Основной + + + ru + Основной + + + + + + + + + + + + + + + + +