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
+ Основной
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+