mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-09-19 08:05:53 +03:00
feat(meta-edit,meta-remove): точечная правка и удаление внешних источников
meta-edit добавляет поля в таблицу внешнего источника: тот же парсер реквизита, свой тег <Field>, свой контекст (без индексов и полнотекстового поиска — чужой таблицей 1С не владеет) и три своих свойства в хвосте. Сам источник точечно не правится: и таблица (отдельный файл), и функция (узел с полным набором свойств) требуют эмиттера, который живёт в meta-compile, — дублировать его здесь значило бы завести вторую реализацию одного и того же. Заодно закрыт тихий отказ, существовавший независимо от внешних источников: проверка допустимых детей смотрела на истинность списка, а не на наличие ключа, поэтому для вида с пустым списком трактовалась как «ограничений нет» и чужой ребёнок молча записывался в объект. Теперь add-attribute на таблице внешнего источника отвергается с предупреждением, а не пишет <Attribute> вместо <Field>. meta-remove понимает две формы: ExternalDataSource.PG — источник целиком, и четырёхчастную ExternalDataSource.PG.Table.products — одну таблицу. Таблица числится в ChildObjects файла источника, а не конфигурации, поэтому дерегистрация разведена переменной реестра; поиск ссылок дополнен путями вида ExternalDataSource.И.Table.Т и ExternalDataSourceTableRef.И.Т. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NBsZA5cr2WFThtgp7i5WVi
This commit is contained in:
co-authored by
Claude Opus 5
parent
bb56a7e898
commit
ed766154fe
@@ -1,4 +1,4 @@
|
||||
# meta-edit v1.43 — Edit existing 1C metadata object XML
|
||||
# meta-edit v1.44 — Edit existing 1C metadata object XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
[CmdletBinding(PositionalBinding=$false)]
|
||||
param(
|
||||
@@ -465,6 +465,7 @@ $script:childTypeSynonyms = @{
|
||||
"templates" = "templates"; "макеты" = "templates"
|
||||
"commands" = "commands"; "команды" = "commands"
|
||||
"properties" = "properties"; "свойства" = "properties"
|
||||
"fields" = "fields"; "поля" = "fields"
|
||||
}
|
||||
|
||||
# Type synonyms (from meta-compile)
|
||||
@@ -955,6 +956,10 @@ function Parse-AttributeShorthand {
|
||||
indexing = if ($val.indexing) { "$($val.indexing)" } else { "" }
|
||||
after = if ($val.after) { "$($val.after)" } else { "" }
|
||||
before = if ($val.before) { "$($val.before)" } else { "" }
|
||||
# Поле таблицы внешнего источника (контекст eds-field).
|
||||
nameInDataSource = if ($val.nameInDataSource) { "$($val.nameInDataSource)" } else { "" }
|
||||
readOnly = if ($val.readOnly -eq $true) { $true } else { $false }
|
||||
allowNull = if ($val.allowNull -eq $true) { $true } else { $false }
|
||||
}
|
||||
# Map flags to properties
|
||||
if ($result.flags -contains "req" -and -not $result.fillChecking) {
|
||||
@@ -994,6 +999,7 @@ function Parse-EnumValueShorthand {
|
||||
function Get-AttributeContext {
|
||||
switch ($script:objType) {
|
||||
"Catalog" { return "catalog" }
|
||||
"Table" { return "eds-field" }
|
||||
"Document" { return "document" }
|
||||
{ $_ -in @("InformationRegister","AccumulationRegister","AccountingRegister","CalculationRegister") } { return "register" }
|
||||
{ $_ -in @("DataProcessor","Report","ExternalDataProcessor","ExternalReport") } { return "processor" }
|
||||
@@ -1022,7 +1028,7 @@ $script:reservedByContext = @{
|
||||
}
|
||||
|
||||
function Build-AttributeFragment {
|
||||
param($parsed, [string]$context, [string]$indent)
|
||||
param($parsed, [string]$context, [string]$indent, [string]$elemTag = "Attribute")
|
||||
|
||||
if (-not $context) { $context = Get-AttributeContext }
|
||||
|
||||
@@ -1037,7 +1043,7 @@ function Build-AttributeFragment {
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
} elseif ($context -notin @("tabular", "processor-tabular") -and
|
||||
} elseif ($context -notin @("tabular", "processor-tabular", "eds-field") -and
|
||||
($script:reservedAttrNames.ContainsKey($attrName) -or ($script:reservedAttrNames.Values -contains $attrName))) {
|
||||
Write-Warning "Attribute '$attrName' conflicts with a standard attribute name. This may cause errors when loading into 1C."
|
||||
}
|
||||
@@ -1045,7 +1051,7 @@ function Build-AttributeFragment {
|
||||
$uuid = New-Guid-String
|
||||
$sb = New-Object System.Text.StringBuilder
|
||||
|
||||
$sb.AppendLine("$indent<Attribute uuid=`"$uuid`">") | Out-Null
|
||||
$sb.AppendLine("$indent<$elemTag uuid=`"$uuid`">") | Out-Null
|
||||
$sb.AppendLine("$indent`t<Properties>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<Name>$(Esc-XmlText $parsed.name)</Name>") | Out-Null
|
||||
$sb.AppendLine($(Build-MLTextXml "$indent`t`t" "Synonym" $parsed.synonym)) | Out-Null
|
||||
@@ -1084,22 +1090,39 @@ function Build-AttributeFragment {
|
||||
if ($parsed.fillChecking) { $fillChecking = Normalize-EnumValue "FillChecking" $parsed.fillChecking }
|
||||
$sb.AppendLine("$indent`t`t<FillChecking>$fillChecking</FillChecking>") | Out-Null
|
||||
|
||||
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
|
||||
# Поле внешнего источника (eds-field) не имеет ChoiceFoldersAndItems и LinkByType, а ChoiceForm
|
||||
# у него стоит ПОСЛЕ ChoiceHistoryOnInput — порядок снят с выгрузки платформы.
|
||||
if ($context -ne "eds-field") {
|
||||
$sb.AppendLine("$indent`t`t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>") | Out-Null
|
||||
}
|
||||
$sb.AppendLine("$indent`t`t<ChoiceParameterLinks/>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<ChoiceParameters/>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<QuickChoice>Auto</QuickChoice>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<CreateOnInput>Auto</CreateOnInput>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<LinkByType/>") | Out-Null
|
||||
if ($context -ne "eds-field") {
|
||||
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
|
||||
$sb.AppendLine("$indent`t`t<LinkByType/>") | Out-Null
|
||||
}
|
||||
$sb.AppendLine("$indent`t`t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>") | Out-Null
|
||||
|
||||
if ($context -eq "eds-field") {
|
||||
$sb.AppendLine("$indent`t`t<ChoiceForm/>") | Out-Null
|
||||
$nids = if ($parsed.nameInDataSource) { "$($parsed.nameInDataSource)" } else { $parsed.name }
|
||||
$sb.AppendLine("$indent`t`t<NameInDataSource>$(Esc-XmlText $nids)</NameInDataSource>") | Out-Null
|
||||
$ro = if ($parsed.readOnly -eq $true -or $parsed.flags -contains "readonly") { "true" } else { "false" }
|
||||
$sb.AppendLine("$indent`t`t<ReadOnly>$ro</ReadOnly>") | Out-Null
|
||||
$an = if ($parsed.allowNull -eq $true -or $parsed.flags -contains "nullable") { "true" } else { "false" }
|
||||
$sb.AppendLine("$indent`t`t<AllowNull>$an</AllowNull>") | Out-Null
|
||||
}
|
||||
|
||||
# Use — catalog only
|
||||
if ($context -eq "catalog") {
|
||||
$sb.AppendLine("$indent`t`t<Use>ForItem</Use>") | Out-Null
|
||||
}
|
||||
|
||||
# Indexing/FullTextSearch/DataHistory — not for non-stored objects (processor, processor-tabular)
|
||||
if ($context -notin @("processor", "processor-tabular")) {
|
||||
# Поля внешнего источника: индексами чужой таблицы 1С не владеет.
|
||||
if ($context -notin @("processor", "processor-tabular", "eds-field")) {
|
||||
$indexing = "DontIndex"
|
||||
if ($parsed.flags -contains "index") { $indexing = "Index" }
|
||||
if ($parsed.flags -contains "indexadditional") { $indexing = "IndexWithAdditionalOrder" }
|
||||
@@ -1111,7 +1134,7 @@ function Build-AttributeFragment {
|
||||
}
|
||||
|
||||
$sb.AppendLine("$indent`t</Properties>") | Out-Null
|
||||
$sb.Append("$indent</Attribute>") | Out-Null
|
||||
$sb.Append("$indent</$elemTag>") | Out-Null
|
||||
return $sb.ToString()
|
||||
}
|
||||
|
||||
@@ -1538,11 +1561,15 @@ $script:validChildTypes = @{
|
||||
"CalculationRegister" = @("dimensions","resources","attributes","forms","templates","commands")
|
||||
"DocumentJournal" = @("columns","forms","templates","commands")
|
||||
"Constant" = @("forms")
|
||||
# Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл),
|
||||
# и функция (узел с полным набором свойств) требуют эмиттера, который живёт там.
|
||||
"ExternalDataSource" = @()
|
||||
"Table" = @("fields","forms","templates","commands")
|
||||
}
|
||||
|
||||
# Canonical child order in ChildObjects
|
||||
$script:childOrder = @(
|
||||
"Resource", "Dimension", "Attribute", "TabularSection",
|
||||
"Resource", "Dimension", "Attribute", "TabularSection", "Field", "Function",
|
||||
"AccountingFlag", "ExtDimensionAccountingFlag",
|
||||
"EnumValue", "Column", "AddressingAttribute", "Recalculation",
|
||||
"Form", "Template", "Command"
|
||||
@@ -1559,6 +1586,7 @@ $script:childTypeToXmlTag = @{
|
||||
"forms" = "Form"
|
||||
"templates" = "Template"
|
||||
"commands" = "Command"
|
||||
"fields" = "Field"
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -1904,11 +1932,15 @@ function Process-Add($addDef) {
|
||||
return
|
||||
}
|
||||
|
||||
# Validate allowed
|
||||
$allowed = $script:validChildTypes[$script:objType]
|
||||
if ($allowed -and $childType -notin $allowed) {
|
||||
Warn "$childType not allowed for $($script:objType), skipping"
|
||||
return
|
||||
# Validate allowed. Проверяем НАЛИЧИЕ ключа, а не истинность списка: пустой список
|
||||
# ($script:objType без допустимых детей) трактовался как «ограничений нет», и чужой
|
||||
# ребёнок молча записывался в объект.
|
||||
if ($script:validChildTypes.ContainsKey($script:objType)) {
|
||||
$allowed = $script:validChildTypes[$script:objType]
|
||||
if ($childType -notin $allowed) {
|
||||
Warn "$childType not allowed for $($script:objType), skipping"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
$xmlTag = $script:childTypeToXmlTag[$childType]
|
||||
@@ -1941,6 +1973,25 @@ function Process-Add($addDef) {
|
||||
$existingNames[$parsed.name] = "Attribute"
|
||||
}
|
||||
}
|
||||
"fields" {
|
||||
# Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст.
|
||||
foreach ($item in $items) {
|
||||
$parsed = Parse-AttributeShorthand $item
|
||||
if ($existingNames.ContainsKey($parsed.name)) {
|
||||
Warn "Field '$($parsed.name)' already exists, skipping"
|
||||
continue
|
||||
}
|
||||
$fragmentXml = Build-AttributeFragment $parsed "eds-field" $indent "Field"
|
||||
$nodes = Import-Fragment $fragmentXml
|
||||
$refNode = Find-InsertionPoint "Field" $parsed
|
||||
foreach ($node in $nodes) {
|
||||
Insert-BeforeElement $script:childObjectsEl $node $refNode $indent
|
||||
}
|
||||
Info "Added field: $($parsed.name)"
|
||||
$script:addCount++
|
||||
$existingNames[$parsed.name] = "Field"
|
||||
}
|
||||
}
|
||||
"tabularSections" {
|
||||
foreach ($item in $items) {
|
||||
$tsName = if ($item -is [string]) { "$item" } else { "$($item.name)" }
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# meta-edit v1.43 — Edit existing 1C metadata object XML
|
||||
# meta-edit v1.44 — Edit existing 1C metadata object XML
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -503,6 +503,7 @@ child_type_synonyms = {
|
||||
"templates": "templates", "макеты": "templates",
|
||||
"commands": "commands", "команды": "commands",
|
||||
"properties": "properties", "свойства": "properties",
|
||||
"fields": "fields", "поля": "fields",
|
||||
}
|
||||
|
||||
type_synonyms = {
|
||||
@@ -967,6 +968,10 @@ def parse_attribute_shorthand(val):
|
||||
"indexing": normalize_enum_value("Indexing", str(val.get("indexing", ""))) if val.get("indexing") else "",
|
||||
"after": str(val.get("after", "")),
|
||||
"before": str(val.get("before", "")),
|
||||
# Поле таблицы внешнего источника (контекст eds-field).
|
||||
"nameInDataSource": str(val.get("nameInDataSource", "")),
|
||||
"readOnly": val.get("readOnly") is True,
|
||||
"allowNull": val.get("allowNull") is True,
|
||||
}
|
||||
# Map flags to properties
|
||||
if "req" in result["flags"] and not result["fillChecking"]:
|
||||
@@ -1002,6 +1007,8 @@ def get_attribute_context():
|
||||
"""Determine attribute context from object type."""
|
||||
if obj_type == "Catalog":
|
||||
return "catalog"
|
||||
if obj_type == "Table":
|
||||
return "eds-field"
|
||||
if obj_type == "Document":
|
||||
return "document"
|
||||
if obj_type in ("InformationRegister", "AccumulationRegister", "AccountingRegister", "CalculationRegister"):
|
||||
@@ -1057,7 +1064,7 @@ RESERVED_BY_CONTEXT = {
|
||||
}
|
||||
|
||||
|
||||
def build_attribute_fragment(parsed, context, indent):
|
||||
def build_attribute_fragment(parsed, context, indent, elem_tag="Attribute"):
|
||||
"""Build XML fragment string for an Attribute element."""
|
||||
if not context:
|
||||
context = get_attribute_context()
|
||||
@@ -1069,13 +1076,13 @@ def build_attribute_fragment(parsed, context, indent):
|
||||
if attr_name.lower() in ctx_reserved:
|
||||
print(f"meta-edit: имя реквизита '{attr_name}' зарезервировано стандартным реквизитом объекта '{context}'. Выберите другое имя.", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
elif context not in ('tabular', 'processor-tabular') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU):
|
||||
elif context not in ('tabular', 'processor-tabular', 'eds-field') and (attr_name in RESERVED_ATTR_NAMES or attr_name in RESERVED_ATTR_NAMES_RU):
|
||||
print(f"WARNING: Attribute '{attr_name}' conflicts with a standard attribute name. This may cause errors when loading into 1C.", file=sys.stderr)
|
||||
|
||||
uid = new_uuid()
|
||||
lines = []
|
||||
|
||||
lines.append(f'{indent}<Attribute uuid="{uid}">')
|
||||
lines.append(f'{indent}<{elem_tag} uuid="{uid}">')
|
||||
lines.append(f"{indent}\t<Properties>")
|
||||
lines.append(f"{indent}\t\t<Name>{esc_xml_text(parsed['name'])}</Name>")
|
||||
lines.append(build_mltext_xml(f"{indent}\t\t", "Synonym", parsed["synonym"]))
|
||||
@@ -1114,21 +1121,35 @@ def build_attribute_fragment(parsed, context, indent):
|
||||
fill_checking = parsed["fillChecking"]
|
||||
lines.append(f"{indent}\t\t<FillChecking>{fill_checking}</FillChecking>")
|
||||
|
||||
lines.append(f"{indent}\t\t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>")
|
||||
# Поле внешнего источника (eds-field) не имеет ChoiceFoldersAndItems и LinkByType, а ChoiceForm
|
||||
# у него стоит ПОСЛЕ ChoiceHistoryOnInput — порядок снят с выгрузки платформы.
|
||||
if context != "eds-field":
|
||||
lines.append(f"{indent}\t\t<ChoiceFoldersAndItems>Items</ChoiceFoldersAndItems>")
|
||||
lines.append(f"{indent}\t\t<ChoiceParameterLinks/>")
|
||||
lines.append(f"{indent}\t\t<ChoiceParameters/>")
|
||||
lines.append(f"{indent}\t\t<QuickChoice>Auto</QuickChoice>")
|
||||
lines.append(f"{indent}\t\t<CreateOnInput>Auto</CreateOnInput>")
|
||||
lines.append(f"{indent}\t\t<ChoiceForm/>")
|
||||
lines.append(f"{indent}\t\t<LinkByType/>")
|
||||
if context != "eds-field":
|
||||
lines.append(f"{indent}\t\t<ChoiceForm/>")
|
||||
lines.append(f"{indent}\t\t<LinkByType/>")
|
||||
lines.append(f"{indent}\t\t<ChoiceHistoryOnInput>Auto</ChoiceHistoryOnInput>")
|
||||
|
||||
if context == "eds-field":
|
||||
lines.append(f"{indent}\t\t<ChoiceForm/>")
|
||||
nids = parsed.get("nameInDataSource") or parsed["name"]
|
||||
lines.append(f"{indent}\t\t<NameInDataSource>{esc_xml_text(str(nids))}</NameInDataSource>")
|
||||
ro = "true" if (parsed.get("readOnly") is True or "readonly" in parsed["flags"]) else "false"
|
||||
lines.append(f"{indent}\t\t<ReadOnly>{ro}</ReadOnly>")
|
||||
an = "true" if (parsed.get("allowNull") is True or "nullable" in parsed["flags"]) else "false"
|
||||
lines.append(f"{indent}\t\t<AllowNull>{an}</AllowNull>")
|
||||
|
||||
# Use -- catalog only
|
||||
if context == "catalog":
|
||||
lines.append(f"{indent}\t\t<Use>ForItem</Use>")
|
||||
|
||||
# Indexing/FullTextSearch/DataHistory -- not for non-stored objects
|
||||
if context not in ("processor", "processor-tabular"):
|
||||
# Поля внешнего источника: индексами чужой таблицы 1С не владеет.
|
||||
if context not in ("processor", "processor-tabular", "eds-field"):
|
||||
indexing = "DontIndex"
|
||||
if "index" in parsed["flags"]:
|
||||
indexing = "Index"
|
||||
@@ -1141,7 +1162,7 @@ def build_attribute_fragment(parsed, context, indent):
|
||||
lines.append(f"{indent}\t\t<DataHistory>Use</DataHistory>")
|
||||
|
||||
lines.append(f"{indent}\t</Properties>")
|
||||
lines.append(f"{indent}</Attribute>")
|
||||
lines.append(f"{indent}</{elem_tag}>")
|
||||
return "\r\n".join(lines)
|
||||
|
||||
|
||||
@@ -1530,11 +1551,15 @@ valid_child_types = {
|
||||
"CalculationRegister": ["dimensions", "resources", "attributes", "forms", "templates", "commands"],
|
||||
"DocumentJournal": ["columns", "forms", "templates", "commands"],
|
||||
"Constant": ["forms"],
|
||||
# Внешний источник данных правится целиком через meta-compile: и таблица (отдельный файл),
|
||||
# и функция (узел с полным набором свойств) требуют эмиттера, который живёт там.
|
||||
"ExternalDataSource": [],
|
||||
"Table": ["fields", "forms", "templates", "commands"],
|
||||
}
|
||||
|
||||
# Canonical child order in ChildObjects
|
||||
child_order = [
|
||||
"Resource", "Dimension", "Attribute", "TabularSection",
|
||||
"Resource", "Dimension", "Attribute", "TabularSection", "Field", "Function",
|
||||
"AccountingFlag", "ExtDimensionAccountingFlag",
|
||||
"EnumValue", "Column", "AddressingAttribute", "Recalculation",
|
||||
"Form", "Template", "Command",
|
||||
@@ -1551,6 +1576,7 @@ child_type_to_xml_tag = {
|
||||
"forms": "Form",
|
||||
"templates": "Template",
|
||||
"commands": "Command",
|
||||
"fields": "Field",
|
||||
}
|
||||
|
||||
# ============================================================
|
||||
@@ -1855,11 +1881,13 @@ def process_add(add_def):
|
||||
warn(f"Unknown add child type: {raw_key}")
|
||||
continue
|
||||
|
||||
# Validate allowed
|
||||
allowed = valid_child_types.get(obj_type)
|
||||
if allowed and child_type not in allowed:
|
||||
warn(f"{child_type} not allowed for {obj_type}, skipping")
|
||||
continue
|
||||
# Validate allowed. Проверяем НАЛИЧИЕ ключа, а не истинность списка: пустой список
|
||||
# (объект без допустимых детей) трактовался как «ограничений нет», и чужой ребёнок
|
||||
# молча записывался в объект.
|
||||
if obj_type in valid_child_types:
|
||||
if child_type not in valid_child_types[obj_type]:
|
||||
warn(f"{child_type} not allowed for {obj_type}, skipping")
|
||||
continue
|
||||
|
||||
xml_tag = child_type_to_xml_tag.get(child_type)
|
||||
if not xml_tag:
|
||||
@@ -1886,6 +1914,22 @@ def process_add(add_def):
|
||||
add_count += 1
|
||||
existing_names[parsed["name"]] = "Attribute"
|
||||
|
||||
elif child_type == "fields":
|
||||
# Поле таблицы внешнего источника: тот же парсер реквизита, свой тег и контекст.
|
||||
for item in items:
|
||||
parsed = parse_attribute_shorthand(item)
|
||||
if parsed["name"] in existing_names:
|
||||
warn(f"Field '{parsed['name']}' already exists, skipping")
|
||||
continue
|
||||
fragment_xml = build_attribute_fragment(parsed, "eds-field", indent, "Field")
|
||||
nodes = import_fragment(fragment_xml)
|
||||
ref_node = find_insertion_point("Field", parsed)
|
||||
for node in nodes:
|
||||
insert_before_element(child_objects_el, node, ref_node, indent)
|
||||
info(f"Added field: {parsed['name']}")
|
||||
add_count += 1
|
||||
existing_names[parsed["name"]] = "Field"
|
||||
|
||||
elif child_type == "tabularSections":
|
||||
for item in items:
|
||||
if isinstance(item, str):
|
||||
|
||||
Reference in New Issue
Block a user