From 41df32015344ef2d6fc9b3dc6cd1461ebdbaca4b Mon Sep 17 00:00:00 2001 From: Nick Shirokov Date: Sun, 12 Jul 2026 10:34:04 +0300 Subject: [PATCH] =?UTF-8?q?feat(meta-edit):=20=D1=81=D1=82=D1=80=D1=83?= =?UTF-8?q?=D0=BA=D1=82=D1=83=D1=80=D0=BD=D1=8B=D0=B5=20=D1=81=D0=B2=D0=BE?= =?UTF-8?q?=D0=B9=D1=81=D1=82=D0=B2=D0=B0=20LinkByType/ChoiceParameterLink?= =?UTF-8?q?s=20(=D0=A4=D0=B0=D0=B7=D0=B0=202=20=D0=A8=D0=B0=D0=B3=203b-1,?= =?UTF-8?q?=20v1.15)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit modify-attribute/-dimension/-resource: LinkByType ({dataPath,linkItem}) и ChoiceParameterLinks ([{name,dataPath,valueChange}]) — порт эмиттеров Emit-LinkByType/ Emit-ChoiceParameterLinks в meta-edit (используют xr: namespace, уже в Import-Fragment; app не нужен). Портированы вспомогательные Expand-DataPath (+Resolve-StdAttrEn на существующих reserved-картах), Get-ChElProp, ConvertFrom-ChLinkShorthand. Прощающий ввод путей (короткое имя реквизита → полный путь). Cert декомпиляцией: выход meta-edit БАЙТ-В-БАЙТ совпадает с meta-compile для того же ввода (ps1 и py; py-lxml пишет LF vs ps1 CRLF — нормализуется раннером/git). verify-snapshots --case modify-attribute-structural (расширен) грузится в 1С. meta-edit 14/14 ps1+py. Остаток Шага 3: fillValue явный + choiceParameters (fill-ref машинерия + фикс app-namespace). Co-Authored-By: Claude Opus 4.8 (1M context) --- .../skills/meta-edit/scripts/meta-edit.ps1 | 109 ++++++++++++++- .claude/skills/meta-edit/scripts/meta-edit.py | 132 +++++++++++++++++- .../modify-attribute-structural.json | 6 +- .../Catalogs/Спр.xml | 13 +- 4 files changed, 255 insertions(+), 5 deletions(-) diff --git a/.claude/skills/meta-edit/scripts/meta-edit.ps1 b/.claude/skills/meta-edit/scripts/meta-edit.ps1 index cad03079..a5ae63c3 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.14 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue) +# meta-edit v1.15 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills param( [string]$DefinitionFile, @@ -2270,6 +2270,16 @@ function Modify-ChildElements($modifyDef, [string]$childType) { Info "Set $xmlTag '$elemName'.MaxValue"; $script:modifyCount++ } } + "LinkByType" { + if (Set-AttrPropertyElement $propsEl "LinkByType" (Build-LinkByTypeXml (Get-ChildIndent $propsEl) $changeValue)) { + Info "Set $xmlTag '$elemName'.LinkByType"; $script:modifyCount++ + } + } + "ChoiceParameterLinks" { + if (Set-AttrPropertyElement $propsEl "ChoiceParameterLinks" (Build-ChoiceParameterLinksXml (Get-ChildIndent $propsEl) $changeValue)) { + Info "Set $xmlTag '$elemName'.ChoiceParameterLinks"; $script:modifyCount++ + } + } default { # Scalar property change (Indexing, FillChecking, Use, etc.) $scalarEl = $null @@ -2437,6 +2447,103 @@ function Build-MinMaxValueXml([string]$tag, $val) { return "<$tag xsi:type=`"$t`">$(Esc-Xml "$val")" } +# --- Порт из meta-compile: развёртка путей данных + связи выбора / тип по ссылке (structural modify) --- + +# Свойство из dict/PSCustomObject по списку синонимов (первый найденный, иначе $null). +function Get-ChElProp($obj, [string[]]$names) { + if ($null -eq $obj) { return $null } + foreach ($n in $names) { + if ($obj -is [System.Collections.IDictionary]) { if ($obj.Contains($n)) { return $obj[$n] } } + elseif ($obj.PSObject -and $obj.PSObject.Properties[$n]) { return $obj.PSObject.Properties[$n].Value } + } + return $null +} + +# Стандартный реквизит рус/англ → английский (для Catalog/Document); использует существующие reserved-карты. +function Resolve-StdAttrEn([string]$name) { + $ctx = switch ("$script:objType") { 'Catalog' { 'catalog' } 'Document' { 'document' } default { $null } } + if (-not $ctx) { return $null } + $stdSet = $script:reservedByContext[$ctx] + foreach ($en in $stdSet) { + $ru = $script:reservedAttrNames[$en] + if (($name -ieq $en) -or ($ru -and $name -ieq $ru)) { return $en } + } + return $null +} + +# Прощающий ввод пути данных: короткое имя реквизита → полный путь объекта (порт Expand-DataPath). +function Expand-DataPath([string]$dp) { + if (-not $dp) { return $dp } + $s = "$dp" + if ($s -match '[:/]') { return $s } + if ($s -match '^-?\d+$') { return $s } + if ($s -match '^(StandardAttribute|Attribute)\.') { return "$($script:objType).$($script:objName).$s" } + if (-not $s.Contains('.')) { + $en = Resolve-StdAttrEn $s + if ($en) { return "$($script:objType).$($script:objName).StandardAttribute.$en" } + return "$($script:objType).$($script:objName).Attribute.$s" + } + return $s +} + +# Shorthand "name=path" | "name=path:Clear|DontChange" → {name, dataPath, valueChange?}. +function ConvertFrom-ChLinkShorthand([string]$s) { + $eq = $s.IndexOf('=') + if ($eq -lt 0) { return @{ name = $s.Trim() } } + $o = @{ name = $s.Substring(0, $eq).Trim() }; $rest = $s.Substring($eq + 1).Trim() + if ($rest -match '^(.*):(?i:(Clear|DontChange|очистить|неизменять))$') { $o['dataPath'] = $matches[1].Trim(); $o['valueChange'] = $matches[2] } + else { $o['dataPath'] = $rest } + return $o +} + +# LinkByType — {dataPath, linkItem?} (порт Emit-LinkByType). Строка → dataPath, linkItem=0. +function Build-LinkByTypeXml([string]$indent, $spec) { + if (-not $spec) { return "$indent" } + if ($spec -is [string]) { $dp = "$spec"; $li = 0 } + else { + $dp = "$(Get-ChElProp $spec @('dataPath','path','путь'))" + $liRaw = Get-ChElProp $spec @('linkItem','элементСвязи') + $li = if ($null -ne $liRaw) { $liRaw } else { 0 } + } + if (-not $dp) { return "$indent" } + $dp = Expand-DataPath $dp + $lines = @( + "$indent" + "$indent`t$(Esc-Xml "$dp")" + "$indent`t$li" + "$indent" + ) + return $lines -join "`r`n" +} + +# ChoiceParameterLinks — [{name, dataPath, valueChange?}] (порт Emit-ChoiceParameterLinks). valueChange дефолт Clear. +function Build-ChoiceParameterLinksXml([string]$indent, $cpl) { + if (-not $cpl -or @($cpl).Count -eq 0) { return "$indent" } + $sb = New-Object System.Text.StringBuilder + $sb.Append("$indent") | Out-Null + foreach ($lk in @($cpl)) { + if ($lk -is [string]) { $lk = ConvertFrom-ChLinkShorthand $lk } + $name = Get-ChElProp $lk @('name','имя') + $dp = Expand-DataPath (Get-ChElProp $lk @('dataPath','path','путь')) + $vcRaw = Get-ChElProp $lk @('valueChange','режимИзменения') + $vc = 'Clear' + if ($vcRaw) { + $vc = switch -Regex ("$vcRaw".ToLower()) { + '^(clear|очистить|очистка)$' { 'Clear'; break } + '^(dontchange|неизменять|неменять|нет)$' { 'DontChange'; break } + default { "$vcRaw" } + } + } + $sb.Append("`r`n$indent`t") | Out-Null + $sb.Append("`r`n$indent`t`t$(Esc-Xml "$name")") | Out-Null + $sb.Append("`r`n$indent`t`t$(Esc-Xml "$dp")") | Out-Null + $sb.Append("`r`n$indent`t`t$vc") | 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 713a08c7..037a3927 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.14 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue) +# meta-edit v1.15 — Edit existing 1C metadata object XML (+structural attr props Format/EditFormat/ToolTip/ChoiceForm/MinValue/MaxValue/LinkByType/ChoiceParameterLinks) # Source: https://github.com/Nikolay-Shirokov/cc-1c-skills import argparse @@ -2113,6 +2113,14 @@ def modify_child_elements(modify_def, child_type): if set_attr_property_element(props_el, "MaxValue", build_min_max_value_xml("MaxValue", change_value)): info(f"Set {xml_tag} '{elem_name}'.MaxValue") modify_count += 1 + elif change_prop == "LinkByType": + if set_attr_property_element(props_el, "LinkByType", build_link_by_type_xml(get_child_indent(props_el), change_value)): + info(f"Set {xml_tag} '{elem_name}'.LinkByType") + modify_count += 1 + elif change_prop == "ChoiceParameterLinks": + 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 else: # Scalar property change (Indexing, FillChecking, Use, etc.) @@ -2266,6 +2274,128 @@ def build_min_max_value_xml(tag, val): return f'<{tag} xsi:type="{t}">{esc_xml(str(val))}' +# --- Порт из meta-compile: развёртка путей данных + связи выбора / тип по ссылке (structural modify) --- + +def get_ch_el_prop(obj, names): + """Свойство из dict по списку синонимов (первый найденный, иначе None).""" + if obj is None: + return None + if isinstance(obj, dict): + for n in names: + if n in obj: + return obj[n] + return None + + +# Пары EN↔RU стандартных реквизитов Catalog/Document (для resolve_std_attr_en; py-карты плоские, восстанавливаем). +_STD_ATTR_BY_CTX = { + 'Catalog': [('Ref', 'Ссылка'), ('DeletionMark', 'ПометкаУдаления'), ('Predefined', 'Предопределенный'), + ('PredefinedDataName', 'ИмяПредопределенныхДанных'), ('Code', 'Код'), ('Description', 'Наименование'), + ('Owner', 'Владелец'), ('Parent', 'Родитель'), ('IsFolder', 'ЭтоГруппа')], + 'Document': [('Ref', 'Ссылка'), ('DeletionMark', 'ПометкаУдаления'), ('Date', 'Дата'), + ('Number', 'Номер'), ('Posted', 'Проведен')], +} + + +def resolve_std_attr_en(name): + """Стандартный реквизит рус/англ → английский (Catalog/Document).""" + pairs = _STD_ATTR_BY_CTX.get(obj_type) + if not pairs: + return None + nl = str(name).lower() + for en, ru in pairs: + if nl == en.lower() or nl == ru.lower(): + return en + return None + + +def expand_data_path(dp): + """Прощающий ввод пути данных: короткое имя реквизита → полный путь объекта (порт Expand-DataPath).""" + if not dp: + return dp + s = str(dp) + if re.search(r'[:/]', s): + return s + if re.match(r'^-?\d+$', s): + return s + if re.match(r'^(StandardAttribute|Attribute)\.', s): + return f"{obj_type}.{obj_name}.{s}" + if '.' not in s: + en = resolve_std_attr_en(s) + if en: + return f"{obj_type}.{obj_name}.StandardAttribute.{en}" + return f"{obj_type}.{obj_name}.Attribute.{s}" + return s + + +def convert_from_ch_link_shorthand(s): + """Shorthand "name=path" | "name=path:Clear|DontChange" → {name, dataPath, valueChange?}.""" + eq = s.find('=') + if eq < 0: + return {'name': s.strip()} + o = {'name': s[:eq].strip()} + rest = s[eq + 1:].strip() + m = re.match(r'^(.*):(Clear|DontChange|очистить|неизменять)$', rest, re.IGNORECASE) + if m: + o['dataPath'] = m.group(1).strip() + o['valueChange'] = m.group(2) + else: + o['dataPath'] = rest + return o + + +def build_link_by_type_xml(indent, spec): + """LinkByType — {dataPath, linkItem?} (порт Emit-LinkByType).""" + if not spec: + return f"{indent}" + if isinstance(spec, str): + dp = spec + li = 0 + else: + dp = str(get_ch_el_prop(spec, ['dataPath', 'path', 'путь']) or '') + li_raw = get_ch_el_prop(spec, ['linkItem', 'элементСвязи']) + li = li_raw if li_raw is not None else 0 + if not dp: + return f"{indent}" + dp = expand_data_path(dp) + return "\r\n".join([ + f"{indent}", + f"{indent}\t{esc_xml(str(dp))}", + f"{indent}\t{li}", + f"{indent}", + ]) + + +def build_choice_parameter_links_xml(indent, cpl): + """ChoiceParameterLinks — [{name, dataPath, valueChange?}] (порт Emit-ChoiceParameterLinks).""" + items = cpl if isinstance(cpl, list) else ([cpl] if cpl else []) + if not items: + return f"{indent}" + parts = [f"{indent}"] + for lk in items: + if isinstance(lk, str): + lk = convert_from_ch_link_shorthand(lk) + name = get_ch_el_prop(lk, ['name', 'имя']) + dp = expand_data_path(get_ch_el_prop(lk, ['dataPath', 'path', 'путь'])) + vc_raw = get_ch_el_prop(lk, ['valueChange', 'режимИзменения']) + vc = 'Clear' + if vc_raw: + low = str(vc_raw).lower() + if re.match(r'^(clear|очистить|очистка)$', low): + vc = 'Clear' + elif re.match(r'^(dontchange|неизменять|неменять|нет)$', low): + vc = 'DontChange' + else: + vc = str(vc_raw) + parts.append(f"{indent}\t") + parts.append(f"{indent}\t\t{esc_xml(str(name) if name is not None else '')}") + parts.append(f'{indent}\t\t{esc_xml(str(dp) if dp is not None else "")}') + parts.append(f"{indent}\t\t{vc}") + 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 758d0c3d..92003953 100644 --- a/tests/skills/cases/meta-edit/modify-attribute-structural.json +++ b/tests/skills/cases/meta-edit/modify-attribute-structural.json @@ -17,7 +17,11 @@ "modify": { "attributes": { "Дата": { "Format": "ДФ=dd.MM.yyyy", "EditFormat": "ДФ=dd.MM.yyyy", "ToolTip": "Дата документа" }, - "Контр": { "ChoiceForm": "Catalog.Спр.Form.ФормаВыбора" }, + "Контр": { + "ChoiceForm": "Catalog.Спр.Form.ФормаВыбора", + "ChoiceParameterLinks": ["Отбор.Владелец=Ссылка"], + "LinkByType": { "dataPath": "Ссылка", "linkItem": 0 } + }, "Сумма": { "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 a081af85..536ab406 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 @@ -172,12 +172,21 @@ DontCheck Items - + + + Отбор.Владелец + Catalog.Спр.StandardAttribute.Ref + Clear + + Auto Auto Catalog.Спр.Form.ФормаВыбора - + + Catalog.Спр.StandardAttribute.Ref + 0 + Auto ForItem DontIndex