fix(cfe-borrow): strip DataPath, Events and preserve form properties in Borrow-Form

Borrowed forms failed to load with "Неверный путь к данным" and "Событие не было загружено"
errors. Root cause: base form elements contained DataPath and Events referencing attributes
and handlers not present in the extension.

Changes:
- Strip <DataPath> from base elements in both AutoCommandBar and ChildItems
- Strip element-level <Events> from both sections
- Collect form-level properties (AutoTitle, WindowOpeningMode, etc.) and write them
  into both main and BaseForm sections
- Update 1c-extension-spec.md with rules 5-7

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-03-10 19:12:24 +03:00
parent d94ffdea99
commit c72f2210b5
3 changed files with 60 additions and 6 deletions
@@ -476,13 +476,23 @@ function Borrow-Form {
$formVersion = $srcFormEl.GetAttribute("version")
if (-not $formVersion) { $formVersion = "2.17" }
# Find direct children: AutoCommandBar, ChildItems (visual elements only)
# Find direct children: form properties, AutoCommandBar, ChildItems
$srcAutoCmd = $null
$srcChildItems = $null
$formProps = @()
$reachedVisual = $false
foreach ($fc in $srcFormEl.ChildNodes) {
if ($fc.NodeType -ne 'Element') { continue }
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) { $srcAutoCmd = $fc }
elseif ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) { $srcChildItems = $fc }
if ($fc.LocalName -eq 'AutoCommandBar' -and -not $srcAutoCmd) {
$reachedVisual = $true; $srcAutoCmd = $fc; continue
}
if ($fc.LocalName -eq 'ChildItems' -and -not $srcChildItems) {
$reachedVisual = $true; $srcChildItems = $fc; continue
}
if (-not $reachedVisual) {
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
$formProps += $fc.OuterXml
}
}
# Get OuterXml and strip redundant namespace redeclarations (they're on root <Form>)
@@ -496,6 +506,10 @@ function Borrow-Form {
$autoCmdXml = [regex]::Replace($autoCmdXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
# Replace Autofill true → false
$autoCmdXml = $autoCmdXml -replace '<Autofill>true</Autofill>', '<Autofill>false</Autofill>'
# Strip DataPath (references base form attributes not present in extension)
$autoCmdXml = [regex]::Replace($autoCmdXml, '\s*<DataPath>[^<]*</DataPath>', '')
# Strip element-level Events (base form event handlers not present in extension)
$autoCmdXml = [regex]::Replace($autoCmdXml, '(?s)\s*<Events>.*?</Events>', '')
}
$childItemsXml = ""
@@ -504,6 +518,9 @@ function Borrow-Form {
$childItemsXml = [regex]::Replace($childItemsXml, $nsStripPattern, '')
# Replace all CommandName values with 0 in ChildItems too
$childItemsXml = [regex]::Replace($childItemsXml, '<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>')
# Strip DataPath and element-level Events
$childItemsXml = [regex]::Replace($childItemsXml, '\s*<DataPath>[^<]*</DataPath>', '')
$childItemsXml = [regex]::Replace($childItemsXml, '(?s)\s*<Events>.*?</Events>', '')
} else {
$childItemsXml = "<ChildItems/>"
}
@@ -521,7 +538,11 @@ function Borrow-Form {
$formXmlSb.Append($formTag) | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
# Part 1: visual elements (add leading tab to first line of each block)
# Part 1: form properties + visual elements
foreach ($propXml in $formProps) {
$propXml = [regex]::Replace($propXml, $nsStripPattern, '')
$formXmlSb.Append("`t$propXml`r`n") | Out-Null
}
if ($autoCmdXml) {
$formXmlSb.Append("`t$autoCmdXml") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
@@ -531,10 +552,14 @@ function Borrow-Form {
$formXmlSb.Append("`t<Attributes/>") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
# BaseForm: same visual elements, indented one more level
# BaseForm: form properties + same visual elements, indented one more level
$formXmlSb.Append("`t<BaseForm version=`"${formVersion}`">") | Out-Null
$formXmlSb.Append("`r`n") | Out-Null
foreach ($propXml in $formProps) {
$propXml = [regex]::Replace($propXml, $nsStripPattern, '')
$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"
@@ -653,14 +653,23 @@ def main():
src_auto_cmd = None
src_child_items = None
form_props = []
reached_visual = False
for fc in src_form_el:
if not isinstance(fc.tag, str):
continue
ln = localname(fc)
if ln == "AutoCommandBar" and src_auto_cmd is None:
reached_visual = True
src_auto_cmd = fc
elif ln == "ChildItems" and src_child_items is None:
continue
if ln == "ChildItems" and src_child_items is None:
reached_visual = True
src_child_items = fc
continue
if not reached_visual:
# Form-level properties before AutoCommandBar (WindowOpeningMode, AutoFillCheck, etc.)
form_props.append(etree.tostring(fc, encoding="unicode"))
ns_strip_pattern = re.compile(r'\s+xmlns(?::\w+)?="[^"]*"')
@@ -670,12 +679,19 @@ def main():
auto_cmd_xml = ns_strip_pattern.sub("", auto_cmd_xml)
auto_cmd_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', auto_cmd_xml)
auto_cmd_xml = auto_cmd_xml.replace('<Autofill>true</Autofill>', '<Autofill>false</Autofill>')
# Strip DataPath (references base form attributes not present in extension)
auto_cmd_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', auto_cmd_xml)
# Strip element-level Events (base form event handlers not present in extension)
auto_cmd_xml = re.sub(r'\s*<Events>.*?</Events>', '', auto_cmd_xml, flags=re.DOTALL)
child_items_xml = ""
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)
child_items_xml = re.sub(r'<CommandName>[^<]*</CommandName>', '<CommandName>0</CommandName>', child_items_xml)
# Strip DataPath and element-level Events
child_items_xml = re.sub(r'\s*<DataPath>[^<]*</DataPath>', '', child_items_xml)
child_items_xml = re.sub(r'\s*<Events>.*?</Events>', '', child_items_xml, flags=re.DOTALL)
else:
child_items_xml = "<ChildItems/>"
@@ -696,6 +712,10 @@ def main():
parts.append(form_tag)
parts.append("\r\n")
# Form properties (WindowOpeningMode, AutoFillCheck, etc.)
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")
parts.append(f"\t{child_items_xml}\r\n")
@@ -704,6 +724,9 @@ def main():
# BaseForm
parts.append(f'\t<BaseForm version="{form_version}">\r\n')
for prop_xml in form_props:
prop_xml_clean = ns_strip_pattern.sub("", prop_xml)
parts.append(f"\t\t{prop_xml_clean}\r\n")
if auto_cmd_xml:
ac_lines = auto_cmd_xml.split("\n")
for li, line in enumerate(ac_lines):
+6
View File
@@ -422,6 +422,12 @@ Form.xml заимствованной формы — **двухчастный ф
4. Элемент `<BaseForm>` всегда идёт **последним** в `<Form>` и имеет атрибут `version`.
5. **Правило `<DataPath>`: удаление** — все элементы `<DataPath>...</DataPath>` из базовых визуальных элементов удаляются (и в Part 1, и в BaseForm). DataPath ссылается на реквизиты формы базовой конфигурации, которые не включены в расширение. Сохраняются только DataPath элементов, добавленных расширением (ссылающихся на собственные реквизиты расширения).
6. **Правило `<Events>` элементов: удаление** — все блоки `<Events>` внутри визуальных элементов (AutoCommandBar и ChildItems) удаляются (и в Part 1, и в BaseForm). Обработчики событий базовой конфигурации не переносятся. При модификации формы обработчики расширения добавляются только в Part 1 с атрибутом `callType`.
7. **Свойства формы** — элементы между `<Form>` и `<AutoCommandBar>` (например, `WindowOpeningMode`, `AutoFillCheck`, `AutoTitle`, `AutoTime`, `UsePostingMode`, `RepostOnWrite`, `Customizable`, `CommandBarLocation`) копируются из исходной формы в обе части (Part 1 и BaseForm).
#### 5.4.3. Нумерация ID элементов
| Диапазон | Принадлежность |