mirror of
https://github.com/Nikolay-Shirokov/cc-1c-skills.git
synced 2026-08-21 02:29:42 +03:00
refactor(skills): регистрация в ChildObjects — именованная функция под анти-дрейфом
Три навыка делали одну работу тремя инлайновыми копиями в main(), и одна копия молча разъехалась: role-compile.py рапортовал об успехе, не тронув файл. Это ровно тот класс, от которого заведён check-inline-drift.mjs, но в реестр регистрацию было не записать — он работает по именованным функциям. Регистрация вынесена в Register-InChildObjects / register_in_childobjects с контрактом (родительский XML, тег родителя, тег потомка, имя) и исходом added | already | no-childobj | no-config. Печать сообщений осталась на вызывающей стороне: тексты у навыков разные, и сведение их меняло бы вывод. Эталон — meta-compile, форма функции задана на .ps1. role-compile берёт его тело копией, и гард это подтверждает. subsystem-compile идёт отдельным вариантом с обоснованием: родителем бывает вложенный Subsystem.xml произвольной глубины, поэтому отступ он берёт из документа, а запись дописывает в конец блока — фиксированные три табуляции там неверны, и группировать по типу нечего. Вынос поведенчески нейтрален: 837 кейсов зелёные на обоих рантаймах, снэпшоты не дрейфовали, матрица «навык × порт × изломанная форма Configuration.xml» даёт те же исходы, а порты — байт-в-байт одинаковый файл. Попутно из subsystem-compile.py убран мёртвый ET.SubElement перед pass: дерево мутировалось и выбрасывалось, правка идёт по сырому тексту. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2b0ac3ea75
commit
1c63d9ddcc
@@ -1,4 +1,4 @@
|
|||||||
# meta-compile v1.94 — Compile 1C metadata object from JSON
|
# meta-compile v1.95 — Compile 1C metadata object from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -5169,91 +5169,85 @@ if ($commands -and $commands.Count -gt 0) {
|
|||||||
|
|
||||||
# --- 17. Register in Configuration.xml ---
|
# --- 17. Register in Configuration.xml ---
|
||||||
|
|
||||||
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
|
# Регистрация объекта в <ChildObjects> родительского XML. Общая реализация: эталон —
|
||||||
$regResult = $null
|
# meta-compile, копия — role-compile. Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
# Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
|
||||||
|
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
|
||||||
|
|
||||||
|
$doc = New-Object System.Xml.XmlDocument
|
||||||
|
$doc.PreserveWhitespace = $true
|
||||||
|
$doc.Load($ParentXmlPath)
|
||||||
|
|
||||||
|
$nsMgr = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||||
|
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
|
||||||
|
$childObjects = $doc.SelectSingleNode("//md:$ParentTag/md:ChildObjects", $nsMgr)
|
||||||
|
if (-not $childObjects) { return "no-childobj" }
|
||||||
|
|
||||||
|
$existing = $childObjects.SelectNodes("md:$ChildTag", $nsMgr)
|
||||||
|
foreach ($e in $existing) {
|
||||||
|
if ($e.InnerText -eq $ChildName) { return "already" }
|
||||||
|
}
|
||||||
|
|
||||||
|
$newElem = $doc.CreateElement($ChildTag, "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
$newElem.InnerText = $ChildName
|
||||||
|
|
||||||
|
if ($existing.Count -gt 0) {
|
||||||
|
# Insert after last existing element of same type
|
||||||
|
$lastElem = $existing[$existing.Count - 1]
|
||||||
|
$newWs = $doc.CreateWhitespace("`n`t`t`t")
|
||||||
|
$childObjects.InsertAfter($newWs, $lastElem) | Out-Null
|
||||||
|
$childObjects.InsertAfter($newElem, $newWs) | Out-Null
|
||||||
|
} else {
|
||||||
|
# No existing elements of this type — insert before closing whitespace.
|
||||||
|
# Самозакрытый <ChildObjects/> попадает сюда же: LastChild пуст, идёт ветка AppendChild.
|
||||||
|
$lastChild = $childObjects.LastChild
|
||||||
|
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||||
|
$newWs = $doc.CreateWhitespace("`n`t`t`t")
|
||||||
|
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
||||||
|
$childObjects.InsertBefore($newElem, $lastChild) | Out-Null
|
||||||
|
} else {
|
||||||
|
$childObjects.AppendChild($doc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||||
|
$childObjects.AppendChild($newElem) | Out-Null
|
||||||
|
$childObjects.AppendChild($doc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save. Пишем через MemoryStream, а не прямо в файл: нужен шаг пост-обработки
|
||||||
|
# строки — XmlWriter отдаёт `encoding="utf-8"` и `<a />`, Конфигуратор пишет
|
||||||
|
# `encoding="UTF-8"` и `<a/>`.
|
||||||
|
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$cfgSettings.Indent = $false
|
||||||
|
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
||||||
|
$doc.Save($writer)
|
||||||
|
$writer.Flush(); $writer.Close()
|
||||||
|
|
||||||
|
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
||||||
|
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ([System.IO.File]::ReadAllText($ParentXmlPath) -notmatch "`r`n") { "`n" } else { "`r`n" }
|
||||||
|
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($ParentXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
||||||
|
|
||||||
|
return "added"
|
||||||
|
}
|
||||||
|
|
||||||
# XML tag name for Configuration.xml ChildObjects
|
# XML tag name for Configuration.xml ChildObjects
|
||||||
$childTag = $objType
|
$childTag = $objType
|
||||||
|
|
||||||
if (Test-Path $configXmlPath) {
|
$configXmlPath = Join-Path $OutputDir "Configuration.xml"
|
||||||
$configDoc = New-Object System.Xml.XmlDocument
|
$regResult = Register-InChildObjects $configXmlPath "Configuration" $childTag $objName
|
||||||
$configDoc.PreserveWhitespace = $true
|
|
||||||
$configDoc.Load($configXmlPath)
|
|
||||||
|
|
||||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable)
|
|
||||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
|
|
||||||
$childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr)
|
|
||||||
if ($childObjects) {
|
|
||||||
$existing = $childObjects.SelectNodes("md:$childTag", $nsMgr)
|
|
||||||
$alreadyExists = $false
|
|
||||||
foreach ($e in $existing) {
|
|
||||||
if ($e.InnerText -eq $objName) {
|
|
||||||
$alreadyExists = $true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($alreadyExists) {
|
|
||||||
$regResult = "already"
|
|
||||||
} else {
|
|
||||||
$newElem = $configDoc.CreateElement($childTag, "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
$newElem.InnerText = $objName
|
|
||||||
|
|
||||||
if ($existing.Count -gt 0) {
|
|
||||||
# Insert after last existing element of same type
|
|
||||||
$lastElem = $existing[$existing.Count - 1]
|
|
||||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
||||||
$childObjects.InsertAfter($newWs, $lastElem) | Out-Null
|
|
||||||
$childObjects.InsertAfter($newElem, $newWs) | Out-Null
|
|
||||||
} else {
|
|
||||||
# No existing elements of this type — insert before closing whitespace
|
|
||||||
$lastChild = $childObjects.LastChild
|
|
||||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
|
||||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
||||||
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
|
||||||
$childObjects.InsertBefore($newElem, $lastChild) | Out-Null
|
|
||||||
} else {
|
|
||||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
|
||||||
$childObjects.AppendChild($newElem) | Out-Null
|
|
||||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save. Пишем через MemoryStream, а не прямо в файл: нужен шаг пост-обработки
|
|
||||||
# строки — XmlWriter отдаёт `encoding="utf-8"` и `<a />`, Конфигуратор пишет
|
|
||||||
# `encoding="UTF-8"` и `<a/>`.
|
|
||||||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
|
||||||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
||||||
$cfgSettings.Indent = $false
|
|
||||||
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
|
||||||
$memStream = New-Object System.IO.MemoryStream
|
|
||||||
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
|
||||||
$configDoc.Save($writer)
|
|
||||||
$writer.Flush(); $writer.Close()
|
|
||||||
|
|
||||||
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
|
||||||
$memStream.Close()
|
|
||||||
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
|
||||||
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
|
||||||
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
|
||||||
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
|
||||||
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
|
||||||
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
|
||||||
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
|
||||||
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
|
||||||
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
|
||||||
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
|
||||||
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
|
||||||
|
|
||||||
$regResult = "added"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$regResult = "no-childobj"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$regResult = "no-config"
|
|
||||||
}
|
|
||||||
|
|
||||||
# --- 18. Summary ---
|
# --- 18. Summary ---
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# meta-compile v1.94 — Compile 1C metadata object from JSON
|
# meta-compile v1.95 — Compile 1C metadata object from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
@@ -5148,78 +5148,82 @@ if commands:
|
|||||||
# 17. Register in Configuration.xml
|
# 17. Register in Configuration.xml
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
config_xml_path = os.path.join(output_dir, 'Configuration.xml')
|
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
|
||||||
reg_result = None
|
"""Регистрация объекта в <ChildObjects> родительского XML.
|
||||||
|
|
||||||
child_tag = obj_type
|
Общая реализация: эталон — meta-compile, копия — role-compile.
|
||||||
|
Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(parent_xml_path):
|
||||||
|
return 'no-config'
|
||||||
|
|
||||||
if os.path.isfile(config_xml_path):
|
# Read raw content, preserving BOM/EOL byte-for-byte (newline='' => no translation)
|
||||||
# Read raw content, preserving BOM/EOL byte-for-byte (newline='' => no translation).
|
with open(parent_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
||||||
with open(config_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
|
||||||
config_content = f.read()
|
config_content = f.read()
|
||||||
|
|
||||||
ns = 'http://v8.1c.ru/8.3/MDClasses'
|
ns = 'http://v8.1c.ru/8.3/MDClasses'
|
||||||
# ET is used ONLY read-only here: to locate ChildObjects and detect a duplicate.
|
# ET is used ONLY read-only here: to locate ChildObjects and detect a duplicate.
|
||||||
# We deliberately do NOT re-serialize Configuration.xml with ElementTree.write():
|
# We deliberately do NOT re-serialize Configuration.xml with ElementTree.write():
|
||||||
# it drops every xmlns declaration used only inside attribute VALUES (e.g.
|
# it drops every xmlns declaration used only inside attribute VALUES (e.g.
|
||||||
# xsi:type="app:ApplicationUsePurpose" in UsePurposes) because ET never sees those
|
# xsi:type="app:ApplicationUsePurpose" in UsePurposes) because ET never sees such
|
||||||
# prefixes in element/attribute names. The dropped declaration makes XDTO read the
|
# prefixes in element/attribute names. The dropped declaration makes XDTO read the
|
||||||
# value as anyType and Designer refuses to load the file (issue #38). Registration is
|
# value as anyType and Designer refuses to load the file (issue #38). Registration is
|
||||||
# therefore done by raw-text insertion, preserving BOM, EOL and all namespaces
|
# therefore done by raw-text insertion, preserving BOM, EOL and all namespaces
|
||||||
# byte-for-byte (same approach as subsystem-compile).
|
# byte-for-byte (same approach as subsystem-compile).
|
||||||
tree = ET.parse(config_xml_path)
|
tree = ET.parse(parent_xml_path)
|
||||||
root = tree.getroot()
|
root = tree.getroot()
|
||||||
|
|
||||||
child_objects = root.find(f'{{{ns}}}Configuration/{{{ns}}}ChildObjects')
|
child_objects = root.find(f'{{{ns}}}{parent_tag}/{{{ns}}}ChildObjects')
|
||||||
if child_objects is None:
|
if child_objects is None:
|
||||||
# Try direct path
|
# Try direct path
|
||||||
config_elem = root.find(f'{{{ns}}}Configuration')
|
parent_elem = root.find(f'{{{ns}}}{parent_tag}')
|
||||||
if config_elem is not None:
|
if parent_elem is not None:
|
||||||
child_objects = config_elem.find(f'{{{ns}}}ChildObjects')
|
child_objects = parent_elem.find(f'{{{ns}}}ChildObjects')
|
||||||
|
|
||||||
if child_objects is None:
|
if child_objects is None:
|
||||||
reg_result = 'no-childobj'
|
return 'no-childobj'
|
||||||
|
|
||||||
|
existing = child_objects.findall(f'{{{ns}}}{child_tag}')
|
||||||
|
if any((e.text or '').strip() == child_name for e in existing):
|
||||||
|
return 'already'
|
||||||
|
|
||||||
|
eol = '\r\n' if '\r\n' in config_content else '\n'
|
||||||
|
entry = f'<{child_tag}>{esc_xml_text(child_name)}</{child_tag}>'
|
||||||
|
|
||||||
|
block = re.search(r'<ChildObjects\s*>.*?</ChildObjects>', config_content, re.S)
|
||||||
|
if block is None:
|
||||||
|
# Empty self-closing <ChildObjects/> => open it with the first entry.
|
||||||
|
empty = re.search(r'<ChildObjects\s*/>', config_content)
|
||||||
|
if empty is None:
|
||||||
|
return 'no-childobj'
|
||||||
|
replacement = f'<ChildObjects>{eol}\t\t\t{entry}{eol}\t\t</ChildObjects>'
|
||||||
|
new_content = config_content[:empty.start()] + replacement + config_content[empty.end():]
|
||||||
|
write_utf8_bom(parent_xml_path, new_content)
|
||||||
|
return 'added'
|
||||||
|
|
||||||
|
close_same = f'</{child_tag}>'
|
||||||
|
last_same = config_content.rfind(close_same, block.start(), block.end())
|
||||||
|
if last_same != -1:
|
||||||
|
# After the last element of the same type (keeps them grouped).
|
||||||
|
insert_at = last_same + len(close_same)
|
||||||
|
new_content = (config_content[:insert_at]
|
||||||
|
+ f'{eol}\t\t\t{entry}'
|
||||||
|
+ config_content[insert_at:])
|
||||||
else:
|
else:
|
||||||
existing = child_objects.findall(f'{{{ns}}}{child_tag}')
|
# No element of this type yet: new line before </ChildObjects>,
|
||||||
already_exists = any((e.text or '').strip() == obj_name for e in existing)
|
# reusing the block's existing closing indent for </ChildObjects>.
|
||||||
|
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
|
||||||
|
new_content = (config_content[:close_at]
|
||||||
|
+ f'\t{entry}{eol}\t\t'
|
||||||
|
+ config_content[close_at:])
|
||||||
|
write_utf8_bom(parent_xml_path, new_content)
|
||||||
|
return 'added'
|
||||||
|
|
||||||
if already_exists:
|
|
||||||
reg_result = 'already'
|
|
||||||
else:
|
|
||||||
eol = '\r\n' if '\r\n' in config_content else '\n'
|
|
||||||
entry = f'<{child_tag}>{esc_xml_text(obj_name)}</{child_tag}>'
|
|
||||||
|
|
||||||
block = re.search(r'<ChildObjects\s*>.*?</ChildObjects>', config_content, re.S)
|
child_tag = obj_type
|
||||||
if block is None:
|
config_xml_path = os.path.join(output_dir, 'Configuration.xml')
|
||||||
# Empty self-closing <ChildObjects/> => open it with the first entry.
|
reg_result = register_in_childobjects(config_xml_path, 'Configuration', child_tag, obj_name)
|
||||||
empty = re.search(r'<ChildObjects\s*/>', config_content)
|
|
||||||
if empty is None:
|
|
||||||
reg_result = 'no-childobj'
|
|
||||||
else:
|
|
||||||
replacement = f'<ChildObjects>{eol}\t\t\t{entry}{eol}\t\t</ChildObjects>'
|
|
||||||
new_content = config_content[:empty.start()] + replacement + config_content[empty.end():]
|
|
||||||
write_utf8_bom(config_xml_path, new_content)
|
|
||||||
reg_result = 'added'
|
|
||||||
else:
|
|
||||||
close_same = f'</{child_tag}>'
|
|
||||||
last_same = config_content.rfind(close_same, block.start(), block.end())
|
|
||||||
if last_same != -1:
|
|
||||||
# After the last element of the same type (keeps them grouped).
|
|
||||||
insert_at = last_same + len(close_same)
|
|
||||||
new_content = (config_content[:insert_at]
|
|
||||||
+ f'{eol}\t\t\t{entry}'
|
|
||||||
+ config_content[insert_at:])
|
|
||||||
else:
|
|
||||||
# No element of this type yet: new line before </ChildObjects>,
|
|
||||||
# reusing the block's existing closing indent for </ChildObjects>.
|
|
||||||
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
|
|
||||||
new_content = (config_content[:close_at]
|
|
||||||
+ f'\t{entry}{eol}\t\t'
|
|
||||||
+ config_content[close_at:])
|
|
||||||
write_utf8_bom(config_xml_path, new_content)
|
|
||||||
reg_result = 'added'
|
|
||||||
else:
|
|
||||||
reg_result = 'no-config'
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# 18. Summary
|
# 18. Summary
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# role-compile v1.27 — Compile 1C role from JSON
|
# role-compile v1.28 — Compile 1C role from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -1120,88 +1120,83 @@ $enc = New-Object System.Text.UTF8Encoding($true)
|
|||||||
|
|
||||||
# --- 12. Register in Configuration.xml ---
|
# --- 12. Register in Configuration.xml ---
|
||||||
|
|
||||||
$configXmlPath = Join-Path $configDir "Configuration.xml"
|
# Регистрация объекта в <ChildObjects> родительского XML. Общая реализация: эталон —
|
||||||
$regResult = $null
|
# meta-compile, копия — role-compile. Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
# Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
|
||||||
|
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
|
||||||
|
|
||||||
if (Test-Path $configXmlPath) {
|
$doc = New-Object System.Xml.XmlDocument
|
||||||
$configDoc = New-Object System.Xml.XmlDocument
|
$doc.PreserveWhitespace = $true
|
||||||
$configDoc.PreserveWhitespace = $true
|
$doc.Load($ParentXmlPath)
|
||||||
$configDoc.Load($configXmlPath)
|
|
||||||
|
|
||||||
$nsMgr = New-Object System.Xml.XmlNamespaceManager($configDoc.NameTable)
|
$nsMgr = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||||
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
$nsMgr.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
|
||||||
$childObjects = $configDoc.SelectSingleNode("//md:Configuration/md:ChildObjects", $nsMgr)
|
$childObjects = $doc.SelectSingleNode("//md:$ParentTag/md:ChildObjects", $nsMgr)
|
||||||
if ($childObjects) {
|
if (-not $childObjects) { return "no-childobj" }
|
||||||
$existing = $childObjects.SelectNodes("md:Role", $nsMgr)
|
|
||||||
$alreadyExists = $false
|
|
||||||
foreach ($r in $existing) {
|
|
||||||
if ($r.InnerText -eq $roleName) {
|
|
||||||
$alreadyExists = $true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($alreadyExists) {
|
$existing = $childObjects.SelectNodes("md:$ChildTag", $nsMgr)
|
||||||
$regResult = "already"
|
foreach ($e in $existing) {
|
||||||
} else {
|
if ($e.InnerText -eq $ChildName) { return "already" }
|
||||||
$roleElem = $configDoc.CreateElement("Role", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
$roleElem.InnerText = $roleName
|
|
||||||
|
|
||||||
if ($existing.Count -gt 0) {
|
|
||||||
# Insert after last existing <Role>
|
|
||||||
$lastRole = $existing[$existing.Count - 1]
|
|
||||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
||||||
$childObjects.InsertAfter($newWs, $lastRole) | Out-Null
|
|
||||||
$childObjects.InsertAfter($roleElem, $newWs) | Out-Null
|
|
||||||
} else {
|
|
||||||
# No existing roles — insert before closing whitespace
|
|
||||||
$lastChild = $childObjects.LastChild
|
|
||||||
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
|
||||||
$newWs = $configDoc.CreateWhitespace("`n`t`t`t")
|
|
||||||
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
|
||||||
$childObjects.InsertBefore($roleElem, $lastChild) | Out-Null
|
|
||||||
} else {
|
|
||||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
|
||||||
$childObjects.AppendChild($roleElem) | Out-Null
|
|
||||||
$childObjects.AppendChild($configDoc.CreateWhitespace("`n`t`t")) | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save
|
|
||||||
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
|
||||||
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
||||||
$cfgSettings.Indent = $false
|
|
||||||
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
|
||||||
# Через MemoryStream, а не прямо в файл: нужен шаг пост-обработки строки.
|
|
||||||
$memStream = New-Object System.IO.MemoryStream
|
|
||||||
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
|
||||||
$configDoc.Save($writer)
|
|
||||||
$writer.Flush(); $writer.Close()
|
|
||||||
|
|
||||||
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
|
||||||
$memStream.Close()
|
|
||||||
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
|
||||||
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
|
||||||
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
|
||||||
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
|
||||||
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
|
||||||
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
|
||||||
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
|
||||||
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
|
||||||
$targetEol = if ((Test-Path -LiteralPath $configXmlPath) -and ([System.IO.File]::ReadAllText($configXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
|
||||||
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
|
||||||
[System.IO.File]::WriteAllText($configXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
|
||||||
|
|
||||||
$regResult = "added"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
$regResult = "no-childobj"
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
$regResult = "no-config"
|
$newElem = $doc.CreateElement($ChildTag, "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
$newElem.InnerText = $ChildName
|
||||||
|
|
||||||
|
if ($existing.Count -gt 0) {
|
||||||
|
# Insert after last existing element of same type
|
||||||
|
$lastElem = $existing[$existing.Count - 1]
|
||||||
|
$newWs = $doc.CreateWhitespace("`n`t`t`t")
|
||||||
|
$childObjects.InsertAfter($newWs, $lastElem) | Out-Null
|
||||||
|
$childObjects.InsertAfter($newElem, $newWs) | Out-Null
|
||||||
|
} else {
|
||||||
|
# No existing elements of this type — insert before closing whitespace.
|
||||||
|
# Самозакрытый <ChildObjects/> попадает сюда же: LastChild пуст, идёт ветка AppendChild.
|
||||||
|
$lastChild = $childObjects.LastChild
|
||||||
|
if ($lastChild.NodeType -eq [System.Xml.XmlNodeType]::Whitespace) {
|
||||||
|
$newWs = $doc.CreateWhitespace("`n`t`t`t")
|
||||||
|
$childObjects.InsertBefore($newWs, $lastChild) | Out-Null
|
||||||
|
$childObjects.InsertBefore($newElem, $lastChild) | Out-Null
|
||||||
|
} else {
|
||||||
|
$childObjects.AppendChild($doc.CreateWhitespace("`n`t`t`t")) | Out-Null
|
||||||
|
$childObjects.AppendChild($newElem) | Out-Null
|
||||||
|
$childObjects.AppendChild($doc.CreateWhitespace("`n`t`t")) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save. Пишем через MemoryStream, а не прямо в файл: нужен шаг пост-обработки
|
||||||
|
# строки — XmlWriter отдаёт `encoding="utf-8"` и `<a />`, Конфигуратор пишет
|
||||||
|
# `encoding="UTF-8"` и `<a/>`.
|
||||||
|
$cfgSettings = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$cfgSettings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$cfgSettings.Indent = $false
|
||||||
|
$cfgSettings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $cfgSettings)
|
||||||
|
$doc.Save($writer)
|
||||||
|
$writer.Flush(); $writer.Close()
|
||||||
|
|
||||||
|
$cfgText = [System.Text.Encoding]::UTF8.GetString($memStream.ToArray())
|
||||||
|
$memStream.Close()
|
||||||
|
if ($cfgText.Length -gt 0 -and $cfgText[0] -eq [char]0xFEFF) { $cfgText = $cfgText.Substring(1) }
|
||||||
|
$cfgText = $cfgText.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$cfgText = [regex]::Replace($cfgText, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ([System.IO.File]::ReadAllText($ParentXmlPath) -notmatch "`r`n") { "`n" } else { "`r`n" }
|
||||||
|
$cfgText = ($cfgText -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($ParentXmlPath, $cfgText, (New-Object System.Text.UTF8Encoding($true)))
|
||||||
|
|
||||||
|
return "added"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$configXmlPath = Join-Path $configDir "Configuration.xml"
|
||||||
|
$regResult = Register-InChildObjects $configXmlPath "Configuration" "Role" $roleName
|
||||||
|
|
||||||
# --- 13. Summary ---
|
# --- 13. Summary ---
|
||||||
|
|
||||||
Write-Host "[OK] Role '$roleName' compiled"
|
Write-Host "[OK] Role '$roleName' compiled"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# role-compile v1.27 — Compile 1C role from JSON
|
# role-compile v1.28 — Compile 1C role from JSON
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
@@ -7,6 +7,7 @@ import os
|
|||||||
import re
|
import re
|
||||||
import sys
|
import sys
|
||||||
import uuid
|
import uuid
|
||||||
|
import xml.etree.ElementTree as ET
|
||||||
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
|
|
||||||
@@ -993,6 +994,79 @@ def parse_object_entry(entry):
|
|||||||
return {'Name': obj_name, 'Rights': rights}
|
return {'Name': obj_name, 'Rights': rights}
|
||||||
|
|
||||||
|
|
||||||
|
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
|
||||||
|
"""Регистрация объекта в <ChildObjects> родительского XML.
|
||||||
|
|
||||||
|
Общая реализация: эталон — meta-compile, копия — role-compile.
|
||||||
|
Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(parent_xml_path):
|
||||||
|
return 'no-config'
|
||||||
|
|
||||||
|
# Read raw content, preserving BOM/EOL byte-for-byte (newline='' => no translation)
|
||||||
|
with open(parent_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
||||||
|
config_content = f.read()
|
||||||
|
|
||||||
|
ns = 'http://v8.1c.ru/8.3/MDClasses'
|
||||||
|
# ET is used ONLY read-only here: to locate ChildObjects and detect a duplicate.
|
||||||
|
# We deliberately do NOT re-serialize Configuration.xml with ElementTree.write():
|
||||||
|
# it drops every xmlns declaration used only inside attribute VALUES (e.g.
|
||||||
|
# xsi:type="app:ApplicationUsePurpose" in UsePurposes) because ET never sees such
|
||||||
|
# prefixes in element/attribute names. The dropped declaration makes XDTO read the
|
||||||
|
# value as anyType and Designer refuses to load the file (issue #38). Registration is
|
||||||
|
# therefore done by raw-text insertion, preserving BOM, EOL and all namespaces
|
||||||
|
# byte-for-byte (same approach as subsystem-compile).
|
||||||
|
tree = ET.parse(parent_xml_path)
|
||||||
|
root = tree.getroot()
|
||||||
|
|
||||||
|
child_objects = root.find(f'{{{ns}}}{parent_tag}/{{{ns}}}ChildObjects')
|
||||||
|
if child_objects is None:
|
||||||
|
# Try direct path
|
||||||
|
parent_elem = root.find(f'{{{ns}}}{parent_tag}')
|
||||||
|
if parent_elem is not None:
|
||||||
|
child_objects = parent_elem.find(f'{{{ns}}}ChildObjects')
|
||||||
|
|
||||||
|
if child_objects is None:
|
||||||
|
return 'no-childobj'
|
||||||
|
|
||||||
|
existing = child_objects.findall(f'{{{ns}}}{child_tag}')
|
||||||
|
if any((e.text or '').strip() == child_name for e in existing):
|
||||||
|
return 'already'
|
||||||
|
|
||||||
|
eol = '\r\n' if '\r\n' in config_content else '\n'
|
||||||
|
entry = f'<{child_tag}>{esc_xml_text(child_name)}</{child_tag}>'
|
||||||
|
|
||||||
|
block = re.search(r'<ChildObjects\s*>.*?</ChildObjects>', config_content, re.S)
|
||||||
|
if block is None:
|
||||||
|
# Empty self-closing <ChildObjects/> => open it with the first entry.
|
||||||
|
empty = re.search(r'<ChildObjects\s*/>', config_content)
|
||||||
|
if empty is None:
|
||||||
|
return 'no-childobj'
|
||||||
|
replacement = f'<ChildObjects>{eol}\t\t\t{entry}{eol}\t\t</ChildObjects>'
|
||||||
|
new_content = config_content[:empty.start()] + replacement + config_content[empty.end():]
|
||||||
|
write_utf8_bom(parent_xml_path, new_content)
|
||||||
|
return 'added'
|
||||||
|
|
||||||
|
close_same = f'</{child_tag}>'
|
||||||
|
last_same = config_content.rfind(close_same, block.start(), block.end())
|
||||||
|
if last_same != -1:
|
||||||
|
# After the last element of the same type (keeps them grouped).
|
||||||
|
insert_at = last_same + len(close_same)
|
||||||
|
new_content = (config_content[:insert_at]
|
||||||
|
+ f'{eol}\t\t\t{entry}'
|
||||||
|
+ config_content[insert_at:])
|
||||||
|
else:
|
||||||
|
# No element of this type yet: new line before </ChildObjects>,
|
||||||
|
# reusing the block's existing closing indent for </ChildObjects>.
|
||||||
|
close_at = config_content.rfind('</ChildObjects>', block.start(), block.end())
|
||||||
|
new_content = (config_content[:close_at]
|
||||||
|
+ f'\t{entry}{eol}\t\t'
|
||||||
|
+ config_content[close_at:])
|
||||||
|
write_utf8_bom(parent_xml_path, new_content)
|
||||||
|
return 'added'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -1163,59 +1237,7 @@ def main():
|
|||||||
|
|
||||||
# --- 7. Register in Configuration.xml ---
|
# --- 7. Register in Configuration.xml ---
|
||||||
config_xml_path = os.path.join(config_dir, 'Configuration.xml')
|
config_xml_path = os.path.join(config_dir, 'Configuration.xml')
|
||||||
reg_result = None
|
reg_result = register_in_childobjects(config_xml_path, 'Configuration', 'Role', role_name)
|
||||||
|
|
||||||
if os.path.exists(config_xml_path):
|
|
||||||
# newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
|
|
||||||
# в LF при чтении и файл будет переписан в LF.
|
|
||||||
with open(config_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
|
||||||
raw_text = f.read()
|
|
||||||
|
|
||||||
eol = detect_eol(raw_text)
|
|
||||||
|
|
||||||
# Check if already registered
|
|
||||||
if f'<Role>{role_name}</Role>' in raw_text:
|
|
||||||
reg_result = 'already'
|
|
||||||
else:
|
|
||||||
new_role_tag = f'<Role>{role_name}</Role>'
|
|
||||||
block = re.search(r'<ChildObjects\s*>.*?</ChildObjects>', raw_text, re.S)
|
|
||||||
|
|
||||||
if block is None:
|
|
||||||
# Самозакрытый <ChildObjects/> раскрываем первой записью; если тега нет вовсе —
|
|
||||||
# регистрировать некуда, и файл трогать нельзя. Раньше обе эти ветки писали
|
|
||||||
# исход 'added', не изменив ни байта: модель считала роль включённой в состав,
|
|
||||||
# а её там не было.
|
|
||||||
empty = re.search(r'<ChildObjects\s*/>', raw_text)
|
|
||||||
if empty is None:
|
|
||||||
reg_result = 'no-childobj'
|
|
||||||
else:
|
|
||||||
replacement = ('<ChildObjects>' + eol + f'\t\t\t{new_role_tag}'
|
|
||||||
+ eol + '\t\t</ChildObjects>')
|
|
||||||
raw_text = raw_text[:empty.start()] + replacement + raw_text[empty.end():]
|
|
||||||
write_utf8_bom(config_xml_path, raw_text)
|
|
||||||
reg_result = 'added'
|
|
||||||
else:
|
|
||||||
role_pattern = re.compile(r'(<Role>[^<]*</Role>)')
|
|
||||||
matches = list(role_pattern.finditer(raw_text))
|
|
||||||
|
|
||||||
if matches:
|
|
||||||
# Insert after last existing <Role>
|
|
||||||
last_match = matches[-1]
|
|
||||||
insert_pos = last_match.end()
|
|
||||||
raw_text = raw_text[:insert_pos] + eol + f'\t\t\t{new_role_tag}' + raw_text[insert_pos:]
|
|
||||||
else:
|
|
||||||
# No existing roles — insert before </ChildObjects>
|
|
||||||
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
|
|
||||||
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
|
|
||||||
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
|
|
||||||
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
|
|
||||||
lambda m: m.group(1) + '\t' + new_role_tag + eol + m.group(1) + '</ChildObjects>',
|
|
||||||
raw_text, count=1)
|
|
||||||
|
|
||||||
write_utf8_bom(config_xml_path, raw_text)
|
|
||||||
reg_result = 'added'
|
|
||||||
else:
|
|
||||||
reg_result = 'no-config'
|
|
||||||
|
|
||||||
# --- 8. Summary ---
|
# --- 8. Summary ---
|
||||||
print(f"[OK] Role '{role_name}' compiled")
|
print(f"[OK] Role '{role_name}' compiled")
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# subsystem-compile v1.25 — Create 1C subsystem from JSON definition (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
# subsystem-compile v1.26 — Create 1C subsystem from JSON definition
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
param(
|
param(
|
||||||
[string]$DefinitionFile,
|
[string]$DefinitionFile,
|
||||||
@@ -600,9 +600,106 @@ if ($children.Count -gt 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- 6. Register in parent ---
|
# --- 6. Register in parent ---
|
||||||
|
|
||||||
|
# Регистрация объекта в <ChildObjects> родительского XML. Вариант семьи: отступ берётся
|
||||||
|
# из самого документа, а запись дописывается в конец блока. Отличие от эталона
|
||||||
|
# (meta-compile) осознанное: родителем бывает вложенный Subsystem.xml произвольной
|
||||||
|
# глубины, где фиксированные три табуляции неверны, а группировать записи по типу
|
||||||
|
# внутри подсистемы нечего — потомок там всегда один и тот же.
|
||||||
|
# Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
# Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
function Register-InChildObjects([string]$ParentXmlPath, [string]$ParentTag, [string]$ChildTag, [string]$ChildName) {
|
||||||
|
if (-not (Test-Path $ParentXmlPath)) { return "no-config" }
|
||||||
|
|
||||||
|
$doc = New-Object System.Xml.XmlDocument
|
||||||
|
$doc.PreserveWhitespace = $true
|
||||||
|
$doc.Load($ParentXmlPath)
|
||||||
|
|
||||||
|
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
||||||
|
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
|
||||||
|
$childObjects = $doc.SelectSingleNode("//md:$ParentTag/md:ChildObjects", $ns)
|
||||||
|
if (-not $childObjects) { return "no-childobj" }
|
||||||
|
|
||||||
|
# Check for self-closing tag
|
||||||
|
$isSelfClosing = (-not $childObjects.HasChildNodes) -or ($childObjects.IsEmpty)
|
||||||
|
|
||||||
|
# Check if already registered
|
||||||
|
foreach ($child in $childObjects.ChildNodes) {
|
||||||
|
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq $ChildTag -and $child.InnerText -eq $ChildName) {
|
||||||
|
return "already"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$newEl = $doc.CreateElement($ChildTag, "http://v8.1c.ru/8.3/MDClasses")
|
||||||
|
$newEl.InnerText = $ChildName
|
||||||
|
|
||||||
|
if ($isSelfClosing) {
|
||||||
|
# Expand self-closing tag
|
||||||
|
$parentIndent = ""
|
||||||
|
$prev = $childObjects.PreviousSibling
|
||||||
|
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
|
||||||
|
if ($prev.Value -match '(\t+)$') { $parentIndent = $Matches[1] }
|
||||||
|
}
|
||||||
|
$childIndent = "$parentIndent`t"
|
||||||
|
$ws1 = $doc.CreateWhitespace("`r`n$childIndent")
|
||||||
|
$ws2 = $doc.CreateWhitespace("`r`n$parentIndent")
|
||||||
|
$childObjects.AppendChild($ws1) | Out-Null
|
||||||
|
$childObjects.AppendChild($newEl) | Out-Null
|
||||||
|
$childObjects.AppendChild($ws2) | Out-Null
|
||||||
|
} else {
|
||||||
|
# Insert before trailing whitespace
|
||||||
|
$childIndent = "`t`t`t"
|
||||||
|
foreach ($child in $childObjects.ChildNodes) {
|
||||||
|
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
|
||||||
|
if ($child.Value -match '^\r?\n(\t+)') { $childIndent = $Matches[1]; break }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$trailing = $childObjects.LastChild
|
||||||
|
$ws = $doc.CreateWhitespace("`r`n$childIndent")
|
||||||
|
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
||||||
|
$childObjects.InsertBefore($ws, $trailing) | Out-Null
|
||||||
|
$childObjects.InsertBefore($newEl, $trailing) | Out-Null
|
||||||
|
} else {
|
||||||
|
$childObjects.AppendChild($ws) | Out-Null
|
||||||
|
$childObjects.AppendChild($newEl) | Out-Null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save parent XML
|
||||||
|
$settings = New-Object System.Xml.XmlWriterSettings
|
||||||
|
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
||||||
|
$settings.Indent = $false
|
||||||
|
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
||||||
|
|
||||||
|
$memStream = New-Object System.IO.MemoryStream
|
||||||
|
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
||||||
|
$doc.Save($writer)
|
||||||
|
$writer.Flush(); $writer.Close()
|
||||||
|
|
||||||
|
$bytes = $memStream.ToArray()
|
||||||
|
$memStream.Close()
|
||||||
|
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||||
|
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
||||||
|
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
||||||
|
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
||||||
|
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
||||||
|
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
||||||
|
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
||||||
|
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
||||||
|
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
||||||
|
$targetEol = if ((Test-Path -LiteralPath $ParentXmlPath) -and ([System.IO.File]::ReadAllText($ParentXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
||||||
|
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
||||||
|
[System.IO.File]::WriteAllText($ParentXmlPath, $text, (New-Object System.Text.UTF8Encoding($true)))
|
||||||
|
|
||||||
|
return "added"
|
||||||
|
}
|
||||||
|
|
||||||
$parentXmlPath = $null
|
$parentXmlPath = $null
|
||||||
|
$parentTag = "Configuration"
|
||||||
if ($Parent) {
|
if ($Parent) {
|
||||||
$parentXmlPath = $Parent
|
$parentXmlPath = $Parent
|
||||||
|
$parentTag = "Subsystem"
|
||||||
} else {
|
} else {
|
||||||
$configXml = Join-Path $OutputDir "Configuration.xml"
|
$configXml = Join-Path $OutputDir "Configuration.xml"
|
||||||
if (Test-Path $configXml) {
|
if (Test-Path $configXml) {
|
||||||
@@ -610,103 +707,12 @@ if ($Parent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($parentXmlPath -and (Test-Path $parentXmlPath)) {
|
if ($parentXmlPath) {
|
||||||
$doc = New-Object System.Xml.XmlDocument
|
switch (Register-InChildObjects $parentXmlPath $parentTag "Subsystem" $objName) {
|
||||||
$doc.PreserveWhitespace = $true
|
"added" { Write-Host "[OK] Registered in: $parentXmlPath" }
|
||||||
$doc.Load($parentXmlPath)
|
"already" { Write-Host "[SKIP] Already registered in: $parentXmlPath" }
|
||||||
|
"no-childobj" { Write-Host "[WARN] ChildObjects not found in: $parentXmlPath" }
|
||||||
$ns = New-Object System.Xml.XmlNamespaceManager($doc.NameTable)
|
"no-config" { Write-Host "[INFO] No parent XML to register in" }
|
||||||
$ns.AddNamespace("md", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
|
|
||||||
# Find ChildObjects
|
|
||||||
$childObjects = $null
|
|
||||||
if ($Parent) {
|
|
||||||
$childObjects = $doc.SelectSingleNode("//md:Subsystem/md:ChildObjects", $ns)
|
|
||||||
} else {
|
|
||||||
$childObjects = $doc.SelectSingleNode("//md:Configuration/md:ChildObjects", $ns)
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($childObjects) {
|
|
||||||
# Check for self-closing tag
|
|
||||||
$isSelfClosing = (-not $childObjects.HasChildNodes) -or ($childObjects.IsEmpty)
|
|
||||||
|
|
||||||
# Check if already registered
|
|
||||||
$alreadyExists = $false
|
|
||||||
foreach ($child in $childObjects.ChildNodes) {
|
|
||||||
if ($child.NodeType -eq 'Element' -and $child.LocalName -eq "Subsystem" -and $child.InnerText -eq $objName) {
|
|
||||||
$alreadyExists = $true
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $alreadyExists) {
|
|
||||||
$newEl = $doc.CreateElement("Subsystem", "http://v8.1c.ru/8.3/MDClasses")
|
|
||||||
$newEl.InnerText = $objName
|
|
||||||
|
|
||||||
if ($isSelfClosing) {
|
|
||||||
# Expand self-closing tag
|
|
||||||
$parentIndent = ""
|
|
||||||
$prev = $childObjects.PreviousSibling
|
|
||||||
if ($prev -and ($prev.NodeType -eq 'Whitespace' -or $prev.NodeType -eq 'SignificantWhitespace')) {
|
|
||||||
if ($prev.Value -match '(\t+)$') { $parentIndent = $Matches[1] }
|
|
||||||
}
|
|
||||||
$childIndent = "$parentIndent`t"
|
|
||||||
$ws1 = $doc.CreateWhitespace("`r`n$childIndent")
|
|
||||||
$ws2 = $doc.CreateWhitespace("`r`n$parentIndent")
|
|
||||||
$childObjects.AppendChild($ws1) | Out-Null
|
|
||||||
$childObjects.AppendChild($newEl) | Out-Null
|
|
||||||
$childObjects.AppendChild($ws2) | Out-Null
|
|
||||||
} else {
|
|
||||||
# Insert before trailing whitespace
|
|
||||||
$childIndent = "`t`t`t"
|
|
||||||
foreach ($child in $childObjects.ChildNodes) {
|
|
||||||
if ($child.NodeType -eq 'Whitespace' -or $child.NodeType -eq 'SignificantWhitespace') {
|
|
||||||
if ($child.Value -match '^\r?\n(\t+)') { $childIndent = $Matches[1]; break }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
$trailing = $childObjects.LastChild
|
|
||||||
$ws = $doc.CreateWhitespace("`r`n$childIndent")
|
|
||||||
if ($trailing -and ($trailing.NodeType -eq 'Whitespace' -or $trailing.NodeType -eq 'SignificantWhitespace')) {
|
|
||||||
$childObjects.InsertBefore($ws, $trailing) | Out-Null
|
|
||||||
$childObjects.InsertBefore($newEl, $trailing) | Out-Null
|
|
||||||
} else {
|
|
||||||
$childObjects.AppendChild($ws) | Out-Null
|
|
||||||
$childObjects.AppendChild($newEl) | Out-Null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save parent XML
|
|
||||||
$settings = New-Object System.Xml.XmlWriterSettings
|
|
||||||
$settings.Encoding = New-Object System.Text.UTF8Encoding($true)
|
|
||||||
$settings.Indent = $false
|
|
||||||
$settings.NewLineHandling = [System.Xml.NewLineHandling]::None
|
|
||||||
|
|
||||||
$memStream = New-Object System.IO.MemoryStream
|
|
||||||
$writer = [System.Xml.XmlWriter]::Create($memStream, $settings)
|
|
||||||
$doc.Save($writer)
|
|
||||||
$writer.Flush(); $writer.Close()
|
|
||||||
|
|
||||||
$bytes = $memStream.ToArray()
|
|
||||||
$memStream.Close()
|
|
||||||
$text = [System.Text.Encoding]::UTF8.GetString($bytes)
|
|
||||||
if ($text.Length -gt 0 -and $text[0] -eq [char]0xFEFF) { $text = $text.Substring(1) }
|
|
||||||
$text = $text.Replace('encoding="utf-8"', 'encoding="UTF-8"')
|
|
||||||
# Пустой элемент: XmlWriter отдаёт `<a />`, Конфигуратор пишет `<a/>`. Внутри
|
|
||||||
# CDATA/комментария ` />` может быть содержимым (там `>` не экранируется),
|
|
||||||
# поэтому они идут первыми ветками альтернации и возвращаются как есть.
|
|
||||||
$text = [regex]::Replace($text, '(?s)<!\[CDATA\[.*?\]\]>|<!--.*?-->|(?<=\S) />', { param($m) if ($m.Value -eq ' />') { '/>' } else { $m.Value } })
|
|
||||||
# Целевой перевод строки: стиль файла-назначения — правка наследует его (#44/#46/#47),
|
|
||||||
# новый файл получает канон выгрузки CRLF. Зеркало _detect_xml_style в py-порту.
|
|
||||||
$targetEol = if ((Test-Path -LiteralPath $parentXmlPath) -and ([System.IO.File]::ReadAllText($parentXmlPath) -notmatch "`r`n")) { "`n" } else { "`r`n" }
|
|
||||||
$text = ($text -replace "`r`n", "`n") -replace "`n", $targetEol
|
|
||||||
[System.IO.File]::WriteAllText($parentXmlPath, $text, $utf8Bom)
|
|
||||||
|
|
||||||
Write-Host "[OK] Registered in: $parentXmlPath"
|
|
||||||
} else {
|
|
||||||
Write-Host "[SKIP] Already registered in: $parentXmlPath"
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "[WARN] ChildObjects not found in: $parentXmlPath"
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[INFO] No parent XML to register in"
|
Write-Host "[INFO] No parent XML to register in"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# subsystem-compile v1.25 — Create 1C subsystem from JSON definition (+тип Bot; cfe-diff/cfe-borrow: недостающие типы)
|
# subsystem-compile v1.26 — Create 1C subsystem from JSON definition
|
||||||
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
|
||||||
import argparse
|
import argparse
|
||||||
import json
|
import json
|
||||||
@@ -388,6 +388,60 @@ def write_child_subsystem_stub(child_path, child_name, format_version):
|
|||||||
write_utf8_bom(child_path, '\r\n'.join(lines))
|
write_utf8_bom(child_path, '\r\n'.join(lines))
|
||||||
|
|
||||||
|
|
||||||
|
def register_in_childobjects(parent_xml_path, parent_tag, child_tag, child_name):
|
||||||
|
"""Регистрация объекта в <ChildObjects> родительского XML.
|
||||||
|
|
||||||
|
Вариант семьи: отступ берётся из самого документа, а запись дописывается в конец
|
||||||
|
блока. Отличие от эталона (meta-compile) осознанное: родителем бывает вложенный
|
||||||
|
Subsystem.xml произвольной глубины, где фиксированные три табуляции неверны,
|
||||||
|
а группировать записи по типу внутри подсистемы нечего — потомок там всегда один.
|
||||||
|
Реестр семьи: tests/skills/check-inline-drift.mjs.
|
||||||
|
Возвращает исход: added | already | no-childobj | no-config.
|
||||||
|
"""
|
||||||
|
if not os.path.exists(parent_xml_path):
|
||||||
|
return 'no-config'
|
||||||
|
|
||||||
|
# newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
|
||||||
|
# в LF при чтении и файл будет переписан в LF.
|
||||||
|
with open(parent_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
||||||
|
raw_text = f.read()
|
||||||
|
|
||||||
|
eol = detect_eol(raw_text)
|
||||||
|
doc = ET.ElementTree(ET.fromstring(raw_text))
|
||||||
|
root = doc.getroot()
|
||||||
|
md_ns = 'http://v8.1c.ru/8.3/MDClasses'
|
||||||
|
|
||||||
|
# Find ChildObjects
|
||||||
|
child_objects = None
|
||||||
|
for holder in root.iter(f'{{{md_ns}}}{parent_tag}'):
|
||||||
|
child_objects = holder.find(f'{{{md_ns}}}ChildObjects')
|
||||||
|
break
|
||||||
|
|
||||||
|
if child_objects is None:
|
||||||
|
return 'no-childobj'
|
||||||
|
|
||||||
|
for child in child_objects:
|
||||||
|
if child.tag == f'{{{md_ns}}}{child_tag}' and child.text == child_name:
|
||||||
|
return 'already'
|
||||||
|
|
||||||
|
# Правку ведём по сырому тексту, а не сериализацией ET: она не сохраняет отступы
|
||||||
|
# и теряет xmlns, объявленные только внутри значений атрибутов (#38).
|
||||||
|
entry = f'<{child_tag}>{esc_xml_text(child_name)}</{child_tag}>'
|
||||||
|
if '<ChildObjects/>' in raw_text:
|
||||||
|
replacement = '<ChildObjects>' + eol + f'\t\t\t{entry}' + eol + '\t\t</ChildObjects>'
|
||||||
|
raw_text = raw_text.replace('<ChildObjects/>', replacement, 1)
|
||||||
|
elif '</ChildObjects>' in raw_text:
|
||||||
|
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
|
||||||
|
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
|
||||||
|
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
|
||||||
|
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
|
||||||
|
lambda m: m.group(1) + '\t' + entry + eol + m.group(1) + '</ChildObjects>',
|
||||||
|
raw_text, count=1)
|
||||||
|
|
||||||
|
write_utf8_bom(parent_xml_path, raw_text)
|
||||||
|
return 'added'
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
sys.stdout.reconfigure(encoding="utf-8")
|
sys.stdout.reconfigure(encoding="utf-8")
|
||||||
sys.stderr.reconfigure(encoding="utf-8")
|
sys.stderr.reconfigure(encoding="utf-8")
|
||||||
@@ -656,71 +710,25 @@ def main():
|
|||||||
|
|
||||||
# --- 5. Register in parent ---
|
# --- 5. Register in parent ---
|
||||||
parent_xml_path = None
|
parent_xml_path = None
|
||||||
|
parent_tag = 'Configuration'
|
||||||
if parent:
|
if parent:
|
||||||
parent_xml_path = parent
|
parent_xml_path = parent
|
||||||
|
parent_tag = 'Subsystem'
|
||||||
else:
|
else:
|
||||||
config_xml = os.path.join(output_dir, 'Configuration.xml')
|
config_xml = os.path.join(output_dir, 'Configuration.xml')
|
||||||
if os.path.exists(config_xml):
|
if os.path.exists(config_xml):
|
||||||
parent_xml_path = config_xml
|
parent_xml_path = config_xml
|
||||||
|
|
||||||
if parent_xml_path and os.path.exists(parent_xml_path):
|
if parent_xml_path:
|
||||||
# newline='' => без трансляции переводов строк: иначе CRLF молча схлопнется
|
outcome = register_in_childobjects(parent_xml_path, parent_tag, 'Subsystem', obj_name)
|
||||||
# в LF при чтении и файл будет переписан в LF.
|
if outcome == 'added':
|
||||||
with open(parent_xml_path, 'r', encoding='utf-8-sig', newline='') as f:
|
print(f"[OK] Registered in: {parent_xml_path}")
|
||||||
raw_text = f.read()
|
elif outcome == 'already':
|
||||||
|
print(f"[SKIP] Already registered in: {parent_xml_path}")
|
||||||
eol = detect_eol(raw_text)
|
elif outcome == 'no-childobj':
|
||||||
doc = ET.ElementTree(ET.fromstring(raw_text))
|
|
||||||
root = doc.getroot()
|
|
||||||
md_ns = 'http://v8.1c.ru/8.3/MDClasses'
|
|
||||||
|
|
||||||
# Find ChildObjects
|
|
||||||
child_objects = None
|
|
||||||
if parent:
|
|
||||||
for sub in root.iter(f'{{{md_ns}}}Subsystem'):
|
|
||||||
child_objects = sub.find(f'{{{md_ns}}}ChildObjects')
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
for cfg in root.iter(f'{{{md_ns}}}Configuration'):
|
|
||||||
child_objects = cfg.find(f'{{{md_ns}}}ChildObjects')
|
|
||||||
break
|
|
||||||
|
|
||||||
if child_objects is not None:
|
|
||||||
# Check if already registered
|
|
||||||
already_exists = False
|
|
||||||
for child in child_objects:
|
|
||||||
if child.tag == f'{{{md_ns}}}Subsystem' and child.text == obj_name:
|
|
||||||
already_exists = True
|
|
||||||
break
|
|
||||||
|
|
||||||
if not already_exists:
|
|
||||||
new_el = ET.SubElement(child_objects, f'{{{md_ns}}}Subsystem')
|
|
||||||
new_el.text = obj_name
|
|
||||||
|
|
||||||
# Re-serialize with whitespace preservation via raw text manipulation instead
|
|
||||||
# Since ElementTree doesn't preserve whitespace well, use regex-based insertion
|
|
||||||
# Find </ChildObjects> or <ChildObjects/> and inject
|
|
||||||
pass # Fall through to raw text approach below
|
|
||||||
|
|
||||||
if not already_exists:
|
|
||||||
# Use raw text manipulation to preserve formatting
|
|
||||||
if '<ChildObjects/>' in raw_text:
|
|
||||||
replacement = ('<ChildObjects>' + eol + f'\t\t\t<Subsystem>{esc_xml_text(obj_name)}</Subsystem>' + eol + '\t\t</ChildObjects>')
|
|
||||||
raw_text = raw_text.replace('<ChildObjects/>', replacement, 1)
|
|
||||||
elif '</ChildObjects>' in raw_text:
|
|
||||||
# Отступ вставки берём у закрывающего тега +1 уровень: подстановка
|
|
||||||
# по голому '</ChildObjects>' удваивала бы уже присутствующий отступ
|
|
||||||
# строки (получалось 5 табов вместо 3 — PS-порт через DOM даёт 3).
|
|
||||||
raw_text = re.sub(r'([ \t]*)</ChildObjects>',
|
|
||||||
lambda m: m.group(1) + '\t' + f'<Subsystem>{esc_xml_text(obj_name)}</Subsystem>' + eol + m.group(1) + '</ChildObjects>',
|
|
||||||
raw_text, count=1)
|
|
||||||
|
|
||||||
write_utf8_bom(parent_xml_path, raw_text)
|
|
||||||
print(f"[OK] Registered in: {parent_xml_path}")
|
|
||||||
else:
|
|
||||||
print(f"[SKIP] Already registered in: {parent_xml_path}")
|
|
||||||
else:
|
|
||||||
print(f"[WARN] ChildObjects not found in: {parent_xml_path}")
|
print(f"[WARN] ChildObjects not found in: {parent_xml_path}")
|
||||||
|
else:
|
||||||
|
print("[INFO] No parent XML to register in")
|
||||||
else:
|
else:
|
||||||
print("[INFO] No parent XML to register in")
|
print("[INFO] No parent XML to register in")
|
||||||
|
|
||||||
|
|||||||
@@ -383,6 +383,16 @@ const FAMILIES = [
|
|||||||
{ id: 'legacy', authority: 'meta-decompile', consumers: [],
|
{ id: 'legacy', authority: 'meta-decompile', consumers: [],
|
||||||
why: 'ранний вариант со своим Quote-Json и без inline-попытки; сведение меняет вывод meta-decompile' }],
|
why: 'ранний вариант со своим Quote-Json и без inline-попытки; сведение меняет вывод meta-decompile' }],
|
||||||
},
|
},
|
||||||
|
// ─── Регистрация объекта в <ChildObjects> файла конфигурации ─────────────
|
||||||
|
{
|
||||||
|
name: 'ChildObjects: регистрация объекта в составе',
|
||||||
|
py: 'register_in_childobjects', ps1: 'Register-InChildObjects',
|
||||||
|
variants: [
|
||||||
|
{ id: 'grouped', authority: 'meta-compile', consumers: ['role-compile'] },
|
||||||
|
{ id: 'nested-parent', authority: 'subsystem-compile', consumers: [],
|
||||||
|
why: 'родителем бывает вложенный Subsystem.xml произвольной глубины: отступ берётся из документа, а запись дописывается в конец блока — фиксированные три табуляции там неверны, и группировать по типу нечего' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user