mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-16 08:19:01 +03:00
fix(cfe-borrow): вставка только в собственный ChildObjects объекта
Заимствованное содержимое вставлялось текстом по ВСЕМ вхождениям <ChildObjects>, а у объекта с табличными частями свой контейнер один, но вхождений столько же, сколько ТЧ. При повторном заимствовании по тому же объекту ps1 выдавал невалидный XML («mismatched tag»), py — валидный, но с табличными частями внутри табличной части и дублем ТЧ. Отдельно ветка «Replace empty ChildObjects» в ps1 переписывала КАЖДЫЙ блок содержимым первого — теряла данные. Свой <ChildObjects> закрывается в файле последним: объект в файле один, вложенные ТЧ закрываются раньше. Правило вынесено в общую функцию каждого порта, на неё переведены все места вставки. Дедуп при повторном заимствовании брал имена регуляркой «первый <ChildObjects> до первого </ChildObjects>» — у объекта с ТЧ этот отрезок обрывается на закрытии первой ТЧ: захватывает имена её колонок и теряет всё, что идёт после. Заменён разбором прямых детей своего контейнера. В py дедуп дополнительно сканировал все <Name> во всём файле, включая имя самого объекта. Проверено: два заимствования подряд по одному документу дают в обоих портах валидный XML, 5 табличных частей на верхнем уровне, вложенности и дублей нет; выходы портов отличаются только свежими uuid. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
8c915df0d9
commit
b5662a380d
@@ -1,4 +1,4 @@
|
||||
# cfe-borrow v1.27 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.28 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
param(
|
||||
[Parameter(Mandatory)][string]$ExtensionPath,
|
||||
@@ -75,6 +75,50 @@ function Rewrite-ChoiceParameterLinks {
|
||||
return $xml
|
||||
}
|
||||
|
||||
# Имена ПРЯМЫХ детей собственного <ChildObjects> объекта — для дедупа при повторном
|
||||
# заимствовании. Текстом это не снять: regex «первый <ChildObjects> до первого </ChildObjects>»
|
||||
# у объекта с табличными частями обрывается на закрытии первой ТЧ, забирает имена её колонок и
|
||||
# теряет то, что идёт после неё.
|
||||
function Get-OwnChildObjectNames {
|
||||
param([string]$objFile)
|
||||
|
||||
$names = @{}
|
||||
if (-not (Test-Path -LiteralPath $objFile)) { return $names }
|
||||
$doc = New-Object System.Xml.XmlDocument
|
||||
$doc.PreserveWhitespace = $false
|
||||
try { $doc.Load($objFile) } catch { return $names }
|
||||
$objEl = $null
|
||||
foreach ($c in $doc.DocumentElement.ChildNodes) {
|
||||
if ($c.NodeType -eq 'Element') { $objEl = $c; break }
|
||||
}
|
||||
if (-not $objEl) { return $names }
|
||||
$childObjs = $objEl.SelectSingleNode("*[local-name()='ChildObjects']")
|
||||
if (-not $childObjs) { return $names }
|
||||
foreach ($child in $childObjs.ChildNodes) {
|
||||
if ($child.NodeType -ne 'Element') { continue }
|
||||
$nameNode = $child.SelectSingleNode("*[local-name()='Properties']/*[local-name()='Name']")
|
||||
if ($nameNode) { $names[$nameNode.InnerText.Trim()] = $true }
|
||||
}
|
||||
return $names
|
||||
}
|
||||
|
||||
# Вставка в СОБСТВЕННЫЙ <ChildObjects> объекта. Свой контейнер закрывается в файле последним:
|
||||
# объект в файле один, а вложенные <ChildObjects> табличных частей закрываются раньше. Замена по
|
||||
# всем вхождениям раскидывала реквизиты по каждой ТЧ — ps1 рвал XML, py прятал ТЧ внутрь ТЧ.
|
||||
function Insert-IntoOwnChildObjects {
|
||||
param([string]$text, [string]$content)
|
||||
|
||||
$closeIdx = $text.LastIndexOf('</ChildObjects>')
|
||||
if ($closeIdx -ge 0) {
|
||||
return $text.Substring(0, $closeIdx) + "${content}`r`n`t`t" + $text.Substring($closeIdx)
|
||||
}
|
||||
# Своего закрывающего тега нет — значит контейнер самозакрытый (детей у него нет, вложенных тоже)
|
||||
$selfMatches = [regex]::Matches($text, '<ChildObjects\s*/>')
|
||||
if ($selfMatches.Count -eq 0) { return $text }
|
||||
$m = $selfMatches[$selfMatches.Count - 1]
|
||||
return $text.Substring(0, $m.Index) + "<ChildObjects>${content}`r`n`t`t</ChildObjects>" + $text.Substring($m.Index + $m.Length)
|
||||
}
|
||||
|
||||
# --- 1. Resolve paths ---
|
||||
if (-not [System.IO.Path]::IsPathRooted($ExtensionPath)) {
|
||||
$ExtensionPath = Join-Path (Get-Location).Path $ExtensionPath
|
||||
@@ -1597,14 +1641,9 @@ function Merge-AttributesIntoObject {
|
||||
if ($text3.Length -gt 0 -and $text3[0] -eq [char]0xFEFF) { $text3 = $text3.Substring(1) }
|
||||
$text3 = $text3.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||
|
||||
# Insert attributes — handle both <ChildObjects/> and <ChildObjects>...</ChildObjects>.
|
||||
# Самозакрытый элемент раскрывается здесь же, а не пробельным узлом в DOM: тот давал
|
||||
# Самозакрытый элемент раскрывается текстом, а не пробельным узлом в DOM: тот давал
|
||||
# лишнюю строку с табуляцией перед первым <Attribute> (у Конфигуратора пустых строк нет).
|
||||
if ($text3 -match '<ChildObjects\s*/>') {
|
||||
$text3 = [regex]::Replace($text3, '<ChildObjects\s*/>', "<ChildObjects>${allAttrXml}`r`n`t`t</ChildObjects>")
|
||||
} else {
|
||||
$text3 = $text3.Replace('</ChildObjects>', "${allAttrXml}`r`n`t`t</ChildObjects>")
|
||||
}
|
||||
$text3 = Insert-IntoOwnChildObjects $text3 $allAttrXml
|
||||
|
||||
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||
@@ -1664,12 +1703,7 @@ function Borrow-MainAttribute {
|
||||
$objContent = [System.IO.File]::ReadAllText($objFile, (New-Object System.Text.UTF8Encoding($true)))
|
||||
|
||||
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||
$existingChildNames = @{}
|
||||
if ($objContent -match '(?s)<ChildObjects>(.*?)</ChildObjects>') {
|
||||
foreach ($nm in [regex]::Matches($Matches[1], '<Name>(\w+)</Name>')) {
|
||||
$existingChildNames[$nm.Groups[1].Value] = $true
|
||||
}
|
||||
}
|
||||
$existingChildNames = Get-OwnChildObjectNames $objFile
|
||||
$insertAttrs = @($srcAttrs | Where-Object { -not $existingChildNames.ContainsKey($_.Name) })
|
||||
$insertTS = @($srcTS | Where-Object { -not $existingChildNames.ContainsKey($_.Name) })
|
||||
|
||||
@@ -1701,17 +1735,9 @@ function Borrow-MainAttribute {
|
||||
}
|
||||
}
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
# Добавить заимствованное содержимое в ChildObjects объекта (там уже может лежать <Form>)
|
||||
if ($adoptedContent) {
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if ($objContent -match '<ChildObjects\s*/>') {
|
||||
$objContent = $objContent -replace '<ChildObjects\s*/>', "<ChildObjects>`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elseif ($objContent -match '(?s)<ChildObjects>(.*?)</ChildObjects>') {
|
||||
$existingInner = $Matches[1]
|
||||
$objContent = $objContent -replace '(?s)<ChildObjects>(.*?)</ChildObjects>', "<ChildObjects>${existingInner}`r`n${adoptedContent}`r`n`t`t</ChildObjects>"
|
||||
}
|
||||
$objContent = Insert-IntoOwnChildObjects $objContent "`r`n${adoptedContent}"
|
||||
}
|
||||
|
||||
$encBom = New-Object System.Text.UTF8Encoding($true)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/usr/bin/env python3
|
||||
# cfe-borrow v1.27 — Borrow objects from configuration into extension (CFE)
|
||||
# cfe-borrow v1.28 — Borrow objects from configuration into extension (CFE)
|
||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||
|
||||
import argparse
|
||||
@@ -94,6 +94,50 @@ def rewrite_choice_parameter_links(xml, attr_uuids):
|
||||
return xml
|
||||
|
||||
|
||||
def get_own_child_object_names(obj_file):
|
||||
"""Имена ПРЯМЫХ детей собственного <ChildObjects> объекта — для дедупа при повторном
|
||||
заимствовании. Текстом это не снять: regex «первый <ChildObjects> до первого </ChildObjects>»
|
||||
у объекта с табличными частями обрывается на закрытии первой ТЧ, забирает имена её колонок и
|
||||
теряет то, что идёт после неё."""
|
||||
names = set()
|
||||
try:
|
||||
tree = etree.parse(obj_file)
|
||||
except Exception:
|
||||
return names
|
||||
root = tree.getroot()
|
||||
obj_el = next((c for c in root if isinstance(c.tag, str)), None)
|
||||
if obj_el is None:
|
||||
return names
|
||||
child_objs = next((c for c in obj_el if isinstance(c.tag, str) and localname(c) == "ChildObjects"), None)
|
||||
if child_objs is None:
|
||||
return names
|
||||
for child in child_objs:
|
||||
if not isinstance(child.tag, str):
|
||||
continue
|
||||
props = next((p for p in child if isinstance(p.tag, str) and localname(p) == "Properties"), None)
|
||||
if props is None:
|
||||
continue
|
||||
nm = next((n for n in props if isinstance(n.tag, str) and localname(n) == "Name"), None)
|
||||
if nm is not None and nm.text:
|
||||
names.add(nm.text.strip())
|
||||
return names
|
||||
|
||||
|
||||
def insert_into_own_child_objects(text, content):
|
||||
"""Вставка в СОБСТВЕННЫЙ <ChildObjects> объекта. Свой контейнер закрывается в файле последним:
|
||||
объект в файле один, а вложенные <ChildObjects> табличных частей закрываются раньше. Замена по
|
||||
всем вхождениям раскидывала реквизиты по каждой ТЧ — ps1 рвал XML, py прятал ТЧ внутрь ТЧ."""
|
||||
close_idx = text.rfind("</ChildObjects>")
|
||||
if close_idx >= 0:
|
||||
return text[:close_idx] + content + "\r\n\t\t" + text[close_idx:]
|
||||
# Своего закрывающего тега нет — значит контейнер самозакрытый (детей у него нет, вложенных тоже)
|
||||
self_matches = list(re.finditer(r'<ChildObjects\s*/>', text))
|
||||
if not self_matches:
|
||||
return text
|
||||
m = self_matches[-1]
|
||||
return text[:m.start()] + f"<ChildObjects>{content}\r\n\t\t</ChildObjects>" + text[m.end():]
|
||||
|
||||
|
||||
def decode_numeric_entities(s):
|
||||
"""lxml emits numeric character refs (&#xNNNN;) for non-ASCII in some self-closed
|
||||
elements where the PowerShell port writes literal characters. Normalize numeric refs
|
||||
@@ -1203,10 +1247,8 @@ def main():
|
||||
with open(obj_file, "r", encoding="utf-8-sig", newline="") as fh:
|
||||
obj_content = fh.read()
|
||||
|
||||
# Collect existing attribute names for dedup (text-based)
|
||||
existing_names = set()
|
||||
for m in re.finditer(r'<Name>(\w+)</Name>', obj_content):
|
||||
existing_names.add(m.group(1))
|
||||
# Collect existing names for dedup — только прямые дети своего ChildObjects
|
||||
existing_names = get_own_child_object_names(obj_file)
|
||||
|
||||
all_attr_xml = ""
|
||||
added = 0
|
||||
@@ -1217,11 +1259,7 @@ def main():
|
||||
added += 1
|
||||
|
||||
if added > 0:
|
||||
# Insert attributes — handle both <ChildObjects/> and <ChildObjects>...</ChildObjects>
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>{all_attr_xml}\r\n\t\t</ChildObjects>", obj_content)
|
||||
else:
|
||||
obj_content = obj_content.replace("</ChildObjects>", f"{all_attr_xml}\r\n\t\t</ChildObjects>")
|
||||
obj_content = insert_into_own_child_objects(obj_content, all_attr_xml)
|
||||
write_utf8_bom(obj_file, obj_content)
|
||||
info(f" Merged {added} attribute(s) into: {obj_file}")
|
||||
|
||||
@@ -1265,11 +1303,7 @@ def main():
|
||||
obj_content = fh.read()
|
||||
|
||||
# Dedup: skip attributes/TS already present in object's ChildObjects (idempotent re-borrow)
|
||||
existing_child_names = set()
|
||||
m_co = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||
if m_co:
|
||||
for nm in re.findall(r'<Name>(\w+)</Name>', m_co.group(1)):
|
||||
existing_child_names.add(nm)
|
||||
existing_child_names = get_own_child_object_names(obj_file)
|
||||
insert_attrs = [a for a in src_attrs if a["Name"] not in existing_child_names]
|
||||
insert_ts = [t for t in src_ts if t["Name"] not in existing_child_names]
|
||||
|
||||
@@ -1295,19 +1329,9 @@ def main():
|
||||
if props_xml:
|
||||
obj_content = obj_content.replace("</ExtendedConfigurationObject>", f"</ExtendedConfigurationObject>{props_xml}", 1)
|
||||
|
||||
# Replace empty ChildObjects with adopted content
|
||||
# Добавить заимствованное содержимое в ChildObjects объекта (там уже может лежать <Form>)
|
||||
if adopted_content:
|
||||
# Handle <ChildObjects/> (self-closing)
|
||||
if re.search(r'<ChildObjects\s*/>', obj_content):
|
||||
obj_content = re.sub(r'<ChildObjects\s*/>', f"<ChildObjects>\r\n{adopted_content}\r\n\t\t</ChildObjects>", obj_content)
|
||||
# Handle <ChildObjects>...</ChildObjects> (may already have Form entry)
|
||||
elif re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content):
|
||||
m = re.search(r'(?s)<ChildObjects>(.*?)</ChildObjects>', obj_content)
|
||||
existing_inner = m.group(1)
|
||||
obj_content = obj_content.replace(
|
||||
f"<ChildObjects>{existing_inner}</ChildObjects>",
|
||||
f"<ChildObjects>{existing_inner}\r\n{adopted_content}\r\n\t\t</ChildObjects>"
|
||||
)
|
||||
obj_content = insert_into_own_child_objects(obj_content, f"\r\n{adopted_content}")
|
||||
|
||||
write_utf8_bom(obj_file, obj_content)
|
||||
info(f" Enriched object: {obj_file}")
|
||||
|
||||
Reference in New Issue
Block a user