mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-06-14 01:44:57 +03:00
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:
@@ -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
|
||||
|
||||
@@ -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
@@ -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 элементов
|
||||
|
||||
|
||||
Reference in New Issue
Block a user