diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1
index a5ae63c3..0b7101f1 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.ps1
+++ b/.claude/skills/meta-edit/scripts/meta-edit.ps1
@@ -1,4 +1,4 @@
-# meta-edit v1.15 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks)
+# meta-edit v1.16 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[string]$DefinitionFile,
@@ -607,6 +607,8 @@ function Import-Fragment([string]$xmlString) {
xmlns:v8="http://v8.1c.ru/8.1/data/core"
xmlns:xr="http://v8.1c.ru/8.3/xcf/readable"
xmlns:cfg="http://v8.1c.ru/8.1/data/enterprise/current-config"
+ xmlns:app="http://v8.1c.ru/8.2/managed-application/core"
+ xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"
xmlns:xs="http://www.w3.org/2001/XMLSchema">$xmlString
"@
$frag = New-Object System.Xml.XmlDocument
@@ -2280,6 +2282,11 @@ function Modify-ChildElements($modifyDef, [string]$childType) {
Info "Set $xmlTag '$elemName'.ChoiceParameterLinks"; $script:modifyCount++
}
}
+ "ChoiceParameters" {
+ if (Set-AttrPropertyElement $propsEl "ChoiceParameters" (Build-ChoiceParametersXml (Get-ChildIndent $propsEl) $changeValue)) {
+ Info "Set $xmlTag '$elemName'.ChoiceParameters"; $script:modifyCount++
+ }
+ }
default {
# Scalar property change (Indexing, FillChecking, Use, etc.)
$scalarEl = $null
@@ -2544,6 +2551,153 @@ function Build-ChoiceParameterLinksXml([string]$indent, $cpl) {
return $sb.ToString()
}
+# --- Порт из meta-compile: значения параметров выбора (ChoiceParameters) ---
+
+$script:fillRefRoots = @{
+ 'перечисление'='Enum'; 'справочник'='Catalog'; 'документ'='Document';
+ 'плансчетов'='ChartOfAccounts'; 'планвидовхарактеристик'='ChartOfCharacteristicTypes';
+ 'планвидоврасчета'='ChartOfCalculationTypes'; 'планвидоврасчёта'='ChartOfCalculationTypes';
+ 'планобмена'='ExchangePlan'; 'бизнеспроцесс'='BusinessProcess'; 'задача'='Task';
+ 'enum'='Enum'; 'catalog'='Catalog'; 'document'='Document'; 'chartofaccounts'='ChartOfAccounts';
+ 'chartofcharacteristictypes'='ChartOfCharacteristicTypes'; 'chartofcalculationtypes'='ChartOfCalculationTypes';
+ 'exchangeplan'='ExchangePlan'; 'businessprocess'='BusinessProcess'; 'task'='Task'
+}
+$script:fillEmptyRefWords = @('emptyref','пустаяссылка')
+$script:fillEnumValWords = @('enumvalue','значениеперечисления')
+$script:accountTypeValues = @('Active','Passive','ActivePassive')
+$script:fillRefKindRoot = @{
+ 'catalogref'='Catalog'; 'documentref'='Document'; 'enumref'='Enum';
+ 'chartofaccountsref'='ChartOfAccounts'; 'chartofcharacteristictypesref'='ChartOfCharacteristicTypes';
+ 'chartofcalculationtypesref'='ChartOfCalculationTypes'; 'exchangeplanref'='ExchangePlan';
+ 'businessprocessref'='BusinessProcess'; 'taskref'='Task'
+}
+
+function ConvertTo-ChScalar([string]$s) {
+ $t = "$s".Trim()
+ if ($t -match '^(?i:true|истина)$') { return $true }
+ if ($t -match '^(?i:false|ложь)$') { return $false }
+ if ($t -match '^-?\d+$') { return [int]$t }
+ if ($t -match '^-?\d+\.\d+$') { return [double]::Parse($t, [System.Globalization.CultureInfo]::InvariantCulture) }
+ return $t
+}
+
+function Format-FillNum($n) {
+ if ($n -is [double] -or $n -is [decimal]) { return $n.ToString([System.Globalization.CultureInfo]::InvariantCulture) }
+ return "$n"
+}
+
+function Normalize-FillRef([string]$s) {
+ if ([string]::IsNullOrEmpty($s)) { return $null }
+ if ($s -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.[0-9a-fA-F-]+$') { return $s }
+ $parts = $s -split '\.'
+ if ($parts.Count -lt 2) { return $null }
+ $root = $script:fillRefRoots[$parts[0].ToLower()]
+ if (-not $root) { return $null }
+ $typeName = $parts[1]
+ if ($root -eq 'Enum') {
+ if ($parts.Count -eq 2) { return $null }
+ if ($parts.Count -eq 3) {
+ if ($script:fillEmptyRefWords -contains $parts[2].ToLower()) { return "Enum.$typeName.EmptyRef" }
+ return "Enum.$typeName.EnumValue.$($parts[2])"
+ }
+ $member = $parts[2]
+ if ($script:fillEnumValWords -contains $member.ToLower()) { $rest = $parts[3..($parts.Count-1)] -join '.' }
+ else { $rest = $parts[2..($parts.Count-1)] -join '.' }
+ return "Enum.$typeName.EnumValue.$rest"
+ }
+ $tail = @($parts[1..($parts.Count-1)])
+ for ($i = 0; $i -lt $tail.Count; $i++) {
+ if ($script:fillEmptyRefWords -contains $tail[$i].ToLower()) { $tail[$i] = 'EmptyRef' }
+ }
+ return "$root." + ($tail -join '.')
+}
+
+function Expand-ChoiceRefValue([string]$value, [string]$typeStr) {
+ if (-not $typeStr) { return $null }
+ $t = Resolve-TypeStr $typeStr
+ $root = $null; $tn = $null
+ if ($t -match '^(\w+Ref)\.(.+)$') { $root = $script:fillRefKindRoot[$Matches[1].ToLower()]; $tn = $Matches[2] }
+ elseif ($t -match '^([^.]+)\.(.+)$') { $root = $script:fillRefRoots[$Matches[1].ToLower()]; $tn = $Matches[2] }
+ if (-not $root) { return $null }
+ if ($script:fillEmptyRefWords -contains "$value".ToLower()) { return "$root.$tn.EmptyRef" }
+ if ($root -eq 'Enum') { return "Enum.$tn.EnumValue.$value" }
+ return "$root.$tn.$value"
+}
+
+function Normalize-ChoiceValue($value) {
+ if ($value -is [bool]) { return @{ XsiType='xs:boolean'; Text=$(if ($value) { 'true' } else { 'false' }) } }
+ if ($value -is [int] -or $value -is [long] -or $value -is [double] -or $value -is [decimal]) {
+ return @{ XsiType='xs:decimal'; Text=(Format-FillNum $value) }
+ }
+ $s = "$value"
+ if ($s -eq '') { return @{ XsiType='xs:string'; Text='' } }
+ if ($s -match '^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$') { return @{ XsiType='xs:dateTime'; Text=$s } }
+ $ref = Normalize-FillRef $s
+ if ($ref) { return @{ XsiType='xr:DesignTimeRef'; Text=$ref } }
+ if ($script:accountTypeValues -contains $s) { return @{ XsiType='ent:AccountType'; Text=$s } }
+ return @{ XsiType='xs:string'; Text=$s }
+}
+
+function Normalize-ChoiceValueT($value, [string]$typeStr) {
+ if ($typeStr -and ($value -is [string]) -and (-not "$value".Contains('.'))) {
+ $ex = Expand-ChoiceRefValue "$value" $typeStr
+ if ($ex) { return @{ XsiType='xr:DesignTimeRef'; Text=$ex } }
+ }
+ return Normalize-ChoiceValue $value
+}
+
+function ConvertFrom-ChParamShorthand([string]$s) {
+ $eq = $s.IndexOf('=')
+ if ($eq -lt 0) { return @{ name = $s.Trim() } }
+ $name = $s.Substring(0, $eq).Trim(); $rest = $s.Substring($eq + 1)
+ if ($rest -match ',') {
+ $vals = @(); foreach ($p in ($rest -split ',')) { $vals += ,(ConvertTo-ChScalar $p) }
+ return @{ name = $name; value = $vals }
+ }
+ return @{ name = $name; value = (ConvertTo-ChScalar $rest) }
+}
+
+# ChoiceParameters — [{name, type?, value?}] (порт Emit-ChoiceParameters). Значение на app:value (xsi:type=тип);
+# массив → v8:FixedArray с v8:Value; без value → nil. Требует xmlns:app в Import-Fragment.
+function Build-ChoiceParametersXml([string]$indent, $cp) {
+ if (-not $cp -or @($cp).Count -eq 0) { return "$indent" }
+ $sb = New-Object System.Text.StringBuilder
+ $sb.Append("$indent") | Out-Null
+ foreach ($item in @($cp)) {
+ if ($item -is [string]) { $item = ConvertFrom-ChParamShorthand $item }
+ $name = Get-ChElProp $item @('name','имя')
+ $ptype = Get-ChElProp $item @('type','тип')
+ $hasVal = $false; $val = $null
+ if ($item -is [System.Collections.IDictionary]) {
+ if ($item.Contains('value')) { $hasVal = $true; $val = $item['value'] }
+ elseif ($item.Contains('значение')) { $hasVal = $true; $val = $item['значение'] }
+ } elseif ($item.PSObject) {
+ if ($item.PSObject.Properties['value']) { $hasVal = $true; $val = $item.PSObject.Properties['value'].Value }
+ elseif ($item.PSObject.Properties['значение']) { $hasVal = $true; $val = $item.PSObject.Properties['значение'].Value }
+ }
+ $valIsArray = ($val -is [System.Array]) -or ($val -is [System.Collections.IList] -and $val -isnot [string])
+ $sb.Append("`r`n$indent`t") | Out-Null
+ if (-not $hasVal) {
+ $sb.Append("`r`n$indent`t`t") | Out-Null
+ } elseif ($valIsArray) {
+ $sb.Append("`r`n$indent`t`t") | Out-Null
+ foreach ($v in $val) {
+ $norm = Normalize-ChoiceValueT $v $ptype
+ if ([string]::IsNullOrEmpty($norm.Text)) { $sb.Append("`r`n$indent`t`t`t") | Out-Null }
+ else { $sb.Append("`r`n$indent`t`t`t$(Esc-Xml $norm.Text)") | Out-Null }
+ }
+ $sb.Append("`r`n$indent`t`t") | Out-Null
+ } else {
+ $norm = Normalize-ChoiceValueT $val $ptype
+ if ([string]::IsNullOrEmpty($norm.Text)) { $sb.Append("`r`n$indent`t`t") | Out-Null }
+ else { $sb.Append("`r`n$indent`t`t$(Esc-Xml $norm.Text)") | Out-Null }
+ }
+ $sb.Append("`r`n$indent`t") | Out-Null
+ }
+ $sb.Append("`r`n$indent") | Out-Null
+ return $sb.ToString()
+}
+
function Find-PropertyElement([string]$propName) {
foreach ($child in $script:propertiesEl.ChildNodes) {
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $propName) {
diff --git a/.claude/skills/meta-edit/scripts/meta-edit.py b/.claude/skills/meta-edit/scripts/meta-edit.py
index 037a3927..21c67487 100644
--- a/.claude/skills/meta-edit/scripts/meta-edit.py
+++ b/.claude/skills/meta-edit/scripts/meta-edit.py
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
-# meta-edit v1.15 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks)
+# meta-edit v1.16 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks/ChoiceParameters)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -567,6 +567,8 @@ def import_fragment(xml_string):
f' xmlns:v8="{V8_NS}"'
f' xmlns:xr="{XR_NS}"'
f' xmlns:cfg="{CFG_NS}"'
+ f' xmlns:app="http://v8.1c.ru/8.2/managed-application/core"'
+ f' xmlns:ent="http://v8.1c.ru/8.1/data/enterprise"'
f' xmlns:xs="{XS_NS}">'
f"{xml_string}"
)
@@ -2121,6 +2123,10 @@ def modify_child_elements(modify_def, child_type):
if set_attr_property_element(props_el, "ChoiceParameterLinks", build_choice_parameter_links_xml(get_child_indent(props_el), change_value)):
info(f"Set {xml_tag} '{elem_name}'.ChoiceParameterLinks")
modify_count += 1
+ elif change_prop == "ChoiceParameters":
+ if set_attr_property_element(props_el, "ChoiceParameters", build_choice_parameters_xml(get_child_indent(props_el), change_value)):
+ info(f"Set {xml_tag} '{elem_name}'.ChoiceParameters")
+ modify_count += 1
else:
# Scalar property change (Indexing, FillChecking, Use, etc.)
@@ -2396,6 +2402,181 @@ def build_choice_parameter_links_xml(indent, cpl):
return "\r\n".join(parts)
+# --- Порт из meta-compile: значения параметров выбора (ChoiceParameters) ---
+
+fill_ref_roots = {
+ 'перечисление': 'Enum', 'справочник': 'Catalog', 'документ': 'Document',
+ 'плансчетов': 'ChartOfAccounts', 'планвидовхарактеристик': 'ChartOfCharacteristicTypes',
+ 'планвидоврасчета': 'ChartOfCalculationTypes', 'планвидоврасчёта': 'ChartOfCalculationTypes',
+ 'планобмена': 'ExchangePlan', 'бизнеспроцесс': 'BusinessProcess', 'задача': 'Task',
+ 'enum': 'Enum', 'catalog': 'Catalog', 'document': 'Document', 'chartofaccounts': 'ChartOfAccounts',
+ 'chartofcharacteristictypes': 'ChartOfCharacteristicTypes', 'chartofcalculationtypes': 'ChartOfCalculationTypes',
+ 'exchangeplan': 'ExchangePlan', 'businessprocess': 'BusinessProcess', 'task': 'Task',
+}
+fill_empty_ref_words = {'emptyref', 'пустаяссылка'}
+fill_enum_val_words = {'enumvalue', 'значениеперечисления'}
+account_type_values = ('Active', 'Passive', 'ActivePassive')
+fill_ref_kind_root = {
+ 'catalogref': 'Catalog', 'documentref': 'Document', 'enumref': 'Enum',
+ 'chartofaccountsref': 'ChartOfAccounts', 'chartofcharacteristictypesref': 'ChartOfCharacteristicTypes',
+ 'chartofcalculationtypesref': 'ChartOfCalculationTypes', 'exchangeplanref': 'ExchangePlan',
+ 'businessprocessref': 'BusinessProcess', 'taskref': 'Task',
+}
+
+
+def convert_to_ch_scalar(s):
+ t = str(s).strip()
+ if re.match(r'^(true|истина)$', t, re.IGNORECASE):
+ return True
+ if re.match(r'^(false|ложь)$', t, re.IGNORECASE):
+ return False
+ if re.match(r'^-?\d+$', t):
+ return int(t)
+ if re.match(r'^-?\d+\.\d+$', t):
+ return float(t)
+ return t
+
+
+def format_fill_num(n):
+ if isinstance(n, float):
+ return str(int(n)) if n == int(n) else repr(n)
+ return str(n)
+
+
+def normalize_fill_ref(s):
+ if not s:
+ return None
+ if re.match(r'^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}\.[0-9a-fA-F-]+$', s):
+ return s
+ parts = s.split('.')
+ if len(parts) < 2:
+ return None
+ root = fill_ref_roots.get(parts[0].lower())
+ if not root:
+ return None
+ type_name = parts[1]
+ if root == 'Enum':
+ if len(parts) == 2:
+ return None
+ if len(parts) == 3:
+ if parts[2].lower() in fill_empty_ref_words:
+ return f"Enum.{type_name}.EmptyRef"
+ return f"Enum.{type_name}.EnumValue.{parts[2]}"
+ member = parts[2]
+ rest = '.'.join(parts[3:]) if member.lower() in fill_enum_val_words else '.'.join(parts[2:])
+ return f"Enum.{type_name}.EnumValue.{rest}"
+ tail = list(parts[1:])
+ for i in range(len(tail)):
+ if tail[i].lower() in fill_empty_ref_words:
+ tail[i] = 'EmptyRef'
+ return f"{root}." + '.'.join(tail)
+
+
+def expand_choice_ref_value(value, type_str):
+ if not type_str:
+ return None
+ t = resolve_type_str(type_str)
+ root = None
+ tn = None
+ m = re.match(r'^(\w+Ref)\.(.+)$', t)
+ if m:
+ root = fill_ref_kind_root.get(m.group(1).lower())
+ tn = m.group(2)
+ else:
+ m2 = re.match(r'^([^.]+)\.(.+)$', t)
+ if m2:
+ root = fill_ref_roots.get(m2.group(1).lower())
+ tn = m2.group(2)
+ if not root:
+ return None
+ if str(value).lower() in fill_empty_ref_words:
+ return f"{root}.{tn}.EmptyRef"
+ if root == 'Enum':
+ return f"Enum.{tn}.EnumValue.{value}"
+ return f"{root}.{tn}.{value}"
+
+
+def normalize_choice_value(value):
+ if isinstance(value, bool):
+ return {'XsiType': 'xs:boolean', 'Text': 'true' if value else 'false'}
+ if isinstance(value, (int, float)):
+ return {'XsiType': 'xs:decimal', 'Text': format_fill_num(value)}
+ s = str(value)
+ if s == '':
+ return {'XsiType': 'xs:string', 'Text': ''}
+ if re.match(r'^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$', s):
+ return {'XsiType': 'xs:dateTime', 'Text': s}
+ ref = normalize_fill_ref(s)
+ if ref:
+ return {'XsiType': 'xr:DesignTimeRef', 'Text': ref}
+ if s in account_type_values:
+ return {'XsiType': 'ent:AccountType', 'Text': s}
+ return {'XsiType': 'xs:string', 'Text': s}
+
+
+def normalize_choice_value_t(value, type_str):
+ if type_str and isinstance(value, str) and '.' not in str(value):
+ ex = expand_choice_ref_value(value, type_str)
+ if ex:
+ return {'XsiType': 'xr:DesignTimeRef', 'Text': ex}
+ return normalize_choice_value(value)
+
+
+def convert_from_ch_param_shorthand(s):
+ eq = s.find('=')
+ if eq < 0:
+ return {'name': s.strip()}
+ name = s[:eq].strip()
+ rest = s[eq + 1:]
+ if ',' in rest:
+ return {'name': name, 'value': [convert_to_ch_scalar(p) for p in rest.split(',')]}
+ return {'name': name, 'value': convert_to_ch_scalar(rest)}
+
+
+def build_choice_parameters_xml(indent, cp):
+ """ChoiceParameters — [{name, type?, value?}] (порт Emit-ChoiceParameters). Требует xmlns:app в import_fragment."""
+ items = cp if isinstance(cp, list) else ([cp] if cp else [])
+ if not items:
+ return f"{indent}"
+ parts = [f"{indent}"]
+ for item in items:
+ if isinstance(item, str):
+ item = convert_from_ch_param_shorthand(item)
+ name = get_ch_el_prop(item, ['name', 'имя'])
+ ptype = get_ch_el_prop(item, ['type', 'тип'])
+ has_val = False
+ val = None
+ if isinstance(item, dict):
+ if 'value' in item:
+ has_val = True
+ val = item['value']
+ elif 'значение' in item:
+ has_val = True
+ val = item['значение']
+ val_is_array = isinstance(val, list)
+ parts.append(f'{indent}\t')
+ if not has_val:
+ parts.append(f'{indent}\t\t')
+ elif val_is_array:
+ parts.append(f'{indent}\t\t')
+ for v in val:
+ norm = normalize_choice_value_t(v, ptype)
+ if not norm['Text']:
+ parts.append(f'{indent}\t\t\t')
+ else:
+ parts.append(f'{indent}\t\t\t{esc_xml(norm["Text"])}')
+ parts.append(f'{indent}\t\t')
+ else:
+ norm = normalize_choice_value_t(val, ptype)
+ if not norm['Text']:
+ parts.append(f'{indent}\t\t')
+ else:
+ parts.append(f'{indent}\t\t{esc_xml(norm["Text"])}')
+ parts.append(f'{indent}\t')
+ parts.append(f"{indent}")
+ return "\r\n".join(parts)
+
+
def find_property_element(prop_name):
for child in properties_el:
if localname(child) == prop_name:
diff --git a/tests/skills/cases/meta-edit/modify-attribute-structural.json b/tests/skills/cases/meta-edit/modify-attribute-structural.json
index 92003953..48ecc214 100644
--- a/tests/skills/cases/meta-edit/modify-attribute-structural.json
+++ b/tests/skills/cases/meta-edit/modify-attribute-structural.json
@@ -20,7 +20,11 @@
"Контр": {
"ChoiceForm": "Catalog.Спр.Form.ФормаВыбора",
"ChoiceParameterLinks": ["Отбор.Владелец=Ссылка"],
- "LinkByType": { "dataPath": "Ссылка", "linkItem": 0 }
+ "LinkByType": { "dataPath": "Ссылка", "linkItem": 0 },
+ "ChoiceParameters": [
+ { "name": "Отбор.ПометкаУдаления", "value": false },
+ { "name": "Отбор.Ссылка", "value": "Catalog.Спр.EmptyRef" }
+ ]
},
"Сумма": { "MinValue": 0, "MaxValue": 1000000 }
}
diff --git a/tests/skills/cases/meta-edit/snapshots/modify-attribute-structural/Catalogs/Спр.xml b/tests/skills/cases/meta-edit/snapshots/modify-attribute-structural/Catalogs/Спр.xml
index 536ab406..bc8edd86 100644
--- a/tests/skills/cases/meta-edit/snapshots/modify-attribute-structural/Catalogs/Спр.xml
+++ b/tests/skills/cases/meta-edit/snapshots/modify-attribute-structural/Catalogs/Спр.xml
@@ -179,7 +179,14 @@
Clear
-
+
+
+ false
+
+
+ Catalog.Спр.EmptyRef
+
+
Auto
Auto
Catalog.Спр.Form.ФормаВыбора