feat(cfe-borrow): full ChildItems form borrowing with auto-borrow dependencies

Borrow-Form now generates full ChildItems tree (matching Configurator output)
with stripping of DataPath, TitleDataPath, TypeLink, Events, CommandName→0.
Auto-borrows CommonPictures, StyleItems, and Enums+EnumValues referenced by
form elements. Verified loading into BP database with two production forms.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-10 20:18:12 +03:00
parent 007b4ec69c
commit 6df64ae1c1
3 changed files with 412 additions and 68 deletions
+195 -10
View File
@@ -1,4 +1,4 @@
# cfe-borrow v1.0 — Borrow objects from configuration into extension (CFE)
# cfe-borrow v1.1 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory)][string]$ExtensionPath,
@@ -476,8 +476,9 @@ function Borrow-Form {
$formVersion = $srcFormEl.GetAttribute("version")
if (-not $formVersion) { $formVersion = "2.17" }
# Find direct children: form properties and AutoCommandBar
# Find direct children: form properties, AutoCommandBar, ChildItems
$srcAutoCmd = $null
$srcChildItems = $null
$formProps = @()
$reachedVisual = $false
foreach ($fc in $srcFormEl.ChildNodes) {
@@ -485,11 +486,13 @@ function Borrow-Form {
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) {
$reachedVisual = $true; $srcAutoCmd = $fc; continue
}
if ($fc.LocalName -eq 'ChildItems' -or $fc.LocalName -eq 'Events' -or $fc.LocalName -eq 'Attributes' -or $fc.LocalName -eq 'Commands' -or $fc.LocalName -eq 'Parameters') {
if ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) {
$reachedVisual = $true; $srcChildItems = $fc; continue
}
if ($fc.LocalName -eq 'Events' -or $fc.LocalName -eq 'Attributes' -or $fc.LocalName -eq 'Commands' -or $fc.LocalName -eq 'Parameters') {
$reachedVisual = $true; continue
}
if (-not $reachedVisual) {
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
$formProps += $fc.OuterXml
}
}
@@ -497,17 +500,187 @@ function Borrow-Form {
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
$nsStripPattern = '\s+xmlns(?::\w+)?="[^"]*"'
# AutoCommandBar: copy only properties (Autofill etc.), strip ChildItems
# AutoCommandBar: strip ChildItems (buttons), replace CommandName→0, Autofill→false
$autoCmdXml = ""
if ($srcAutoCmd) {
$autoCmdXml = $srcAutoCmd.OuterXml
$autoCmdXml = [regex]::Replace($autoCmdXml, $nsStripPattern, '')
# Strip ChildItems from AutoCommandBar (buttons will appear from base form at runtime)
$autoCmdXml = [regex]::Replace($autoCmdXml, '(?s)\s*<ChildItems>.*?</ChildItems>', '')
# Replace Autofill true → false
$autoCmdXml = $autoCmdXml -replace '<Autofill>true</Autofill>', '<Autofill>false</Autofill>'
}
# ChildItems: copy full tree, clean up base-config references
$childItemsXml = ""
if ($srcChildItems) {
$childItemsXml = $srcChildItems.OuterXml
$childItemsXml = [regex]::Replace($childItemsXml, $nsStripPattern, '')
# Replace all CommandName values with 0
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
# Strip DataPath (references base form attributes not in extension)
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<DataPath>[^<]*</DataPath>', '')
# Strip TitleDataPath (e.g. Объект.Товары.RowsCount — invalid without base attributes)
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<TitleDataPath>[^<]*</TitleDataPath>', '')
# Strip TypeLink blocks with human-readable DataPath (Items.XXX — can't convert to UUID)
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<TypeLink>\s*<xr:DataPath>Items\.[^<]*</xr:DataPath>.*?</TypeLink>', '')
# Strip element-level Events (base form handlers not in extension)
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Events>.*?</Events>', '')
# Collect CommonPicture references from ChildItems
$picRefs = [regex]::Matches($childItemsXml, '<xr:Ref>CommonPicture\.(\w+)</xr:Ref>')
$referencedPictures = @{}
foreach ($m in $picRefs) { $referencedPictures[$m.Groups[1].Value] = $true }
# Auto-borrow referenced CommonPictures (if not already borrowed)
$autoBorrowedPics = @()
foreach ($picName in $referencedPictures.Keys) {
if (-not (Test-ObjectBorrowed "CommonPicture" $picName)) {
$picSrcFile = Join-Path (Join-Path $cfgDir "CommonPictures") "${picName}.xml"
if (Test-Path $picSrcFile) {
$src = Read-SourceObject "CommonPicture" $picName
$borrowedXml = Build-BorrowedObjectXml "CommonPicture" $picName $src.Uuid $src.Properties
$targetDir = Join-Path $extDir "CommonPictures"
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
$targetFile = Join-Path $targetDir "${picName}.xml"
$encBom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
Add-ToChildObjects "CommonPicture" $picName
$autoBorrowedPics += $picName
$borrowedFiles += $targetFile
Info " Auto-borrowed: CommonPicture.${picName}"
} else {
Warn " CommonPicture.${picName} not found in source config — will strip from form"
}
}
}
# Collect all borrowed CommonPictures (including previously borrowed)
$borrowedPicSet = @{}
$nsMgr2 = New-Object System.Xml.XmlNamespaceManager($script:xmlDoc.NameTable)
$nsMgr2.AddNamespace("md", $script:mdNs)
$picNodes = $script:xmlDoc.SelectNodes("//md:ChildObjects/md:CommonPicture", $nsMgr2)
foreach ($pn in $picNodes) { $borrowedPicSet[$pn.InnerText] = $true }
# Strip <Picture> blocks referencing non-borrowed CommonPictures
$picBlockPattern = '(?s)\s*<Picture>\s*<xr:Ref>CommonPicture\.(\w+)</xr:Ref>.*?</Picture>'
$picMatches = [regex]::Matches($childItemsXml, $picBlockPattern)
# Process in reverse order to preserve positions
for ($mi = $picMatches.Count - 1; $mi -ge 0; $mi--) {
$pm = $picMatches[$mi]
$cpName = $pm.Groups[1].Value
if (-not $borrowedPicSet.ContainsKey($cpName)) {
$childItemsXml = $childItemsXml.Remove($pm.Index, $pm.Length)
}
}
# Strip StdPicture blocks (except Print)
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '')
# Auto-borrow StyleItems referenced in ChildItems
# Pattern 1: <Font ref="style:XXX" kind="StyleItem"/>, <TitleFont ref="style:XXX" ... kind="StyleItem"/>
# Pattern 2: <BackColor>style:XXX</BackColor>, <TextColor>style:XXX</TextColor>, etc.
$referencedStyles = @{}
$styleRefs1 = [regex]::Matches($childItemsXml, 'ref="style:(\w+)"[^>]*kind="StyleItem"')
foreach ($m in $styleRefs1) { $referencedStyles[$m.Groups[1].Value] = $true }
$styleRefs2 = [regex]::Matches($childItemsXml, '>style:(\w+)</\w+>')
foreach ($m in $styleRefs2) { $referencedStyles[$m.Groups[1].Value] = $true }
foreach ($styleName in $referencedStyles.Keys) {
if (-not (Test-ObjectBorrowed "StyleItem" $styleName)) {
$styleSrcFile = Join-Path (Join-Path $cfgDir "StyleItems") "${styleName}.xml"
if (Test-Path $styleSrcFile) {
$src = Read-SourceObject "StyleItem" $styleName
$borrowedXml = Build-BorrowedObjectXml "StyleItem" $styleName $src.Uuid $src.Properties
$targetDir = Join-Path $extDir "StyleItems"
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
$targetFile = Join-Path $targetDir "${styleName}.xml"
$encBom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
Add-ToChildObjects "StyleItem" $styleName
$borrowedFiles += $targetFile
Info " Auto-borrowed: StyleItem.${styleName}"
} else {
Warn " StyleItem.${styleName} not found in source config"
}
}
}
# Auto-borrow Enums + EnumValues referenced via DesignTimeRef in ChoiceParameters
# Collect Enum -> [EnumValue names] map
$dtRefs = [regex]::Matches($childItemsXml, 'xr:DesignTimeRef">Enum\.(\w+)\.EnumValue\.(\w+)')
$referencedEnumValues = @{}
foreach ($m in $dtRefs) {
$eName = $m.Groups[1].Value
$evName = $m.Groups[2].Value
if (-not $referencedEnumValues.ContainsKey($eName)) { $referencedEnumValues[$eName] = @{} }
$referencedEnumValues[$eName][$evName] = $true
}
foreach ($enumName in $referencedEnumValues.Keys) {
if (-not (Test-ObjectBorrowed "Enum" $enumName)) {
$enumSrcFile = Join-Path (Join-Path $cfgDir "Enums") "${enumName}.xml"
if (Test-Path $enumSrcFile) {
# Read source Enum to get UUID and EnumValue UUIDs
$srcParser = New-Object System.Xml.XmlDocument
$srcParser.PreserveWhitespace = $true
$srcParser.Load($enumSrcFile)
$srcEnumEl = $null
foreach ($cn in $srcParser.DocumentElement.ChildNodes) {
if ($cn.NodeType -eq 'Element') { $srcEnumEl = $cn; break }
}
$srcEnumUuid = $srcEnumEl.GetAttribute("uuid")
# Find source EnumValues by name
$enumValueXmls = @()
$neededValues = $referencedEnumValues[$enumName]
$srcNsMgr = New-Object System.Xml.XmlNamespaceManager($srcParser.NameTable)
$srcNsMgr.AddNamespace("md", $script:mdNs)
$srcEvNodes = $srcEnumEl.SelectNodes("md:ChildObjects/md:EnumValue", $srcNsMgr)
foreach ($evNode in $srcEvNodes) {
$evUuid = $evNode.GetAttribute("uuid")
$evNameNode = $evNode.SelectSingleNode("md:Properties/md:Name", $srcNsMgr)
if ($evNameNode -and $neededValues.ContainsKey($evNameNode.InnerText)) {
$newEvUuid = [guid]::NewGuid().ToString()
$enumValueXmls += @"
<EnumValue uuid="${newEvUuid}">
<InternalInfo/>
<Properties>
<ObjectBelonging>Adopted</ObjectBelonging>
<Name>$($evNameNode.InnerText)</Name>
<Comment/>
<ExtendedConfigurationObject>${evUuid}</ExtendedConfigurationObject>
</Properties>
</EnumValue>
"@
}
}
# Build borrowed Enum with EnumValues in ChildObjects
$src = Read-SourceObject "Enum" $enumName
$borrowedXml = Build-BorrowedObjectXml "Enum" $enumName $src.Uuid $src.Properties
if ($enumValueXmls.Count -gt 0) {
$evBlock = ($enumValueXmls -join "`r`n")
$borrowedXml = $borrowedXml -replace '<ChildObjects/>', "<ChildObjects>`r`n${evBlock}`r`n`t`t</ChildObjects>"
}
$targetDir = Join-Path $extDir "Enums"
if (-not (Test-Path $targetDir)) {
New-Item -ItemType Directory -Path $targetDir -Force | Out-Null
}
$targetFile = Join-Path $targetDir "${enumName}.xml"
$encBom = New-Object System.Text.UTF8Encoding($true)
[System.IO.File]::WriteAllText($targetFile, $borrowedXml, $encBom)
Add-ToChildObjects "Enum" $enumName
$borrowedFiles += $targetFile
Info " Auto-borrowed: Enum.${enumName} (with $($enumValueXmls.Count) EnumValue(s))"
} else {
Warn " Enum.${enumName} not found in source config"
}
}
}
}
# Extract the <Form ...> opening tag from source text (preserves namespace declarations)
$xmlDecl = '<?xml version="1.0" encoding="UTF-8"?>'
$formTag = "<Form version=`"${formVersion}`">"
@@ -521,7 +694,7 @@ function Borrow-Form {
$formXmlSb.Append($formTag) | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
# Part 1: form properties + AutoCommandBar (no ChildItems — they come from base form at runtime)
# Part 1: form properties + AutoCommandBar + ChildItems
foreach ($propXml in $formProps) {
$propXml = [regex]::Replace($propXml, $nsStripPattern, '')
$formXmlSb.Append("`t$propXml`r`n") | Out-Null
@@ -530,10 +703,14 @@ function Borrow-Form {
$formXmlSb.Append("`t$autoCmdXml") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
}
if ($childItemsXml) {
$formXmlSb.Append("`t$childItemsXml") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
}
$formXmlSb.Append("`t<Attributes/>") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
# BaseForm: same properties + AutoCommandBar (no ChildItems)
# BaseForm: same content, indented one more level
$formXmlSb.Append("`t<BaseForm version=`"${formVersion}`">") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
@@ -542,7 +719,6 @@ function Borrow-Form {
$formXmlSb.Append("`t`t$propXml`r`n") | Out-Null
}
if ($autoCmdXml) {
# Reindent for BaseForm: first line gets 2 tabs, other lines get +1 tab
$acLines = $autoCmdXml -split "`r?`n"
for ($li = 0; $li -lt $acLines.Count; $li++) {
if ($li -eq 0) { $formXmlSb.Append("`t`t$($acLines[$li])") | Out-Null }
@@ -550,6 +726,15 @@ function Borrow-Form {
$formXmlSb.Append("`r`n") | Out-Null
}
}
if ($childItemsXml) {
# Reindent ChildItems for BaseForm (+1 tab level)
$ciLines = $childItemsXml -split "`r?`n"
for ($li = 0; $li -lt $ciLines.Count; $li++) {
if ($li -eq 0) { $formXmlSb.Append("`t`t$($ciLines[$li])") | Out-Null }
else { $formXmlSb.Append("`t$($ciLines[$li])") | Out-Null }
$formXmlSb.Append("`r`n") | Out-Null
}
}
$formXmlSb.Append("`t`t<Attributes/>") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
+159 -7
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
# cfe-borrow v1.0 — Borrow objects from configuration into extension (CFE)
# cfe-borrow v1.1 — Borrow objects from configuration into extension (CFE)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
@@ -671,17 +671,159 @@ def main():
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
# AutoCommandBar: copy only properties (Autofill etc.), strip ChildItems
# AutoCommandBar: strip ChildItems (buttons), replace CommandName→0, Autofill→false
auto_cmd_xml = ""
if src_auto_cmd is not None:
auto_cmd_xml = etree.tostring(src_auto_cmd, encoding="unicode")
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
# Strip ChildItems from AutoCommandBar (buttons will appear from base form at runtime)
auto_cmd_xml = re.sub(r'\s*<ChildItems>.*?</ChildItems>', '', auto_cmd_xml, flags=re.DOTALL)
# Replace Autofill true -> false
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
# Extract source form opening tag
# ChildItems: copy full tree, clean up base-config references
child_items_xml = ""
src_child_items = None
for fc in src_form_el:
if isinstance(fc.tag, str) and localname(fc) == "ChildItems":
src_child_items = fc
break
if src_child_items is not None:
child_items_xml = etree.tostring(src_child_items, encoding="unicode")
child_items_xml = ns_strip_pattern.sub("", child_items_xml)
# Replace all CommandName values with 0
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
# Strip DataPath
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
# Strip TitleDataPath
child_items_xml = re.sub(r'\s*<TitleDataPath>[^<]*</TitleDataPath>', '', child_items_xml)
# Strip TypeLink blocks with human-readable DataPath (Items.XXX)
child_items_xml = re.sub(r'\s*<TypeLink>\s*<xr:DataPath>Items\.[^<]*</xr:DataPath>.*?</TypeLink>', '', child_items_xml, flags=re.DOTALL)
# Strip element-level Events
child_items_xml = re.sub(r'\s*<Events>.*?</Events>', '', child_items_xml, flags=re.DOTALL)
# Auto-borrow referenced CommonPictures
pic_refs = re.findall(r'<xr:Ref>CommonPicture\.(\w+)</xr:Ref>', child_items_xml)
referenced_pictures = {name: True for name in pic_refs}
auto_borrowed_pics = []
for pic_name in referenced_pictures:
if not test_object_borrowed("CommonPicture", pic_name):
pic_src_file = os.path.join(cfg_dir, "CommonPictures", f"{pic_name}.xml")
if os.path.isfile(pic_src_file):
src = read_source_object("CommonPicture", pic_name)
borrowed_xml = build_borrowed_object_xml("CommonPicture", pic_name, src["uuid"], src["properties"])
target_dir = os.path.join(ext_dir, "CommonPictures")
os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{pic_name}.xml")
save_text_bom(target_file, borrowed_xml)
add_to_child_objects("CommonPicture", pic_name)
auto_borrowed_pics.append(pic_name)
info(f" Auto-borrowed: CommonPicture.{pic_name}")
else:
warn(f" CommonPicture.{pic_name} not found in source config — will strip from form")
# Collect all borrowed CommonPictures for Picture stripping
borrowed_pic_set = set()
for co_child in child_objs_el:
if isinstance(co_child.tag, str) and localname(co_child) == "CommonPicture":
borrowed_pic_set.add((co_child.text or "").strip())
# Strip <Picture> blocks referencing non-borrowed CommonPictures (reverse order)
pic_block_pattern = re.compile(r'\s*<Picture>\s*<xr:Ref>CommonPicture\.(\w+)</xr:Ref>.*?</Picture>', re.DOTALL)
pic_matches = list(pic_block_pattern.finditer(child_items_xml))
for pm in reversed(pic_matches):
cp_name = pm.group(1)
if cp_name not in borrowed_pic_set:
child_items_xml = child_items_xml[:pm.start()] + child_items_xml[pm.end():]
# Strip StdPicture blocks (except Print)
child_items_xml = re.sub(r'\s*<Picture>\s*<xr:Ref>StdPicture\.(?!Print\b)\w+</xr:Ref>.*?</Picture>', '', child_items_xml, flags=re.DOTALL)
# Auto-borrow StyleItems referenced in ChildItems
referenced_styles = set()
for m in re.finditer(r'ref="style:(\w+)"[^>]*kind="StyleItem"', child_items_xml):
referenced_styles.add(m.group(1))
for m in re.finditer(r'>style:(\w+)</\w+>', child_items_xml):
referenced_styles.add(m.group(1))
for style_name in referenced_styles:
if not test_object_borrowed("StyleItem", style_name):
style_src_file = os.path.join(cfg_dir, "StyleItems", f"{style_name}.xml")
if os.path.isfile(style_src_file):
src = read_source_object("StyleItem", style_name)
borrowed_xml = build_borrowed_object_xml("StyleItem", style_name, src["uuid"], src["properties"])
target_dir = os.path.join(ext_dir, "StyleItems")
os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{style_name}.xml")
save_text_bom(target_file, borrowed_xml)
add_to_child_objects("StyleItem", style_name)
info(f" Auto-borrowed: StyleItem.{style_name}")
else:
warn(f" StyleItem.{style_name} not found in source config")
# Auto-borrow Enums + EnumValues referenced via DesignTimeRef
referenced_enum_values = {} # enum_name -> set of value_names
for m in re.finditer(r'xr:DesignTimeRef">Enum\.(\w+)\.EnumValue\.(\w+)', child_items_xml):
e_name, ev_name = m.group(1), m.group(2)
if e_name not in referenced_enum_values:
referenced_enum_values[e_name] = set()
referenced_enum_values[e_name].add(ev_name)
for enum_name, needed_values in referenced_enum_values.items():
if not test_object_borrowed("Enum", enum_name):
enum_src_file = os.path.join(cfg_dir, "Enums", f"{enum_name}.xml")
if os.path.isfile(enum_src_file):
# Read source Enum to find EnumValue UUIDs
src_enum_tree = etree.parse(enum_src_file, etree.XMLParser(remove_blank_text=False))
src_enum_root = src_enum_tree.getroot()
src_enum_el = None
for cn in src_enum_root:
if isinstance(cn.tag, str):
src_enum_el = cn
break
# Find needed EnumValues
ev_xmls = []
for ev_node in src_enum_el.iter():
if isinstance(ev_node.tag, str) and localname(ev_node) == "EnumValue":
ev_uuid = ev_node.get("uuid", "")
name_el = None
for props in ev_node:
if isinstance(props.tag, str) and localname(props) == "Properties":
for prop in props:
if isinstance(prop.tag, str) and localname(prop) == "Name":
name_el = prop
break
if name_el is not None and (name_el.text or "").strip() in needed_values:
new_ev_uuid = str(uuid.uuid4())
ev_xmls.append(
f'\t\t\t<EnumValue uuid="{new_ev_uuid}">\n'
f'\t\t\t\t<InternalInfo/>\n'
f'\t\t\t\t<Properties>\n'
f'\t\t\t\t\t<ObjectBelonging>Adopted</ObjectBelonging>\n'
f'\t\t\t\t\t<Name>{name_el.text.strip()}</Name>\n'
f'\t\t\t\t\t<Comment/>\n'
f'\t\t\t\t\t<ExtendedConfigurationObject>{ev_uuid}</ExtendedConfigurationObject>\n'
f'\t\t\t\t</Properties>\n'
f'\t\t\t</EnumValue>'
)
# Build borrowed Enum with EnumValues
src_obj = read_source_object("Enum", enum_name)
borrowed_xml = build_borrowed_object_xml("Enum", enum_name, src_obj["uuid"], src_obj["properties"])
if ev_xmls:
ev_block = "\n".join(ev_xmls)
borrowed_xml = borrowed_xml.replace("<ChildObjects/>", f"<ChildObjects>\n{ev_block}\n\t\t</ChildObjects>")
target_dir = os.path.join(ext_dir, "Enums")
os.makedirs(target_dir, exist_ok=True)
target_file = os.path.join(target_dir, f"{enum_name}.xml")
save_text_bom(target_file, borrowed_xml)
add_to_child_objects("Enum", enum_name)
info(f" Auto-borrowed: Enum.{enum_name} (with {len(ev_xmls)} EnumValue(s))")
else:
warn(f" Enum.{enum_name} not found in source config")
# Extract the <Form ...> opening tag from source text
xml_decl = '<?xml version="1.0" encoding="UTF-8"?>'
form_tag = f'<Form version="{form_version}">'
m_decl = re.search(r'^(<\?xml[^?]*\?>)', src_form_content)
@@ -698,15 +840,17 @@ def main():
parts.append(form_tag)
parts.append("\r\n")
# Part 1: form properties + AutoCommandBar (no ChildItems — they come from base form at runtime)
# Part 1: form properties + AutoCommandBar + ChildItems
for prop_xml in form_props:
prop_xml_clean = ns_strip_pattern.sub("", prop_xml)
parts.append(f"\t{prop_xml_clean}\r\n")
if auto_cmd_xml:
parts.append(f"\t{auto_cmd_xml}\r\n")
if child_items_xml:
parts.append(f"\t{child_items_xml}\r\n")
parts.append("\t<Attributes/>\r\n")
# BaseForm: same properties + AutoCommandBar (no ChildItems)
# BaseForm: same content, indented one more level
parts.append(f'\t<BaseForm version="{form_version}">\r\n')
for prop_xml in form_props:
@@ -720,6 +864,14 @@ def main():
else:
parts.append(f"\t{line}")
parts.append("\r\n")
if child_items_xml:
ci_lines = child_items_xml.split("\n")
for li, line in enumerate(ci_lines):
if li == 0:
parts.append(f"\t\t{line}")
else:
parts.append(f"\t{line}")
parts.append("\r\n")
parts.append("\t\t<Attributes/>\r\n")
parts.append("\t</BaseForm>\r\n")
+58 -51
View File
@@ -356,77 +356,84 @@ Enums/ # Перечисления
#### 5.4.2. Структура Form.xml заимствованной формы
Form.xml заимствованной формы — **двухчастный файл**: Part 1 (результирующая форма) и BaseForm (исходная форма). Обе части содержат **только визуальные элементы** — атрибуты, события, параметры и команды базовой конфигурации **НЕ включаются**.
Form.xml заимствованной формы — **двухчастный файл**: Part 1 (результирующая форма) и BaseForm (исходная форма). Существуют **два варианта** в зависимости от наличия модификаций модуля формы.
##### Вариант A — Минимальная форма (чистое заимствование)
Когда форма заимствована без модификации модуля — Form.xml содержит **только свойства формы**, AutoCommandBar без кнопок и пустые Attributes. **Нет ChildItems**. Обе секции (Part 1 и BaseForm) идентичны.
```xml
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
<!-- ═══ ЧАСТЬ 1: Результирующая форма (база + изменения расширения) ═══ -->
<AutoTitle>false</AutoTitle>
<AutoTime>CurrentOrLast</AutoTime>
<!-- ... другие свойства формы ... -->
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<ChildItems>
<!-- Базовые кнопки: CommandName заменён на 0 -->
<Button name="ФормаОбработкаЗагрузитьИзФайла" id="51">
<CommandName>0</CommandName>
...
</Button>
<!-- Кнопки, добавленные расширением: CommandName указывает на команду -->
<Button name="ФормаНоваяКоманда" id="159">
<CommandName>Form.Command.НоваяКоманда</CommandName>
...
</Button>
</ChildItems>
<Autofill>false</Autofill>
</AutoCommandBar>
<ChildItems>
<!-- Все визуальные элементы: базовые + добавленные расширением -->
</ChildItems>
<Attributes/> <!-- пустой, ИЛИ только реквизиты расширения (id ≥ 1000000) -->
<!-- Events — только обработчики расширения с callType (если есть) -->
<Events>
<Event name="OnCreateAtServer" callType="After">Расш1_ПриСозданииПосле</Event>
</Events>
<!-- Commands — только команды расширения (id ≥ 1000000, если есть) -->
<Commands>
<Command name="НоваяКоманда" id="1000000">
<Action callType="Override">Расш1_НоваяКомандаВместо</Action>
</Command>
</Commands>
<Attributes/>
<!-- Events, Commands — только расширения (если есть) -->
<!-- ═══ ЧАСТЬ 2: Исходная форма из базовой конфигурации ═══ -->
<BaseForm version="2.17">
<AutoTitle>false</AutoTitle>
<AutoTime>CurrentOrLast</AutoTime>
<!-- те же свойства -->
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<ChildItems>
<!-- Только базовые кнопки, все CommandName = 0 -->
<Button name="ФормаОбработкаЗагрузитьИзФайла" id="51">
<CommandName>0</CommandName>
...
</Button>
</ChildItems>
<Autofill>false</Autofill>
</AutoCommandBar>
<ChildItems>
<!-- Только визуальные элементы базовой конфигурации -->
</ChildItems>
<Attributes/> <!-- всегда пустой -->
<!-- НЕТ Events, Commands, Parameters -->
<Attributes/>
</BaseForm>
</Form>
```
**Ключевые правила:**
##### Вариант B — Полная форма (заимствование процедуры модуля через `ИзменениеИКонтроль`)
1. **Часть 1** (до `<BaseForm>`) — **результирующая форма**. Содержит визуальные элементы (AutoCommandBar + ChildItems) из базовой конфигурации плюс элементы расширения. Атрибуты базовой конфигурации (DynamicList, QueryText и др.) **не включаются** — только реквизиты расширения (id ≥ 1000000) или пустой `<Attributes/>`. Events и Commands — только добавленные расширением (с `callType`).
Когда в расширении заимствуется процедура из модуля формы, Конфигуратор выгружает **полное дерево ChildItems** с применением правил очистки.
2. **Часть 2** (`<BaseForm>`) — **визуальный снимок исходной формы**. Содержит только AutoCommandBar + ChildItems + пустой `<Attributes/>`. НЕ содержит Events, Commands, Parameters. Все `<CommandName>` в кнопках заменены на `0`. Платформа использует BaseForm для контроля совместимости при обновлении конфигурации.
```xml
<Form xmlns="http://v8.1c.ru/8.3/xcf/logform" ... version="2.17">
<AutoTitle>false</AutoTitle>
<!-- свойства формы -->
<AutoCommandBar name="ФормаКоманднаяПанель" id="-1">
<Autofill>false</Autofill>
<!-- Без ChildItems (кнопки удалены) -->
</AutoCommandBar>
<ChildItems>
<!-- Полное дерево визуальных элементов -->
</ChildItems>
<Attributes/>
<!-- Events, Commands — только расширения -->
3. **Правило `<CommandName>0</CommandName>`**: во всех кнопках базовой формы (как в Part 1, так и в BaseForm) значение `<CommandName>` заменяется на `0`. Ссылки на команды конфигурации не сохраняются. Только кнопки, добавленные расширением, сохраняют ссылку на команду (напр. `Form.Command.XXX`).
<BaseForm version="2.17">
<!-- Идентичная копия (свойства + AutoCommandBar + ChildItems + Attributes) -->
</BaseForm>
</Form>
```
4. Элемент `<BaseForm>` всегда идёт **последним** в `<Form>` и имеет атрибут `version`.
**Ключевые правила (для обоих вариантов):**
5. **Правило `<DataPath>`: удаление** все элементы `<DataPath>...</DataPath>` из базовых визуальных элементов удаляются (и в Part 1, и в BaseForm). DataPath ссылается на реквизиты формы базовой конфигурации, которые не включены в расширение. Сохраняются только DataPath элементов, добавленных расширением (ссылающихся на собственные реквизиты расширения).
1. **Свойства формы** — элементы между `<Form>` и `<AutoCommandBar>` (напр. `AutoTitle`, `AutoTime`, `UsePostingMode`, `RepostOnWrite`, `WindowOpeningMode`, `Customizable`, `CommandBarLocation`) копируются из исходной формы в обе секции.
6. **Правило `<Events>` элементов: удаление** — все блоки `<Events>` внутри визуальных элементов (AutoCommandBar и ChildItems) удаляются (и в Part 1, и в BaseForm). Обработчики событий базовой конфигурации не переносятся. При модификации формы обработчики расширения добавляются только в Part 1 с атрибутом `callType`.
2. **AutoCommandBar** — присутствует всегда с `id="-1"`, но без `<ChildItems>` (кнопки удаляются). `<Autofill>` = `false`.
7. **Свойства формы** — элементы между `<Form>` и `<AutoCommandBar>` (например, `WindowOpeningMode`, `AutoFillCheck`, `AutoTitle`, `AutoTime`, `UsePostingMode`, `RepostOnWrite`, `Customizable`, `CommandBarLocation`) копируются из исходной формы в обе части (Part 1 и BaseForm).
3. **Attributes** — пустой `<Attributes/>` в обеих секциях. Атрибуты базовой конфигурации **не включаются**. Реквизиты расширения (id ≥ 1000000) добавляются только в Part 1.
4. **BaseForm** — последний элемент в `<Form>`, атрибут `version`. В BaseForm **нет** Events, Commands, Parameters.
5. **DataPath: удаление** (вариант B) — все `<DataPath>` из базовых элементов удаляются в обеих секциях. DataPath ссылается на реквизиты, не включённые в расширение.
6. **TitleDataPath: удаление** (вариант B) — все `<TitleDataPath>` удаляются (напр. `Объект.Товары.RowsCount` — путь недействителен без базовых атрибутов).
7. **TypeLink: удаление** (вариант B) — блоки `<TypeLink>` с `<xr:DataPath>Items.*</xr:DataPath>` удаляются (человекочитаемые пути, которые нельзя преобразовать в UUID-формат Конфигуратора).
8. **Events элементов: удаление** (вариант B) — все `<Events>` внутри визуальных элементов удаляются в обеих секциях. Обработчики расширения добавляются через `elementEvents` в Part 1 с `callType`.
9. **Picture stripping** (вариант B) — блоки `<Picture>` с `<xr:Ref>CommonPicture.XXX</xr:Ref>` удаляются, если `CommonPicture.XXX` **не заимствован** в расширение. Сам элемент PictureDecoration остаётся, только `<Picture>` убирается. `StdPicture.Print` сохраняется, остальные StdPicture удаляются.
10. **Авто-заимствование CommonPictures** — при заимствовании формы автоматически заимствуются все CommonPictures, на которые ссылаются элементы формы.
11. **Авто-заимствование StyleItems** — элементы формы ссылаются на StyleItems через `<Font ref="style:XXX" kind="StyleItem"/>` и `<BackColor>style:XXX</BackColor>`. Все такие StyleItems должны быть заимствованы. Стандартные стили (NormalTextFont, AccentColor, FormBackColor и др.) не имеют файлов и автоматически пропускаются.
12. **Авто-заимствование Enums + EnumValues**`<ChoiceParameters>` могут содержать `<Value xsi:type="xr:DesignTimeRef">Enum.XXX.EnumValue.YYY</Value>`. Перечисление `Enum.XXX` заимствуется вместе с конкретными `EnumValue` (borrowed с `ExtendedConfigurationObject` указывающим на UUID оригинального значения).
#### 5.4.3. Нумерация ID элементов